@devalok/shilp-sutra 0.57.0 → 0.59.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 (258) hide show
  1. package/MIGRATION.md +91 -0
  2. package/dist/_chunks/badge-group.js +75 -188
  3. package/dist/_chunks/badge-group.js.map +1 -1
  4. package/dist/_chunks/chat.js +244 -243
  5. package/dist/_chunks/chat.js.map +1 -1
  6. package/dist/_chunks/image-preview.js +71 -70
  7. package/dist/_chunks/image-preview.js.map +1 -1
  8. package/dist/_chunks/motion-preference.js +15 -0
  9. package/dist/_chunks/motion-preference.js.map +1 -0
  10. package/dist/_chunks/motion-provider.js +1 -1
  11. package/dist/_chunks/motion-provider.js.map +1 -1
  12. package/dist/_chunks/popover.js +50 -49
  13. package/dist/_chunks/popover.js.map +1 -1
  14. package/dist/_chunks/primitives-internal.js +29 -29
  15. package/dist/_chunks/primitives-internal.js.map +1 -1
  16. package/dist/_chunks/shared.js +48 -47
  17. package/dist/_chunks/shared.js.map +1 -1
  18. package/dist/_chunks/success.js +34 -34
  19. package/dist/_chunks/tree-view.js +1 -1
  20. package/dist/_chunks/tree-view.js.map +1 -1
  21. package/dist/ai/block-renderer.js +8 -8
  22. package/dist/ai/command-bar.js +65 -65
  23. package/dist/ai/command-bar.js.map +1 -1
  24. package/dist/ai/conversation.js +35 -35
  25. package/dist/ai/conversation.js.map +1 -1
  26. package/dist/ai/index.js +4 -4
  27. package/dist/composed/activity-feed.d.ts.map +1 -1
  28. package/dist/composed/activity-feed.js +108 -107
  29. package/dist/composed/activity-feed.js.map +1 -1
  30. package/dist/composed/command-palette.js +137 -137
  31. package/dist/composed/command-palette.js.map +1 -1
  32. package/dist/composed/content-card.js +1 -1
  33. package/dist/composed/content-card.js.map +1 -1
  34. package/dist/composed/date-picker/index.js +5 -5
  35. package/dist/composed/date-picker/index.js.map +1 -1
  36. package/dist/composed/empty-state.js +4 -4
  37. package/dist/composed/file-preview/image-preview.d.ts.map +1 -1
  38. package/dist/composed/file-preview/shared.d.ts.map +1 -1
  39. package/dist/composed/file-preview/video-preview.d.ts.map +1 -1
  40. package/dist/composed/file-preview.js +151 -150
  41. package/dist/composed/file-preview.js.map +1 -1
  42. package/dist/composed/filter-bar.d.ts.map +1 -1
  43. package/dist/composed/filter-bar.js +53 -52
  44. package/dist/composed/filter-bar.js.map +1 -1
  45. package/dist/composed/form-section.d.ts.map +1 -1
  46. package/dist/composed/form-section.js +38 -37
  47. package/dist/composed/form-section.js.map +1 -1
  48. package/dist/composed/master-detail.d.ts.map +1 -1
  49. package/dist/composed/master-detail.js +1 -1
  50. package/dist/composed/master-detail.js.map +1 -1
  51. package/dist/composed/multi-select-popover.d.ts.map +1 -1
  52. package/dist/composed/multi-select-popover.js +109 -108
  53. package/dist/composed/multi-select-popover.js.map +1 -1
  54. package/dist/composed/rich-chat-input/attachment-strip.d.ts.map +1 -1
  55. package/dist/composed/rich-chat-input/reply-banner.d.ts.map +1 -1
  56. package/dist/composed/rich-chat-input/schedule-send.d.ts.map +1 -1
  57. package/dist/composed/rich-chat-input.d.ts.map +1 -1
  58. package/dist/composed/rich-chat-input.js +467 -466
  59. package/dist/composed/rich-chat-input.js.map +1 -1
  60. package/dist/composed/rich-text-editor.js +3 -3
  61. package/dist/composed/rich-text-editor.js.map +1 -1
  62. package/dist/composed/schedule-view.js +2 -2
  63. package/dist/composed/schedule-view.js.map +1 -1
  64. package/dist/composed/status-badge.d.ts.map +1 -1
  65. package/dist/composed/status-badge.js +29 -28
  66. package/dist/composed/status-badge.js.map +1 -1
  67. package/dist/motion/index.d.ts +1 -0
  68. package/dist/motion/index.d.ts.map +1 -1
  69. package/dist/motion/index.js +2 -15
  70. package/dist/motion/motion-preference.d.ts +36 -0
  71. package/dist/motion/motion-preference.d.ts.map +1 -0
  72. package/dist/motion/motion-provider.d.ts +2 -1
  73. package/dist/motion/motion-provider.d.ts.map +1 -1
  74. package/dist/motion/primitives-index.js +103 -102
  75. package/dist/motion/primitives-index.js.map +1 -1
  76. package/dist/motion/primitives.d.ts.map +1 -1
  77. package/dist/shell/bottom-navbar.js +19 -19
  78. package/dist/shell/bottom-navbar.js.map +1 -1
  79. package/dist/shell/index.d.ts +1 -1
  80. package/dist/shell/index.d.ts.map +1 -1
  81. package/dist/shell/notification-center.d.ts +17 -0
  82. package/dist/shell/notification-center.d.ts.map +1 -1
  83. package/dist/shell/notification-center.js +109 -104
  84. package/dist/shell/notification-center.js.map +1 -1
  85. package/dist/shell/notification-preferences.js +1 -1
  86. package/dist/shell/notification-preferences.js.map +1 -1
  87. package/dist/shell/top-bar.js +1 -1
  88. package/dist/shell/top-bar.js.map +1 -1
  89. package/dist/tokens/index.css +1 -0
  90. package/dist/tokens/palettes.css +292 -0
  91. package/dist/tokens/semantic.css +72 -8
  92. package/dist/tokens/shilp-sutra.css +1 -0
  93. package/dist/tokens/utilities.css +10 -7
  94. package/dist/ui/accordion.d.ts.map +1 -1
  95. package/dist/ui/accordion.js +27 -26
  96. package/dist/ui/accordion.js.map +1 -1
  97. package/dist/ui/alert-dialog.d.ts.map +1 -1
  98. package/dist/ui/alert-dialog.js +60 -59
  99. package/dist/ui/alert-dialog.js.map +1 -1
  100. package/dist/ui/alert.d.ts.map +1 -1
  101. package/dist/ui/alert.js +55 -113
  102. package/dist/ui/alert.js.map +1 -1
  103. package/dist/ui/autocomplete.js +80 -80
  104. package/dist/ui/autocomplete.js.map +1 -1
  105. package/dist/ui/avatar.d.ts.map +1 -1
  106. package/dist/ui/avatar.js +69 -68
  107. package/dist/ui/avatar.js.map +1 -1
  108. package/dist/ui/badge-indicator.d.ts +4 -8
  109. package/dist/ui/badge-indicator.d.ts.map +1 -1
  110. package/dist/ui/badge-indicator.js +3 -8
  111. package/dist/ui/badge-indicator.js.map +1 -1
  112. package/dist/ui/badge.d.ts +8 -1
  113. package/dist/ui/badge.d.ts.map +1 -1
  114. package/dist/ui/banner.d.ts.map +1 -1
  115. package/dist/ui/banner.js +42 -40
  116. package/dist/ui/banner.js.map +1 -1
  117. package/dist/ui/button-group.d.ts.map +1 -1
  118. package/dist/ui/button-group.js +3 -23
  119. package/dist/ui/button-group.js.map +1 -1
  120. package/dist/ui/button.d.ts +10 -1
  121. package/dist/ui/button.d.ts.map +1 -1
  122. package/dist/ui/button.js +79 -212
  123. package/dist/ui/button.js.map +1 -1
  124. package/dist/ui/card.d.ts.map +1 -1
  125. package/dist/ui/card.js +75 -72
  126. package/dist/ui/card.js.map +1 -1
  127. package/dist/ui/charts/radar-chart.d.ts.map +1 -1
  128. package/dist/ui/charts/radar-chart.js +1 -1
  129. package/dist/ui/charts/radar-chart.js.map +1 -1
  130. package/dist/ui/chat/message-list.d.ts.map +1 -1
  131. package/dist/ui/chat/message.d.ts.map +1 -1
  132. package/dist/ui/chat/typing-indicator.d.ts.map +1 -1
  133. package/dist/ui/code.js +1 -1
  134. package/dist/ui/code.js.map +1 -1
  135. package/dist/ui/color-input.d.ts.map +1 -1
  136. package/dist/ui/color-input.js +151 -150
  137. package/dist/ui/color-input.js.map +1 -1
  138. package/dist/ui/combobox.d.ts +14 -0
  139. package/dist/ui/combobox.d.ts.map +1 -1
  140. package/dist/ui/combobox.js +152 -148
  141. package/dist/ui/combobox.js.map +1 -1
  142. package/dist/ui/context-menu.d.ts.map +1 -1
  143. package/dist/ui/context-menu.js +70 -69
  144. package/dist/ui/context-menu.js.map +1 -1
  145. package/dist/ui/data-table-body.js +2 -2
  146. package/dist/ui/data-table-body.js.map +1 -1
  147. package/dist/ui/data-table-context.js +2 -2
  148. package/dist/ui/data-table-context.js.map +1 -1
  149. package/dist/ui/data-table-header.d.ts.map +1 -1
  150. package/dist/ui/data-table-header.js +41 -40
  151. package/dist/ui/data-table-header.js.map +1 -1
  152. package/dist/ui/data-table-pagination.js +2 -2
  153. package/dist/ui/data-table-pagination.js.map +1 -1
  154. package/dist/ui/dialog.d.ts.map +1 -1
  155. package/dist/ui/dialog.js +61 -60
  156. package/dist/ui/dialog.js.map +1 -1
  157. package/dist/ui/dot.d.ts.map +1 -1
  158. package/dist/ui/dot.js +10 -41
  159. package/dist/ui/dot.js.map +1 -1
  160. package/dist/ui/dropdown-menu.d.ts.map +1 -1
  161. package/dist/ui/dropdown-menu.js +79 -78
  162. package/dist/ui/dropdown-menu.js.map +1 -1
  163. package/dist/ui/file-upload.d.ts.map +1 -1
  164. package/dist/ui/file-upload.js +116 -115
  165. package/dist/ui/file-upload.js.map +1 -1
  166. package/dist/ui/form.d.ts.map +1 -1
  167. package/dist/ui/form.js +27 -26
  168. package/dist/ui/form.js.map +1 -1
  169. package/dist/ui/hover-card.d.ts.map +1 -1
  170. package/dist/ui/hover-card.js +26 -25
  171. package/dist/ui/hover-card.js.map +1 -1
  172. package/dist/ui/icon.js +7 -7
  173. package/dist/ui/index.d.ts +2 -2
  174. package/dist/ui/index.d.ts.map +1 -1
  175. package/dist/ui/index.js +3 -3
  176. package/dist/ui/index.js.map +1 -1
  177. package/dist/ui/input.js +1 -1
  178. package/dist/ui/input.js.map +1 -1
  179. package/dist/ui/menubar.d.ts.map +1 -1
  180. package/dist/ui/menubar.js +73 -72
  181. package/dist/ui/menubar.js.map +1 -1
  182. package/dist/ui/navigation-menu.d.ts.map +1 -1
  183. package/dist/ui/navigation-menu.js +80 -79
  184. package/dist/ui/navigation-menu.js.map +1 -1
  185. package/dist/ui/popover.d.ts.map +1 -1
  186. package/dist/ui/progress.d.ts.map +1 -1
  187. package/dist/ui/progress.js +72 -68
  188. package/dist/ui/progress.js.map +1 -1
  189. package/dist/ui/radio.d.ts.map +1 -1
  190. package/dist/ui/radio.js +32 -31
  191. package/dist/ui/radio.js.map +1 -1
  192. package/dist/ui/search-input.d.ts.map +1 -1
  193. package/dist/ui/search-input.js +28 -27
  194. package/dist/ui/search-input.js.map +1 -1
  195. package/dist/ui/select.d.ts.map +1 -1
  196. package/dist/ui/select.js +64 -63
  197. package/dist/ui/select.js.map +1 -1
  198. package/dist/ui/sidebar.d.ts.map +1 -1
  199. package/dist/ui/sidebar.js +4 -4
  200. package/dist/ui/sidebar.js.map +1 -1
  201. package/dist/ui/slider.d.ts +63 -9
  202. package/dist/ui/slider.d.ts.map +1 -1
  203. package/dist/ui/slider.js +74 -32
  204. package/dist/ui/slider.js.map +1 -1
  205. package/dist/ui/split-button.d.ts +8 -2
  206. package/dist/ui/split-button.d.ts.map +1 -1
  207. package/dist/ui/split-button.js +49 -95
  208. package/dist/ui/split-button.js.map +1 -1
  209. package/dist/ui/stat-card.d.ts.map +1 -1
  210. package/dist/ui/stat-card.js +89 -88
  211. package/dist/ui/stat-card.js.map +1 -1
  212. package/dist/ui/stat-flash.js +16 -16
  213. package/dist/ui/stepper.d.ts.map +1 -1
  214. package/dist/ui/stepper.js +63 -62
  215. package/dist/ui/stepper.js.map +1 -1
  216. package/dist/ui/table.js +3 -3
  217. package/dist/ui/table.js.map +1 -1
  218. package/dist/ui/tabs.d.ts.map +1 -1
  219. package/dist/ui/tabs.js +67 -66
  220. package/dist/ui/tabs.js.map +1 -1
  221. package/dist/ui/textarea.js +1 -1
  222. package/dist/ui/textarea.js.map +1 -1
  223. package/dist/ui/toast.d.ts.map +1 -1
  224. package/dist/ui/toast.js +195 -194
  225. package/dist/ui/toast.js.map +1 -1
  226. package/dist/ui/toggle.d.ts.map +1 -1
  227. package/dist/ui/toggle.js +24 -22
  228. package/dist/ui/toggle.js.map +1 -1
  229. package/dist/ui/tooltip.d.ts.map +1 -1
  230. package/dist/ui/tooltip.js +37 -36
  231. package/dist/ui/tooltip.js.map +1 -1
  232. package/dist/ui/tree-view/tree-item.d.ts.map +1 -1
  233. package/docs/recipes/customize-brand.md +58 -0
  234. package/docs/recipes/install-astro.md +1 -1
  235. package/docs/recipes/install-next-app-router.md +1 -1
  236. package/docs/recipes/install-next-pages.md +22 -1
  237. package/docs/recipes/install-remix.md +1 -1
  238. package/docs/recipes/install-tanstack-start.md +1 -1
  239. package/docs/recipes/install-vite.md +2 -1
  240. package/docs/recipes/server-components.md +7 -4
  241. package/docs/recipes/troubleshoot.md +1 -0
  242. package/llms.txt +1 -1
  243. package/mcp-manifest.json +9 -4
  244. package/package.json +3 -2
  245. package/skill/SKILL.md +1 -1
  246. package/skill/references/components.md +1 -1
  247. package/skill/references/customize-brand.md +58 -0
  248. package/skill/references/server-components.md +7 -4
  249. package/skill/references/setup-astro.md +1 -1
  250. package/skill/references/setup-next-app-router.md +1 -1
  251. package/skill/references/setup-next-pages.md +22 -1
  252. package/skill/references/setup-remix.md +1 -1
  253. package/skill/references/setup-tanstack-start.md +1 -1
  254. package/skill/references/setup-vite.md +2 -1
  255. package/skill/references/troubleshoot.md +1 -0
  256. package/dist/motion/check-motion-provider.d.ts +0 -2
  257. package/dist/motion/check-motion-provider.d.ts.map +0 -1
  258. package/dist/motion/index.js.map +0 -1
