@meistrari/tela-build 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/README.md +75 -0
  2. package/app.config.ts +73 -0
  3. package/components/tela/animated/animated-calculating-number.vue +16 -0
  4. package/components/tela/animated/animated-number.mdx +248 -0
  5. package/components/tela/animated/animated-number.stories.ts +52 -0
  6. package/components/tela/animated/animated-number.vue +23 -0
  7. package/components/tela/animated/animated-text.vue +124 -0
  8. package/components/tela/animated/animated-value.vue +68 -0
  9. package/components/tela/avatar/avatar.mdx +117 -0
  10. package/components/tela/avatar/avatar.stories.ts +62 -0
  11. package/components/tela/avatar/avatar.vue +71 -0
  12. package/components/tela/avatar/group/avatar-group.stories.ts +78 -0
  13. package/components/tela/avatar/group/avatar-group.vue +46 -0
  14. package/components/tela/badge/badge.mdx +154 -0
  15. package/components/tela/badge/badge.stories.ts +82 -0
  16. package/components/tela/badge/badge.vue +41 -0
  17. package/components/tela/button/button.mdx +155 -0
  18. package/components/tela/button/button.stories.ts +202 -0
  19. package/components/tela/button/button.vue +107 -0
  20. package/components/tela/card.vue +30 -0
  21. package/components/tela/chart/chart-bar.vue +58 -0
  22. package/components/tela/chat/chat.mdx +268 -0
  23. package/components/tela/chat/chat.stories.ts +253 -0
  24. package/components/tela/chat/command/index.vue +41 -0
  25. package/components/tela/chat/command/mention/index.vue +138 -0
  26. package/components/tela/chat/index.vue +112 -0
  27. package/components/tela/chat/pure-text-input/chat-text-input.vue +190 -0
  28. package/components/tela/chat/text-input/chat-text-input.stories.ts +128 -0
  29. package/components/tela/chat/text-input/index.vue +217 -0
  30. package/components/tela/chat/text-message/chat-text-message.stories.ts +138 -0
  31. package/components/tela/chat/text-message/index.vue +355 -0
  32. package/components/tela/chat/types.ts +19 -0
  33. package/components/tela/checkbox/checkbox-card.vue +30 -0
  34. package/components/tela/checkbox/checkbox.mdx +164 -0
  35. package/components/tela/checkbox/checkbox.stories.ts +104 -0
  36. package/components/tela/checkbox/checkbox.vue +43 -0
  37. package/components/tela/collapsible/Collapsible.vue +15 -0
  38. package/components/tela/collapsible/CollapsibleContent.vue +59 -0
  39. package/components/tela/collapsible/CollapsibleTrigger.vue +12 -0
  40. package/components/tela/collapsible/collapsible.mdx +157 -0
  41. package/components/tela/collapsible-section/collapsible-section.mdx +180 -0
  42. package/components/tela/collapsible-section/collapsible-section.stories.ts +53 -0
  43. package/components/tela/collapsible-section/collapsible-section.vue +51 -0
  44. package/components/tela/collapsible-section-with-actions.vue +98 -0
  45. package/components/tela/combobox/combobox-anchor.vue +24 -0
  46. package/components/tela/combobox/combobox-empty.vue +19 -0
  47. package/components/tela/combobox/combobox-group.vue +24 -0
  48. package/components/tela/combobox/combobox-indicator.vue +22 -0
  49. package/components/tela/combobox/combobox-input.vue +31 -0
  50. package/components/tela/combobox/combobox-item.vue +28 -0
  51. package/components/tela/combobox/combobox-label.vue +24 -0
  52. package/components/tela/combobox/combobox-list.vue +90 -0
  53. package/components/tela/combobox/combobox-module-selector.vue +366 -0
  54. package/components/tela/combobox/combobox-root.vue +15 -0
  55. package/components/tela/combobox/combobox-trigger.vue +12 -0
  56. package/components/tela/combobox/combobox.mdx +285 -0
  57. package/components/tela/combobox/combobox.stories.ts +232 -0
  58. package/components/tela/combobox/combobox.vue +497 -0
  59. package/components/tela/command/command-dialog.vue +22 -0
  60. package/components/tela/command/command-empty.vue +25 -0
  61. package/components/tela/command/command-group.vue +46 -0
  62. package/components/tela/command/command-input.vue +38 -0
  63. package/components/tela/command/command-item.vue +78 -0
  64. package/components/tela/command/command-list.vue +78 -0
  65. package/components/tela/command/command-separator.vue +23 -0
  66. package/components/tela/command/command-shortcut.vue +13 -0
  67. package/components/tela/command/command.vue +88 -0
  68. package/components/tela/command/dialog-base.vue +15 -0
  69. package/components/tela/command/dialog-content.vue +50 -0
  70. package/components/tela/command/utils.ts +15 -0
  71. package/components/tela/complex-table/complex-table-cell.stories.ts +145 -0
  72. package/components/tela/complex-table/complex-table-cell.vue +45 -0
  73. package/components/tela/complex-table/complex-table-header-cell.stories.ts +103 -0
  74. package/components/tela/complex-table/complex-table-header-cell.vue +48 -0
  75. package/components/tela/complex-table/complex-table-header.stories.ts +89 -0
  76. package/components/tela/complex-table/complex-table-header.vue +70 -0
  77. package/components/tela/complex-table/complex-table-row.vue +199 -0
  78. package/components/tela/complex-table/complex-table-virtualized.vue +326 -0
  79. package/components/tela/complex-table/complex-table.stories.ts +358 -0
  80. package/components/tela/complex-table/complex-table.vue +237 -0
  81. package/components/tela/complex-table/composables/table-common.ts +93 -0
  82. package/components/tela/complex-table/composables/table-selection.ts +87 -0
  83. package/components/tela/complex-table/composables/virtual-scroll.ts +252 -0
  84. package/components/tela/complex-table/styles/table-shared.css +170 -0
  85. package/components/tela/complex-table/types.ts +63 -0
  86. package/components/tela/complex-table/utils.ts +35 -0
  87. package/components/tela/confirm-button/confirm-button.vue +137 -0
  88. package/components/tela/confirmation-modal/confirmation-modal.vue +72 -0
  89. package/components/tela/copy-button.vue +86 -0
  90. package/components/tela/date-range-picker.vue +221 -0
  91. package/components/tela/dialog/dialog.mdx +170 -0
  92. package/components/tela/dialog/dialog.vue +182 -0
  93. package/components/tela/disabled-area.vue +16 -0
  94. package/components/tela/disclaimer/disclaimer.mdx +238 -0
  95. package/components/tela/disclaimer/disclaimer.stories.ts +196 -0
  96. package/components/tela/disclaimer/disclaimer.vue +125 -0
  97. package/components/tela/dropdown-menu/DropdownMenu.vue +121 -0
  98. package/components/tela/dropdown-menu/DropdownMenuCheckboxItem.vue +40 -0
  99. package/components/tela/dropdown-menu/DropdownMenuContent.vue +75 -0
  100. package/components/tela/dropdown-menu/DropdownMenuGroup.vue +12 -0
  101. package/components/tela/dropdown-menu/DropdownMenuItem.vue +137 -0
  102. package/components/tela/dropdown-menu/DropdownMenuLabel.vue +26 -0
  103. package/components/tela/dropdown-menu/DropdownMenuRadioGroup.vue +18 -0
  104. package/components/tela/dropdown-menu/DropdownMenuRadioItem.vue +40 -0
  105. package/components/tela/dropdown-menu/DropdownMenuRoot.vue +15 -0
  106. package/components/tela/dropdown-menu/DropdownMenuSeparator.vue +21 -0
  107. package/components/tela/dropdown-menu/DropdownMenuShortcut.vue +14 -0
  108. package/components/tela/dropdown-menu/DropdownMenuSub.vue +18 -0
  109. package/components/tela/dropdown-menu/DropdownMenuSubContent.vue +30 -0
  110. package/components/tela/dropdown-menu/DropdownMenuSubTrigger.vue +35 -0
  111. package/components/tela/dropdown-menu/DropdownMenuTrigger.vue +14 -0
  112. package/components/tela/dropdown-menu/dropdown-menu.mdx +265 -0
  113. package/components/tela/dropdown-menu/dropdown-menu.stories.ts +156 -0
  114. package/components/tela/expandable-input.vue +96 -0
  115. package/components/tela/file-drop.vue +37 -0
  116. package/components/tela/file-upload/file-upload.mdx +189 -0
  117. package/components/tela/file-upload/file-upload.stories.ts +48 -0
  118. package/components/tela/file-upload/file-upload.vue +205 -0
  119. package/components/tela/filters/checkbox-filter.stories.ts +218 -0
  120. package/components/tela/filters/checkbox-filter.vue +165 -0
  121. package/components/tela/filters/date-filter.stories.ts +258 -0
  122. package/components/tela/filters/date-filter.vue +200 -0
  123. package/components/tela/filters/user-filter.stories.ts +344 -0
  124. package/components/tela/filters/user-filter.vue +271 -0
  125. package/components/tela/hover-card/hover-card.mdx +221 -0
  126. package/components/tela/hover-card/hover-card.stories.ts +87 -0
  127. package/components/tela/hover-card/hover-card.vue +61 -0
  128. package/components/tela/icon/custom.vue +319 -0
  129. package/components/tela/icon/spinner.vue +12 -0
  130. package/components/tela/icon-button/icon-button.vue +114 -0
  131. package/components/tela/icon.vue +37 -0
  132. package/components/tela/initials.vue +28 -0
  133. package/components/tela/inline-input.vue +77 -0
  134. package/components/tela/input/input.mdx +182 -0
  135. package/components/tela/input/input.stories.ts +153 -0
  136. package/components/tela/input/tela-input.vue +240 -0
  137. package/components/tela/kbd/kbd-return.vue +6 -0
  138. package/components/tela/kbd/kbd.mdx +238 -0
  139. package/components/tela/kbd/kbd.vue +18 -0
  140. package/components/tela/label/label.mdx +121 -0
  141. package/components/tela/label/label.stories.ts +37 -0
  142. package/components/tela/label/label.vue +25 -0
  143. package/components/tela/link-decoration/link-decoration.vue +19 -0
  144. package/components/tela/live-label.vue +32 -0
  145. package/components/tela/long-press-button.vue +98 -0
  146. package/components/tela/menubar/menubar-content.vue +77 -0
  147. package/components/tela/menubar/menubar-item.vue +32 -0
  148. package/components/tela/menubar/menubar-label.vue +14 -0
  149. package/components/tela/menubar/menubar-menu.vue +12 -0
  150. package/components/tela/menubar/menubar-root.vue +30 -0
  151. package/components/tela/menubar/menubar-separator.vue +17 -0
  152. package/components/tela/menubar/menubar-shortcut.vue +14 -0
  153. package/components/tela/menubar/menubar-sub-content.vue +36 -0
  154. package/components/tela/menubar/menubar-sub-trigger.vue +28 -0
  155. package/components/tela/menubar/menubar-sub.vue +20 -0
  156. package/components/tela/menubar/menubar-trigger.vue +27 -0
  157. package/components/tela/menubar/menubar.vue +298 -0
  158. package/components/tela/modal/modal.mdx +145 -0
  159. package/components/tela/modal/modal.vue +242 -0
  160. package/components/tela/multiple-select/multiple-select.mdx +274 -0
  161. package/components/tela/multiple-select/multiple-select.stories.ts +325 -0
  162. package/components/tela/multiple-select/multiple-select.vue +666 -0
  163. package/components/tela/pane.vue +110 -0
  164. package/components/tela/popover/popover-content.vue +48 -0
  165. package/components/tela/popover/popover-trigger.vue +12 -0
  166. package/components/tela/popover/popover.mdx +239 -0
  167. package/components/tela/popover/popover.stories.ts +150 -0
  168. package/components/tela/popover/popover.vue +15 -0
  169. package/components/tela/popover-list/popover-list-nested.vue +104 -0
  170. package/components/tela/popover-list/popover-list.stories.ts +330 -0
  171. package/components/tela/popover-list/popover-list.vue +191 -0
  172. package/components/tela/radio-button.vue +66 -0
  173. package/components/tela/radio-group/radio-group-item.vue +40 -0
  174. package/components/tela/radio-group/radio-group-root.vue +26 -0
  175. package/components/tela/radio-group/radio-group.mdx +78 -0
  176. package/components/tela/radio-group/radio-group.stories.ts +106 -0
  177. package/components/tela/radio-group/radio-group.vue +23 -0
  178. package/components/tela/range-calendar.stories.ts +110 -0
  179. package/components/tela/range-calendar.vue +109 -0
  180. package/components/tela/scroll-area/scroll-area.mdx +183 -0
  181. package/components/tela/scroll-area/scroll-area.vue +30 -0
  182. package/components/tela/scroll-area/scroll-bar.vue +31 -0
  183. package/components/tela/segment-toggle.stories.ts +114 -0
  184. package/components/tela/segment-toggle.vue +66 -0
  185. package/components/tela/select-menu/select-menu-content.vue +106 -0
  186. package/components/tela/select-menu/select-menu-down-button.vue +20 -0
  187. package/components/tela/select-menu/select-menu-group.vue +16 -0
  188. package/components/tela/select-menu/select-menu-item.vue +40 -0
  189. package/components/tela/select-menu/select-menu-root.vue +15 -0
  190. package/components/tela/select-menu/select-menu-trigger.vue +34 -0
  191. package/components/tela/select-menu/select-menu-up-button.vue +20 -0
  192. package/components/tela/select-menu/select-menu-value.vue +12 -0
  193. package/components/tela/select-menu/select-menu.mdx +221 -0
  194. package/components/tela/select-menu/select-menu.stories.ts +91 -0
  195. package/components/tela/select-menu/select-menu.vue +165 -0
  196. package/components/tela/selector/selector.vue +47 -0
  197. package/components/tela/sheet/sheet-close.vue +12 -0
  198. package/components/tela/sheet/sheet-content.vue +57 -0
  199. package/components/tela/sheet/sheet-description.vue +23 -0
  200. package/components/tela/sheet/sheet-footer.vue +18 -0
  201. package/components/tela/sheet/sheet-header.vue +15 -0
  202. package/components/tela/sheet/sheet-root.vue +18 -0
  203. package/components/tela/sheet/sheet-title.vue +23 -0
  204. package/components/tela/sheet/sheet-trigger.vue +12 -0
  205. package/components/tela/sheet/sheet.client.vue +150 -0
  206. package/components/tela/sheet/sheet.mdx +176 -0
  207. package/components/tela/sheet/sheet.stories.ts +201 -0
  208. package/components/tela/sheet/variants.ts +22 -0
  209. package/components/tela/side-sheet/side-sheet.mdx +131 -0
  210. package/components/tela/side-sheet/side-sheet.stories.ts +134 -0
  211. package/components/tela/side-sheet/side-sheet.vue +106 -0
  212. package/components/tela/skeleton/skeleton.mdx +165 -0
  213. package/components/tela/skeleton/skeleton.stories.ts +35 -0
  214. package/components/tela/skeleton/skeleton.vue +45 -0
  215. package/components/tela/skeleton-icon.vue +24 -0
  216. package/components/tela/span.vue +24 -0
  217. package/components/tela/star-button.vue +70 -0
  218. package/components/tela/status/status-lean.vue +30 -0
  219. package/components/tela/status/status.mdx +187 -0
  220. package/components/tela/status/status.stories.ts +160 -0
  221. package/components/tela/status/status.vue +420 -0
  222. package/components/tela/status-bar/status-bar.mdx +178 -0
  223. package/components/tela/status-bar/status-bar.stories.ts +64 -0
  224. package/components/tela/status-bar/status-bar.vue +56 -0
  225. package/components/tela/status-bar/types.ts +5 -0
  226. package/components/tela/switch/switch.mdx +118 -0
  227. package/components/tela/switch/switch.stories.ts +80 -0
  228. package/components/tela/switch/switch.vue +56 -0
  229. package/components/tela/table/table-body.vue +13 -0
  230. package/components/tela/table/table-caption.vue +13 -0
  231. package/components/tela/table/table-cell.vue +20 -0
  232. package/components/tela/table/table-empty.vue +37 -0
  233. package/components/tela/table/table-footer.vue +13 -0
  234. package/components/tela/table/table-head.vue +13 -0
  235. package/components/tela/table/table-header.vue +13 -0
  236. package/components/tela/table/table-row.vue +13 -0
  237. package/components/tela/table/table.mdx +230 -0
  238. package/components/tela/table/table.stories.ts +384 -0
  239. package/components/tela/table/table.vue +15 -0
  240. package/components/tela/tabs/tabs-content.vue +20 -0
  241. package/components/tela/tabs/tabs-indicator.vue +22 -0
  242. package/components/tela/tabs/tabs-list.vue +23 -0
  243. package/components/tela/tabs/tabs-root.vue +15 -0
  244. package/components/tela/tabs/tabs-trigger.vue +27 -0
  245. package/components/tela/tabs/tabs.mdx +138 -0
  246. package/components/tela/tabs/tabs.stories.ts +72 -0
  247. package/components/tela/tabs/tabs.vue +61 -0
  248. package/components/tela/tags/tags-select.mdx +318 -0
  249. package/components/tela/tags/tags-select.stories.ts +47 -0
  250. package/components/tela/tags/tags-select.vue +637 -0
  251. package/components/tela/tags/tags.mdx +151 -0
  252. package/components/tela/tags/tags.stories.ts +118 -0
  253. package/components/tela/tags/tags.vue +112 -0
  254. package/components/tela/textarea/textarea.mdx +102 -0
  255. package/components/tela/textarea/textarea.stories.ts +50 -0
  256. package/components/tela/textarea/textarea.vue +34 -0
  257. package/components/tela/toggle-group.vue +91 -0
  258. package/components/tela/tooltip/tooltip-content.vue +45 -0
  259. package/components/tela/tooltip/tooltip-provider.vue +12 -0
  260. package/components/tela/tooltip/tooltip-root.vue +15 -0
  261. package/components/tela/tooltip/tooltip-trigger.vue +12 -0
  262. package/components/tela/tooltip/tooltip.mdx +196 -0
  263. package/components/tela/tooltip/tooltip.stories.ts +200 -0
  264. package/components/tela/tooltip/tooltip.vue +91 -0
  265. package/components/tela/tooltip-group/tooltip-group-trigger.vue +92 -0
  266. package/components/tela/tooltip-group/tooltip-group.mdx +236 -0
  267. package/components/tela/tooltip-group/tooltip-group.stories.ts +465 -0
  268. package/components/tela/tooltip-group/tooltip-group.vue +35 -0
  269. package/components/tela/transparent-input.vue +151 -0
  270. package/components/tela/variable-icon.vue +28 -0
  271. package/components/tela/variable-input.vue +77 -0
  272. package/components/tela/wide-button/wide-button.vue +40 -0
  273. package/components.json +18 -0
  274. package/composables/status-toast.ts +67 -0
  275. package/css/reset.css +386 -0
  276. package/css/text.css +22 -0
  277. package/lib/doc-generator.ts +903 -0
  278. package/lib/extractors/volar-extract.ts +186 -0
  279. package/lib/type-resolver.ts +402 -0
  280. package/lib/utils.ts +6 -0
  281. package/modules/tela-build-docs/index.ts +139 -0
  282. package/nuxt.config.ts +80 -0
  283. package/package.json +84 -0
  284. package/plugins/test-id.ts +7 -0
  285. package/tsconfig.json +7 -0
  286. package/types/custom-icon.ts +1 -0
  287. package/types/index.ts +2 -0
  288. package/types/status.ts +1 -0
  289. package/unocss.config.ts +89 -0
  290. package/utils/component-utils.ts +30 -0
  291. package/utils/design-tokens.ts +431 -0
  292. package/utils/fold.ts +8 -0
  293. package/utils/select-menu.ts +10 -0
  294. package/utils/status.ts +1 -0
  295. package/utils/without-keys.ts +34 -0
