mirror of
https://github.com/zammad/zammad
synced 2026-05-24 09:48:36 +00:00
118 lines
3.3 KiB
Vue
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>
|