@flikk/ui 1.0.0-beta.25 → 1.0.0-beta.27

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 (211) hide show
  1. package/dist/components/ai/CodeBlock/CodeBlock.theme.js +2 -2
  2. package/dist/components/ai/PromptInput/PromptInput.js +4 -0
  3. package/dist/components/ai/PromptInput/VoiceRecorder.js +4 -0
  4. package/dist/components/ai/StreamingResponse/MarkdownRenderer.js +1 -1
  5. package/dist/components/ai/TokenCounter/TokenCounter.theme.js +1 -1
  6. package/dist/components/charts/BarChart/BarChart.js +2 -2
  7. package/dist/components/charts/StackedBarChart/StackedBarChart.js +1 -1
  8. package/dist/components/charts/shared/BarRenderer/BarRenderer.js +2 -2
  9. package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.js +1 -1
  10. package/dist/components/core/Button/Button.js +1 -1
  11. package/dist/components/core/Button/Button.theme.js +5 -5
  12. package/dist/components/core/Calendar/Calendar.theme.js +1 -1
  13. package/dist/components/core/Calendar/CalendarMini/CalendarMini.theme.js +1 -1
  14. package/dist/components/core/CommandPalette/CommandItem.js +4 -5
  15. package/dist/components/core/CommandPalette/CommandPalette.js +1 -1
  16. package/dist/components/core/CommandPalette/CommandPalette.theme.js +11 -13
  17. package/dist/components/core/CommandPalette/CommandPalette.types.d.ts +0 -2
  18. package/dist/components/core/ContextMenu/ContextMenu.theme.js +13 -14
  19. package/dist/components/core/DotSeparator/DotSeparator.d.ts +3 -0
  20. package/dist/components/core/DotSeparator/DotSeparator.js +19 -0
  21. package/dist/components/core/DotSeparator/DotSeparator.theme.d.ts +2 -0
  22. package/dist/components/core/DotSeparator/DotSeparator.theme.js +10 -0
  23. package/dist/components/core/DotSeparator/DotSeparator.types.d.ts +25 -0
  24. package/dist/components/core/DotSeparator/index.d.ts +3 -0
  25. package/dist/components/core/Drawer/Drawer.theme.js +2 -2
  26. package/dist/components/core/Dropdown/Dropdown.theme.js +4 -5
  27. package/dist/components/core/Dropdown/DropdownItem.js +1 -1
  28. package/dist/components/core/Dropdown/DropdownMenu.js +1 -1
  29. package/dist/components/core/Empty/Empty.js +1 -1
  30. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.d.ts +14 -0
  31. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.js +28 -0
  32. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.theme.d.ts +6 -0
  33. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.theme.js +8 -0
  34. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.types.d.ts +32 -0
  35. package/dist/components/core/ImagePlaceholder/index.d.ts +2 -0
  36. package/dist/components/core/MenuItem/MenuItem.js +5 -2
  37. package/dist/components/core/MenuItem/MenuItem.theme.d.ts +6 -0
  38. package/dist/components/core/MenuItem/MenuItem.theme.js +19 -16
  39. package/dist/components/core/MenuItem/MenuItem.types.d.ts +2 -0
  40. package/dist/components/core/Message/Message.theme.js +1 -1
  41. package/dist/components/core/Modal/Modal.theme.js +1 -1
  42. package/dist/components/core/NavItem/NavItem.d.ts +1 -1
  43. package/dist/components/core/NavItem/NavItem.js +187 -68
  44. package/dist/components/core/NavItem/NavItem.theme.js +31 -12
  45. package/dist/components/core/NavItem/NavItem.types.d.ts +17 -39
  46. package/dist/components/core/Pagination/Pagination.theme.js +3 -3
  47. package/dist/components/core/Popover/Popover.theme.js +1 -1
  48. package/dist/components/core/Popover/Popover.types.d.ts +6 -2
  49. package/dist/components/core/Popover/PopoverBody.js +10 -3
  50. package/dist/components/core/Progress/Progress.theme.js +1 -1
  51. package/dist/components/core/ScrollArea/ScrollArea.js +2 -2
  52. package/dist/components/core/ScrollArea/ScrollArea.theme.js +1 -1
  53. package/dist/components/core/Segmented/Segmented.js +8 -6
  54. package/dist/components/core/Segmented/Segmented.theme.js +3 -3
  55. package/dist/components/core/Sidebar/Sidebar.d.ts +3 -1
  56. package/dist/components/core/Sidebar/Sidebar.js +53 -16
  57. package/dist/components/core/Sidebar/Sidebar.theme.js +5 -7
  58. package/dist/components/core/Sidebar/Sidebar.types.d.ts +125 -8
  59. package/dist/components/core/Sidebar/SidebarCard.d.ts +25 -0
  60. package/dist/components/core/Sidebar/SidebarCard.js +68 -0
  61. package/dist/components/core/Sidebar/SidebarCard.theme.d.ts +8 -0
  62. package/dist/components/core/Sidebar/SidebarCard.theme.js +18 -0
  63. package/dist/components/core/Sidebar/SidebarContent.js +10 -6
  64. package/dist/components/core/Sidebar/SidebarContext.d.ts +17 -16
  65. package/dist/components/core/Sidebar/SidebarContext.js +43 -20
  66. package/dist/components/core/Sidebar/SidebarFooter.js +4 -6
  67. package/dist/components/core/Sidebar/SidebarHeader.js +12 -9
  68. package/dist/components/core/Sidebar/SidebarLogo.js +39 -8
  69. package/dist/components/core/Sidebar/SidebarNav.js +4 -3
  70. package/dist/components/core/Sidebar/SidebarNavGroup.js +4 -7
  71. package/dist/components/core/Sidebar/SidebarSearch.d.ts +22 -0
  72. package/dist/components/core/Sidebar/SidebarSearch.js +135 -0
  73. package/dist/components/core/Sidebar/SidebarToggle.js +6 -1
  74. package/dist/components/core/Sidebar/SidebarUserProfile.js +33 -17
  75. package/dist/components/core/Sidebar/index.d.ts +4 -3
  76. package/dist/components/core/Skeleton/Skeleton.theme.js +1 -1
  77. package/dist/components/core/Spinner/Spinner.theme.js +1 -1
  78. package/dist/components/core/Tabs/Tabs.js +6 -1
  79. package/dist/components/core/Tabs/Tabs.types.d.ts +2 -0
  80. package/dist/components/core/Tabs/TabsBody.js +5 -3
  81. package/dist/components/core/Tag/Tag.js +29 -5
  82. package/dist/components/core/Tag/Tag.theme.js +14 -9
  83. package/dist/components/core/Tag/Tag.types.d.ts +7 -0
  84. package/dist/components/core/Tooltip/Tooltip.js +1 -1
  85. package/dist/components/core/Tooltip/Tooltip.theme.js +1 -2
  86. package/dist/components/core/index.d.ts +2 -0
  87. package/dist/components/core/index.js +6 -2
  88. package/dist/components/data-display/GanttChart/GanttChart.d.ts +1 -1
  89. package/dist/components/data-display/GanttChart/GanttChart.js +31 -4
  90. package/dist/components/data-display/GanttChart/GanttChart.theme.js +19 -17
  91. package/dist/components/data-display/GanttChart/GanttChart.types.d.ts +19 -0
  92. package/dist/components/data-display/GanttChart/GanttChart.utils.js +1 -1
  93. package/dist/components/data-display/GanttChart/GanttChartPanel.d.ts +1 -1
  94. package/dist/components/data-display/GanttChart/GanttChartPanel.js +3 -3
  95. package/dist/components/data-display/GanttChart/GanttDependencyArrows.js +7 -4
  96. package/dist/components/data-display/GanttChart/GanttMilestone.js +5 -5
  97. package/dist/components/data-display/GanttChart/GanttTableGroupRow.js +1 -1
  98. package/dist/components/data-display/GanttChart/GanttTablePanel.js +1 -1
  99. package/dist/components/data-display/GanttChart/GanttTableTaskRow.js +2 -2
  100. package/dist/components/data-display/GanttChart/GanttTaskBar.js +37 -16
  101. package/dist/components/data-display/GanttChart/GanttTimelineGrid.js +2 -1
  102. package/dist/components/data-display/GanttChart/GanttTimelineHeader.js +35 -11
  103. package/dist/components/data-display/GanttChart/GanttToolbar.js +2 -2
  104. package/dist/components/data-display/GanttChart/hooks/useGanttDrag.js +3 -2
  105. package/dist/components/data-display/GanttChart/index.d.ts +1 -1
  106. package/dist/components/data-display/KPI/KPI.js +13 -3
  107. package/dist/components/data-display/KPI/KPI.theme.js +3 -3
  108. package/dist/components/data-display/Metric/Metric.theme.js +3 -3
  109. package/dist/components/data-display/Table/Table.js +4 -3
  110. package/dist/components/data-display/Table/Table.theme.js +14 -6
  111. package/dist/components/data-display/Table/Table.types.d.ts +15 -0
  112. package/dist/components/data-display/Table/TableColumnManager.js +1 -1
  113. package/dist/components/data-display/Table/index.d.ts +2 -2
  114. package/dist/components/effects/Aurora/Aurora.d.ts +6 -17
  115. package/dist/components/effects/Aurora/Aurora.js +310 -105
  116. package/dist/components/effects/Aurora/Aurora.types.d.ts +6 -0
  117. package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.js +3 -3
  118. package/dist/components/effects/InteractiveCharacters/characters/BlobCharacter.js +13 -4
  119. package/dist/components/effects/InteractiveCharacters/characters/SmallCharacter.js +13 -4
  120. package/dist/components/effects/InteractiveCharacters/characters/TallCharacter.js +14 -4
  121. package/dist/components/effects/MorphingText/MorphingText.js +40 -7
  122. package/dist/components/effects/StripePattern/StripePattern.d.ts +18 -0
  123. package/dist/components/effects/StripePattern/StripePattern.js +54 -0
  124. package/dist/components/effects/StripePattern/StripePattern.types.d.ts +53 -0
  125. package/dist/components/effects/StripePattern/index.d.ts +2 -0
  126. package/dist/components/effects/index.d.ts +2 -0
  127. package/dist/components/effects/index.js +1 -0
  128. package/dist/components/forms/Checkbox/Checkbox.theme.js +1 -1
  129. package/dist/components/forms/Combobox/Combobox.js +1 -1
  130. package/dist/components/forms/Combobox/Combobox.theme.js +2 -4
  131. package/dist/components/forms/CronInput/CronInput.js +1 -1
  132. package/dist/components/forms/CronInput/CronInput.theme.js +22 -31
  133. package/dist/components/forms/DatePicker/DatePicker.theme.js +11 -18
  134. package/dist/components/forms/DatePicker/DatePickerTrigger.js +1 -1
  135. package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +7 -14
  136. package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +1 -1
  137. package/dist/components/forms/FileUpload/FileUpload.animations.d.ts +29 -0
  138. package/dist/components/forms/FileUpload/FileUpload.animations.js +19 -0
  139. package/dist/components/forms/FileUpload/FileUpload.js +88 -49
  140. package/dist/components/forms/FileUpload/FileUpload.theme.d.ts +2 -10
  141. package/dist/components/forms/FileUpload/FileUpload.theme.js +16 -30
  142. package/dist/components/forms/FileUpload/FileUpload.types.d.ts +26 -10
  143. package/dist/components/forms/FileUpload/FileUploadProgress.js +27 -20
  144. package/dist/components/forms/FileUpload/FileUploadProgress.theme.js +2 -2
  145. package/dist/components/forms/Input/Input.theme.js +16 -15
  146. package/dist/components/forms/InputCounter/InputCounter.theme.js +1 -1
  147. package/dist/components/forms/Mention/Mention.js +152 -57
  148. package/dist/components/forms/Mention/Mention.theme.js +16 -23
  149. package/dist/components/forms/Mention/Mention.types.d.ts +12 -12
  150. package/dist/components/forms/Mention/Mention.utils.js +32 -4
  151. package/dist/components/forms/Radio/Radio.theme.js +1 -1
  152. package/dist/components/forms/RichTextEditor/RichTextEditor.d.ts +18 -0
  153. package/dist/components/forms/RichTextEditor/RichTextEditor.js +1231 -236
  154. package/dist/components/forms/RichTextEditor/RichTextEditor.theme.d.ts +53 -1
  155. package/dist/components/forms/RichTextEditor/RichTextEditor.theme.js +99 -25
  156. package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +42 -3
  157. package/dist/components/forms/RichTextEditor/index.d.ts +2 -2
  158. package/dist/components/forms/Select/Select.js +2 -2
  159. package/dist/components/forms/Select/Select.theme.js +3 -3
  160. package/dist/components/forms/Slider/Slider.theme.js +1 -1
  161. package/dist/components/forms/Switch/Switch.theme.js +1 -1
  162. package/dist/components/forms/TimePicker/TimePicker.theme.js +2 -2
  163. package/dist/components/forms/TimePicker/TimePickerContent.js +4 -0
  164. package/dist/components/forms/TimePicker/TimePickerTrigger.js +1 -1
  165. package/dist/components/forms/forms.theme.js +24 -19
  166. package/dist/components/forms/index.js +4 -0
  167. package/dist/components/layout/AppShell/AppShell.d.ts +10 -0
  168. package/dist/components/layout/AppShell/AppShell.js +84 -0
  169. package/dist/components/layout/AppShell/AppShell.theme.d.ts +3 -0
  170. package/dist/components/layout/AppShell/AppShell.theme.js +42 -0
  171. package/dist/components/layout/AppShell/AppShell.types.d.ts +127 -0
  172. package/dist/components/layout/AppShell/AppShellContext.d.ts +14 -0
  173. package/dist/components/layout/AppShell/AppShellContext.js +19 -0
  174. package/dist/components/layout/AppShell/AppShellFooter.d.ts +3 -0
  175. package/dist/components/layout/AppShell/AppShellFooter.js +12 -0
  176. package/dist/components/layout/AppShell/AppShellHeader.d.ts +3 -0
  177. package/dist/components/layout/AppShell/AppShellHeader.js +12 -0
  178. package/dist/components/layout/AppShell/AppShellMain.d.ts +3 -0
  179. package/dist/components/layout/AppShell/AppShellMain.js +19 -0
  180. package/dist/components/layout/AppShell/AppShellRightPanel.d.ts +3 -0
  181. package/dist/components/layout/AppShell/AppShellRightPanel.js +16 -0
  182. package/dist/components/layout/AppShell/AppShellSidebar.d.ts +3 -0
  183. package/dist/components/layout/AppShell/AppShellSidebar.js +16 -0
  184. package/dist/components/layout/AppShell/AppShellTopbar.d.ts +3 -0
  185. package/dist/components/layout/AppShell/AppShellTopbar.js +12 -0
  186. package/dist/components/layout/AppShell/index.d.ts +3 -0
  187. package/dist/components/layout/FormLayout/FormLayout.js +9 -4
  188. package/dist/components/layout/FormLayout/FormLayout.types.d.ts +3 -3
  189. package/dist/components/layout/FormLayout/FormLayoutSection.js +6 -1
  190. package/dist/components/layout/PageLayout/PageLayout.d.ts +2 -2
  191. package/dist/components/layout/PageLayout/PageLayout.js +23 -5
  192. package/dist/components/layout/PageLayout/PageLayout.theme.d.ts +2 -1
  193. package/dist/components/layout/PageLayout/PageLayout.theme.js +35 -6
  194. package/dist/components/layout/PageLayout/PageLayout.types.d.ts +45 -0
  195. package/dist/components/layout/PageLayout/PageLayoutContent.js +6 -4
  196. package/dist/components/layout/PageLayout/PageLayoutHeader.js +3 -3
  197. package/dist/components/layout/PageLayout/PageLayoutSidebar.js +5 -3
  198. package/dist/components/layout/PageLayout/index.d.ts +1 -1
  199. package/dist/components/layout/index.d.ts +1 -0
  200. package/dist/components/layout/index.js +2 -0
  201. package/dist/hooks/useSelectPortal.d.ts +10 -2
  202. package/dist/hooks/useSelectPortal.js +45 -24
  203. package/dist/index.js +8 -2
  204. package/dist/registry.json +10021 -0
  205. package/dist/styles.css +1 -1
  206. package/dist/utils/dateUtils.js +11 -1
  207. package/package.json +4 -2
  208. package/src/global.scss +29 -8
  209. package/src/styles/theme.css +174 -100
  210. package/dist/components/core/Sidebar/SidebarSubmenu.d.ts +0 -7
  211. package/dist/components/core/Sidebar/SidebarSubmenu.js +0 -12
