LayoutSidebar.vue 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { useActiveElement } from '@vueuse/core'
  4. import { ref, watch } from 'vue'
  5. import CollapseButton from '#desktop/components/CollapseButton/CollapseButton.vue'
  6. import { useCollapseHandler } from '#desktop/components/CollapseButton/composables/useCollapseHandler.ts'
  7. import CommonButton from '#desktop/components/CommonButton/CommonButton.vue'
  8. import { useResizeWidthHandle } from '#desktop/components/ResizeHandle/composables/useResizeWidthHandle.ts'
  9. import ResizeHandle from '#desktop/components/ResizeHandle/ResizeHandle.vue'
  10. interface Props {
  11. name: string
  12. /**
  13. @property currentWidth
  14. @property minWidth
  15. @property maxWidth
  16. - used for accessibility
  17. / */
  18. currentWidth?: number
  19. minWidth?: number
  20. maxWidth?: number
  21. collapsible?: boolean
  22. iconCollapsed?: string
  23. resizable?: boolean
  24. id: string
  25. }
  26. const props = defineProps<Props>()
  27. const emit = defineEmits<{
  28. 'resize-horizontal': [number]
  29. 'resize-horizontal-start': []
  30. 'resize-horizontal-end': []
  31. 'reset-width': []
  32. collapse: [boolean]
  33. expand: [boolean]
  34. }>()
  35. const { toggleCollapse, isCollapsed } = useCollapseHandler(emit, {
  36. storageKey: `${props.name}-sidebar-collapsed`,
  37. })
  38. // a11y keyboard navigation
  39. const resizeHandleComponent = ref<InstanceType<typeof ResizeHandle>>()
  40. const activeElement = useActiveElement()
  41. const handleKeyStroke = (e: KeyboardEvent, adjustment: number) => {
  42. if (
  43. !props.currentWidth ||
  44. activeElement.value !== resizeHandleComponent.value?.$el
  45. )
  46. return
  47. e.preventDefault()
  48. const newWidth = props.currentWidth + adjustment
  49. if (
  50. props.maxWidth &&
  51. props.minWidth &&
  52. (newWidth >= props.maxWidth || newWidth <= props.minWidth)
  53. )
  54. return
  55. emit('resize-horizontal', newWidth)
  56. }
  57. const { startResizing, isResizingHorizontal } = useResizeWidthHandle(
  58. (positionX) => emit('resize-horizontal', positionX),
  59. resizeHandleComponent,
  60. handleKeyStroke,
  61. )
  62. watch(isResizingHorizontal, (isResizing) => {
  63. if (isResizing) {
  64. emit('resize-horizontal-start')
  65. } else {
  66. emit('resize-horizontal-end')
  67. }
  68. })
  69. </script>
  70. <template>
  71. <aside
  72. :id="props.id"
  73. class="group/sidebar -:bg-neutral-950 relative flex max-h-screen flex-col border-e border-neutral-100 dark:border-gray-900"
  74. :class="{
  75. 'py-3': isCollapsed,
  76. }"
  77. >
  78. <CollapseButton
  79. v-if="collapsible"
  80. :is-collapsed="isCollapsed"
  81. :owner-id="id"
  82. group="sidebar"
  83. class="absolute top-[49px] z-20 ltr:right-0 ltr:translate-x-1/2 rtl:left-0 rtl:-translate-x-1/2"
  84. @toggle-collapse="toggleCollapse"
  85. />
  86. <ResizeHandle
  87. v-if="resizable && !isCollapsed"
  88. ref="resizeHandleComponent"
  89. class="absolute top-1/2 -translate-y-1/2 ltr:right-0 rtl:left-0"
  90. :aria-label="$t('Resize sidebar')"
  91. :aria-valuenow="currentWidth"
  92. :aria-valuemax="maxWidth?.toFixed(2)"
  93. :aria-valuemin="minWidth"
  94. role="separator"
  95. aria-orientation="horizontal"
  96. tabindex="0"
  97. @mousedown="startResizing"
  98. @touchstart="startResizing"
  99. @dblclick="$emit('reset-width')"
  100. />
  101. <CommonButton
  102. v-if="iconCollapsed && isCollapsed"
  103. class="mx-auto"
  104. size="medium"
  105. data-test-id="action-button"
  106. variant="neutral"
  107. :icon="iconCollapsed"
  108. @click="toggleCollapse"
  109. />
  110. <div
  111. v-else
  112. class="flex h-full flex-col overflow-y-auto"
  113. :class="{ 'p-3': !isCollapsed }"
  114. >
  115. <slot v-bind="{ isCollapsed }" />
  116. </div>
  117. </aside>
  118. </template>