@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,497 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed } from 'vue'
3
+ import type { Component } from 'vue'
4
+ import { useMagicKeys } from '@vueuse/core'
5
+ import { useI18n } from 'vue-i18n'
6
+
7
+ import ComboboxRoot from './combobox-root.vue'
8
+ import ComboboxAnchor from './combobox-anchor.vue'
9
+ import ComboboxEmpty from './combobox-empty.vue'
10
+ import ComboboxGroup from './combobox-group.vue'
11
+ import ComboboxInput from './combobox-input.vue'
12
+ import ComboboxItem from './combobox-item.vue'
13
+ import ComboboxItemIndicator from './combobox-indicator.vue'
14
+ import ComboboxList from './combobox-list.vue'
15
+ import ComboboxLabel from './combobox-label.vue'
16
+ import ComboboxTrigger from './combobox-trigger.vue'
17
+
18
+ interface ComboboxOption {
19
+ value: string
20
+ label: string
21
+ description?: string
22
+ icon?: string | Component
23
+ externalIconSrc?: string
24
+ maxInputTokens?: number
25
+ maxOutputTokens?: number
26
+ group?: string
27
+ badge?: string
28
+ tabs?: string[]
29
+ to?: string
30
+ isMultiModal?: boolean
31
+ features?: Array<{ text: string }>
32
+ cost?: number
33
+ }
34
+
35
+ type ComboboxOptions = ComboboxOption[]
36
+
37
+ const props = withDefaults(defineProps<{
38
+ modelValue?: string
39
+ placeholder?: string
40
+ inputPlaceholder?: string
41
+ options: ComboboxOptions
42
+ readOnly?: boolean
43
+ size?: 'xs' | 'sm' | 'md'
44
+ iconWithBackground?: {
45
+ size: 'xs' | 'sm' | 'md'
46
+ backgroundClass: string
47
+ }
48
+ compact?: boolean
49
+ hasGroupingLabels?: boolean
50
+ colorIcon?: string
51
+ disablePortal?: boolean
52
+ labelClass?: string
53
+ triggerClass?: string
54
+ iconClass?: string
55
+ contentClass?: string
56
+ labelItemClass?: string
57
+ labelIconVisible?: boolean
58
+ descriptionClass?: string
59
+ hasSearchbar?: boolean
60
+ }>(), {
61
+ compact: true,
62
+ hasGroupingLabels: true,
63
+ disablePortal: false,
64
+ contentClass: '',
65
+ triggerClass: '',
66
+ labelItemClass: '',
67
+ labelIconVisible: true,
68
+ descriptionClass: '',
69
+ hasSearchbar: true,
70
+ })
71
+
72
+ const emit = defineEmits<{
73
+ 'update:modelValue': [value: string]
74
+ 'select': [value: string]
75
+ }>()
76
+
77
+ const { t } = useI18n()
78
+
79
+ const selectedTab = ref('all')
80
+ const search = ref('')
81
+ const innerValue = ref<string>('')
82
+ const isOpen = ref(false)
83
+
84
+ const currentOption = computed(() => {
85
+ if (!innerValue.value && !props.modelValue && props.placeholder) {
86
+ return { label: props.placeholder, value: '' }
87
+ }
88
+
89
+ const found = props.options.find(option => option.value === innerValue.value || option.value === props.modelValue)
90
+
91
+ return found ?? props.options[0] ?? { label: props.placeholder || t('common.select'), value: '' }
92
+ })
93
+
94
+ const tabs = computed(() => {
95
+ const allTabs = props.options.reduce((acc, option) => {
96
+ if (option.tabs) {
97
+ option.tabs.forEach((tab) => {
98
+ if (!acc.find(t => t.tab === tab)) {
99
+ const labelKey = `models.tabs.${tab}`
100
+ const label = t(labelKey) !== labelKey ? t(labelKey) : tab.charAt(0).toUpperCase() + tab.slice(1)
101
+ acc.push({ tab, label })
102
+ }
103
+ })
104
+ }
105
+ return acc
106
+ }, [] as Array<{ tab: string, label: string }>)
107
+
108
+ const hasAll = allTabs.find(t => t.tab === 'all')
109
+
110
+ if (!hasAll) {
111
+ allTabs.unshift({ tab: 'all', label: t('models.tabs.all') })
112
+ }
113
+
114
+ return allTabs
115
+ })
116
+
117
+ const hasGroups = computed(() => props.options.some(option => option.group))
118
+
119
+ const hasTabs = computed(() => props.options.some(option => option.tabs && option.tabs.length > 0))
120
+
121
+ const groupedOptions = computed(() => {
122
+ let filteredOptions = props.options
123
+
124
+ if (selectedTab.value !== 'all') {
125
+ filteredOptions = props.options.filter(option =>
126
+ option.tabs?.includes(selectedTab.value),
127
+ )
128
+ }
129
+
130
+ if (search.value.trim()) {
131
+ filteredOptions = filteredOptions.filter(option =>
132
+ option.label.toLowerCase().includes(search.value.toLowerCase())
133
+ || option.description?.toLowerCase().includes(search.value.toLowerCase()),
134
+ )
135
+
136
+ filteredOptions = filteredOptions.filter((item, idx, arr) =>
137
+ arr.findIndex(i => i.value === item.value) === idx,
138
+ )
139
+ }
140
+
141
+ if (!hasGroups.value) {
142
+ return [{ heading: t('common.options'), children: filteredOptions }]
143
+ }
144
+
145
+ const groups: Record<string, typeof filteredOptions> = {}
146
+
147
+ filteredOptions.forEach((option) => {
148
+ const groupKey = option.group || t('models.groups.other')
149
+ if (!groups[groupKey]) {
150
+ groups[groupKey] = []
151
+ }
152
+ groups[groupKey].push(option)
153
+ })
154
+
155
+ return Object.entries(groups).map(([heading, children]) => ({
156
+ heading,
157
+ children: children.filter((item, idx, arr) =>
158
+ arr.findIndex(i => i.value === item.value) === idx,
159
+ ),
160
+ })).filter(group => group.children.length > 0)
161
+ })
162
+
163
+ const handleDeleteSearchValue = () => search.value = ''
164
+
165
+ const keys = useMagicKeys()
166
+ const CmdBackspace = keys['Cmd+Backspace']
167
+
168
+ watch(CmdBackspace as Ref<boolean>, (v) => {
169
+ if (v)
170
+ handleDeleteSearchValue()
171
+ })
172
+
173
+ function handleFormatNumber(value: number | undefined) {
174
+ if (!value)
175
+ return '0'
176
+
177
+ if (value >= 1000) {
178
+ return `${(value / 1000).toFixed(0)}k`
179
+ }
180
+
181
+ return value
182
+ }
183
+
184
+ function renderCostIndicator(cost: number | undefined) {
185
+ if (cost === undefined)
186
+ return null
187
+
188
+ const totalSymbols = 5
189
+ const costValue = Math.min(Math.max(cost, 0), totalSymbols)
190
+ const blackSymbols = '$'.repeat(costValue)
191
+ const graySymbols = '$'.repeat(totalSymbols - costValue)
192
+
193
+ return { blackSymbols, graySymbols }
194
+ }
195
+
196
+ function onOpenChange(open: boolean) {
197
+ isOpen.value = open
198
+ }
199
+
200
+ function handleSelect(option: any) {
201
+ emit('select', option.value)
202
+ emit('update:modelValue', option.value)
203
+ }
204
+
205
+ watch(innerValue, (value) => {
206
+ if (!value)
207
+ return
208
+
209
+ const option = props.options.find(option => option.value === value)
210
+ if (!option)
211
+ return
212
+
213
+ innerValue.value = ''
214
+
215
+ emit('select', value)
216
+ emit('update:modelValue', value)
217
+ })
218
+ </script>
219
+
220
+ <template>
221
+ <ComboboxRoot
222
+ v-model="innerValue" by="value" :open="isOpen" :disabled="readOnly" @update:open="onOpenChange"
223
+ @update:model-value="handleSelect"
224
+ >
225
+ <ComboboxAnchor>
226
+ <ComboboxTrigger
227
+ as="button"
228
+ tabindex="0"
229
+ :class="cn(
230
+ 'group select-none flex items-center justify-between gap-3 rounded-10px px-8px py-7px bg-white border-[0.5px] border-gray-300 transition hover:bg-gray-100 data-[state=open]:bg-gray-100',
231
+ !compact && 'w-full !py-2.5 pl-3 pr-3',
232
+ props.triggerClass,
233
+ )"
234
+ >
235
+ <div :class="cn('flex items-center', !compact ? 'gap-2' : 'gap-1')">
236
+ <div v-if="currentOption?.icon && labelIconVisible" :class="cn('group-data-[state=open]:opacity-70', props.iconClass)">
237
+ <div v-if="currentOption?.icon && !(currentOption?.value === '')">
238
+ <TelaIcon v-if="typeof currentOption.icon === 'string'" :name="currentOption.icon" :color="colorIcon" />
239
+ <Component
240
+ :is="currentOption.icon"
241
+ v-else
242
+ :class="cn((currentOption?.label.startsWith('DeepSeek') || currentOption?.label.startsWith('Gemini')) && 'scale-75')"
243
+ />
244
+ </div>
245
+ <div v-if="!currentOption?.icon && currentOption?.externalIconSrc">
246
+ <img :src="currentOption.externalIconSrc" :alt="t('models.accessibility.modelIcon')" w-16px h-16px>
247
+ </div>
248
+ </div>
249
+ <span
250
+ :class="cn('truncate max-w-120px text-gray-900 group-data-[state=open]:text-gray-500',
251
+ compact ? 'text-sm font-580' : 'text-body-14-regular', props.labelClass)"
252
+ >
253
+ {{ currentOption?.label }}
254
+ </span>
255
+ </div>
256
+ <TelaIcon name="i-ph-caret-down" :class="cn('op-60 transition-all ease-out duration-150', isOpen && 'rotate-180')" size="12px" />
257
+ </ComboboxTrigger>
258
+ </ComboboxAnchor>
259
+ <ComboboxList :class="cn('z-999', compact ? 'w-440px' : 'w-327px!', props.contentClass)" :disable-portal="props.disablePortal" align="start">
260
+ <div v-if="hasSearchbar" class="relative">
261
+ <span class="absolute inset-y-0 start-0 flex items-center justify-center px-2.5">
262
+ <TelaIcon name="i-ph-magnifying-glass" :size="compact ? '12px' : '14px'" class="text-gray-400" />
263
+ </span>
264
+ <ComboboxInput
265
+ v-model="search" :class="cn('rounded-b-none', compact ? 'pl-7' : 'pl-8')"
266
+ :placeholder="inputPlaceholder || t('models.labels.search')"
267
+ />
268
+ <div v-if="search.length > 0" class="absolute inset-y-0 end-0 flex items-center justify-center px-2.5">
269
+ <button class="flex items-center justify-center" @click="handleDeleteSearchValue">
270
+ <TelaIcon name="i-ph-x-circle" size="16px" class="text-gray-400" />
271
+ </button>
272
+ </div>
273
+ </div>
274
+ <template v-if="hasTabs">
275
+ <TelaTabsRoot v-model="selectedTab" :default-value="selectedTab">
276
+ <TelaTabsList class="sticky z-10 top-0 shrink-0 flex gap-2 w-full">
277
+ <TelaTabsIndicator />
278
+ <TelaTabsTrigger v-for="tab in tabs" :key="tab.tab" :value="tab.tab">
279
+ {{ tab.label }}
280
+ </TelaTabsTrigger>
281
+ </TelaTabsList>
282
+ <TelaTabsContent :value="selectedTab" class="max-h-262px overflow-y-auto no-scrollbar">
283
+ <ComboboxEmpty
284
+ v-if="groupedOptions.length === 0 || groupedOptions.every(g => g.children.length === 0)"
285
+ class="flex flex-col items-center justify-center gap-5 px-7"
286
+ >
287
+ <div class="flex flex-col items-center justify-center gap-3">
288
+ <TelaIcon name="i-ph-smiley-sad" size="24px" class="text-gray-400" />
289
+ <span class="text-xl font-medium leading-none">
290
+ {{ search.trim() ? t('models.messages.noModelsFoundWith', { search }) : t('models.messages.noModelsFound') }}
291
+ </span>
292
+ <span v-if="search.trim()" class="inline-flex items-center gap-1.5 text-md text-gray-500">
293
+ {{ t('common.press') }}
294
+ <div class="flex items-center gap-1">
295
+ <div class="p-2px bg-gray-200 rounded">
296
+ <TelaIcon name="i-ph-command" size="16px" />
297
+ </div>
298
+ <div class="p-2px bg-gray-200 rounded">
299
+ <TelaIcon name="i-ph-backspace" size="16px" />
300
+ </div>
301
+ </div>
302
+ {{ t('common.toClear') }}
303
+ </span>
304
+ </div>
305
+ <TelaButton
306
+ v-if="search.trim()" variant="secondary" size="sm"
307
+ @click="handleDeleteSearchValue"
308
+ >
309
+ {{ t('common.clear') }}
310
+ </TelaButton>
311
+ </ComboboxEmpty>
312
+ <template v-for="group in groupedOptions" :key="group.heading">
313
+ <ComboboxGroup :heading="group.heading">
314
+ <ComboboxLabel v-if="!search.trim() && hasGroupingLabels">
315
+ {{ group.heading }}
316
+ </ComboboxLabel>
317
+ <ComboboxItem v-for="item in group.children" :key="item.value" :value="item" :class="cn('group py-2', !compact && '!px-1.5')">
318
+ <div class="flex items-center">
319
+ <div :class="cn('flex items-center w-270px', compact ? 'gap-2' : 'gap-1.5')">
320
+ <div v-if="item.icon" class="w-5 h-5 shrink-0 flex items-center justify-center">
321
+ <TelaIcon
322
+ v-if="typeof item.icon === 'string'" :name="item.icon" :color="colorIcon" :size="iconWithBackground?.size"
323
+ :background-class="iconWithBackground?.backgroundClass"
324
+ />
325
+ <Component
326
+ :is="item.icon"
327
+ v-else
328
+ :class="cn((item?.label.startsWith('DeepSeek') || item?.label.startsWith('Gemini')) && 'scale-75')"
329
+ />
330
+ </div>
331
+ <div v-if="!item.icon && item?.externalIconSrc">
332
+ <img :src="item.externalIconSrc" :alt="item.label" class="w-5 h-5">
333
+ </div>
334
+ <div class="flex flex-col">
335
+ <div class="flex items-center gap-1">
336
+ <span :class="cn('font-medium truncate max-w-140px', compact ? 'text-sm font-580' : 'text-body-14-regular', labelItemClass)">{{ item.label }}</span>
337
+ <TelaBadge v-if="item.isMultiModal" variant="filled" class="py-[2px] bg-gray-100" text-class="leading-none">
338
+ {{ t('models.labels.multimodal') }}
339
+ </TelaBadge>
340
+ <span v-if="item.cost !== undefined" class="text-10px font-580 ml-1px">
341
+ <span class="text-black-900">{{ renderCostIndicator(item.cost)?.blackSymbols }}</span>
342
+ <span class="text-gray-300">{{ renderCostIndicator(item.cost)?.graySymbols }}</span>
343
+ </span>
344
+ </div>
345
+ <span v-if="item.description" :class="cn('font-normal text-sm text-balance leading-none text-gray-500', descriptionClass)">
346
+ {{ item.description }}
347
+ </span>
348
+ <slot name="tags" :option="item" />
349
+ </div>
350
+ </div>
351
+ <div class="flex gap-3">
352
+ <div v-if="item.maxInputTokens" class="flex flex-col gap-1.5">
353
+ <span class="text-gray-400 leading-none text-9px uppercase tracking-wider font-semibold">
354
+ {{ t('models.labels.inputMax') }}
355
+ </span>
356
+ <span class="text-gray-400 leading-none text-11px uppercase font-semibold group-data-[highlighted]:text-gray-700">
357
+ {{ handleFormatNumber(item.maxInputTokens) }}
358
+ </span>
359
+ </div>
360
+ <div v-if="item.maxOutputTokens" class="flex flex-col gap-1.5">
361
+ <span class="text-gray-400 leading-none text-9px uppercase tracking-wider font-semibold">
362
+ {{ t('models.labels.outputMax') }}
363
+ </span>
364
+ <span class="text-gray-400 leading-none text-11px uppercase font-semibold group-data-[highlighted]:text-gray-700">
365
+ {{ handleFormatNumber(item.maxOutputTokens) }}
366
+ </span>
367
+ </div>
368
+ </div>
369
+ </div>
370
+ <div class="flex gap-2 items-center">
371
+ <ComboboxItemIndicator>
372
+ <TelaIcon name="i-ph-check" size="14px" />
373
+ </ComboboxItemIndicator>
374
+ </div>
375
+ </ComboboxItem>
376
+ </ComboboxGroup>
377
+ </template>
378
+ </TelaTabsContent>
379
+ </TelaTabsRoot>
380
+ </template>
381
+ <template v-else>
382
+ <div class="max-h-262px overflow-y-auto no-scrollbar">
383
+ <ComboboxEmpty
384
+ v-if="groupedOptions.length === 0 || groupedOptions.every(g => g.children.length === 0)"
385
+ class="flex flex-col items-center justify-center gap-5 px-7"
386
+ >
387
+ <div class="flex flex-col items-center justify-center gap-3">
388
+ <TelaIcon name="i-ph-smiley-sad" size="24px" class="text-gray-400" />
389
+ <span class="text-xl font-medium leading-none">
390
+ {{ search.trim() ? t('models.messages.noModelsFoundWith', { search }) : t('models.messages.noModelsFound') }}
391
+ </span>
392
+ <span v-if="search.trim()" class="inline-flex gap-1.5 text-md text-gray-500">
393
+ {{ t('common.press') }}
394
+ <div class="flex items-center gap-1">
395
+ <div class="p-2px bg-gray-200 rounded">
396
+ <TelaIcon name="i-ph-command" size="16px" />
397
+ </div>
398
+ <div class="p-2px bg-gray-200 rounded">
399
+ <TelaIcon name="i-ph-backspace" size="16px" />
400
+ </div>
401
+ </div>
402
+ {{ t('common.toClear') }}
403
+ </span>
404
+ </div>
405
+ <TelaButton
406
+ v-if="search.trim()" variant="secondary" size="sm" class="rounded-lg"
407
+ @click="handleDeleteSearchValue"
408
+ >
409
+ {{ t('common.clear') }}
410
+ </TelaButton>
411
+ </ComboboxEmpty>
412
+ <template v-for="group in groupedOptions" :key="group.heading">
413
+ <ComboboxGroup :heading="group.heading">
414
+ <ComboboxLabel v-if="!search.trim() && hasGroupingLabels">
415
+ {{ group.heading }}
416
+ </ComboboxLabel>
417
+ <ComboboxItem v-for="item in group.children" :key="item.value" :value="item" :class="cn('group py-2', !compact && '!px-1.5')">
418
+ <div class="flex items-center">
419
+ <div :class="cn('flex items-center w-250px', compact ? 'gap-2' : 'gap-1.5')">
420
+ <div v-if="item.icon" class="w-5 h-5 shrink-0 flex items-center justify-center">
421
+ <TelaIcon
422
+ v-if="typeof item.icon === 'string'" :name="item.icon" :color="colorIcon" :size="iconWithBackground?.size"
423
+ :background-class="iconWithBackground?.backgroundClass"
424
+ />
425
+ <Component
426
+ :is="item.icon"
427
+ v-else
428
+ :class="cn((item?.label.startsWith('DeepSeek') || item?.label.startsWith('Gemini')) && 'scale-75')"
429
+ />
430
+ </div>
431
+ <div v-if="!item.icon && item?.externalIconSrc">
432
+ <img :src="item.externalIconSrc" :alt="item.label" class="w-5 h-5">
433
+ </div>
434
+ <div class="flex flex-col">
435
+ <div class="flex items-center gap-1">
436
+ <span :class="cn('font-medium truncate max-w-140px', compact ? 'text-sm font-580' : 'text-body-14-regular', labelItemClass)">{{ item.label }}</span>
437
+ <TelaBadge v-if="item.isMultiModal" variant="filled" class="py-[2px] bg-gray-100" text-class="leading-none">
438
+ {{ t('models.labels.multimodal') }}
439
+ </TelaBadge>
440
+ <span v-if="item.cost !== undefined" class="text-10px font-580 ml-1px">
441
+ <span class="text-black-900">{{ renderCostIndicator(item.cost)?.blackSymbols }}</span>
442
+ <span class="text-gray-300">{{ renderCostIndicator(item.cost)?.graySymbols }}</span>
443
+ </span>
444
+ </div>
445
+ <span v-if="item.description" :class="cn('font-normal text-sm leading-none text-gray-500', descriptionClass)">
446
+ {{ item.description }}
447
+ </span>
448
+ <slot name="tags" :option="item" />
449
+ </div>
450
+ </div>
451
+ <div v-if="item.maxInputTokens || item.maxOutputTokens" class="flex gap-3">
452
+ <div class="flex flex-col gap-1.5">
453
+ <span class="text-gray-400 leading-none text-9px uppercase tracking-wider font-semibold">
454
+ {{ t('models.labels.inputMax') }}
455
+ </span>
456
+ <span class="text-gray-400 leading-none text-11px uppercase font-semibold group-data-[highlighted]:text-gray-700">
457
+ {{ handleFormatNumber(item.maxInputTokens) }}
458
+ </span>
459
+ </div>
460
+ <div class="flex flex-col gap-1.5">
461
+ <span class="text-gray-400 leading-none text-9px uppercase tracking-wider font-semibold">
462
+ {{ t('models.labels.outputMax') }}
463
+ </span>
464
+ <span class="text-gray-400 leading-none text-11px uppercase font-semibold group-data-[highlighted]:text-gray-700">
465
+ {{ handleFormatNumber(item.maxOutputTokens) }}
466
+ </span>
467
+ </div>
468
+ </div>
469
+ </div>
470
+ <div class="flex gap-2 items-center">
471
+ <ComboboxItemIndicator>
472
+ <TelaIcon name="i-ph-check" size="14px" />
473
+ </ComboboxItemIndicator>
474
+ </div>
475
+ </ComboboxItem>
476
+ </ComboboxGroup>
477
+ </template>
478
+ </div>
479
+ </template>
480
+ </ComboboxList>
481
+ </ComboboxRoot>
482
+ </template>
483
+
484
+ <style scoped>
485
+ .nuxt-icon {
486
+ margin-bottom: 0;
487
+ }
488
+
489
+ .no-scrollbar {
490
+ -ms-overflow-style: none;
491
+ scrollbar-width: none;
492
+
493
+ &::-webkit-scrollbar {
494
+ display: none;
495
+ }
496
+ }
497
+ </style>
@@ -0,0 +1,22 @@
1
+ <script setup lang="ts">
2
+ import type { DialogRootEmits, DialogRootProps } from 'reka-ui'
3
+ import { useForwardPropsEmits } from 'reka-ui'
4
+ import Command from './command.vue'
5
+ import Dialog from './dialog-base.vue'
6
+ import DialogContent from './dialog-content.vue'
7
+
8
+ const props = defineProps<DialogRootProps>()
9
+ const emits = defineEmits<DialogRootEmits>()
10
+
11
+ const forwarded = useForwardPropsEmits(props, emits)
12
+ </script>
13
+
14
+ <template>
15
+ <Dialog v-bind="forwarded">
16
+ <DialogContent class="overflow-hidden p-0 shadow-lg">
17
+ <Command class="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
18
+ <slot />
19
+ </Command>
20
+ </DialogContent>
21
+ </Dialog>
22
+ </template>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import type { PrimitiveProps } from 'reka-ui'
3
+ import { Primitive } from 'reka-ui'
4
+ import { computed } from 'vue'
5
+ import type { HTMLAttributes } from 'vue'
6
+ import { useCommand } from './utils'
7
+
8
+ const props = defineProps<PrimitiveProps & { class?: HTMLAttributes['class'] }>()
9
+
10
+ const delegatedProps = computed(() => {
11
+ const { class: _, ...delegated } = props
12
+
13
+ return delegated
14
+ })
15
+
16
+ const { filterState } = useCommand()
17
+ const isRender = computed(() => !!filterState.search && filterState.filtered.count === 0,
18
+ )
19
+ </script>
20
+
21
+ <template>
22
+ <Primitive v-if="isRender" v-bind="delegatedProps" :class="cn('py-6 text-center text-sm', props.class)">
23
+ <slot />
24
+ </Primitive>
25
+ </template>
@@ -0,0 +1,46 @@
1
+ <script setup lang="ts">
2
+ import type { ListboxGroupProps } from 'reka-ui'
3
+ import { ListboxGroup, ListboxGroupLabel, useId } from 'reka-ui'
4
+ import { computed, onMounted, onUnmounted } from 'vue'
5
+ import type { HTMLAttributes } from 'vue'
6
+ import { provideCommandGroupContext, useCommand } from './utils'
7
+
8
+ const props = defineProps<ListboxGroupProps & {
9
+ class?: HTMLAttributes['class']
10
+ heading?: string
11
+ }>()
12
+
13
+ const delegatedProps = computed(() => {
14
+ const { class: _, ...delegated } = props
15
+
16
+ return delegated
17
+ })
18
+
19
+ const { allGroups, filterState } = useCommand()
20
+ const id = useId()
21
+
22
+ const isRender = computed(() => !filterState.search ? true : filterState.filtered.groups.has(id))
23
+
24
+ provideCommandGroupContext({ id })
25
+ onMounted(() => {
26
+ if (!allGroups.value.has(id))
27
+ allGroups.value.set(id, new Set())
28
+ })
29
+ onUnmounted(() => {
30
+ allGroups.value.delete(id)
31
+ })
32
+ </script>
33
+
34
+ <template>
35
+ <ListboxGroup
36
+ v-bind="delegatedProps"
37
+ :id="id"
38
+ :class="cn('overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground', props.class)"
39
+ :hidden="isRender ? undefined : true"
40
+ >
41
+ <ListboxGroupLabel v-if="heading" class="px-2 py-1.5 text-xs font-medium text-muted-foreground">
42
+ {{ heading }}
43
+ </ListboxGroupLabel>
44
+ <slot />
45
+ </ListboxGroup>
46
+ </template>
@@ -0,0 +1,38 @@
1
+ <script setup lang="ts">
2
+ import { Search } from 'lucide-vue-next'
3
+ import { ListboxFilter, useForwardProps } from 'reka-ui'
4
+ import type { ListboxFilterProps } from 'reka-ui'
5
+ import { computed } from 'vue'
6
+ import type { HTMLAttributes } from 'vue'
7
+ import { useCommand } from './utils'
8
+
9
+ defineOptions({
10
+ inheritAttrs: false,
11
+ })
12
+
13
+ const props = defineProps<ListboxFilterProps & {
14
+ class?: HTMLAttributes['class']
15
+ }>()
16
+
17
+ const delegatedProps = computed(() => {
18
+ const { class: _, ...delegated } = props
19
+
20
+ return delegated
21
+ })
22
+
23
+ const forwardedProps = useForwardProps(delegatedProps)
24
+
25
+ const { filterState } = useCommand()
26
+ </script>
27
+
28
+ <template>
29
+ <div class="flex items-center border-[1px] mt-3 mb-1 mx-4 rounded-[8px] pl-[12px] pr-[8px] [box-shadow:0px_1px_4px_0px_rgba(103,127,148,0.03)]" cmdk-input-wrapper>
30
+ <Search class="mr-2 h-4 w-4 shrink-0 opacity-50" />
31
+ <ListboxFilter
32
+ v-bind="{ ...forwardedProps, ...$attrs }"
33
+ v-model="filterState.search"
34
+ auto-focus
35
+ :class="cn('flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)"
36
+ />
37
+ </div>
38
+ </template>