CommonButtonGroup.vue 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. <!-- Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { type Props as IconProps } from '@shared/components/CommonIcon/CommonIcon.vue'
  4. import { useSessionStore } from '@shared/stores/session'
  5. import { computed } from 'vue'
  6. import type { CommonButtonOption } from './types'
  7. export interface Props {
  8. modelValue?: string | number
  9. mode?: 'full' | 'compressed'
  10. controls?: string
  11. as?: 'tabs' | 'buttons'
  12. options: CommonButtonOption[]
  13. }
  14. const props = withDefaults(defineProps<Props>(), {
  15. mode: 'compressed',
  16. as: 'buttons',
  17. })
  18. const emit = defineEmits<{
  19. (e: 'update:modelValue', value?: string | number): void
  20. }>()
  21. const session = useSessionStore()
  22. const filteredOptions = computed(() => {
  23. return props.options.filter(
  24. (option) =>
  25. !option.hidden &&
  26. (!option.permissions || session.hasPermission(option.permissions)),
  27. )
  28. })
  29. const getIconProps = (option: CommonButtonOption): IconProps => {
  30. if (!option.icon) return {} as IconProps
  31. if (typeof option.icon === 'string') {
  32. return { name: option.icon, size: 'small' }
  33. }
  34. return option.icon
  35. }
  36. const onButtonClick = (option: CommonButtonOption) => {
  37. if (option.disabled) return
  38. option.onAction?.()
  39. emit('update:modelValue', option.value)
  40. }
  41. const isTabs = computed(() => props.as === 'tabs')
  42. </script>
  43. <template>
  44. <div
  45. class="flex max-w-[100vw] gap-2 overflow-x-auto"
  46. :class="{ 'w-full': mode === 'full' }"
  47. :role="isTabs ? 'tablist' : undefined"
  48. >
  49. <Component
  50. :is="option.link ? 'CommonLink' : 'button'"
  51. v-for="option of filteredOptions"
  52. :key="option.label"
  53. :type="option.link ? undefined : 'button'"
  54. :role="isTabs ? 'tab' : undefined"
  55. :disabled="option.disabled"
  56. :link="option.link"
  57. class="flex flex-col items-center justify-center gap-1 rounded-xl bg-gray-500 px-3 text-sm text-white"
  58. :data-value="option.value"
  59. :class="{
  60. 'bg-gray-600/50 text-white/30': option.disabled,
  61. 'bg-gray-200':
  62. option.selected ||
  63. (option.value != null && modelValue === option.value),
  64. 'flex-1 py-2': mode === 'full',
  65. 'py-1': mode === 'compressed',
  66. }"
  67. :aria-controls="isTabs ? controls || option.controls : undefined"
  68. :aria-selected="isTabs ? modelValue === option.value : undefined"
  69. @click="onButtonClick(option)"
  70. >
  71. <CommonIcon v-if="option.icon" v-bind="getIconProps(option)" decorative />
  72. <span>{{ $t(option.label, ...(option.labelPlaceholder || [])) }}</span>
  73. </Component>
  74. </div>
  75. </template>