CommonTabManager.vue 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { computed, nextTick, onMounted, ref } from 'vue'
  4. import CommonTab from '#desktop/components/CommonTabManager/CommonTab.vue'
  5. import type { Tab } from '#desktop/components/CommonTabManager/types.ts'
  6. interface Props {
  7. multiple?: boolean
  8. label?: string
  9. tabs: Tab[]
  10. modelValue?: Tab[] | Tab['key']
  11. }
  12. const props = defineProps<Props>()
  13. const tabNodes = ref<InstanceType<typeof CommonTab>[]>()
  14. const emit = defineEmits<{
  15. 'update:modelValue': [unknown]
  16. }>()
  17. const isTabMode = computed(() => !props.multiple)
  18. const activeTabIndex = computed(() => {
  19. return props.tabs.findIndex(
  20. (tab) => tab.key === (props.modelValue as Tab['key']),
  21. )
  22. })
  23. const activeTabWidth = computed(() => {
  24. return tabNodes.value?.at(activeTabIndex.value)?.$el.offsetWidth || 0
  25. })
  26. const activeTabHeight = computed(() => {
  27. return tabNodes.value?.at(activeTabIndex.value)?.$el.offsetHeight || 0
  28. })
  29. const activeTabOffsetLeft = computed(() => {
  30. return tabNodes.value?.at(activeTabIndex.value)?.$el.offsetLeft || 0
  31. })
  32. // Functions are invoked only if multiple is true
  33. const removeActiveTab = (tab: Tab) => {
  34. return (props.modelValue as Tab[])?.filter(
  35. (activeTab) => activeTab?.key !== tab.key,
  36. )
  37. }
  38. const addActiveTab = (tab: Tab) => {
  39. // Initial modelValue could be falsy
  40. if (props.modelValue) return [...(props.modelValue as Tab[]), tab]
  41. return [tab]
  42. }
  43. const updateModelValue = (tab: Tab) => {
  44. if (!props.multiple) return emit('update:modelValue', tab.key)
  45. // If tab is already included, remove it, otherwise add it
  46. return (props.modelValue as Tab[])?.some(
  47. (activeTab) => activeTab.label === tab.label,
  48. )
  49. ? emit('update:modelValue', removeActiveTab(tab))
  50. : emit('update:modelValue', addActiveTab(tab))
  51. }
  52. onMounted(() => {
  53. if (props.multiple) return
  54. nextTick(() => {
  55. const defaultTabIndex = props.tabs.findIndex((tab) => tab.default)
  56. if (defaultTabIndex === -1) return updateModelValue(props.tabs[0])
  57. updateModelValue(props.tabs[defaultTabIndex])
  58. })
  59. })
  60. </script>
  61. <template>
  62. <div
  63. :role="isTabMode ? 'tablist' : 'listbox'"
  64. class="relative flex w-fit items-center gap-1 rounded-full bg-blue-200 p-1 dark:bg-gray-700"
  65. >
  66. <CommonTab v-if="label" id="filter-select-label">
  67. <CommonLabel class="text-stone-200 dark:text-neutral-500" size="medium">{{
  68. $t(label)
  69. }}</CommonLabel>
  70. </CommonTab>
  71. <CommonTab
  72. v-if="!multiple"
  73. :style="{
  74. width: activeTabWidth + 'px',
  75. left: activeTabOffsetLeft + 'px',
  76. height: activeTabHeight + 'px',
  77. }"
  78. active
  79. role="presentation"
  80. class="absolute z-0 transition-[left]"
  81. />
  82. <CommonTab
  83. v-for="(tab, index) in tabs"
  84. :id="isTabMode ? `tab-label-${tab.key}` : undefined"
  85. ref="tabNodes"
  86. :key="`${tab.key}-${index}`"
  87. :role="isTabMode ? 'tab' : 'option'"
  88. :aria-controls="isTabMode ? `tab-panel-${tab.key}` : undefined"
  89. tabindex="0"
  90. class="relative z-10 cursor-pointer"
  91. :no-active-background="isTabMode"
  92. :aria-labelledby="label && !isTabMode ? 'filter-select-label' : undefined"
  93. :aria-selected="
  94. Array.isArray(modelValue)
  95. ? modelValue?.some((activeTab) => activeTab.key === tab.key)
  96. : modelValue === tab.key
  97. "
  98. :active="
  99. Array.isArray(modelValue)
  100. ? modelValue?.some((activeTab) => activeTab.key === tab.key)
  101. : modelValue === tab.key
  102. "
  103. @click="updateModelValue(tab)"
  104. @keydown.enter="updateModelValue(tab)"
  105. >{{ $t(tab.label) }}
  106. </CommonTab>
  107. </div>
  108. </template>