@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,165 @@
1
+ <script setup lang="ts">
2
+ const props = withDefaults(defineProps<{
3
+ disabled?: boolean
4
+ loading?: boolean
5
+ class?: string
6
+ identifier: string
7
+ buttonLabel: string
8
+ selected: { identifier: string, label: string, value?: string | string[] }
9
+ isActive: boolean
10
+ options: Record<string, { value: string, label: string }>
11
+ searchPlaceholder?: string
12
+ applyLabel?: string
13
+ noOptionsFoundLabel?: string
14
+ }>(), {
15
+ searchPlaceholder: 'Search',
16
+ applyLabel: 'Apply',
17
+ noOptionsFoundLabel: 'No options found',
18
+ })
19
+
20
+ const emit = defineEmits<{
21
+ (e: 'apply', value: Record<string, any>): void
22
+ (e: 'reset'): void
23
+ }>()
24
+
25
+ const isDropdownOpen = ref(false)
26
+ const dropdownRef = useTemplateRef('dropdownEl')
27
+ const selectedOptions = ref<string[]>([])
28
+ const searchQuery = ref('')
29
+
30
+ const filteredOptions = computed(() => {
31
+ if (!searchQuery.value.trim()) {
32
+ return Object.values(props.options)
33
+ }
34
+ const query = searchQuery.value.toLowerCase()
35
+ return Object.values(props.options).filter(option =>
36
+ option.label.toLowerCase().includes(query),
37
+ )
38
+ })
39
+
40
+ function toggleOption(option: string) {
41
+ if (selectedOptions.value.includes(option)) {
42
+ selectedOptions.value = selectedOptions.value.filter(o => o !== option)
43
+ }
44
+ else {
45
+ selectedOptions.value.push(option)
46
+ }
47
+ }
48
+
49
+ function handleApply() {
50
+ emit('apply', { [props.identifier.toLowerCase()]: selectedOptions.value })
51
+ isDropdownOpen.value = false
52
+ }
53
+
54
+ function resetToCurrentValue() {
55
+ selectedOptions.value = Array.isArray(props.selected.value) ? [...props.selected.value] : []
56
+ }
57
+
58
+ watch(isDropdownOpen, (isOpen) => {
59
+ if (isOpen) {
60
+ resetToCurrentValue()
61
+ }
62
+ else {
63
+ searchQuery.value = ''
64
+ }
65
+ })
66
+
67
+ watch(() => props.isActive, (isActive) => {
68
+ if (!isActive && isDropdownOpen.value) {
69
+ isDropdownOpen.value = false
70
+ }
71
+ })
72
+
73
+ onClickOutside(dropdownRef, () => {
74
+ isDropdownOpen.value = false
75
+ })
76
+ </script>
77
+
78
+ <template>
79
+ <div ref="dropdownEl" class="relative">
80
+ <TelaButton
81
+ :class="[props.class, isDropdownOpen && !props.isActive ? 'bg-gray-40! border-gray-240!' : '']"
82
+ class="group rounded-10px! focus:ring-0! px-12px! gap-6px!"
83
+ variant="secondary"
84
+ size="md"
85
+ h-32px
86
+ :disabled="props.disabled"
87
+ :loading="props.loading"
88
+ :leading="false"
89
+ @click="isDropdownOpen = !isDropdownOpen"
90
+ >
91
+ <span class="max-w-125px! truncate">{{ props.buttonLabel }}</span>
92
+ <TelaIcon
93
+ v-if="!props.isActive && !props.loading"
94
+ name="i-ph-caret-down" size="sm" text-black-1000
95
+ transition
96
+ :class="[
97
+ isDropdownOpen && 'rotate-180',
98
+ ]"
99
+ />
100
+ <TelaIcon v-if="props.isActive && !props.loading" name="i-ph-x" size="sm" @click.stop="emit('reset')" />
101
+ </TelaButton>
102
+
103
+ <div
104
+ v-if="isDropdownOpen"
105
+ class="absolute top-full left-0 z-50 mt-1 w-220px bg-white rounded-12px shadow-[0px_3px_12px_0px_rgba(15,_32,_48,_0.08),_0px_12px_52px_0px_rgba(3,_12,_20,_0.16)] border border-gray-120 flex flex-col"
106
+ >
107
+ <div p-8px border-b border-gray-120>
108
+ <div class="relative">
109
+ <TelaIcon
110
+ name="i-ph-magnifying-glass"
111
+ size="sm"
112
+ class="absolute left-8px top-1/2 -translate-y-1/2 text-gray-600"
113
+ />
114
+ <input
115
+ v-model="searchQuery"
116
+ type="text"
117
+ :placeholder="props.searchPlaceholder"
118
+ class="w-full h-32px pl-32px pr-8px rounded-8px border border-gray-120 text-14px focus:outline-none focus:border-gray-240"
119
+ >
120
+ </div>
121
+ </div>
122
+ <div class="flex-1 overflow-y-auto max-h-180px" p-8px>
123
+ <div v-if="filteredOptions.length > 0" flex="~ col" gap-8px>
124
+ <div
125
+ v-for="option in filteredOptions"
126
+ :key="option.value"
127
+ cursor-pointer
128
+ flex="~ justify-between"
129
+ items-center
130
+ gap-12px
131
+ p-8px
132
+ h-28px
133
+ rounded-8px
134
+ class="hover:bg-gray-20"
135
+ @click="toggleOption(option.value)"
136
+ >
137
+ <span flex="~" items-center gap-8px text-black-900 text-14px font-medium leading-tight>
138
+ {{ option.label }}
139
+ </span>
140
+ <TelaCheckbox
141
+ :model-value="selectedOptions.includes(option.value)"
142
+ @update:model-value="toggleOption(option.value)"
143
+ @click.stop
144
+ />
145
+ </div>
146
+ </div>
147
+ <div v-else flex="~" items-center justify-center p-16px>
148
+ <span text-14px text-gray-600>{{ props.noOptionsFoundLabel }}{{ searchQuery ? ` with '${searchQuery}'` : '' }}</span>
149
+ </div>
150
+ </div>
151
+ <div pb-8px px-8px pt-8px bg-white rounded-b-12px>
152
+ <TelaButton
153
+ variant="secondary"
154
+ w-full
155
+ size="sm"
156
+ rounded-lg
157
+ :disabled="selectedOptions.length === 0"
158
+ @click="handleApply"
159
+ >
160
+ {{ props.applyLabel }}
161
+ </TelaButton>
162
+ </div>
163
+ </div>
164
+ </div>
165
+ </template>
@@ -0,0 +1,258 @@
1
+ import { ref } from 'vue'
2
+ import type { Meta, StoryObj } from '@storybook/vue3'
3
+ import DateFilter from './date-filter.vue'
4
+
5
+ const meta: Meta<typeof DateFilter> = {
6
+ title: 'Filters/DateFilter',
7
+ component: DateFilter,
8
+ tags: ['autodocs'],
9
+ parameters: {
10
+ layout: 'centered',
11
+ docs: {
12
+ description: {
13
+ component: 'A dropdown filter component for selecting date ranges. Supports predefined ranges (Today, This Week, This Month) and custom date range selection via a calendar picker. Emits start and end dates in ISO format.',
14
+ },
15
+ },
16
+ },
17
+ argTypes: {
18
+ buttonLabel: {
19
+ control: 'text',
20
+ description: 'The label displayed on the filter button',
21
+ },
22
+ isActive: {
23
+ control: 'boolean',
24
+ description: 'Whether the filter is currently active (has a date range applied)',
25
+ },
26
+ disabled: {
27
+ control: 'boolean',
28
+ description: 'Whether the filter button is disabled',
29
+ },
30
+ loading: {
31
+ control: 'boolean',
32
+ description: 'Whether the filter is in a loading state',
33
+ },
34
+ },
35
+ }
36
+
37
+ export default meta
38
+
39
+ type Story = StoryObj<typeof meta>
40
+
41
+ export const Default: Story = {
42
+ render: args => ({
43
+ components: { DateFilter },
44
+ setup() {
45
+ const selected = ref({ identifier: 'date', label: 'Date', value: undefined })
46
+ const isActive = ref(false)
47
+
48
+ function handleApply(value: any) {
49
+ // In a real app, this would use the date filter handler to format the label
50
+ const label = value.value === 'today'
51
+ ? 'Today'
52
+ : value.value === 'thisWeek'
53
+ ? 'This Week'
54
+ : value.value === 'thisMonth'
55
+ ? 'This Month'
56
+ : value.value === 'specificDate'
57
+ ? 'Custom Range'
58
+ : 'Date'
59
+
60
+ selected.value = {
61
+ identifier: 'date',
62
+ label,
63
+ value: value.value,
64
+ }
65
+ isActive.value = true
66
+ }
67
+
68
+ function handleReset() {
69
+ selected.value = { identifier: 'date', label: 'Date', value: undefined }
70
+ isActive.value = false
71
+ }
72
+
73
+ return { selected, isActive, handleApply, handleReset, args }
74
+ },
75
+ template: `
76
+ <div class="flex flex-col gap-4 p-4">
77
+ <DateFilter
78
+ v-bind="args"
79
+ :selected="selected"
80
+ :is-active="isActive"
81
+ @apply="handleApply"
82
+ @reset="handleReset"
83
+ />
84
+ </div>
85
+ `,
86
+ }),
87
+ args: {
88
+ buttonLabel: 'Date',
89
+ isActive: false,
90
+ disabled: false,
91
+ loading: false,
92
+ },
93
+ }
94
+
95
+ export const ActiveToday: Story = {
96
+ render: args => ({
97
+ components: { DateFilter },
98
+ setup() {
99
+ const selected = ref({ identifier: 'date', label: 'Today', value: 'today' })
100
+ const isActive = ref(true)
101
+
102
+ function handleApply(value: any) {
103
+ const label = value.value === 'today'
104
+ ? 'Today'
105
+ : value.value === 'thisWeek'
106
+ ? 'This Week'
107
+ : value.value === 'thisMonth'
108
+ ? 'This Month'
109
+ : value.value === 'specificDate'
110
+ ? 'Custom Range'
111
+ : 'Date'
112
+
113
+ selected.value = {
114
+ identifier: 'date',
115
+ label,
116
+ value: value.value,
117
+ }
118
+ isActive.value = true
119
+ }
120
+
121
+ function handleReset() {
122
+ selected.value = { identifier: 'date', label: 'Date', value: '' }
123
+ isActive.value = false
124
+ }
125
+
126
+ return { selected, isActive, handleApply, handleReset, args }
127
+ },
128
+ template: `
129
+ <div class="flex flex-col gap-4 p-4">
130
+ <DateFilter
131
+ v-bind="args"
132
+ :selected="selected"
133
+ :is-active="isActive"
134
+ @apply="handleApply"
135
+ @reset="handleReset"
136
+ />
137
+ </div>
138
+ `,
139
+ }),
140
+ args: {
141
+ buttonLabel: 'Today',
142
+ isActive: true,
143
+ disabled: false,
144
+ loading: false,
145
+ },
146
+ }
147
+
148
+ export const ActiveThisWeek: Story = {
149
+ render: args => ({
150
+ components: { DateFilter },
151
+ setup() {
152
+ const selected = ref({ identifier: 'date', label: 'This Week', value: 'thisWeek' })
153
+ const isActive = ref(true)
154
+
155
+ function handleApply(value: any) {
156
+ const label = value.value === 'today'
157
+ ? 'Today'
158
+ : value.value === 'thisWeek'
159
+ ? 'This Week'
160
+ : value.value === 'thisMonth'
161
+ ? 'This Month'
162
+ : value.value === 'specificDate'
163
+ ? 'Custom Range'
164
+ : 'Date'
165
+
166
+ selected.value = {
167
+ identifier: 'date',
168
+ label,
169
+ value: value.value,
170
+ }
171
+ isActive.value = true
172
+ }
173
+
174
+ function handleReset() {
175
+ selected.value = { identifier: 'date', label: 'Date', value: '' }
176
+ isActive.value = false
177
+ }
178
+
179
+ return { selected, isActive, handleApply, handleReset, args }
180
+ },
181
+ template: `
182
+ <div class="flex flex-col gap-4 p-4">
183
+ <DateFilter
184
+ v-bind="args"
185
+ :selected="selected"
186
+ :is-active="isActive"
187
+ @apply="handleApply"
188
+ @reset="handleReset"
189
+ />
190
+ </div>
191
+ `,
192
+ }),
193
+ args: {
194
+ buttonLabel: 'This Week',
195
+ isActive: true,
196
+ disabled: false,
197
+ loading: false,
198
+ },
199
+ }
200
+
201
+ export const ActiveCustomRange: Story = {
202
+ render: args => ({
203
+ components: { DateFilter },
204
+ setup() {
205
+ const selected = ref({ identifier: 'date', label: '01/01/2025 — 15/01/2025', value: 'specificDate' })
206
+ const isActive = ref(true)
207
+
208
+ function handleApply(value: any) {
209
+ selected.value = {
210
+ identifier: 'date',
211
+ label: '01/01/2025 — 15/01/2025',
212
+ value: value.value,
213
+ }
214
+ isActive.value = true
215
+ }
216
+
217
+ function handleReset() {
218
+ selected.value = { identifier: 'date', label: 'Date', value: '' }
219
+ isActive.value = false
220
+ }
221
+
222
+ return { selected, isActive, handleApply, handleReset, args }
223
+ },
224
+ template: `
225
+ <div class="flex flex-col gap-4 p-4">
226
+ <DateFilter
227
+ v-bind="args"
228
+ :selected="selected"
229
+ :is-active="isActive"
230
+ @apply="handleApply"
231
+ @reset="handleReset"
232
+ />
233
+ </div>
234
+ `,
235
+ }),
236
+ args: {
237
+ buttonLabel: '01/01/2025 — 15/01/2025',
238
+ isActive: true,
239
+ disabled: false,
240
+ loading: false,
241
+ },
242
+ }
243
+
244
+ export const Disabled: Story = {
245
+ ...Default,
246
+ args: {
247
+ ...Default.args,
248
+ disabled: true,
249
+ },
250
+ }
251
+
252
+ export const Loading: Story = {
253
+ ...Default,
254
+ args: {
255
+ ...Default.args,
256
+ loading: true,
257
+ },
258
+ }
@@ -0,0 +1,200 @@
1
+ <script setup lang="ts">
2
+ import { endOfDay, startOfDay, startOfMonth, startOfWeek } from 'date-fns'
3
+ import { getLocalTimeZone } from '@internationalized/date'
4
+ import type { DateRange } from 'radix-vue'
5
+
6
+ const props = withDefaults(defineProps<{
7
+ disabled?: boolean
8
+ loading?: boolean
9
+ buttonLabel?: string
10
+ class?: string
11
+ selected: { identifier: string, label: string, value?: string | string[] }
12
+ isActive: boolean
13
+ applyLabel?: string
14
+ todayLabel?: string
15
+ thisWeekLabel?: string
16
+ thisMonthLabel?: string
17
+ specificDateLabel?: string
18
+ }>(), {
19
+ applyLabel: 'Apply',
20
+ todayLabel: 'Today',
21
+ thisWeekLabel: 'This Week',
22
+ thisMonthLabel: 'This Month',
23
+ specificDateLabel: 'Specific Date',
24
+ })
25
+
26
+ const emit = defineEmits<{
27
+ (e: 'apply', value: Record<string, any>): void
28
+ (e: 'reset'): void
29
+ }>()
30
+
31
+ const isDropdownOpen = ref(false)
32
+ const dropdownRef = useTemplateRef('dropdownEl')
33
+ const specificRange = ref<DateRange>()
34
+
35
+ const selectedDateValue = ref<string | undefined>(props.selected.value as string)
36
+
37
+ const dateFilterValueOptions = computed(() => [
38
+ { label: props.todayLabel, value: 'today' },
39
+ { label: props.thisWeekLabel, value: 'thisWeek' },
40
+ { label: props.thisMonthLabel, value: 'thisMonth' },
41
+ { label: props.specificDateLabel, value: 'specificDate' },
42
+ ])
43
+
44
+ function handleDateValueSelect(value: string) {
45
+ if (selectedDateValue.value === value) {
46
+ selectedDateValue.value = undefined
47
+ specificRange.value = undefined
48
+ }
49
+ else {
50
+ selectedDateValue.value = value
51
+ if (value !== 'specificDate') {
52
+ specificRange.value = undefined
53
+ }
54
+ }
55
+ }
56
+
57
+ function handleApply() {
58
+ const today = new Date()
59
+ let start: string | undefined
60
+ let end: string | undefined
61
+
62
+ if (selectedDateValue.value === 'today') {
63
+ start = startOfDay(today).toISOString()
64
+ end = today.toISOString()
65
+ }
66
+ else if (selectedDateValue.value === 'thisWeek') {
67
+ const firstDayOfWeek = startOfWeek(today, { weekStartsOn: 0 })
68
+ start = firstDayOfWeek.toISOString()
69
+ end = today.toISOString()
70
+ }
71
+ else if (selectedDateValue.value === 'thisMonth') {
72
+ const firstDayOfMonth = startOfMonth(today)
73
+ start = firstDayOfMonth.toISOString()
74
+ end = today.toISOString()
75
+ }
76
+ else if (selectedDateValue.value === 'specificDate' && specificRange.value?.start && specificRange.value?.end) {
77
+ const startDate = startOfDay(specificRange.value.start.toDate(getLocalTimeZone()))
78
+ const endDate = endOfDay(specificRange.value.end.toDate(getLocalTimeZone()))
79
+ start = startDate.toISOString()
80
+ end = endDate.toISOString()
81
+ }
82
+
83
+ const value = {
84
+ value: selectedDateValue.value,
85
+ start,
86
+ end,
87
+ specificRange: specificRange.value
88
+ ? [
89
+ specificRange.value.start?.toDate(getLocalTimeZone()).toISOString(),
90
+ specificRange.value.end?.toDate(getLocalTimeZone()).toISOString(),
91
+ ]
92
+ : undefined,
93
+ }
94
+
95
+ emit('apply', value)
96
+ isDropdownOpen.value = false
97
+ }
98
+
99
+ function resetToCurrentValue() {
100
+ selectedDateValue.value = props.selected.value as string | undefined
101
+ }
102
+
103
+ watch(isDropdownOpen, (isOpen) => {
104
+ if (isOpen) {
105
+ resetToCurrentValue()
106
+ }
107
+ })
108
+
109
+ watch(() => props.isActive, (isActive) => {
110
+ if (!isActive && isDropdownOpen.value) {
111
+ isDropdownOpen.value = false
112
+ }
113
+ })
114
+
115
+ onClickOutside(dropdownRef, () => {
116
+ isDropdownOpen.value = false
117
+ })
118
+ </script>
119
+
120
+ <template>
121
+ <div ref="dropdownEl" class="relative">
122
+ <TelaButton
123
+ :class="[props.class, isDropdownOpen && !props.isActive ? 'bg-gray-40! border-gray-240!' : '']"
124
+ class="group rounded-10px! focus:ring-0! px-12px! gap-6px!"
125
+ variant="secondary"
126
+ size="md"
127
+ h-32px
128
+ :disabled="props.disabled"
129
+ :loading="props.loading"
130
+ :leading="false"
131
+ @click="isDropdownOpen = !isDropdownOpen"
132
+ >
133
+ <TelaIcon v-if="props.isActive" name="i-ph-calendar-blank" size="md" />
134
+ <span class="max-w-210px! truncate">{{ props.buttonLabel }}</span>
135
+ <TelaIcon
136
+ v-if="!props.isActive && !props.loading"
137
+ name="i-ph-caret-down" size="sm" text-black-1000
138
+ transition
139
+ :class="[
140
+ isDropdownOpen && 'rotate-180',
141
+ ]"
142
+ />
143
+ <TelaIcon v-if="props.isActive && !props.loading" name="i-ph-x" size="sm" @click.stop="emit('reset')" />
144
+ </TelaButton>
145
+
146
+ <div
147
+ v-if="isDropdownOpen"
148
+ class="absolute top-full left-0 z-50 mt-1 w-256px bg-white rounded-12px shadow-[0px_3px_12px_0px_rgba(15,_32,_48,_0.08),_0px_12px_52px_0px_rgba(3,_12,_20,_0.16)] border border-gray-120 flex flex-col"
149
+ >
150
+ <div flex="~ col" gap-4px py-8px px-8px>
151
+ <div
152
+ v-for="option in dateFilterValueOptions"
153
+ :key="option.value"
154
+ cursor-pointer
155
+ flex="~ justify-between"
156
+ items-center
157
+ gap-12px
158
+ p-8px
159
+ h-28px
160
+ rounded-8px
161
+ class="hover:bg-gray-20"
162
+ :class="[
163
+ (selectedDateValue === option.value)
164
+ ? 'outline outline-0.5px outline-gray-240'
165
+ : '',
166
+ ]"
167
+ @click="handleDateValueSelect(option.value)"
168
+ >
169
+ <span justify-start text-black-900 text-14px font-medium leading-tight>
170
+ {{ option.label }}
171
+ </span>
172
+ <TelaRadioButton
173
+ :model-value="selectedDateValue === option.value"
174
+ class="border border-gray-300!"
175
+ @update:model-value="handleDateValueSelect(option.value)"
176
+ @click.stop
177
+ />
178
+ </div>
179
+ </div>
180
+ <div v-if="selectedDateValue === 'specificDate'">
181
+ <TelaRangeCalendar
182
+ v-model="specificRange"
183
+ class="shadow-none! px-2px! pt-8px!"
184
+ />
185
+ </div>
186
+ <div pb-8px px-8px pt-8px bg-white rounded-b-12px>
187
+ <TelaButton
188
+ variant="secondary"
189
+ w-full
190
+ size="sm"
191
+ rounded-lg
192
+ :disabled="!selectedDateValue || (selectedDateValue === 'specificDate' && (!specificRange?.start || !specificRange?.end))"
193
+ @click="handleApply"
194
+ >
195
+ {{ props.applyLabel }}
196
+ </TelaButton>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ </template>