@devalok/shilp-sutra 0.44.1 → 0.45.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.
- package/AGENTS.md +13 -11
- package/BREAKING.json +209 -27
- package/MIGRATION.md +34 -0
- package/README.md +12 -0
- package/dist/_chunks/badge-group.js +1 -0
- package/dist/_chunks/badge-group.js.map +1 -1
- package/dist/_chunks/block-shell.js +25 -0
- package/dist/_chunks/block-shell.js.map +1 -0
- package/dist/_chunks/chat.js +4 -2
- package/dist/_chunks/chat.js.map +1 -1
- package/dist/_chunks/document-preview.js +1 -1
- package/dist/_chunks/image-preview.js +4 -4
- package/dist/_chunks/shared.js +65 -78
- package/dist/_chunks/shared.js.map +1 -1
- package/dist/_chunks/success.js +108 -106
- package/dist/_chunks/success.js.map +1 -1
- package/dist/ai/blocks/block-shell.d.ts +19 -0
- package/dist/ai/blocks/block-shell.d.ts.map +1 -0
- package/dist/ai/blocks/block-table.d.ts.map +1 -1
- package/dist/ai/blocks/confirm.d.ts.map +1 -1
- package/dist/ai/blocks/error.d.ts.map +1 -1
- package/dist/ai/blocks/error.js +5 -5
- package/dist/ai/blocks/error.js.map +1 -1
- package/dist/ai/blocks/success.d.ts.map +1 -1
- package/dist/ai/blocks/text.d.ts.map +1 -1
- package/dist/ai/blocks/text.js +4 -3
- package/dist/ai/blocks/text.js.map +1 -1
- package/dist/composed/activity-feed.d.ts.map +1 -1
- package/dist/composed/activity-feed.js +14 -12
- package/dist/composed/activity-feed.js.map +1 -1
- package/dist/composed/file-preview/audio-preview.d.ts.map +1 -1
- package/dist/composed/file-preview/shared.d.ts +10 -0
- package/dist/composed/file-preview/shared.d.ts.map +1 -1
- package/dist/composed/file-preview/video-preview.d.ts.map +1 -1
- package/dist/composed/file-preview.js +149 -188
- package/dist/composed/file-preview.js.map +1 -1
- package/dist/composed/master-detail.d.ts.map +1 -1
- package/dist/composed/master-detail.js +13 -4
- package/dist/composed/master-detail.js.map +1 -1
- package/dist/composed/schedule-view.d.ts.map +1 -1
- package/dist/composed/schedule-view.js +52 -39
- package/dist/composed/schedule-view.js.map +1 -1
- package/dist/shell/notification-preferences.d.ts.map +1 -1
- package/dist/shell/notification-preferences.js +115 -114
- package/dist/shell/notification-preferences.js.map +1 -1
- package/dist/ui/badge-group.d.ts.map +1 -1
- package/dist/ui/card.d.ts +66 -16
- package/dist/ui/card.d.ts.map +1 -1
- package/dist/ui/card.js +85 -60
- package/dist/ui/card.js.map +1 -1
- package/dist/ui/chat/message.d.ts.map +1 -1
- package/dist/ui/color-input.d.ts.map +1 -1
- package/dist/ui/color-input.js +18 -18
- package/dist/ui/color-input.js.map +1 -1
- package/dist/ui/data-table-body.d.ts.map +1 -1
- package/dist/ui/data-table-body.js +94 -64
- package/dist/ui/data-table-body.js.map +1 -1
- package/dist/ui/data-table-card.d.ts.map +1 -1
- package/dist/ui/data-table-card.js +50 -43
- package/dist/ui/data-table-card.js.map +1 -1
- package/dist/ui/data-table-context.d.ts +0 -4
- package/dist/ui/data-table-context.d.ts.map +1 -1
- package/dist/ui/data-table-context.js +8 -12
- package/dist/ui/data-table-context.js.map +1 -1
- package/dist/ui/data-table-header.d.ts.map +1 -1
- package/dist/ui/data-table-header.js +2 -2
- package/dist/ui/data-table-header.js.map +1 -1
- package/dist/ui/data-table.d.ts.map +1 -1
- package/dist/ui/data-table.js +185 -182
- package/dist/ui/data-table.js.map +1 -1
- package/dist/ui/index.d.ts +3 -2
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +59 -58
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/segmented-control.js +4 -4
- package/dist/ui/segmented-control.js.map +1 -1
- package/dist/ui/separator.d.ts +6 -5
- package/dist/ui/separator.d.ts.map +1 -1
- package/dist/ui/separator.js +7 -10
- package/dist/ui/separator.js.map +1 -1
- package/dist/ui/split-button.d.ts.map +1 -1
- package/dist/ui/split-button.js +62 -129
- package/dist/ui/split-button.js.map +1 -1
- package/dist/ui/stat-card.d.ts +7 -0
- package/dist/ui/stat-card.d.ts.map +1 -1
- package/dist/ui/stat-card.js +144 -126
- package/dist/ui/stat-card.js.map +1 -1
- package/dist/ui/table-row-link.d.ts +45 -0
- package/dist/ui/table-row-link.d.ts.map +1 -0
- package/dist/ui/table-row-link.js +16 -0
- package/dist/ui/table-row-link.js.map +1 -0
- package/dist/ui/table.d.ts +49 -5
- package/dist/ui/table.d.ts.map +1 -1
- package/dist/ui/table.js +39 -29
- package/dist/ui/table.js.map +1 -1
- package/dist/ui/toast-types.d.ts +10 -0
- package/dist/ui/toast-types.d.ts.map +1 -1
- package/dist/ui/toast.d.ts +6 -2
- package/dist/ui/toast.d.ts.map +1 -1
- package/dist/ui/toast.js +56 -54
- package/dist/ui/toast.js.map +1 -1
- package/docs/components/composed/content-card.md +2 -0
- package/docs/components/ui/button.md +3 -3
- package/docs/components/ui/card.md +27 -10
- package/docs/components/ui/color-input.md +2 -2
- package/docs/components/ui/data-table.md +8 -1
- package/docs/components/ui/segmented-control.md +2 -2
- package/docs/components/ui/separator.md +3 -1
- package/docs/components/ui/split-button.md +3 -0
- package/docs/components/ui/stat-card.md +10 -2
- package/docs/components/ui/table-row-link.md +41 -0
- package/docs/components/ui/table.md +69 -0
- package/docs/components/ui/toast.md +10 -6
- package/docs/recipes/install-next-app-router.md +1 -1
- package/docs/recipes/troubleshoot.md +1 -1
- package/llms.txt +159 -781
- package/make-kit/components/card.md +54 -16
- package/make-kit/components/table.md +45 -7
- package/make-kit/foundations/spacing.md +3 -1
- package/make-kit/foundations/surfaces.md +1 -1
- package/mcp-manifest.json +12349 -0
- package/mcp-manifest.schema.json +232 -0
- package/package.json +3 -3
- package/skill/README.md +0 -1
- package/skill/SKILL.md +9 -8
- package/skill/references/components.md +159 -781
- package/skill/references/setup-next-app-router.md +1 -1
- package/skill/references/troubleshoot.md +1 -1
- package/llms-full.txt +0 -7198
- package/llms-quick.txt +0 -251
- package/skill/references/components-full.md +0 -7200
package/dist/ui/stat-card.js
CHANGED
|
@@ -4,12 +4,12 @@ import { springs as t, tweens as n } from "./lib/motion.js";
|
|
|
4
4
|
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
|
-
import { Card as o, CardContent as s } from "./card.js";
|
|
8
|
-
import { n as
|
|
9
|
-
import { StatFlash as
|
|
10
|
-
import * as
|
|
11
|
-
import { Fragment as
|
|
12
|
-
import { IconMinus as
|
|
7
|
+
import { Card as o, CardContent as s, CardFooter as c } from "./card.js";
|
|
8
|
+
import { n as l } from "../_chunks/link-context.js";
|
|
9
|
+
import { StatFlash as u } from "./stat-flash.js";
|
|
10
|
+
import * as d from "react";
|
|
11
|
+
import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
12
|
+
import { IconMinus as h, IconTrendingDown as ee, IconTrendingUp as g } from "@tabler/icons-react";
|
|
13
13
|
import { motion as _ } from "framer-motion";
|
|
14
14
|
//#region src/ui/stat-card.tsx
|
|
15
15
|
function v(e, t, n) {
|
|
@@ -22,40 +22,40 @@ function v(e, t, n) {
|
|
|
22
22
|
}).join(" ");
|
|
23
23
|
}
|
|
24
24
|
function y({ data: e, colorClass: t }) {
|
|
25
|
-
let n =
|
|
26
|
-
if (
|
|
25
|
+
let n = d.useId(), i = d.useRef(null), [a, o] = d.useState(0), s = v(e, 80, 32);
|
|
26
|
+
if (d.useEffect(() => {
|
|
27
27
|
i.current && o(i.current.getTotalLength());
|
|
28
28
|
}, [s]), !s) return null;
|
|
29
29
|
let c = `${s} L${80 .toFixed(1)},${32 .toFixed(1)} L0,${32 .toFixed(1)} Z`;
|
|
30
|
-
return /* @__PURE__ */
|
|
30
|
+
return /* @__PURE__ */ m("div", {
|
|
31
31
|
className: r("w-20 h-8", t),
|
|
32
32
|
"aria-hidden": "true",
|
|
33
|
-
children: [/* @__PURE__ */
|
|
33
|
+
children: [/* @__PURE__ */ m("svg", {
|
|
34
34
|
viewBox: "0 0 80 32",
|
|
35
35
|
className: "w-full h-full overflow-visible",
|
|
36
36
|
preserveAspectRatio: "none",
|
|
37
37
|
children: [
|
|
38
|
-
/* @__PURE__ */
|
|
38
|
+
/* @__PURE__ */ p("defs", { children: /* @__PURE__ */ m("linearGradient", {
|
|
39
39
|
id: `sparkline-fill-${n}`,
|
|
40
40
|
x1: "0",
|
|
41
41
|
y1: "0",
|
|
42
42
|
x2: "0",
|
|
43
43
|
y2: "1",
|
|
44
|
-
children: [/* @__PURE__ */
|
|
44
|
+
children: [/* @__PURE__ */ p("stop", {
|
|
45
45
|
offset: "0%",
|
|
46
46
|
stopColor: "currentColor",
|
|
47
47
|
stopOpacity: "0.1"
|
|
48
|
-
}), /* @__PURE__ */
|
|
48
|
+
}), /* @__PURE__ */ p("stop", {
|
|
49
49
|
offset: "100%",
|
|
50
50
|
stopColor: "currentColor",
|
|
51
51
|
stopOpacity: "0"
|
|
52
52
|
})]
|
|
53
53
|
}) }),
|
|
54
|
-
/* @__PURE__ */
|
|
54
|
+
/* @__PURE__ */ p("path", {
|
|
55
55
|
d: c,
|
|
56
56
|
fill: `url(#sparkline-fill-${n})`
|
|
57
57
|
}),
|
|
58
|
-
/* @__PURE__ */
|
|
58
|
+
/* @__PURE__ */ p("path", {
|
|
59
59
|
ref: i,
|
|
60
60
|
d: s,
|
|
61
61
|
fill: "none",
|
|
@@ -71,39 +71,44 @@ function y({ data: e, colorClass: t }) {
|
|
|
71
71
|
}
|
|
72
72
|
})
|
|
73
73
|
]
|
|
74
|
-
}), a > 0 && /* @__PURE__ */
|
|
74
|
+
}), a > 0 && /* @__PURE__ */ p("style", { children: `@keyframes sparkline-draw-${n.replace(/:/g, "")} { to { stroke-dashoffset: 0; } }` })]
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
function b({ progress: e, label: t }) {
|
|
78
78
|
let n = Math.max(0, Math.min(100, e)), i = n >= 90 ? "bg-success-9" : n >= 70 ? "bg-warning-9" : "bg-accent-9";
|
|
79
|
-
return /* @__PURE__ */
|
|
80
|
-
className: "h-1 w-full rounded-pill bg-surface-raised
|
|
79
|
+
return /* @__PURE__ */ p("div", {
|
|
80
|
+
className: "h-1 w-full rounded-pill bg-surface-raised",
|
|
81
81
|
role: "progressbar",
|
|
82
82
|
"aria-label": `${t} progress`,
|
|
83
83
|
"aria-valuenow": n,
|
|
84
84
|
"aria-valuemin": 0,
|
|
85
85
|
"aria-valuemax": 100,
|
|
86
|
-
children: /* @__PURE__ */
|
|
86
|
+
children: /* @__PURE__ */ p("div", {
|
|
87
87
|
className: r("h-full rounded-pill transition-[width] duration-moderate-02 ease-productive-standard", i),
|
|
88
88
|
style: { width: `${n}%` }
|
|
89
89
|
})
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
|
-
var x =
|
|
93
|
-
let
|
|
94
|
-
if (O) return /* @__PURE__ */
|
|
95
|
-
ref:
|
|
92
|
+
var x = d.forwardRef(({ className: d, label: v, title: x, value: S, prefix: C, suffix: w, delta: T, deltaPlacement: E = "block", icon: D, loading: O, comparisonLabel: k, secondaryLabel: A, progress: j, variant: M = "default", size: N = "md", accentStyle: P = "none", iconFill: F = "soft", flash: I, flashSpeed: L, sparkline: R, onClick: z, href: B, footer: V, ...H }, U) => {
|
|
93
|
+
let W = l(), G = x ?? v ?? "", K = (z || B) && !H["aria-label"] ? `View ${G}` : H["aria-label"];
|
|
94
|
+
if (O) return /* @__PURE__ */ p(o, {
|
|
95
|
+
ref: U,
|
|
96
96
|
variant: M,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
size: N,
|
|
98
|
+
className: d,
|
|
99
|
+
"aria-busy": "true",
|
|
100
|
+
...H,
|
|
101
|
+
children: /* @__PURE__ */ m(s, {
|
|
102
|
+
className: "flex flex-col gap-ds-03",
|
|
103
|
+
children: [
|
|
104
|
+
/* @__PURE__ */ p("div", { className: "h-ds-04 w-24 rounded-control-inner bg-skeleton-base animate-pulse" }),
|
|
105
|
+
/* @__PURE__ */ p("div", { className: "h-ds-sm w-32 rounded-control bg-skeleton-base animate-pulse" }),
|
|
106
|
+
/* @__PURE__ */ p("div", { className: "h-3 w-16 rounded-control-inner bg-skeleton-base animate-pulse" })
|
|
107
|
+
]
|
|
108
|
+
})
|
|
104
109
|
});
|
|
105
|
-
let
|
|
106
|
-
className: r("flex items-center gap-ds-02 text-ds-sm font-medium",
|
|
110
|
+
let q = T?.direction === "up" ? g : T?.direction === "down" ? ee : h, J = T?.direction === "up" ? "text-success-11" : T?.direction === "down" ? "text-error-11" : "text-surface-fg-muted", Y = T?.direction === "up" ? "text-success-11" : T?.direction === "down" ? "text-error-11" : "text-accent-11", X = T ? /* @__PURE__ */ m(_.div, {
|
|
111
|
+
className: r("flex items-center gap-ds-02 text-ds-sm font-medium", J),
|
|
107
112
|
initial: {
|
|
108
113
|
opacity: 0,
|
|
109
114
|
y: 8
|
|
@@ -117,7 +122,7 @@ var x = u.forwardRef(({ className: u, label: v, title: x, value: S, prefix: C, s
|
|
|
117
122
|
delay: .2
|
|
118
123
|
},
|
|
119
124
|
children: [
|
|
120
|
-
/* @__PURE__ */
|
|
125
|
+
/* @__PURE__ */ p(_.span, {
|
|
121
126
|
className: "inline-flex",
|
|
122
127
|
initial: {
|
|
123
128
|
opacity: .5,
|
|
@@ -128,38 +133,38 @@ var x = u.forwardRef(({ className: u, label: v, title: x, value: S, prefix: C, s
|
|
|
128
133
|
scale: 1
|
|
129
134
|
},
|
|
130
135
|
transition: t.bouncy,
|
|
131
|
-
children: /* @__PURE__ */
|
|
132
|
-
icon:
|
|
136
|
+
children: /* @__PURE__ */ p(i, {
|
|
137
|
+
icon: q,
|
|
133
138
|
size: "sm"
|
|
134
139
|
})
|
|
135
140
|
}),
|
|
136
|
-
/* @__PURE__ */
|
|
137
|
-
k && /* @__PURE__ */
|
|
141
|
+
/* @__PURE__ */ p("span", { children: T.value }),
|
|
142
|
+
k && /* @__PURE__ */ p("span", {
|
|
138
143
|
className: "text-surface-fg-subtle font-normal",
|
|
139
144
|
children: k
|
|
140
145
|
})
|
|
141
146
|
]
|
|
142
|
-
}) : null,
|
|
143
|
-
/* @__PURE__ */
|
|
144
|
-
className: "flex items-center justify-between
|
|
145
|
-
children: [/* @__PURE__ */
|
|
147
|
+
}) : null, Z = /* @__PURE__ */ m(f, { children: [
|
|
148
|
+
/* @__PURE__ */ m("div", {
|
|
149
|
+
className: "flex items-center justify-between",
|
|
150
|
+
children: [/* @__PURE__ */ p(_.p, {
|
|
146
151
|
className: "text-ds-md font-medium text-surface-fg-muted",
|
|
147
152
|
initial: { opacity: 0 },
|
|
148
153
|
animate: { opacity: 1 },
|
|
149
154
|
transition: n.fade,
|
|
150
|
-
children:
|
|
151
|
-
}), /* @__PURE__ */
|
|
155
|
+
children: G
|
|
156
|
+
}), /* @__PURE__ */ m("div", {
|
|
152
157
|
className: "flex items-center gap-ds-03",
|
|
153
|
-
children: [
|
|
154
|
-
data:
|
|
155
|
-
colorClass:
|
|
156
|
-
}), D &&
|
|
158
|
+
children: [R && R.length >= 2 && /* @__PURE__ */ p(y, {
|
|
159
|
+
data: R,
|
|
160
|
+
colorClass: Y
|
|
161
|
+
}), D && I ? /* @__PURE__ */ p(u, {
|
|
157
162
|
icon: D,
|
|
158
|
-
flash:
|
|
159
|
-
fill:
|
|
160
|
-
speed:
|
|
161
|
-
}) : D ?
|
|
162
|
-
className: r("inline-flex items-center justify-center rounded-control p-ds-02",
|
|
163
|
+
flash: I,
|
|
164
|
+
fill: F,
|
|
165
|
+
speed: L
|
|
166
|
+
}) : D ? P === "icon" ? /* @__PURE__ */ p(_.span, {
|
|
167
|
+
className: r("inline-flex items-center justify-center rounded-control p-ds-02", F === "solid" ? "bg-accent-9 text-accent-fg" : "bg-accent-3 text-accent-11"),
|
|
163
168
|
initial: {
|
|
164
169
|
opacity: 0,
|
|
165
170
|
scale: .96
|
|
@@ -170,11 +175,11 @@ var x = u.forwardRef(({ className: u, label: v, title: x, value: S, prefix: C, s
|
|
|
170
175
|
},
|
|
171
176
|
transition: t.snappy,
|
|
172
177
|
"aria-hidden": "true",
|
|
173
|
-
children: /* @__PURE__ */
|
|
178
|
+
children: /* @__PURE__ */ p(e, {
|
|
174
179
|
size: "md",
|
|
175
180
|
children: a(D, "md")
|
|
176
181
|
})
|
|
177
|
-
}) : /* @__PURE__ */
|
|
182
|
+
}) : /* @__PURE__ */ p(_.span, {
|
|
178
183
|
className: "text-surface-fg-muted",
|
|
179
184
|
initial: {
|
|
180
185
|
opacity: 0,
|
|
@@ -186,112 +191,125 @@ var x = u.forwardRef(({ className: u, label: v, title: x, value: S, prefix: C, s
|
|
|
186
191
|
},
|
|
187
192
|
transition: t.snappy,
|
|
188
193
|
"aria-hidden": "true",
|
|
189
|
-
children: /* @__PURE__ */
|
|
194
|
+
children: /* @__PURE__ */ p(e, {
|
|
190
195
|
size: "lg",
|
|
191
196
|
children: a(D, "lg")
|
|
192
197
|
})
|
|
193
198
|
}) : null]
|
|
194
199
|
})]
|
|
195
200
|
}),
|
|
196
|
-
/* @__PURE__ */
|
|
197
|
-
className:
|
|
198
|
-
children: [/* @__PURE__ */
|
|
199
|
-
className: "
|
|
200
|
-
children: /* @__PURE__ */ p(
|
|
201
|
-
className:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
201
|
+
/* @__PURE__ */ m("div", {
|
|
202
|
+
className: "flex flex-col gap-ds-01",
|
|
203
|
+
children: [/* @__PURE__ */ m("div", {
|
|
204
|
+
className: r(E === "inline" && T && "flex items-baseline gap-ds-03"),
|
|
205
|
+
children: [/* @__PURE__ */ p("div", {
|
|
206
|
+
className: "overflow-hidden",
|
|
207
|
+
children: /* @__PURE__ */ m(_.p, {
|
|
208
|
+
className: r("inline-block font-semibold", N === "sm" ? "text-ds-2xl" : "text-ds-3xl", P === "tint" ? "text-accent-11" : "text-surface-fg"),
|
|
209
|
+
initial: {
|
|
210
|
+
opacity: 0,
|
|
211
|
+
y: "100%"
|
|
212
|
+
},
|
|
213
|
+
animate: {
|
|
214
|
+
opacity: 1,
|
|
215
|
+
y: 0
|
|
216
|
+
},
|
|
217
|
+
transition: t.smooth,
|
|
218
|
+
children: [
|
|
219
|
+
C && /* @__PURE__ */ p("span", {
|
|
220
|
+
className: r("text-surface-fg-muted", N === "sm" ? "text-ds-md" : "text-ds-lg"),
|
|
221
|
+
children: C
|
|
222
|
+
}),
|
|
223
|
+
/* @__PURE__ */ p("span", {
|
|
224
|
+
className: "tabular-nums",
|
|
225
|
+
children: S
|
|
226
|
+
}),
|
|
227
|
+
w && /* @__PURE__ */ p("span", {
|
|
228
|
+
className: r("text-surface-fg-muted", N === "sm" ? "text-ds-md" : "text-ds-lg"),
|
|
229
|
+
children: w
|
|
230
|
+
})
|
|
231
|
+
]
|
|
232
|
+
})
|
|
233
|
+
}), E === "inline" && X]
|
|
234
|
+
}), A && /* @__PURE__ */ p(_.p, {
|
|
235
|
+
className: "text-ds-sm text-surface-fg-subtle",
|
|
236
|
+
initial: { opacity: 0 },
|
|
237
|
+
animate: { opacity: 1 },
|
|
238
|
+
transition: {
|
|
239
|
+
...n.fade,
|
|
240
|
+
delay: .1
|
|
241
|
+
},
|
|
242
|
+
children: A
|
|
243
|
+
})]
|
|
237
244
|
}),
|
|
238
|
-
j != null && /* @__PURE__ */
|
|
245
|
+
j != null && /* @__PURE__ */ p(_.div, {
|
|
239
246
|
initial: { opacity: 0 },
|
|
240
247
|
animate: { opacity: 1 },
|
|
241
248
|
transition: {
|
|
242
249
|
...n.fade,
|
|
243
250
|
delay: .15
|
|
244
251
|
},
|
|
245
|
-
children: /* @__PURE__ */
|
|
252
|
+
children: /* @__PURE__ */ p(b, {
|
|
246
253
|
progress: j,
|
|
247
|
-
label:
|
|
254
|
+
label: G
|
|
248
255
|
})
|
|
249
256
|
}),
|
|
250
|
-
E === "block" &&
|
|
251
|
-
|
|
252
|
-
|
|
257
|
+
E === "block" && X
|
|
258
|
+
] }), Q = P === "tint" ? "bg-linear-to-t from-accent-2 to-surface-raised" : void 0, $ = /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(s, {
|
|
259
|
+
className: "flex flex-col gap-ds-03",
|
|
260
|
+
children: Z
|
|
261
|
+
}), V && /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("div", {
|
|
262
|
+
"aria-hidden": "true",
|
|
263
|
+
className: "h-px w-full bg-surface-border-subtle"
|
|
264
|
+
}), /* @__PURE__ */ p(c, {
|
|
265
|
+
className: "text-ds-sm",
|
|
266
|
+
children: /* @__PURE__ */ p(_.div, {
|
|
267
|
+
className: "w-full",
|
|
253
268
|
initial: { opacity: 0 },
|
|
254
269
|
animate: { opacity: 1 },
|
|
255
270
|
transition: {
|
|
256
271
|
...n.fade,
|
|
257
272
|
delay: .25
|
|
258
273
|
},
|
|
259
|
-
children:
|
|
274
|
+
children: V
|
|
260
275
|
})
|
|
261
|
-
] })
|
|
262
|
-
return
|
|
263
|
-
ref:
|
|
264
|
-
href:
|
|
265
|
-
onClick:
|
|
276
|
+
})] })] });
|
|
277
|
+
return B ? /* @__PURE__ */ p(W, {
|
|
278
|
+
ref: U,
|
|
279
|
+
href: B,
|
|
280
|
+
onClick: z,
|
|
266
281
|
className: "block no-underline",
|
|
267
|
-
"aria-label":
|
|
268
|
-
...
|
|
269
|
-
children: /* @__PURE__ */
|
|
282
|
+
"aria-label": K,
|
|
283
|
+
...H,
|
|
284
|
+
children: /* @__PURE__ */ p(o, {
|
|
270
285
|
variant: M,
|
|
286
|
+
size: N,
|
|
271
287
|
interactive: !0,
|
|
272
|
-
className:
|
|
273
|
-
children:
|
|
288
|
+
className: Q,
|
|
289
|
+
children: $
|
|
274
290
|
})
|
|
275
|
-
}) :
|
|
276
|
-
ref:
|
|
291
|
+
}) : z ? /* @__PURE__ */ p(o, {
|
|
292
|
+
ref: U,
|
|
277
293
|
variant: M,
|
|
294
|
+
size: N,
|
|
278
295
|
interactive: !0,
|
|
279
|
-
className: r(
|
|
296
|
+
className: r(Q, d),
|
|
280
297
|
role: "button",
|
|
281
298
|
tabIndex: 0,
|
|
282
|
-
"aria-label":
|
|
283
|
-
onClick:
|
|
299
|
+
"aria-label": K,
|
|
300
|
+
onClick: z,
|
|
284
301
|
onKeyDown: (e) => {
|
|
285
|
-
(e.key === "Enter" || e.key === " ") && (e.preventDefault(),
|
|
302
|
+
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), z());
|
|
286
303
|
},
|
|
287
|
-
...
|
|
288
|
-
children:
|
|
289
|
-
}) : /* @__PURE__ */
|
|
290
|
-
ref:
|
|
304
|
+
...H,
|
|
305
|
+
children: $
|
|
306
|
+
}) : /* @__PURE__ */ p(o, {
|
|
307
|
+
ref: U,
|
|
291
308
|
variant: M,
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
309
|
+
size: N,
|
|
310
|
+
className: r(Q, d),
|
|
311
|
+
...H,
|
|
312
|
+
children: $
|
|
295
313
|
});
|
|
296
314
|
});
|
|
297
315
|
x.displayName = "StatCard";
|
package/dist/ui/stat-card.js.map
CHANGED
|
@@ -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 { Card, CardContent } from './card'\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 { type FlashPreset, type FlashSpec, type FlashSpeed,StatFlash } 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:** delegated to `Card` via `variant` — `default` (ring-in-shadow, no border) |\n * `elevated` | `outline` (border, no shadow) | `flat`. StatCard composes `<Card>`, so the surface,\n * padding (gap model), and elevation all live in one place.\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 Omit<React.HTMLAttributes<HTMLDivElement>, 'color'> {\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 /** Where the delta sits relative to the value: `block` (below — default) or `inline` (on the value's baseline, to its right). @default 'block' */\n deltaPlacement?: 'block' | 'inline'\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 variant, delegated to Card: `default` (ring-in-shadow) | `elevated` | `outline` (border, no shadow) | `flat`. @default 'default' */\n variant?: 'default' | 'elevated' | 'outline' | '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 deltaPlacement = 'block',\n icon,\n loading,\n comparisonLabel,\n secondaryLabel,\n progress,\n variant = 'default',\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 <Card ref={ref} variant={variant} className={className} {...props}>\n <CardContent>\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 </CardContent>\n </Card>\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 deltaNode = delta ? (\n <motion.div\n className={cn(\n 'flex items-center gap-ds-02 text-ds-sm font-medium',\n deltaColour,\n // block placement owns the gap to the value; inline rides the value's baseline row.\n deltaPlacement === 'block' && 'mt-ds-03',\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 ) : null\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={cn(deltaPlacement === 'inline' && delta && 'flex items-baseline gap-ds-03')}>\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 {deltaPlacement === 'inline' && deltaNode}\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 {deltaPlacement === 'block' && deltaNode}\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 // accentStyle=\"tint\" → a subtle accent gradient wash over Card's surface.\n const tintClass =\n accentStyle === 'tint' ? 'bg-linear-to-t from-accent-2 to-surface-raised' : undefined\n const body = <CardContent>{cardContent}</CardContent>\n\n // href → wrap Card in the framework Link (Card stays the shell; hover-lift inside).\n if (href) {\n return (\n <Link\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n onClick={onClick}\n className=\"block no-underline\"\n aria-label={computedAriaLabel}\n {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}\n >\n <Card variant={variant} interactive className={tintClass}>\n {body}\n </Card>\n </Link>\n )\n }\n\n // onClick → interactive Card with button semantics + keyboard activation.\n if (onClick) {\n return (\n <Card\n ref={ref}\n variant={variant}\n interactive\n className={cn(tintClass, className)}\n role=\"button\"\n tabIndex={0}\n aria-label={computedAriaLabel}\n onClick={onClick}\n onKeyDown={(e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onClick()\n }\n }}\n {...props}\n >\n {body}\n </Card>\n )\n }\n\n return (\n <Card ref={ref} variant={variant} className={cn(tintClass, className)} {...props}>\n {body}\n </Card>\n )\n },\n)\n\nStatCard.displayName = 'StatCard'\n\nexport { StatCard }\n"],"mappings":"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"}
|
|
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 { Card, CardContent, CardFooter, type CardSize } from './card'\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 { type FlashPreset, type FlashSpec, type FlashSpeed,StatFlash } 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:** delegated to `Card` via `variant` — `default` (ring-in-shadow, no border) |\n * `elevated` | `outline` (border, no shadow) | `flat`. StatCard composes `<Card>`, so the surface,\n * padding (gap model), and elevation all live in one place.\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 Omit<React.HTMLAttributes<HTMLDivElement>, 'color'> {\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 /** Where the delta sits relative to the value: `block` (below — default) or `inline` (on the value's baseline, to its right). @default 'block' */\n deltaPlacement?: 'block' | 'inline'\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 variant, delegated to Card: `default` (ring-in-shadow) | `elevated` | `outline` (border, no shadow) | `flat`. @default 'default' */\n variant?: 'default' | 'elevated' | 'outline' | 'flat'\n /**\n * Tile density, delegated to Card's size axis. `sm` tightens padding to 16px and steps\n * the value down to `text-ds-2xl` — use it for dense KPI rows and narrow stat grids\n * (e.g. stat-row's 140px tiles). @default 'md'\n */\n size?: CardSize\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\" 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 deltaPlacement = 'block',\n icon,\n loading,\n comparisonLabel,\n secondaryLabel,\n progress,\n variant = 'default',\n size = 'md',\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 <Card ref={ref} variant={variant} size={size} className={className} aria-busy=\"true\" {...props}>\n <CardContent className=\"flex flex-col gap-ds-03\">\n <div className=\"h-ds-04 w-24 rounded-control-inner bg-skeleton-base animate-pulse\" />\n <div className=\"h-ds-sm w-32 rounded-control bg-skeleton-base animate-pulse\" />\n <div className=\"h-3 w-16 rounded-control-inner bg-skeleton-base animate-pulse\" />\n </CardContent>\n </Card>\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 deltaNode = delta ? (\n <motion.div\n className={cn(\n // Rhythm comes from CardContent's flex gap — no placement margins.\n '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 ) : null\n\n const cardContent = (\n <>\n <div className=\"flex items-center justify-between\">\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=\"flex flex-col gap-ds-01\">\n <div className={cn(deltaPlacement === 'inline' && delta && 'flex items-baseline gap-ds-03')}>\n <div className=\"overflow-hidden\">\n <motion.p\n className={cn(\n 'inline-block font-semibold',\n size === 'sm' ? 'text-ds-2xl' : 'text-ds-3xl',\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={cn('text-surface-fg-muted', size === 'sm' ? 'text-ds-md' : 'text-ds-lg')}>{prefix}</span>\n )}\n <span className=\"tabular-nums\">{value}</span>\n {suffix && (\n <span className={cn('text-surface-fg-muted', size === 'sm' ? 'text-ds-md' : 'text-ds-lg')}>{suffix}</span>\n )}\n </motion.p>\n </div>\n {deltaPlacement === 'inline' && deltaNode}\n </div>\n {secondaryLabel && (\n <motion.p\n className=\"text-ds-sm text-surface-fg-subtle\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ ...tweens.fade, delay: 0.1 }}\n >\n {secondaryLabel}\n </motion.p>\n )}\n </div>\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 {deltaPlacement === 'block' && deltaNode}\n </>\n )\n\n // accentStyle=\"tint\" → a subtle accent gradient wash over Card's surface.\n const tintClass =\n accentStyle === 'tint' ? 'bg-linear-to-t from-accent-2 to-surface-raised' : undefined\n // Footer sits outside CardContent behind a full-width divider (both are direct\n // children of Card, so the rule spans edge-to-edge — no inset border-t).\n const body = (\n <>\n <CardContent className=\"flex flex-col gap-ds-03\">{cardContent}</CardContent>\n {footer && (\n <>\n <div aria-hidden=\"true\" className=\"h-px w-full bg-surface-border-subtle\" />\n <CardFooter className=\"text-ds-sm\">\n <motion.div\n className=\"w-full\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ ...tweens.fade, delay: 0.25 }}\n >\n {footer}\n </motion.div>\n </CardFooter>\n </>\n )}\n </>\n )\n\n // href → wrap Card in the framework Link (Card stays the shell; hover-lift inside).\n if (href) {\n return (\n <Link\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n onClick={onClick}\n className=\"block no-underline\"\n aria-label={computedAriaLabel}\n {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}\n >\n <Card variant={variant} size={size} interactive className={tintClass}>\n {body}\n </Card>\n </Link>\n )\n }\n\n // onClick → interactive Card with button semantics + keyboard activation.\n if (onClick) {\n return (\n <Card\n ref={ref}\n variant={variant}\n size={size}\n interactive\n className={cn(tintClass, className)}\n role=\"button\"\n tabIndex={0}\n aria-label={computedAriaLabel}\n onClick={onClick}\n onKeyDown={(e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onClick()\n }\n }}\n {...props}\n >\n {body}\n </Card>\n )\n }\n\n return (\n <Card ref={ref} variant={variant} size={size} className={cn(tintClass, className)} {...props}>\n {body}\n </Card>\n )\n },\n)\n\nStatCard.displayName = 'StatCard'\n\nexport { StatCard }\n"],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
export interface TableRowLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
4
|
+
href: string;
|
|
5
|
+
/**
|
|
6
|
+
* Stretch the click target across the whole row via a pseudo-element
|
|
7
|
+
* (a real `<a>`, so cmd/ctrl+click, middle-click, and "open in new tab"
|
|
8
|
+
* all work). Trade-off: the overlay blocks text selection inside the row —
|
|
9
|
+
* pass `stretch={false}` for a title-only link (GitHub-style) when row text
|
|
10
|
+
* must stay selectable.
|
|
11
|
+
* @default true
|
|
12
|
+
*/
|
|
13
|
+
stretch?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A real-anchor row link for tables — place inside the row's primary
|
|
17
|
+
* `<TableCell className="relative">`. Replaces `onClick`-on-row navigation,
|
|
18
|
+
* which breaks cmd+click / middle-click / context-menu and isn't announced as
|
|
19
|
+
* a link by screen readers (never put onClick on a `<tr>`).
|
|
20
|
+
*
|
|
21
|
+
* Mechanics: the stretch pseudo-element is anchored to the CELL, not the row —
|
|
22
|
+
* Safari ignores `position: relative` on `<tr>` — and spans `100vw`; the Table
|
|
23
|
+
* root clips it (`overflow-x-clip`). Keyboard focus draws a row-level ring via
|
|
24
|
+
* TableRow's `has-[[data-slot=row-link]:focus-visible]` rule, so the anchor
|
|
25
|
+
* suppresses its own outline.
|
|
26
|
+
*
|
|
27
|
+
* Other interactive elements in the row (menu buttons, checkboxes) must sit
|
|
28
|
+
* above the stretch: give them `className="relative z-[1]"` (IconButton etc.).
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* <TableRow>
|
|
32
|
+
* <TableCell className="relative">
|
|
33
|
+
* <TableRowLink href={`/projects/${id}`}>{name}</TableRowLink>
|
|
34
|
+
* </TableCell>
|
|
35
|
+
* <TableCell><Badge color="success">Active</Badge></TableCell>
|
|
36
|
+
* <TableCell>
|
|
37
|
+
* <TableRowActions>
|
|
38
|
+
* <IconButton className="relative z-[1]" size="xs" variant="ghost" aria-label={`Actions for ${name}`} icon={<IconDots />} />
|
|
39
|
+
* </TableRowActions>
|
|
40
|
+
* </TableCell>
|
|
41
|
+
* </TableRow>
|
|
42
|
+
*/
|
|
43
|
+
declare const TableRowLink: React.ForwardRefExoticComponent<TableRowLinkProps & React.RefAttributes<HTMLAnchorElement>>;
|
|
44
|
+
export { TableRowLink };
|
|
45
|
+
//# sourceMappingURL=table-row-link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-row-link.d.ts","sourceRoot":"","sources":["../../src/ui/table-row-link.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,MAAM,WAAW,iBACf,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IACrD,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,QAAA,MAAM,YAAY,6FAqBjB,CAAA;AAGD,OAAO,EAAE,YAAY,EAAE,CAAA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn as e } from "./lib/utils.js";
|
|
3
|
+
import { n as t } from "../_chunks/link-context.js";
|
|
4
|
+
import * as n from "react";
|
|
5
|
+
import { jsx as r } from "react/jsx-runtime";
|
|
6
|
+
//#region src/ui/table-row-link.tsx
|
|
7
|
+
var i = n.forwardRef(({ className: n, stretch: i = !0, children: a, ...o }, s) => /* @__PURE__ */ r(t(), {
|
|
8
|
+
ref: s,
|
|
9
|
+
"data-slot": "row-link",
|
|
10
|
+
className: e("font-medium text-surface-fg no-underline", i ? "focus-visible:outline-hidden after:absolute after:inset-y-0 after:left-0 after:w-[100vw] after:content-[\"\"]" : "rounded-control-inner hover:underline focus-visible:outline-2 focus-visible:outline-accent-9 focus-visible:outline-offset-2", n),
|
|
11
|
+
...o,
|
|
12
|
+
children: a
|
|
13
|
+
}));
|
|
14
|
+
i.displayName = "TableRowLink";
|
|
15
|
+
//#endregion
|
|
16
|
+
export { i as TableRowLink };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-row-link.js","names":[],"sources":["../../src/ui/table-row-link.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nimport { useLink } from './lib/link-context'\nimport { cn } from './lib/utils'\n\nexport interface TableRowLinkProps\n extends React.AnchorHTMLAttributes<HTMLAnchorElement> {\n href: string\n /**\n * Stretch the click target across the whole row via a pseudo-element\n * (a real `<a>`, so cmd/ctrl+click, middle-click, and \"open in new tab\"\n * all work). Trade-off: the overlay blocks text selection inside the row —\n * pass `stretch={false}` for a title-only link (GitHub-style) when row text\n * must stay selectable.\n * @default true\n */\n stretch?: boolean\n}\n\n/**\n * A real-anchor row link for tables — place inside the row's primary\n * `<TableCell className=\"relative\">`. Replaces `onClick`-on-row navigation,\n * which breaks cmd+click / middle-click / context-menu and isn't announced as\n * a link by screen readers (never put onClick on a `<tr>`).\n *\n * Mechanics: the stretch pseudo-element is anchored to the CELL, not the row —\n * Safari ignores `position: relative` on `<tr>` — and spans `100vw`; the Table\n * root clips it (`overflow-x-clip`). Keyboard focus draws a row-level ring via\n * TableRow's `has-[[data-slot=row-link]:focus-visible]` rule, so the anchor\n * suppresses its own outline.\n *\n * Other interactive elements in the row (menu buttons, checkboxes) must sit\n * above the stretch: give them `className=\"relative z-[1]\"` (IconButton etc.).\n *\n * @example\n * <TableRow>\n * <TableCell className=\"relative\">\n * <TableRowLink href={`/projects/${id}`}>{name}</TableRowLink>\n * </TableCell>\n * <TableCell><Badge color=\"success\">Active</Badge></TableCell>\n * <TableCell>\n * <TableRowActions>\n * <IconButton className=\"relative z-[1]\" size=\"xs\" variant=\"ghost\" aria-label={`Actions for ${name}`} icon={<IconDots />} />\n * </TableRowActions>\n * </TableCell>\n * </TableRow>\n */\nconst TableRowLink = React.forwardRef<HTMLAnchorElement, TableRowLinkProps>(\n ({ className, stretch = true, children, ...props }, ref) => {\n const Link = useLink()\n return (\n <Link\n ref={ref}\n data-slot=\"row-link\"\n className={cn(\n 'font-medium text-surface-fg no-underline',\n stretch\n ? // row ring comes from TableRow's has-[] rule; suppress the anchor's own\n 'focus-visible:outline-hidden after:absolute after:inset-y-0 after:left-0 after:w-[100vw] after:content-[\"\"]'\n : 'rounded-control-inner hover:underline focus-visible:outline-2 focus-visible:outline-accent-9 focus-visible:outline-offset-2',\n className,\n )}\n {...props}\n >\n {children}\n </Link>\n )\n },\n)\nTableRowLink.displayName = 'TableRowLink'\n\nexport { TableRowLink }\n"],"mappings":";;;;;;AAiDA,IAAM,IAAe,EAAM,YACxB,EAAE,cAAW,aAAU,IAAM,aAAU,GAAG,KAAS,MAGhD,kBAFW,GAAS,EAEpB;CACO;CACL,aAAU;CACV,WAAW,EACT,4CACA,IAEI,kHACA,+HACJ,EACD;CACD,GAAI;CAEH;CACI,CAAA,CAGZ;AACD,EAAa,cAAc"}
|