@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,47 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import TagsSelect from './tags-select.vue'
3
+
4
+ const meta: Meta<typeof TagsSelect> = {
5
+ title: 'Core/TagsSelect',
6
+ component: TagsSelect,
7
+ parameters: {
8
+ layout: 'centered',
9
+ docs: {
10
+ description: {
11
+ component: 'A button variant of tags that displays multiple overlapping colored dots (up to 3) with optional icons. Used for actions like adding tags or showing tag groups. The dots are displayed with a mask effect to create an overlapping appearance. Each dot can have a custom color, icon, and icon color. The component opens a popover with a full tag management interface that allows users to create new tags, edit existing tags, and select/deselect tags. Supports 16 different color options and maintains a list of all created tags.',
12
+ },
13
+ },
14
+ },
15
+ argTypes: {
16
+ class: {
17
+ control: 'text',
18
+ description: 'Custom CSS classes for the button container.',
19
+ },
20
+ labelClass: {
21
+ control: 'text',
22
+ description: 'Custom CSS classes for the label text.',
23
+ },
24
+ modelValue: {
25
+ control: 'object',
26
+ description: 'Array of selected tags. Each tag must have a `color` (one of 16 predefined colors) and a `name` (string). The `isSelected` property is computed internally and should not be included when passing tags via v-model. This prop supports v-model binding.',
27
+ },
28
+ options: {
29
+ control: 'object',
30
+ description: 'Array of available tag options to display in the popover. These tags will be merged with the selected tags to form the complete list of available tags. Each tag must have a `color` and `name` property. The `isSelected` property is computed internally based on `modelValue` and should not be included when passing tags.',
31
+ },
32
+ },
33
+ }
34
+
35
+ export default meta
36
+
37
+ type Story = StoryObj<typeof TagsSelect>
38
+
39
+ export const Default: Story = {
40
+ render: () => ({
41
+ components: { TagsSelect },
42
+ setup() {
43
+ return {}
44
+ },
45
+ template: `<TagsSelect>Add tag</TagsSelect>`,
46
+ }),
47
+ }
@@ -0,0 +1,637 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from 'vue'
3
+ import { PopoverRoot, PopoverTrigger, PopoverPortal, PopoverContent } from 'reka-ui'
4
+
5
+ const props = withDefaults(defineProps<{
6
+ class?: HTMLAttributes['class']
7
+ labelClass?: HTMLAttributes['class']
8
+ modelValue?: Tag[]
9
+ options: Tag[]
10
+ createTagLabel?: string
11
+ selectedLabel?: string
12
+ newTagLabel?: string
13
+ editTagLabel?: string
14
+ defineNamePlaceholder?: string
15
+ addNewNamePlaceholder?: string
16
+ createButtonLabel?: string
17
+ saveChangesButtonLabel?: string
18
+ }>(), {
19
+ modelValue: () => [],
20
+ createTagLabel: 'Create tag',
21
+ selectedLabel: 'selected',
22
+ newTagLabel: 'New tag',
23
+ editTagLabel: 'Edit tag',
24
+ defineNamePlaceholder: 'Define a name',
25
+ addNewNamePlaceholder: 'Add new name',
26
+ createButtonLabel: 'Create',
27
+ saveChangesButtonLabel: 'Save changes',
28
+ })
29
+
30
+ const emit = defineEmits<{
31
+ 'update:modelValue': [tags: Tag[]]
32
+ }>()
33
+
34
+ const ALL_COLORS = ['orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'purple', 'pink', 'rose', 'red', 'neutral'] as const
35
+
36
+ type Color = typeof ALL_COLORS[number]
37
+ type ContentType = 'new-tag' | 'existing-tag' | 'edit-tag' | null
38
+
39
+ interface Tag {
40
+ id: string;
41
+ color: Color
42
+ name: string
43
+ isSelected?: boolean
44
+ }
45
+
46
+ interface Dot {
47
+ color: Color
48
+ lightColor?: boolean
49
+ class?: HTMLAttributes['class']
50
+ icon?: string
51
+ iconColor?: string
52
+ }
53
+
54
+ const EMPTY_DOTS: Dot[] = [
55
+ { color: 'neutral', lightColor: true },
56
+ { color: 'neutral', lightColor: true },
57
+ { color: 'neutral', icon: 'i-ph-plus-bold' },
58
+ ]
59
+
60
+ function getTagKey(tag: Tag): string {
61
+ return tag.id
62
+ }
63
+
64
+ function getTagKeys(tags: Tag[]): Set<string> {
65
+ return new Set(tags.map(getTagKey))
66
+ }
67
+
68
+ function mergeTagsUnique(...tagArrays: (Tag[] | undefined)[]): Tag[] {
69
+ const seen = new Set<string>()
70
+ const result: Tag[] = []
71
+
72
+ for (const tags of tagArrays) {
73
+ if (!tags?.length) {
74
+ continue
75
+ }
76
+
77
+ for (const tag of tags) {
78
+ const key = getTagKey(tag)
79
+
80
+ if (!seen.has(key)) {
81
+ seen.add(key)
82
+ result.push(tag)
83
+ }
84
+ }
85
+ }
86
+
87
+ return result
88
+ }
89
+
90
+ function resolveColor(color: Color, lightColor = false): string {
91
+ const colorMap: Record<Color, string> = {
92
+ orange: lightColor ? 'bg-orange-300' : 'bg-orange-500',
93
+ amber: lightColor ? 'bg-amber-300' : 'bg-amber-500',
94
+ yellow: lightColor ? 'bg-yellow-300' : 'bg-yellow-500',
95
+ lime: lightColor ? 'bg-lime-300' : 'bg-lime-500',
96
+ green: lightColor ? 'bg-green-300' : 'bg-green-500',
97
+ emerald: lightColor ? 'bg-emerald-300' : 'bg-emerald-500',
98
+ teal: lightColor ? 'bg-teal-300' : 'bg-teal-500',
99
+ cyan: lightColor ? 'bg-cyan-300' : 'bg-cyan-500',
100
+ sky: lightColor ? 'bg-sky-300' : 'bg-sky-500',
101
+ blue: lightColor ? 'bg-blue-300' : 'bg-blue-500',
102
+ indigo: lightColor ? 'bg-indigo-300' : 'bg-indigo-500',
103
+ purple: lightColor ? 'bg-purple-300' : 'bg-purple-500',
104
+ pink: lightColor ? 'bg-pink-300' : 'bg-pink-500',
105
+ rose: lightColor ? 'bg-rose-300' : 'bg-rose-500',
106
+ red: lightColor ? 'bg-red-300' : 'bg-red-500',
107
+ neutral: lightColor ? 'bg-neutral-300' : 'bg-neutral-500',
108
+ }
109
+
110
+ if (lightColor && !colorMap[color]) {
111
+ const colorWith300 = color.replace(/\d+$/, '300')
112
+ return `bg-${colorWith300}`
113
+ }
114
+
115
+ return colorMap[color] || `bg-${color}`
116
+ }
117
+
118
+ const allCreatedTags = ref<Tag[]>([])
119
+ const localSelectedTags = ref<Tag[]>(props.modelValue || [])
120
+ const popoverOpen = ref(false)
121
+ const selectedContent = ref<ContentType>(allCreatedTags.value.length > 0 ? 'existing-tag' : 'new-tag')
122
+ const newTagName = ref<string>('')
123
+ const selectedColorsForNewTag = ref<Color | null>(null)
124
+ const selectedTagsForApply = ref<Set<string>>(new Set())
125
+ const editingTag = ref<Tag | null>(null)
126
+ const editingTagName = ref<string>('')
127
+ const editingTagColor = ref<Color | null>(null)
128
+ const isCreatingTag = ref(false)
129
+
130
+ function updateAllCreatedTags() {
131
+ const mergedTags = mergeTagsUnique(props.options, props.modelValue)
132
+ const selectedKeys = getTagKeys(props.modelValue || [])
133
+
134
+ allCreatedTags.value = mergedTags.map(tag => ({
135
+ ...tag,
136
+ isSelected: selectedKeys.has(getTagKey(tag)),
137
+ }))
138
+ }
139
+
140
+ function resetEditingState() {
141
+ editingTag.value = null
142
+ editingTagName.value = ''
143
+ editingTagColor.value = null
144
+ }
145
+
146
+ function resetNewTagForm() {
147
+ newTagName.value = ''
148
+ selectedColorsForNewTag.value = null
149
+ }
150
+
151
+ const contentMeasure = useTemplateRef<HTMLElement>('contentMeasure')
152
+ const { height: heightContent } = useElementSize(contentMeasure)
153
+
154
+ const tags = computed({
155
+ get: () => localSelectedTags.value,
156
+ set: (value) => {
157
+ localSelectedTags.value = value
158
+ emit('update:modelValue', value)
159
+ },
160
+ })
161
+
162
+ const hasTags = computed(() => allCreatedTags.value.length > 0)
163
+
164
+ const triggerDots = computed<Dot[]>(() => {
165
+ if (tags.value.length > 0) {
166
+ return tags.value.slice(0, 3).map(tag => ({ color: tag.color }))
167
+ }
168
+ if (hasTags.value) {
169
+ return allCreatedTags.value.slice(0, 3).map(tag => ({ color: tag.color }))
170
+ }
171
+ return EMPTY_DOTS
172
+ })
173
+
174
+ const computedHeightContent = computed(() => {
175
+ if (heightContent.value > 0) {
176
+ return `${heightContent.value}px`
177
+ }
178
+ return undefined
179
+ })
180
+
181
+ const labelText = computed(() => {
182
+ if (hasTags.value && tags.value.length === 0) {
183
+ return `${allCreatedTags.value.length} tag${allCreatedTags.value.length > 1 ? 's' : ''}`
184
+ }
185
+
186
+ if (tags.value.length > 0) {
187
+ return `${tags.value.length} ${props.selectedLabel}`
188
+ }
189
+
190
+ return props.createTagLabel
191
+ })
192
+
193
+ const contentConfigs = computed(() => {
194
+ const configs = [
195
+ { type: 'new-tag' as const, slideIndex: -1 },
196
+ { type: 'existing-tag' as const, slideIndex: 1 },
197
+ { type: 'edit-tag' as const, slideIndex: -1 },
198
+ ]
199
+
200
+ return configs.filter(config => selectedContent.value === config.type)
201
+ })
202
+
203
+ watch(() => props.options, updateAllCreatedTags, { immediate: true })
204
+
205
+ watch(() => props.modelValue, (newTags) => {
206
+ if (!newTags?.length) {
207
+ localSelectedTags.value = []
208
+ updateAllCreatedTags()
209
+ return
210
+ }
211
+
212
+ const currentKeys = getTagKeys(localSelectedTags.value)
213
+ const newKeys = getTagKeys(newTags)
214
+ const isDifferent = currentKeys.size !== newKeys.size || !Array.from(newKeys).every(key => currentKeys.has(key))
215
+
216
+ if (isDifferent) {
217
+ localSelectedTags.value = [...newTags]
218
+ updateAllCreatedTags()
219
+ }
220
+ }, { immediate: true })
221
+
222
+ watch(popoverOpen, (isOpen) => {
223
+ if (isOpen) {
224
+ selectedContent.value = hasTags.value ? 'existing-tag' : 'new-tag'
225
+ selectedTagsForApply.value = getTagKeys(localSelectedTags.value)
226
+ }
227
+ else {
228
+ resetEditingState()
229
+ }
230
+ })
231
+
232
+ watch(() => localSelectedTags.value, (newTags) => {
233
+ if (isCreatingTag.value) {
234
+ return
235
+ }
236
+
237
+ if (newTags?.length) {
238
+ selectedTagsForApply.value = getTagKeys(newTags)
239
+
240
+ const selectedKeys = getTagKeys(newTags)
241
+
242
+ allCreatedTags.value = allCreatedTags.value.map(tag => ({
243
+ ...tag,
244
+ isSelected: selectedKeys.has(getTagKey(tag)),
245
+ }))
246
+
247
+ if (selectedContent.value === null) {
248
+ selectedContent.value = 'existing-tag'
249
+ }
250
+ }
251
+ else {
252
+ selectedTagsForApply.value = new Set()
253
+
254
+ allCreatedTags.value = allCreatedTags.value.map(tag => ({
255
+ ...tag,
256
+ isSelected: false,
257
+ }))
258
+ }
259
+ }, { immediate: true })
260
+
261
+ function handleSelectColor(color: Color) {
262
+ if (!newTagName.value?.trim()) {
263
+ return
264
+ }
265
+
266
+ selectedColorsForNewTag.value = selectedColorsForNewTag.value === color ? null : color
267
+ }
268
+
269
+ function handleSelectColorForEdit(color: Color) {
270
+ if (!editingTagName.value?.trim()) {
271
+ return
272
+ }
273
+
274
+ editingTagColor.value = editingTagColor.value === color ? null : color
275
+ }
276
+
277
+ function createNewTag() {
278
+ const trimmedName = newTagName.value?.trim()
279
+
280
+ if (!trimmedName || !selectedColorsForNewTag.value) {
281
+ return
282
+ }
283
+
284
+ isCreatingTag.value = true
285
+
286
+ const newTag: Tag = {
287
+ id: crypto.randomUUID(),
288
+ color: selectedColorsForNewTag.value,
289
+ name: trimmedName,
290
+ }
291
+
292
+ const tagKey = getTagKey(newTag)
293
+ const allCreatedKeys = getTagKeys(allCreatedTags.value)
294
+
295
+ if (!allCreatedKeys.has(tagKey)) {
296
+ const tagWithSelection: Tag = { ...newTag, isSelected: true }
297
+
298
+ allCreatedTags.value = [...allCreatedTags.value, tagWithSelection]
299
+ tags.value = [...tags.value, newTag]
300
+
301
+ selectedTagsForApply.value.add(tagKey)
302
+ }
303
+
304
+ resetNewTagForm()
305
+
306
+ selectedContent.value = 'existing-tag'
307
+
308
+ nextTick(() => {
309
+ isCreatingTag.value = false
310
+ })
311
+ }
312
+
313
+ function toggleTagSelection(tag: Tag, checked: boolean) {
314
+ const tagKey = getTagKey(tag)
315
+
316
+ if (checked) {
317
+ selectedTagsForApply.value.add(tagKey)
318
+ }
319
+ else {
320
+ selectedTagsForApply.value.delete(tagKey)
321
+ }
322
+
323
+ // Apply changes immediately
324
+ const selectedTags = allCreatedTags.value.filter(isTagSelected)
325
+ tags.value = selectedTags.map((tag) => {
326
+ const { isSelected, ...tagWithoutSelection } = tag
327
+ return tagWithoutSelection
328
+ })
329
+ }
330
+
331
+ function isTagSelected(tag: Tag): boolean {
332
+ return selectedTagsForApply.value.has(getTagKey(tag))
333
+ }
334
+
335
+ function handleCreateMore() {
336
+ resetNewTagForm()
337
+ selectedContent.value = 'new-tag'
338
+ }
339
+
340
+ function handleBackToTags() {
341
+ selectedContent.value = 'existing-tag'
342
+ }
343
+
344
+ function handleEditTag(tag: Tag) {
345
+ editingTag.value = tag
346
+ editingTagName.value = tag.name
347
+ editingTagColor.value = tag.color
348
+
349
+ selectedContent.value = 'edit-tag'
350
+ }
351
+
352
+ function saveEditedTag() {
353
+ if (!editingTag.value || !editingTagName.value?.trim() || !editingTagColor.value) {
354
+ return
355
+ }
356
+
357
+ const oldTagKey = getTagKey(editingTag.value)
358
+ const newTag: Tag = {
359
+ id: editingTag.value.id,
360
+ color: editingTagColor.value,
361
+ name: editingTagName.value.trim(),
362
+ }
363
+ const newTagKey = getTagKey(newTag)
364
+
365
+ const tagIndex = allCreatedTags.value.findIndex(t => getTagKey(t) === oldTagKey)
366
+
367
+ if (tagIndex !== -1 && allCreatedTags.value[tagIndex]) {
368
+ const wasSelected = allCreatedTags.value[tagIndex].isSelected ?? false
369
+
370
+ allCreatedTags.value[tagIndex] = { ...newTag, isSelected: wasSelected }
371
+ }
372
+
373
+ const wasSelected = selectedTagsForApply.value.has(oldTagKey)
374
+
375
+ if (wasSelected) {
376
+ selectedTagsForApply.value.delete(oldTagKey)
377
+ selectedTagsForApply.value.add(newTagKey)
378
+ }
379
+
380
+ const selectedIndex = localSelectedTags.value.findIndex(t => getTagKey(t) === oldTagKey)
381
+
382
+ if (selectedIndex !== -1) {
383
+ localSelectedTags.value[selectedIndex] = { ...newTag }
384
+ tags.value = localSelectedTags.value
385
+ }
386
+
387
+ resetEditingState()
388
+
389
+ selectedContent.value = 'existing-tag'
390
+ }
391
+
392
+ function cancelEditTag() {
393
+ resetEditingState()
394
+ selectedContent.value = 'existing-tag'
395
+ }
396
+
397
+ function getSlideMotionVariants(index: number) {
398
+ return {
399
+ initial: {
400
+ x: `${110 * index}%`,
401
+ opacity: 0,
402
+ },
403
+ animate: {
404
+ x: 0,
405
+ opacity: 1,
406
+ },
407
+ exit: {
408
+ x: `${-110 * index}%`,
409
+ opacity: 0,
410
+ },
411
+ }
412
+ }
413
+
414
+ const SPRING_CONFIG = {
415
+ type: 'spring',
416
+ duration: 0.5,
417
+ bounce: 0,
418
+ } as const
419
+
420
+ updateAllCreatedTags()
421
+ </script>
422
+
423
+ <template>
424
+ <MotionConfig :transition="SPRING_CONFIG">
425
+ <PopoverRoot v-model:open="popoverOpen">
426
+ <PopoverTrigger
427
+ as="button"
428
+ :class="cn(
429
+ 'group w-fit flex items-center gap-[6px] px-[12px] py-[7px] rounded-[10px] bg-background border-[0.5px] border-border-strong',
430
+ 'transition ease-in-out duration-80 hover:bg-background-subtle data-[state=open]:bg-background-subtle',
431
+ props.class,
432
+ )"
433
+ >
434
+ <div class="flex items-center">
435
+ <div
436
+ v-for="(dot, index) in triggerDots"
437
+ :key="index"
438
+ :class="cn(
439
+ 'flex items-center justify-center w-[12px] h-[12px] rounded-full dot-mask [&:not(:first-child)]:-ml-[4px]',
440
+ resolveColor(dot.color, dot.lightColor),
441
+ )"
442
+ >
443
+ <TelaIcon v-if="dot.icon" :name="dot.icon" size="7px" :color="dot.iconColor || 'white-1000'" />
444
+ </div>
445
+ </div>
446
+ <span :class="cn('body-14-semibold text-text-primary', props.labelClass)">
447
+ {{ labelText }}
448
+ </span>
449
+ </PopoverTrigger>
450
+ <PopoverPortal>
451
+ <PopoverContent class="TagsSelectContent" :side-offset="8" w-220px pt-12px px-16px bg-background border-0.5px border-border shadow-lg rounded-12px overflow-hidden>
452
+ <div
453
+ class="[transition:height_0.3s_ease-out]"
454
+ :style="computedHeightContent ? { height: computedHeightContent } : {}"
455
+ >
456
+ <div ref="contentMeasure">
457
+ <AnimatePresence mode="popLayout" :initial="false">
458
+ <Motion
459
+ v-for="config in contentConfigs"
460
+ :key="config.type"
461
+ v-bind="getSlideMotionVariants(config.slideIndex)"
462
+ flex="~ col"
463
+ tabindex="-1"
464
+ >
465
+ <div v-if="config.type === 'new-tag'" flex="~ col" gap-12px pb-16px>
466
+ <div flex="~ col" gap-4px>
467
+ <button
468
+ v-if="allCreatedTags.length > 0"
469
+ flex items-center justify-center w-24px h-24px rounded-6px hover:bg-background-muted ml--6px mt--4px
470
+ @click="handleBackToTags"
471
+ >
472
+ <TelaIcon name="i-ph-arrow-left" size="16px" color="icon" />
473
+ </button>
474
+ <h5 heading-h5-semibold>
475
+ {{ props.newTagLabel }}
476
+ </h5>
477
+ </div>
478
+ <TelaInput
479
+ v-model="newTagName"
480
+ :placeholder="props.defineNamePlaceholder"
481
+ input-class="px-[6px]! pt-[2px]! pb-[3px]! rounded-[6px]!"
482
+ input-font-class="body-12-regular!"
483
+ autofocus
484
+ @keydown.enter="createNewTag"
485
+ />
486
+ <div class="grid grid-cols-8 gap-4px">
487
+ <button
488
+ v-for="color in ALL_COLORS"
489
+ :key="color"
490
+ w-20px h-20px rounded-4px flex items-center justify-center hover:bg-background-lowered
491
+ @click="handleSelectColor(color)"
492
+ >
493
+ <div
494
+ :class="cn('flex items-center justify-center w-12px h-12px rounded-full', resolveColor(color))"
495
+ >
496
+ <TelaIcon
497
+ v-if="selectedColorsForNewTag === color"
498
+ name="i-ph-check-bold"
499
+ size="8px"
500
+ color="white-1000"
501
+ />
502
+ </div>
503
+ </button>
504
+ </div>
505
+ <TelaButton size="sm" :disabled="!newTagName || newTagName.trim() === '' || !selectedColorsForNewTag" @click="createNewTag">
506
+ {{ props.createButtonLabel }}
507
+ </TelaButton>
508
+ </div>
509
+ <div v-if="config.type === 'existing-tag'" flex="~ col" pb-10px>
510
+ <div flex items-center justify-between>
511
+ <h5 heading-h5-semibold>
512
+ {{ allCreatedTags.length }} tag{{ allCreatedTags.length > 1 ? 's' : '' }}
513
+ </h5>
514
+ <button flex items-center justify-center w-24px h-24px rounded-6px hover:bg-background-muted mr--4px @click="handleCreateMore">
515
+ <TelaIcon name="i-ph-plus" size="16px" color="icon" />
516
+ </button>
517
+ </div>
518
+ <div flex="~ col" mt-4px>
519
+ <button v-for="tag in allCreatedTags" :key="`${tag.color}-${tag.name}`" class="group/tag" flex items-center justify-between hover:bg-background-muted px-8px mx--8px py-4px rounded-8px @click="toggleTagSelection(tag, !isTagSelected(tag))">
520
+ <div flex items-center gap-6px min-w-0>
521
+ <div :class="cn('w-[8px] h-[8px] rounded-full flex-shrink-0', resolveColor(tag.color))" />
522
+ <span body-14-medium text-text-primary capitalize truncate text-start>
523
+ {{ tag.name }}
524
+ </span>
525
+ </div>
526
+ <div flex items-center gap-4px>
527
+ <button
528
+ class="group opacity-0 group-hover/tag:opacity-100" flex items-center justify-center w-17px h-17px rounded-5px hover:bg-background-lowered
529
+ @click.stop="handleEditTag(tag)"
530
+ >
531
+ <TelaIcon name="i-ph-pencil-simple" size="12px" color="icon-subtle group-hover:icon" />
532
+ </button>
533
+ <TelaCheckbox :model-value="isTagSelected(tag)" @update:model-value="(checked) => toggleTagSelection(tag, checked === true)" @click.stop />
534
+ </div>
535
+ </button>
536
+ </div>
537
+ </div>
538
+ <div v-if="config.type === 'edit-tag'" flex="~ col" gap-12px pb-16px>
539
+ <div flex="~ col" gap-4px>
540
+ <button
541
+ flex items-center justify-center w-24px h-24px rounded-6px hover:bg-background-muted ml--6px mt--4px
542
+ @click="cancelEditTag"
543
+ >
544
+ <TelaIcon name="i-ph-arrow-left" size="16px" color="icon" />
545
+ </button>
546
+ <h5 heading-h5-semibold>
547
+ {{ props.editTagLabel }}
548
+ </h5>
549
+ </div>
550
+ <TelaInput
551
+ v-model="editingTagName"
552
+ :placeholder="props.addNewNamePlaceholder"
553
+ input-class="px-[6px]! pt-[2px]! pb-[3px]! rounded-[6px]!"
554
+ input-font-class="body-12-regular!"
555
+ @keydown.enter="saveEditedTag"
556
+ />
557
+ <div class="grid grid-cols-8 gap-4px">
558
+ <button
559
+ v-for="color in ALL_COLORS"
560
+ :key="color"
561
+ w-20px h-20px rounded-4px flex items-center justify-center hover:bg-background-lowered
562
+ @click="handleSelectColorForEdit(color)"
563
+ >
564
+ <div
565
+ :class="cn('flex items-center justify-center w-12px h-12px rounded-full', resolveColor(color))"
566
+ >
567
+ <TelaIcon
568
+ v-if="editingTagColor === color"
569
+ name="i-ph-check-bold"
570
+ size="8px"
571
+ color="white-1000"
572
+ />
573
+ </div>
574
+ </button>
575
+ </div>
576
+ <TelaButton size="sm" :disabled="!editingTagName || editingTagName.trim() === '' || !editingTagColor" @click="saveEditedTag">
577
+ {{ props.saveChangesButtonLabel }}
578
+ </TelaButton>
579
+ </div>
580
+ </Motion>
581
+ </AnimatePresence>
582
+ </div>
583
+ </div>
584
+ </PopoverContent>
585
+ </PopoverPortal>
586
+ </PopoverRoot>
587
+ </MotionConfig>
588
+ </template>
589
+
590
+ <style>
591
+ .dot-mask:not(:last-child) {
592
+ mask-image: radial-gradient(circle 6px at right center, transparent 6px, #fff 6px);
593
+ }
594
+
595
+ .TagsSelectContent {
596
+ transform-origin: var(--reka-popover-content-transform-origin);
597
+
598
+ &[data-state="open"] {
599
+ animation: slideDownAndFade 0.12s ease-out forwards;
600
+ }
601
+
602
+ &[data-state="closed"] {
603
+ animation: slideUpAndFade 0.12s ease-out forwards;
604
+ animation-delay: 0s;
605
+ }
606
+ }
607
+
608
+ @keyframes slideDownAndFade {
609
+ from {
610
+ opacity: 0;
611
+ transform: translateY(2px) scale(0.97);
612
+ }
613
+ to {
614
+ opacity: 1;
615
+ transform: scale(1);
616
+ }
617
+ }
618
+
619
+ @keyframes slideUpAndFade {
620
+ from {
621
+ opacity: 1;
622
+ transform: translateY(0) scale(1);
623
+ }
624
+ to {
625
+ opacity: 0;
626
+ transform: translateY(-2px) scale(0.97);
627
+ }
628
+ }
629
+
630
+ .slide-fade-enter-active {
631
+ transition: all 0.3s ease;
632
+ }
633
+
634
+ .slide-fade-leave-active {
635
+ transition: all 0.3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
636
+ }
637
+ </style>