@@ -0,0 +1,666 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ SelectContent,
4
+ SelectGroup,
5
+ SelectItem,
6
+ SelectItemIndicator,
7
+ SelectLabel,
8
+ SelectPortal,
9
+ SelectRoot,
10
+ SelectScrollDownButton,
11
+ SelectScrollUpButton,
12
+ SelectTrigger,
13
+ SelectViewport,
14
+ } from 'radix-vue'
15
+ import { nextTick } from 'vue'
16
+ import PopoverList from '../popover-list/popover-list.vue'
17
+
18
+ const props = withDefaults(defineProps<{
19
+ modelValue?: string[]
20
+ readOnly?: boolean
21
+ options: SelectMenuOption[]
22
+ placeholder?: string
23
+ required?: boolean
24
+ maxSelectedDisplay?: number
25
+ allowSearch?: boolean
26
+ searchPlaceholder?: string
27
+ buttonLabel?: string
28
+ closeButtonLabel?: string
29
+ closeOnSelect?: boolean
30
+ buttonIcon?: string
31
+ variant?: 'list' | 'preview'
32
+ previewMaxItems?: number
33
+ description?: string
34
+ }>(), {
35
+ maxSelectedDisplay: 10,
36
+ searchPlaceholder: 'Search options...',
37
+ closeOnSelect: false,
38
+ closeButtonLabel: 'Close',
39
+ buttonIcon: 'i-ph-plus',
40
+ variant: 'list',
41
+ previewMaxItems: 3,
42
+ })
43
+
44
+ const emit = defineEmits<{
45
+ 'update:modelValue': [value: string[]]
46
+ 'navigate': [to: string]
47
+ 'select': [value: string]
48
+ 'deselect': [value: string]
49
+ 'focus': []
50
+ 'blur': []
51
+ }>()
52
+
53
+ const { t } = useTranslation()
54
+
55
+ type SelectMenuOption = {
56
+ value: string
57
+ label: string
58
+ description?: string
59
+ icon?: string
60
+ externalIconSrc?: string
61
+ group?: string
62
+ to?: string
63
+ }
64
+
65
+ const innerValue = ref<string>('')
66
+ const container = ref<HTMLElement>()
67
+ const triggerElementRef = ref<HTMLElement | null>(null)
68
+ const { width: contentWidth } = useElementBounding(triggerElementRef)
69
+
70
+ const isFocused = ref(false)
71
+ const isOpen = ref(false)
72
+
73
+ watch(isFocused, val => val ? emit('focus') : emit('blur'))
74
+
75
+ function handleEscKey(e: KeyboardEvent) {
76
+ if (e.key === 'Escape') {
77
+ isFocused.value = false
78
+ isOpen.value = false
79
+
80
+ if (triggerElementRef.value) {
81
+ const button = triggerElementRef.value.querySelector('button')
82
+ if (button) {
83
+ button.blur()
84
+ }
85
+ }
86
+
87
+ e.stopPropagation()
88
+ }
89
+ }
90
+
91
+ onMounted(() => {
92
+ const button = triggerElementRef.value?.querySelector('button')
93
+ if (button) {
94
+ const handleFocus = () => {
95
+ isFocused.value = true
96
+ }
97
+ const handleBlur = () => {
98
+ isFocused.value = false
99
+ }
100
+ button.addEventListener('focus', handleFocus)
101
+ button.addEventListener('blur', handleBlur)
102
+
103
+ onBeforeUnmount(() => {
104
+ button.removeEventListener('focus', handleFocus)
105
+ button.removeEventListener('blur', handleBlur)
106
+ })
107
+ }
108
+ })
109
+
110
+ const testId = ref('')
111
+ onMounted(() => {
112
+ if (container.value?.attributes && container.value?.attributes.getNamedItem('data-testid')) {
113
+ testId.value = container.value?.attributes.getNamedItem('data-testid')?.value as string
114
+
115
+ // Remove the data-testid attribute from the container element
116
+ container.value?.removeAttribute('data-testid')
117
+
118
+ // Add the data-testid attribute to the input element
119
+ triggerElementRef.value?.setAttribute('data-testid', testId.value)
120
+ }
121
+
122
+ window.addEventListener('keydown', handleEscKey)
123
+
124
+ const button = triggerElementRef.value?.querySelector('button')
125
+ if (button) {
126
+ const handleFocus = () => {
127
+ isFocused.value = true
128
+ }
129
+ const handleBlur = () => {
130
+ isFocused.value = false
131
+ }
132
+ button.addEventListener('focus', handleFocus)
133
+ button.addEventListener('blur', handleBlur)
134
+
135
+ button.addEventListener('keydown', handleEscKey)
136
+
137
+ onBeforeUnmount(() => {
138
+ window.removeEventListener('keydown', handleEscKey)
139
+ button.removeEventListener('focus', handleFocus)
140
+ button.removeEventListener('blur', handleBlur)
141
+ button.removeEventListener('keydown', handleEscKey)
142
+ })
143
+ }
144
+ })
145
+
146
+ const search = ref('')
147
+ const searchInputEl = ref<HTMLInputElement>()
148
+
149
+ function onOpenChange(open: boolean) {
150
+ isOpen.value = open
151
+
152
+ if (!open) {
153
+ isFocused.value = false
154
+ search.value = '' // Clear search when closing
155
+ }
156
+ else if (props.allowSearch) {
157
+ // Auto-focus search input when opening
158
+ setTimeout(() => {
159
+ if (searchInputEl.value) {
160
+ searchInputEl.value.focus()
161
+ }
162
+ }, 50)
163
+ }
164
+ }
165
+
166
+ const filteredOptions = computed(() => {
167
+ if (!search.value)
168
+ return props.options
169
+ return props.options.filter(option =>
170
+ option.label.toLowerCase().includes(search.value.toLowerCase())
171
+ || option.description?.toLowerCase().includes(search.value.toLowerCase()),
172
+ )
173
+ })
174
+
175
+ const hasGroups = computed(() => filteredOptions.value.some(option => option.group))
176
+
177
+ const selectedValues = computed(() => props.modelValue || [])
178
+
179
+ const selectedOptions = computed(() => {
180
+ return props.options.filter(option => selectedValues.value.includes(option.value))
181
+ })
182
+
183
+ const groupedOptions = computed(() => {
184
+ const availableOptions = filteredOptions.value
185
+
186
+ if (!hasGroups.value)
187
+ return { __default: availableOptions }
188
+
189
+ const groups: Record<string, typeof availableOptions> = {}
190
+ availableOptions.forEach((option) => {
191
+ const groupKey = option.group ? option.group : '__default'
192
+
193
+ if (!groups[groupKey])
194
+ groups[groupKey] = []
195
+
196
+ groups[groupKey].push(option)
197
+ })
198
+ return groups
199
+ })
200
+
201
+ const hasAvailableOptions = computed(() => {
202
+ return Object.values(groupedOptions.value).some(group => group.length > 0)
203
+ })
204
+
205
+ const displayText = computed(() => {
206
+ if (selectedOptions.value.length === 0) {
207
+ return props.placeholder || t('common.selectOptions')
208
+ }
209
+
210
+ const maxDisplay = props.maxSelectedDisplay
211
+ if (selectedOptions.value.length <= maxDisplay) {
212
+ return selectedOptions.value.map(option => option.label).join(', ')
213
+ }
214
+
215
+ return `${selectedOptions.value.slice(0, maxDisplay).map(option => option.label).join(', ')} ${t('common.andMore', { count: selectedOptions.value.length - maxDisplay })}`
216
+ })
217
+
218
+ const previewDisplayText = computed(() => {
219
+ if (selectedOptions.value.length === 0) {
220
+ return { itemsText: '', remainingCount: 0 }
221
+ }
222
+
223
+ const maxItems = props.previewMaxItems
224
+ const displayItems = selectedOptions.value.slice(0, maxItems)
225
+ const remainingCount = selectedOptions.value.length - maxItems
226
+
227
+ if (remainingCount <= 0) {
228
+ return { itemsText: displayItems.map(option => option.label).join(', '), remainingCount: 0 }
229
+ }
230
+
231
+ const itemsText = displayItems.map(option => option.label).join(', ')
232
+ return { itemsText, remainingCount }
233
+ })
234
+
235
+ const remainingItems = computed(() => {
236
+ if (selectedOptions.value.length <= props.previewMaxItems) {
237
+ return []
238
+ }
239
+ return selectedOptions.value.slice(props.previewMaxItems)
240
+ })
241
+
242
+ const isSelected = (value: string) => selectedValues.value.includes(value)
243
+
244
+ const animationClass = `
245
+ will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade
246
+ `
247
+
248
+ const buttonLabel = computed(() => props.buttonLabel ?? (selectedOptions.value.length === 0 ? t('common.add') : t('common.addAnother')))
249
+
250
+ function toggleSelection(value: string) {
251
+ if (!value)
252
+ return
253
+
254
+ const option = props.options.find(option => option.value === value)
255
+ if (!option)
256
+ return
257
+
258
+ const newValues = [...selectedValues.value]
259
+ const index = newValues.indexOf(value)
260
+
261
+ if (index > -1) {
262
+ // Remove from selection
263
+ newValues.splice(index, 1)
264
+ emit('deselect', value)
265
+ }
266
+ else {
267
+ // Add to selection
268
+ newValues.push(value)
269
+ emit('select', value)
270
+ }
271
+
272
+ emit('update:modelValue', newValues)
273
+
274
+ if (props.closeOnSelect) {
275
+ nextTick(() => {
276
+ isOpen.value = false
277
+ if (triggerElementRef.value) {
278
+ const button = triggerElementRef.value.querySelector('button')
279
+ if (button) {
280
+ button.blur()
281
+ }
282
+ }
283
+ })
284
+ }
285
+ else {
286
+ nextTick(() => {
287
+ isOpen.value = true
288
+ })
289
+ }
290
+
291
+ if (option.to) {
292
+ navigateTo(option.to)
293
+ }
294
+ }
295
+
296
+ function removeSelection(value: string) {
297
+ const newValues = selectedValues.value.filter(v => v !== value)
298
+ emit('update:modelValue', newValues)
299
+ emit('deselect', value)
300
+ }
301
+
302
+ // Handle selection through the SelectRoot component
303
+ watch(innerValue, (value) => {
304
+ if (!value)
305
+ return
306
+
307
+ toggleSelection(value)
308
+ innerValue.value = ''
309
+ })
310
+
311
+ function handleDeleteSearchValue() {
312
+ search.value = ''
313
+ if (searchInputEl.value) {
314
+ searchInputEl.value.focus()
315
+ }
316
+ }
317
+
318
+ function handleSearchKeydown(e: KeyboardEvent) {
319
+ if ((e.metaKey || e.ctrlKey) && e.key === 'Backspace') {
320
+ e.preventDefault()
321
+ handleDeleteSearchValue()
322
+ }
323
+ }
324
+
325
+ function selectAll() {
326
+ if (selectedValues.value.length === filteredOptions.value.length) {
327
+ emit('update:modelValue', [])
328
+ }
329
+ else {
330
+ emit('update:modelValue', filteredOptions.value.map(option => option.value))
331
+ }
332
+ }
333
+ </script>
334
+
335
+ <template>
336
+ <div ref="container">
337
+ <SelectRoot v-model="innerValue" :open="isOpen" :disabled="readOnly" :required="required" @update:open="onOpenChange">
338
+ <div
339
+ ref="triggerElementRef"
340
+ v-bind="$attrs"
341
+ flex flex-col
342
+ @keydown.esc.stop="handleEscKey($event)"
343
+ >
344
+ <!-- Selected options - List variant (existing) -->
345
+ <div v-if="variant === 'list' && selectedOptions.length > 0" flex items-center w-full mb-8px>
346
+ <div
347
+ v-if="selectedOptions.length > 0"
348
+ flex items-center flex-wrap flex-1 min-w-0
349
+ >
350
+ <div
351
+ v-for="(option, index) in selectedOptions.slice(0, props.maxSelectedDisplay)"
352
+ :key="option.value"
353
+ transition-all
354
+ duration-200
355
+ p-8px
356
+ flex items-center gap-8px
357
+ body-12-medium
358
+ text-gray-900
359
+ w-full
360
+ border="0.5px gray-300"
361
+ :class="{
362
+ 'rounded-t-8px': index === 0,
363
+ 'rounded-b-8px': index === selectedOptions.length - 1,
364
+ 'border-b-0': index !== selectedOptions.length - 1,
365
+ }"
366
+ h-24px
367
+ >
368
+ <div
369
+ v-if="option.icon"
370
+ w-16px h-16px flex items-center justify-center
371
+ text-12px flex-shrink-0
372
+ >
373
+ <TelaIcon v-if="typeof option.icon === 'string'" :name="option.icon" />
374
+ <Component :is="option.icon" v-else class="!w-12px !h-12px" />
375
+ </div>
376
+ <div v-if="!option.icon && option.externalIconSrc" w-16px h-16px flex items-center justify-center flex-shrink-0>
377
+ <img :src="option.externalIconSrc" alt="" w-16px h-16px>
378
+ </div>
379
+ <div flex-1 flex items-center justify-start w-full overflow-hidden>
380
+ <span :title="option.label" truncate>{{ option.label }}</span>
381
+ </div>
382
+ <TelaIconButton
383
+ v-if="!readOnly"
384
+ icon="i-ph-trash text-gray-400"
385
+ size="xs"
386
+ color="secondary"
387
+ @click.stop.prevent="removeSelection(option.value)"
388
+ @mousedown.stop.prevent
389
+ @pointerdown.stop.prevent
390
+ />
391
+ </div>
392
+ <span
393
+ v-if="selectedOptions.length > (props.maxSelectedDisplay)"
394
+ text-12px text-gray-500 px-4px flex-shrink-0
395
+ w-full
396
+ border-t="0.5px gray-300"
397
+ pt-4px
398
+ >
399
+ +{{ selectedOptions.length - (props.maxSelectedDisplay) }} {{ t('common.more') }}
400
+ </span>
401
+ </div>
402
+ </div>
403
+
404
+ <Motion v-if="description" body-12-regular text-gray-500 :animate="{ opacity: 1, y: 0 }" :initial="{ opacity: 0, y: 10 }" :exit="{ opacity: 0, y: 10 }" :transition="{ duration: 0.43, ease: [0.18, 0.89, 0.32, 1.28] }">
405
+ {{ description }}
406
+ </Motion>
407
+ <!-- Preview variant - shows selected items inline -->
408
+ <div v-if="variant === 'preview'" flex items-center justify-between w-full mb-8px>
409
+ <span
410
+ class="body-12-semibold flex-1 text-left text-gray-700 max-w-90% flex items-center overflow-hidden"
411
+ >
412
+ <span class="truncate min-w-0">
413
+ {{ previewDisplayText.itemsText }}
414
+ </span>
415
+ <span
416
+ v-if="previewDisplayText.remainingCount > 0"
417
+ class="body-12-regular flex-shrink-0 ml-4px"
418
+ >
419
+ {{ t('common.and') }}
420
+ </span>
421
+ <PopoverList
422
+ v-if="previewDisplayText.remainingCount > 0"
423
+ :items="remainingItems.map(item => item.label)"
424
+ position="center"
425
+ :title="t('workstationComponents.assignedColumns')"
426
+ >
427
+ <template #trigger>
428
+ <span class="body-12-regular underline flex-shrink-0 ml-4px cursor-pointer hover:text-blue-600 transition-colors">
429
+ {{ t('common.otherCount', { count: previewDisplayText.remainingCount }) }}
430
+ </span>
431
+ </template>
432
+ </PopoverList>
433
+ </span>
434
+ </div>
435
+
436
+ <!-- Trigger -->
437
+ <slot
438
+ v-bind="{ selectedOptions, selectedValues, displayText, readOnly, buttonIcon, buttonLabel, toggleSelection }"
439
+ name="trigger"
440
+ >
441
+ <SelectTrigger
442
+ bg-white text-textcolor
443
+ b="1 gray-200"
444
+ rounded-8px
445
+ transition focus:b="#08344D/70" focus:ring="2 #08344D/12"
446
+ outline-none
447
+ class="data-[placeholder]:text-textcolor/50"
448
+ aria-label="Select multiple options"
449
+ w-full
450
+ v-bind="$attrs"
451
+ :class="[
452
+ (selectedOptions.length === 0) ? '!text-gray-600 !font-light' : '',
453
+ ]"
454
+ >
455
+ <span
456
+ v-if="selectedOptions.length === 0"
457
+ class="body-14-medium" :class="[
458
+ (selectedOptions.length === 0) ? 'text-gray-400 font-light' : '',
459
+ ]"
460
+ text-left flex-auto
461
+ >
462
+ {{ displayText }}
463
+ </span>
464
+ <TelaButton
465
+ size="sm"
466
+ :disabled="readOnly"
467
+ variant="ghost"
468
+ leading
469
+ w-full
470
+ :icon="buttonIcon"
471
+ :class="[
472
+ selectedOptions.length === 0 && 'hover:bg-transparent',
473
+ ]"
474
+ @click="toggleSelection('')"
475
+ >
476
+ <span>{{ buttonLabel }}</span>
477
+ </TelaButton>
478
+ </SelectTrigger>
479
+ </slot>
480
+ </div>
481
+
482
+ <SelectPortal>
483
+ <SelectContent
484
+ class="bg-white rounded-8px"
485
+ flying-shadow
486
+ b="0.5px gray-300" overflow-hidden
487
+ :class="[animationClass]"
488
+ :side-offset="4"
489
+ relative
490
+ position="popper"
491
+ :style="{
492
+ width: `${contentWidth}px`,
493
+ minWidth: '328px',
494
+ }"
495
+ avoid-collisions
496
+ max-h-400px
497
+ z-999
498
+ >
499
+ <SelectScrollUpButton class="flex items-center justify-center h-25px bg-white text-dark cursor-default">
500
+ <TelaIcon name="i-ph-caret-up" text="16px" />
501
+ </SelectScrollUpButton>
502
+
503
+ <!-- Search input -->
504
+ <div v-if="allowSearch" flex items-center px-12px py-8px border-b="0.5px gray-300">
505
+ <TelaIcon name="i-ph-magnifying-glass-bold" text-gray-500 mr-8px />
506
+ <input
507
+ ref="searchInputEl"
508
+ v-model="search"
509
+ :placeholder="searchPlaceholder"
510
+ body-14-regular text-gray-900
511
+ class="placeholder:text-gray-400 focus-visible:outline-none flex-1"
512
+ @click.stop
513
+ @keydown.stop="handleSearchKeydown"
514
+ >
515
+ </div>
516
+ <div v-if="filteredOptions.length > 0" flex items-center justify-between pl-6px pr-10px py-8px border-b="0.5px gray-200">
517
+ <span body-12-semibold text-gray-700>{{ t('common.itemCount', { count: filteredOptions.length }) }}</span>
518
+
519
+ <TelaButton variant="ghost" size="sm" class="rounded-lg" @click="selectAll">
520
+ <span
521
+ v-if="selectedValues.length !== filteredOptions.length"
522
+ body-12-regular
523
+ text-gray-900
524
+ underline
525
+ >
526
+ {{ t('common.selectAll') }}
527
+ </span>
528
+ <span
529
+ v-else
530
+ body-12-regular
531
+ text-gray-900
532
+ underline
533
+ >
534
+ {{ t('common.clearAll') }}
535
+ </span>
536
+ </TelaButton>
537
+ </div>
538
+
539
+ <SelectViewport class="p-5px" w-full>
540
+ <div
541
+ v-if="!hasAvailableOptions"
542
+ class="flex flex-col items-center justify-center gap-5 px-7 py-12"
543
+ >
544
+ <div class="flex flex-col items-center justify-center gap-3">
545
+ <TelaIcon name="i-ph-smiley-sad" size="24px" class="text-gray-400" />
546
+ <span class="text-xl font-medium leading-none text-center">
547
+ {{ search.trim() ? t('common.noOptionsFound', { search: search.trim() }) : t('common.noRemainingValues') }}
548
+ </span>
549
+ <span v-if="search.trim()" class="inline-flex gap-1.5 text-md text-gray-500 items-center">
550
+ Press
551
+ <div class="flex items-center gap-1">
552
+ <div class="p-2px bg-gray-200 rounded">
553
+ <TelaIcon name="i-ph-command" size="16px" />
554
+ </div>
555
+ <div class="p-2px bg-gray-200 rounded">
556
+ <TelaIcon name="i-ph-backspace" size="16px" />
557
+ </div>
558
+ </div>
559
+ {{ t('common.toClear') }}
560
+ </span>
561
+ </div>
562
+ <TelaButton
563
+ v-if="search.trim()"
564
+ variant="secondary"
565
+ size="sm"
566
+ class="rounded-lg"
567
+ @click="handleDeleteSearchValue"
568
+ >
569
+ {{ t('common.clear') }}
570
+ </TelaButton>
571
+ </div>
572
+
573
+ <SelectGroup
574
+ v-for="[group, groupOptions], idx of Object.entries(groupedOptions!)"
575
+ v-else
576
+ :key="idx"
577
+ >
578
+ <SelectLabel v-if="hasGroups && group !== '__default'" body-12-regular text="gray-700" mt-4px p="6px" pb-8px capitalize>
579
+ {{ group }}
580
+ </SelectLabel>
581
+ <SelectItem
582
+ v-for="option in groupOptions"
583
+ :key="option.value"
584
+ :value="option.value"
585
+ as-child
586
+ @select.prevent
587
+ >
588
+ <button
589
+ v-test="testId ? `${testId}:option:${option.value}` : undefined"
590
+ as-child
591
+ p-6px
592
+ rounded-4px
593
+ transition
594
+ outline-none
595
+ select-none
596
+ flex
597
+ w-full
598
+ items-center
599
+ class="
600
+ data-[disabled]:text-gray-300
601
+ data-[disabled]:pointer-events-none
602
+ data-[highlighted]:bg-#F5F6F6
603
+ data-[highlighted]:outline-none
604
+ hover:cursor-pointer
605
+ text-gray-900
606
+ "
607
+ :class="[
608
+ isSelected(option.value) && 'bg-blue-50 border-blue-200',
609
+ ]"
610
+ >
611
+ <TelaCheckbox
612
+ :model-value="isSelected(option.value)"
613
+ :disabled="readOnly"
614
+ cursor-pointer
615
+ mr-12px
616
+ min-w-16px
617
+ />
618
+ <div
619
+ v-if="option.icon" mr-12px rounded-4px w-40px h-40px flex items-center justify-center
620
+ b="0.5px gray-200"
621
+ text="gray-900"
622
+ bg-white flex-none
623
+ >
624
+ <TelaIcon v-if="typeof option.icon === 'string'" size="sm" :name="option.icon" />
625
+ <Component :is="option.icon" v-else class="!w-24px !h-24px" />
626
+ </div>
627
+ <div v-if="!option.icon && option.externalIconSrc" mr-12px rounded-4px w-16px h-36px flex items-center justify-center>
628
+ <img :src="option.externalIconSrc" alt="" min-w-18px h-18px>
629
+ </div>
630
+ <div flex="~ col" overflow-hidden>
631
+ <p text="gray-900" body-14-medium text-start truncate :title="option.label">
632
+ {{ option.label }}
633
+ </p>
634
+
635
+ <span v-if="option.description" text="gray-700" body-9-regular text-start>
636
+ {{ option.description }}
637
+ </span>
638
+ </div>
639
+ <div flex-auto />
640
+ <SelectItemIndicator flex items-center justify-center>
641
+ <div>
642
+ <TelaIcon
643
+ name="i-ph-square"
644
+ text="16px"
645
+ class="text-gray-400"
646
+ />
647
+ </div>
648
+ </SelectItemIndicator>
649
+ </button>
650
+ </SelectItem>
651
+ </SelectGroup>
652
+ </SelectViewport>
653
+
654
+ <SelectScrollDownButton class="flex items-center justify-center h-25px bg-white text-dark cursor-default">
655
+ <TelaIcon name="i-ph-caret-down" text="16px" />
656
+ </SelectScrollDownButton>
657
+ <div class="p-8px border-t border-gray-300">
658
+ <TelaButton variant="secondary" w-full @click="isOpen = false">
659
+ {{ closeButtonLabel }}
660
+ </TelaButton>
661
+ </div>
662
+ </SelectContent>
663
+ </SelectPortal>
664
+ </SelectRoot>
665
+ </div>
666
+ </template>