@devalok/shilp-sutra 0.42.0 → 0.43.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 (92) hide show
  1. package/AGENTS.md +10 -0
  2. package/MIGRATION.md +32 -0
  3. package/dist/_chunks/chat.js +1 -1
  4. package/dist/_chunks/chat.js.map +1 -1
  5. package/dist/_chunks/mention-suggestion.js +1 -1
  6. package/dist/_chunks/mention-suggestion.js.map +1 -1
  7. package/dist/_chunks/popover.js +1 -1
  8. package/dist/_chunks/popover.js.map +1 -1
  9. package/dist/_chunks/shared.js +1 -1
  10. package/dist/_chunks/shared.js.map +1 -1
  11. package/dist/ai/command-bar.js +1 -1
  12. package/dist/ai/command-bar.js.map +1 -1
  13. package/dist/composed/bulk-action-bar.js +1 -1
  14. package/dist/composed/bulk-action-bar.js.map +1 -1
  15. package/dist/composed/command-palette.js +1 -1
  16. package/dist/composed/command-palette.js.map +1 -1
  17. package/dist/composed/content-card.js +1 -1
  18. package/dist/composed/content-card.js.map +1 -1
  19. package/dist/composed/error-boundary.js +1 -1
  20. package/dist/composed/error-boundary.js.map +1 -1
  21. package/dist/composed/extensions/emoji-suggestion.js +1 -1
  22. package/dist/composed/extensions/emoji-suggestion.js.map +1 -1
  23. package/dist/composed/file-preview.js +1 -1
  24. package/dist/composed/file-preview.js.map +1 -1
  25. package/dist/composed/rich-chat-input.js +5 -5
  26. package/dist/composed/rich-chat-input.js.map +1 -1
  27. package/dist/composed/rich-text-editor.js +2 -2
  28. package/dist/composed/rich-text-editor.js.map +1 -1
  29. package/dist/shell/notification-center.js +1 -1
  30. package/dist/shell/notification-center.js.map +1 -1
  31. package/dist/shell/sidebar.d.ts +6 -0
  32. package/dist/shell/sidebar.d.ts.map +1 -1
  33. package/dist/shell/sidebar.js +27 -18
  34. package/dist/shell/sidebar.js.map +1 -1
  35. package/dist/shell/top-bar.js +1 -1
  36. package/dist/shell/top-bar.js.map +1 -1
  37. package/dist/tokens/semantic.css +8 -2
  38. package/dist/ui/alert-dialog.js +1 -1
  39. package/dist/ui/alert-dialog.js.map +1 -1
  40. package/dist/ui/autocomplete.js +1 -1
  41. package/dist/ui/autocomplete.js.map +1 -1
  42. package/dist/ui/card.d.ts.map +1 -1
  43. package/dist/ui/card.js +3 -3
  44. package/dist/ui/card.js.map +1 -1
  45. package/dist/ui/color-input.js +1 -1
  46. package/dist/ui/color-input.js.map +1 -1
  47. package/dist/ui/combobox.js +1 -1
  48. package/dist/ui/combobox.js.map +1 -1
  49. package/dist/ui/context-menu.js +2 -2
  50. package/dist/ui/context-menu.js.map +1 -1
  51. package/dist/ui/data-table-bulk-actions.js +1 -1
  52. package/dist/ui/data-table-bulk-actions.js.map +1 -1
  53. package/dist/ui/dialog.js +1 -1
  54. package/dist/ui/dialog.js.map +1 -1
  55. package/dist/ui/dropdown-menu.js +2 -2
  56. package/dist/ui/dropdown-menu.js.map +1 -1
  57. package/dist/ui/hover-card.js +1 -1
  58. package/dist/ui/hover-card.js.map +1 -1
  59. package/dist/ui/index.d.ts +1 -0
  60. package/dist/ui/index.d.ts.map +1 -1
  61. package/dist/ui/index.js +20 -19
  62. package/dist/ui/index.js.map +1 -1
  63. package/dist/ui/input-otp.js +1 -1
  64. package/dist/ui/input-otp.js.map +1 -1
  65. package/dist/ui/menubar.js +3 -3
  66. package/dist/ui/menubar.js.map +1 -1
  67. package/dist/ui/navigation-menu.js +1 -1
  68. package/dist/ui/navigation-menu.js.map +1 -1
  69. package/dist/ui/select.js +1 -1
  70. package/dist/ui/select.js.map +1 -1
  71. package/dist/ui/sidebar.js +1 -1
  72. package/dist/ui/sidebar.js.map +1 -1
  73. package/dist/ui/split-button.js +1 -1
  74. package/dist/ui/split-button.js.map +1 -1
  75. package/dist/ui/stat-card.d.ts +22 -2
  76. package/dist/ui/stat-card.d.ts.map +1 -1
  77. package/dist/ui/stat-card.js +101 -86
  78. package/dist/ui/stat-card.js.map +1 -1
  79. package/dist/ui/stat-flash.d.ts +46 -0
  80. package/dist/ui/stat-flash.d.ts.map +1 -0
  81. package/dist/ui/stat-flash.js +114 -0
  82. package/dist/ui/stat-flash.js.map +1 -0
  83. package/dist/ui/toast.js +2 -2
  84. package/dist/ui/toast.js.map +1 -1
  85. package/docs/components/ui/stat-card.md +13 -3
  86. package/docs/components/ui/stat-flash.md +44 -0
  87. package/llms-full.txt +58 -4
  88. package/llms.txt +4 -0
  89. package/package.json +1 -1
  90. package/skill/SKILL.md +1 -1
  91. package/skill/references/components-full.md +58 -4
  92. package/skill/references/components.md +4 -0
@@ -5,18 +5,12 @@ import { cn as r } from "./lib/utils.js";
5
5
  import { Icon as i } from "./icon.js";
6
6
  import { t as a } from "../_chunks/normalize-icon.js";
7
7
  import { n as o } from "../_chunks/link-context.js";
8
- import * as s from "react";
9
- import { Fragment as c, jsx as l, jsxs as u } from "react/jsx-runtime";
10
- import { IconMinus as d, IconTrendingDown as f, IconTrendingUp as p } from "@tabler/icons-react";
11
- import { motion as m } from "framer-motion";
8
+ import { StatFlash as s } from "./stat-flash.js";
9
+ import * as c from "react";
10
+ import { Fragment as l, jsx as u, jsxs as d } from "react/jsx-runtime";
11
+ import { IconMinus as f, IconTrendingDown as p, IconTrendingUp as m } from "@tabler/icons-react";
12
+ import { motion as h } from "framer-motion";
12
13
  //#region src/ui/stat-card.tsx
13
- var h = {
14
- default: "border-l-accent-9",
15
- success: "border-l-success-9",
16
- warning: "border-l-warning-9",
17
- error: "border-l-error-9",
18
- info: "border-l-info-9"
19
- };
20
14
  function g(e, t, n) {
21
15
  let r = e.filter((e) => Number.isFinite(e));
22
16
  if (r.length < 2) return "";
@@ -27,42 +21,42 @@ function g(e, t, n) {
27
21
  }).join(" ");
28
22
  }
