FieldTreeSelectInputDropdownItem.vue 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. /* eslint-disable vue/no-v-html */
  4. import { computed } from 'vue'
  5. import type {
  6. FlatSelectOption,
  7. MatchedFlatSelectOption,
  8. } from '#shared/components/Form/fields/FieldTreeSelect/types.ts'
  9. import { i18n } from '#shared/i18n.ts'
  10. import { useLocaleStore } from '#shared/stores/locale.ts'
  11. const props = defineProps<{
  12. option: FlatSelectOption | MatchedFlatSelectOption
  13. selected?: boolean
  14. multiple?: boolean
  15. noLabelTranslate?: boolean
  16. filter?: string
  17. noSelectionIndicator?: boolean
  18. }>()
  19. const emit = defineEmits<{
  20. select: [option: FlatSelectOption]
  21. next: [{ option: FlatSelectOption; noFocus?: boolean }]
  22. }>()
  23. const locale = useLocaleStore()
  24. const select = (option: FlatSelectOption) => {
  25. if (props.option.disabled) {
  26. return
  27. }
  28. emit('select', option)
  29. }
  30. const label = computed(() => {
  31. const { option } = props
  32. if (props.noLabelTranslate) return option.label || option.value.toString()
  33. return (
  34. i18n.t(option.label, ...(option.labelPlaceholder || [])) ||
  35. option.value.toString()
  36. )
  37. })
  38. const goToNextPage = (option: FlatSelectOption, noFocus?: boolean) => {
  39. emit('next', { option, noFocus })
  40. }
  41. </script>
  42. <template>
  43. <div
  44. :class="{
  45. 'cursor-pointer hover:bg-blue-600 focus:bg-blue-800 focus:text-white dark:hover:bg-blue-900 dark:hover:focus:bg-blue-800':
  46. !option.disabled,
  47. }"
  48. tabindex="0"
  49. :aria-selected="selected"
  50. :aria-disabled="option.disabled ? 'true' : undefined"
  51. class="group flex h-9 cursor-default items-center gap-1.5 self-stretch px-2.5 text-sm text-black outline-none dark:text-white"
  52. role="option"
  53. :data-value="option.value"
  54. @click="select(option)"
  55. @keypress.space.prevent="select(option)"
  56. @keypress.enter.prevent="select(option)"
  57. >
  58. <CommonIcon
  59. v-if="multiple && !noSelectionIndicator"
  60. :class="{
  61. 'fill-gray-100 group-hover:fill-black group-focus:fill-white dark:fill-neutral-400 dark:group-hover:fill-white':
  62. !option.disabled,
  63. 'fill-stone-200 dark:fill-neutral-500': option.disabled,
  64. }"
  65. size="xs"
  66. decorative
  67. :name="selected ? 'check-square' : 'square'"
  68. class="m-0.5 shrink-0"
  69. />
  70. <CommonIcon
  71. v-else-if="!noSelectionIndicator"
  72. class="shrink-0 fill-gray-100 group-hover:fill-black group-focus:fill-white dark:fill-neutral-400 dark:group-hover:fill-white"
  73. :class="{
  74. invisible: !selected,
  75. 'fill-stone-200 dark:fill-neutral-500': option.disabled,
  76. }"
  77. decorative
  78. size="tiny"
  79. name="check2"
  80. />
  81. <CommonIcon
  82. v-if="option.icon"
  83. :name="option.icon"
  84. size="tiny"
  85. :class="{
  86. 'fill-stone-200 dark:fill-neutral-500': option.disabled,
  87. }"
  88. decorative
  89. class="shrink-0 fill-gray-100 group-hover:fill-black group-focus:fill-white dark:fill-neutral-400 dark:group-hover:fill-white"
  90. />
  91. <span
  92. v-if="filter"
  93. :class="{
  94. 'pointer-events-none text-stone-200 dark:text-neutral-500':
  95. option.disabled,
  96. }"
  97. class="grow truncate"
  98. :title="label"
  99. v-html="(option as MatchedFlatSelectOption).matchedPath"
  100. />
  101. <span
  102. v-else
  103. :class="{
  104. 'pointer-events-none text-stone-200 dark:text-neutral-500':
  105. option.disabled && !option.hasChildren,
  106. 'pointer-events-none text-gray-100 dark:text-neutral-400':
  107. option.disabled && option.hasChildren,
  108. }"
  109. :title="label"
  110. class="grow truncate"
  111. >
  112. {{ label }}
  113. </span>
  114. <div
  115. v-if="option.hasChildren && !filter"
  116. class="group/nav -me-2 shrink-0 flex-nowrap items-center justify-center gap-x-2.5 rounded-[5px] p-2.5 hover:bg-blue-800 group-focus:hover:bg-blue-600 dark:group-focus:hover:bg-blue-900"
  117. :aria-label="$t('Has submenu')"
  118. role="button"
  119. tabindex="-1"
  120. @click.stop="goToNextPage(option, true)"
  121. @keypress.enter.prevent.stop="goToNextPage(option)"
  122. @keypress.space.prevent.stop="goToNextPage(option)"
  123. >
  124. <CommonIcon
  125. :class="{
  126. 'group-hover:fill-black group-focus:fill-white group-focus:group-hover/nav:!fill-black dark:group-hover:fill-white dark:group-focus:group-hover/nav:!fill-white':
  127. !option.disabled,
  128. }"
  129. class="shrink-0 fill-stone-200 group-hover/nav:!fill-white dark:fill-neutral-500"
  130. :name="
  131. locale.localeData?.dir === 'rtl' ? 'chevron-left' : 'chevron-right'
  132. "
  133. size="xs"
  134. tabindex="-1"
  135. decorative
  136. />
  137. </div>
  138. </div>
  139. </template>