@@ -1,6 +1,8 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import React__default, { useMemo, useId, useRef, useState, useCallback } from 'react';
2
+ import React__default, { useMemo, useId, useRef, useCallback, useState, useEffect } from 'react';
3
+ import { useReducedMotion, AnimatePresence, motion } from 'motion/react';
3
4
  import { fileUploadTheme } from './FileUpload.theme.js';
5
+ import { PREVIEW_LAYOUT_SPRING, PREVIEW_ITEM } from './FileUpload.animations.js';
4
6
  import { cn } from '../../../utils/cn.js';
5
7
  import { CloudArrowUpIcon, DocumentIcon, XMarkIcon } from '@heroicons/react/24/outline';
6
8
  import '../../core/Accordion/Accordion.js';
@@ -32,6 +34,7 @@ import '../../core/Dropdown/DropdownItem.js';
32
34
  import '../../core/Dropdown/DropdownSection.js';
33
35
  import '../../core/Dropdown/DropdownSeparator.js';
34
36
  import '../../core/Dropdown/Dropdown.theme.js';
37
+ import '../../core/DotSeparator/DotSeparator.js';
35
38
  import '../../core/Kbd/Kbd.js';
36
39
  import '../../core/MenuItem/MenuItem.js';
37
40
  import '../../core/Link/Link.js';
