@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,189 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as FileUploadStories from './file-upload.stories.ts';
3
+
4
+ <Meta of={FileUploadStories} />
5
+
6
+ # TelaFileUpload
7
+
8
+ A file upload component that provides drag-and-drop functionality and traditional file selection. Supports multiple file selection, file type restrictions, and custom styling. Emits events for file selection and validation.
9
+
10
+ ## Examples
11
+
12
+ ### Basic Usage
13
+
14
+ ```vue
15
+ <script setup>
16
+ const handleFiles = (files) => {
17
+ console.log('Selected files:', files)
18
+ }
19
+ </script>
20
+
21
+ <template>
22
+ <TelaFileUpload @change="handleFiles" />
23
+ </template>
24
+ ```
25
+
26
+ ### Accept Specific File Types
27
+
28
+ ```vue
29
+ <!-- Images only -->
30
+ <TelaFileUpload accept="image/*" />
31
+
32
+ <!-- PDFs only -->
33
+ <TelaFileUpload accept=".pdf" />
34
+
35
+ <!-- Images and PDFs -->
36
+ <TelaFileUpload accept="image/*,.pdf" />
37
+
38
+ <!-- Multiple types -->
39
+ <TelaFileUpload accept=".jpg,.jpeg,.png,.pdf,.doc,.docx" />
40
+ ```
41
+
42
+ ### Multiple Files
43
+
44
+ ```vue
45
+ <TelaFileUpload multiple @change="handleFiles" />
46
+ ```
47
+
48
+ ### With Max Size
49
+
50
+ ```vue
51
+ <TelaFileUpload
52
+ :max-size="5242880"
53
+ @change="handleFiles"
54
+ @error="handleError"
55
+ />
56
+ <!-- 5MB = 5 * 1024 * 1024 bytes -->
57
+ ```
58
+
59
+ ### Disabled State
60
+
61
+ ```vue
62
+ <TelaFileUpload disabled />
63
+ ```
64
+
65
+ ### Complete Example
66
+
67
+ ```vue
68
+ <script setup>
69
+ import { ref } from 'vue'
70
+
71
+ const files = ref([])
72
+ const error = ref(null)
73
+
74
+ const handleChange = (selectedFiles) => {
75
+ files.value = Array.from(selectedFiles)
76
+ error.value = null
77
+ }
78
+
79
+ const handleError = (errorMessage) => {
80
+ error.value = errorMessage
81
+ }
82
+
83
+ const removeFile = (index) => {
84
+ files.value.splice(index, 1)
85
+ }
86
+ </script>
87
+
88
+ <template>
89
+ <div>
90
+ <TelaFileUpload
91
+ accept="image/*,.pdf"
92
+ multiple
93
+ :max-size="10485760"
94
+ @change="handleChange"
95
+ @error="handleError"
96
+ />
97
+
98
+ <div v-if="error" class="text-red-500 mt-2">
99
+ {{ error }}
100
+ </div>
101
+
102
+ <div v-if="files.length > 0" class="mt-4">
103
+ <h4 class="font-semibold mb-2">Selected Files:</h4>
104
+ <ul class="space-y-2">
105
+ <li
106
+ v-for="(file, index) in files"
107
+ :key="index"
108
+ class="flex items-center justify-between p-2 bg-gray-100 rounded"
109
+ >
110
+ <span>{{ file.name }} ({{ formatFileSize(file.size) }})</span>
111
+ <TelaButton
112
+ size="sm"
113
+ variant="ghost"
114
+ @click="removeFile(index)"
115
+ >
116
+ Remove
117
+ </TelaButton>
118
+ </li>
119
+ </ul>
120
+ </div>
121
+ </div>
122
+ </template>
123
+ ```
124
+
125
+ ## Props
126
+
127
+ <ArgTypes />
128
+
129
+ ```typescript
130
+ type FileUploadProps = {
131
+ accept?: string
132
+ multiple?: boolean
133
+ disabled?: boolean
134
+ maxSize?: number
135
+ }
136
+ ```
137
+
138
+ ## Events
139
+
140
+ - `change` - Emitted when files are selected with FileList
141
+ - `error` - Emitted when validation fails (e.g., file too large)
142
+
143
+ ## Features
144
+
145
+ - **Drag and Drop**: Drag files onto the component
146
+ - **Click to Browse**: Traditional file picker
147
+ - **File Type Filtering**: Accept specific file types
148
+ - **Multiple Files**: Select multiple files at once
149
+ - **Size Validation**: Enforce maximum file size
150
+ - **Disabled State**: Prevent file selection
151
+ - **Visual Feedback**: Hover and drag states
152
+ - **Accessible**: Keyboard navigation support
153
+
154
+ ## File Size Limits
155
+
156
+ Common file size limits:
157
+ - 1MB = 1048576 bytes
158
+ - 5MB = 5242880 bytes
159
+ - 10MB = 10485760 bytes
160
+ - 50MB = 52428800 bytes
161
+ - 100MB = 104857600 bytes
162
+
163
+ ## Accepted File Types
164
+
165
+ Common accept patterns:
166
+ - Images: `image/*`
167
+ - PDFs: `.pdf` or `application/pdf`
168
+ - Documents: `.doc,.docx,.txt`
169
+ - Spreadsheets: `.xls,.xlsx,.csv`
170
+ - Audio: `audio/*`
171
+ - Video: `video/*`
172
+ - Any: `*/*` (not recommended)
173
+
174
+ ## Best Practices
175
+
176
+ 1. **Set Accept Types**: Always specify accepted file types
177
+ 2. **Validate Size**: Set reasonable max size limits
178
+ 3. **Provide Feedback**: Show selected files and errors
179
+ 4. **Handle Errors**: Gracefully handle validation failures
180
+ 5. **Show Progress**: Display upload progress for large files
181
+ 6. **Clear Instructions**: Tell users what files are accepted
182
+
183
+ ## Accessibility
184
+
185
+ - Keyboard accessible file input
186
+ - Proper ARIA labels
187
+ - Focus states
188
+ - Disabled state properly conveyed
189
+ - Screen reader friendly
@@ -0,0 +1,48 @@
1
+ import type { Meta, StoryObj } from '@storybook-vue/vue3'
2
+
3
+ import FileUpload from './file-upload.vue'
4
+
5
+ const meta: Meta<typeof FileUpload> = {
6
+ title: 'Core/FileUpload',
7
+ component: FileUpload,
8
+ parameters: {
9
+ layout: 'centered',
10
+ docs: {
11
+ description: {
12
+ component: 'A file upload component that provides drag-and-drop functionality and traditional file selection. Supports multiple file selection, file type restrictions, and custom styling. Emits events for file selection and validation.',
13
+ },
14
+ },
15
+ },
16
+ argTypes: {
17
+ accept: {
18
+ control: 'text',
19
+ description: 'Comma-separated list of accepted file types (e.g., "image/*,.pdf").',
20
+ },
21
+ multiple: {
22
+ control: 'boolean',
23
+ description: 'Allow selection of multiple files.',
24
+ },
25
+ disabled: {
26
+ control: 'boolean',
27
+ description: 'Disable the file upload component.',
28
+ },
29
+ maxSize: {
30
+ control: 'number',
31
+ description: 'Maximum file size in bytes allowed for upload.',
32
+ },
33
+ },
34
+ }
35
+
36
+ export default meta
37
+
38
+ type Story = StoryObj<typeof meta>
39
+
40
+ export const Default: Story = {
41
+ render: () => ({
42
+ components: { FileUpload },
43
+ template: `
44
+ <div style="width: 100%; height: 300px; display: flex; align-items: center; justify-content: center;">
45
+ <FileUpload />
46
+ </div>`,
47
+ }),
48
+ }
@@ -0,0 +1,205 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import { useI18n } from 'vue-i18n'
4
+
5
+ const props = withDefaults(defineProps<{
6
+ class?: string
7
+ multiple?: boolean
8
+ maxFiles?: number
9
+ maxFileSize?: number // in MB
10
+ acceptedFormats?: string[] // e.g., ['image/png', 'image/jpeg', 'application/pdf']
11
+ acceptedExtensions?: string[] // e.g., ['.png', '.jpg', '.pdf'] - for display
12
+ }>(), {
13
+ multiple: true,
14
+ maxFiles: 5,
15
+ maxFileSize: 10, // 10MB default
16
+ acceptedFormats: () => [],
17
+ acceptedExtensions: () => [],
18
+ })
19
+
20
+ const emit = defineEmits<{
21
+ error: [message: string]
22
+ }>()
23
+
24
+ const { t } = useI18n()
25
+ const files = defineModel<File[]>({ required: true })
26
+ const isDragging = ref(false)
27
+
28
+ function validateFiles(newFiles: FileList | File[]): File[] {
29
+ const fileArray = Array.from(newFiles)
30
+ const maxSizeBytes = props.maxFileSize * 1024 * 1024
31
+
32
+ // Check file formats if specified
33
+ if (props.acceptedFormats.length > 0) {
34
+ const invalidFiles = fileArray.filter((file) => {
35
+ // Check MIME type
36
+ const mimeTypeMatch = props.acceptedFormats.includes(file.type)
37
+
38
+ // Check file extension as fallback
39
+ const extension = `.${file.name.split('.').pop()?.toLowerCase()}`
40
+ const extensionMatch = props.acceptedExtensions.some(ext =>
41
+ extension === ext.toLowerCase(),
42
+ )
43
+
44
+ return !mimeTypeMatch && !extensionMatch
45
+ })
46
+
47
+ if (invalidFiles.length > 0) {
48
+ const formatsDisplay = props.acceptedExtensions.length > 0
49
+ ? props.acceptedExtensions.join(', ').toUpperCase()
50
+ : props.acceptedFormats.join(', ')
51
+ emit('error', t('common.fileUpload.errors.invalidFormat', { formats: formatsDisplay }))
52
+ return []
53
+ }
54
+ }
55
+
56
+ // Check file sizes
57
+ const oversizedFiles = fileArray.filter(file => file.size > maxSizeBytes)
58
+ if (oversizedFiles.length > 0) {
59
+ emit('error', t('common.fileUpload.errors.fileTooLarge', { maxSize: props.maxFileSize }))
60
+ return []
61
+ }
62
+
63
+ // Check total file count
64
+ const totalFiles = files.value.length + fileArray.length
65
+ if (totalFiles > props.maxFiles) {
66
+ emit('error', t('common.fileUpload.errors.tooManyFiles', { maxFiles: props.maxFiles }))
67
+ return []
68
+ }
69
+
70
+ return fileArray
71
+ }
72
+
73
+ function handleFileChange(event: Event) {
74
+ const input = event.target as HTMLInputElement
75
+
76
+ if (input.files && input.files.length > 0) {
77
+ const validFiles = validateFiles(input.files)
78
+
79
+ if (validFiles.length > 0) {
80
+ if (props.multiple) {
81
+ // Append new files to existing ones
82
+ files.value = [...files.value, ...validFiles]
83
+ }
84
+ else {
85
+ files.value = [validFiles[0]]
86
+ }
87
+ }
88
+ }
89
+
90
+ // Reset input value to allow selecting the same file again
91
+ input.value = ''
92
+ }
93
+
94
+ function handleDragEnter(event: DragEvent) {
95
+ event.preventDefault()
96
+ isDragging.value = true
97
+ }
98
+
99
+ function handleDragLeave(event: DragEvent) {
100
+ event.preventDefault()
101
+ isDragging.value = false
102
+ }
103
+
104
+ function handleDragOver(event: DragEvent) {
105
+ event.preventDefault()
106
+ }
107
+
108
+ function handleDrop(event: DragEvent) {
109
+ event.preventDefault()
110
+ isDragging.value = false
111
+
112
+ const droppedFiles = event.dataTransfer?.files
113
+
114
+ if (droppedFiles && droppedFiles.length > 0) {
115
+ const validFiles = validateFiles(droppedFiles)
116
+
117
+ if (validFiles.length > 0) {
118
+ if (props.multiple) {
119
+ // Append new files to existing ones
120
+ files.value = [...files.value, ...validFiles]
121
+ }
122
+ else {
123
+ files.value = [validFiles[0]]
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ function removeFile(index: number) {
130
+ files.value = files.value.filter((_, i) => i !== index)
131
+ }
132
+
133
+ function formatFileSize(bytes: number): string {
134
+ if (bytes === 0)
135
+ return '0 Bytes'
136
+ const k = 1024
137
+ const sizes = ['Bytes', 'KB', 'MB', 'GB']
138
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
139
+ return `${Math.round(bytes / k ** i * 100) / 100} ${sizes[i]}`
140
+ }
141
+ </script>
142
+
143
+ <template>
144
+ <div class="flex flex-col gap-2 w-full">
145
+ <label
146
+ for="file-upload"
147
+ :class="cn(
148
+ 'w-full py-4.5 flex flex-col items-center justify-center gap-2.5 cursor-pointer bg-gray-50 border-[1px] border-gray-300 border-dashed rounded-xl transition-colors',
149
+ isDragging && 'border-gray-300 bg-gray-100',
150
+ props.class,
151
+ )"
152
+ @dragenter="handleDragEnter"
153
+ @dragleave="handleDragLeave"
154
+ @dragover="handleDragOver"
155
+ @drop="handleDrop"
156
+ >
157
+ <TelaIcon name="i-ph-upload-simple-bold" size="16px" class="text-gray-700" />
158
+ <div class="flex flex-col items-center justify-center gap-1">
159
+ <h3 class="text-gray-700 text-body-14-semibold font-580">
160
+ {{ $t('common.fileUpload.dragAndDrop') }} <TelaLinkDecoration class="text-gray-700">{{ $t('common.fileUpload.orBrowse') }}</TelaLinkDecoration>
161
+ </h3>
162
+ <p class="text-10px leading-12px tracking-normal text-gray-400">
163
+ <template v-if="acceptedExtensions.length > 0">
164
+ {{ acceptedExtensions.join(', ').toUpperCase() }}
165
+ </template>
166
+ <template v-else>
167
+ {{ $t('common.fileUpload.supportedFormats') }}
168
+ </template>
169
+ </p>
170
+ <span class="text-10px leading-12px tracking-normal text-gray-400">
171
+ {{ t('common.fileUpload.maxSize', { maxSize: maxFileSize }) }} • {{ t('common.fileUpload.filesCount', { current: files.length, max: maxFiles }) }}
172
+ </span>
173
+ </div>
174
+ </label>
175
+
176
+ <div v-if="files.length > 0" class="flex flex-col gap-2px ">
177
+ <div
178
+ v-for="(file, index) in files"
179
+ :key="`${file.name}-${index}`"
180
+ class="flex items-center justify-between px-3 py-2 bg-gray-50 rounded-lg border-1px border-gray-300"
181
+ >
182
+ <div class="flex items-center gap-2 flex-1 min-w-0">
183
+ <TelaIcon name="i-ph-file-bold" size="14px" class="text-gray-500 flex-shrink-0" />
184
+ <span class="text-body-14-regular text-gray-700 truncate">{{ file.name }}</span>
185
+ <span class="text-10px text-gray-400 flex-shrink-0">({{ formatFileSize(file.size) }})</span>
186
+ </div>
187
+ <button
188
+ type="button"
189
+ class="flex-shrink-0 p-1 hover:bg-gray-100 rounded transition-colors"
190
+ @click="removeFile(index)"
191
+ >
192
+ <TelaIcon name="i-ph-x-bold" size="12px" class="text-gray-500" />
193
+ </button>
194
+ </div>
195
+ </div>
196
+ </div>
197
+ <input
198
+ id="file-upload"
199
+ type="file"
200
+ class="hidden"
201
+ :multiple="props.multiple"
202
+ :accept="acceptedFormats.length > 0 ? acceptedFormats.join(',') : undefined"
203
+ @change="handleFileChange"
204
+ >
205
+ </template>
@@ -0,0 +1,218 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import { ref } from 'vue'
3
+ import CheckboxFilter from './checkbox-filter.vue'
4
+
5
+ const meta: Meta<typeof CheckboxFilter> = {
6
+ title: 'Filters/CheckboxFilter',
7
+ component: CheckboxFilter,
8
+ tags: ['autodocs'],
9
+ parameters: {
10
+ layout: 'centered',
11
+ docs: {
12
+ description: {
13
+ component: 'A dropdown filter component with searchable checkboxes for multi-select filtering. Features include a search bar, scrollable options list, and a sticky apply button. Ideal for filtering data by multiple criteria.',
14
+ },
15
+ },
16
+ },
17
+ argTypes: {
18
+ buttonLabel: {
19
+ control: 'text',
20
+ description: 'The label displayed on the filter button',
21
+ },
22
+ identifier: {
23
+ control: 'text',
24
+ description: 'Unique identifier for the filter',
25
+ },
26
+ options: {
27
+ control: 'object',
28
+ description: 'Object containing filter options. Each key should have a value and label property',
29
+ },
30
+ isActive: {
31
+ control: 'boolean',
32
+ description: 'Whether the filter is currently active (has selections applied)',
33
+ },
34
+ disabled: {
35
+ control: 'boolean',
36
+ description: 'Whether the filter button is disabled',
37
+ },
38
+ loading: {
39
+ control: 'boolean',
40
+ description: 'Whether the filter is in a loading state',
41
+ },
42
+ },
43
+ }
44
+
45
+ export default meta
46
+
47
+ type Story = StoryObj<typeof meta>
48
+
49
+ export const Default: Story = {
50
+ render: args => ({
51
+ components: { CheckboxFilter },
52
+ setup() {
53
+ const selected = ref({ identifier: 'type', label: 'Type', value: undefined })
54
+ const isActive = ref(false)
55
+
56
+ function handleApply(value: any) {
57
+ selected.value = {
58
+ identifier: 'type',
59
+ label: value.type.map((t: string) => t.charAt(0).toUpperCase() + t.slice(1)).join(', '),
60
+ value: value.type,
61
+ }
62
+ isActive.value = true
63
+ }
64
+
65
+ function handleReset() {
66
+ selected.value = { identifier: 'type', label: 'Type', value: undefined }
67
+ isActive.value = false
68
+ }
69
+
70
+ return { selected, isActive, handleApply, handleReset, args }
71
+ },
72
+ template: `
73
+ <div class="flex flex-col gap-4 p-4">
74
+ <CheckboxFilter
75
+ v-bind="args"
76
+ :selected="selected"
77
+ :is-active="isActive"
78
+ @apply="handleApply"
79
+ @reset="handleReset"
80
+ />
81
+ </div>
82
+ `,
83
+ }),
84
+ args: {
85
+ buttonLabel: 'Type',
86
+ identifier: 'type',
87
+ options: {
88
+ completion: { value: 'completion', label: 'Completion' },
89
+ parser: { value: 'parser', label: 'Parser' },
90
+ workflow: { value: 'workflow', label: 'Workflow' },
91
+ },
92
+ isActive: false,
93
+ disabled: false,
94
+ loading: false,
95
+ },
96
+ }
97
+
98
+ export const WithManyOptions: Story = {
99
+ render: args => ({
100
+ components: { CheckboxFilter },
101
+ setup() {
102
+ const selected = ref({ identifier: 'model', label: 'Model', value: undefined })
103
+ const isActive = ref(false)
104
+
105
+ function handleApply(value: any) {
106
+ const models = value.model
107
+ const label = models.length > 2
108
+ ? `${models[0]} & ${models.length - 1} others`
109
+ : models.join(' & ')
110
+ selected.value = {
111
+ identifier: 'model',
112
+ label,
113
+ value: models,
114
+ }
115
+ isActive.value = true
116
+ }
117
+
118
+ function handleReset() {
119
+ selected.value = { identifier: 'model', label: 'Model', value: undefined }
120
+ isActive.value = false
121
+ }
122
+
123
+ return { selected, isActive, handleApply, handleReset, args }
124
+ },
125
+ template: `
126
+ <div class="flex flex-col gap-4 p-4">
127
+ <CheckboxFilter
128
+ v-bind="args"
129
+ :selected="selected"
130
+ :is-active="isActive"
131
+ @apply="handleApply"
132
+ @reset="handleReset"
133
+ />
134
+ </div>
135
+ `,
136
+ }),
137
+ args: {
138
+ buttonLabel: 'Model',
139
+ identifier: 'model',
140
+ options: {
141
+ 'gpt-4': { value: 'gpt-4', label: 'GPT-4' },
142
+ 'gpt-4-turbo': { value: 'gpt-4-turbo', label: 'GPT-4 Turbo' },
143
+ 'gpt-3.5-turbo': { value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo' },
144
+ 'claude-3-opus': { value: 'claude-3-opus', label: 'Claude 3 Opus' },
145
+ 'claude-3-sonnet': { value: 'claude-3-sonnet', label: 'Claude 3 Sonnet' },
146
+ 'claude-3-haiku': { value: 'claude-3-haiku', label: 'Claude 3 Haiku' },
147
+ 'gemini-pro': { value: 'gemini-pro', label: 'Gemini Pro' },
148
+ 'llama-2-70b': { value: 'llama-2-70b', label: 'Llama 2 70B' },
149
+ },
150
+ isActive: false,
151
+ disabled: false,
152
+ loading: false,
153
+ },
154
+ }
155
+
156
+ export const Active: Story = {
157
+ render: args => ({
158
+ components: { CheckboxFilter },
159
+ setup() {
160
+ const selected = ref({ identifier: 'status', label: 'Succeeded, Failed', value: ['success', 'failed'] })
161
+ const isActive = ref(true)
162
+
163
+ function handleApply(value: any) {
164
+ selected.value = {
165
+ identifier: 'status',
166
+ label: value.status.map((s: string) => s.charAt(0).toUpperCase() + s.slice(1)).join(', '),
167
+ value: value.status,
168
+ }
169
+ isActive.value = true
170
+ }
171
+
172
+ function handleReset() {
173
+ selected.value = { identifier: 'status', label: 'Status', value: [] }
174
+ isActive.value = false
175
+ }
176
+
177
+ return { selected, isActive, handleApply, handleReset, args }
178
+ },
179
+ template: `
180
+ <div class="flex flex-col gap-4 p-4">
181
+ <CheckboxFilter
182
+ v-bind="args"
183
+ :selected="selected"
184
+ :is-active="isActive"
185
+ @apply="handleApply"
186
+ @reset="handleReset"
187
+ />
188
+ </div>
189
+ `,
190
+ }),
191
+ args: {
192
+ buttonLabel: 'Status',
193
+ identifier: 'status',
194
+ options: {
195
+ success: { value: 'success', label: 'Succeeded' },
196
+ failed: { value: 'failed', label: 'Failed' },
197
+ },
198
+ isActive: true,
199
+ disabled: false,
200
+ loading: false,
201
+ },
202
+ }
203
+
204
+ export const Disabled: Story = {
205
+ ...Default,
206
+ args: {
207
+ ...Default.args,
208
+ disabled: true,
209
+ },
210
+ }
211
+
212
+ export const Loading: Story = {
213
+ ...Default,
214
+ args: {
215
+ ...Default.args,
216
+ loading: true,
217
+ },
218
+ }