@umbra.ui/core 0.1.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 (272) hide show
  1. package/dist/components/controls/Dropdown/types.d.ts +5 -0
  2. package/dist/components/controls/Dropdown/types.d.ts.map +1 -0
  3. package/dist/components/controls/Dropdown/types.js +1 -0
  4. package/dist/components/controls/SegmentedControl/types.d.ts +6 -0
  5. package/dist/components/controls/SegmentedControl/types.d.ts.map +1 -0
  6. package/dist/components/controls/SegmentedControl/types.js +1 -0
  7. package/dist/components/dialogs/Alert/types.d.ts +7 -0
  8. package/dist/components/dialogs/Alert/types.d.ts.map +1 -0
  9. package/dist/components/dialogs/Alert/types.js +1 -0
  10. package/dist/components/dialogs/Toast/types.d.ts +34 -0
  11. package/dist/components/dialogs/Toast/types.d.ts.map +1 -0
  12. package/dist/components/dialogs/Toast/types.js +10 -0
  13. package/dist/components/dialogs/Toast/useToast.d.ts +36 -0
  14. package/dist/components/dialogs/Toast/useToast.d.ts.map +1 -0
  15. package/dist/components/dialogs/Toast/useToast.js +90 -0
  16. package/dist/components/indicators/Tooltip/tooltip.d.ts +3 -0
  17. package/dist/components/indicators/Tooltip/tooltip.d.ts.map +1 -0
  18. package/dist/components/indicators/Tooltip/tooltip.js +33 -0
  19. package/dist/components/indicators/Tooltip/types.d.ts +14 -0
  20. package/dist/components/indicators/Tooltip/types.d.ts.map +1 -0
  21. package/dist/components/indicators/Tooltip/types.js +1 -0
  22. package/dist/components/indicators/Tooltip/useTooltip.d.ts +18 -0
  23. package/dist/components/indicators/Tooltip/useTooltip.d.ts.map +1 -0
  24. package/dist/components/indicators/Tooltip/useTooltip.js +57 -0
  25. package/dist/components/inputs/Tags/tag-bar-styles.d.ts +14 -0
  26. package/dist/components/inputs/Tags/tag-bar-styles.d.ts.map +1 -0
  27. package/dist/components/inputs/Tags/tag-bar-styles.js +313 -0
  28. package/dist/components/inputs/Tags/types.d.ts +93 -0
  29. package/dist/components/inputs/Tags/types.d.ts.map +1 -0
  30. package/dist/components/inputs/Tags/types.js +216 -0
  31. package/dist/components/inputs/search/types.d.ts +9 -0
  32. package/dist/components/inputs/search/types.d.ts.map +1 -0
  33. package/dist/components/inputs/search/types.js +1 -0
  34. package/dist/components/navigation/adaptive/types.d.ts +16 -0
  35. package/dist/components/navigation/adaptive/types.d.ts.map +1 -0
  36. package/dist/components/navigation/adaptive/types.js +1 -0
  37. package/dist/components/navigation/adaptive/useAdaptiveLayout.d.ts +27 -0
  38. package/dist/components/navigation/adaptive/useAdaptiveLayout.d.ts.map +1 -0
  39. package/dist/components/navigation/adaptive/useAdaptiveLayout.js +40 -0
  40. package/dist/components/navigation/adaptive/useBreakpoints.d.ts +6 -0
  41. package/dist/components/navigation/adaptive/useBreakpoints.d.ts.map +1 -0
  42. package/dist/components/navigation/adaptive/useBreakpoints.js +37 -0
  43. package/dist/components/navigation/adaptive/useContainerMonitor.d.ts +93 -0
  44. package/dist/components/navigation/adaptive/useContainerMonitor.d.ts.map +1 -0
  45. package/dist/components/navigation/adaptive/useContainerMonitor.js +145 -0
  46. package/dist/components/navigation/adaptive/useViewAnimation.d.ts +31 -0
  47. package/dist/components/navigation/adaptive/useViewAnimation.d.ts.map +1 -0
  48. package/dist/components/navigation/adaptive/useViewAnimation.js +591 -0
  49. package/dist/components/navigation/adaptive/useViewResize.d.ts +52 -0
  50. package/dist/components/navigation/adaptive/useViewResize.d.ts.map +1 -0
  51. package/dist/components/navigation/adaptive/useViewResize.js +146 -0
  52. package/dist/components/navigation/navstack/useNavigationStack.d.ts +25 -0
  53. package/dist/components/navigation/navstack/useNavigationStack.d.ts.map +1 -0
  54. package/dist/components/navigation/navstack/useNavigationStack.js +133 -0
  55. package/dist/components/navigation/slideover/useSlideoverController.d.ts +20 -0
  56. package/dist/components/navigation/slideover/useSlideoverController.d.ts.map +1 -0
  57. package/dist/components/navigation/slideover/useSlideoverController.js +267 -0
  58. package/dist/components/navigation/splitview/useSplitViewController.d.ts +20 -0
  59. package/dist/components/navigation/splitview/useSplitViewController.d.ts.map +1 -0
  60. package/dist/components/navigation/splitview/useSplitViewController.js +325 -0
  61. package/dist/components/navigation/tabcontroller/types.d.ts +21 -0
  62. package/dist/components/navigation/tabcontroller/types.d.ts.map +1 -0
  63. package/dist/components/navigation/tabcontroller/types.js +1 -0
  64. package/dist/components/navigation/tabcontroller/useTabController.d.ts +5 -0
  65. package/dist/components/navigation/tabcontroller/useTabController.d.ts.map +1 -0
  66. package/dist/components/navigation/tabcontroller/useTabController.js +10 -0
  67. package/dist/components/navigation/types.d.ts +8 -0
  68. package/dist/components/navigation/types.d.ts.map +1 -0
  69. package/dist/components/navigation/types.js +1 -0
  70. package/dist/components/pickers/CollectionPicker/types.d.ts +11 -0
  71. package/dist/components/pickers/CollectionPicker/types.d.ts.map +1 -0
  72. package/dist/components/pickers/CollectionPicker/types.js +1 -0
  73. package/dist/components/pickers/ColorPicker/colors.d.ts +13 -0
  74. package/dist/components/pickers/ColorPicker/colors.d.ts.map +1 -0
  75. package/dist/components/pickers/ColorPicker/colors.js +266 -0
  76. package/dist/components/pickers/FilePicker/types.d.ts +10 -0
  77. package/dist/components/pickers/FilePicker/types.d.ts.map +1 -0
  78. package/dist/components/pickers/FilePicker/types.js +1 -0
  79. package/dist/index.d.ts +91 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +196 -0
  82. package/dist/theme.d.ts +73 -0
  83. package/dist/theme.d.ts.map +1 -0
  84. package/dist/theme.js +279 -0
  85. package/dist/themes/blank.d.ts +7 -0
  86. package/dist/themes/blank.d.ts.map +1 -0
  87. package/dist/themes/blank.js +543 -0
  88. package/dist/themes/crimson-dark.d.ts +4 -0
  89. package/dist/themes/crimson-dark.d.ts.map +1 -0
  90. package/dist/themes/crimson-dark.js +552 -0
  91. package/dist/themes/cyan-light.d.ts +4 -0
  92. package/dist/themes/cyan-light.d.ts.map +1 -0
  93. package/dist/themes/cyan-light.js +552 -0
  94. package/dist/themes/dark.d.ts +4 -0
  95. package/dist/themes/dark.d.ts.map +1 -0
  96. package/dist/themes/dark.js +551 -0
  97. package/dist/themes/gold-dark.d.ts +4 -0
  98. package/dist/themes/gold-dark.d.ts.map +1 -0
  99. package/dist/themes/gold-dark.js +552 -0
  100. package/dist/themes/grass-dark.d.ts +4 -0
  101. package/dist/themes/grass-dark.d.ts.map +1 -0
  102. package/dist/themes/grass-dark.js +552 -0
  103. package/dist/themes/indigo.d.ts +4 -0
  104. package/dist/themes/indigo.d.ts.map +1 -0
  105. package/dist/themes/indigo.js +552 -0
  106. package/dist/themes/light.d.ts +4 -0
  107. package/dist/themes/light.d.ts.map +1 -0
  108. package/dist/themes/light.js +551 -0
  109. package/dist/themes/orange-dark.d.ts +4 -0
  110. package/dist/themes/orange-dark.d.ts.map +1 -0
  111. package/dist/themes/orange-dark.js +551 -0
  112. package/dist/themes/orange-light.d.ts +4 -0
  113. package/dist/themes/orange-light.d.ts.map +1 -0
  114. package/dist/themes/orange-light.js +551 -0
  115. package/package.json +62 -0
  116. package/src/components/controls/Button/Button.vue +417 -0
  117. package/src/components/controls/Button/README.md +348 -0
  118. package/src/components/controls/Button/theme.css +200 -0
  119. package/src/components/controls/Checkbox/Checkbox.vue +164 -0
  120. package/src/components/controls/Checkbox/README.md +441 -0
  121. package/src/components/controls/Checkbox/theme.css +36 -0
  122. package/src/components/controls/Dropdown/Dropdown.vue +476 -0
  123. package/src/components/controls/Dropdown/README.md +370 -0
  124. package/src/components/controls/Dropdown/theme.css +50 -0
  125. package/src/components/controls/Dropdown/types.ts +6 -0
  126. package/src/components/controls/IconButton/IconButton.vue +267 -0
  127. package/src/components/controls/IconButton/README.md +502 -0
  128. package/src/components/controls/IconButton/theme.css +89 -0
  129. package/src/components/controls/Radio/README.md +591 -0
  130. package/src/components/controls/Radio/Radio.vue +89 -0
  131. package/src/components/controls/Radio/theme.css +14 -0
  132. package/src/components/controls/RangeSlider/README.md +608 -0
  133. package/src/components/controls/RangeSlider/RangeSlider.vue +535 -0
  134. package/src/components/controls/RangeSlider/theme.css +80 -0
  135. package/src/components/controls/SegmentedControl/README.md +587 -0
  136. package/src/components/controls/SegmentedControl/SegmentedControl.vue +284 -0
  137. package/src/components/controls/SegmentedControl/theme.css +60 -0
  138. package/src/components/controls/SegmentedControl/types.ts +5 -0
  139. package/src/components/controls/Slider/README.md +627 -0
  140. package/src/components/controls/Slider/Slider.vue +260 -0
  141. package/src/components/controls/Slider/theme.css +74 -0
  142. package/src/components/controls/Stepper/README.md +601 -0
  143. package/src/components/controls/Stepper/Stepper.vue +103 -0
  144. package/src/components/controls/Stepper/theme.css +53 -0
  145. package/src/components/controls/Switch/README.md +667 -0
  146. package/src/components/controls/Switch/Switch.vue +127 -0
  147. package/src/components/controls/Switch/theme.css +42 -0
  148. package/src/components/dialogs/Alert/Alert.vue +218 -0
  149. package/src/components/dialogs/Alert/README.md +450 -0
  150. package/src/components/dialogs/Alert/theme.css +44 -0
  151. package/src/components/dialogs/Alert/types.ts +11 -0
  152. package/src/components/dialogs/Toast/README.md +522 -0
  153. package/src/components/dialogs/Toast/Toast.vue +296 -0
  154. package/src/components/dialogs/Toast/ToastContainer.vue +330 -0
  155. package/src/components/dialogs/Toast/theme.css +44 -0
  156. package/src/components/dialogs/Toast/types.ts +46 -0
  157. package/src/components/dialogs/Toast/useToast.ts +127 -0
  158. package/src/components/indicators/ProgressBar/ProgressBar.vue +98 -0
  159. package/src/components/indicators/ProgressBar/README.md +744 -0
  160. package/src/components/indicators/ProgressBar/theme.css +36 -0
  161. package/src/components/indicators/Tooltip/README.md +723 -0
  162. package/src/components/indicators/Tooltip/TooltipProvider.vue +142 -0
  163. package/src/components/indicators/Tooltip/theme.css +18 -0
  164. package/src/components/indicators/Tooltip/tooltip.ts +48 -0
  165. package/src/components/indicators/Tooltip/types.ts +15 -0
  166. package/src/components/indicators/Tooltip/useTooltip.ts +71 -0
  167. package/src/components/inputs/AutogrowTextView/AutogrowTextView.vue +110 -0
  168. package/src/components/inputs/AutogrowTextView/README.md +643 -0
  169. package/src/components/inputs/AutogrowTextView/theme.css +28 -0
  170. package/src/components/inputs/InputCard/InputCard.vue +600 -0
  171. package/src/components/inputs/InputCard/README.md +636 -0
  172. package/src/components/inputs/InputEmail/InputEmail.vue +698 -0
  173. package/src/components/inputs/InputEmail/README.md +764 -0
  174. package/src/components/inputs/InputNumber/InputNumber.vue +300 -0
  175. package/src/components/inputs/InputNumber/README.md +749 -0
  176. package/src/components/inputs/InputPhone/InputPhone.vue +645 -0
  177. package/src/components/inputs/InputPhone/README.md +636 -0
  178. package/src/components/inputs/InputSecure/InputSecure.vue +646 -0
  179. package/src/components/inputs/InputSecure/README.md +771 -0
  180. package/src/components/inputs/InputText/InputText.vue +225 -0
  181. package/src/components/inputs/InputText/README.md +844 -0
  182. package/src/components/inputs/OTP/OTP.vue +349 -0
  183. package/src/components/inputs/OTP/README.md +736 -0
  184. package/src/components/inputs/OTP/theme.css +50 -0
  185. package/src/components/inputs/StringCapture/README.md +718 -0
  186. package/src/components/inputs/StringCapture/StringCapture.vue +315 -0
  187. package/src/components/inputs/StringCapture/theme.css +86 -0
  188. package/src/components/inputs/Tags/README.md +897 -0
  189. package/src/components/inputs/Tags/TagBar.vue +793 -0
  190. package/src/components/inputs/Tags/TagCreation.vue +219 -0
  191. package/src/components/inputs/Tags/TagPicker.vue +380 -0
  192. package/src/components/inputs/Tags/tag-bar-styles.ts +354 -0
  193. package/src/components/inputs/Tags/theme.css +121 -0
  194. package/src/components/inputs/Tags/types.ts +346 -0
  195. package/src/components/inputs/search/README.md +759 -0
  196. package/src/components/inputs/search/SearchBar.vue +394 -0
  197. package/src/components/inputs/search/SearchResults.vue +310 -0
  198. package/src/components/inputs/search/theme.css +187 -0
  199. package/src/components/inputs/search/types.ts +8 -0
  200. package/src/components/inputs/theme.css +102 -0
  201. package/src/components/menus/ActionMenu/ActionMenu.vue +383 -0
  202. package/src/components/menus/ActionMenu/README.md +825 -0
  203. package/src/components/menus/ActionMenu/theme.css +93 -0
  204. package/src/components/models/Popover/Popover.vue +551 -0
  205. package/src/components/models/Popover/README.md +885 -0
  206. package/src/components/models/Popover/theme.css +52 -0
  207. package/src/components/models/Sheet/README.md +1159 -0
  208. package/src/components/models/Sheet/Sheet.vue +465 -0
  209. package/src/components/models/Sheet/theme.css +72 -0
  210. package/src/components/models/Sidebar/README.md +1228 -0
  211. package/src/components/models/Sidebar/Sidebar.vue +480 -0
  212. package/src/components/models/Sidebar/theme.css +90 -0
  213. package/src/components/navigation/adaptive/AdaptiveLayout.vue +779 -0
  214. package/src/components/navigation/adaptive/AdaptiveLayoutBreadcrumbs.vue +192 -0
  215. package/src/components/navigation/adaptive/AdaptiveLayoutMenuButton.vue +149 -0
  216. package/src/components/navigation/adaptive/README.md +768 -0
  217. package/src/components/navigation/adaptive/types.ts +19 -0
  218. package/src/components/navigation/adaptive/useAdaptiveLayout.ts +89 -0
  219. package/src/components/navigation/adaptive/useBreakpoints.ts +41 -0
  220. package/src/components/navigation/adaptive/useContainerMonitor.ts +214 -0
  221. package/src/components/navigation/adaptive/useViewAnimation.ts +721 -0
  222. package/src/components/navigation/adaptive/useViewResize.ts +211 -0
  223. package/src/components/navigation/navstack/NavigationStack.vue +180 -0
  224. package/src/components/navigation/navstack/README.md +994 -0
  225. package/src/components/navigation/navstack/useNavigationStack.ts +164 -0
  226. package/src/components/navigation/slideover/README.md +1275 -0
  227. package/src/components/navigation/slideover/SlideoverController.vue +287 -0
  228. package/src/components/navigation/slideover/useSlideoverController.ts +320 -0
  229. package/src/components/navigation/splitview/README.md +1115 -0
  230. package/src/components/navigation/splitview/SplitViewController.vue +176 -0
  231. package/src/components/navigation/splitview/useSplitViewController.ts +388 -0
  232. package/src/components/navigation/tabcontroller/README.md +919 -0
  233. package/src/components/navigation/tabcontroller/TabController.vue +307 -0
  234. package/src/components/navigation/tabcontroller/TabItem.vue +57 -0
  235. package/src/components/navigation/tabcontroller/types.ts +24 -0
  236. package/src/components/navigation/tabcontroller/useTabController.ts +18 -0
  237. package/src/components/navigation/theme.css +91 -0
  238. package/src/components/navigation/types.ts +7 -0
  239. package/src/components/pickers/CollectionPicker/CollectionPicker.vue +398 -0
  240. package/src/components/pickers/CollectionPicker/README.md +1115 -0
  241. package/src/components/pickers/CollectionPicker/theme.css +14 -0
  242. package/src/components/pickers/CollectionPicker/types.ts +11 -0
  243. package/src/components/pickers/ColorPicker/ColorPicker.vue +376 -0
  244. package/src/components/pickers/ColorPicker/README.md +1439 -0
  245. package/src/components/pickers/ColorPicker/colors.ts +299 -0
  246. package/src/components/pickers/ColorPicker/theme.css +32 -0
  247. package/src/components/pickers/DatePicker/DatePicker.vue +660 -0
  248. package/src/components/pickers/DatePicker/README.md +1195 -0
  249. package/src/components/pickers/DatePicker/theme.css +22 -0
  250. package/src/components/pickers/FilePicker/FilePicker.vue +534 -0
  251. package/src/components/pickers/FilePicker/README.md +1542 -0
  252. package/src/components/pickers/FilePicker/theme.css +48 -0
  253. package/src/components/pickers/FilePicker/types.ts +10 -0
  254. package/src/components/pickers/IconPicker/IconPicker.vue +327 -0
  255. package/src/components/pickers/IconPicker/README.md +1161 -0
  256. package/src/components/pickers/IconPicker/theme.css +28 -0
  257. package/src/components/pickers/theme.css +82 -0
  258. package/src/components/views/MarkdownViewer/MarkdownViewer.vue +442 -0
  259. package/src/components/views/MarkdownViewer/README.md +833 -0
  260. package/src/components/views/MarkdownViewer/theme.css +130 -0
  261. package/src/index.ts +263 -0
  262. package/src/theme.ts +378 -0
  263. package/src/themes/crimson-dark.ts +556 -0
  264. package/src/themes/cyan-light.ts +556 -0
  265. package/src/themes/dark.ts +557 -0
  266. package/src/themes/gold-dark.ts +556 -0
  267. package/src/themes/grass-dark.ts +556 -0
  268. package/src/themes/indigo.ts +556 -0
  269. package/src/themes/light.ts +557 -0
  270. package/src/themes/orange-dark.ts +557 -0
  271. package/src/themes/orange-light.ts +557 -0
  272. package/src/vue.d.ts +45 -0
