@astra-kit/ui 1.1.0 → 1.2.1

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 (35) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +5 -3
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/dist/components/empty-state.d.ts +20 -0
  5. package/dist/components/empty-state.js +357 -0
  6. package/dist/components/metric-card.d.ts +22 -0
  7. package/dist/components/metric-card.js +378 -0
  8. package/dist/components/stepper.d.ts +39 -0
  9. package/dist/components/stepper.js +693 -0
  10. package/dist/components/timeline.d.ts +51 -0
  11. package/dist/components/timeline.js +1013 -0
  12. package/dist/components/usage-meter.d.ts +39 -0
  13. package/dist/components/usage-meter.js +1093 -0
  14. package/dist/constants/empty-state.d.ts +12 -0
  15. package/dist/constants/empty-state.js +12 -0
  16. package/dist/constants/metric-card.d.ts +12 -0
  17. package/dist/constants/metric-card.js +12 -0
  18. package/dist/constants/stepper.d.ts +52 -0
  19. package/dist/constants/stepper.js +23 -0
  20. package/dist/constants/timeline.d.ts +62 -0
  21. package/dist/constants/timeline.js +31 -0
  22. package/dist/constants/usage-meter.d.ts +100 -0
  23. package/dist/constants/usage-meter.js +38 -0
  24. package/dist/hooks/use-has-entered-view.d.ts +9 -0
  25. package/dist/hooks/use-has-entered-view.js +46 -0
  26. package/dist/index.d.ts +5 -0
  27. package/dist/index.js +5 -0
  28. package/dist/motion/arc-odometer.d.ts +53 -0
  29. package/dist/motion/arc-odometer.js +424 -0
  30. package/dist/motion/arc-text-swap.d.ts +22 -0
  31. package/dist/motion/arc-text-swap.js +142 -0
  32. package/dist/motion/arc-tokens.d.ts +1 -0
  33. package/dist/motion/arc-tokens.js +2 -1
  34. package/dist/styles.css +1 -1
  35. package/package.json +26 -1
