CommonTabManager.vue 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { computed, nextTick, onMounted } 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['key'] | Tab['key'][]
  11. size?: 'medium' | 'large'
  12. }
  13. const props = withDefaults(defineProps<Props>(), {
  14. size: 'large',
  15. })
  16. const emit = defineEmits<{
  17. 'update:modelValue': [Tab['key'] | Tab['key'][]]
  18. }>()
  19. const isTabMode = computed(() => !props.multiple)
  20. const isActiveTab = (tab: Tab) =>
  21. Array.isArray(props.modelValue)
  22. ? props.modelValue.some((activeTab) => activeTab === tab.key)
  23. : props.modelValue === tab.key
  24. const updateModelValue = (tab: Tab) => {
  25. if (tab.disabled) return
  26. if (!props.multiple) return emit('update:modelValue', tab.key)
  27. // If tab is already included, remove it, otherwise add it
  28. return Array.isArray(props.modelValue) && props.modelValue?.includes(tab.key)
  29. ? emit(
  30. 'update:modelValue',
  31. props.modelValue?.filter((activeTab) => activeTab !== tab.key),
  32. )
  33. : emit('update:modelValue', [...(props.modelValue || []), tab.key])
  34. }
  35. onMounted(() => {
  36. if (props.multiple || props.modelValue) return
  37. nextTick(() => {
  38. const defaultTabIndex = props.tabs.findIndex((tab) => tab.default)
  39. if (defaultTabIndex === -1) return updateModelValue(props.tabs[0])
  40. updateModelValue(props.tabs[defaultTabIndex])
  41. })
  42. })
  43. const labelSize = computed(() => (props.size === 'large' ? 'medium' : 'small'))
  44. </script>
  45. <template>
  46. <div
  47. :role="isTabMode ? 'tablist' : 'listbox'"
  48. class="relative flex w-fit items-center gap-1 rounded-full bg-blue-200 p-1 dark:bg-gray-700"
  49. >
  50. <CommonLabel
  51. v-if="label"
  52. id="filter-select-label"
  53. class="px-3.5 py-1 text-stone-200 dark:text-neutral-500"
  54. :size="labelSize"
  55. >{{ $t(label) }}</CommonLabel
  56. >
  57. <CommonTab
  58. v-for="(tab, index) in tabs"
  59. :id="isTabMode ? `tab-label-${tab.key}` : undefined"
  60. :key="`${tab.key}-${index}`"
  61. :role="isTabMode ? 'tab' : 'option'"
  62. :aria-controls="isTabMode ? `tab-panel-${tab.key}` : undefined"
  63. tabindex="0"
  64. class="relative z-10"
  65. :size="size"
  66. :disabled="tab.disabled"
  67. :tab-mode="isTabMode"
  68. :aria-labelledby="label && !isTabMode ? 'filter-select-label' : undefined"
  69. :aria-selected="isActiveTab(tab)"
  70. :active="isActiveTab(tab)"
  71. :label="tab.label"
  72. :icon="tab.icon"
  73. :tooltip="tab.tooltip"
  74. @click="updateModelValue(tab)"
  75. @keydown.enter.prevent="updateModelValue(tab)"
  76. @keydown.space.prevent="updateModelValue(tab)"
  77. />
  78. </div>
  79. </template>