@ceebee/ui 0.2.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/dist/index.js ADDED
@@ -0,0 +1,486 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { ChevronRight, Check } from 'lucide-react';
3
+
4
+ // src/lib/cn.ts
5
+ function cn(...parts) {
6
+ return parts.filter(Boolean).join(" ");
7
+ }
8
+ function Surface({
9
+ variant = "plain",
10
+ tone = "neutral",
11
+ hue,
12
+ elevation = "sm",
13
+ radius = "lg",
14
+ padding = "md",
15
+ bordered = true,
16
+ className,
17
+ children,
18
+ asSection = false
19
+ }) {
20
+ const Tag = asSection ? "section" : "div";
21
+ return /* @__PURE__ */ jsx(
22
+ Tag,
23
+ {
24
+ className: cn(
25
+ "cb-surface",
26
+ `cb-surface--${variant}`,
27
+ `cb-elevation--${elevation}`,
28
+ `cb-radius--${radius}`,
29
+ `cb-pad--${padding}`,
30
+ bordered && "cb-surface--bordered",
31
+ className
32
+ ),
33
+ "data-tone": variant === "plain" ? void 0 : tone,
34
+ "data-hue": hue,
35
+ children
36
+ }
37
+ );
38
+ }
39
+ function Stack({
40
+ direction = "column",
41
+ gap = 4,
42
+ align = "stretch",
43
+ justify = "start",
44
+ wrap = false,
45
+ className,
46
+ children
47
+ }) {
48
+ const style = { "--cb-stack-gap": `var(--cb-space-${gap})` };
49
+ return /* @__PURE__ */ jsx(
50
+ "div",
51
+ {
52
+ className: cn("cb-stack", `cb-stack--${direction}`, wrap && "cb-stack--wrap", className),
53
+ style,
54
+ "data-align": align,
55
+ "data-justify": justify,
56
+ children
57
+ }
58
+ );
59
+ }
60
+ function Grid({ columns = 3, gap = 4, minItemWidth, className, children }) {
61
+ const style = {
62
+ "--cb-grid-gap": `var(--cb-space-${gap})`,
63
+ "--cb-grid-columns": String(columns),
64
+ ...minItemWidth ? { "--cb-grid-min": minItemWidth } : {}
65
+ };
66
+ return /* @__PURE__ */ jsx("div", { className: cn("cb-grid", minItemWidth && "cb-grid--auto", className), style, children });
67
+ }
68
+ function Container({ size = "lg", className, children }) {
69
+ return /* @__PURE__ */ jsx("div", { className: cn("cb-container", `cb-container--${size}`, className), children });
70
+ }
71
+ function Text({
72
+ size = "md",
73
+ tone = "neutral",
74
+ weight = "regular",
75
+ as: Tag = "p",
76
+ numeric = false,
77
+ className,
78
+ children
79
+ }) {
80
+ return /* @__PURE__ */ jsx(
81
+ Tag,
82
+ {
83
+ className: cn("cb-text", `cb-text--${size}`, `cb-weight--${weight}`, numeric && "cb-numeric", className),
84
+ "data-tone": tone,
85
+ children
86
+ }
87
+ );
88
+ }
89
+ function Heading({ level = 2, size, className, children }) {
90
+ const Tag = `h${level}`;
91
+ const resolved = size ?? ["3xl", "2xl", "xl", "lg"][level - 1] ?? "lg";
92
+ return /* @__PURE__ */ jsx(Tag, { className: cn("cb-heading", `cb-text--${resolved}`, className), children });
93
+ }
94
+ function Badge({ children, tone = "neutral", variant = "soft", size = "md", dot, className }) {
95
+ return /* @__PURE__ */ jsxs("span", { className: cn("cb-badge", `cb-badge--${variant}`, `cb-badge--${size}`, className), "data-tone": tone, children: [
96
+ dot ? /* @__PURE__ */ jsx("span", { className: "cb-badge__dot", "aria-hidden": "true" }) : null,
97
+ children
98
+ ] });
99
+ }
100
+ function EmptyState({ title, description, icon, actions, variant = "first-run", className }) {
101
+ return /* @__PURE__ */ jsxs("div", { className: cn("cb-empty", className), "data-variant": variant, children: [
102
+ icon ? /* @__PURE__ */ jsx("span", { className: "cb-empty__icon", children: icon }) : null,
103
+ /* @__PURE__ */ jsx("p", { className: "cb-empty__title", children: title }),
104
+ description ? /* @__PURE__ */ jsx("p", { className: "cb-empty__description", children: description }) : null,
105
+ actions ? /* @__PURE__ */ jsx("div", { className: "cb-empty__actions", children: actions }) : null
106
+ ] });
107
+ }
108
+ function Spinner({ size = "md", tone = "brand", label, className }) {
109
+ return /* @__PURE__ */ jsx(
110
+ "span",
111
+ {
112
+ className: cn("cb-spinner", `cb-spinner--${size}`, className),
113
+ "data-tone": tone,
114
+ role: label ? "status" : void 0,
115
+ "aria-label": label,
116
+ "aria-hidden": label ? void 0 : true
117
+ }
118
+ );
119
+ }
120
+ function ProgressBar({ value, max = 100, tone = "brand", size = "md", label, showValue, className }) {
121
+ const indeterminate = value === void 0;
122
+ const clamped = indeterminate ? 0 : Math.min(Math.max(value, 0), max);
123
+ const percent = max > 0 ? Math.round(clamped / max * 100) : 0;
124
+ return /* @__PURE__ */ jsxs("div", { className: cn("cb-progress", className), "data-tone": tone, "data-size": size, children: [
125
+ /* @__PURE__ */ jsx(
126
+ "div",
127
+ {
128
+ className: "cb-progress__track",
129
+ role: "progressbar",
130
+ "aria-label": label,
131
+ "aria-valuemin": indeterminate ? void 0 : 0,
132
+ "aria-valuemax": indeterminate ? void 0 : max,
133
+ "aria-valuenow": indeterminate ? void 0 : clamped,
134
+ children: /* @__PURE__ */ jsx(
135
+ "div",
136
+ {
137
+ className: "cb-progress__fill",
138
+ "data-indeterminate": indeterminate || void 0,
139
+ style: indeterminate ? void 0 : { width: `${percent}%` }
140
+ }
141
+ )
142
+ }
143
+ ),
144
+ showValue && !indeterminate ? /* @__PURE__ */ jsxs("span", { className: "cb-progress__value", children: [
145
+ percent,
146
+ "%"
147
+ ] }) : null
148
+ ] });
149
+ }
150
+ function box({ width = "100%", height = "1rem", radius = "md", className }) {
151
+ const style = { "--cb-skeleton-w": width, "--cb-skeleton-h": height };
152
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("cb-skeleton", `cb-radius--${radius}`, className), style });
153
+ }
154
+ function text({ lines = 3, lastLineWidth = "62%", className }) {
155
+ return /* @__PURE__ */ jsx("span", { className: cn("cb-skeleton-text", className), children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx(SkeletonBox, { height: "0.75rem", width: i === lines - 1 ? lastLineWidth : "100%" }, i)) });
156
+ }
157
+ function circle({ size = "2.5rem", className }) {
158
+ return /* @__PURE__ */ jsx(SkeletonBox, { width: size, height: size, radius: "full", className });
159
+ }
160
+ var SkeletonBox = box;
161
+ var Skeleton = Object.assign(box, {
162
+ Text: text,
163
+ Circle: circle,
164
+ Rect: box
165
+ });
166
+ function TimelineRoot({ entries, className }) {
167
+ return /* @__PURE__ */ jsx("ol", { className: cn("cb-timeline", className), children: entries.map((entry, index) => /* @__PURE__ */ jsxs("li", { className: "cb-timeline__entry", "data-tone": entry.tone ?? "neutral", children: [
168
+ /* @__PURE__ */ jsx("span", { className: "cb-timeline__rail", "aria-hidden": "true", children: /* @__PURE__ */ jsx("span", { className: "cb-timeline__marker", children: entry.icon }) }),
169
+ /* @__PURE__ */ jsxs("div", { className: "cb-timeline__body", children: [
170
+ /* @__PURE__ */ jsxs("div", { className: "cb-timeline__head", children: [
171
+ /* @__PURE__ */ jsx("p", { className: "cb-timeline__title", children: entry.title }),
172
+ /* @__PURE__ */ jsx("span", { className: "cb-timeline__time", children: entry.time })
173
+ ] }),
174
+ entry.description ? /* @__PURE__ */ jsx("p", { className: "cb-timeline__description", children: entry.description }) : null
175
+ ] })
176
+ ] }, index)) });
177
+ }
178
+ function TimelineSkeleton({ entries = 3, className }) {
179
+ return /* @__PURE__ */ jsx("div", { className: cn("cb-timeline", className), "aria-hidden": "true", children: Array.from({ length: entries }, (_, index) => /* @__PURE__ */ jsxs("div", { className: "cb-timeline__entry", children: [
180
+ /* @__PURE__ */ jsx("span", { className: "cb-timeline__rail", children: /* @__PURE__ */ jsx(Skeleton.Circle, { size: "1.5rem" }) }),
181
+ /* @__PURE__ */ jsxs("div", { className: "cb-timeline__body", children: [
182
+ /* @__PURE__ */ jsx(Skeleton, { width: "45%", height: "0.875rem" }),
183
+ /* @__PURE__ */ jsx("div", { style: { marginTop: "var(--cb-space-2)" }, children: /* @__PURE__ */ jsx(Skeleton, { width: "70%", height: "0.75rem" }) })
184
+ ] })
185
+ ] }, index)) });
186
+ }
187
+ var Timeline = Object.assign(TimelineRoot, { Skeleton: TimelineSkeleton });
188
+
189
+ // src/data/progress-ring.math.ts
190
+ function ringGeometry(value, max, size, thickness) {
191
+ const safeMax = max > 0 ? max : 1;
192
+ const clamped = Math.min(Math.max(Number.isFinite(value) ? value : 0, 0), safeMax);
193
+ const radius = (size - thickness) / 2;
194
+ const circumference = 2 * Math.PI * radius;
195
+ const dashOffset = circumference * (1 - clamped / safeMax);
196
+ return { radius, circumference, dashOffset, clamped };
197
+ }
198
+ function ProgressRing({
199
+ value,
200
+ max = 100,
201
+ size = 96,
202
+ thickness = 10,
203
+ tone = "brand",
204
+ hue,
205
+ children,
206
+ label,
207
+ className
208
+ }) {
209
+ const { radius, circumference, dashOffset, clamped } = ringGeometry(value, max, size, thickness);
210
+ const center = size / 2;
211
+ const style = {
212
+ "--cb-ring-size": `${size}px`,
213
+ "--cb-ring-circumference": `${circumference}`,
214
+ "--cb-ring-offset": `${dashOffset}`
215
+ };
216
+ return /* @__PURE__ */ jsxs(
217
+ "div",
218
+ {
219
+ className: cn("cb-ring", className),
220
+ style,
221
+ "data-tone": tone,
222
+ "data-hue": hue,
223
+ role: "img",
224
+ "aria-label": `${label}: ${Math.round(clamped / (max || 1) * 100)}%`,
225
+ children: [
226
+ /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, "aria-hidden": "true", children: [
227
+ /* @__PURE__ */ jsx("circle", { className: "cb-ring__track", cx: center, cy: center, r: radius, strokeWidth: thickness, fill: "none" }),
228
+ /* @__PURE__ */ jsx(
229
+ "circle",
230
+ {
231
+ className: "cb-ring__value",
232
+ cx: center,
233
+ cy: center,
234
+ r: radius,
235
+ strokeWidth: thickness,
236
+ fill: "none",
237
+ strokeLinecap: "round",
238
+ strokeDasharray: circumference,
239
+ strokeDashoffset: dashOffset,
240
+ transform: `rotate(-90 ${center} ${center})`
241
+ }
242
+ )
243
+ ] }),
244
+ /* @__PURE__ */ jsx("div", { className: "cb-ring__center", children: children ?? `${Math.round(clamped / (max || 1) * 100)}%` })
245
+ ]
246
+ }
247
+ );
248
+ }
249
+
250
+ // src/data/donut.math.ts
251
+ function donutArcs(slices, circumference) {
252
+ const usable = slices.filter((slice) => Number.isFinite(slice.value) && slice.value > 0);
253
+ const total = usable.reduce((sum, slice) => sum + slice.value, 0);
254
+ if (total <= 0) return [];
255
+ let consumed = 0;
256
+ return usable.map((slice, index) => {
257
+ const fraction = slice.value / total;
258
+ const length = fraction * circumference;
259
+ const arc = { ...slice, fraction, length, offset: -consumed, index };
260
+ consumed += length;
261
+ return arc;
262
+ });
263
+ }
264
+ var DEFAULT_HUES = ["violet", "blue", "teal", "green", "amber", "rose"];
265
+ function Donut({ slices, size = 120, thickness = 16, hues = DEFAULT_HUES, children, label, className }) {
266
+ const radius = (size - thickness) / 2;
267
+ const circumference = 2 * Math.PI * radius;
268
+ const arcs = donutArcs(slices, circumference);
269
+ const center = size / 2;
270
+ return /* @__PURE__ */ jsxs("div", { className: cn("cb-donut", className), style: { width: size, height: size }, role: "img", "aria-label": label, children: [
271
+ /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, "aria-hidden": "true", children: [
272
+ /* @__PURE__ */ jsx("circle", { className: "cb-donut__track", cx: center, cy: center, r: radius, strokeWidth: thickness, fill: "none" }),
273
+ arcs.map((arc) => /* @__PURE__ */ jsx(
274
+ "circle",
275
+ {
276
+ className: "cb-donut__arc",
277
+ "data-hue": hues[arc.index % hues.length],
278
+ cx: center,
279
+ cy: center,
280
+ r: radius,
281
+ strokeWidth: thickness,
282
+ fill: "none",
283
+ strokeDasharray: `${arc.length} ${circumference - arc.length}`,
284
+ strokeDashoffset: arc.offset,
285
+ transform: `rotate(-90 ${center} ${center})`
286
+ },
287
+ `${arc.label}-${arc.index}`
288
+ ))
289
+ ] }),
290
+ children ? /* @__PURE__ */ jsx("div", { className: "cb-donut__center", children }) : null
291
+ ] });
292
+ }
293
+
294
+ // src/data/sparkline.math.ts
295
+ function sparklineGeometry(values, width, height, padding = 1) {
296
+ const usable = values.filter((value) => Number.isFinite(value));
297
+ if (usable.length === 0) return { line: "", area: "", points: [] };
298
+ const min = Math.min(...usable);
299
+ const max = Math.max(...usable);
300
+ const span = max - min;
301
+ const innerHeight = height - padding * 2;
302
+ const step = usable.length > 1 ? width / (usable.length - 1) : 0;
303
+ const points = usable.map((value, index) => ({
304
+ x: usable.length === 1 ? width / 2 : index * step,
305
+ // A flat series sits on the middle line rather than collapsing onto the floor.
306
+ y: padding + (span === 0 ? innerHeight / 2 : innerHeight - (value - min) / span * innerHeight)
307
+ }));
308
+ const line = points.map((point, index) => `${index === 0 ? "M" : "L"}${round(point.x)} ${round(point.y)}`).join(" ");
309
+ const area = `${line} L${round(points[points.length - 1].x)} ${height} L${round(points[0].x)} ${height} Z`;
310
+ return { line, area, points };
311
+ }
312
+ function round(value) {
313
+ return Math.round(value * 100) / 100;
314
+ }
315
+ function Sparkline({
316
+ values,
317
+ width = 120,
318
+ height = 32,
319
+ tone = "brand",
320
+ hue,
321
+ filled = false,
322
+ showLast = true,
323
+ label,
324
+ className
325
+ }) {
326
+ const { line, area, points } = sparklineGeometry(values, width, height);
327
+ const last = points[points.length - 1];
328
+ return /* @__PURE__ */ jsx("div", { className: cn("cb-sparkline", className), "data-tone": tone, "data-hue": hue, role: "img", "aria-label": label, children: /* @__PURE__ */ jsxs("svg", { width, height, viewBox: `0 0 ${width} ${height}`, "aria-hidden": "true", children: [
329
+ filled && area ? /* @__PURE__ */ jsx("path", { className: "cb-sparkline__area", d: area }) : null,
330
+ line ? /* @__PURE__ */ jsx("path", { className: "cb-sparkline__line", d: line, fill: "none" }) : null,
331
+ showLast && last ? /* @__PURE__ */ jsx("circle", { className: "cb-sparkline__last", cx: last.x, cy: last.y, r: 2.5 }) : null
332
+ ] }) });
333
+ }
334
+ function BarMini({ values, width = 120, height = 32, tone = "brand", hue, label, className }) {
335
+ const usable = values.filter((value) => Number.isFinite(value));
336
+ const max = Math.max(...usable, 0);
337
+ const gap = 2;
338
+ const barWidth = usable.length > 0 ? Math.max((width - gap * (usable.length - 1)) / usable.length, 1) : 0;
339
+ return /* @__PURE__ */ jsx("div", { className: cn("cb-sparkline", className), "data-tone": tone, "data-hue": hue, role: "img", "aria-label": label, children: /* @__PURE__ */ jsx("svg", { width, height, viewBox: `0 0 ${width} ${height}`, "aria-hidden": "true", children: usable.map((value, index) => {
340
+ const barHeight = max > 0 ? Math.max(value / max * height, 1) : 1;
341
+ return /* @__PURE__ */ jsx(
342
+ "rect",
343
+ {
344
+ className: "cb-sparkline__bar",
345
+ x: index * (barWidth + gap),
346
+ y: height - barHeight,
347
+ width: barWidth,
348
+ height: barHeight,
349
+ rx: 1
350
+ },
351
+ index
352
+ );
353
+ }) }) });
354
+ }
355
+
356
+ // src/media/avatar.util.ts
357
+ var HUES = ["violet", "blue", "teal", "green", "amber", "rose"];
358
+ function initialsOf(name) {
359
+ const words = name.trim().split(/\s+/).filter(Boolean);
360
+ if (words.length === 0) return "?";
361
+ const first = words[0][0] ?? "";
362
+ const last = words.length > 1 ? words[words.length - 1][0] ?? "" : "";
363
+ return (first + last).toUpperCase();
364
+ }
365
+ function hueForName(name) {
366
+ let hash = 0;
367
+ for (let i = 0; i < name.length; i += 1) hash = hash * 31 + name.charCodeAt(i) >>> 0;
368
+ return HUES[hash % HUES.length];
369
+ }
370
+ function Avatar({ name, src, size = "md", hue, status, className }) {
371
+ return /* @__PURE__ */ jsxs(
372
+ "span",
373
+ {
374
+ className: cn("cb-avatar", `cb-avatar--${size}`, className),
375
+ "data-hue": hue ?? hueForName(name),
376
+ role: "img",
377
+ "aria-label": name,
378
+ children: [
379
+ /* @__PURE__ */ jsx("span", { className: "cb-avatar__initials", "aria-hidden": "true", children: initialsOf(name) }),
380
+ src ? /* @__PURE__ */ jsx("img", { className: "cb-avatar__image", src, alt: "", loading: "lazy" }) : null,
381
+ status ? /* @__PURE__ */ jsx("span", { className: "cb-avatar__status", "data-status": status, "aria-hidden": "true" }) : null
382
+ ]
383
+ }
384
+ );
385
+ }
386
+ function AvatarGroup({ children, overflow, size = "md", className }) {
387
+ return /* @__PURE__ */ jsxs("span", { className: cn("cb-avatar-group", className), children: [
388
+ children,
389
+ overflow && overflow > 0 ? /* @__PURE__ */ jsx("span", { className: cn("cb-avatar", `cb-avatar--${size}`, "cb-avatar--overflow"), children: /* @__PURE__ */ jsxs("span", { className: "cb-avatar__initials", children: [
390
+ "+",
391
+ overflow
392
+ ] }) }) : null
393
+ ] });
394
+ }
395
+ function LeaderboardRoot({ entries, label, medals = true, className }) {
396
+ return /* @__PURE__ */ jsx("ol", { className: cn("cb-leaderboard", className), "aria-label": label, children: entries.map((entry, index) => {
397
+ const rank = index + 1;
398
+ return /* @__PURE__ */ jsxs(
399
+ "li",
400
+ {
401
+ className: "cb-leaderboard__row",
402
+ "data-rank": medals && rank <= 3 ? rank : void 0,
403
+ "data-you": entry.you || void 0,
404
+ children: [
405
+ /* @__PURE__ */ jsx("span", { className: "cb-leaderboard__rank", "aria-hidden": "true", children: rank }),
406
+ /* @__PURE__ */ jsx(Avatar, { name: entry.name, src: entry.avatarSrc, size: "sm", hue: entry.hue }),
407
+ /* @__PURE__ */ jsxs("span", { className: "cb-leaderboard__text", children: [
408
+ /* @__PURE__ */ jsxs("span", { className: "cb-leaderboard__name", children: [
409
+ entry.name,
410
+ entry.you ? /* @__PURE__ */ jsx("span", { className: "cb-leaderboard__you", children: "You" }) : null
411
+ ] }),
412
+ entry.detail ? /* @__PURE__ */ jsx("span", { className: "cb-leaderboard__detail", children: entry.detail }) : null
413
+ ] }),
414
+ entry.delta ? /* @__PURE__ */ jsx("span", { className: "cb-leaderboard__delta", "data-direction": entry.delta.direction, children: entry.delta.value }) : null,
415
+ /* @__PURE__ */ jsx("span", { className: "cb-leaderboard__score", children: entry.score })
416
+ ]
417
+ },
418
+ entry.id
419
+ );
420
+ }) });
421
+ }
422
+ function LeaderboardSkeleton({ rows = 5, className }) {
423
+ return /* @__PURE__ */ jsx("div", { className: cn("cb-leaderboard", className), "aria-hidden": "true", children: Array.from({ length: rows }, (_, index) => /* @__PURE__ */ jsxs("div", { className: "cb-leaderboard__row", children: [
424
+ /* @__PURE__ */ jsx("span", { className: "cb-leaderboard__rank", children: /* @__PURE__ */ jsx(Skeleton, { width: "0.75rem", height: "0.75rem" }) }),
425
+ /* @__PURE__ */ jsx(Skeleton.Circle, { size: "1.75rem" }),
426
+ /* @__PURE__ */ jsx("span", { className: "cb-leaderboard__text", children: /* @__PURE__ */ jsx(Skeleton, { width: "7rem", height: "0.875rem" }) }),
427
+ /* @__PURE__ */ jsx(Skeleton, { width: "2.5rem", height: "0.875rem" })
428
+ ] }, index)) });
429
+ }
430
+ var Leaderboard = Object.assign(LeaderboardRoot, { Skeleton: LeaderboardSkeleton });
431
+ function Breadcrumbs({ items, maxItems = 4, className }) {
432
+ const collapsed = items.length > maxItems ? [items[0], { label: "\u2026" }, ...items.slice(items.length - (maxItems - 2))] : items;
433
+ return /* @__PURE__ */ jsx("nav", { className: cn("cb-crumbs", className), "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsx("ol", { className: "cb-crumbs__list", children: collapsed.map((crumb, index) => {
434
+ const isLast = index === collapsed.length - 1;
435
+ return /* @__PURE__ */ jsxs("li", { className: "cb-crumbs__item", children: [
436
+ isLast ? /* @__PURE__ */ jsx("span", { className: "cb-crumbs__current", "aria-current": "page", children: crumb.label }) : crumb.href ? /* @__PURE__ */ jsx("a", { className: "cb-crumbs__link", href: crumb.href, children: crumb.label }) : /* @__PURE__ */ jsx("span", { className: "cb-crumbs__text", children: crumb.label }),
437
+ isLast ? null : /* @__PURE__ */ jsx(ChevronRight, { size: 14, className: "cb-crumbs__separator", "aria-hidden": "true" })
438
+ ] }, `${String(crumb.label)}-${index}`);
439
+ }) }) });
440
+ }
441
+ function Stepper({ steps, current, orientation = "horizontal", className }) {
442
+ return /* @__PURE__ */ jsx("ol", { className: cn("cb-stepper", `cb-stepper--${orientation}`, className), children: steps.map((step, index) => {
443
+ const state = index < current ? "done" : index === current ? "current" : "todo";
444
+ return /* @__PURE__ */ jsxs("li", { className: "cb-stepper__step", "data-state": state, children: [
445
+ /* @__PURE__ */ jsx("span", { className: "cb-stepper__marker", "aria-hidden": "true", children: state === "done" ? /* @__PURE__ */ jsx(Check, { size: 13, strokeWidth: 3 }) : index + 1 }),
446
+ /* @__PURE__ */ jsxs("span", { className: "cb-stepper__text", children: [
447
+ /* @__PURE__ */ jsxs("span", { className: "cb-stepper__label", children: [
448
+ step.label,
449
+ /* @__PURE__ */ jsx("span", { className: "cb-visually-hidden", children: state === "done" ? " (completed)" : state === "current" ? " (current step)" : "" })
450
+ ] }),
451
+ step.description ? /* @__PURE__ */ jsx("span", { className: "cb-stepper__description", children: step.description }) : null
452
+ ] })
453
+ ] }, index);
454
+ }) });
455
+ }
456
+ function StatCard({ label, value, delta, caption, hue, icon, visual, className }) {
457
+ return /* @__PURE__ */ jsxs(Surface, { variant: "tinted", hue, radius: "lg", padding: "md", className: cn("cb-stat", className), children: [
458
+ /* @__PURE__ */ jsxs("div", { className: "cb-stat__head", children: [
459
+ /* @__PURE__ */ jsx(Text, { size: "sm", tone: "muted", weight: "medium", children: label }),
460
+ icon ? /* @__PURE__ */ jsx("span", { className: "cb-stat__icon", children: icon }) : null
461
+ ] }),
462
+ /* @__PURE__ */ jsxs("div", { className: "cb-stat__body", children: [
463
+ /* @__PURE__ */ jsxs("div", { children: [
464
+ /* @__PURE__ */ jsx("p", { className: "cb-stat__value cb-numeric", children: value }),
465
+ delta ? /* @__PURE__ */ jsx("span", { className: "cb-stat__delta", "data-direction": delta.direction, children: delta.value }) : null,
466
+ caption ? /* @__PURE__ */ jsx(Text, { size: "xs", tone: "subtle", className: "cb-stat__caption", children: caption }) : null
467
+ ] }),
468
+ visual ? /* @__PURE__ */ jsx("div", { className: "cb-stat__visual", children: visual }) : null
469
+ ] })
470
+ ] });
471
+ }
472
+ function StatCardSkeleton({ withVisual = false, className }) {
473
+ return /* @__PURE__ */ jsxs(Surface, { variant: "tinted", radius: "lg", padding: "md", className: cn("cb-stat", className), children: [
474
+ /* @__PURE__ */ jsx("div", { className: "cb-stat__head", children: /* @__PURE__ */ jsx(Skeleton, { width: "7rem", height: "0.875rem" }) }),
475
+ /* @__PURE__ */ jsxs("div", { className: "cb-stat__body", children: [
476
+ /* @__PURE__ */ jsxs("div", { className: "cb-stat__skeleton-col", children: [
477
+ /* @__PURE__ */ jsx(Skeleton, { width: "5.5rem", height: "2rem" }),
478
+ /* @__PURE__ */ jsx(Skeleton, { width: "3.5rem", height: "0.75rem" })
479
+ ] }),
480
+ withVisual ? /* @__PURE__ */ jsx(Skeleton.Circle, { size: "4rem" }) : null
481
+ ] })
482
+ ] });
483
+ }
484
+ StatCard.Skeleton = StatCardSkeleton;
485
+
486
+ export { Avatar, AvatarGroup, Badge, BarMini, Breadcrumbs, Container, Donut, EmptyState, Grid, Heading, Leaderboard, ProgressBar, ProgressRing, Skeleton, Sparkline, Spinner, Stack, StatCard, Stepper, Surface, Text, Timeline, cn, donutArcs, hueForName, initialsOf, ringGeometry, sparklineGeometry };
@@ -0,0 +1,30 @@
1
+ /* The "Astra" skin: the violet-to-blue gradient dashboard look from the reference board.
2
+ A Skin rewrites skin tokens only — never structure, never a component rule (ADR 0002). */
3
+ :root {
4
+ --cb-hue-brand: 278;
5
+
6
+ --cb-bg: oklch(0.97 0.02 285);
7
+ --cb-bg-subtle: oklch(0.94 0.035 280);
8
+ --cb-surface: oklch(1 0 0 / 0.86);
9
+ --cb-surface-raised: oklch(1 0 0);
10
+ --cb-border: oklch(0.9 0.02 285);
11
+
12
+ --cb-tint-strength: 0.12;
13
+ --cb-glass-blur: 22px;
14
+ --cb-shadow-md: 0 8px 28px oklch(0.45 0.12 285 / 0.14);
15
+ --cb-shadow-lg: 0 24px 64px oklch(0.45 0.12 285 / 0.2);
16
+ }
17
+
18
+ :root[data-theme="dark"] {
19
+ --cb-bg: oklch(0.21 0.04 285);
20
+ --cb-bg-subtle: oklch(0.25 0.045 285);
21
+ --cb-surface: oklch(0.27 0.045 285);
22
+ }
23
+
24
+ @media (prefers-color-scheme: dark) {
25
+ :root:not([data-theme="light"]) {
26
+ --cb-bg: oklch(0.21 0.04 285);
27
+ --cb-bg-subtle: oklch(0.25 0.045 285);
28
+ --cb-surface: oklch(0.27 0.045 285);
29
+ }
30
+ }