@@ -78,17 +81,19 @@ import '../../core/Sidebar/SidebarContent.js';
78
81
  import '../../core/Sidebar/SidebarFooter.js';
79
82
  import '../../core/Sidebar/SidebarNav.js';
80
83
  import '../../core/Sidebar/SidebarNavGroup.js';
81
- import 'motion/react';
82
84
  import '../../core/Sidebar/SidebarToggle.js';
83
85
  import '../../core/Sidebar/SidebarMobileTrigger.js';
84
86
  import '../../core/Sidebar/SidebarUserProfile.js';
85
87
  import '../../core/Sidebar/SidebarLogo.js';
88
+ import '../../core/Sidebar/SidebarSearch.js';
89
+ import '../../core/Sidebar/SidebarCard.js';
86
90
  import '../../core/Sidebar/SidebarContext.js';
87
91
  import '../../core/OfflineIndicator/OfflineIndicator.js';
88
92
  import '../../core/ContextMenu/ContextMenu.js';
89
93
  import '../../core/ContextMenu/ContextMenu.theme.js';
90
94
  import '../../core/Pill/Pill.js';
91
95
  import '../../core/Icon/Icon.js';
96
+ import '../../core/ImagePlaceholder/ImagePlaceholder.js';
92
97
  import { FormLabel } from '../FormLabel/FormLabel.js';
93
98
 
94
99
  /**
@@ -111,7 +116,7 @@ import { FormLabel } from '../FormLabel/FormLabel.js';
111
116
  * onUpload={(files) => console.log(files)}
112
117
  * />
113
118
  */
