zammad/app/frontend/apps/desktop/components/CommonSectionCollapse/CommonSectionCollapse.vue
2026-01-02 15:41:09 +02:00

118 lines
3.3 KiB
Vue

<!-- Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/ -->
<script setup lang="ts">
import { computed, watch } from 'vue'
import CollapseButton from '#desktop/components/CollapseButton/CollapseButton.vue'
import { useCollapseHandler } from '#desktop/components/CollapseButton/useCollapseHandler.ts'
import { useTransitionCollapse } from '#desktop/composables/useTransitionCollapse.ts'
export interface Props {
id: string
title?: string
size?: 'small' | 'large'
noCollapse?: boolean
noNegativeMargin?: boolean
noHeader?: boolean
scrollable?: boolean
containerClass?: string
}
const props = withDefaults(defineProps<Props>(), {
size: 'small',
})
const modelValue = defineModel<boolean>({
default: false,
})
const emit = defineEmits<{
collapse: [boolean]
expand: [boolean]
}>()
const headerId = computed(() => `${props.id}-header`)
const { toggleCollapse, isCollapsed } = useCollapseHandler(emit)
const { collapseDuration, collapseEnter, collapseAfterEnter, collapseLeave } =
useTransitionCollapse()
watch(
modelValue,
(newValue) => {
if (isCollapsed.value === newValue) return
isCollapsed.value = newValue
},
{
immediate: true,
},
)
watch(
isCollapsed,
(newValue) => {
if (modelValue.value === newValue) return
modelValue.value = newValue
},
{
immediate: true,
},
)
</script>
<template>
<!-- eslint-disable vuejs-accessibility/no-static-element-interactions-->
<div class="flex flex-col gap-1" :class="{ 'overflow-y-auto outline-none': scrollable }">
<header
v-if="!noHeader"
:id="headerId"
class="group flex cursor-default items-center justify-between text-stone-200 group-focus-within:focus-visible:outline-1 has-focus-visible:outline-1 has-focus-visible:outline-offset-1 has-focus-visible:outline-blue-800 dark:text-neutral-500"
:class="{
'cursor-pointer rounded-md hover:bg-blue-600 hover:text-black dark:hover:bg-blue-900 hover:dark:text-white':
!noCollapse,
'px-1 py-0.5': size === 'small',
'-mx-1': size === 'small' && !noNegativeMargin,
'px-2 py-2.5': size === 'large',
'-mx-2': size === 'large' && !noNegativeMargin,
}"
@click="!noCollapse && toggleCollapse()"
@keydown.enter="!noCollapse && toggleCollapse()"
>
<slot name="title" :title="title" :size="size">
<CommonLabel class="grow text-current! select-none" :size="size" tag="h3">
{{ $t(title) }}
</CommonLabel>
</slot>
<CollapseButton
v-if="!noCollapse"
:collapsed="isCollapsed"
:owner-id="id"
no-padded
class="group-hover:text-black! group-hover:opacity-100 focus-visible:text-black dark:group-hover:text-white!"
:class="{ 'opacity-100': isCollapsed }"
orientation="vertical"
@keydown.enter="toggleCollapse()"
/>
</header>
<Transition
name="collapse"
:duration="collapseDuration"
@enter="collapseEnter"
@after-enter="collapseAfterEnter"
@leave="collapseLeave"
>
<div
v-show="!isCollapsed || noHeader"
:id="id"
:data-test-id="id"
:class="[containerClass, { 'overflow-y-auto outline-none': scrollable }]"
>
<slot :header-id="headerId" />
</div>
</Transition>
</div>
</template>