@@ -0,0 +1,378 @@
1
+ "use client";
2
+ import { c as _c } from "react/compiler-runtime";
3
+ /**
4
+ * Arc's Metric card (https://uiarc.dev, MIT License, see THIRD_PARTY_NOTICES.md): a compact summary for a number that
5
+ * needs context. The number rolls up from zero the first time it scrolls into view and then turns like an odometer in
6
+ * the direction it moved; the label, context and change chip roll in from the same side, and the chip width springs.
7
+ */
8
+ import { useRef, useState } from "react";
9
+ import { AnimatePresence, motion } from "motion/react";
10
+ import { METRIC_CARD_CHANGE_ENTER_SCALE, METRIC_CARD_DEFAULT_LOCALE, METRIC_CARD_REVEAL_AMOUNT, METRIC_CARD_TREND_PATTERN } from "../constants/metric-card.js";
11
+ import { useHasEnteredView } from "../hooks/use-has-entered-view.js";
12
+ import { usePrefersReducedMotion } from "../hooks/use-prefers-reduced-motion.js";
13
+ import { cn } from "../lib/utils.js";
14
+ import { ArcOdometer, formatOdometerValue } from "../motion/arc-odometer.js";
15
+ import { ArcTextSwap } from "../motion/arc-text-swap.js";
16
+ import { ARC_DURATION, ARC_EASE, ARC_SPRING_SNAPPY } from "../motion/arc-tokens.js";
17
+ import { useMorphWidth } from "../motion/use-morph-width.js";
18
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
+ /**
20
+ * Reads the first number in a line of copy, so copy that holds a number enters from the side it moved toward.
21
+ * @param text - Copy that may contain a number.
22
+ * @returns The number, or `NaN` when there is none.
23
+ */
24
+ function amountIn(text) {
25
+ const match = text.match(/[-−]?\d+(?:[.,]\d+)?/)?.[0];
26
+ return match ? Number(match.replace("−", "-").replace(",", ".")) : Number.NaN;
27
+ }
28
+ /**
29
+ * Keeps the direction of the last change of a line of copy.
30
+ * @param text - Current copy.
31
+ * @returns `-1` when the number in it went down, otherwise `1`.
32
+ */
33
+ function useTextDirection(text) {
34
+ const $ = _c(2);
35
+ let t0;
36
+ if ($[0] !== text) {
37
+ t0 = {
38
+ text,
39
+ direction: 1
40
+ };
41
+ $[0] = text;
42
+ $[1] = t0;
43
+ } else {
44
+ t0 = $[1];
45
+ }
46
+ const [shown, setShown] = useState(t0);
47
+ if (shown.text !== text) {
48
+ setShown({
49
+ text,
50
+ direction: amountIn(text) < amountIn(shown.text) ? -1 : 1
51
+ });
52
+ }
53
+ return shown.direction;
54
+ }
55
+ /** A line of copy that rolls in from the direction its number moved. */
56
+ function MetricCopy(t0) {
57
+ const $ = _c(5);
58
+ const { text, reduceMotion, className } = t0;
59
+ const direction = useTextDirection(text);
60
+ let t1;
61
+ if ($[0] !== className || $[1] !== direction || $[2] !== reduceMotion || $[3] !== text) {
62
+ t1 = /* @__PURE__ */ _jsx(ArcTextSwap, {
63
+ text,
64
+ reduceMotion,
65
+ direction,
66
+ className
67
+ });
68
+ $[0] = className;
69
+ $[1] = direction;
70
+ $[2] = reduceMotion;
71
+ $[3] = text;
72
+ $[4] = t1;
73
+ } else {
74
+ t1 = $[4];
75
+ }
76
+ return t1;
77
+ }
78
+ /** The change copy, whose slot springs to the width of each new value instead of snapping. */
79
+ function ChangeCopy(t0) {
80
+ const $ = _c(12);
81
+ const { text, reduceMotion } = t0;
82
+ const direction = useTextDirection(text);
83
+ const sizer = useRef(null);
84
+ const width = useMorphWidth(sizer, text, reduceMotion);
85
+ let t1;
86
+ if ($[0] !== width) {
87
+ t1 = { width };
88
+ $[0] = width;
89
+ $[1] = t1;
90
+ } else {
91
+ t1 = $[1];
92
+ }
93
+ let t2;
94
+ if ($[2] !== text) {
95
+ t2 = /* @__PURE__ */ _jsx("span", {
96
+ ref: sizer,
97
+ "aria-hidden": "true",
98
+ className: "pointer-events-none invisible absolute top-0 left-0 whitespace-nowrap",
99
+ children: text
100
+ });
101
+ $[2] = text;
102
+ $[3] = t2;
103
+ } else {
104
+ t2 = $[3];
105
+ }
106
+ let t3;
107
+ if ($[4] !== direction || $[5] !== reduceMotion || $[6] !== text) {
108
+ t3 = /* @__PURE__ */ _jsx(ArcTextSwap, {
109
+ text,
110
+ reduceMotion,
111
+ direction,
112
+ className: "inline-flex",
113
+ lineClassName: "inline-block"
114
+ });
115
+ $[4] = direction;
116
+ $[5] = reduceMotion;
117
+ $[6] = text;
118
+ $[7] = t3;
119
+ } else {
120
+ t3 = $[7];
121
+ }
122
+ let t4;
123
+ if ($[8] !== t1 || $[9] !== t2 || $[10] !== t3) {
124
+ t4 = /* @__PURE__ */ _jsxs(motion.span, {
125
+ className: "relative inline-flex max-w-full overflow-x-clip align-top whitespace-nowrap",
126
+ style: t1,
127
+ children: [t2, t3]
128
+ });
129
+ $[8] = t1;
130
+ $[9] = t2;
131
+ $[10] = t3;
132
+ $[11] = t4;
133
+ } else {
134
+ t4 = $[11];
135
+ }
136
+ return t4;
137
+ }
138
+ /** Chip classes: a neutral outline, or a faint tint with the trend colour on the text when the change is signed. */
139
+ const changeClassName = "inline-flex flex-none rounded-(--radius-pill) border border-border px-2 py-[3px] text-sm text-text-secondary tabular-nums transition-[color,background-color] duration-(--duration-fast) ease-(--ease-standard) data-trend:border-transparent data-[trend=up]:bg-success/11 data-[trend=up]:text-success data-[trend=down]:bg-danger/11 data-[trend=down]:text-danger";
140
+ /**
141
+ * A compact card with a label, an odometer style number, a context line and an optional change chip.
142
+ * @param props - Label, value, suffix, context, change, locale and native article attributes.
143
+ * @returns The metric card.
144
+ */
145
+ function MetricCard(t0) {
146
+ const $ = _c(50);
147
+ let change;
148
+ let className;
149
+ let context;
150
+ let label;
151
+ let props;
152
+ let suffix;
153
+ let t1;
154
+ let value;
155
+ if ($[0] !== t0) {
156
+ ({label, value, suffix, context, change, locale: t1, className, ...props} = t0);
157
+ $[0] = t0;
158
+ $[1] = change;
159
+ $[2] = className;
160
+ $[3] = context;
161
+ $[4] = label;
162
+ $[5] = props;
163
+ $[6] = suffix;
164
+ $[7] = t1;
165
+ $[8] = value;
166
+ } else {
167
+ change = $[1];
168
+ className = $[2];
169
+ context = $[3];
170
+ label = $[4];
171
+ props = $[5];
172
+ suffix = $[6];
173
+ t1 = $[7];
174
+ value = $[8];
175
+ }
176
+ const locale = t1 === undefined ? METRIC_CARD_DEFAULT_LOCALE : t1;
177
+ const reduceMotion = usePrefersReducedMotion();
178
+ const numberRef = useRef(null);
179
+ const hasEnteredView = useHasEnteredView(numberRef, METRIC_CARD_REVEAL_AMOUNT);
180
+ const trend = change ? METRIC_CARD_TREND_PATTERN.up.test(change) ? "up" : METRIC_CARD_TREND_PATTERN.down.test(change) ? "down" : undefined : undefined;
181
+ let t2;
182
+ if ($[9] !== locale || $[10] !== value) {
183
+ t2 = formatOdometerValue(value, 0, locale);
184
+ $[9] = locale;
185
+ $[10] = value;
186
+ $[11] = t2;
187
+ } else {
188
+ t2 = $[11];
189
+ }
190
+ const valueText = `${t2}${suffix ?? ""}`;
191
+ let t3;
192
+ if ($[12] !== className) {
193
+ t3 = cn("min-w-0 rounded-(--radius-surface) border border-border bg-surface p-(--space-6) text-foreground shadow-(--shadow-resting) max-[380px]:p-(--space-4)", className);
194
+ $[12] = className;
195
+ $[13] = t3;
196
+ } else {
197
+ t3 = $[13];
198
+ }
199
+ let t4;
200
+ if ($[14] !== label || $[15] !== reduceMotion) {
201
+ t4 = /* @__PURE__ */ _jsx("span", {
202
+ "data-slot": "metric-card-label",
203
+ className: "min-w-0 text-sm text-text-secondary",
204
+ children: /* @__PURE__ */ _jsx(MetricCopy, {
205
+ text: label,
206
+ reduceMotion
207
+ })
208
+ });
209
+ $[14] = label;
210
+ $[15] = reduceMotion;
211
+ $[16] = t4;
212
+ } else {
213
+ t4 = $[16];
214
+ }
215
+ let t5;
216
+ if ($[17] !== change || $[18] !== reduceMotion || $[19] !== trend) {
217
+ t5 = change ? /* @__PURE__ */ _jsx(motion.small, {
218
+ "data-slot": "metric-card-change",
219
+ "data-trend": trend,
220
+ className: changeClassName,
221
+ initial: {
222
+ opacity: 0,
223
+ scale: reduceMotion ? 1 : METRIC_CARD_CHANGE_ENTER_SCALE
224
+ },
225
+ animate: {
226
+ opacity: 1,
227
+ scale: 1
228
+ },
229
+ exit: {
230
+ opacity: 0,
231
+ scale: reduceMotion ? 1 : METRIC_CARD_CHANGE_ENTER_SCALE,
232
+ transition: {
233
+ duration: ARC_DURATION.fast,
234
+ ease: [...ARC_EASE.standard]
235
+ }
236
+ },
237
+ transition: reduceMotion ? { duration: 0 } : ARC_SPRING_SNAPPY,
238
+ children: /* @__PURE__ */ _jsx(ChangeCopy, {
239
+ text: change,
240
+ reduceMotion
241
+ })
242
+ }, "change") : null;
243
+ $[17] = change;
244
+ $[18] = reduceMotion;
245
+ $[19] = trend;
246
+ $[20] = t5;
247
+ } else {
248
+ t5 = $[20];
249
+ }
250
+ let t6;
251
+ if ($[21] !== t5) {
252
+ t6 = /* @__PURE__ */ _jsx(AnimatePresence, {
253
+ initial: false,
254
+ children: t5
255
+ });
256
+ $[21] = t5;
257
+ $[22] = t6;
258
+ } else {
259
+ t6 = $[22];
260
+ }
261
+ let t7;
262
+ if ($[23] !== t4 || $[24] !== t6) {
263
+ t7 = /* @__PURE__ */ _jsxs("div", {
264
+ className: "mb-(--space-8) flex items-center justify-between gap-(--space-3) max-[380px]:mb-(--space-6) max-[380px]:flex-wrap max-[380px]:items-start",
265
+ children: [t4, t6]
266
+ });
267
+ $[23] = t4;
268
+ $[24] = t6;
269
+ $[25] = t7;
270
+ } else {
271
+ t7 = $[25];
272
+ }
273
+ let t8;
274
+ if ($[26] !== valueText) {
275
+ t8 = /* @__PURE__ */ _jsx("span", {
276
+ className: "sr-only",
277
+ children: valueText
278
+ });
279
+ $[26] = valueText;
280
+ $[27] = t8;
281
+ } else {
282
+ t8 = $[27];
283
+ }
284
+ const t9 = !hasEnteredView && !reduceMotion;
285
+ let t10;
286
+ if ($[28] !== locale || $[29] !== reduceMotion || $[30] !== t9 || $[31] !== value) {
287
+ t10 = /* @__PURE__ */ _jsx(ArcOdometer, {
288
+ value,
289
+ locale,
290
+ armed: t9,
291
+ reduceMotion
292
+ });
293
+ $[28] = locale;
294
+ $[29] = reduceMotion;
295
+ $[30] = t9;
296
+ $[31] = value;
297
+ $[32] = t10;
298
+ } else {
299
+ t10 = $[32];
300
+ }
301
+ let t11;
302
+ if ($[33] !== suffix) {
303
+ t11 = suffix ? /* @__PURE__ */ _jsx("span", {
304
+ className: "inline-block whitespace-pre",
305
+ children: suffix
306
+ }) : null;
307
+ $[33] = suffix;
308
+ $[34] = t11;
309
+ } else {
310
+ t11 = $[34];
311
+ }
312
+ let t12;
313
+ if ($[35] !== t10 || $[36] !== t11) {
314
+ t12 = /* @__PURE__ */ _jsxs("span", {
315
+ "aria-hidden": "true",
316
+ className: "inline-flex items-start whitespace-nowrap select-none",
317
+ children: [t10, t11]
318
+ });
319
+ $[35] = t10;
320
+ $[36] = t11;
321
+ $[37] = t12;
322
+ } else {
323
+ t12 = $[37];
324
+ }
325
+ let t13;
326
+ if ($[38] !== t12 || $[39] !== t8) {
327
+ t13 = /* @__PURE__ */ _jsxs("span", {
328
+ ref: numberRef,
329
+ "data-slot": "metric-card-value",
330
+ className: "relative inline-flex font-display text-3xl leading-none font-medium tracking-(--tracking-display) tabular-nums",
331
+ children: [t8, t12]
332
+ });
333
+ $[38] = t12;
334
+ $[39] = t8;
335
+ $[40] = t13;
336
+ } else {
337
+ t13 = $[40];
338
+ }
339
+ let t14;
340
+ if ($[41] !== context || $[42] !== reduceMotion) {
341
+ t14 = /* @__PURE__ */ _jsx("p", {
342
+ "data-slot": "metric-card-context",
343
+ className: "mt-(--space-4) text-sm leading-(--leading-body) text-text-muted",
344
+ children: /* @__PURE__ */ _jsx(MetricCopy, {
345
+ text: context,
346
+ reduceMotion
347
+ })
348
+ });
349
+ $[41] = context;
350
+ $[42] = reduceMotion;
351
+ $[43] = t14;
352
+ } else {
353
+ t14 = $[43];
354
+ }
355
+ let t15;
356
+ if ($[44] !== props || $[45] !== t13 || $[46] !== t14 || $[47] !== t3 || $[48] !== t7) {
357
+ t15 = /* @__PURE__ */ _jsxs("article", {
358
+ "data-slot": "metric-card",
359
+ className: t3,
360
+ ...props,
361
+ children: [
362
+ t7,
363
+ t13,
364
+ t14
365
+ ]
366
+ });
367
+ $[44] = props;
368
+ $[45] = t13;
369
+ $[46] = t14;
370
+ $[47] = t3;
371
+ $[48] = t7;
372
+ $[49] = t15;
373
+ } else {
374
+ t15 = $[49];
375
+ }
376
+ return t15;
377
+ }
378
+ export { MetricCard };
@@ -0,0 +1,39 @@
1
+ import type { HTMLAttributes } from "react";
2
+ import type { StepperDetails, StepperMessages, StepperOrientation, StepperStatus } from "../constants/stepper.js";
3
+ export type { StepperDetails, StepperMessages, StepperOrientation, StepperStatus };
4
+ export type StepperStep = {
5
+ /** Stable key, so each step keeps its own motion when the list changes. */
6
+ id: string;
7
+ label: string;
8
+ /** A short hint under the label. */
9
+ description?: string;
10
+ /** Marks the step as failed: the marker morphs into an alert and this message replaces the description. */
11
+ error?: string;
12
+ };
13
+ export type StepperProps = Omit<HTMLAttributes<HTMLElement>, "children" | "role"> & {
14
+ /** Steps in order. */
15
+ steps: StepperStep[];
16
+ /** Index of the step in progress. `steps.length` marks the whole flow complete. */
17
+ current: number;
18
+ /** Layout direction. Defaults to `"horizontal"`. */
19
+ orientation?: StepperOrientation;
20
+ /** Called with the index of a completed step when it is chosen. Without it the stepper is read only. */
21
+ onStepSelect?: (index: number) => void;
22
+ /** `"current"` shows only the active step's description. Errors always show. Defaults to `"all"`. */
23
+ details?: StepperDetails;
24
+ /** Markers only. Horizontal steppers switch to this below 30rem on their own. */
25
+ compact?: boolean;
26
+ /** Accessible name of the stepper. Defaults to "Progreso". */
27
+ label?: string;
28
+ /** Announced, and shown in the compact caption, once every step is complete. Defaults to "Todos los pasos completados". */
29
+ completeLabel?: string;
30
+ /** Replaces any of the Spanish screen-reader statuses and the step announcement. */
31
+ messages?: Partial<StepperMessages>;
32
+ };
33
+ /**
34
+ * A horizontal or vertical steps indicator driven by the current index, with optional navigation back to completed steps.
35
+ * @param props - Steps, current index, orientation, selection callback, copy and native attributes.
36
+ * @returns The stepper: a labelled `nav` when interactive, otherwise a `role="group"`.
37
+ */
38
+ declare function Stepper({ steps, current, orientation, onStepSelect, details, compact, label, completeLabel, messages, className, ...props }: StepperProps): import("react").JSX.Element;
39
+ export { Stepper };