CommonTabManager.vue 2.8 KB

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