114
- const FileUpload = React__default.forwardRef(({ accept, multiple = false, maxSize, maxFiles, state = "default", disabled = false, label, errorMessage, onUpload, onRemove, onError, showPreview = true, buttonText = "Choose files", helperText = "or drag and drop", description, className, theme: customTheme, ...props }, ref) => {
119
+ const FileUpload = React__default.forwardRef(({ accept, multiple = false, maxSize, maxFiles, state = "default", disabled = false, label, errorMessage, value, defaultFiles, onChange, onUpload, onRemove, onError, showPreview = true, showIcon = true, buttonText = "Choose files", helperText = "or drag and drop", description, className, theme: customTheme, ...props }, ref) => {
115
120
  // Merge custom theme with default theme
116
121
  const theme = useMemo(() => customTheme
117
122
  ? { ...fileUploadTheme, ...customTheme }
@@ -119,10 +124,39 @@ const FileUpload = React__default.forwardRef(({ accept, multiple = false, maxSiz
119
124
  const generatedId = useId();
120
125
  const isDisabled = state === "disabled" || disabled;
121
126
  const isInvalid = state === "invalid";
127
+ // Wire helper/error text to the control for screen readers.
128
+ const describedBy = [
129
+ description ? `${generatedId}-description` : null,
130
+ errorMessage ? `${generatedId}-error` : null,
131
+ ]
132
+ .filter(Boolean)
133
+ .join(" ") || undefined;
134
+ const shouldReduceMotion = useReducedMotion();
135
+ // Stable key per File reference — index keys would break exit/reflow
136
+ // animations as the list reorders. WeakMap so removed files are GC'd.
137
+ const fileKeysRef = useRef(new WeakMap());
138
+ const keyCounterRef = useRef(0);
139
+ const getFileKey = useCallback((file) => {
140
+ let key = fileKeysRef.current.get(file);
141
+ if (!key) {
142
+ key = `file-${keyCounterRef.current++}`;
143
+ fileKeysRef.current.set(file, key);
144
+ }
145
+ return key;
146
+ }, []);
122
147
  const inputRef = useRef(null);
123
148
  const [isDragActive, setIsDragActive] = useState(false);
124
- const [selectedFiles, setSelectedFiles] = useState([]);
149
+ // Controlled when `value` is provided; otherwise track the list internally.
150
+ const isControlled = value !== undefined;
151
+ const [internalFiles, setInternalFiles] = useState(defaultFiles !== null && defaultFiles !== void 0 ? defaultFiles : []);
152
+ const selectedFiles = isControlled ? value : internalFiles;
125
153
  const [previewUrls, setPreviewUrls] = useState(new Map());
154
+ // Commit a new list: update internal state when uncontrolled, always notify.
155
+ const commitFiles = useCallback((next) => {
156
+ if (!isControlled)
157
+ setInternalFiles(next);
158
+ onChange === null || onChange === void 0 ? void 0 : onChange(next);
159
+ }, [isControlled, onChange]);
126
160
  // Convert accept prop to array
127
161
  const acceptTypes = Array.isArray(accept) ? accept : accept ? [accept] : [];
128
162
  const acceptString = acceptTypes.join(",");
@@ -150,25 +184,40 @@ const FileUpload = React__default.forwardRef(({ accept, multiple = false, maxSiz
150
184
  return true;
151
185
  return file.size <= maxSize;
152
186
  }, [maxSize]);
153
- // Create preview URL for images
154
- const createPreview = useCallback((file) => {
155
- if (file.type.startsWith("image/")) {
156
- const url = URL.createObjectURL(file);
157
- setPreviewUrls((prev) => new Map(prev).set(file, url));
158
- }
187
+ // Keep image preview URLs in sync with the current list. Runs for files added
188
+ // via the dropzone as well as files injected through `value`/`defaultFiles`.
189
+ useEffect(() => {
190
+ setPreviewUrls((prev) => {
191
+ const next = new Map();
192
+ let changed = false;
193
+ for (const file of selectedFiles) {
194
+ if (!file.type.startsWith("image/"))
195
+ continue;
196
+ const existing = prev.get(file);
197
+ if (existing) {
198
+ next.set(file, existing);
199
+ }
200
+ else {
201
+ next.set(file, URL.createObjectURL(file));
202
+ changed = true;
203
+ }
204
+ }
205
+ // Revoke URLs for files no longer present.
206
+ for (const [file, url] of prev) {
207
+ if (!next.has(file)) {
208
+ URL.revokeObjectURL(url);
209
+ changed = true;
210
+ }
211
+ }
212
+ return changed ? next : prev;
213
+ });
214
+ }, [selectedFiles]);
215
+ // Revoke any outstanding object URLs on unmount.
216
+ const previewUrlsRef = useRef(previewUrls);
217
+ previewUrlsRef.current = previewUrls;
218
+ useEffect(() => () => {
219
+ previewUrlsRef.current.forEach((url) => URL.revokeObjectURL(url));
159
220
  }, []);
160
- // Clean up preview URLs
161
- const revokePreview = useCallback((file) => {
162
- const url = previewUrls.get(file);
163
- if (url) {
164
- URL.revokeObjectURL(url);
165
- setPreviewUrls((prev) => {
166
- const newMap = new Map(prev);
167
- newMap.delete(file);
168
- return newMap;
169
- });
170
- }
171
- }, [previewUrls]);
172
221
  // Handle file selection/drop
173
222
  const handleFiles = useCallback((files) => {
174
223
  if (!files || files.length === 0)
@@ -209,14 +258,13 @@ const FileUpload = React__default.forwardRef(({ accept, multiple = false, maxSiz
209
258
  onError === null || onError === void 0 ? void 0 : onError(error);
210
259
  return;
211
260
  }
212
- // Update selected files
261
+ // Append when multiple, otherwise replace. Previews are handled by the
262
+ // sync effect above.
213
263
  const newFiles = multiple
214
264
  ? [...selectedFiles, ...validFiles]
215
265
  : validFiles;
216
- setSelectedFiles(newFiles);
217
- // Create previews
218
- validFiles.forEach(createPreview);
219
- // Call onUpload callback
266
+ commitFiles(newFiles);
267
+ // Call onUpload callback with only the newly added files
220
268
  onUpload === null || onUpload === void 0 ? void 0 : onUpload(validFiles);
221
269
  }, [
222
270
  isValidFileType,
@@ -227,7 +275,7 @@ const FileUpload = React__default.forwardRef(({ accept, multiple = false, maxSiz
227
275
  multiple,
228
276
  onUpload,
229
277
  onError,
230
- createPreview,
278
+ commitFiles,
231
279
  ]);
232
280
  // Handle drag events
233
281
  const handleDragEnter = useCallback((e) => {
@@ -269,32 +317,23 @@ const FileUpload = React__default.forwardRef(({ accept, multiple = false, maxSiz
269
317
  }, [handleFiles]);
270
318
  // Handle file removal
271
319
  const handleRemoveFile = useCallback((file) => {
272
- setSelectedFiles((prev) => prev.filter((f) => f !== file));
273
- revokePreview(file);
320
+ // Preview cleanup is handled by the sync effect.
321
+ commitFiles(selectedFiles.filter((f) => f !== file));
274
322
  onRemove === null || onRemove === void 0 ? void 0 : onRemove(file);
275
- }, [onRemove, revokePreview]);
276
- // Format file size
277
- const formatFileSize = (bytes) => {
278
- if (bytes === 0)
279
- return "0 Bytes";
280
- const k = 1024;
281
- const sizes = ["Bytes", "KB", "MB", "GB"];
282
- const i = Math.floor(Math.log(bytes) / Math.log(k));
283
- return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
284
- };
285
- return (jsxs("div", { ref: ref, className: cn(theme.containerStyle, className), ...props, children: [label && (typeof label === "string" ? (jsx(FormLabel, { htmlFor: `${generatedId}-input`, state: isDisabled ? "disabled" : isInvalid ? "invalid" : "default", children: label })) : (label)), jsx("input", { ref: inputRef, id: `${generatedId}-input`, type: "file", accept: acceptString, multiple: multiple, onChange: handleInputChange, disabled: isDisabled, className: "sr-only", "aria-label": "File upload" }), jsx("div", { className: cn(theme.dropzoneStyle, isDragActive && theme.dropzoneActiveStyle, isDisabled && theme.dropzoneDisabledStyle, isInvalid && "border-[var(--color-danger)]"), onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, onClick: handleClick, role: "button", tabIndex: isDisabled ? -1 : 0, "aria-disabled": isDisabled, "aria-invalid": isInvalid || undefined, onKeyDown: (e) => {
323
+ }, [commitFiles, selectedFiles, onRemove]);
324
+ return (jsxs("div", { ref: ref, className: cn(theme.containerStyle, className), ...props, children: [label && (typeof label === "string" ? (jsx(FormLabel, { htmlFor: `${generatedId}-input`, state: isDisabled ? "disabled" : isInvalid ? "invalid" : "default", children: label })) : (label)), jsx("input", { ref: inputRef, id: `${generatedId}-input`, type: "file", accept: acceptString, multiple: multiple, onChange: handleInputChange, disabled: isDisabled, className: "sr-only", "aria-label": "File upload", "aria-invalid": isInvalid || undefined, "aria-describedby": describedBy }), jsx("div", { className: cn(theme.dropzoneStyle, isDragActive && theme.dropzoneActiveStyle, isDisabled && theme.dropzoneDisabledStyle, isInvalid && "border-[var(--color-danger)]"), onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, onClick: handleClick, role: "button", tabIndex: isDisabled ? -1 : 0, "aria-disabled": isDisabled, "aria-invalid": isInvalid || undefined, "aria-describedby": describedBy, onKeyDown: (e) => {
286
325
  if (e.key === "Enter" || e.key === " ") {
287
326
  e.preventDefault();
288
327
  handleClick();
289
328
  }
290
- }, children: jsxs("div", { className: theme.contentStyle, children: [jsx("div", { className: "size-11 border border-border bg-white rounded-full flex items-center justify-center mb-4", children: jsx(CloudArrowUpIcon, { className: `size-6 text-[var(--color-text-secondary)]` }) }), jsxs("p", { className: "text-base font-medium text-[var(--color-text-primary)]", children: [jsx("span", { className: "text-[var(--color-primary)] font-semibold", children: buttonText }), " ", helperText] }), description && (jsx("p", { className: "text-sm text-[var(--color-text-muted)] mt-1", children: description }))] }) }), errorMessage && (jsx("div", { className: "text-sm mt-1 text-[var(--color-danger)]", children: errorMessage })), showPreview && selectedFiles.length > 0 && (jsx("div", { className: theme.previewContainerStyle, children: selectedFiles.map((file, index) => {
291
- const previewUrl = previewUrls.get(file);
292
- const isImage = file.type.startsWith("image/");
293
- return (jsxs("div", { className: theme.previewItemStyle, children: [isImage && previewUrl ? (jsx("img", { src: previewUrl, alt: file.name, className: theme.previewImageStyle })) : (jsxs("div", { className: theme.previewFileStyle, children: [jsx(DocumentIcon, { className: "w-8 h-8 mx-auto mb-2 text-[var(--color-text-muted)]" }), jsx("span", { className: "text-sm", children: file.name }), jsx("span", { className: "text-sm text-[var(--color-text-placeholder)] mt-1", children: formatFileSize(file.size) })] })), jsx(Button, { variant: "filled", color: "danger", size: "sm", iconOnly: true, onClick: (e) => {
294
- e.stopPropagation();
295
- handleRemoveFile(file);
296
- }, "aria-label": `Remove ${file.name}`, className: cn(theme.removeButtonStyle, "rounded-full !p-0 size-6"), children: jsx(XMarkIcon, { className: "size-4", strokeWidth: 2 }) })] }, `${file.name}-${index}`));
297
- }) }))] }));
329
+ }, children: jsxs("div", { className: "flex flex-col items-center text-center pointer-events-none", children: [showIcon && (jsx("div", { className: "size-10 border border-[var(--color-border)] bg-[var(--color-surface-raised)] rounded-full flex items-center justify-center mb-3", children: jsx(CloudArrowUpIcon, { className: "size-6 text-[var(--color-text-secondary)]" }) })), jsxs("p", { className: "text-sm font-medium text-[var(--color-text-primary)]/80", children: [jsx("span", { className: "text-[var(--color-primary)]", children: buttonText }), " ", helperText] }), description && (jsx("p", { id: `${generatedId}-description`, className: "text-xs text-[var(--color-text-muted)] mt-0.5", children: description }))] }) }), errorMessage && (jsx("div", { id: `${generatedId}-error`, className: "text-sm mt-1 text-[var(--color-danger)]", children: errorMessage })), showPreview && selectedFiles.length > 0 && (jsx("div", { className: theme.previewContainerStyle, children: jsx(AnimatePresence, { initial: false, children: selectedFiles.map((file) => {
330
+ const previewUrl = previewUrls.get(file);
331
+ const isImage = file.type.startsWith("image/");
332
+ return (jsxs(motion.div, { layout: !shouldReduceMotion, initial: shouldReduceMotion ? false : PREVIEW_ITEM.initial, animate: PREVIEW_ITEM.animate, exit: shouldReduceMotion ? { opacity: 0 } : PREVIEW_ITEM.exit, transition: PREVIEW_LAYOUT_SPRING, className: theme.previewItemStyle, children: [isImage && previewUrl ? (jsx("img", { src: previewUrl, alt: file.name, className: theme.previewImageStyle })) : (jsx("div", { className: theme.previewFileStyle, title: file.name, children: jsx(DocumentIcon, { className: "size-6" }) })), jsx(Button, { variant: "soft", color: "danger", size: "sm", iconOnly: true, onClick: (e) => {
333
+ e.stopPropagation();
334
+ handleRemoveFile(file);
335
+ }, "aria-label": `Remove ${file.name}`, className: cn(theme.removeButtonStyle, "rounded-full !p-0 size-6"), children: jsx(XMarkIcon, { className: "size-4", strokeWidth: 2 }) })] }, getFileKey(file)));
336
+ }) }) }))] }));
298
337
  });
299
338
  FileUpload.displayName = "FileUpload";
300
339
 
@@ -1,25 +1,17 @@
1
1
  /**
2
- * Internal theme structure for the FileUpload component
2
+ * Theme for the FileUpload component.
3
+ * Uses production-ready CSS variables from theme.css.
3
4
  */
4
5
  interface FileUploadTheme {
5
6
  containerStyle: string;
6
7
  dropzoneStyle: string;
7
8
  dropzoneActiveStyle: string;
8
9
  dropzoneDisabledStyle: string;
9
- contentStyle: string;
10
- iconStyle: string;
11
- textStyle: string;
12
- helperTextStyle: string;
13
- descriptionStyle: string;
14
10
  previewContainerStyle: string;
15
11
  previewItemStyle: string;
16
12
  previewImageStyle: string;
17
13
  previewFileStyle: string;
18
14
  removeButtonStyle: string;
19
15
  }
20
- /**
21
- * Default theme for the FileUpload component
22
- * Uses production-ready CSS variables from global.scss
23
- */
24
16
  export declare const fileUploadTheme: FileUploadTheme;
25
17
  export {};
@@ -1,36 +1,22 @@
1
- /**
2
- * Default theme for the FileUpload component
3
- * Uses production-ready CSS variables from global.scss
4
- */
5
1
  const fileUploadTheme = {
6
- // Container wrapper
2
+ // Root container
7
3
  containerStyle: "w-full",
8
4
  // Dropzone area (default state)
9
- dropzoneStyle: "relative flex flex-col items-center justify-center w-full min-h-[160px] p-6 border border-dashed border-[var(--color-border)] rounded-[var(--form-radius)] bg-[var(--color-background-secondary)] hover:bg-[var(--color-background-secondary)] hover:border-[var(--color-primary)] transition-colors duration-400 cursor-pointer",
10
- // Dropzone when dragging over
11
- dropzoneActiveStyle: "border-[var(--color-primary)] bg-[var(--color-primary-50)] scale-[1.01]",
12
- // Dropzone when disabled
13
- dropzoneDisabledStyle: "cursor-not-allowed opacity-60 hover:bg-[var(--color-background-secondary)] hover:border-[var(--color-border)]",
14
- // Content wrapper inside dropzone
15
- contentStyle: "flex flex-col items-center text-center pointer-events-none",
16
- // Upload icon
17
- iconStyle: "size-8 text-[var(--color-text-placeholder)]",
18
- // Main text
19
- textStyle: "text-sm font-medium text-[var(--color-text-primary)]",
20
- // Helper text
21
- helperTextStyle: "text-sm text-[var(--color-text-muted)] mt-1",
22
- // Description below dropzone
23
- descriptionStyle: "text-xs text-[var(--color-text-muted)] mt-1",
24
- // Preview container
25
- previewContainerStyle: "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4 mt-4",
26
- // Individual preview item
27
- previewItemStyle: "relative group rounded-[var(--radius-base)] border border-[var(--color-border)] overflow-hidden bg-[var(--color-background-secondary)]",
28
- // Preview image
29
- previewImageStyle: "w-full h-32 object-cover",
30
- // Preview file (non-image)
31
- previewFileStyle: "flex items-center justify-center w-full h-32 p-3 text-sm text-[var(--color-text-secondary)] text-center break-all",
32
- // Remove button
33
- removeButtonStyle: "absolute top-1 right-1 -translate-y-1 opacity-0 group-hover:opacity-100 transition-all duration-400 group-hover:-translate-y-0",
5
+ dropzoneStyle: "relative flex flex-col items-center justify-center w-full min-h-[160px] p-6 border border-dashed border-[var(--color-border)] rounded-[var(--form-radius)] bg-[var(--color-surface)] hover:border-[var(--color-border-hover)] transition-colors duration-400 cursor-pointer",
6
+ // Dragging over translucent primary tint composites over the surface in both modes
7
+ dropzoneActiveStyle: "border-solid border-[var(--color-primary)] bg-[var(--color-primary)]/5",
8
+ // Disabled
9
+ dropzoneDisabledStyle: "cursor-not-allowed opacity-60 hover:bg-[var(--color-surface)] hover:border-[var(--color-border)]",
10
+ // Wrapping row of fixed-width thumbnails (not a stretch grid)
11
+ previewContainerStyle: "flex flex-wrap gap-1 mt-2",
12
+ // Fixed responsive thumbnail — stays small on large containers, wraps to more-per-row
13
+ previewItemStyle: "relative group shrink-0 size-10 sm:size-12 md:size-16 rounded-[var(--radius-base)] border border-[var(--color-border)] overflow-hidden bg-[var(--color-surface)]",
14
+ // Image fills the thumbnail box
15
+ previewImageStyle: "size-full object-cover",
16
+ // Non-image tile — centered document icon (filename surfaced via title tooltip)
17
+ previewFileStyle: "flex items-center justify-center size-full text-[var(--color-text-muted)]",
18
+ // Remove button (revealed on item hover)
19
+ removeButtonStyle: "absolute top-1 right-1 -translate-y-1 opacity-0 group-hover:opacity-100 transition-all duration-400 group-hover:-translate-y-0",
34
20
  };
35
21
 
36
22
  export { fileUploadTheme };
@@ -4,7 +4,7 @@ export type FileUploadState = "default" | "disabled" | "invalid";
4
4
  * FileUpload component props
5
5
  * Supports both click-to-upload and drag-and-drop
6
6
  */
7
- export interface FileUploadProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onError'> {
7
+ export interface FileUploadProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onError' | 'onChange'> {
8
8
  /**
9
9
  * Accepted file types (MIME types or file extensions)
10
10
  * @example ["image/*", ".pdf", ".doc"]
@@ -43,7 +43,22 @@ export interface FileUploadProps extends Omit<React.HTMLAttributes<HTMLDivElemen
43
43
  */
44
44
  errorMessage?: React.ReactNode;
45
45
  /**
46
- * Callback when files are selected/dropped
46
+ * Controlled file list. When provided, the component is controlled and the
47
+ * parent owns the list — pair with `onChange`. Leave undefined for the
48
+ * uncontrolled behavior (the component tracks its own list internally).
49
+ */
50
+ value?: File[];
51
+ /**
52
+ * Initial file list for the uncontrolled case. Ignored when `value` is set.
53
+ */
54
+ defaultFiles?: File[];
55
+ /**
56
+ * Callback fired whenever the full file list changes (add or remove),
57
+ * with the complete next list. Required to read state in controlled mode.
58
+ */
59
+ onChange?: (files: File[]) => void;
60
+ /**
61
+ * Callback when files are selected/dropped, with only the newly added files.
47
62
  */
48
63
  onUpload?: (files: File[]) => void;
49
64
  /**
@@ -59,20 +74,26 @@ export interface FileUploadProps extends Omit<React.HTMLAttributes<HTMLDivElemen
59
74
  * @default true
60
75
  */
61
76
  showPreview?: boolean;
77
+ /**
78
+ * Show the upload icon inside the dropzone. Set `false` for slim/compact
79
+ * containers where the icon would crowd the text.
80
+ * @default true
81
+ */
82
+ showIcon?: boolean;
62
83
  /**
63
84
  * Custom upload button text
64
85
  * @default "Choose files"
65
86
  */
66
- buttonText?: string;
87
+ buttonText?: React.ReactNode;
67
88
  /**
68
89
  * Custom dropzone helper text
69
90
  * @default "or drag and drop"
70
91
  */
71
- helperText?: string;
92
+ helperText?: React.ReactNode;
72
93
  /**
73
94
  * Helper text displayed below the upload area
74
95
  */
75
- description?: string;
96
+ description?: React.ReactNode;
76
97
  /**
77
98
  * Custom CSS class for the container
78
99
  */
@@ -96,11 +117,6 @@ export interface FileUploadThemeOverrides {
96
117
  dropzoneStyle?: string;
97
118
  dropzoneActiveStyle?: string;
98
119
  dropzoneDisabledStyle?: string;
99
- contentStyle?: string;
100
- iconStyle?: string;
101
- textStyle?: string;
102
- helperTextStyle?: string;
103
- descriptionStyle?: string;
104
120
  previewContainerStyle?: string;
105
121
  previewItemStyle?: string;
106
122
  previewImageStyle?: string;
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import React__default, { useState, useEffect } from 'react';
3
3
  import { useReducedMotion, motion, AnimatePresence } from 'motion/react';
4
- import { DocumentIcon, TrashIcon, XMarkIcon, ArrowPathIcon } from '@heroicons/react/24/outline';
4
+ import { DocumentIcon, TrashIcon, ArrowPathIcon, XMarkIcon } from '@heroicons/react/24/outline';
5
5
  import { Icon } from '../../core/Icon/Icon.js';
6
6
  import { fileTypes } from '../../core/Icon/data/fileTypes.js';
7
7
  import { cn } from '../../../utils/cn.js';
@@ -39,27 +39,38 @@ const RadialProgress = ({ progress, status, strokeWidth, trackColor, progressCol
39
39
  setShowRemove(false);
40
40
  }, [isComplete, completionDelay]);
41
41
  const handleClick = () => {
42
- if (isError && onRetry) {
43
- onRetry();
42
+ if (!onRemove)
44
43
  return;
44
+ if (confirmRemove) {
45
+ setConfirmOpen(true);
45
46
  }
46
- if (onRemove) {
47
- if (confirmRemove) {
48
- setConfirmOpen(true);
49
- }
50
- else {
51
- onRemove();
52
- }
47
+ else {
48
+ onRemove();
53
49
  }
54
50
  };
55
51
  const handleConfirm = () => {
56
52
  setConfirmOpen(false);
57
53
  onRemove === null || onRemove === void 0 ? void 0 : onRemove();
58
54
  };
59
- const isClickable = (isError && onRetry) || onRemove;
60
- const ariaLabel = isError && onRetry ? "Retry upload" : onRemove ? "Remove file" : undefined;
55
+ // Uploading/complete: the radial doubles as the cancel/remove target.
56
+ const isClickable = !!onRemove;
57
+ const ariaLabel = onRemove ? "Remove file" : undefined;
58
+ // Confirm popover shared by the radial (cancel/remove) and the error-state delete button.
59
+ const confirmPopover = (trigger, label, offsetValue) => (jsxs(Popover, { isOpen: confirmOpen, onOpenChange: (open) => {
60
+ setConfirmOpen(open);
61
+ if (!open)
62
+ setIsHovered(false);
63
+ }, placement: "bottom-end", offset: offsetValue, triggerType: "click", children: [jsx(Popover.Trigger, { children: trigger }), jsxs(Popover.Body, { className: "flex items-center p-1 border-[0.5px] rounded-xl gap-0.5 bg-opacity-50 backdrop-blur-md", children: [jsx("span", { className: "text-sm font-semibold text-[var(--color-text-secondary)] px-3", children: label }), jsxs("div", { className: "flex gap-1", children: [jsx(Button, { size: "sm", color: "danger", variant: "filled", onClick: handleConfirm, children: "Yes" }), jsx(Button, { size: "sm", variant: "soft", color: "neutral", onClick: () => setConfirmOpen(false), children: "No" })] })] })] }));
61
64
  // Shared remove button used after completion tick
62
65
  const removeButton = (jsx(motion.div, { initial: shouldReduceMotion ? false : REMOVE_BUTTON_ENTER.initial, animate: REMOVE_BUTTON_ENTER.animate, transition: REMOVE_BUTTON_ENTER.transition, className: "shrink-0", children: jsx(Button, { size: "sm", variant: "soft", color: "neutral", "aria-label": "Remove file", iconOnly: true, className: "rounded-full size-8", onClick: confirmRemove ? undefined : () => onRemove === null || onRemove === void 0 ? void 0 : onRemove(), children: jsx(TrashIcon, { className: "size-3.5", strokeWidth: 2 }) }) }, "remove-btn"));
66
+ // Error state: retry + delete buttons (no progress arc/percentage).
67
+ if (isError) {
68
+ const deleteButton = onRemove ? (jsx(Button, { size: "sm", variant: "soft", color: "neutral", "aria-label": "Remove file", iconOnly: true, className: "rounded-full size-8", onClick: confirmRemove ? undefined : () => onRemove(), children: jsx(TrashIcon, { className: "size-3.5", strokeWidth: 2 }) })) : null;
69
+ return (jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [onRetry && (jsx(Button, { size: "sm", variant: "soft", color: "neutral", "aria-label": "Retry upload", iconOnly: true, className: "rounded-full size-8", onClick: () => onRetry(), children: jsx(ArrowPathIcon, { className: "size-3.5", strokeWidth: 2 }) })), deleteButton &&
70
+ (confirmRemove
71
+ ? confirmPopover(deleteButton, "Remove file?", 6)
72
+ : deleteButton)] }));
73
+ }
63
74
  const radialElement = (jsxs("div", { className: cn("relative shrink-0", sizeClass, isClickable && "cursor-pointer"), onMouseEnter: () => setIsHovered(true), onMouseLeave: () => {
64
75
  if (!confirmOpen)
65
76
  setIsHovered(false);
@@ -70,18 +81,14 @@ const RadialProgress = ({ progress, status, strokeWidth, trackColor, progressCol
70
81
  handleClick();
71
82
  }
72
83
  }
73
- : undefined, children: [jsxs("svg", { viewBox: `0 0 ${VIEWBOX} ${VIEWBOX}`, className: "w-full h-full -rotate-90", children: [jsx("circle", { cx: CENTER, cy: CENTER, r: radius, fill: "none", stroke: trackColor, strokeWidth: strokeWidth }), jsx(motion.circle, { cx: CENTER, cy: CENTER, r: radius, fill: "none", stroke: progressColor, strokeWidth: strokeWidth, strokeLinecap: "round", strokeDasharray: circumference, initial: { strokeDashoffset: circumference }, animate: { strokeDashoffset: isComplete ? 0 : offset }, transition: shouldReduceMotion ? { duration: 0 } : PROGRESS_SPRING })] }), jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: jsxs(AnimatePresence, { mode: "wait", initial: false, children: [isUploading && !isHovered && (jsx(motion.span, { className: centerTextStyle, ...(shouldReduceMotion ? {} : CROSSFADE), children: progress }, "percent")), isUploading && isHovered && onRemove && (jsx(motion.span, { ...(shouldReduceMotion ? {} : CROSSFADE), children: jsx(XMarkIcon, { className: "size-4 text-[var(--color-text-muted)]", strokeWidth: 2.5 }) }, "close")), isComplete && !showRemove && (jsx(motion.span, { ...(shouldReduceMotion ? {} : CROSSFADE), children: jsx("svg", { viewBox: "0 0 24 24", fill: "none", className: cn("size-4", statusIconColor), children: jsx("path", { d: "M5 13l4.5 4.5L19 7", stroke: "currentColor", strokeWidth: 3.5, strokeLinecap: "round", strokeLinejoin: "round" }) }) }, "check")), isError && (jsx(motion.span, { ...(shouldReduceMotion ? {} : CROSSFADE), children: jsx(ArrowPathIcon, { className: cn("size-3.5", statusIconColor), strokeWidth: 2.5 }) }, "retry"))] }) })] }));
84
+ : undefined, children: [jsxs("svg", { viewBox: `0 0 ${VIEWBOX} ${VIEWBOX}`, className: "w-full h-full -rotate-90", children: [jsx("circle", { cx: CENTER, cy: CENTER, r: radius, fill: "none", stroke: trackColor, strokeWidth: strokeWidth }), jsx(motion.circle, { cx: CENTER, cy: CENTER, r: radius, fill: "none", stroke: progressColor, strokeWidth: strokeWidth, strokeLinecap: "round", strokeDasharray: circumference, initial: { strokeDashoffset: circumference }, animate: { strokeDashoffset: isComplete ? 0 : offset }, transition: shouldReduceMotion ? { duration: 0 } : PROGRESS_SPRING })] }), jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: jsxs(AnimatePresence, { mode: "wait", initial: false, children: [isUploading && !isHovered && (jsx(motion.span, { className: centerTextStyle, ...(shouldReduceMotion ? {} : CROSSFADE), children: progress }, "percent")), isUploading && isHovered && onRemove && (jsx(motion.span, { ...(shouldReduceMotion ? {} : CROSSFADE), children: jsx(XMarkIcon, { className: "size-4 text-[var(--color-text-muted)]", strokeWidth: 2.5 }) }, "close")), isComplete && !showRemove && (jsx(motion.span, { ...(shouldReduceMotion ? {} : CROSSFADE), children: jsx("svg", { viewBox: "0 0 24 24", fill: "none", className: cn("size-4", statusIconColor), children: jsx("path", { d: "M5 13l4.5 4.5L19 7", stroke: "currentColor", strokeWidth: 3.5, strokeLinecap: "round", strokeLinejoin: "round" }) }) }, "check"))] }) })] }));
74
85
  // Determine what to render: radial indicator or remove button
75
86
  const showRemoveButton = isComplete && showRemove && onRemove;
76
87
  const triggerElement = showRemoveButton ? removeButton : radialElement;
77
- // No confirm popover needed — return the element directly
88
+ // No confirm popover needed — return the element directly.
78
89
  if (!confirmRemove || !onRemove)
79
90
  return triggerElement;
80
- return (jsxs(Popover, { isOpen: confirmOpen, onOpenChange: (open) => {
81
- setConfirmOpen(open);
82
- if (!open)
83
- setIsHovered(false);
84
- }, placement: "bottom-end", offset: isComplete ? 2 : 6, triggerType: "click", children: [jsx(Popover.Trigger, { children: triggerElement }), jsxs(Popover.Body, { portal: false, className: "flex items-center p-0.5 border-[0.5px] rounded-xl gap-0.5", children: [jsx("span", { className: "text-base font-medium text-[var(--color-text-secondary)] px-3", children: isComplete ? "Remove file?" : "Cancel upload?" }), jsx(Button, { size: "sm", color: "danger", variant: "filled", onClick: handleConfirm, children: "Yes" }), jsx(Button, { size: "sm", variant: "soft", color: "neutral", onClick: () => setConfirmOpen(false), children: "No" })] })] }));
91
+ return confirmPopover(triggerElement, isComplete ? "Remove file?" : "Cancel upload?", isComplete ? 2 : 6);
85
92
  };
86
93
  // --- Main Component ---
87
94
  const FileUploadProgress = React__default.forwardRef(({ items, onRemove, onRetry, showFileSize = true, confirmRemove = true, completionDelay = 1500, className, theme: themeOverrides, ...rest }, ref) => {
@@ -103,7 +110,7 @@ const FileUploadProgress = React__default.forwardRef(({ items, onRemove, onRetry
103
110
  const clampedProgress = Math.min(100, Math.max(0, item.progress));
104
111
  const ext = getFileExtension(item.file.name);
105
112
  const hasFileTypeIcon = ext !== null && isKnownFileType(ext);
106
- return (jsxs("div", { className: (_a = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.itemStyle) !== null && _a !== void 0 ? _a : t.itemStyle, children: [hasFileTypeIcon ? (jsx("span", { className: "shrink-0", children: jsx(Icon, { icon: ext, size: "lg" }) })) : (jsx("span", { className: cn((_b = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.iconContainerStyle) !== null && _b !== void 0 ? _b : t.iconContainerStyle, t.iconSize), children: jsx(DocumentIcon, { className: (_c = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.iconStyle) !== null && _c !== void 0 ? _c : t.iconStyle }) })), jsxs("div", { className: (_d = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.fileInfoStyle) !== null && _d !== void 0 ? _d : t.fileInfoStyle, children: [jsx("p", { className: (_e = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.fileNameStyle) !== null && _e !== void 0 ? _e : t.fileNameStyle, children: item.file.name }), showFileSize && (jsx("p", { className: (_f = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.fileSizeStyle) !== null && _f !== void 0 ? _f : t.fileSizeStyle, children: formatFileSize(item.file.size) })), item.status === "error" && item.error && (jsx("p", { className: (_g = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.errorTextStyle) !== null && _g !== void 0 ? _g : t.errorTextStyle, children: item.error }))] }), jsx(RadialProgress, { progress: clampedProgress, status: item.status, strokeWidth: t.radialStroke, trackColor: (_h = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.radialTrackColor) !== null && _h !== void 0 ? _h : t.radialTrackColor, progressColor: radialProgressColors[item.status], centerTextStyle: (_j = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.radialCenterTextStyle) !== null && _j !== void 0 ? _j : t.radialCenterTextStyle, statusIconColor: statusIconColors[item.status], sizeClass: t.radialSize, shouldReduceMotion: shouldReduceMotion, confirmRemove: confirmRemove, completionDelay: completionDelay, onRemove: onRemove ? () => onRemove(item.id) : undefined, onRetry: onRetry ? () => onRetry(item.id) : undefined })] }, item.id));
113
+ return (jsxs("div", { className: (_a = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.itemStyle) !== null && _a !== void 0 ? _a : t.itemStyle, children: [hasFileTypeIcon ? (jsx("span", { className: "shrink-0", children: jsx(Icon, { icon: ext, size: "lg" }) })) : (jsx("span", { className: cn((_b = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.iconContainerStyle) !== null && _b !== void 0 ? _b : t.iconContainerStyle, t.iconSize), children: jsx(DocumentIcon, { className: (_c = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.iconStyle) !== null && _c !== void 0 ? _c : t.iconStyle }) })), jsxs("div", { className: (_d = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.fileInfoStyle) !== null && _d !== void 0 ? _d : t.fileInfoStyle, children: [jsx("p", { className: (_e = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.fileNameStyle) !== null && _e !== void 0 ? _e : t.fileNameStyle, children: item.file.name }), item.status === "error" && item.error ? (jsx("p", { className: (_f = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.errorTextStyle) !== null && _f !== void 0 ? _f : t.errorTextStyle, children: item.error })) : (showFileSize && (jsx("p", { className: (_g = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.fileSizeStyle) !== null && _g !== void 0 ? _g : t.fileSizeStyle, children: formatFileSize(item.file.size) })))] }), jsx(RadialProgress, { progress: clampedProgress, status: item.status, strokeWidth: t.radialStroke, trackColor: (_h = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.radialTrackColor) !== null && _h !== void 0 ? _h : t.radialTrackColor, progressColor: radialProgressColors[item.status], centerTextStyle: (_j = themeOverrides === null || themeOverrides === void 0 ? void 0 : themeOverrides.radialCenterTextStyle) !== null && _j !== void 0 ? _j : t.radialCenterTextStyle, statusIconColor: statusIconColors[item.status], sizeClass: t.radialSize, shouldReduceMotion: shouldReduceMotion, confirmRemove: confirmRemove, completionDelay: completionDelay, onRemove: onRemove ? () => onRemove(item.id) : undefined, onRetry: onRetry ? () => onRetry(item.id) : undefined })] }, item.id));
107
114
  }) }));
108
115
  });
109
116
  FileUploadProgress.displayName = "FileUploadProgress";
@@ -6,13 +6,13 @@ const fileUploadProgressTheme = {
6
6
  fileInfoStyle: "flex-1 min-w-0",
7
7
  fileNameStyle: "text-sm font-medium text-[var(--color-text-primary)] truncate",
8
8
  fileSizeStyle: "text-xs text-[var(--color-text-muted)]",
9
- radialTrackColor: "var(--color-background-tertiary)",
9
+ radialTrackColor: "var(--color-surface-sunken)",
10
10
  radialProgressColors: {
11
11
  uploading: "var(--color-primary)",
12
12
  complete: "var(--color-success)",
13
13
  error: "var(--color-danger)",
14
14
  },
15
- radialCenterTextStyle: "text-[10px] font-semibold tabular-nums text-[var(--color-text-secondary)]",
15
+ radialCenterTextStyle: "text-xs tracking-tight font-semibold tabular-nums text-[var(--color-text-secondary)]",
16
16
  statusIconColors: {
17
17
  uploading: "",
18
18
  complete: "text-[var(--color-success)]",
@@ -8,17 +8,17 @@ const inputTheme = {
8
8
  // Base container styles
9
9
  wrapperStyle: formsBaseTheme.wrapperStyle,
10
10
  // Input group styles - no focus-within (focus is managed via JS for contentStart/contentEnd compatibility)
11
- inputGroupStyle: "relative flex rounded-[var(--form-radius)] outline outline-1 outline-[var(--color-border)] " +
12
- "bg-white transition-all " +
13
- "dark:bg-[var(--color-neutral-900)] dark:outline-[var(--color-neutral-700)]",
11
+ inputGroupStyle: "relative flex rounded-[var(--form-radius)] shadow-[inset_0_0_0_1px_var(--color-border)] " +
12
+ "bg-[var(--color-surface)] transition-all " +
13
+ // Hover edge: strengthen the border to --color-border-secondary on hover. Scoped with
14
+ // not-focus-within so the JS-driven focus border (primary) always wins.
15
+ "not-focus-within:hover:shadow-[inset_0_0_0_1px_var(--color-border-secondary)]",
14
16
  // JS-driven focus ring (applied when isFocused is true)
15
- inputGroupFocusStyle: "outline-2 outline-[var(--color-primary-600)] -outline-offset-1 " +
16
- "ring-4 ring-[var(--color-primary)]/10 " +
17
- "dark:outline-[var(--color-primary-500)] dark:ring-[var(--color-primary-500)]/20",
17
+ inputGroupFocusStyle: "shadow-[inset_0_0_0_1px_var(--color-primary-600)] ring-4 ring-[var(--color-primary)]/10 " +
18
+ "dark:shadow-[inset_0_0_0_1px_var(--color-primary-500)] dark:ring-[var(--color-primary-500)]/20",
18
19
  // JS-driven focus ring for invalid state
19
- inputGroupFocusInvalidStyle: "outline-2 outline-[var(--color-danger)] -outline-offset-1 " +
20
- "ring-4 ring-[var(--color-danger)]/10 " +
21
- "dark:outline-[var(--color-danger-500)] dark:ring-[var(--color-danger-500)]/20",
20
+ inputGroupFocusInvalidStyle: "shadow-[inset_0_0_0_1px_var(--color-danger)] ring-4 ring-[var(--color-danger)]/10 " +
21
+ "dark:shadow-[inset_0_0_0_1px_var(--color-danger-500)] dark:ring-[var(--color-danger-500)]/20",
22
22
  // Base input styles - extends common form control with design tokens
23
23
  baseStyle: `${formsBaseTheme.controlBaseStyle} py-2 px-3`,
24
24
  // Size variants - composed from atomic tokens
@@ -31,8 +31,11 @@ const inputTheme = {
31
31
  states: {
32
32
  default: formsBaseTheme.states.default,
33
33
  disabled: formsBaseTheme.states.disabled,
34
- invalid: "outline-[var(--color-danger)] bg-[var(--color-danger-50)] " +
35
- "dark:bg-[var(--color-danger-950)]/30 dark:outline-[var(--color-danger-500)]",
34
+ invalid: "shadow-[inset_0_0_0_1px_var(--color-danger)] bg-[var(--color-danger-50)] " +
35
+ // Keep the danger border on hover (don't let the hover edge override it)
36
+ "not-focus-within:hover:shadow-[inset_0_0_0_1px_var(--color-danger)] " +
37
+ "dark:bg-[var(--color-danger-950)]/30 dark:shadow-[inset_0_0_0_1px_var(--color-danger-500)] " +
38
+ "dark:not-focus-within:hover:shadow-[inset_0_0_0_1px_var(--color-danger-500)]",
36
39
  },
37
40
  // Helper text styles with state handling
38
41
  helperText: formsBaseTheme.helperText,
@@ -67,10 +70,8 @@ const inputTheme = {
67
70
  // Number input controls
68
71
  numberButtonsContainerStyle: "absolute inset-y-0 right-0 flex flex-row items-center pr-2 z-10 gap-1",
69
72
  numberButtonStyle: "bg-[var(--color-background-secondary)] flex items-center justify-center " +
70
- "text-[var(--color-text-muted)] hover:bg-[var(--color-background-tertiary)] " +
71
- "hover:text-[var(--color-text-primary)] cursor-pointer size-6 rounded-sm transition-colors " +
72
- "dark:bg-[var(--color-neutral-800)] dark:text-[var(--color-neutral-500)] " +
73
- "dark:hover:bg-[var(--color-neutral-700)] dark:hover:text-[var(--color-neutral-200)]",
73
+ "text-[var(--color-text-muted)] hover:bg-[var(--color-surface-hover)] " +
74
+ "hover:text-[var(--color-text-primary)] cursor-pointer size-6 rounded-sm transition-colors",
74
75
  // Type-specific styles organized by input type
75
76
  typeStyles: {
76
77
  password: {
@@ -3,7 +3,7 @@
3
3
  * Follows Flikkui design system with CSS variables
4
4
  */
5
5
  const inputCounterTheme = {
6
- baseStyle: "flex items-center gap-3 transition-all p-1 border border-[var(--color-border)] rounded-[var(--form-radius)] w-fit",
6
+ baseStyle: "flex items-center gap-3 transition-all p-1 bg-[var(--color-surface)] border border-[var(--color-border)] rounded-[var(--form-radius)] w-fit",
7
7
  buttonWrapperStyle: "flex-shrink-0",
8
8
  numberStyle: "min-w-fit text-center text-sm font-medium text-[var(--color-text-primary)] tabular-nums select-none",
9
9
  };