CommonPopoverMenu.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { computed, toRefs, useSlots } from 'vue'
  4. import type { ObjectLike } from '#shared/types/utils.ts'
  5. import { usePopoverMenu } from '#desktop/components/CommonPopover/usePopoverMenu.ts'
  6. import CommonPopoverMenuItem from './CommonPopoverMenuItem.vue'
  7. import type { CommonPopoverInstance, MenuItem, Variant } from './types'
  8. export interface Props {
  9. popover: CommonPopoverInstance | undefined
  10. headerLabel?: string
  11. items?: MenuItem[]
  12. entity?: ObjectLike
  13. }
  14. const props = defineProps<Props>()
  15. const { items, entity } = toRefs(props)
  16. const { filteredMenuItems } = usePopoverMenu(items, entity)
  17. const slots = useSlots()
  18. const showHeaderLabel = computed(() => {
  19. if (!filteredMenuItems.value && !slots.default) return false
  20. return slots.header || props.headerLabel
  21. })
  22. const onClickItem = (event: MouseEvent, item: MenuItem) => {
  23. if (item.onClick) {
  24. item.onClick(props.entity)
  25. }
  26. if (!item.noCloseOnClick) {
  27. props.popover?.closePopover()
  28. }
  29. }
  30. const getHoverFocusStyles = (variant?: Variant) => {
  31. if (variant === 'secondary') {
  32. return 'focus-within:bg-blue-500 hover:bg-blue-500 hover:focus-within:bg-blue-500 dark:focus-within:bg-blue-950 dark:hover:bg-blue-950 dark:hover:focus-within:bg-blue-950'
  33. }
  34. if (variant === 'danger') {
  35. return 'focus-within:bg-pink-100 hover:bg-pink-100 hover:focus-within:bg-pink-100 dark:focus-within:bg-red-900 dark:hover:bg-red-900 dark:hover:focus-within:bg-red-900'
  36. }
  37. return 'focus-within:bg-blue-800 focus-within:text-white hover:bg-blue-600 hover:focus-within:bg-blue-800 dark:hover:bg-blue-900 dark:hover:focus-within:bg-blue-800'
  38. }
  39. </script>
  40. <template>
  41. <section class="min-w-58 flex flex-col gap-0.5">
  42. <div
  43. v-if="showHeaderLabel"
  44. role="heading"
  45. aria-level="2"
  46. class="p-2 leading-snug"
  47. >
  48. <slot name="header">
  49. <CommonLabel size="small" class="text-stone-200 dark:text-neutral-500"
  50. >{{ i18n.t(headerLabel) }}
  51. </CommonLabel>
  52. </slot>
  53. </div>
  54. <template v-if="filteredMenuItems || $slots.default">
  55. <slot>
  56. <ul role="menu" v-bind="$attrs" class="flex w-full flex-col">
  57. <template v-for="item in filteredMenuItems" :key="item.key">
  58. <li
  59. role="menuitem"
  60. class="group flex items-center justify-between last:rounded-b-[10px]"
  61. :class="[
  62. {
  63. 'first:rounded-t-[10px]': !showHeaderLabel,
  64. 'border-t border-neutral-100 dark:border-gray-900':
  65. item.separatorTop,
  66. },
  67. getHoverFocusStyles(item.variant),
  68. ]"
  69. >
  70. <slot :name="`item-${item.key}`" v-bind="item">
  71. <component
  72. :is="item.component || CommonPopoverMenuItem"
  73. class="flex grow p-2.5"
  74. :label="item.label"
  75. :variant="item.variant"
  76. :link="item.link"
  77. :icon="item.icon"
  78. :label-placeholder="item.labelPlaceholder"
  79. @click="onClickItem($event, item)"
  80. />
  81. <slot :name="`itemRight-${item.key}`" v-bind="item" />
  82. </slot>
  83. </li>
  84. </template>
  85. </ul>
  86. </slot>
  87. </template>
  88. </section>
  89. </template>