29
23
  function _({ data: e, colorClass: t }) {
30
- let n = s.useId(), i = s.useRef(null), [a, o] = s.useState(0), c = g(e, 80, 32);
31
- if (s.useEffect(() => {
24
+ let n = c.useId(), i = c.useRef(null), [a, o] = c.useState(0), s = g(e, 80, 32);
25
+ if (c.useEffect(() => {
32
26
  i.current && o(i.current.getTotalLength());
33
- }, [c]), !c) return null;
34
- let d = `${c} L${80 .toFixed(1)},${32 .toFixed(1)} L0,${32 .toFixed(1)} Z`;
35
- return /* @__PURE__ */ u("div", {
27
+ }, [s]), !s) return null;
28
+ let l = `${s} L${80 .toFixed(1)},${32 .toFixed(1)} L0,${32 .toFixed(1)} Z`;
29
+ return /* @__PURE__ */ d("div", {
36
30
  className: r("w-20 h-8", t),
37
31
  "aria-hidden": "true",
38
- children: [/* @__PURE__ */ u("svg", {
32
+ children: [/* @__PURE__ */ d("svg", {
39
33
  viewBox: "0 0 80 32",
40
34
  className: "w-full h-full overflow-visible",
41
35
  preserveAspectRatio: "none",
42
36
  children: [
43
- /* @__PURE__ */ l("defs", { children: /* @__PURE__ */ u("linearGradient", {
37
+ /* @__PURE__ */ u("defs", { children: /* @__PURE__ */ d("linearGradient", {
44
38
  id: `sparkline-fill-${n}`,
45
39
  x1: "0",
46
40
  y1: "0",
47
41
  x2: "0",
48
42
  y2: "1",
49
- children: [/* @__PURE__ */ l("stop", {
43
+ children: [/* @__PURE__ */ u("stop", {
50
44
  offset: "0%",
51
45
  stopColor: "currentColor",
52
46
  stopOpacity: "0.1"
53
- }), /* @__PURE__ */ l("stop", {
47
+ }), /* @__PURE__ */ u("stop", {
54
48
  offset: "100%",
55
49
  stopColor: "currentColor",
56
50
  stopOpacity: "0"
57
51
  })]
58
52
  }) }),
59
- /* @__PURE__ */ l("path", {
60
- d,
53
+ /* @__PURE__ */ u("path", {
54
+ d: l,
61
55
  fill: `url(#sparkline-fill-${n})`
62
56
  }),
63
- /* @__PURE__ */ l("path", {
57
+ /* @__PURE__ */ u("path", {
64
58
  ref: i,
65
- d: c,
59
+ d: s,
66
60
  fill: "none",
67
61
  stroke: "currentColor",
68
62
  strokeWidth: "1.5",
@@ -76,51 +70,72 @@ function _({ data: e, colorClass: t }) {
76
70
  }
77
71
  })
78
72
  ]
79
- }), a > 0 && /* @__PURE__ */ l("style", { children: `@keyframes sparkline-draw-${n.replace(/:/g, "")} { to { stroke-dashoffset: 0; } }` })]
73
+ }), a > 0 && /* @__PURE__ */ u("style", { children: `@keyframes sparkline-draw-${n.replace(/:/g, "")} { to { stroke-dashoffset: 0; } }` })]
80
74
  });
81
75
  }
82
76
  function v({ progress: e, label: t }) {
83
77
  let n = Math.max(0, Math.min(100, e)), i = n >= 90 ? "bg-success-9" : n >= 70 ? "bg-warning-9" : "bg-accent-9";
84
- return /* @__PURE__ */ l("div", {
78
+ return /* @__PURE__ */ u("div", {
85
79
  className: "h-1 w-full rounded-pill bg-surface-raised mt-ds-04",
86
80
  role: "progressbar",
87
81
  "aria-label": `${t} progress`,
88
82
  "aria-valuenow": n,
89
83
  "aria-valuemin": 0,
90
84
  "aria-valuemax": 100,
91
- children: /* @__PURE__ */ l("div", {
85
+ children: /* @__PURE__ */ u("div", {
92
86
  className: r("h-full rounded-pill transition-[width] duration-moderate-02 ease-productive-standard", i),
93
87
  style: { width: `${n}%` }
94
88
  })
95
89
  });
96
90
  }
97
- var y = s.forwardRef(({ className: s, label: g, title: y, value: b, prefix: x, suffix: S, delta: C, icon: w, loading: T, comparisonLabel: E, secondaryLabel: D, progress: O, accent: k, sparkline: A, onClick: j, href: M, footer: N, ...P }, F) => {
98
- let I = o(), L = y ?? g ?? "", R = !!(j || M), z = R && !P["aria-label"] ? `View ${L}` : P["aria-label"];
99
- if (T) return /* @__PURE__ */ u("div", {
100
- ref: F,
101
- className: r("rounded-surface border border-surface-border bg-surface-raised shadow-raised p-ds-05b", k && `border-l-[3px] ${h[k]}`, s),
102
- ...P,
91
+ var y = c.forwardRef(({ className: c, label: g, title: y, value: b, prefix: x, suffix: S, delta: C, icon: w, loading: T, comparisonLabel: E, secondaryLabel: D, progress: O, surface: k = "raised", accentStyle: A = "none", iconFill: j = "soft", flash: M, flashSpeed: N, sparkline: P, onClick: F, href: I, footer: L, ...R }, z) => {
92
+ let B = o(), V = y ?? g ?? "", H = !!(F || I), U = H && !R["aria-label"] ? `View ${V}` : R["aria-label"];
93
+ if (T) return /* @__PURE__ */ d("div", {
94
+ ref: z,
95
+ className: r("rounded-surface bg-surface-raised p-ds-05b", k === "flat" ? "border border-surface-border" : "shadow-raised", c),
96
+ ...R,
103
97
  children: [
104
- /* @__PURE__ */ l("div", { className: "h-ds-04 w-24 rounded-control-inner bg-skeleton-base animate-pulse mb-ds-05" }),
105
- /* @__PURE__ */ l("div", { className: "h-ds-sm w-32 rounded-control bg-skeleton-base animate-pulse mb-ds-03" }),
106
- /* @__PURE__ */ l("div", { className: "h-3 w-16 rounded-control-inner bg-skeleton-base animate-pulse" })
98
+ /* @__PURE__ */ u("div", { className: "h-ds-04 w-24 rounded-control-inner bg-skeleton-base animate-pulse mb-ds-05" }),
99
+ /* @__PURE__ */ u("div", { className: "h-ds-sm w-32 rounded-control bg-skeleton-base animate-pulse mb-ds-03" }),
100
+ /* @__PURE__ */ u("div", { className: "h-3 w-16 rounded-control-inner bg-skeleton-base animate-pulse" })
107
101
  ]
108
102
  });
109
- let B = C?.direction === "up" ? p : C?.direction === "down" ? f : d, V = C?.direction === "up" ? "text-success-11" : C?.direction === "down" ? "text-error-11" : "text-surface-fg-muted", H = C?.direction === "up" ? "text-success-11" : C?.direction === "down" ? "text-error-11" : "text-accent-11", U = /* @__PURE__ */ u(c, { children: [
110
- /* @__PURE__ */ u("div", {
103
+ let W = C?.direction === "up" ? m : C?.direction === "down" ? p : f, G = C?.direction === "up" ? "text-success-11" : C?.direction === "down" ? "text-error-11" : "text-surface-fg-muted", K = C?.direction === "up" ? "text-success-11" : C?.direction === "down" ? "text-error-11" : "text-accent-11", q = /* @__PURE__ */ d(l, { children: [
104
+ /* @__PURE__ */ d("div", {
111
105
  className: "flex items-center justify-between mb-ds-04",
112
- children: [/* @__PURE__ */ l(m.p, {
106
+ children: [/* @__PURE__ */ u(h.p, {
113
107
  className: "text-ds-md font-medium text-surface-fg-muted",
114
108
  initial: { opacity: 0 },
115
109
  animate: { opacity: 1 },
116
110
  transition: n.fade,
117
- children: L
118
- }), /* @__PURE__ */ u("div", {
111
+ children: V
112
+ }), /* @__PURE__ */ d("div", {
119
113
  className: "flex items-center gap-ds-03",
120
- children: [A && A.length >= 2 && /* @__PURE__ */ l(_, {
121
- data: A,
122
- colorClass: H
123
- }), w && /* @__PURE__ */ l(m.span, {
114
+ children: [P && P.length >= 2 && /* @__PURE__ */ u(_, {
115
+ data: P,
116
+ colorClass: K
117
+ }), w && M ? /* @__PURE__ */ u(s, {
118
+ icon: w,
119
+ flash: M,
120
+ fill: j,
121
+ speed: N
122
+ }) : w ? A === "icon" ? /* @__PURE__ */ u(h.span, {
123
+ className: r("inline-flex items-center justify-center rounded-control p-ds-02", j === "solid" ? "bg-accent-9 text-accent-fg" : "bg-accent-3 text-accent-11"),
124
+ initial: {
125
+ opacity: 0,
126
+ scale: .96
127
+ },
128
+ animate: {
129
+ opacity: 1,
130
+ scale: 1
131
+ },
132
+ transition: t.snappy,
133
+ "aria-hidden": "true",
134
+ children: /* @__PURE__ */ u(e, {
135
+ size: "md",
136
+ children: a(w, "md")
137
+ })
138
+ }) : /* @__PURE__ */ u(h.span, {
124
139
  className: "text-surface-fg-muted",
125
140
  initial: {
126
141
  opacity: 0,
@@ -132,17 +147,17 @@ var y = s.forwardRef(({ className: s, label: g, title: y, value: b, prefix: x, s
132
147
  },
133
148
  transition: t.snappy,
134
149
  "aria-hidden": "true",
135
- children: /* @__PURE__ */ l(e, {
150
+ children: /* @__PURE__ */ u(e, {
136
151
  size: "lg",
137
152
  children: a(w, "lg")
138
153
  })
139
- })]
154
+ }) : null]
140
155
  })]
141
156
  }),
142
- /* @__PURE__ */ l("div", {
157
+ /* @__PURE__ */ u("div", {
143
158
  className: "overflow-hidden",
144
- children: /* @__PURE__ */ u(m.p, {
145
- className: "inline-block text-ds-3xl font-semibold text-surface-fg",
159
+ children: /* @__PURE__ */ d(h.p, {
160
+ className: r("inline-block text-ds-3xl font-semibold", A === "tint" ? "text-accent-11" : "text-surface-fg"),
146
161
  initial: {
147
162
  opacity: 0,
148
163
  y: "100%"
@@ -153,22 +168,22 @@ var y = s.forwardRef(({ className: s, label: g, title: y, value: b, prefix: x, s
153
168
  },
154
169
  transition: t.smooth,
155
170
  children: [
156
- x && /* @__PURE__ */ l("span", {
171
+ x && /* @__PURE__ */ u("span", {
157
172
  className: "text-surface-fg-muted text-ds-lg",
158
173
  children: x
159
174
  }),
160
- /* @__PURE__ */ l("span", {
175
+ /* @__PURE__ */ u("span", {
161
176
  className: "tabular-nums",
162
177
  children: b
163
178
  }),
164
- S && /* @__PURE__ */ l("span", {
179
+ S && /* @__PURE__ */ u("span", {
165
180
  className: "text-surface-fg-muted text-ds-lg",
166
181
  children: S
167
182
  })
168
183
  ]
169
184
  })
170
185
  }),
171
- D && /* @__PURE__ */ l(m.p, {
186
+ D && /* @__PURE__ */ u(h.p, {
172
187
  className: "text-ds-sm text-surface-fg-subtle mt-ds-01",
173
188
  initial: { opacity: 0 },
174
189
  animate: { opacity: 1 },
@@ -178,20 +193,20 @@ var y = s.forwardRef(({ className: s, label: g, title: y, value: b, prefix: x, s
178
193
  },
179
194
  children: D
180
195
  }),
181
- O != null && /* @__PURE__ */ l(m.div, {
196
+ O != null && /* @__PURE__ */ u(h.div, {
182
197
  initial: { opacity: 0 },
183
198
  animate: { opacity: 1 },
184
199
  transition: {
185
200
  ...n.fade,
186
201
  delay: .15
187
202
  },
188
- children: /* @__PURE__ */ l(v, {
203
+ children: /* @__PURE__ */ u(v, {
189
204
  progress: O,
190
- label: L
205
+ label: V
191
206
  })
192
207
  }),
193
- C && /* @__PURE__ */ u(m.div, {
194
- className: r("mt-ds-03 flex items-center gap-ds-02 text-ds-sm font-medium", V),
208
+ C && /* @__PURE__ */ d(h.div, {
209
+ className: r("mt-ds-03 flex items-center gap-ds-02 text-ds-sm font-medium", G),
195
210
  initial: {
196
211
  opacity: 0,
197
212
  y: 8
@@ -205,7 +220,7 @@ var y = s.forwardRef(({ className: s, label: g, title: y, value: b, prefix: x, s
205
220
  delay: .2
206
221
  },
207
222
  children: [
208
- /* @__PURE__ */ l(m.span, {
223
+ /* @__PURE__ */ u(h.span, {
209
224
  className: "inline-flex",
210
225
  initial: {
211
226
  opacity: .5,
@@ -216,19 +231,19 @@ var y = s.forwardRef(({ className: s, label: g, title: y, value: b, prefix: x, s
216
231
  scale: 1
217
232
  },
218
233
  transition: t.bouncy,
219
- children: /* @__PURE__ */ l(i, {
220
- icon: B,
234
+ children: /* @__PURE__ */ u(i, {
235
+ icon: W,
221
236
  size: "sm"
222
237
  })
223
238
  }),
224
- /* @__PURE__ */ l("span", { children: C.value }),
225
- E && /* @__PURE__ */ l("span", {
239
+ /* @__PURE__ */ u("span", { children: C.value }),
240
+ E && /* @__PURE__ */ u("span", {
226
241
  className: "text-surface-fg-subtle font-normal",
227
242
  children: E
228
243
  })
229
244
  ]
230
245
  }),
231
- N && /* @__PURE__ */ l(m.div, {
246
+ L && /* @__PURE__ */ u(h.div, {
232
247
  className: "mt-ds-04 pt-ds-04 border-t border-surface-border text-ds-sm",
233
248
  initial: { opacity: 0 },
234
249
  animate: { opacity: 1 },
@@ -236,29 +251,29 @@ var y = s.forwardRef(({ className: s, label: g, title: y, value: b, prefix: x, s
236
251
  ...n.fade,
237
252
  delay: .25
238
253
  },
239
- children: N
254
+ children: L
240
255
  })
241
- ] }), W = r("rounded-surface border border-surface-border bg-surface-raised shadow-raised p-ds-05b", k && `border-l-[3px] ${h[k]}`, R && "cursor-pointer hover:shadow-raised-hover hover:border-surface-border-strong transition-[box-shadow,border-color] duration-fast-02 ease-productive-standard group", s);
242
- return M ? /* @__PURE__ */ l(I, {
243
- ref: F,
244
- href: M,
245
- onClick: j,
246
- className: r(W, "block no-underline"),
247
- "aria-label": z,
248
- ...P,
249
- children: U
250
- }) : /* @__PURE__ */ l("div", {
251
- ref: F,
252
- className: W,
253
- onClick: j,
254
- role: j ? "button" : void 0,
255
- tabIndex: j ? 0 : void 0,
256
- "aria-label": z,
257
- onKeyDown: j ? (e) => {
258
- (e.key === "Enter" || e.key === " ") && (e.preventDefault(), j());
256
+ ] }), J = r("rounded-surface p-ds-05b", A === "tint" ? "bg-linear-to-t from-accent-2 to-surface-raised" : "bg-surface-raised", k === "flat" ? "border border-surface-border" : "shadow-raised", H && "cursor-pointer hover:shadow-raised-hover transition-[box-shadow] duration-fast-02 ease-productive-standard group", c);
257
+ return I ? /* @__PURE__ */ u(B, {
258
+ ref: z,
259
+ href: I,
260
+ onClick: F,
261
+ className: r(J, "block no-underline"),
262
+ "aria-label": U,
263
+ ...R,
264
+ children: q
265
+ }) : /* @__PURE__ */ u("div", {
266
+ ref: z,
267
+ className: J,
268
+ onClick: F,
269
+ role: F ? "button" : void 0,
270
+ tabIndex: F ? 0 : void 0,
271
+ "aria-label": U,
272
+ onKeyDown: F ? (e) => {
273
+ (e.key === "Enter" || e.key === " ") && (e.preventDefault(), F());
259
274
  } : void 0,
260
- ...P,
261
- children: U
275
+ ...R,
276
+ children: q
262
277
  });
263
278
  });
264
279
  y.displayName = "StatCard";
@@ -1 +1 @@
1
- {"version":3,"file":"stat-card.js","names":[],"sources":["../../src/ui/stat-card.tsx"],"sourcesContent":["'use client'\n\nimport { IconMinus, IconTrendingDown, IconTrendingUp } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Icon } from './icon'\nimport { IconProvider } from './icon-context'\nimport type { IconInput } from './lib/icon-input'\nimport { useLink } from './lib/link-context'\nimport { springs, tweens } from './lib/motion'\nimport { normalizeIcon } from './lib/normalize-icon'\nimport { cn } from './lib/utils'\n\n/**\n * Props for StatCard — a dashboard metric tile displaying a label, a large numeric value,\n * an optional trend delta (with directional arrow icon), and an optional header icon.\n *\n * **Delta direction:** `'up'` renders a green trending-up arrow, `'down'` renders a red\n * trending-down arrow, `'neutral'` renders a grey dash. The `delta.value` is a formatted\n * string (e.g. `\"+8%\"` or `\"−120\"`).\n *\n * **Loading state:** Pass `loading={true}` to render a pulse-skeleton placeholder instead of data.\n *\n * @example\n * // Revenue metric with a positive trend:\n * <StatCard\n * label=\"Monthly Revenue\"\n * value=\"$48,200\"\n * delta={{ value: \"+12%\", direction: \"up\" }}\n * icon={<IconCurrencyDollar />}\n * />\n *\n * @example\n * // Open ticket count with a downward trend (good for support queues):\n * <StatCard\n * label=\"Open Tickets\"\n * value={142}\n * delta={{ value: \"−18\", direction: \"down\" }}\n * />\n *\n * @example\n * // Loading skeleton while data is fetching:\n * <StatCard label=\"Users\" value={0} loading={isFetching} />\n *\n * @example\n * // Simple metric with no trend (stable, neutral):\n * <StatCard\n * label=\"Storage Used\"\n * value=\"4.2 GB\"\n * delta={{ value: \"No change\", direction: \"neutral\" }}\n * />\n * // These are just a few ways — feel free to combine props creatively!\n */\nexport interface StatCardProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Heading text for the metric. `title` is an alias for `label`. */\n label?: string\n /** Alias for `label` — use whichever feels natural. */\n title?: string\n value: string | number\n /** Prefix before value, e.g. \"$\" */\n prefix?: string\n /** Suffix after value, e.g. \"users\" */\n suffix?: string\n delta?: {\n value: string\n direction: 'up' | 'down' | 'neutral'\n }\n icon?: IconInput\n loading?: boolean\n /** Comparison period label shown after delta, e.g. \"vs last month\" */\n comparisonLabel?: string\n /** Secondary metric below main value, e.g. \"of $50,000 target\" */\n secondaryLabel?: string\n /** Progress toward a target (0-100). Renders a thin progress bar below the value. */\n progress?: number\n /** Color accent for the left border decoration */\n accent?: 'default' | 'success' | 'warning' | 'error' | 'info'\n /** Sparkline data points. Renders a mini SVG line chart in the card. */\n sparkline?: number[]\n /** Make the card clickable with hover state */\n onClick?: () => void\n /** Href — makes the card a link (uses the LinkContext Link component) */\n href?: string\n /** Footer content rendered below the card body, e.g. \"View details →\" */\n footer?: React.ReactNode\n}\n\nconst accentBorderMap: Record<NonNullable<StatCardProps['accent']>, string> = {\n default: 'border-l-accent-9',\n success: 'border-l-success-9',\n warning: 'border-l-warning-9',\n error: 'border-l-error-9',\n info: 'border-l-info-9',\n}\n\nfunction buildSparklinePath(raw: number[], width: number, height: number): string {\n const data = raw.filter((v) => Number.isFinite(v))\n if (data.length < 2) return ''\n const min = Math.min(...data)\n const max = Math.max(...data)\n const range = max - min || 1\n const stepX = width / (data.length - 1)\n return data\n .map((v, i) => {\n const x = i * stepX\n const y = height - ((v - min) / range) * height\n return `${i === 0 ? 'M' : 'L'}${x.toFixed(1)},${y.toFixed(1)}`\n })\n .join(' ')\n}\n\nfunction Sparkline({\n data,\n colorClass,\n}: {\n data: number[]\n colorClass: string\n}) {\n const id = React.useId()\n const pathRef = React.useRef<SVGPathElement>(null)\n const [pathLength, setPathLength] = React.useState(0)\n const svgWidth = 80\n const svgHeight = 32\n const path = buildSparklinePath(data, svgWidth, svgHeight)\n\n React.useEffect(() => {\n if (pathRef.current) {\n setPathLength(pathRef.current.getTotalLength())\n }\n }, [path])\n\n if (!path) return null\n\n // Build area fill path (close to bottom-right then bottom-left)\n const areaPath = `${path} L${svgWidth.toFixed(1)},${svgHeight.toFixed(1)} L0,${svgHeight.toFixed(1)} Z`\n\n return (\n <div className={cn('w-20 h-8', colorClass)} aria-hidden=\"true\">\n <svg\n viewBox={`0 0 ${svgWidth} ${svgHeight}`}\n className=\"w-full h-full overflow-visible\"\n preserveAspectRatio=\"none\"\n >\n <defs>\n <linearGradient id={`sparkline-fill-${id}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor=\"currentColor\" stopOpacity=\"0.1\" />\n <stop offset=\"100%\" stopColor=\"currentColor\" stopOpacity=\"0\" />\n </linearGradient>\n </defs>\n <path d={areaPath} fill={`url(#sparkline-fill-${id})`} />\n <path\n ref={pathRef}\n d={path}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n style={{\n opacity: pathLength === 0 ? 0 : 1,\n strokeDasharray: pathLength,\n strokeDashoffset: pathLength,\n animation: pathLength > 0 ? `sparkline-draw-${id.replace(/:/g, '')} 1s ease-out forwards` : 'none',\n }}\n />\n </svg>\n {pathLength > 0 && (\n <style>{`@keyframes sparkline-draw-${id.replace(/:/g, '')} { to { stroke-dashoffset: 0; } }`}</style>\n )}\n </div>\n )\n}\n\nfunction ProgressBar({ progress, label }: { progress: number; label: string }) {\n const clamped = Math.max(0, Math.min(100, progress))\n const barColor =\n clamped >= 90 ? 'bg-success-9' : clamped >= 70 ? 'bg-warning-9' : 'bg-accent-9'\n\n return (\n <div className=\"h-1 w-full rounded-pill bg-surface-raised mt-ds-04\" role=\"progressbar\" aria-label={`${label} progress`} aria-valuenow={clamped} aria-valuemin={0} aria-valuemax={100}>\n <div\n className={cn('h-full rounded-pill transition-[width] duration-moderate-02 ease-productive-standard', barColor)}\n style={{ width: `${clamped}%` }}\n />\n </div>\n )\n}\n\nconst StatCard = React.forwardRef<HTMLDivElement, StatCardProps>(\n (\n {\n className,\n label,\n title,\n value,\n prefix,\n suffix,\n delta,\n icon,\n loading,\n comparisonLabel,\n secondaryLabel,\n progress,\n accent,\n sparkline,\n onClick,\n href,\n footer,\n ...props\n },\n ref,\n ) => {\n const Link = useLink()\n const resolvedLabel = title ?? label ?? ''\n const isClickable = !!(onClick || href)\n const computedAriaLabel =\n isClickable && !props['aria-label'] ? `View ${resolvedLabel}` : props['aria-label']\n\n if (loading) {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-surface border border-surface-border bg-surface-raised shadow-raised p-ds-05b',\n accent && `border-l-[3px] ${accentBorderMap[accent]}`,\n className,\n )}\n {...props}\n >\n <div className=\"h-ds-04 w-24 rounded-control-inner bg-skeleton-base animate-pulse mb-ds-05\" />\n <div className=\"h-ds-sm w-32 rounded-control bg-skeleton-base animate-pulse mb-ds-03\" />\n <div className=\"h-3 w-16 rounded-control-inner bg-skeleton-base animate-pulse\" />\n </div>\n )\n }\n\n const DeltaIcon =\n delta?.direction === 'up'\n ? IconTrendingUp\n : delta?.direction === 'down'\n ? IconTrendingDown\n : IconMinus\n\n const deltaColour =\n delta?.direction === 'up'\n ? 'text-success-11'\n : delta?.direction === 'down'\n ? 'text-error-11'\n : 'text-surface-fg-muted'\n\n const sparklineColor =\n delta?.direction === 'up'\n ? 'text-success-11'\n : delta?.direction === 'down'\n ? 'text-error-11'\n : 'text-accent-11'\n\n const cardContent = (\n <>\n <div className=\"flex items-center justify-between mb-ds-04\">\n <motion.p\n className=\"text-ds-md font-medium text-surface-fg-muted\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={tweens.fade}\n >\n {resolvedLabel}\n </motion.p>\n <div className=\"flex items-center gap-ds-03\">\n {sparkline && sparkline.length >= 2 && (\n <Sparkline data={sparkline} colorClass={sparklineColor} />\n )}\n {icon && (\n <motion.span\n className=\"text-surface-fg-muted\"\n initial={{ opacity: 0, scale: 0.96 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={springs.snappy}\n aria-hidden=\"true\"\n >\n <IconProvider size=\"lg\">{normalizeIcon(icon, 'lg')}</IconProvider>\n </motion.span>\n )}\n </div>\n </div>\n <div className=\"overflow-hidden\">\n <motion.p\n className=\"inline-block text-ds-3xl font-semibold text-surface-fg\"\n initial={{ opacity: 0, y: '100%' }}\n animate={{ opacity: 1, y: 0 }}\n transition={springs.smooth}\n >\n {prefix && (\n <span className=\"text-surface-fg-muted text-ds-lg\">{prefix}</span>\n )}\n <span className=\"tabular-nums\">{value}</span>\n {suffix && (\n <span className=\"text-surface-fg-muted text-ds-lg\">{suffix}</span>\n )}\n </motion.p>\n </div>\n {secondaryLabel && (\n <motion.p\n className=\"text-ds-sm text-surface-fg-subtle mt-ds-01\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ ...tweens.fade, delay: 0.1 }}\n >\n {secondaryLabel}\n </motion.p>\n )}\n {progress != null && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ ...tweens.fade, delay: 0.15 }}\n >\n <ProgressBar progress={progress} label={resolvedLabel} />\n </motion.div>\n )}\n {delta && (\n <motion.div\n className={cn(\n 'mt-ds-03 flex items-center gap-ds-02 text-ds-sm font-medium',\n deltaColour,\n )}\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ ...springs.smooth, delay: 0.2 }}\n >\n <motion.span\n className=\"inline-flex\"\n initial={{ opacity: 0.5, scale: 1.4 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={springs.bouncy}\n >\n <Icon icon={DeltaIcon} size=\"sm\" />\n </motion.span>\n <span>{delta.value}</span>\n {comparisonLabel && (\n <span className=\"text-surface-fg-subtle font-normal\">{comparisonLabel}</span>\n )}\n </motion.div>\n )}\n {footer && (\n <motion.div\n className=\"mt-ds-04 pt-ds-04 border-t border-surface-border text-ds-sm\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ ...tweens.fade, delay: 0.25 }}\n >\n {footer}\n </motion.div>\n )}\n </>\n )\n\n const cardClasses = cn(\n 'rounded-surface border border-surface-border bg-surface-raised shadow-raised p-ds-05b',\n accent && `border-l-[3px] ${accentBorderMap[accent]}`,\n isClickable &&\n 'cursor-pointer hover:shadow-raised-hover hover:border-surface-border-strong transition-[box-shadow,border-color] duration-fast-02 ease-productive-standard group',\n className,\n )\n\n if (href) {\n return (\n <Link\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n onClick={onClick}\n className={cn(cardClasses, 'block no-underline')}\n aria-label={computedAriaLabel}\n {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}\n >\n {cardContent}\n </Link>\n )\n }\n\n return (\n <div\n ref={ref}\n className={cardClasses}\n onClick={onClick}\n role={onClick ? 'button' : undefined}\n tabIndex={onClick ? 0 : undefined}\n aria-label={computedAriaLabel}\n onKeyDown={\n onClick\n ? (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onClick()\n }\n }\n : undefined\n }\n {...props}\n >\n {cardContent}\n </div>\n )\n },\n)\n\nStatCard.displayName = 'StatCard'\n\nexport { StatCard }\n"],"mappings":";;;;;;;;;;;;AAwFA,IAAM,IAAwE;CAC5E,SAAS;CACT,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACP;AAED,SAAS,EAAmB,GAAe,GAAe,GAAwB;CAChF,IAAM,IAAO,EAAI,QAAQ,MAAM,OAAO,SAAS,EAAE,CAAC;AAClD,KAAI,EAAK,SAAS,EAAG,QAAO;CAC5B,IAAM,IAAM,KAAK,IAAI,GAAG,EAAK,EAEvB,IADM,KAAK,IAAI,GAAG,EAAK,GACT,KAAO,GACrB,IAAQ,KAAS,EAAK,SAAS;AACrC,QAAO,EACJ,KAAK,GAAG,MAAM;EACb,IAAM,IAAI,IAAI,GACR,IAAI,KAAW,IAAI,KAAO,IAAS;AACzC,SAAO,GAAG,MAAM,IAAI,MAAM,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE;GAC5D,CACD,KAAK,IAAI;;AAGd,SAAS,EAAU,EACjB,SACA,iBAIC;CACD,IAAM,IAAK,EAAM,OAAO,EAClB,IAAU,EAAM,OAAuB,KAAK,EAC5C,CAAC,GAAY,KAAiB,EAAM,SAAS,EAAE,EAG/C,IAAO,EAAmB,GAAM,IAAU,GAAU;AAQ1D,KANA,EAAM,gBAAgB;AACpB,EAAI,EAAQ,WACV,EAAc,EAAQ,QAAQ,gBAAgB,CAAC;IAEhD,CAAC,EAAK,CAAC,EAEN,CAAC,EAAM,QAAO;CAGlB,IAAM,IAAW,GAAG,EAAK,IAAI,IAAS,QAAQ,EAAE,CAAC,GAAG,IAAU,QAAQ,EAAE,CAAC,MAAM,IAAU,QAAQ,EAAE,CAAC;AAEpG,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,YAAY,EAAW;EAAE,eAAY;YAAxD,CACE,kBAAC,OAAD;GACE,SAAS;GACT,WAAU;GACV,qBAAoB;aAHtB;IAKE,kBAAC,QAAD,EAAA,UACE,kBAAC,kBAAD;KAAgB,IAAI,kBAAkB;KAAM,IAAG;KAAI,IAAG;KAAI,IAAG;KAAI,IAAG;eAApE,CACE,kBAAC,QAAD;MAAM,QAAO;MAAK,WAAU;MAAe,aAAY;MAAQ,CAAA,EAC/D,kBAAC,QAAD;MAAM,QAAO;MAAO,WAAU;MAAe,aAAY;MAAM,CAAA,CAChD;QACZ,CAAA;IACP,kBAAC,QAAD;KAAS;KAAU,MAAM,uBAAuB,EAAG;KAAM,CAAA;IACzD,kBAAC,QAAD;KACE,KAAK;KACL,GAAG;KACH,MAAK;KACL,QAAO;KACP,aAAY;KACZ,eAAc;KACd,gBAAe;KACf,OAAO;MACL,SAAS,MAAe,IAAI,IAAI;MAChC,iBAAiB;MACjB,kBAAkB;MAClB,WAAW,IAAa,IAAI,kBAAkB,EAAG,QAAQ,MAAM,GAAG,CAAC,yBAAyB;MAC7F;KACD,CAAA;IACE;MACL,IAAa,KACZ,kBAAC,SAAD,EAAA,UAAQ,6BAA6B,EAAG,QAAQ,MAAM,GAAG,CAAC,oCAA2C,CAAA,CAEnG;;;AAIV,SAAS,EAAY,EAAE,aAAU,YAA8C;CAC7E,IAAM,IAAU,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,EAAS,CAAC,EAC9C,IACJ,KAAW,KAAK,iBAAiB,KAAW,KAAK,iBAAiB;AAEpE,QACE,kBAAC,OAAD;EAAK,WAAU;EAAqD,MAAK;EAAc,cAAY,GAAG,EAAM;EAAY,iBAAe;EAAS,iBAAe;EAAG,iBAAe;YAC/K,kBAAC,OAAD;GACE,WAAW,EAAG,wFAAwF,EAAS;GAC/G,OAAO,EAAE,OAAO,GAAG,EAAQ,IAAI;GAC/B,CAAA;EACE,CAAA;;AAIV,IAAM,IAAW,EAAM,YAEnB,EACE,cACA,UACA,UACA,UACA,WACA,WACA,UACA,SACA,YACA,oBACA,mBACA,aACA,WACA,cACA,YACA,SACA,WACA,GAAG,KAEL,MACG;CACH,IAAM,IAAO,GAAS,EAChB,IAAgB,KAAS,KAAS,IAClC,IAAc,CAAC,EAAE,KAAW,IAC5B,IACJ,KAAe,CAAC,EAAM,gBAAgB,QAAQ,MAAkB,EAAM;AAExE,KAAI,EACF,QACE,kBAAC,OAAD;EACO;EACL,WAAW,EACT,yFACA,KAAU,kBAAkB,EAAgB,MAC5C,EACD;EACD,GAAI;YAPN;GASE,kBAAC,OAAD,EAAK,WAAU,8EAA+E,CAAA;GAC9F,kBAAC,OAAD,EAAK,WAAU,wEAAyE,CAAA;GACxF,kBAAC,OAAD,EAAK,WAAU,iEAAkE,CAAA;GAC7E;;CAIV,IAAM,IACJ,GAAO,cAAc,OACjB,IACA,GAAO,cAAc,SACnB,IACA,GAEF,IACJ,GAAO,cAAc,OACjB,oBACA,GAAO,cAAc,SACnB,kBACA,yBAEF,IACJ,GAAO,cAAc,OACjB,oBACA,GAAO,cAAc,SACnB,kBACA,kBAEF,IACJ,kBAAA,GAAA,EAAA,UAAA;EACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,EAAO,GAAR;IACE,WAAU;IACV,SAAS,EAAE,SAAS,GAAG;IACvB,SAAS,EAAE,SAAS,GAAG;IACvB,YAAY,EAAO;cAElB;IACQ,CAAA,EACX,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,KAAa,EAAU,UAAU,KAChC,kBAAC,GAAD;KAAW,MAAM;KAAW,YAAY;KAAkB,CAAA,EAE3D,KACC,kBAAC,EAAO,MAAR;KACE,WAAU;KACV,SAAS;MAAE,SAAS;MAAG,OAAO;MAAM;KACpC,SAAS;MAAE,SAAS;MAAG,OAAO;MAAG;KACjC,YAAY,EAAQ;KACpB,eAAY;eAEZ,kBAAC,GAAD;MAAc,MAAK;gBAAM,EAAc,GAAM,KAAK;MAAgB,CAAA;KACtD,CAAA,CAEZ;MACF;;EACN,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,EAAO,GAAR;IACE,WAAU;IACV,SAAS;KAAE,SAAS;KAAG,GAAG;KAAQ;IAClC,SAAS;KAAE,SAAS;KAAG,GAAG;KAAG;IAC7B,YAAY,EAAQ;cAJtB;KAMG,KACC,kBAAC,QAAD;MAAM,WAAU;gBAAoC;MAAc,CAAA;KAEpE,kBAAC,QAAD;MAAM,WAAU;gBAAgB;MAAa,CAAA;KAC5C,KACC,kBAAC,QAAD;MAAM,WAAU;gBAAoC;MAAc,CAAA;KAE3D;;GACP,CAAA;EACL,KACC,kBAAC,EAAO,GAAR;GACE,WAAU;GACV,SAAS,EAAE,SAAS,GAAG;GACvB,SAAS,EAAE,SAAS,GAAG;GACvB,YAAY;IAAE,GAAG,EAAO;IAAM,OAAO;IAAK;aAEzC;GACQ,CAAA;EAEZ,KAAY,QACX,kBAAC,EAAO,KAAR;GACE,SAAS,EAAE,SAAS,GAAG;GACvB,SAAS,EAAE,SAAS,GAAG;GACvB,YAAY;IAAE,GAAG,EAAO;IAAM,OAAO;IAAM;aAE3C,kBAAC,GAAD;IAAuB;IAAU,OAAO;IAAiB,CAAA;GAC9C,CAAA;EAEd,KACC,kBAAC,EAAO,KAAR;GACE,WAAW,EACT,+DACA,EACD;GACD,SAAS;IAAE,SAAS;IAAG,GAAG;IAAG;GAC7B,SAAS;IAAE,SAAS;IAAG,GAAG;IAAG;GAC7B,YAAY;IAAE,GAAG,EAAQ;IAAQ,OAAO;IAAK;aAP/C;IASE,kBAAC,EAAO,MAAR;KACE,WAAU;KACV,SAAS;MAAE,SAAS;MAAK,OAAO;MAAK;KACrC,SAAS;MAAE,SAAS;MAAG,OAAO;MAAG;KACjC,YAAY,EAAQ;eAEpB,kBAAC,GAAD;MAAM,MAAM;MAAW,MAAK;MAAO,CAAA;KACvB,CAAA;IACd,kBAAC,QAAD,EAAA,UAAO,EAAM,OAAa,CAAA;IACzB,KACC,kBAAC,QAAD;KAAM,WAAU;eAAsC;KAAuB,CAAA;IAEpE;;EAEd,KACC,kBAAC,EAAO,KAAR;GACE,WAAU;GACV,SAAS,EAAE,SAAS,GAAG;GACvB,SAAS,EAAE,SAAS,GAAG;GACvB,YAAY;IAAE,GAAG,EAAO;IAAM,OAAO;IAAM;aAE1C;GACU,CAAA;EAEd,EAAA,CAAA,EAGC,IAAc,EAClB,yFACA,KAAU,kBAAkB,EAAgB,MAC5C,KACE,oKACF,EACD;AAiBD,QAfI,IAEA,kBAAC,GAAD;EACO;EACC;EACG;EACT,WAAW,EAAG,GAAa,qBAAqB;EAChD,cAAY;EACZ,GAAK;YAEJ;EACI,CAAA,GAKT,kBAAC,OAAD;EACO;EACL,WAAW;EACF;EACT,MAAM,IAAU,WAAW,KAAA;EAC3B,UAAU,IAAU,IAAI,KAAA;EACxB,cAAY;EACZ,WACE,KACK,MAA2B;AAC1B,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,GAAS;MAGb,KAAA;EAEN,GAAI;YAEH;EACG,CAAA;EAGX;AAED,EAAS,cAAc"}
1
+ {"version":3,"file":"stat-card.js","names":[],"sources":["../../src/ui/stat-card.tsx"],"sourcesContent":["'use client'\n\nimport { IconMinus, IconTrendingDown, IconTrendingUp } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Icon } from './icon'\nimport { IconProvider } from './icon-context'\nimport type { IconInput } from './lib/icon-input'\nimport { useLink } from './lib/link-context'\nimport { springs, tweens } from './lib/motion'\nimport { normalizeIcon } from './lib/normalize-icon'\nimport { cn } from './lib/utils'\nimport { StatFlash, type FlashPreset, type FlashSpec, type FlashSpeed } from './stat-flash'\n\n/**\n * Props for StatCard — a dashboard metric tile displaying a label, a large numeric value,\n * an optional trend delta (with directional arrow icon), and an optional header icon.\n *\n * **Surface:** `surface=\"raised\"` (default) is elevation-led — the shadow's own 1px ring is the\n * edge, no border (make-kit rule #6). `surface=\"flat\"` is border-led, no shadow.\n *\n * **Accent:** `accentStyle=\"none\"` (default) is neutral; `\"icon\"` wraps `icon` in an accent chip\n * (`iconFill=\"soft\" | \"solid\"`); `\"tint\"` applies a subtle accent surface wash + accent value.\n * (Replaces the removed `accent` left-rail prop — see CHANGELOG migration.)\n *\n * **Delta direction:** `'up'` renders a green trending-up arrow, `'down'` renders a red\n * trending-down arrow, `'neutral'` renders a grey dash. The `delta.value` is a formatted\n * string (e.g. `\"+8%\"` or `\"−120\"`).\n *\n * **Loading state:** Pass `loading={true}` to render a pulse-skeleton placeholder instead of data.\n *\n * @example\n * // Revenue metric with a positive trend:\n * <StatCard\n * label=\"Monthly Revenue\"\n * value=\"$48,200\"\n * delta={{ value: \"+12%\", direction: \"up\" }}\n * icon={<IconCurrencyDollar />}\n * />\n *\n * @example\n * // Open ticket count with a downward trend (good for support queues):\n * <StatCard\n * label=\"Open Tickets\"\n * value={142}\n * delta={{ value: \"−18\", direction: \"down\" }}\n * />\n *\n * @example\n * // Loading skeleton while data is fetching:\n * <StatCard label=\"Users\" value={0} loading={isFetching} />\n *\n * @example\n * // Simple metric with no trend (stable, neutral):\n * <StatCard\n * label=\"Storage Used\"\n * value=\"4.2 GB\"\n * delta={{ value: \"No change\", direction: \"neutral\" }}\n * />\n * // These are just a few ways — feel free to combine props creatively!\n */\nexport interface StatCardProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Heading text for the metric. `title` is an alias for `label`. */\n label?: string\n /** Alias for `label` — use whichever feels natural. */\n title?: string\n value: string | number\n /** Prefix before value, e.g. \"$\" */\n prefix?: string\n /** Suffix after value, e.g. \"users\" */\n suffix?: string\n delta?: {\n value: string\n direction: 'up' | 'down' | 'neutral'\n }\n icon?: IconInput\n loading?: boolean\n /** Comparison period label shown after delta, e.g. \"vs last month\" */\n comparisonLabel?: string\n /** Secondary metric below main value, e.g. \"of $50,000 target\" */\n secondaryLabel?: string\n /** Progress toward a target (0-100). Renders a thin progress bar below the value. */\n progress?: number\n /** Surface treatment: `raised` (ring-in-shadow, no border — default) or `flat` (border, no shadow). */\n surface?: 'raised' | 'flat'\n /** How the brand accent reads: `none` (neutral — default), `icon` (accent chip around `icon`), or `tint` (subtle accent surface wash + accent value). */\n accentStyle?: 'none' | 'icon' | 'tint'\n /** When `accentStyle=\"icon\"`: `soft` (tinted chip) or `solid` (filled accent chip). @default 'soft' */\n iconFill?: 'soft' | 'solid'\n /**\n * Opt-in entrance flash: the icon chip mounts showing a transient state (e.g. a green up-arrow)\n * then settles to `icon`. A preset (`'up' | 'down' | 'goal' | 'record' | 'alert' | 'live'`) or an\n * explicit `{ tone, icon }`. Requires `icon`; implies the icon chip. Off by default.\n */\n flash?: FlashPreset | FlashSpec\n /** Speed of the entrance flash: `fast` | `normal` | `slow`. @default 'normal' */\n flashSpeed?: FlashSpeed\n /** Sparkline data points. Renders a mini SVG line chart in the card. */\n sparkline?: number[]\n /** Make the card clickable with hover state */\n onClick?: () => void\n /** Href — makes the card a link (uses the LinkContext Link component) */\n href?: string\n /** Footer content rendered below the card body, e.g. \"View details →\" */\n footer?: React.ReactNode\n}\n\nfunction buildSparklinePath(raw: number[], width: number, height: number): string {\n const data = raw.filter((v) => Number.isFinite(v))\n if (data.length < 2) return ''\n const min = Math.min(...data)\n const max = Math.max(...data)\n const range = max - min || 1\n const stepX = width / (data.length - 1)\n return data\n .map((v, i) => {\n const x = i * stepX\n const y = height - ((v - min) / range) * height\n return `${i === 0 ? 'M' : 'L'}${x.toFixed(1)},${y.toFixed(1)}`\n })\n .join(' ')\n}\n\nfunction Sparkline({\n data,\n colorClass,\n}: {\n data: number[]\n colorClass: string\n}) {\n const id = React.useId()\n const pathRef = React.useRef<SVGPathElement>(null)\n const [pathLength, setPathLength] = React.useState(0)\n const svgWidth = 80\n const svgHeight = 32\n const path = buildSparklinePath(data, svgWidth, svgHeight)\n\n React.useEffect(() => {\n if (pathRef.current) {\n setPathLength(pathRef.current.getTotalLength())\n }\n }, [path])\n\n if (!path) return null\n\n // Build area fill path (close to bottom-right then bottom-left)\n const areaPath = `${path} L${svgWidth.toFixed(1)},${svgHeight.toFixed(1)} L0,${svgHeight.toFixed(1)} Z`\n\n return (\n <div className={cn('w-20 h-8', colorClass)} aria-hidden=\"true\">\n <svg\n viewBox={`0 0 ${svgWidth} ${svgHeight}`}\n className=\"w-full h-full overflow-visible\"\n preserveAspectRatio=\"none\"\n >\n <defs>\n <linearGradient id={`sparkline-fill-${id}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor=\"currentColor\" stopOpacity=\"0.1\" />\n <stop offset=\"100%\" stopColor=\"currentColor\" stopOpacity=\"0\" />\n </linearGradient>\n </defs>\n <path d={areaPath} fill={`url(#sparkline-fill-${id})`} />\n <path\n ref={pathRef}\n d={path}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n style={{\n opacity: pathLength === 0 ? 0 : 1,\n strokeDasharray: pathLength,\n strokeDashoffset: pathLength,\n animation: pathLength > 0 ? `sparkline-draw-${id.replace(/:/g, '')} 1s ease-out forwards` : 'none',\n }}\n />\n </svg>\n {pathLength > 0 && (\n <style>{`@keyframes sparkline-draw-${id.replace(/:/g, '')} { to { stroke-dashoffset: 0; } }`}</style>\n )}\n </div>\n )\n}\n\nfunction ProgressBar({ progress, label }: { progress: number; label: string }) {\n const clamped = Math.max(0, Math.min(100, progress))\n const barColor =\n clamped >= 90 ? 'bg-success-9' : clamped >= 70 ? 'bg-warning-9' : 'bg-accent-9'\n\n return (\n <div className=\"h-1 w-full rounded-pill bg-surface-raised mt-ds-04\" role=\"progressbar\" aria-label={`${label} progress`} aria-valuenow={clamped} aria-valuemin={0} aria-valuemax={100}>\n <div\n className={cn('h-full rounded-pill transition-[width] duration-moderate-02 ease-productive-standard', barColor)}\n style={{ width: `${clamped}%` }}\n />\n </div>\n )\n}\n\nconst StatCard = React.forwardRef<HTMLDivElement, StatCardProps>(\n (\n {\n className,\n label,\n title,\n value,\n prefix,\n suffix,\n delta,\n icon,\n loading,\n comparisonLabel,\n secondaryLabel,\n progress,\n surface = 'raised',\n accentStyle = 'none',\n iconFill = 'soft',\n flash,\n flashSpeed,\n sparkline,\n onClick,\n href,\n footer,\n ...props\n },\n ref,\n ) => {\n const Link = useLink()\n const resolvedLabel = title ?? label ?? ''\n const isClickable = !!(onClick || href)\n const computedAriaLabel =\n isClickable && !props['aria-label'] ? `View ${resolvedLabel}` : props['aria-label']\n\n if (loading) {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-surface bg-surface-raised p-ds-05b',\n surface === 'flat' ? 'border border-surface-border' : 'shadow-raised',\n className,\n )}\n {...props}\n >\n <div className=\"h-ds-04 w-24 rounded-control-inner bg-skeleton-base animate-pulse mb-ds-05\" />\n <div className=\"h-ds-sm w-32 rounded-control bg-skeleton-base animate-pulse mb-ds-03\" />\n <div className=\"h-3 w-16 rounded-control-inner bg-skeleton-base animate-pulse\" />\n </div>\n )\n }\n\n const DeltaIcon =\n delta?.direction === 'up'\n ? IconTrendingUp\n : delta?.direction === 'down'\n ? IconTrendingDown\n : IconMinus\n\n const deltaColour =\n delta?.direction === 'up'\n ? 'text-success-11'\n : delta?.direction === 'down'\n ? 'text-error-11'\n : 'text-surface-fg-muted'\n\n const sparklineColor =\n delta?.direction === 'up'\n ? 'text-success-11'\n : delta?.direction === 'down'\n ? 'text-error-11'\n : 'text-accent-11'\n\n const cardContent = (\n <>\n <div className=\"flex items-center justify-between mb-ds-04\">\n <motion.p\n className=\"text-ds-md font-medium text-surface-fg-muted\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={tweens.fade}\n >\n {resolvedLabel}\n </motion.p>\n <div className=\"flex items-center gap-ds-03\">\n {sparkline && sparkline.length >= 2 && (\n <Sparkline data={sparkline} colorClass={sparklineColor} />\n )}\n {icon && flash ? (\n <StatFlash icon={icon} flash={flash} fill={iconFill} speed={flashSpeed} />\n ) : icon ? (\n accentStyle === 'icon' ? (\n <motion.span\n className={cn(\n 'inline-flex items-center justify-center rounded-control p-ds-02',\n iconFill === 'solid'\n ? 'bg-accent-9 text-accent-fg'\n : 'bg-accent-3 text-accent-11',\n )}\n initial={{ opacity: 0, scale: 0.96 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={springs.snappy}\n aria-hidden=\"true\"\n >\n <IconProvider size=\"md\">{normalizeIcon(icon, 'md')}</IconProvider>\n </motion.span>\n ) : (\n <motion.span\n className=\"text-surface-fg-muted\"\n initial={{ opacity: 0, scale: 0.96 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={springs.snappy}\n aria-hidden=\"true\"\n >\n <IconProvider size=\"lg\">{normalizeIcon(icon, 'lg')}</IconProvider>\n </motion.span>\n )\n ) : null}\n </div>\n </div>\n <div className=\"overflow-hidden\">\n <motion.p\n className={cn(\n 'inline-block text-ds-3xl font-semibold',\n accentStyle === 'tint' ? 'text-accent-11' : 'text-surface-fg',\n )}\n initial={{ opacity: 0, y: '100%' }}\n animate={{ opacity: 1, y: 0 }}\n transition={springs.smooth}\n >\n {prefix && (\n <span className=\"text-surface-fg-muted text-ds-lg\">{prefix}</span>\n )}\n <span className=\"tabular-nums\">{value}</span>\n {suffix && (\n <span className=\"text-surface-fg-muted text-ds-lg\">{suffix}</span>\n )}\n </motion.p>\n </div>\n {secondaryLabel && (\n <motion.p\n className=\"text-ds-sm text-surface-fg-subtle mt-ds-01\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ ...tweens.fade, delay: 0.1 }}\n >\n {secondaryLabel}\n </motion.p>\n )}\n {progress != null && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ ...tweens.fade, delay: 0.15 }}\n >\n <ProgressBar progress={progress} label={resolvedLabel} />\n </motion.div>\n )}\n {delta && (\n <motion.div\n className={cn(\n 'mt-ds-03 flex items-center gap-ds-02 text-ds-sm font-medium',\n deltaColour,\n )}\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ ...springs.smooth, delay: 0.2 }}\n >\n <motion.span\n className=\"inline-flex\"\n initial={{ opacity: 0.5, scale: 1.4 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={springs.bouncy}\n >\n <Icon icon={DeltaIcon} size=\"sm\" />\n </motion.span>\n <span>{delta.value}</span>\n {comparisonLabel && (\n <span className=\"text-surface-fg-subtle font-normal\">{comparisonLabel}</span>\n )}\n </motion.div>\n )}\n {footer && (\n <motion.div\n className=\"mt-ds-04 pt-ds-04 border-t border-surface-border text-ds-sm\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ ...tweens.fade, delay: 0.25 }}\n >\n {footer}\n </motion.div>\n )}\n </>\n )\n\n const cardClasses = cn(\n 'rounded-surface p-ds-05b',\n accentStyle === 'tint'\n ? 'bg-linear-to-t from-accent-2 to-surface-raised'\n : 'bg-surface-raised',\n surface === 'flat' ? 'border border-surface-border' : 'shadow-raised',\n isClickable &&\n 'cursor-pointer hover:shadow-raised-hover transition-[box-shadow] duration-fast-02 ease-productive-standard group',\n className,\n )\n\n if (href) {\n return (\n <Link\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n onClick={onClick}\n className={cn(cardClasses, 'block no-underline')}\n aria-label={computedAriaLabel}\n {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}\n >\n {cardContent}\n </Link>\n )\n }\n\n return (\n <div\n ref={ref}\n className={cardClasses}\n onClick={onClick}\n role={onClick ? 'button' : undefined}\n tabIndex={onClick ? 0 : undefined}\n aria-label={computedAriaLabel}\n onKeyDown={\n onClick\n ? (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onClick()\n }\n }\n : undefined\n }\n {...props}\n >\n {cardContent}\n </div>\n )\n },\n)\n\nStatCard.displayName = 'StatCard'\n\nexport { StatCard }\n"],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ "use client";
2
+ import { Transition } from 'framer-motion';
3
+ import { IconInput } from './lib/icon-input';
4
+ export type FlashTone = 'success' | 'error' | 'warning' | 'info' | 'accent';
5
+ /** Named entrance states. Each maps to a tone + glyph. */
6
+ export type FlashPreset = 'up' | 'down' | 'goal' | 'record' | 'alert' | 'live';
7
+ /** Explicit flash: any tone + any glyph. */
8
+ export interface FlashSpec {
9
+ tone: FlashTone;
10
+ icon: IconInput;
11
+ }
12
+ /** Composable speed — coarse presets built from the DS motion tokens. */
13
+ export type FlashSpeed = 'fast' | 'normal' | 'slow';
14
+ export interface StatFlashProps {
15
+ /** Settled identity glyph, shown after the flash resolves. */
16
+ icon: IconInput;
17
+ /** The entrance flash — a named preset or an explicit `{ tone, icon }`. */
18
+ flash: FlashPreset | FlashSpec;
19
+ /** Resting chip style once settled. @default 'soft' */
20
+ fill?: 'soft' | 'solid';
21
+ /** Coarse speed preset (hold + settle + fade), built from DS motion tokens. @default 'normal' */
22
+ speed?: FlashSpeed;
23
+ /** Override how long the flash holds before settling, in ms. Wins over `speed`. */
24
+ holdMs?: number;
25
+ /** Override the settle (icon scale-in) transition. Wins over `speed`. */
26
+ settleTransition?: Transition;
27
+ /** Override the flash enter/exit fade transition. Wins over `speed`. */
28
+ flashTransition?: Transition;
29
+ }
30
+ /**
31
+ * StatFlash — a chip that mounts showing a transient **state** (a toned glyph: an up-arrow on
32
+ * green, a check, an alert…) then settles into the metric's stable **identity** icon. The pattern
33
+ * is *state → identity*: communicate what just happened, then resolve to what the metric is.
34
+ *
35
+ * Used by `StatCard` via its `flash` prop, but standalone too (list rows, badges, toasts).
36
+ * Honors `prefers-reduced-motion` — renders the settled identity directly, no flash.
37
+ *
38
+ * @example
39
+ * <StatFlash icon={<IconFolder />} flash="up" /> // green up-arrow → folder
40
+ * <StatFlash icon={<IconBolt />} flash={{ tone: 'info', icon: <IconActivity /> }} />
41
+ */
42
+ export declare function StatFlash({ icon, flash, fill, speed, holdMs, settleTransition, flashTransition, }: StatFlashProps): import("react/jsx-runtime").JSX.Element;
43
+ export declare namespace StatFlash {
44
+ var displayName: string;
45
+ }
46
+ //# sourceMappingURL=stat-flash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stat-flash.d.ts","sourceRoot":"","sources":["../../src/ui/stat-flash.tsx"],"names":[],"mappings":"AAUA,OAAO,EAA2B,KAAK,UAAU,EAAoB,MAAM,eAAe,CAAA;AAI1F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAKjD,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAA;AAE3E,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAA;AAE9E,4CAA4C;AAC5C,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,SAAS,CAAA;IACf,IAAI,EAAE,SAAS,CAAA;CAChB;AA2BD,yEAAyE;AACzE,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAA;AAQnD,MAAM,WAAW,cAAc;IAC7B,8DAA8D;IAC9D,IAAI,EAAE,SAAS,CAAA;IACf,2EAA2E;IAC3E,KAAK,EAAE,WAAW,GAAG,SAAS,CAAA;IAC9B,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB,iGAAiG;IACjG,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,mFAAmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,UAAU,CAAA;IAC7B,wEAAwE;IACxE,eAAe,CAAC,EAAE,UAAU,CAAA;CAC7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,KAAK,EACL,IAAa,EACb,KAAgB,EAChB,MAAM,EACN,gBAAgB,EAChB,eAAe,GAChB,EAAE,cAAc,2CA0DhB;yBAlEe,SAAS"}
@@ -0,0 +1,114 @@
1
+ "use client";
2
+ import { IconProvider as e } from "./icon-context.js";
3
+ import { springs as t, tweens as n } from "./lib/motion.js";
4
+ import { cn as r } from "./lib/utils.js";
5
+ import { t as i } from "../_chunks/normalize-icon.js";
6
+ import * as a from "react";
7
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
8
+ import { IconAlertTriangle as c, IconArrowDown as l, IconArrowUp as u, IconBolt as d, IconCheck as f, IconStar as p } from "@tabler/icons-react";
9
+ import { AnimatePresence as m, motion as h, useReducedMotion as g } from "framer-motion";
10
+ //#region src/ui/stat-flash.tsx
11
+ var _ = {
12
+ up: {
13
+ tone: "success",
14
+ Glyph: u
15
+ },
16
+ down: {
17
+ tone: "error",
18
+ Glyph: l
19
+ },
20
+ goal: {
21
+ tone: "success",
22
+ Glyph: f
23
+ },
24
+ record: {
25
+ tone: "accent",
26
+ Glyph: p
27
+ },
28
+ alert: {
29
+ tone: "warning",
30
+ Glyph: c
31
+ },
32
+ live: {
33
+ tone: "info",
34
+ Glyph: d
35
+ }
36
+ }, v = {
37
+ success: "bg-success-9",
38
+ error: "bg-error-9",
39
+ warning: "bg-warning-9",
40
+ info: "bg-info-9",
41
+ accent: "bg-accent-9"
42
+ }, y = {
43
+ success: "text-success-fg",
44
+ error: "text-error-fg",
45
+ warning: "text-warning-fg",
46
+ info: "text-info-fg",
47
+ accent: "text-accent-fg"
48
+ }, b = {
49
+ fast: {
50
+ holdMs: 450,
51
+ settle: t.snappy,
52
+ fade: n.fade
53
+ },
54
+ normal: {
55
+ holdMs: 650,
56
+ settle: t.snappy,
57
+ fade: n.fade
58
+ },
59
+ slow: {
60
+ holdMs: 950,
61
+ settle: t.gentle,
62
+ fade: n.elegant
63
+ }
64
+ };
65
+ function x({ icon: t, flash: n, fill: c = "soft", speed: l = "normal", holdMs: u, settleTransition: d, flashTransition: f }) {
66
+ let p = g(), x = b[l], C = u ?? x.holdMs, w = d ?? x.settle, T = f ?? x.fade, E = typeof n == "string" ? {
67
+ tone: _[n].tone,
68
+ icon: /* @__PURE__ */ o(S, { preset: n })
69
+ } : n, [D, O] = a.useState(!1);
70
+ return a.useEffect(() => {
71
+ if (p) {
72
+ O(!0);
73
+ return;
74
+ }
75
+ let e = setTimeout(() => O(!0), C);
76
+ return () => clearTimeout(e);
77
+ }, [p, C]), /* @__PURE__ */ s("span", {
78
+ className: r("relative inline-flex items-center justify-center overflow-hidden rounded-control p-ds-02", c === "solid" ? "bg-accent-9 text-accent-fg" : "bg-accent-3 text-accent-11"),
79
+ "aria-hidden": "true",
80
+ children: [/* @__PURE__ */ o(h.span, {
81
+ className: "inline-flex",
82
+ initial: p ? !1 : {
83
+ opacity: 0,
84
+ scale: .55
85
+ },
86
+ animate: {
87
+ opacity: +!!D,
88
+ scale: D ? 1 : .55
89
+ },
90
+ transition: w,
91
+ children: /* @__PURE__ */ o(e, {
92
+ size: "md",
93
+ children: i(t, "md")
94
+ })
95
+ }), /* @__PURE__ */ o(m, { children: !D && /* @__PURE__ */ o(h.span, {
96
+ className: r("absolute inset-0 inline-flex items-center justify-center", v[E.tone], y[E.tone]),
97
+ initial: { opacity: 0 },
98
+ animate: { opacity: 1 },
99
+ exit: { opacity: 0 },
100
+ transition: T,
101
+ children: /* @__PURE__ */ o(e, {
102
+ size: "md",
103
+ children: i(E.icon, "md")
104
+ })
105
+ }) })]
106
+ });
107
+ }
108
+ function S({ preset: e }) {
109
+ let { Glyph: t } = _[e];
110
+ return /* @__PURE__ */ o(t, {});
111
+ }
112
+ x.displayName = "StatFlash";
113
+ //#endregion
114
+ export { x as StatFlash };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stat-flash.js","names":[],"sources":["../../src/ui/stat-flash.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconAlertTriangle,\n IconArrowDown,\n IconArrowUp,\n IconBolt,\n IconCheck,\n IconStar,\n} from '@tabler/icons-react'\nimport { AnimatePresence, motion, type Transition, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { IconProvider } from './icon-context'\nimport type { IconInput } from './lib/icon-input'\nimport { springs, tweens } from './lib/motion'\nimport { normalizeIcon } from './lib/normalize-icon'\nimport { cn } from './lib/utils'\n\nexport type FlashTone = 'success' | 'error' | 'warning' | 'info' | 'accent'\n\n/** Named entrance states. Each maps to a tone + glyph. */\nexport type FlashPreset = 'up' | 'down' | 'goal' | 'record' | 'alert' | 'live'\n\n/** Explicit flash: any tone + any glyph. */\nexport interface FlashSpec {\n tone: FlashTone\n icon: IconInput\n}\n\nconst FLASH_PRESETS: Record<FlashPreset, { tone: FlashTone; Glyph: React.ComponentType }> = {\n up: { tone: 'success', Glyph: IconArrowUp },\n down: { tone: 'error', Glyph: IconArrowDown },\n goal: { tone: 'success', Glyph: IconCheck },\n record: { tone: 'accent', Glyph: IconStar },\n alert: { tone: 'warning', Glyph: IconAlertTriangle },\n live: { tone: 'info', Glyph: IconBolt },\n}\n\nconst toneBg: Record<FlashTone, string> = {\n success: 'bg-success-9',\n error: 'bg-error-9',\n warning: 'bg-warning-9',\n info: 'bg-info-9',\n accent: 'bg-accent-9',\n}\n\nconst toneFg: Record<FlashTone, string> = {\n success: 'text-success-fg',\n error: 'text-error-fg',\n warning: 'text-warning-fg',\n info: 'text-info-fg',\n accent: 'text-accent-fg',\n}\n\n/** Composable speed — coarse presets built from the DS motion tokens. */\nexport type FlashSpeed = 'fast' | 'normal' | 'slow'\n\nconst SPEEDS: Record<FlashSpeed, { holdMs: number; settle: Transition; fade: Transition }> = {\n fast: { holdMs: 450, settle: springs.snappy, fade: tweens.fade },\n normal: { holdMs: 650, settle: springs.snappy, fade: tweens.fade },\n slow: { holdMs: 950, settle: springs.gentle, fade: tweens.elegant },\n}\n\nexport interface StatFlashProps {\n /** Settled identity glyph, shown after the flash resolves. */\n icon: IconInput\n /** The entrance flash — a named preset or an explicit `{ tone, icon }`. */\n flash: FlashPreset | FlashSpec\n /** Resting chip style once settled. @default 'soft' */\n fill?: 'soft' | 'solid'\n /** Coarse speed preset (hold + settle + fade), built from DS motion tokens. @default 'normal' */\n speed?: FlashSpeed\n /** Override how long the flash holds before settling, in ms. Wins over `speed`. */\n holdMs?: number\n /** Override the settle (icon scale-in) transition. Wins over `speed`. */\n settleTransition?: Transition\n /** Override the flash enter/exit fade transition. Wins over `speed`. */\n flashTransition?: Transition\n}\n\n/**\n * StatFlash — a chip that mounts showing a transient **state** (a toned glyph: an up-arrow on\n * green, a check, an alert…) then settles into the metric's stable **identity** icon. The pattern\n * is *state → identity*: communicate what just happened, then resolve to what the metric is.\n *\n * Used by `StatCard` via its `flash` prop, but standalone too (list rows, badges, toasts).\n * Honors `prefers-reduced-motion` — renders the settled identity directly, no flash.\n *\n * @example\n * <StatFlash icon={<IconFolder />} flash=\"up\" /> // green up-arrow → folder\n * <StatFlash icon={<IconBolt />} flash={{ tone: 'info', icon: <IconActivity /> }} />\n */\nexport function StatFlash({\n icon,\n flash,\n fill = 'soft',\n speed = 'normal',\n holdMs,\n settleTransition,\n flashTransition,\n}: StatFlashProps) {\n const prefersReduced = useReducedMotion()\n const preset = SPEEDS[speed]\n const hold = holdMs ?? preset.holdMs\n const settleT = settleTransition ?? preset.settle\n const fadeT = flashTransition ?? preset.fade\n\n const resolved =\n typeof flash === 'string'\n ? { tone: FLASH_PRESETS[flash].tone, icon: <FlashPresetGlyph preset={flash} /> }\n : flash\n\n const [settled, setSettled] = React.useState(false)\n\n React.useEffect(() => {\n if (prefersReduced) {\n setSettled(true)\n return\n }\n const t = setTimeout(() => setSettled(true), hold)\n return () => clearTimeout(t)\n }, [prefersReduced, hold])\n\n return (\n <span\n className={cn(\n 'relative inline-flex items-center justify-center overflow-hidden rounded-control p-ds-02',\n fill === 'solid' ? 'bg-accent-9 text-accent-fg' : 'bg-accent-3 text-accent-11',\n )}\n aria-hidden=\"true\"\n >\n <motion.span\n className=\"inline-flex\"\n initial={prefersReduced ? false : { opacity: 0, scale: 0.55 }}\n animate={{ opacity: settled ? 1 : 0, scale: settled ? 1 : 0.55 }}\n transition={settleT}\n >\n <IconProvider size=\"md\">{normalizeIcon(icon, 'md')}</IconProvider>\n </motion.span>\n <AnimatePresence>\n {!settled && (\n <motion.span\n className={cn(\n 'absolute inset-0 inline-flex items-center justify-center',\n toneBg[resolved.tone],\n toneFg[resolved.tone],\n )}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={fadeT}\n >\n <IconProvider size=\"md\">{normalizeIcon(resolved.icon, 'md')}</IconProvider>\n </motion.span>\n )}\n </AnimatePresence>\n </span>\n )\n}\n\nfunction FlashPresetGlyph({ preset }: { preset: FlashPreset }) {\n const { Glyph } = FLASH_PRESETS[preset]\n return <Glyph />\n}\n\nStatFlash.displayName = 'StatFlash'\n"],"mappings":";;;;;;;;;;AA8BA,IAAM,IAAsF;CAC1F,IAAI;EAAE,MAAM;EAAW,OAAO;EAAa;CAC3C,MAAM;EAAE,MAAM;EAAS,OAAO;EAAe;CAC7C,MAAM;EAAE,MAAM;EAAW,OAAO;EAAW;CAC3C,QAAQ;EAAE,MAAM;EAAU,OAAO;EAAU;CAC3C,OAAO;EAAE,MAAM;EAAW,OAAO;EAAmB;CACpD,MAAM;EAAE,MAAM;EAAQ,OAAO;EAAU;CACxC,EAEK,IAAoC;CACxC,SAAS;CACT,OAAO;CACP,SAAS;CACT,MAAM;CACN,QAAQ;CACT,EAEK,IAAoC;CACxC,SAAS;CACT,OAAO;CACP,SAAS;CACT,MAAM;CACN,QAAQ;CACT,EAKK,IAAuF;CAC3F,MAAM;EAAE,QAAQ;EAAK,QAAQ,EAAQ;EAAQ,MAAM,EAAO;EAAM;CAChE,QAAQ;EAAE,QAAQ;EAAK,QAAQ,EAAQ;EAAQ,MAAM,EAAO;EAAM;CAClE,MAAM;EAAE,QAAQ;EAAK,QAAQ,EAAQ;EAAQ,MAAM,EAAO;EAAS;CACpE;AA+BD,SAAgB,EAAU,EACxB,SACA,UACA,UAAO,QACP,WAAQ,UACR,WACA,qBACA,sBACiB;CACjB,IAAM,IAAiB,GAAkB,EACnC,IAAS,EAAO,IAChB,IAAO,KAAU,EAAO,QACxB,IAAU,KAAoB,EAAO,QACrC,IAAQ,KAAmB,EAAO,MAElC,IACJ,OAAO,KAAU,WACb;EAAE,MAAM,EAAc,GAAO;EAAM,MAAM,kBAAC,GAAD,EAAkB,QAAQ,GAAS,CAAA;EAAE,GAC9E,GAEA,CAAC,GAAS,KAAc,EAAM,SAAS,GAAM;AAWnD,QATA,EAAM,gBAAgB;AACpB,MAAI,GAAgB;AAClB,KAAW,GAAK;AAChB;;EAEF,IAAM,IAAI,iBAAiB,EAAW,GAAK,EAAE,EAAK;AAClD,eAAa,aAAa,EAAE;IAC3B,CAAC,GAAgB,EAAK,CAAC,EAGxB,kBAAC,QAAD;EACE,WAAW,EACT,4FACA,MAAS,UAAU,+BAA+B,6BACnD;EACD,eAAY;YALd,CAOE,kBAAC,EAAO,MAAR;GACE,WAAU;GACV,SAAS,IAAiB,KAAQ;IAAE,SAAS;IAAG,OAAO;IAAM;GAC7D,SAAS;IAAE,SAAS;IAAiB,OAAO,IAAU,IAAI;IAAM;GAChE,YAAY;aAEZ,kBAAC,GAAD;IAAc,MAAK;cAAM,EAAc,GAAM,KAAK;IAAgB,CAAA;GACtD,CAAA,EACd,kBAAC,GAAD,EAAA,UACG,CAAC,KACA,kBAAC,EAAO,MAAR;GACE,WAAW,EACT,4DACA,EAAO,EAAS,OAChB,EAAO,EAAS,MACjB;GACD,SAAS,EAAE,SAAS,GAAG;GACvB,SAAS,EAAE,SAAS,GAAG;GACvB,MAAM,EAAE,SAAS,GAAG;GACpB,YAAY;aAEZ,kBAAC,GAAD;IAAc,MAAK;cAAM,EAAc,EAAS,MAAM,KAAK;IAAgB,CAAA;GAC/D,CAAA,EAEA,CAAA,CACb;;;AAIX,SAAS,EAAiB,EAAE,aAAmC;CAC7D,IAAM,EAAE,aAAU,EAAc;AAChC,QAAO,kBAAC,GAAD,EAAS,CAAA;;AAGlB,EAAU,cAAc"}
package/dist/ui/toast.js CHANGED
@@ -87,7 +87,7 @@ function k({ type: a, title: o, description: u, action: d, cancel: f, duration:
87
87
  role: j ? "alert" : "status",
88
88
  "aria-live": j ? "assertive" : "polite",
89
89
  "aria-atomic": "true",
90
- className: "group relative flex w-full overflow-hidden rounded-overlay-sm border border-surface-border-strong bg-surface-overlay shadow-floating",
90
+ className: "group relative flex w-full overflow-hidden rounded-overlay-sm bg-surface-overlay shadow-floating",
91
91
  onMouseEnter: () => _(!0),
92
92
  onMouseLeave: () => _(!1),
93
93
  onFocusCapture: () => y(!0),
@@ -370,7 +370,7 @@ function I({ files: r, onRetry: a, onRemove: o, selfDismissId: d }) {
370
370
  role: j ? "alert" : "status",
371
371
  "aria-live": j ? "assertive" : "polite",
372
372
  "aria-label": "File uploads",
373
- className: "group relative flex w-full overflow-hidden rounded-overlay-sm border border-surface-border-strong bg-surface-overlay shadow-floating",
373
+ className: "group relative flex w-full overflow-hidden rounded-overlay-sm bg-surface-overlay shadow-floating",
374
374
  onMouseEnter: () => m(!0),
375
375
  onMouseLeave: () => m(!1),
376
376
  onFocusCapture: () => g(!0),