@@ -1,127 +1,128 @@
1
1
  "use client";
2
2
  import { springs as e, tweens as t } from "./lib/motion.js";
3
- import { cn as n } from "./lib/utils.js";
4
- import { Spinner as r } from "./spinner.js";
5
- import { Icon as i } from "./icon.js";
6
- import * as a from "react";
7
- import { jsx as o, jsxs as s } from "react/jsx-runtime";
8
- import { IconCheck as c, IconPaperclip as l, IconUpload as u } from "@tabler/icons-react";
9
- import { AnimatePresence as d, motion as f } from "framer-motion";
3
+ import { MotionPreference as n } from "../_chunks/motion-preference.js";
4
+ import { cn as r } from "./lib/utils.js";
5
+ import { Spinner as i } from "./spinner.js";
6
+ import { Icon as a } from "./icon.js";
7
+ import * as o from "react";
8
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
9
+ import { IconCheck as l, IconPaperclip as u, IconUpload as d } from "@tabler/icons-react";
10
+ import { AnimatePresence as f, motion as p } from "framer-motion";
10
11
  //#region src/ui/file-upload.tsx
11
- var p = 10 * 1024 * 1024;
12
- function m(e, t) {
12
+ var m = 10 * 1024 * 1024;
13
+ function h(e, t) {
13
14
  return t.split(",").map((e) => e.trim()).some((t) => t.startsWith(".") ? e.name.toLowerCase().endsWith(t.toLowerCase()) : t.endsWith("/*") ? e.type.startsWith(t.slice(0, t.indexOf("/") + 1)) : e.type === t);
14
15
  }
15
- function h(e) {
16
+ function g(e) {
16
17
  return e < 1024 ? `${e} B` : e < 1024 * 1024 ? `${(e / 1024).toFixed(1)} KB` : `${(e / (1024 * 1024)).toFixed(1)} MB`;
17
18
  }
18
- var g = a.forwardRef(({ accept: g, maxSize: _ = p, multiple: v = !1, uploading: y = !1, progress: b = 0, onFiles: x, error: S, compact: C = !1, disabled: w = !1, className: T, label: E, sublabel: D, ...O }, k) => {
19
- let A = a.useRef(null), [j, M] = a.useState(!1), [N, P] = a.useState(null), F = a.useRef(!1), I = a.useRef(!1), L = S || N, R = a.useCallback(() => {
20
- w || y || A.current?.click();
21
- }, [w, y]), z = a.useCallback((e) => {
22
- P(null);
19
+ var _ = o.forwardRef(({ accept: _, maxSize: v = m, multiple: y = !1, uploading: b = !1, progress: x = 0, onFiles: S, error: C, compact: w = !1, disabled: T = !1, className: E, label: D, sublabel: O, ...k }, A) => {
20
+ let j = o.useRef(null), [M, N] = o.useState(!1), [P, F] = o.useState(null), I = o.useRef(!1), L = o.useRef(!1), R = C || P, z = o.useCallback(() => {
21
+ T || b || j.current?.click();
22
+ }, [T, b]), B = o.useCallback((e) => {
23
+ F(null);
23
24
  for (let t of e) {
24
- if (t.size > _) {
25
- P(`File "${t.name}" exceeds the maximum size of ${h(_)}`);
25
+ if (t.size > v) {
26
+ F(`File "${t.name}" exceeds the maximum size of ${g(v)}`);
26
27
  return;
27
28
  }
28
- if (g && !m(t, g)) {
29
- P(`File "${t.name}" is not an accepted file type`);
29
+ if (_ && !h(t, _)) {
30
+ F(`File "${t.name}" is not an accepted file type`);
30
31
  return;
31
32
  }
32
33
  }
33
- x(e);
34
+ S(e);
34
35
  }, [
36
+ v,
35
37
  _,
36
- g,
37
- x
38
- ]), B = a.useCallback((e) => {
39
- I.current = !0;
38
+ S
39
+ ]), V = o.useCallback((e) => {
40
+ L.current = !0;
40
41
  let t = e.target.files;
41
- !t || t.length === 0 || (z(Array.from(t)), e.target.value = "");
42
- }, [z]), V = a.useCallback(() => {
43
- F.current = !0, I.current = !1;
44
- }, []), H = a.useCallback(() => {
45
- F.current && !I.current && g && P("File type is not accepted"), F.current = !1;
46
- }, [g]), U = a.useCallback((e) => {
47
- e.preventDefault(), e.stopPropagation(), !w && !y && M(!0);
48
- }, [w, y]), W = a.useCallback((e) => {
49
- e.preventDefault(), e.stopPropagation(), !w && !y && M(!0);
50
- }, [w, y]), G = a.useCallback((e) => {
51
- e.preventDefault(), e.stopPropagation(), M(!1);
52
- }, []), K = a.useCallback((e) => {
53
- if (e.preventDefault(), e.stopPropagation(), M(!1), w || y) return;
42
+ !t || t.length === 0 || (B(Array.from(t)), e.target.value = "");
43
+ }, [B]), H = o.useCallback(() => {
44
+ I.current = !0, L.current = !1;
45
+ }, []), U = o.useCallback(() => {
46
+ I.current && !L.current && _ && F("File type is not accepted"), I.current = !1;
47
+ }, [_]), W = o.useCallback((e) => {
48
+ e.preventDefault(), e.stopPropagation(), !T && !b && N(!0);
49
+ }, [T, b]), G = o.useCallback((e) => {
50
+ e.preventDefault(), e.stopPropagation(), !T && !b && N(!0);
51
+ }, [T, b]), K = o.useCallback((e) => {
52
+ e.preventDefault(), e.stopPropagation(), N(!1);
53
+ }, []), q = o.useCallback((e) => {
54
+ if (e.preventDefault(), e.stopPropagation(), N(!1), T || b) return;
54
55
  let t = e.dataTransfer.files;
55
- !t || t.length === 0 || z(Array.from(t));
56
+ !t || t.length === 0 || B(Array.from(t));
56
57
  }, [
57
- w,
58
- y,
59
- z
60
- ]), q = E ?? "Drop files here or click to browse", J = D ?? `Max file size: ${h(_)}`, Y = a.useId();
61
- return C ? /* @__PURE__ */ s("div", {
62
- ref: k,
63
- className: n("inline-flex flex-col", T),
64
- ...O,
58
+ T,
59
+ b,
60
+ B
61
+ ]), J = D ?? "Drop files here or click to browse", Y = O ?? `Max file size: ${g(v)}`, X = o.useId();
62
+ return w ? /* @__PURE__ */ c("div", {
63
+ ref: A,
64
+ className: r("inline-flex flex-col", E),
65
+ ...k,
65
66
  children: [
66
- /* @__PURE__ */ s("button", {
67
+ /* @__PURE__ */ c("button", {
67
68
  type: "button",
68
- onClick: R,
69
- disabled: w,
70
- className: n("inline-flex items-center gap-ds-02 rounded-control px-ds-03 py-ds-02", "text-body-sm font-medium", "border border-surface-border-interactive", "bg-surface-panel-hover text-surface-fg-muted", "hover:bg-accent-2", "disabled:opacity-action-disabled disabled:cursor-not-allowed", "transition-colors"),
71
- children: [y ? /* @__PURE__ */ o(r, { size: "sm" }) : /* @__PURE__ */ o(i, {
72
- icon: l,
69
+ onClick: z,
70
+ disabled: T,
71
+ className: r("inline-flex items-center gap-ds-02 rounded-control px-ds-03 py-ds-02", "text-body-sm font-medium", "border border-surface-border-interactive", "bg-surface-panel-hover text-surface-fg-muted", "hover:bg-accent-4", "disabled:opacity-action-disabled disabled:cursor-not-allowed", "transition-colors"),
72
+ children: [b ? /* @__PURE__ */ s(i, { size: "sm" }) : /* @__PURE__ */ s(a, {
73
+ icon: u,
73
74
  size: "sm"
74
- }), E ?? "Attach files"]
75
+ }), D ?? "Attach files"]
75
76
  }),
76
- /* @__PURE__ */ o("input", {
77
- ref: A,
78
- id: Y,
77
+ /* @__PURE__ */ s("input", {
78
+ ref: j,
79
+ id: X,
79
80
  type: "file",
80
81
  className: "sr-only",
81
82
  style: { visibility: "hidden" },
82
83
  "aria-hidden": "true",
83
- "aria-label": E ?? "Attach files",
84
- accept: g,
85
- multiple: v,
86
- disabled: w,
87
- onChange: B,
88
- onClick: V,
89
- onFocus: H,
84
+ "aria-label": D ?? "Attach files",
85
+ accept: _,
86
+ multiple: y,
87
+ disabled: T,
88
+ onChange: V,
89
+ onClick: H,
90
+ onFocus: U,
90
91
  tabIndex: -1
91
92
  }),
92
- L && /* @__PURE__ */ o("p", {
93
+ R && /* @__PURE__ */ s("p", {
93
94
  role: "alert",
94
95
  "aria-live": "polite",
95
96
  className: "mt-ds-02 text-body-xs text-error-11",
96
- children: L
97
+ children: R
97
98
  })
98
99
  ]
99
- }) : /* @__PURE__ */ s("div", {
100
- ref: k,
101
- ...O,
100
+ }) : /* @__PURE__ */ s(n, { children: /* @__PURE__ */ c("div", {
101
+ ref: A,
102
+ ...k,
102
103
  role: "presentation",
103
- className: n("flex flex-col", T),
104
- "data-drag-active": j ? "true" : void 0,
105
- onDragEnter: U,
106
- onDragOver: W,
107
- onDragLeave: G,
108
- onDrop: K,
104
+ className: r("flex flex-col", E),
105
+ "data-drag-active": M ? "true" : void 0,
106
+ onDragEnter: W,
107
+ onDragOver: G,
108
+ onDragLeave: K,
109
+ onDrop: q,
109
110
  children: [
110
- /* @__PURE__ */ s(f.div, {
111
+ /* @__PURE__ */ c(p.div, {
111
112
  role: "button",
112
- tabIndex: w ? -1 : 0,
113
- "aria-disabled": w || void 0,
114
- onClick: w ? void 0 : R,
113
+ tabIndex: T ? -1 : 0,
114
+ "aria-disabled": T || void 0,
115
+ onClick: T ? void 0 : z,
115
116
  onKeyDown: (e) => {
116
- !w && (e.key === "Enter" || e.key === " ") && (e.preventDefault(), R());
117
+ !T && (e.key === "Enter" || e.key === " ") && (e.preventDefault(), z());
117
118
  },
118
- className: n("flex flex-col items-center justify-center gap-ds-03 rounded-surface", "border-2 border-dashed p-ds-08", "focus-ring transition-colors duration-fast-02 ease-productive-standard", "border-surface-border-interactive bg-surface-base", w ? "cursor-not-allowed opacity-action-disabled" : "cursor-pointer hover:bg-surface-panel-hover", j && "border-accent-7 bg-accent-2"),
119
- animate: { scale: j ? 1.02 : 1 },
119
+ className: r("flex flex-col items-center justify-center gap-ds-03 rounded-surface", "border-2 border-dashed p-ds-08", "focus-ring transition-colors duration-fast-02 ease-productive-standard", "border-surface-border-interactive bg-surface-base", T ? "cursor-not-allowed opacity-action-disabled" : "cursor-pointer hover:bg-surface-panel-hover", M && "border-accent-7 bg-accent-2"),
120
+ animate: { scale: M ? 1.02 : 1 },
120
121
  transition: e.snappy,
121
122
  children: [
122
- /* @__PURE__ */ o(d, {
123
+ /* @__PURE__ */ s(f, {
123
124
  mode: "wait",
124
- children: y ? /* @__PURE__ */ o(f.div, {
125
+ children: b ? /* @__PURE__ */ s(p.div, {
125
126
  initial: {
126
127
  opacity: 0,
127
128
  scale: .5
@@ -135,8 +136,8 @@ var g = a.forwardRef(({ accept: g, maxSize: _ = p, multiple: v = !1, uploading:
135
136
  scale: .5
136
137
  },
137
138
  transition: t.fade,
138
- children: /* @__PURE__ */ o(r, { size: "md" })
139
- }, "spinner") : b === 100 && !S ? /* @__PURE__ */ o(f.div, {
139
+ children: /* @__PURE__ */ s(i, { size: "md" })
140
+ }, "spinner") : x === 100 && !C ? /* @__PURE__ */ s(p.div, {
140
141
  initial: {
141
142
  opacity: 0,
142
143
  scale: .5
@@ -150,65 +151,65 @@ var g = a.forwardRef(({ accept: g, maxSize: _ = p, multiple: v = !1, uploading:
150
151
  scale: .5
151
152
  },
152
153
  transition: e.bouncy,
153
- children: /* @__PURE__ */ o(i, {
154
- icon: c,
154
+ children: /* @__PURE__ */ s(a, {
155
+ icon: l,
155
156
  size: "2xl",
156
157
  className: "text-success-11"
157
158
  })
158
- }, "complete") : /* @__PURE__ */ o(f.div, {
159
+ }, "complete") : /* @__PURE__ */ s(p.div, {
159
160
  initial: { opacity: 0 },
160
161
  animate: { opacity: 1 },
161
162
  exit: { opacity: 0 },
162
163
  transition: t.fade,
163
- children: /* @__PURE__ */ o(i, {
164
- icon: u,
164
+ children: /* @__PURE__ */ s(a, {
165
+ icon: d,
165
166
  size: "2xl",
166
167
  className: "text-surface-fg-subtle"
167
168
  })
168
169
  }, "upload")
169
170
  }),
170
- /* @__PURE__ */ o("span", {
171
- id: Y + "-label",
171
+ /* @__PURE__ */ s("span", {
172
+ id: X + "-label",
172
173
  className: "text-body-sm text-surface-fg-muted",
173
- children: q
174
+ children: J
174
175
  }),
175
- y ? /* @__PURE__ */ o("div", {
176
+ b ? /* @__PURE__ */ s("div", {
176
177
  className: "w-full max-w-xs",
177
- children: /* @__PURE__ */ o("div", {
178
+ children: /* @__PURE__ */ s("div", {
178
179
  role: "progressbar",
179
- "aria-valuenow": b,
180
+ "aria-valuenow": x,
180
181
  "aria-valuemin": 0,
181
182
  "aria-valuemax": 100,
182
183
  className: "h-2 w-full overflow-hidden rounded-pill bg-surface-panel-hover",
183
- children: /* @__PURE__ */ o(f.div, {
184
+ children: /* @__PURE__ */ s(p.div, {
184
185
  className: "h-full w-full origin-left rounded-pill bg-accent-9",
185
- animate: { scaleX: Math.max(0, Math.min(100, b)) / 100 },
186
+ animate: { scaleX: Math.max(0, Math.min(100, x)) / 100 },
186
187
  transition: e.smooth
187
188
  })
188
189
  })
189
- }) : /* @__PURE__ */ o("span", {
190
+ }) : /* @__PURE__ */ s("span", {
190
191
  className: "text-caption text-surface-fg-subtle",
191
- children: J
192
+ children: Y
192
193
  })
193
194
  ]
194
195
  }),
195
- /* @__PURE__ */ o("input", {
196
- ref: A,
197
- id: Y,
196
+ /* @__PURE__ */ s("input", {
197
+ ref: j,
198
+ id: X,
198
199
  type: "file",
199
200
  className: "sr-only",
200
201
  style: { visibility: "hidden" },
201
202
  "aria-hidden": "true",
202
- "aria-label": q,
203
- accept: g,
204
- multiple: v,
205
- disabled: w,
206
- onChange: B,
207
- onClick: V,
208
- onFocus: H,
203
+ "aria-label": J,
204
+ accept: _,
205
+ multiple: y,
206
+ disabled: T,
207
+ onChange: V,
208
+ onClick: H,
209
+ onFocus: U,
209
210
  tabIndex: -1
210
211
  }),
211
- /* @__PURE__ */ o(d, { children: L && /* @__PURE__ */ o(f.p, {
212
+ /* @__PURE__ */ s(f, { children: R && /* @__PURE__ */ s(p.p, {
212
213
  role: "alert",
213
214
  className: "mt-ds-02 text-body-xs text-error-11",
214
215
  initial: {
@@ -221,11 +222,11 @@ var g = a.forwardRef(({ accept: g, maxSize: _ = p, multiple: v = !1, uploading:
221
222
  },
222
223
  exit: { opacity: 0 },
223
224
  transition: t.fade,
224
- children: L
225
+ children: R
225
226
  }) })
226
227
  ]
227
- });
228
+ }) });
228
229
  });
229
- g.displayName = "FileUpload";
230
+ _.displayName = "FileUpload";
230
231
  //#endregion
231
- export { g as FileUpload };
232
+ export { _ as FileUpload };
@@ -1 +1 @@
1
- {"version":3,"file":"file-upload.js","names":[],"sources":["../../src/ui/file-upload.tsx"],"sourcesContent":["'use client'\n\nimport { IconCheck,IconPaperclip, IconUpload } from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Icon } from './icon'\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\nimport { Spinner } from './spinner'\n\n/**\n * Props for FileUpload — a drag-and-drop file picker with client-side validation (type + size),\n * an upload progress bar, and a compact inline mode.\n *\n * **Two modes:**\n * - Default: large drop zone with an upload icon, label, and sublabel — good for primary upload areas.\n * - `compact={true}`: a small inline button with an attachment icon — good for secondary/inline uploads.\n *\n * **Validation:** Pass `accept` (MIME or extension) and `maxSize` (bytes). Errors are shown inline.\n * The `onFiles` callback only fires with valid files — invalid files are rejected with an error message.\n *\n * **`onFiles` required:** This is the only required prop. Integrating with a real uploader means\n * calling your upload API from this callback and driving `uploading` + `progress` externally.\n *\n * @example\n * // Avatar upload with image-only restriction:\n * <FileUpload\n * accept=\"image/*\"\n * maxSize={2 * 1024 * 1024}\n * onFiles={(files) => uploadAvatar(files[0])}\n * label=\"Upload profile photo\"\n * sublabel=\"PNG, JPG up to 2MB\"\n * />\n *\n * @example\n * // Document multi-upload with progress:\n * <FileUpload\n * multiple\n * accept=\".pdf,.doc,.docx\"\n * uploading={isUploading}\n * progress={uploadPercent}\n * onFiles={handleFiles}\n * />\n *\n * @example\n * // Compact inline attachment button in a comment form:\n * <FileUpload compact onFiles={(files) => attachFiles(files)} label=\"Attach files\" />\n * // These are just a few ways — feel free to combine props creatively!\n */\nexport interface FileUploadProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onError'> {\n /** Accepted file types (e.g., \"image/*\", \".pdf,.doc\") */\n accept?: string\n /** Max file size in bytes (default: 10MB) */\n maxSize?: number\n /** Allow multiple files */\n multiple?: boolean\n /** Currently uploading */\n uploading?: boolean\n /** Upload progress (0-100) */\n progress?: number\n /** Callback when files are selected/dropped */\n onFiles: (files: File[]) => void\n /** Error message to display */\n error?: string\n /** Compact mode (inline button instead of drop zone) */\n compact?: boolean\n /** Disabled */\n disabled?: boolean\n /** Custom label */\n label?: string\n /** Custom sublabel */\n sublabel?: string\n}\n\nconst DEFAULT_MAX_SIZE = 10 * 1024 * 1024 // 10MB\n\nfunction fileMatchesAccept(file: File, accept: string): boolean {\n return accept\n .split(',')\n .map((t) => t.trim())\n .some((type) => {\n if (type.startsWith('.'))\n return file.name.toLowerCase().endsWith(type.toLowerCase())\n if (type.endsWith('/*'))\n return file.type.startsWith(type.slice(0, type.indexOf('/') + 1))\n return file.type === type\n })\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\nconst FileUpload = React.forwardRef<HTMLDivElement, FileUploadProps>(\n (\n {\n accept,\n maxSize = DEFAULT_MAX_SIZE,\n multiple = false,\n uploading = false,\n progress = 0,\n onFiles,\n error,\n compact = false,\n disabled = false,\n className,\n label,\n sublabel,\n ...props\n },\n ref,\n ) => {\n const inputRef = React.useRef<HTMLInputElement>(null)\n const [isDragActive, setIsDragActive] = React.useState(false)\n const [validationError, setValidationError] = React.useState<string | null>(\n null,\n )\n // Track whether a file dialog was opened, so we can detect when the\n // browser (or testing library) filters out all files via the accept attribute\n const dialogOpenRef = React.useRef(false)\n const filesAcceptedRef = React.useRef(false)\n\n const displayError = error || validationError\n\n const openPicker = React.useCallback(() => {\n if (disabled || uploading) return\n inputRef.current?.click()\n }, [disabled, uploading])\n\n const validateAndEmit = React.useCallback(\n (files: File[]) => {\n setValidationError(null)\n\n for (const file of files) {\n if (file.size > maxSize) {\n setValidationError(\n `File \"${file.name}\" exceeds the maximum size of ${formatBytes(maxSize)}`,\n )\n return\n }\n if (accept && !fileMatchesAccept(file, accept)) {\n setValidationError(\n `File \"${file.name}\" is not an accepted file type`,\n )\n return\n }\n }\n\n onFiles(files)\n },\n [maxSize, accept, onFiles],\n )\n\n const handleInputChange = React.useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n filesAcceptedRef.current = true\n const fileList = e.target.files\n if (!fileList || fileList.length === 0) return\n validateAndEmit(Array.from(fileList))\n // Reset input value so the same file can be selected again\n e.target.value = ''\n },\n [validateAndEmit],\n )\n\n // When the file input is clicked, the browser opens a dialog and blurs the input.\n // We track this to detect when the dialog closes without selecting valid files.\n const handleInputClick = React.useCallback(() => {\n dialogOpenRef.current = true\n filesAcceptedRef.current = false\n }, [])\n\n // After the file dialog closes, the browser re-focuses the input.\n // If no files were accepted (e.g., the browser/test library filtered by accept),\n // and the accept prop is set, show a validation error.\n const handleInputFocus = React.useCallback(() => {\n if (dialogOpenRef.current && !filesAcceptedRef.current && accept) {\n setValidationError('File type is not accepted')\n }\n dialogOpenRef.current = false\n }, [accept])\n\n const handleDragEnter = React.useCallback(\n (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n e.stopPropagation()\n if (!disabled && !uploading) {\n setIsDragActive(true)\n }\n },\n [disabled, uploading],\n )\n\n const handleDragOver = React.useCallback(\n (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n e.stopPropagation()\n if (!disabled && !uploading) {\n setIsDragActive(true)\n }\n },\n [disabled, uploading],\n )\n\n const handleDragLeave = React.useCallback(\n (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setIsDragActive(false)\n },\n [],\n )\n\n const handleDrop = React.useCallback(\n (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setIsDragActive(false)\n\n if (disabled || uploading) return\n\n const fileList = e.dataTransfer.files\n if (!fileList || fileList.length === 0) return\n validateAndEmit(Array.from(fileList))\n },\n [disabled, uploading, validateAndEmit],\n )\n\n const defaultLabel = label ?? 'Drop files here or click to browse'\n const defaultSublabel = sublabel ?? `Max file size: ${formatBytes(maxSize)}`\n const inputId = React.useId()\n\n if (compact) {\n return (\n <div ref={ref} className={cn('inline-flex flex-col', className)} {...props}>\n <button\n type=\"button\"\n onClick={openPicker}\n disabled={disabled}\n className={cn(\n 'inline-flex items-center gap-ds-02 rounded-control px-ds-03 py-ds-02',\n 'text-body-sm font-medium',\n 'border border-surface-border-interactive',\n 'bg-surface-panel-hover text-surface-fg-muted',\n 'hover:bg-accent-2',\n 'disabled:opacity-action-disabled disabled:cursor-not-allowed',\n 'transition-colors',\n )}\n >\n {uploading ? (\n <Spinner size=\"sm\" />\n ) : (\n <Icon icon={IconPaperclip} size=\"sm\" />\n )}\n {label ?? 'Attach files'}\n </button>\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n className=\"sr-only\"\n style={{ visibility: 'hidden' }}\n aria-hidden=\"true\"\n aria-label={label ?? 'Attach files'}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n onChange={handleInputChange}\n onClick={handleInputClick}\n onFocus={handleInputFocus}\n tabIndex={-1}\n />\n {displayError && (\n <p\n role=\"alert\"\n aria-live=\"polite\"\n className=\"mt-ds-02 text-body-xs text-error-11\"\n >\n {displayError}\n </p>\n )}\n </div>\n )\n }\n\n return (\n <div\n ref={ref}\n {...props}\n role=\"presentation\"\n className={cn('flex flex-col', className)}\n data-drag-active={isDragActive ? 'true' : undefined}\n onDragEnter={handleDragEnter}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n >\n <motion.div\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n aria-disabled={disabled || undefined}\n onClick={disabled ? undefined : openPicker}\n onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => {\n if (!disabled && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault()\n openPicker()\n }\n }}\n className={cn(\n 'flex flex-col items-center justify-center gap-ds-03 rounded-surface',\n 'border-2 border-dashed p-ds-08',\n 'focus-ring transition-colors duration-fast-02 ease-productive-standard',\n // Rest on the canvas fill; reserve the hover token for actual hover.\n 'border-surface-border-interactive bg-surface-base',\n disabled ? 'cursor-not-allowed opacity-action-disabled' : 'cursor-pointer hover:bg-surface-panel-hover',\n isDragActive && 'border-accent-7 bg-accent-2',\n )}\n animate={{\n scale: isDragActive ? 1.02 : 1,\n }}\n transition={springs.snappy}\n >\n {/* Icon — animates between upload ↔ spinner ↔ checkmark */}\n <AnimatePresence mode=\"wait\">\n {uploading ? (\n <motion.div\n key=\"spinner\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={tweens.fade}\n >\n <Spinner size=\"md\" />\n </motion.div>\n ) : progress === 100 && !error ? (\n <motion.div\n key=\"complete\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n >\n <Icon icon={IconCheck} size=\"2xl\" className=\"text-success-11\" />\n </motion.div>\n ) : (\n <motion.div\n key=\"upload\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Icon icon={IconUpload} size=\"2xl\" className=\"text-surface-fg-subtle\" />\n </motion.div>\n )}\n </AnimatePresence>\n <span id={inputId + '-label'} className=\"text-body-sm text-surface-fg-muted\">\n {defaultLabel}\n </span>\n {uploading ? (\n <div className=\"w-full max-w-xs\">\n <div\n role=\"progressbar\"\n aria-valuenow={progress}\n aria-valuemin={0}\n aria-valuemax={100}\n className=\"h-2 w-full overflow-hidden rounded-pill bg-surface-panel-hover\"\n >\n <motion.div\n // scaleX (compositor-only) not width (layout) → smooth + honored by reduced-motion.\n className=\"h-full w-full origin-left rounded-pill bg-accent-9\"\n animate={{ scaleX: Math.max(0, Math.min(100, progress)) / 100 }}\n transition={springs.smooth}\n />\n </div>\n </div>\n ) : (\n <span className=\"text-caption text-surface-fg-subtle\">\n {defaultSublabel}\n </span>\n )}\n </motion.div>\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n className=\"sr-only\"\n style={{ visibility: 'hidden' }}\n aria-hidden=\"true\"\n aria-label={defaultLabel}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n onChange={handleInputChange}\n onClick={handleInputClick}\n onFocus={handleInputFocus}\n tabIndex={-1}\n />\n {/* Error message — a calm fade/slide in (no decorative shake). */}\n <AnimatePresence>\n {displayError && (\n <motion.p\n role=\"alert\"\n className=\"mt-ds-02 text-body-xs text-error-11\"\n initial={{ opacity: 0, y: -2 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n {displayError}\n </motion.p>\n )}\n </AnimatePresence>\n </div>\n )\n },\n)\nFileUpload.displayName = 'FileUpload'\n\nexport { FileUpload }\n"],"mappings":"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"}
1
+ {"version":3,"file":"file-upload.js","names":[],"sources":["../../src/ui/file-upload.tsx"],"sourcesContent":["'use client'\n\nimport { IconCheck,IconPaperclip, IconUpload } from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { MotionPreference } from '../motion/motion-preference'\nimport { Icon } from './icon'\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\nimport { Spinner } from './spinner'\n\n/**\n * Props for FileUpload — a drag-and-drop file picker with client-side validation (type + size),\n * an upload progress bar, and a compact inline mode.\n *\n * **Two modes:**\n * - Default: large drop zone with an upload icon, label, and sublabel — good for primary upload areas.\n * - `compact={true}`: a small inline button with an attachment icon — good for secondary/inline uploads.\n *\n * **Validation:** Pass `accept` (MIME or extension) and `maxSize` (bytes). Errors are shown inline.\n * The `onFiles` callback only fires with valid files — invalid files are rejected with an error message.\n *\n * **`onFiles` required:** This is the only required prop. Integrating with a real uploader means\n * calling your upload API from this callback and driving `uploading` + `progress` externally.\n *\n * @example\n * // Avatar upload with image-only restriction:\n * <FileUpload\n * accept=\"image/*\"\n * maxSize={2 * 1024 * 1024}\n * onFiles={(files) => uploadAvatar(files[0])}\n * label=\"Upload profile photo\"\n * sublabel=\"PNG, JPG up to 2MB\"\n * />\n *\n * @example\n * // Document multi-upload with progress:\n * <FileUpload\n * multiple\n * accept=\".pdf,.doc,.docx\"\n * uploading={isUploading}\n * progress={uploadPercent}\n * onFiles={handleFiles}\n * />\n *\n * @example\n * // Compact inline attachment button in a comment form:\n * <FileUpload compact onFiles={(files) => attachFiles(files)} label=\"Attach files\" />\n * // These are just a few ways — feel free to combine props creatively!\n */\nexport interface FileUploadProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onError'> {\n /** Accepted file types (e.g., \"image/*\", \".pdf,.doc\") */\n accept?: string\n /** Max file size in bytes (default: 10MB) */\n maxSize?: number\n /** Allow multiple files */\n multiple?: boolean\n /** Currently uploading */\n uploading?: boolean\n /** Upload progress (0-100) */\n progress?: number\n /** Callback when files are selected/dropped */\n onFiles: (files: File[]) => void\n /** Error message to display */\n error?: string\n /** Compact mode (inline button instead of drop zone) */\n compact?: boolean\n /** Disabled */\n disabled?: boolean\n /** Custom label */\n label?: string\n /** Custom sublabel */\n sublabel?: string\n}\n\nconst DEFAULT_MAX_SIZE = 10 * 1024 * 1024 // 10MB\n\nfunction fileMatchesAccept(file: File, accept: string): boolean {\n return accept\n .split(',')\n .map((t) => t.trim())\n .some((type) => {\n if (type.startsWith('.'))\n return file.name.toLowerCase().endsWith(type.toLowerCase())\n if (type.endsWith('/*'))\n return file.type.startsWith(type.slice(0, type.indexOf('/') + 1))\n return file.type === type\n })\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\nconst FileUpload = React.forwardRef<HTMLDivElement, FileUploadProps>(\n (\n {\n accept,\n maxSize = DEFAULT_MAX_SIZE,\n multiple = false,\n uploading = false,\n progress = 0,\n onFiles,\n error,\n compact = false,\n disabled = false,\n className,\n label,\n sublabel,\n ...props\n },\n ref,\n ) => {\n const inputRef = React.useRef<HTMLInputElement>(null)\n const [isDragActive, setIsDragActive] = React.useState(false)\n const [validationError, setValidationError] = React.useState<string | null>(\n null,\n )\n // Track whether a file dialog was opened, so we can detect when the\n // browser (or testing library) filters out all files via the accept attribute\n const dialogOpenRef = React.useRef(false)\n const filesAcceptedRef = React.useRef(false)\n\n const displayError = error || validationError\n\n const openPicker = React.useCallback(() => {\n if (disabled || uploading) return\n inputRef.current?.click()\n }, [disabled, uploading])\n\n const validateAndEmit = React.useCallback(\n (files: File[]) => {\n setValidationError(null)\n\n for (const file of files) {\n if (file.size > maxSize) {\n setValidationError(\n `File \"${file.name}\" exceeds the maximum size of ${formatBytes(maxSize)}`,\n )\n return\n }\n if (accept && !fileMatchesAccept(file, accept)) {\n setValidationError(\n `File \"${file.name}\" is not an accepted file type`,\n )\n return\n }\n }\n\n onFiles(files)\n },\n [maxSize, accept, onFiles],\n )\n\n const handleInputChange = React.useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n filesAcceptedRef.current = true\n const fileList = e.target.files\n if (!fileList || fileList.length === 0) return\n validateAndEmit(Array.from(fileList))\n // Reset input value so the same file can be selected again\n e.target.value = ''\n },\n [validateAndEmit],\n )\n\n // When the file input is clicked, the browser opens a dialog and blurs the input.\n // We track this to detect when the dialog closes without selecting valid files.\n const handleInputClick = React.useCallback(() => {\n dialogOpenRef.current = true\n filesAcceptedRef.current = false\n }, [])\n\n // After the file dialog closes, the browser re-focuses the input.\n // If no files were accepted (e.g., the browser/test library filtered by accept),\n // and the accept prop is set, show a validation error.\n const handleInputFocus = React.useCallback(() => {\n if (dialogOpenRef.current && !filesAcceptedRef.current && accept) {\n setValidationError('File type is not accepted')\n }\n dialogOpenRef.current = false\n }, [accept])\n\n const handleDragEnter = React.useCallback(\n (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n e.stopPropagation()\n if (!disabled && !uploading) {\n setIsDragActive(true)\n }\n },\n [disabled, uploading],\n )\n\n const handleDragOver = React.useCallback(\n (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n e.stopPropagation()\n if (!disabled && !uploading) {\n setIsDragActive(true)\n }\n },\n [disabled, uploading],\n )\n\n const handleDragLeave = React.useCallback(\n (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setIsDragActive(false)\n },\n [],\n )\n\n const handleDrop = React.useCallback(\n (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setIsDragActive(false)\n\n if (disabled || uploading) return\n\n const fileList = e.dataTransfer.files\n if (!fileList || fileList.length === 0) return\n validateAndEmit(Array.from(fileList))\n },\n [disabled, uploading, validateAndEmit],\n )\n\n const defaultLabel = label ?? 'Drop files here or click to browse'\n const defaultSublabel = sublabel ?? `Max file size: ${formatBytes(maxSize)}`\n const inputId = React.useId()\n\n if (compact) {\n return (\n <div ref={ref} className={cn('inline-flex flex-col', className)} {...props}>\n <button\n type=\"button\"\n onClick={openPicker}\n disabled={disabled}\n className={cn(\n 'inline-flex items-center gap-ds-02 rounded-control px-ds-03 py-ds-02',\n 'text-body-sm font-medium',\n 'border border-surface-border-interactive',\n 'bg-surface-panel-hover text-surface-fg-muted',\n // step 4: the resting fill is surface-panel-hover, which sits\n // ABOVE accent-3 in dark — hovering made the button darker.\n 'hover:bg-accent-4',\n 'disabled:opacity-action-disabled disabled:cursor-not-allowed',\n 'transition-colors',\n )}\n >\n {uploading ? (\n <Spinner size=\"sm\" />\n ) : (\n <Icon icon={IconPaperclip} size=\"sm\" />\n )}\n {label ?? 'Attach files'}\n </button>\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n className=\"sr-only\"\n style={{ visibility: 'hidden' }}\n aria-hidden=\"true\"\n aria-label={label ?? 'Attach files'}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n onChange={handleInputChange}\n onClick={handleInputClick}\n onFocus={handleInputFocus}\n tabIndex={-1}\n />\n {displayError && (\n <p\n role=\"alert\"\n aria-live=\"polite\"\n className=\"mt-ds-02 text-body-xs text-error-11\"\n >\n {displayError}\n </p>\n )}\n </div>\n )\n }\n\n return (\n <MotionPreference>\n <div\n ref={ref}\n {...props}\n role=\"presentation\"\n className={cn('flex flex-col', className)}\n data-drag-active={isDragActive ? 'true' : undefined}\n onDragEnter={handleDragEnter}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n >\n <motion.div\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n aria-disabled={disabled || undefined}\n onClick={disabled ? undefined : openPicker}\n onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => {\n if (!disabled && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault()\n openPicker()\n }\n }}\n className={cn(\n 'flex flex-col items-center justify-center gap-ds-03 rounded-surface',\n 'border-2 border-dashed p-ds-08',\n 'focus-ring transition-colors duration-fast-02 ease-productive-standard',\n // Rest on the canvas fill; reserve the hover token for actual hover.\n 'border-surface-border-interactive bg-surface-base',\n disabled ? 'cursor-not-allowed opacity-action-disabled' : 'cursor-pointer hover:bg-surface-panel-hover',\n isDragActive && 'border-accent-7 bg-accent-2',\n )}\n animate={{\n scale: isDragActive ? 1.02 : 1,\n }}\n transition={springs.snappy}\n >\n {/* Icon — animates between upload ↔ spinner ↔ checkmark */}\n <AnimatePresence mode=\"wait\">\n {uploading ? (\n <motion.div\n key=\"spinner\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={tweens.fade}\n >\n <Spinner size=\"md\" />\n </motion.div>\n ) : progress === 100 && !error ? (\n <motion.div\n key=\"complete\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n >\n <Icon icon={IconCheck} size=\"2xl\" className=\"text-success-11\" />\n </motion.div>\n ) : (\n <motion.div\n key=\"upload\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Icon icon={IconUpload} size=\"2xl\" className=\"text-surface-fg-subtle\" />\n </motion.div>\n )}\n </AnimatePresence>\n <span id={inputId + '-label'} className=\"text-body-sm text-surface-fg-muted\">\n {defaultLabel}\n </span>\n {uploading ? (\n <div className=\"w-full max-w-xs\">\n <div\n role=\"progressbar\"\n aria-valuenow={progress}\n aria-valuemin={0}\n aria-valuemax={100}\n className=\"h-2 w-full overflow-hidden rounded-pill bg-surface-panel-hover\"\n >\n <motion.div\n // scaleX (compositor-only) not width (layout) → smooth + honored by reduced-motion.\n className=\"h-full w-full origin-left rounded-pill bg-accent-9\"\n animate={{ scaleX: Math.max(0, Math.min(100, progress)) / 100 }}\n transition={springs.smooth}\n />\n </div>\n </div>\n ) : (\n <span className=\"text-caption text-surface-fg-subtle\">\n {defaultSublabel}\n </span>\n )}\n </motion.div>\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n className=\"sr-only\"\n style={{ visibility: 'hidden' }}\n aria-hidden=\"true\"\n aria-label={defaultLabel}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n onChange={handleInputChange}\n onClick={handleInputClick}\n onFocus={handleInputFocus}\n tabIndex={-1}\n />\n {/* Error message — a calm fade/slide in (no decorative shake). */}\n <AnimatePresence>\n {displayError && (\n <motion.p\n role=\"alert\"\n className=\"mt-ds-02 text-body-xs text-error-11\"\n initial={{ opacity: 0, y: -2 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n {displayError}\n </motion.p>\n )}\n </AnimatePresence>\n </div>\n </MotionPreference>\n )\n },\n)\nFileUpload.displayName = 'FileUpload'\n\nexport { FileUpload }\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/ui/form.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAA;AAExE,UAAU,qBAAqB;IAC7B,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AACD,QAAA,MAAM,gBAAgB,sCAAiD,CAAA;AAEvE;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,cAAe,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC1E;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kFAAkF;IAClF,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,QAAA,MAAM,SAAS,uFAuBd,CAAA;AAMD,MAAM,WAAW,mBAAoB,SAAQ,KAAK,CAAC,cAAc,CAAC,oBAAoB,CAAC;IACrF,KAAK,CAAC,EAAE,eAAe,CAAA;CACxB;AASD,QAAA,MAAM,cAAc,kGAoBnB,CAAA;AAGD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,IAAI,qBAAqB,CAEpD;AAED,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,cAAc,EAAE,CAAA"}
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/ui/form.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAA;AAExE,UAAU,qBAAqB;IAC7B,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AACD,QAAA,MAAM,gBAAgB,sCAAiD,CAAA;AAEvE;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,cAAe,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC1E;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kFAAkF;IAClF,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,QAAA,MAAM,SAAS,uFAuBd,CAAA;AAMD,MAAM,WAAW,mBAAoB,SAAQ,KAAK,CAAC,cAAc,CAAC,oBAAoB,CAAC;IACrF,KAAK,CAAC,EAAE,eAAe,CAAA;CACxB;AASD,QAAA,MAAM,cAAc,kGAsBnB,CAAA;AAGD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,IAAI,qBAAqB,CAEpD;AAED,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,cAAc,EAAE,CAAA"}
package/dist/ui/form.js CHANGED
@@ -1,47 +1,48 @@
1
1
  "use client";
2
2
  import { motionProps as e, tweens as t } from "./lib/motion.js";
3
- import { cn as n } from "./lib/utils.js";
4
- import * as r from "react";
5
- import { jsx as i } from "react/jsx-runtime";
6
- import { motion as a } from "framer-motion";
3
+ import { MotionPreference as n } from "../_chunks/motion-preference.js";
4
+ import { cn as r } from "./lib/utils.js";
5
+ import * as i from "react";
6
+ import { jsx as a } from "react/jsx-runtime";
7
+ import { motion as o } from "framer-motion";
7
8
  //#region src/ui/form.tsx
8
- var o = r.createContext({}), s = r.forwardRef(({ className: e, helperTextId: t, inputId: a, state: s = "helper", required: c, children: l, ...u }, d) => {
9
- let f = r.useId(), p = t || `${f}-helper`, m = a || `${f}-input`, h = `${m}-label`, g = r.useMemo(() => ({
10
- state: s,
9
+ var s = i.createContext({}), c = i.forwardRef(({ className: e, helperTextId: t, inputId: n, state: o = "helper", required: c, children: l, ...u }, d) => {
10
+ let f = i.useId(), p = t || `${f}-helper`, m = n || `${f}-input`, h = `${m}-label`, g = i.useMemo(() => ({
11
+ state: o,
11
12
  helperTextId: p,
12
13
  inputId: m,
13
14
  labelId: h,
14
15
  required: c
15
16
  }), [
16
- s,
17
+ o,
17
18
  p,
18
19
  m,
19
20
  h,
20
21
  c
21
22
  ]);
22
- return /* @__PURE__ */ i(o.Provider, {
23
+ return /* @__PURE__ */ a(s.Provider, {
23
24
  value: g,
24
- children: /* @__PURE__ */ i("div", {
25
+ children: /* @__PURE__ */ a("div", {
25
26
  ref: d,
26
- className: n("flex flex-col gap-ds-02", e),
27
+ className: r("flex flex-col gap-ds-02", e),
27
28
  ...u,
28
29
  children: l
29
30
  })
30
31
  });
31
32
  });
32
- s.displayName = "FormField";
33
- var c = {
33
+ c.displayName = "FormField";
34
+ var l = {
34
35
  helper: "text-surface-fg-subtle",
35
36
  error: "text-error-11",
36
37
  warning: "text-warning-11",
37
38
  success: "text-success-11"
38
- }, l = r.forwardRef(({ className: s, id: l, state: u, ...d }, f) => {
39
- let p = r.useContext(o), m = u ?? p.state ?? "helper", h = l ?? p.helperTextId;
40
- return /* @__PURE__ */ i(a.p, {
41
- ref: f,
42
- id: h,
43
- role: m === "error" ? "alert" : void 0,
44
- className: n("text-body-sm", c[m], s),
39
+ }, u = i.forwardRef(({ className: c, id: u, state: d, ...f }, p) => {
40
+ let m = i.useContext(s), h = d ?? m.state ?? "helper", g = u ?? m.helperTextId;
41
+ return /* @__PURE__ */ a(n, { children: /* @__PURE__ */ a(o.p, {
42
+ ref: p,
43
+ id: g,
44
+ role: h === "error" ? "alert" : void 0,
45
+ className: r("text-body-sm", l[h], c),
45
46
  initial: {
46
47
  opacity: 0,
47
48
  y: -4
@@ -55,12 +56,12 @@ var c = {
55
56
  y: -4
56
57
  },
57
58
  transition: t.fade,
58
- ...e(d)
59
- });
59
+ ...e(f)
60
+ }) });
60
61
  });
61
- l.displayName = "FormHelperText";
62
- function u() {
63
- return r.useContext(o);
62
+ u.displayName = "FormHelperText";
63
+ function d() {
64
+ return i.useContext(s);
64
65
  }
65
66
  //#endregion
66
- export { s as FormField, o as FormFieldContext, l as FormHelperText, u as useFormField };
67
+ export { c as FormField, s as FormFieldContext, u as FormHelperText, d as useFormField };
@@ -1 +1 @@
1
- {"version":3,"file":"form.js","names":[],"sources":["../../src/ui/form.tsx"],"sourcesContent":["'use client'\n\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { motionProps, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\n\nexport type FormHelperState = 'helper' | 'error' | 'warning' | 'success'\n\ninterface FormFieldContextValue {\n state?: FormHelperState\n helperTextId?: string\n /** Auto-generated id shared between Label (htmlFor) and the field input (id). */\n inputId?: string\n /**\n * Auto-generated id of the Label itself. Non-labellable controls (e.g. the\n * Combobox trigger, a `div[role=combobox]`, which `<label htmlFor>` can't target)\n * reference this via `aria-labelledby` to still adopt the visible label as their\n * accessible name.\n */\n labelId?: string\n required?: boolean\n}\nconst FormFieldContext = React.createContext<FormFieldContextValue>({})\n\n/**\n * FormField — vertical flex container that provides validation state and\n * aria-describedby wiring to children via React context.\n *\n * **Automatic a11y wiring:** FormField generates a stable ID (or uses a provided `helperTextId`)\n * and passes it to `FormHelperText` via context. Use the `useFormField()` hook in custom inputs\n * to read `{ state, helperTextId, required }` and spread onto your input element.\n *\n * @example\n * <FormField state=\"error\">\n * <Label htmlFor=\"email\">Email</Label>\n * <Input id=\"email\" state=\"error\" />\n * <FormHelperText>Please enter a valid email address.</FormHelperText>\n * </FormField>\n */\nexport interface FormFieldProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Custom ID for the helper text element. If omitted, an auto-generated ID is used.\n * Pass this to `useFormField()` consumers that need to set `aria-describedby`.\n */\n helperTextId?: string\n /**\n * Custom ID for the field input element. If omitted, an auto-generated ID is used.\n * Label (when inside FormField) reads this via `htmlFor`; Input reads it via `id`.\n * Explicit `htmlFor` / `id` props on children always win.\n */\n inputId?: string\n /** Current validation state — propagated to child `FormHelperText` via context */\n state?: FormHelperState\n /** Whether the field is required — available to children via `useFormField()` */\n required?: boolean\n}\n\nconst FormField = React.forwardRef<HTMLDivElement, FormFieldProps>(\n ({ className, helperTextId, inputId, state = 'helper', required, children, ...props }, ref) => {\n const autoId = React.useId()\n const resolvedHelperId = helperTextId || `${autoId}-helper`\n const resolvedInputId = inputId || `${autoId}-input`\n const resolvedLabelId = `${resolvedInputId}-label`\n const contextValue = React.useMemo(\n () => ({ state, helperTextId: resolvedHelperId, inputId: resolvedInputId, labelId: resolvedLabelId, required }),\n [state, resolvedHelperId, resolvedInputId, resolvedLabelId, required],\n )\n\n return (\n <FormFieldContext.Provider value={contextValue}>\n <div\n ref={ref}\n className={cn('flex flex-col gap-ds-02', className)}\n {...props}\n >\n {children}\n </div>\n </FormFieldContext.Provider>\n )\n },\n)\nFormField.displayName = 'FormField'\n\n// FormHelperText — small helper/error/warning/success message under a field\n//\n// When used for errors, set an `id` matching the input's `aria-describedby`.\nexport interface FormHelperTextProps extends React.HTMLAttributes<HTMLParagraphElement> {\n state?: FormHelperState\n}\n\nconst helperStateClasses: Record<FormHelperState, string> = {\n helper: 'text-surface-fg-subtle',\n error: 'text-error-11',\n warning: 'text-warning-11',\n success: 'text-success-11',\n}\n\nconst FormHelperText = React.forwardRef<HTMLParagraphElement, FormHelperTextProps>(\n ({ className, id: idProp, state: stateProp, ...props }, ref) => {\n const context = React.useContext(FormFieldContext)\n const state = stateProp ?? context.state ?? 'helper'\n const id = idProp ?? context.helperTextId\n\n return (\n <motion.p\n ref={ref}\n id={id}\n role={state === 'error' ? 'alert' : undefined}\n className={cn('text-body-sm', helperStateClasses[state], className)}\n initial={{ opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={tweens.fade}\n {...motionProps(props)}\n />\n )\n },\n)\nFormHelperText.displayName = 'FormHelperText'\n\n/**\n * Hook to read FormField context — returns `{ state, helperTextId, required }`.\n * Use in custom input components to auto-wire `aria-describedby` and `aria-invalid`.\n *\n * @example\n * function MyCustomInput(props) {\n * const { state, helperTextId, required } = useFormField()\n * return (\n * <input\n * aria-describedby={helperTextId}\n * aria-invalid={state === 'error' || undefined}\n * aria-required={required}\n * {...props}\n * />\n * )\n * }\n */\nexport function useFormField(): FormFieldContextValue {\n return React.useContext(FormFieldContext)\n}\n\nexport { FormField, FormFieldContext, FormHelperText }\n"],"mappings":";;;;;;;AAwBA,IAAM,IAAmB,EAAM,cAAqC,CAAC,CAAC,GAmChE,IAAY,EAAM,YACrB,EAAE,cAAW,iBAAc,YAAS,WAAQ,UAAU,aAAU,aAAU,GAAG,KAAS,MAAQ;CAC7F,IAAM,IAAS,EAAM,MAAM,GACrB,IAAmB,KAAgB,GAAG,EAAO,UAC7C,IAAkB,KAAW,GAAG,EAAO,SACvC,IAAkB,GAAG,EAAgB,SACrC,IAAe,EAAM,eAClB;EAAE;EAAO,cAAc;EAAkB,SAAS;EAAiB,SAAS;EAAiB;CAAS,IAC7G;EAAC;EAAO;EAAkB;EAAiB;EAAiB;CAAQ,CACtE;CAEA,OACE,kBAAC,EAAiB,UAAlB;EAA2B,OAAO;YAChC,kBAAC,OAAD;GACO;GACL,WAAW,EAAG,2BAA2B,CAAS;GAClD,GAAI;GAEH;EACE,CAAA;CACoB,CAAA;AAE/B,CACF;AACA,EAAU,cAAc;AASxB,IAAM,IAAsD;CAC1D,QAAS;CACT,OAAS;CACT,SAAS;CACT,SAAS;AACX,GAEM,IAAiB,EAAM,YAC1B,EAAE,cAAW,IAAI,GAAQ,OAAO,GAAW,GAAG,KAAS,MAAQ;CAC9D,IAAM,IAAU,EAAM,WAAW,CAAgB,GAC3C,IAAQ,KAAa,EAAQ,SAAS,UACtC,IAAK,KAAU,EAAQ;CAE7B,OACE,kBAAC,EAAO,GAAR;EACO;EACD;EACJ,MAAM,MAAU,UAAU,UAAU,KAAA;EACpC,WAAW,EAAG,gBAAgB,EAAmB,IAAQ,CAAS;EAClE,SAAS;GAAE,SAAS;GAAG,GAAG;EAAG;EAC7B,SAAS;GAAE,SAAS;GAAG,GAAG;EAAE;EAC5B,MAAM;GAAE,SAAS;GAAG,GAAG;EAAG;EAC1B,YAAY,EAAO;EACnB,GAAI,EAAY,CAAK;CACtB,CAAA;AAEL,CACF;AACA,EAAe,cAAc;AAmB7B,SAAgB,IAAsC;CACpD,OAAO,EAAM,WAAW,CAAgB;AAC1C"}
1
+ {"version":3,"file":"form.js","names":[],"sources":["../../src/ui/form.tsx"],"sourcesContent":["'use client'\n\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { MotionPreference } from '../motion/motion-preference'\nimport { motionProps, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\n\nexport type FormHelperState = 'helper' | 'error' | 'warning' | 'success'\n\ninterface FormFieldContextValue {\n state?: FormHelperState\n helperTextId?: string\n /** Auto-generated id shared between Label (htmlFor) and the field input (id). */\n inputId?: string\n /**\n * Auto-generated id of the Label itself. Non-labellable controls (e.g. the\n * Combobox trigger, a `div[role=combobox]`, which `<label htmlFor>` can't target)\n * reference this via `aria-labelledby` to still adopt the visible label as their\n * accessible name.\n */\n labelId?: string\n required?: boolean\n}\nconst FormFieldContext = React.createContext<FormFieldContextValue>({})\n\n/**\n * FormField — vertical flex container that provides validation state and\n * aria-describedby wiring to children via React context.\n *\n * **Automatic a11y wiring:** FormField generates a stable ID (or uses a provided `helperTextId`)\n * and passes it to `FormHelperText` via context. Use the `useFormField()` hook in custom inputs\n * to read `{ state, helperTextId, required }` and spread onto your input element.\n *\n * @example\n * <FormField state=\"error\">\n * <Label htmlFor=\"email\">Email</Label>\n * <Input id=\"email\" state=\"error\" />\n * <FormHelperText>Please enter a valid email address.</FormHelperText>\n * </FormField>\n */\nexport interface FormFieldProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Custom ID for the helper text element. If omitted, an auto-generated ID is used.\n * Pass this to `useFormField()` consumers that need to set `aria-describedby`.\n */\n helperTextId?: string\n /**\n * Custom ID for the field input element. If omitted, an auto-generated ID is used.\n * Label (when inside FormField) reads this via `htmlFor`; Input reads it via `id`.\n * Explicit `htmlFor` / `id` props on children always win.\n */\n inputId?: string\n /** Current validation state — propagated to child `FormHelperText` via context */\n state?: FormHelperState\n /** Whether the field is required — available to children via `useFormField()` */\n required?: boolean\n}\n\nconst FormField = React.forwardRef<HTMLDivElement, FormFieldProps>(\n ({ className, helperTextId, inputId, state = 'helper', required, children, ...props }, ref) => {\n const autoId = React.useId()\n const resolvedHelperId = helperTextId || `${autoId}-helper`\n const resolvedInputId = inputId || `${autoId}-input`\n const resolvedLabelId = `${resolvedInputId}-label`\n const contextValue = React.useMemo(\n () => ({ state, helperTextId: resolvedHelperId, inputId: resolvedInputId, labelId: resolvedLabelId, required }),\n [state, resolvedHelperId, resolvedInputId, resolvedLabelId, required],\n )\n\n return (\n <FormFieldContext.Provider value={contextValue}>\n <div\n ref={ref}\n className={cn('flex flex-col gap-ds-02', className)}\n {...props}\n >\n {children}\n </div>\n </FormFieldContext.Provider>\n )\n },\n)\nFormField.displayName = 'FormField'\n\n// FormHelperText — small helper/error/warning/success message under a field\n//\n// When used for errors, set an `id` matching the input's `aria-describedby`.\nexport interface FormHelperTextProps extends React.HTMLAttributes<HTMLParagraphElement> {\n state?: FormHelperState\n}\n\nconst helperStateClasses: Record<FormHelperState, string> = {\n helper: 'text-surface-fg-subtle',\n error: 'text-error-11',\n warning: 'text-warning-11',\n success: 'text-success-11',\n}\n\nconst FormHelperText = React.forwardRef<HTMLParagraphElement, FormHelperTextProps>(\n ({ className, id: idProp, state: stateProp, ...props }, ref) => {\n const context = React.useContext(FormFieldContext)\n const state = stateProp ?? context.state ?? 'helper'\n const id = idProp ?? context.helperTextId\n\n return (\n <MotionPreference>\n <motion.p\n ref={ref}\n id={id}\n role={state === 'error' ? 'alert' : undefined}\n className={cn('text-body-sm', helperStateClasses[state], className)}\n initial={{ opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={tweens.fade}\n {...motionProps(props)}\n />\n </MotionPreference>\n )\n },\n)\nFormHelperText.displayName = 'FormHelperText'\n\n/**\n * Hook to read FormField context — returns `{ state, helperTextId, required }`.\n * Use in custom input components to auto-wire `aria-describedby` and `aria-invalid`.\n *\n * @example\n * function MyCustomInput(props) {\n * const { state, helperTextId, required } = useFormField()\n * return (\n * <input\n * aria-describedby={helperTextId}\n * aria-invalid={state === 'error' || undefined}\n * aria-required={required}\n * {...props}\n * />\n * )\n * }\n */\nexport function useFormField(): FormFieldContextValue {\n return React.useContext(FormFieldContext)\n}\n\nexport { FormField, FormFieldContext, FormHelperText }\n"],"mappings":";;;;;;;;AAyBA,IAAM,IAAmB,EAAM,cAAqC,CAAC,CAAC,GAmChE,IAAY,EAAM,YACrB,EAAE,cAAW,iBAAc,YAAS,WAAQ,UAAU,aAAU,aAAU,GAAG,KAAS,MAAQ;CAC7F,IAAM,IAAS,EAAM,MAAM,GACrB,IAAmB,KAAgB,GAAG,EAAO,UAC7C,IAAkB,KAAW,GAAG,EAAO,SACvC,IAAkB,GAAG,EAAgB,SACrC,IAAe,EAAM,eAClB;EAAE;EAAO,cAAc;EAAkB,SAAS;EAAiB,SAAS;EAAiB;CAAS,IAC7G;EAAC;EAAO;EAAkB;EAAiB;EAAiB;CAAQ,CACtE;CAEA,OACE,kBAAC,EAAiB,UAAlB;EAA2B,OAAO;YAChC,kBAAC,OAAD;GACO;GACL,WAAW,EAAG,2BAA2B,CAAS;GAClD,GAAI;GAEH;EACE,CAAA;CACoB,CAAA;AAE/B,CACF;AACA,EAAU,cAAc;AASxB,IAAM,IAAsD;CAC1D,QAAS;CACT,OAAS;CACT,SAAS;CACT,SAAS;AACX,GAEM,IAAiB,EAAM,YAC1B,EAAE,cAAW,IAAI,GAAQ,OAAO,GAAW,GAAG,KAAS,MAAQ;CAC9D,IAAM,IAAU,EAAM,WAAW,CAAgB,GAC3C,IAAQ,KAAa,EAAQ,SAAS,UACtC,IAAK,KAAU,EAAQ;CAE7B,OACE,kBAAC,GAAD,EAAA,UACE,kBAAC,EAAO,GAAR;EACO;EACD;EACJ,MAAM,MAAU,UAAU,UAAU,KAAA;EACpC,WAAW,EAAG,gBAAgB,EAAmB,IAAQ,CAAS;EAClE,SAAS;GAAE,SAAS;GAAG,GAAG;EAAG;EAC7B,SAAS;GAAE,SAAS;GAAG,GAAG;EAAE;EAC5B,MAAM;GAAE,SAAS;GAAG,GAAG;EAAG;EAC1B,YAAY,EAAO;EACnB,GAAI,EAAY,CAAK;CACtB,CAAA,EACe,CAAA;AAEtB,CACF;AACA,EAAe,cAAc;AAmB7B,SAAgB,IAAsC;CACpD,OAAO,EAAM,WAAW,CAAgB;AAC1C"}
@@ -1 +1 @@
1
- {"version":3,"file":"hover-card.d.ts","sourceRoot":"","sources":["../../src/ui/hover-card.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,kBAAkB,MAAM,8BAA8B,CAAA;AAElE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAS9B;;;;;;GAMG;AACH,QAAA,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,wBAAwB,CAAC,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAuBvF,CAAA;AAGD,QAAA,MAAM,gBAAgB,oHAA6B,CAAA;AAEnD,QAAA,MAAM,gBAAgB,oKAmCpB,CAAA;AAGF,MAAM,MAAM,qBAAqB,GAAG,KAAK,CAAC,wBAAwB,CAAC,OAAO,kBAAkB,CAAC,OAAO,CAAC,CAAA;AAErG,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAC,gBAAgB,EAAE,CAAA"}
1
+ {"version":3,"file":"hover-card.d.ts","sourceRoot":"","sources":["../../src/ui/hover-card.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,kBAAkB,MAAM,8BAA8B,CAAA;AAElE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAU9B;;;;;;GAMG;AACH,QAAA,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,wBAAwB,CAAC,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAuBvF,CAAA;AAGD,QAAA,MAAM,gBAAgB,oHAA6B,CAAA;AAEnD,QAAA,MAAM,gBAAgB,oKAqCpB,CAAA;AAGF,MAAM,MAAM,qBAAqB,GAAG,KAAK,CAAC,wBAAwB,CAAC,OAAO,kBAAkB,CAAC,OAAO,CAAC,CAAA;AAErG,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAC,gBAAgB,EAAE,CAAA"}