CommonActionMenu.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { computed, ref, toRefs } from 'vue'
  4. import type { ObjectLike } from '#shared/types/utils.ts'
  5. import getUuid from '#shared/utils/getUuid.ts'
  6. import CommonButton from '#desktop/components/CommonButton/CommonButton.vue'
  7. import type { ButtonSize } from '#desktop/components/CommonButton/types.ts'
  8. import CommonPopover from '#desktop/components/CommonPopover/CommonPopover.vue'
  9. import CommonPopoverMenu from '#desktop/components/CommonPopover/CommonPopoverMenu.vue'
  10. import type {
  11. MenuItem,
  12. Orientation,
  13. Placement,
  14. } from '#desktop/components/CommonPopover/types.ts'
  15. import { usePopover } from '#desktop/components/CommonPopover/usePopover.ts'
  16. import { usePopoverMenu } from '#desktop/components/CommonPopover/usePopoverMenu.ts'
  17. export interface Props {
  18. actions: MenuItem[]
  19. entity?: ObjectLike
  20. buttonSize?: ButtonSize
  21. placement?: Placement
  22. orientation?: Orientation
  23. noSingleActionMode?: boolean
  24. customMenuButtonLabel?: string
  25. }
  26. const props = withDefaults(defineProps<Props>(), {
  27. buttonSize: 'medium',
  28. placement: 'end',
  29. orientation: 'autoVertical',
  30. })
  31. const popoverMenu = ref<InstanceType<typeof CommonPopoverMenu>>()
  32. const { popover, isOpen: popoverIsOpen, popoverTarget, toggle } = usePopover()
  33. const { actions, entity } = toRefs(props)
  34. const { filteredMenuItems, singleMenuItemPresent, singleMenuItem } =
  35. usePopoverMenu(actions, entity, { provides: true })
  36. const entityId = computed(() => props.entity?.id || getUuid())
  37. const menuId = computed(() => `popover-${entityId.value}`)
  38. const singleActionAriaLabel = computed(() => {
  39. if (typeof singleMenuItem.value?.ariaLabel === 'function') {
  40. return singleMenuItem.value.ariaLabel(props.entity)
  41. }
  42. return singleMenuItem.value?.ariaLabel || singleMenuItem.value?.label
  43. })
  44. const singleActionMode = computed(() => {
  45. if (props.noSingleActionMode) return false
  46. return singleMenuItemPresent.value
  47. })
  48. const buttonVariantClass = computed(() => {
  49. if (singleMenuItem.value?.variant === 'secondary') return 'text-blue-800'
  50. if (singleMenuItem.value?.variant === 'danger') return 'text-red-500'
  51. return 'text-stone-200 dark:text-neutral-500'
  52. })
  53. </script>
  54. <template>
  55. <div
  56. v-if="filteredMenuItems && filteredMenuItems.length > 0"
  57. class="inline-block"
  58. >
  59. <CommonButton
  60. v-if="singleActionMode"
  61. :class="buttonVariantClass"
  62. :size="buttonSize"
  63. :aria-label="$t(singleActionAriaLabel)"
  64. :icon="singleMenuItem?.icon"
  65. @click="singleMenuItem?.onClick?.(entity as ObjectLike)"
  66. />
  67. <template v-else>
  68. <CommonButton
  69. :id="`action-menu-${entityId}`"
  70. ref="popoverTarget"
  71. :aria-label="$t(customMenuButtonLabel || 'Action menu button')"
  72. aria-haspopup="true"
  73. :aria-controls="popoverIsOpen ? menuId : undefined"
  74. class="text-stone-200 dark:text-neutral-500"
  75. :class="{
  76. 'outline outline-1 outline-offset-1 outline-blue-800': popoverIsOpen,
  77. }"
  78. :size="buttonSize"
  79. icon="three-dots-vertical"
  80. @click="toggle"
  81. />
  82. <CommonPopover
  83. :id="menuId"
  84. ref="popover"
  85. :placement="placement"
  86. :orientation="orientation"
  87. :owner="popoverTarget"
  88. >
  89. <CommonPopoverMenu
  90. ref="popoverMenu"
  91. :entity="entity"
  92. :popover="popover"
  93. />
  94. </CommonPopover>
  95. </template>
  96. </div>
  97. </template>