@@ -0,0 +1,1542 @@
1
+ # FilePicker
2
+
3
+ A comprehensive file picker component built with Vue 3 Composition API and TypeScript. The FilePicker provides an intuitive drag-and-drop interface for file selection with support for file validation, directory traversal, multiple file handling, and smooth animations using GSAP.
4
+
5
+ ## Installation/Import
6
+
7
+ ```typescript
8
+ import { FilePicker } from "@umbra-ui/core";
9
+ import type { FileError, DropState } from "@umbra-ui/core";
10
+ ```
11
+
12
+ **Dependencies:**
13
+
14
+ - Vue 3.x
15
+ - GSAP (for animations)
16
+ - @umbra-ui/icons (for icons)
17
+
18
+ ## Basic Usage
19
+
20
+ ```vue
21
+ <script setup lang="ts">
22
+ import { ref } from "vue";
23
+ import { FilePicker } from "@umbra-ui/core";
24
+ import type { FileError } from "@umbra-ui/core";
25
+
26
+ const selectedFiles = ref<File[]>([]);
27
+ const fileErrors = ref<FileError[]>([]);
28
+
29
+ const handleFilesUpdate = (files: File[]) => {
30
+ console.log("Files updated:", files);
31
+ selectedFiles.value = files;
32
+ };
33
+
34
+ const handleFileDrop = (files: File[]) => {
35
+ console.log("New files dropped:", files);
36
+ };
37
+
38
+ const handleFileErrors = (errors: FileError[]) => {
39
+ console.log("File errors:", errors);
40
+ fileErrors.value = errors;
41
+ };
42
+ </script>
43
+
44
+ <template>
45
+ <div class="app">
46
+ <h2>Upload Files</h2>
47
+
48
+ <FilePicker
49
+ v-model:files="selectedFiles"
50
+ accept="image/*,.pdf,.doc,.docx"
51
+ :max-size="5 * 1024 * 1024"
52
+ :max-files="10"
53
+ @update:files="handleFilesUpdate"
54
+ @drop="handleFileDrop"
55
+ @error="handleFileErrors"
56
+ />
57
+
58
+ <div v-if="selectedFiles.length > 0" class="file-list">
59
+ <h3>Selected Files ({{ selectedFiles.length }})</h3>
60
+ <div v-for="file in selectedFiles" :key="file.name" class="file-item">
61
+ <span class="file-name">{{ file.name }}</span>
62
+ <span class="file-size">{{ formatFileSize(file.size) }}</span>
63
+ </div>
64
+ </div>
65
+
66
+ <div v-if="fileErrors.length > 0" class="error-list">
67
+ <h3>Errors</h3>
68
+ <div v-for="error in fileErrors" :key="error.file" class="error-item">
69
+ <strong>{{ error.file }}:</strong> {{ error.error }}
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </template>
74
+
75
+ <style module>
76
+ .app {
77
+ padding: 24px;
78
+ max-width: 600px;
79
+ }
80
+
81
+ .file-list {
82
+ margin-top: 20px;
83
+ padding: 16px;
84
+ background: #f8f9fa;
85
+ border-radius: 8px;
86
+ }
87
+
88
+ .file-item {
89
+ display: flex;
90
+ justify-content: space-between;
91
+ align-items: center;
92
+ padding: 8px 0;
93
+ border-bottom: 1px solid #e9ecef;
94
+ }
95
+
96
+ .file-item:last-child {
97
+ border-bottom: none;
98
+ }
99
+
100
+ .file-name {
101
+ font-weight: 500;
102
+ color: #495057;
103
+ }
104
+
105
+ .file-size {
106
+ color: #6c757d;
107
+ font-size: 14px;
108
+ }
109
+
110
+ .error-list {
111
+ margin-top: 20px;
112
+ padding: 16px;
113
+ background: #f8d7da;
114
+ border-radius: 8px;
115
+ border: 1px solid #f5c6cb;
116
+ }
117
+
118
+ .error-item {
119
+ color: #721c24;
120
+ margin-bottom: 8px;
121
+ }
122
+
123
+ .error-item:last-child {
124
+ margin-bottom: 0;
125
+ }
126
+ </style>
127
+ ```
128
+
129
+ ## Props
130
+
131
+ | Prop Name | Type | Required | Default | Description |
132
+ | ------------- | -------------------- | -------- | ---------- | ---------------------------------------------------- |
133
+ | `accept` | `string \| string[]` | No | `"*/*"` | Accepted file types (MIME types or extensions) |
134
+ | `multiple` | `boolean` | No | `true` | Whether to allow multiple file selection |
135
+ | `disabled` | `boolean` | No | `false` | Whether the picker is disabled |
136
+ | `maxSize` | `number` | No | `Infinity` | Maximum file size in bytes |
137
+ | `maxFiles` | `number` | No | `Infinity` | Maximum number of files allowed |
138
+ | `recursive` | `boolean` | No | `true` | Whether to traverse directories recursively |
139
+ | `appendFiles` | `boolean` | No | `true` | Whether to append new files or replace existing ones |
140
+
141
+ ## Events
142
+
143
+ | Event Name | Payload Type | Description |
144
+ | -------------- | ------------- | ----------------------------------------------- |
145
+ | `update:files` | `File[]` | Emitted when the files list is updated |
146
+ | `error` | `FileError[]` | Emitted when file validation errors occur |
147
+ | `drop` | `File[]` | Emitted when files are dropped (only new files) |
148
+
149
+ ## FileError Interface
150
+
151
+ ```typescript
152
+ interface FileError {
153
+ file: string;
154
+ error: string;
155
+ }
156
+ ```
157
+
158
+ ### FileError Properties
159
+
160
+ | Property | Type | Description |
161
+ | -------- | -------- | -------------------------------------- |
162
+ | `file` | `string` | Name of the file that caused the error |
163
+ | `error` | `string` | Description of the error |
164
+
165
+ ## DropState Interface
166
+
167
+ ```typescript
168
+ interface DropState {
169
+ isDragging: boolean;
170
+ isProcessing: boolean;
171
+ dragCount: number;
172
+ }
173
+ ```
174
+
175
+ ### DropState Properties
176
+
177
+ | Property | Type | Description |
178
+ | -------------- | --------- | ------------------------------------------------------------ |
179
+ | `isDragging` | `boolean` | Whether files are currently being dragged over the drop zone |
180
+ | `isProcessing` | `boolean` | Whether files are currently being processed |
181
+ | `dragCount` | `number` | Number of drag enter/leave events (for nested elements) |
182
+
183
+ ## Exposed Methods
184
+
185
+ | Method | Parameters | Description |
186
+ | ---------------- | ---------- | ---------------------------------------------- |
187
+ | `openFileDialog` | None | Opens the native file selection dialog |
188
+ | `clearFiles` | None | Clears all selected files |
189
+ | `files` | None | Reactive reference to the current files array |
190
+ | `errors` | None | Reactive reference to the current errors array |
191
+
192
+ ## CSS Customization
193
+
194
+ ### Layout Variables
195
+
196
+ ```css
197
+ .file-picker {
198
+ --filepicker-container-bg: #ffffff;
199
+ --filepicker-container-border: #d1d5db;
200
+ --filepicker-container-hover-bg: #f9fafb;
201
+ --filepicker-container-hover-border: #9ca3af;
202
+ --filepicker-container-active-bg: #e5e7eb;
203
+ --filepicker-container-active-border: #0090ff;
204
+ --filepicker-container-active-text: #000000;
205
+ --filepicker-overlay-bg: rgba(255, 255, 255, 0.9);
206
+ --filepicker-overlay-text: #000000;
207
+ --filepicker-spinner-border: #d1d5db;
208
+ --filepicker-spinner-border-top: #6366f1;
209
+ --filepicker-disabled-opacity: 0.5;
210
+ --filepicker-icon-color: #6b7280;
211
+ --filepicker-title-color: #374151;
212
+ --filepicker-subtitle-color: #6b7280;
213
+ --filepicker-processing-bg: rgba(255, 255, 255, 0.8);
214
+ }
215
+ ```
216
+
217
+ ### Container Styling
218
+
219
+ ```css
220
+ .file-picker .container {
221
+ position: relative;
222
+ width: 100%;
223
+ min-height: 200px;
224
+ border: 1px dashed var(--filepicker-container-border);
225
+ border-radius: 0.706rem;
226
+ background-color: var(--filepicker-container-bg);
227
+ transition: all 0.2s ease;
228
+ overflow: hidden;
229
+ }
230
+ ```
231
+
232
+ ### Overlay Styling
233
+
234
+ ```css
235
+ .file-picker .overlay {
236
+ position: absolute;
237
+ inset: 0;
238
+ display: flex;
239
+ align-items: center;
240
+ justify-content: center;
241
+ background-color: var(--filepicker-overlay-bg);
242
+ backdrop-filter: blur(4px);
243
+ opacity: 0;
244
+ scale: 0.95;
245
+ pointer-events: none;
246
+ }
247
+ ```
248
+
249
+ ## Examples
250
+
251
+ ### Image Uploader
252
+
253
+ ```vue
254
+ <script setup lang="ts">
255
+ import { ref, computed } from "vue";
256
+ import { FilePicker } from "@umbra-ui/core";
257
+ import type { FileError } from "@umbra-ui/core";
258
+
259
+ const images = ref<File[]>([]);
260
+ const errors = ref<FileError[]>([]);
261
+
262
+ const totalSize = computed(() => {
263
+ return images.value.reduce((total, file) => total + file.size, 0);
264
+ });
265
+
266
+ const formatFileSize = (bytes: number): string => {
267
+ if (bytes === 0) return "0 Bytes";
268
+ const k = 1024;
269
+ const sizes = ["Bytes", "KB", "MB", "GB"];
270
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
271
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
272
+ };
273
+
274
+ const handleImagesUpdate = (files: File[]) => {
275
+ images.value = files;
276
+ console.log("Images updated:", files);
277
+ };
278
+
279
+ const handleImageErrors = (fileErrors: FileError[]) => {
280
+ errors.value = fileErrors;
281
+ console.log("Image errors:", fileErrors);
282
+ };
283
+
284
+ const removeImage = (index: number) => {
285
+ images.value.splice(index, 1);
286
+ };
287
+
288
+ const clearAllImages = () => {
289
+ images.value = [];
290
+ errors.value = [];
291
+ };
292
+ </script>
293
+
294
+ <template>
295
+ <div class="image-uploader">
296
+ <div class="uploader-header">
297
+ <h3>Image Gallery</h3>
298
+ <p>Upload your images (max 5MB each, 10 images total)</p>
299
+ </div>
300
+
301
+ <FilePicker
302
+ v-model:files="images"
303
+ accept="image/*"
304
+ :max-size="5 * 1024 * 1024"
305
+ :max-files="10"
306
+ @update:files="handleImagesUpdate"
307
+ @error="handleImageErrors"
308
+ >
309
+ <div class="custom-content">
310
+ <div class="upload-icon">📸</div>
311
+ <div class="upload-text">
312
+ <h4>Drop images here or click to browse</h4>
313
+ <p>Supports JPG, PNG, GIF, WebP</p>
314
+ </div>
315
+ </div>
316
+ </FilePicker>
317
+
318
+ <div v-if="images.length > 0" class="image-gallery">
319
+ <div class="gallery-header">
320
+ <h4>Uploaded Images ({{ images.length }})</h4>
321
+ <div class="gallery-stats">
322
+ <span>Total size: {{ formatFileSize(totalSize) }}</span>
323
+ <button @click="clearAllImages" class="clear-btn">Clear All</button>
324
+ </div>
325
+ </div>
326
+
327
+ <div class="image-grid">
328
+ <div
329
+ v-for="(image, index) in images"
330
+ :key="image.name"
331
+ class="image-item"
332
+ >
333
+ <div class="image-preview">
334
+ <img
335
+ :src="URL.createObjectURL(image)"
336
+ :alt="image.name"
337
+ class="preview-img"
338
+ />
339
+ <button @click="removeImage(index)" class="remove-btn">×</button>
340
+ </div>
341
+ <div class="image-info">
342
+ <p class="image-name">{{ image.name }}</p>
343
+ <p class="image-size">{{ formatFileSize(image.size) }}</p>
344
+ </div>
345
+ </div>
346
+ </div>
347
+ </div>
348
+
349
+ <div v-if="errors.length > 0" class="error-section">
350
+ <h4>Upload Errors</h4>
351
+ <div v-for="error in errors" :key="error.file" class="error-item">
352
+ <strong>{{ error.file }}:</strong> {{ error.error }}
353
+ </div>
354
+ </div>
355
+ </div>
356
+ </template>
357
+
358
+ <style module>
359
+ .image-uploader {
360
+ padding: 24px;
361
+ max-width: 800px;
362
+ background: #ffffff;
363
+ border-radius: 12px;
364
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
365
+ }
366
+
367
+ .uploader-header {
368
+ margin-bottom: 24px;
369
+ }
370
+
371
+ .uploader-header h3 {
372
+ font-size: 24px;
373
+ font-weight: 600;
374
+ color: #1a202c;
375
+ margin: 0 0 8px 0;
376
+ }
377
+
378
+ .uploader-header p {
379
+ color: #718096;
380
+ margin: 0;
381
+ }
382
+
383
+ .custom-content {
384
+ display: flex;
385
+ flex-direction: column;
386
+ align-items: center;
387
+ gap: 16px;
388
+ text-align: center;
389
+ }
390
+
391
+ .upload-icon {
392
+ font-size: 48px;
393
+ }
394
+
395
+ .upload-text h4 {
396
+ font-size: 18px;
397
+ font-weight: 600;
398
+ color: #2d3748;
399
+ margin: 0 0 8px 0;
400
+ }
401
+
402
+ .upload-text p {
403
+ color: #718096;
404
+ margin: 0;
405
+ }
406
+
407
+ .image-gallery {
408
+ margin-top: 32px;
409
+ }
410
+
411
+ .gallery-header {
412
+ display: flex;
413
+ justify-content: space-between;
414
+ align-items: center;
415
+ margin-bottom: 20px;
416
+ }
417
+
418
+ .gallery-header h4 {
419
+ font-size: 18px;
420
+ font-weight: 500;
421
+ color: #2d3748;
422
+ margin: 0;
423
+ }
424
+
425
+ .gallery-stats {
426
+ display: flex;
427
+ align-items: center;
428
+ gap: 16px;
429
+ }
430
+
431
+ .gallery-stats span {
432
+ color: #6b7280;
433
+ font-size: 14px;
434
+ }
435
+
436
+ .clear-btn {
437
+ padding: 6px 12px;
438
+ background: #ef4444;
439
+ color: white;
440
+ border: none;
441
+ border-radius: 4px;
442
+ font-size: 12px;
443
+ font-weight: 500;
444
+ cursor: pointer;
445
+ transition: background 0.2s ease;
446
+ }
447
+
448
+ .clear-btn:hover {
449
+ background: #dc2626;
450
+ }
451
+
452
+ .image-grid {
453
+ display: grid;
454
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
455
+ gap: 16px;
456
+ }
457
+
458
+ .image-item {
459
+ border: 1px solid #e5e7eb;
460
+ border-radius: 8px;
461
+ overflow: hidden;
462
+ background: #f9fafb;
463
+ }
464
+
465
+ .image-preview {
466
+ position: relative;
467
+ aspect-ratio: 1;
468
+ overflow: hidden;
469
+ }
470
+
471
+ .preview-img {
472
+ width: 100%;
473
+ height: 100%;
474
+ object-fit: cover;
475
+ }
476
+
477
+ .remove-btn {
478
+ position: absolute;
479
+ top: 8px;
480
+ right: 8px;
481
+ width: 24px;
482
+ height: 24px;
483
+ background: rgba(239, 68, 68, 0.9);
484
+ color: white;
485
+ border: none;
486
+ border-radius: 50%;
487
+ font-size: 16px;
488
+ font-weight: bold;
489
+ cursor: pointer;
490
+ display: flex;
491
+ align-items: center;
492
+ justify-content: center;
493
+ transition: background 0.2s ease;
494
+ }
495
+
496
+ .remove-btn:hover {
497
+ background: rgba(220, 38, 38, 0.9);
498
+ }
499
+
500
+ .image-info {
501
+ padding: 12px;
502
+ }
503
+
504
+ .image-name {
505
+ font-size: 14px;
506
+ font-weight: 500;
507
+ color: #374151;
508
+ margin: 0 0 4px 0;
509
+ white-space: nowrap;
510
+ overflow: hidden;
511
+ text-overflow: ellipsis;
512
+ }
513
+
514
+ .image-size {
515
+ font-size: 12px;
516
+ color: #6b7280;
517
+ margin: 0;
518
+ }
519
+
520
+ .error-section {
521
+ margin-top: 24px;
522
+ padding: 16px;
523
+ background: #fef2f2;
524
+ border: 1px solid #fecaca;
525
+ border-radius: 8px;
526
+ }
527
+
528
+ .error-section h4 {
529
+ font-size: 16px;
530
+ font-weight: 500;
531
+ color: #dc2626;
532
+ margin: 0 0 12px 0;
533
+ }
534
+
535
+ .error-item {
536
+ color: #991b1b;
537
+ margin-bottom: 8px;
538
+ font-size: 14px;
539
+ }
540
+
541
+ .error-item:last-child {
542
+ margin-bottom: 0;
543
+ }
544
+ </style>
545
+ ```
546
+
547
+ ### Document Uploader
548
+
549
+ ```vue
550
+ <script setup lang="ts">
551
+ import { ref } from "vue";
552
+ import { FilePicker } from "@umbra-ui/core";
553
+ import type { FileError } from "@umbra-ui/core";
554
+
555
+ const documents = ref<File[]>([]);
556
+ const errors = ref<FileError[]>([]);
557
+
558
+ const acceptedTypes = [
559
+ "application/pdf",
560
+ "application/msword",
561
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
562
+ "text/plain",
563
+ ".pdf",
564
+ ".doc",
565
+ ".docx",
566
+ ".txt",
567
+ ];
568
+
569
+ const formatFileSize = (bytes: number): string => {
570
+ if (bytes === 0) return "0 Bytes";
571
+ const k = 1024;
572
+ const sizes = ["Bytes", "KB", "MB", "GB"];
573
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
574
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
575
+ };
576
+
577
+ const getFileIcon = (file: File) => {
578
+ if (file.type.includes("pdf")) return "📄";
579
+ if (file.type.includes("word")) return "📝";
580
+ if (file.type.includes("text")) return "📃";
581
+ return "📁";
582
+ };
583
+
584
+ const handleDocumentsUpdate = (files: File[]) => {
585
+ documents.value = files;
586
+ console.log("Documents updated:", files);
587
+ };
588
+
589
+ const handleDocumentErrors = (fileErrors: FileError[]) => {
590
+ errors.value = fileErrors;
591
+ console.log("Document errors:", fileErrors);
592
+ };
593
+
594
+ const removeDocument = (index: number) => {
595
+ documents.value.splice(index, 1);
596
+ };
597
+ </script>
598
+
599
+ <template>
600
+ <div class="document-uploader">
601
+ <div class="uploader-header">
602
+ <h3>Document Upload</h3>
603
+ <p>Upload your documents (PDF, Word, Text files)</p>
604
+ </div>
605
+
606
+ <FilePicker
607
+ v-model:files="documents"
608
+ :accept="acceptedTypes"
609
+ :max-size="10 * 1024 * 1024"
610
+ :max-files="5"
611
+ :multiple="true"
612
+ @update:files="handleDocumentsUpdate"
613
+ @error="handleDocumentErrors"
614
+ >
615
+ <div class="custom-content">
616
+ <div class="upload-icon">📁</div>
617
+ <div class="upload-text">
618
+ <h4>Drop documents here or click to browse</h4>
619
+ <p>Supports PDF, DOC, DOCX, TXT (max 10MB each)</p>
620
+ </div>
621
+ </div>
622
+ </FilePicker>
623
+
624
+ <div v-if="documents.length > 0" class="document-list">
625
+ <h4>Uploaded Documents ({{ documents.length }})</h4>
626
+ <div class="document-items">
627
+ <div
628
+ v-for="(document, index) in documents"
629
+ :key="document.name"
630
+ class="document-item"
631
+ >
632
+ <div class="document-icon">{{ getFileIcon(document) }}</div>
633
+ <div class="document-info">
634
+ <p class="document-name">{{ document.name }}</p>
635
+ <p class="document-details">
636
+ {{ formatFileSize(document.size) }} • {{ document.type }}
637
+ </p>
638
+ </div>
639
+ <button @click="removeDocument(index)" class="remove-btn">×</button>
640
+ </div>
641
+ </div>
642
+ </div>
643
+
644
+ <div v-if="errors.length > 0" class="error-section">
645
+ <h4>Upload Errors</h4>
646
+ <div v-for="error in errors" :key="error.file" class="error-item">
647
+ <strong>{{ error.file }}:</strong> {{ error.error }}
648
+ </div>
649
+ </div>
650
+ </div>
651
+ </template>
652
+
653
+ <style module>
654
+ .document-uploader {
655
+ padding: 24px;
656
+ max-width: 600px;
657
+ background: #ffffff;
658
+ border-radius: 12px;
659
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
660
+ }
661
+
662
+ .uploader-header {
663
+ margin-bottom: 24px;
664
+ }
665
+
666
+ .uploader-header h3 {
667
+ font-size: 24px;
668
+ font-weight: 600;
669
+ color: #1a202c;
670
+ margin: 0 0 8px 0;
671
+ }
672
+
673
+ .uploader-header p {
674
+ color: #718096;
675
+ margin: 0;
676
+ }
677
+
678
+ .custom-content {
679
+ display: flex;
680
+ flex-direction: column;
681
+ align-items: center;
682
+ gap: 16px;
683
+ text-align: center;
684
+ }
685
+
686
+ .upload-icon {
687
+ font-size: 48px;
688
+ }
689
+
690
+ .upload-text h4 {
691
+ font-size: 18px;
692
+ font-weight: 600;
693
+ color: #2d3748;
694
+ margin: 0 0 8px 0;
695
+ }
696
+
697
+ .upload-text p {
698
+ color: #718096;
699
+ margin: 0;
700
+ }
701
+
702
+ .document-list {
703
+ margin-top: 32px;
704
+ }
705
+
706
+ .document-list h4 {
707
+ font-size: 18px;
708
+ font-weight: 500;
709
+ color: #2d3748;
710
+ margin: 0 0 16px 0;
711
+ }
712
+
713
+ .document-items {
714
+ display: flex;
715
+ flex-direction: column;
716
+ gap: 12px;
717
+ }
718
+
719
+ .document-item {
720
+ display: flex;
721
+ align-items: center;
722
+ gap: 12px;
723
+ padding: 16px;
724
+ background: #f9fafb;
725
+ border: 1px solid #e5e7eb;
726
+ border-radius: 8px;
727
+ }
728
+
729
+ .document-icon {
730
+ font-size: 24px;
731
+ width: 40px;
732
+ height: 40px;
733
+ display: flex;
734
+ align-items: center;
735
+ justify-content: center;
736
+ background: #e5e7eb;
737
+ border-radius: 8px;
738
+ }
739
+
740
+ .document-info {
741
+ flex: 1;
742
+ }
743
+
744
+ .document-name {
745
+ font-size: 14px;
746
+ font-weight: 500;
747
+ color: #374151;
748
+ margin: 0 0 4px 0;
749
+ }
750
+
751
+ .document-details {
752
+ font-size: 12px;
753
+ color: #6b7280;
754
+ margin: 0;
755
+ }
756
+
757
+ .remove-btn {
758
+ width: 24px;
759
+ height: 24px;
760
+ background: #ef4444;
761
+ color: white;
762
+ border: none;
763
+ border-radius: 50%;
764
+ font-size: 16px;
765
+ font-weight: bold;
766
+ cursor: pointer;
767
+ display: flex;
768
+ align-items: center;
769
+ justify-content: center;
770
+ transition: background 0.2s ease;
771
+ }
772
+
773
+ .remove-btn:hover {
774
+ background: #dc2626;
775
+ }
776
+
777
+ .error-section {
778
+ margin-top: 24px;
779
+ padding: 16px;
780
+ background: #fef2f2;
781
+ border: 1px solid #fecaca;
782
+ border-radius: 8px;
783
+ }
784
+
785
+ .error-section h4 {
786
+ font-size: 16px;
787
+ font-weight: 500;
788
+ color: #dc2626;
789
+ margin: 0 0 12px 0;
790
+ }
791
+
792
+ .error-item {
793
+ color: #991b1b;
794
+ margin-bottom: 8px;
795
+ font-size: 14px;
796
+ }
797
+
798
+ .error-item:last-child {
799
+ margin-bottom: 0;
800
+ }
801
+ </style>
802
+ ```
803
+
804
+ ### Single File Upload
805
+
806
+ ```vue
807
+ <script setup lang="ts">
808
+ import { ref } from "vue";
809
+ import { FilePicker } from "@umbra-ui/core";
810
+ import type { FileError } from "@umbra-ui/core";
811
+
812
+ const selectedFile = ref<File | null>(null);
813
+ const errors = ref<FileError[]>([]);
814
+
815
+ const formatFileSize = (bytes: number): string => {
816
+ if (bytes === 0) return "0 Bytes";
817
+ const k = 1024;
818
+ const sizes = ["Bytes", "KB", "MB", "GB"];
819
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
820
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
821
+ };
822
+
823
+ const handleFileUpdate = (files: File[]) => {
824
+ selectedFile.value = files[0] || null;
825
+ console.log("File updated:", selectedFile.value);
826
+ };
827
+
828
+ const handleFileErrors = (fileErrors: FileError[]) => {
829
+ errors.value = fileErrors;
830
+ console.log("File errors:", fileErrors);
831
+ };
832
+
833
+ const clearFile = () => {
834
+ selectedFile.value = null;
835
+ errors.value = [];
836
+ };
837
+ </script>
838
+
839
+ <template>
840
+ <div class="single-file-uploader">
841
+ <div class="uploader-header">
842
+ <h3>Single File Upload</h3>
843
+ <p>Upload one file at a time</p>
844
+ </div>
845
+
846
+ <FilePicker
847
+ v-model:files="selectedFile ? [selectedFile] : []"
848
+ accept="*/*"
849
+ :max-size="50 * 1024 * 1024"
850
+ :max-files="1"
851
+ :multiple="false"
852
+ :append-files="false"
853
+ @update:files="handleFileUpdate"
854
+ @error="handleFileErrors"
855
+ >
856
+ <div class="custom-content">
857
+ <div class="upload-icon">📎</div>
858
+ <div class="upload-text">
859
+ <h4>Drop a file here or click to browse</h4>
860
+ <p>Any file type, max 50MB</p>
861
+ </div>
862
+ </div>
863
+ </FilePicker>
864
+
865
+ <div v-if="selectedFile" class="file-preview">
866
+ <h4>Selected File</h4>
867
+ <div class="file-card">
868
+ <div class="file-icon">📄</div>
869
+ <div class="file-details">
870
+ <p class="file-name">{{ selectedFile.name }}</p>
871
+ <p class="file-info">
872
+ {{ formatFileSize(selectedFile.size) }} •
873
+ {{ selectedFile.type || "Unknown type" }}
874
+ </p>
875
+ <p class="file-date">
876
+ Last modified:
877
+ {{ new Date(selectedFile.lastModified).toLocaleDateString() }}
878
+ </p>
879
+ </div>
880
+ <button @click="clearFile" class="clear-btn">Clear</button>
881
+ </div>
882
+ </div>
883
+
884
+ <div v-if="errors.length > 0" class="error-section">
885
+ <h4>Upload Error</h4>
886
+ <div v-for="error in errors" :key="error.file" class="error-item">
887
+ <strong>{{ error.file }}:</strong> {{ error.error }}
888
+ </div>
889
+ </div>
890
+ </div>
891
+ </template>
892
+
893
+ <style module>
894
+ .single-file-uploader {
895
+ padding: 24px;
896
+ max-width: 500px;
897
+ background: #ffffff;
898
+ border-radius: 12px;
899
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
900
+ }
901
+
902
+ .uploader-header {
903
+ margin-bottom: 24px;
904
+ }
905
+
906
+ .uploader-header h3 {
907
+ font-size: 24px;
908
+ font-weight: 600;
909
+ color: #1a202c;
910
+ margin: 0 0 8px 0;
911
+ }
912
+
913
+ .uploader-header p {
914
+ color: #718096;
915
+ margin: 0;
916
+ }
917
+
918
+ .custom-content {
919
+ display: flex;
920
+ flex-direction: column;
921
+ align-items: center;
922
+ gap: 16px;
923
+ text-align: center;
924
+ }
925
+
926
+ .upload-icon {
927
+ font-size: 48px;
928
+ }
929
+
930
+ .upload-text h4 {
931
+ font-size: 18px;
932
+ font-weight: 600;
933
+ color: #2d3748;
934
+ margin: 0 0 8px 0;
935
+ }
936
+
937
+ .upload-text p {
938
+ color: #718096;
939
+ margin: 0;
940
+ }
941
+
942
+ .file-preview {
943
+ margin-top: 32px;
944
+ }
945
+
946
+ .file-preview h4 {
947
+ font-size: 18px;
948
+ font-weight: 500;
949
+ color: #2d3748;
950
+ margin: 0 0 16px 0;
951
+ }
952
+
953
+ .file-card {
954
+ display: flex;
955
+ align-items: center;
956
+ gap: 16px;
957
+ padding: 20px;
958
+ background: #f9fafb;
959
+ border: 1px solid #e5e7eb;
960
+ border-radius: 8px;
961
+ }
962
+
963
+ .file-icon {
964
+ font-size: 32px;
965
+ width: 60px;
966
+ height: 60px;
967
+ display: flex;
968
+ align-items: center;
969
+ justify-content: center;
970
+ background: #e5e7eb;
971
+ border-radius: 12px;
972
+ }
973
+
974
+ .file-details {
975
+ flex: 1;
976
+ }
977
+
978
+ .file-name {
979
+ font-size: 16px;
980
+ font-weight: 600;
981
+ color: #374151;
982
+ margin: 0 0 8px 0;
983
+ }
984
+
985
+ .file-info {
986
+ font-size: 14px;
987
+ color: #6b7280;
988
+ margin: 0 0 4px 0;
989
+ }
990
+
991
+ .file-date {
992
+ font-size: 12px;
993
+ color: #9ca3af;
994
+ margin: 0;
995
+ }
996
+
997
+ .clear-btn {
998
+ padding: 8px 16px;
999
+ background: #ef4444;
1000
+ color: white;
1001
+ border: none;
1002
+ border-radius: 6px;
1003
+ font-size: 14px;
1004
+ font-weight: 500;
1005
+ cursor: pointer;
1006
+ transition: background 0.2s ease;
1007
+ }
1008
+
1009
+ .clear-btn:hover {
1010
+ background: #dc2626;
1011
+ }
1012
+
1013
+ .error-section {
1014
+ margin-top: 24px;
1015
+ padding: 16px;
1016
+ background: #fef2f2;
1017
+ border: 1px solid #fecaca;
1018
+ border-radius: 8px;
1019
+ }
1020
+
1021
+ .error-section h4 {
1022
+ font-size: 16px;
1023
+ font-weight: 500;
1024
+ color: #dc2626;
1025
+ margin: 0 0 12px 0;
1026
+ }
1027
+
1028
+ .error-item {
1029
+ color: #991b1b;
1030
+ margin-bottom: 8px;
1031
+ font-size: 14px;
1032
+ }
1033
+
1034
+ .error-item:last-child {
1035
+ margin-bottom: 0;
1036
+ }
1037
+ </style>
1038
+ ```
1039
+
1040
+ ### Custom Overlay
1041
+
1042
+ ```vue
1043
+ <script setup lang="ts">
1044
+ import { ref } from "vue";
1045
+ import { FilePicker } from "@umbra-ui/core";
1046
+ import type { FileError } from "@umbra-ui/core";
1047
+
1048
+ const files = ref<File[]>([]);
1049
+ const errors = ref<FileError[]>([]);
1050
+
1051
+ const handleFilesUpdate = (newFiles: File[]) => {
1052
+ files.value = newFiles;
1053
+ console.log("Files updated:", newFiles);
1054
+ };
1055
+
1056
+ const handleFileErrors = (fileErrors: FileError[]) => {
1057
+ errors.value = fileErrors;
1058
+ console.log("File errors:", fileErrors);
1059
+ };
1060
+ </script>
1061
+
1062
+ <template>
1063
+ <div class="custom-overlay-uploader">
1064
+ <div class="uploader-header">
1065
+ <h3>Custom Overlay Uploader</h3>
1066
+ <p>Drag and drop with custom overlay animation</p>
1067
+ </div>
1068
+
1069
+ <FilePicker
1070
+ v-model:files="files"
1071
+ accept="image/*,video/*"
1072
+ :max-size="100 * 1024 * 1024"
1073
+ :max-files="20"
1074
+ @update:files="handleFilesUpdate"
1075
+ @error="handleFileErrors"
1076
+ >
1077
+ <div class="custom-content">
1078
+ <div class="upload-icon">🎬</div>
1079
+ <div class="upload-text">
1080
+ <h4>Drop media files here</h4>
1081
+ <p>Images and videos up to 100MB each</p>
1082
+ </div>
1083
+ </div>
1084
+
1085
+ <template #overlay>
1086
+ <div class="custom-overlay">
1087
+ <div class="overlay-icon">⬇️</div>
1088
+ <div class="overlay-text">
1089
+ <h4>Release to upload</h4>
1090
+ <p>Drop your files now</p>
1091
+ </div>
1092
+ </div>
1093
+ </template>
1094
+
1095
+ <template #processing>
1096
+ <div class="custom-processing">
1097
+ <div class="processing-spinner"></div>
1098
+ <p>Processing files...</p>
1099
+ </div>
1100
+ </template>
1101
+ </FilePicker>
1102
+
1103
+ <div v-if="files.length > 0" class="files-summary">
1104
+ <h4>Uploaded Files ({{ files.length }})</h4>
1105
+ <div class="files-grid">
1106
+ <div v-for="file in files" :key="file.name" class="file-item">
1107
+ <div class="file-icon">
1108
+ {{ file.type.startsWith("image/") ? "🖼️" : "🎥" }}
1109
+ </div>
1110
+ <div class="file-info">
1111
+ <p class="file-name">{{ file.name }}</p>
1112
+ <p class="file-size">
1113
+ {{ (file.size / 1024 / 1024).toFixed(2) }} MB
1114
+ </p>
1115
+ </div>
1116
+ </div>
1117
+ </div>
1118
+ </div>
1119
+ </div>
1120
+ </template>
1121
+
1122
+ <style module>
1123
+ .custom-overlay-uploader {
1124
+ padding: 24px;
1125
+ max-width: 700px;
1126
+ background: #ffffff;
1127
+ border-radius: 12px;
1128
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1129
+ }
1130
+
1131
+ .uploader-header {
1132
+ margin-bottom: 24px;
1133
+ }
1134
+
1135
+ .uploader-header h3 {
1136
+ font-size: 24px;
1137
+ font-weight: 600;
1138
+ color: #1a202c;
1139
+ margin: 0 0 8px 0;
1140
+ }
1141
+
1142
+ .uploader-header p {
1143
+ color: #718096;
1144
+ margin: 0;
1145
+ }
1146
+
1147
+ .custom-content {
1148
+ display: flex;
1149
+ flex-direction: column;
1150
+ align-items: center;
1151
+ gap: 16px;
1152
+ text-align: center;
1153
+ }
1154
+
1155
+ .upload-icon {
1156
+ font-size: 48px;
1157
+ }
1158
+
1159
+ .upload-text h4 {
1160
+ font-size: 18px;
1161
+ font-weight: 600;
1162
+ color: #2d3748;
1163
+ margin: 0 0 8px 0;
1164
+ }
1165
+
1166
+ .upload-text p {
1167
+ color: #718096;
1168
+ margin: 0;
1169
+ }
1170
+
1171
+ .custom-overlay {
1172
+ display: flex;
1173
+ flex-direction: column;
1174
+ align-items: center;
1175
+ gap: 16px;
1176
+ text-align: center;
1177
+ background: rgba(59, 130, 246, 0.1);
1178
+ border: 2px dashed #3b82f6;
1179
+ border-radius: 12px;
1180
+ padding: 40px;
1181
+ margin: 20px;
1182
+ }
1183
+
1184
+ .overlay-icon {
1185
+ font-size: 48px;
1186
+ animation: bounce 1s infinite;
1187
+ }
1188
+
1189
+ .overlay-text h4 {
1190
+ font-size: 20px;
1191
+ font-weight: 600;
1192
+ color: #1e40af;
1193
+ margin: 0 0 8px 0;
1194
+ }
1195
+
1196
+ .overlay-text p {
1197
+ color: #3b82f6;
1198
+ margin: 0;
1199
+ }
1200
+
1201
+ @keyframes bounce {
1202
+ 0%,
1203
+ 20%,
1204
+ 50%,
1205
+ 80%,
1206
+ 100% {
1207
+ transform: translateY(0);
1208
+ }
1209
+ 40% {
1210
+ transform: translateY(-10px);
1211
+ }
1212
+ 60% {
1213
+ transform: translateY(-5px);
1214
+ }
1215
+ }
1216
+
1217
+ .custom-processing {
1218
+ display: flex;
1219
+ flex-direction: column;
1220
+ align-items: center;
1221
+ gap: 16px;
1222
+ text-align: center;
1223
+ }
1224
+
1225
+ .processing-spinner {
1226
+ width: 40px;
1227
+ height: 40px;
1228
+ border: 4px solid #e5e7eb;
1229
+ border-top-color: #3b82f6;
1230
+ border-radius: 50%;
1231
+ animation: spin 1s linear infinite;
1232
+ }
1233
+
1234
+ @keyframes spin {
1235
+ to {
1236
+ transform: rotate(360deg);
1237
+ }
1238
+ }
1239
+
1240
+ .custom-processing p {
1241
+ color: #6b7280;
1242
+ font-weight: 500;
1243
+ margin: 0;
1244
+ }
1245
+
1246
+ .files-summary {
1247
+ margin-top: 32px;
1248
+ }
1249
+
1250
+ .files-summary h4 {
1251
+ font-size: 18px;
1252
+ font-weight: 500;
1253
+ color: #2d3748;
1254
+ margin: 0 0 16px 0;
1255
+ }
1256
+
1257
+ .files-grid {
1258
+ display: grid;
1259
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
1260
+ gap: 12px;
1261
+ }
1262
+
1263
+ .file-item {
1264
+ display: flex;
1265
+ flex-direction: column;
1266
+ align-items: center;
1267
+ gap: 8px;
1268
+ padding: 16px;
1269
+ background: #f9fafb;
1270
+ border: 1px solid #e5e7eb;
1271
+ border-radius: 8px;
1272
+ text-align: center;
1273
+ }
1274
+
1275
+ .file-icon {
1276
+ font-size: 24px;
1277
+ }
1278
+
1279
+ .file-info {
1280
+ width: 100%;
1281
+ }
1282
+
1283
+ .file-name {
1284
+ font-size: 12px;
1285
+ font-weight: 500;
1286
+ color: #374151;
1287
+ margin: 0 0 4px 0;
1288
+ white-space: nowrap;
1289
+ overflow: hidden;
1290
+ text-overflow: ellipsis;
1291
+ }
1292
+
1293
+ .file-size {
1294
+ font-size: 11px;
1295
+ color: #6b7280;
1296
+ margin: 0;
1297
+ }
1298
+ </style>
1299
+ ```
1300
+
1301
+ ## Advanced Usage
1302
+
1303
+ ### Programmatic Control
1304
+
1305
+ ```vue
1306
+ <script setup lang="ts">
1307
+ import { ref } from "vue";
1308
+ import { FilePicker } from "@umbra-ui/core";
1309
+ import type { FileError } from "@umbra-ui/core";
1310
+
1311
+ const filePickerRef = ref<InstanceType<typeof FilePicker>>();
1312
+ const files = ref<File[]>([]);
1313
+ const errors = ref<FileError[]>([]);
1314
+
1315
+ const handleFilesUpdate = (newFiles: File[]) => {
1316
+ files.value = newFiles;
1317
+ };
1318
+
1319
+ const handleFileErrors = (fileErrors: FileError[]) => {
1320
+ errors.value = fileErrors;
1321
+ };
1322
+
1323
+ const openFileDialog = () => {
1324
+ filePickerRef.value?.openFileDialog();
1325
+ };
1326
+
1327
+ const clearAllFiles = () => {
1328
+ filePickerRef.value?.clearFiles();
1329
+ };
1330
+
1331
+ const getCurrentFiles = () => {
1332
+ return filePickerRef.value?.files || [];
1333
+ };
1334
+
1335
+ const getCurrentErrors = () => {
1336
+ return filePickerRef.value?.errors || [];
1337
+ };
1338
+ </script>
1339
+
1340
+ <template>
1341
+ <div class="programmatic-control">
1342
+ <div class="control-header">
1343
+ <h3>Programmatic Control</h3>
1344
+ <p>Control the file picker programmatically</p>
1345
+ </div>
1346
+
1347
+ <div class="control-buttons">
1348
+ <button @click="openFileDialog" class="control-btn">
1349
+ Open File Dialog
1350
+ </button>
1351
+ <button @click="clearAllFiles" class="control-btn">
1352
+ Clear All Files
1353
+ </button>
1354
+ <button @click="getCurrentFiles" class="control-btn">
1355
+ Get Current Files
1356
+ </button>
1357
+ <button @click="getCurrentErrors" class="control-btn">
1358
+ Get Current Errors
1359
+ </button>
1360
+ </div>
1361
+
1362
+ <FilePicker
1363
+ ref="filePickerRef"
1364
+ v-model:files="files"
1365
+ accept="*/*"
1366
+ :max-size="10 * 1024 * 1024"
1367
+ :max-files="5"
1368
+ @update:files="handleFilesUpdate"
1369
+ @error="handleFileErrors"
1370
+ />
1371
+
1372
+ <div v-if="files.length > 0" class="files-info">
1373
+ <h4>Current Files ({{ files.length }})</h4>
1374
+ <ul>
1375
+ <li v-for="file in files" :key="file.name">
1376
+ {{ file.name }} ({{ (file.size / 1024).toFixed(2) }} KB)
1377
+ </li>
1378
+ </ul>
1379
+ </div>
1380
+
1381
+ <div v-if="errors.length > 0" class="errors-info">
1382
+ <h4>Current Errors ({{ errors.length }})</h4>
1383
+ <ul>
1384
+ <li v-for="error in errors" :key="error.file">
1385
+ {{ error.file }}: {{ error.error }}
1386
+ </li>
1387
+ </ul>
1388
+ </div>
1389
+ </div>
1390
+ </template>
1391
+
1392
+ <style module>
1393
+ .programmatic-control {
1394
+ padding: 24px;
1395
+ max-width: 600px;
1396
+ background: #ffffff;
1397
+ border-radius: 12px;
1398
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1399
+ }
1400
+
1401
+ .control-header {
1402
+ margin-bottom: 24px;
1403
+ }
1404
+
1405
+ .control-header h3 {
1406
+ font-size: 24px;
1407
+ font-weight: 600;
1408
+ color: #1a202c;
1409
+ margin: 0 0 8px 0;
1410
+ }
1411
+
1412
+ .control-header p {
1413
+ color: #718096;
1414
+ margin: 0;
1415
+ }
1416
+
1417
+ .control-buttons {
1418
+ display: flex;
1419
+ flex-wrap: wrap;
1420
+ gap: 12px;
1421
+ margin-bottom: 24px;
1422
+ }
1423
+
1424
+ .control-btn {
1425
+ padding: 8px 16px;
1426
+ background: #3b82f6;
1427
+ color: white;
1428
+ border: none;
1429
+ border-radius: 6px;
1430
+ font-size: 14px;
1431
+ font-weight: 500;
1432
+ cursor: pointer;
1433
+ transition: background 0.2s ease;
1434
+ }
1435
+
1436
+ .control-btn:hover {
1437
+ background: #2563eb;
1438
+ }
1439
+
1440
+ .files-info,
1441
+ .errors-info {
1442
+ margin-top: 24px;
1443
+ padding: 16px;
1444
+ background: #f9fafb;
1445
+ border-radius: 8px;
1446
+ }
1447
+
1448
+ .files-info h4,
1449
+ .errors-info h4 {
1450
+ font-size: 16px;
1451
+ font-weight: 500;
1452
+ color: #2d3748;
1453
+ margin: 0 0 12px 0;
1454
+ }
1455
+
1456
+ .files-info ul,
1457
+ .errors-info ul {
1458
+ margin: 0;
1459
+ padding-left: 20px;
1460
+ }
1461
+
1462
+ .files-info li,
1463
+ .errors-info li {
1464
+ color: #4a5568;
1465
+ margin-bottom: 4px;
1466
+ }
1467
+
1468
+ .files-info li:last-child,
1469
+ .errors-info li:last-child {
1470
+ margin-bottom: 0;
1471
+ }
1472
+ </style>
1473
+ ```
1474
+
1475
+ ## Performance Considerations
1476
+
1477
+ - **GSAP Animations**: Smooth drag-and-drop animations with optimized performance
1478
+ - **File Processing**: Asynchronous file processing with proper error handling
1479
+ - **Memory Management**: Efficient file handling with proper cleanup
1480
+ - **Directory Traversal**: Recursive directory processing with depth control
1481
+ - **Validation**: Client-side validation before file processing
1482
+
1483
+ ## Accessibility
1484
+
1485
+ - **Keyboard Navigation**: Support for keyboard interaction
1486
+ - **Screen Reader Support**: Proper ARIA labels and semantic structure
1487
+ - **Focus Management**: Focus is properly managed during interactions
1488
+ - **Drag and Drop**: Accessible drag-and-drop with proper feedback
1489
+ - **Error Reporting**: Clear error messages for validation failures
1490
+
1491
+ ## Browser Support
1492
+
1493
+ - **Modern Browsers**: Chrome 88+, Firefox 85+, Safari 14+, Edge 88+
1494
+ - **Mobile Browsers**: iOS Safari 14+, Chrome Mobile 88+
1495
+ - **File API**: Requires File API support for drag-and-drop
1496
+ - **Directory API**: Requires File System Access API for directory traversal
1497
+
1498
+ ## Troubleshooting
1499
+
1500
+ ### Common Issues
1501
+
1502
+ 1. **Files not uploading**: Check file size limits and accepted types
1503
+ 2. **Drag and drop not working**: Ensure proper event handling and browser support
1504
+ 3. **Directory traversal issues**: Verify File System Access API support
1505
+ 4. **Animation not smooth**: Check GSAP installation and configuration
1506
+
1507
+ ### Debug Mode
1508
+
1509
+ ```vue
1510
+ <script setup lang="ts">
1511
+ import { ref, watch } from "vue";
1512
+ import { FilePicker } from "@umbra-ui/core";
1513
+
1514
+ const files = ref<File[]>([]);
1515
+ const errors = ref<FileError[]>([]);
1516
+
1517
+ // Watch for changes
1518
+ watch(files, (newFiles) => {
1519
+ console.log("Files changed:", newFiles);
1520
+ });
1521
+
1522
+ watch(errors, (newErrors) => {
1523
+ console.log("Errors changed:", newErrors);
1524
+ });
1525
+ </script>
1526
+ ```
1527
+
1528
+ ## Migration Guide
1529
+
1530
+ ### From v1 to v2
1531
+
1532
+ - `v-model` prop is now `v-model:files`
1533
+ - Event names have been updated to use kebab-case
1534
+ - File validation system has been improved
1535
+ - GSAP integration has been enhanced
1536
+
1537
+ ### Breaking Changes
1538
+
1539
+ - Removed `value` prop in favor of `v-model:files`
1540
+ - Changed event names from camelCase to kebab-case
1541
+ - Updated file prop to use File[] array instead of single File
1542
+ - Modified CSS class naming convention