@blakesteve/roster 5.6.0 → 5.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +106 -1
  2. package/dist/components/atoms/LoadingDots/LoadingDots.cjs +1 -0
  3. package/dist/components/atoms/LoadingDots/LoadingDots.d.ts +20 -0
  4. package/dist/components/atoms/LoadingDots/LoadingDots.js +43 -0
  5. package/dist/components/atoms/Progress/Progress.cjs +1 -0
  6. package/dist/components/atoms/Progress/Progress.d.ts +61 -0
  7. package/dist/components/atoms/Progress/Progress.js +106 -0
  8. package/dist/components/atoms/Progress/progress-values.cjs +1 -0
  9. package/dist/components/atoms/Progress/progress-values.d.ts +26 -0
  10. package/dist/components/atoms/Progress/progress-values.js +12 -0
  11. package/dist/components/atoms/Skeleton/Skeleton.cjs +1 -0
  12. package/dist/components/atoms/Skeleton/Skeleton.d.ts +79 -0
  13. package/dist/components/atoms/Skeleton/Skeleton.js +80 -0
  14. package/dist/components/atoms/Stat/Stat.cjs +1 -1
  15. package/dist/components/atoms/Stat/Stat.js +19 -19
  16. package/dist/components/atoms/Stat/stat-variants.cjs +1 -1
  17. package/dist/components/atoms/Stat/stat-variants.d.ts +1 -0
  18. package/dist/components/atoms/Stat/stat-variants.js +5 -4
  19. package/dist/components/molecules/FileUpload/FileUpload.cjs +1 -1
  20. package/dist/components/molecules/FileUpload/FileUpload.js +120 -141
  21. package/dist/components/molecules/ProgressField/ProgressField.cjs +1 -0
  22. package/dist/components/molecules/ProgressField/ProgressField.d.ts +23 -0
  23. package/dist/components/molecules/ProgressField/ProgressField.js +32 -0
  24. package/dist/components/molecules/SkeletonPresets/SkeletonPresets.cjs +1 -0
  25. package/dist/components/molecules/SkeletonPresets/SkeletonPresets.d.ts +55 -0
  26. package/dist/components/molecules/SkeletonPresets/SkeletonPresets.js +45 -0
  27. package/dist/components/molecules/StepProgress/StepProgress.cjs +1 -0
  28. package/dist/components/molecules/StepProgress/StepProgress.d.ts +36 -0
  29. package/dist/components/molecules/StepProgress/StepProgress.js +89 -0
  30. package/dist/components/organisms/Dialog/Dialog.d.ts +1 -1
  31. package/dist/index.d.ts +6 -0
  32. package/dist/meta.json +3 -3
  33. package/dist/roster.cjs +1 -1
  34. package/dist/roster.css +1 -1
  35. package/dist/roster.js +174 -157
  36. package/package.json +1 -1
package/README.md CHANGED
@@ -591,6 +591,14 @@ per glyph, and holding it to 3:1 would flatten the effect.
591
591
  `Spinner` holds still under reduced motion: the arc still reads as loading, and
592
592
  its `label` (default "loading") says so.
593
593
 
594
+ The loading system has its own: `--roster-skeleton` and
595
+ `--roster-skeleton-highlight` are the skeleton's fill and the light that
596
+ crosses it, and the empty part of every progress bar (gray-200 and gray-100,
597
+ gray-700 and gray-600 in dark). Three durations, unset by default, change the
598
+ pace: `--roster-skeleton-duration` (1.8s), `--roster-progress-travel-duration`
599
+ (1.4s, the indeterminate pill) and `--roster-dots-duration` (1.2s). Under
600
+ reduced motion all three stop on a resting frame.
601
+
594
602
  ### Scrollbars
595
603
 
596
604
  `custom-scrollbar` gives any scrollable surface a slim themed scrollbar.
@@ -1381,6 +1389,97 @@ submit button from sending its form twice. It no longer dims, since
1381
1389
  the spinner says it's busy. `loadingLabel` names the spinner ("loading" by
1382
1390
  default). `disabled` is unchanged and wins over `isLoading`.
1383
1391
 
1392
+ ### The loading system
1393
+
1394
+ Five components for the time between asking and having, chosen by what the
1395
+ wait is:
1396
+
1397
+ | The wait | Use |
1398
+ | --- | --- |
1399
+ | Content is coming, and you know its shape | `Skeleton`, or a preset, inside `SkeletonRegion` |
1400
+ | A task with a fraction (bytes, pages, items) | `ProgressField` (a `Progress` with its label and value) |
1401
+ | A count of discrete things | `ProgressField` or `Progress`, `variant="segmented"` |
1402
+ | Named stages of a flow | `StepProgress` |
1403
+ | A model generating, before its first token | `LoadingDots` |
1404
+ | A short wait with nothing to show | `Spinner` |
1405
+
1406
+ `SegmentBar` isn't one of these: it shows how a whole divides into parts (a
1407
+ vote split, a budget), not how much of a task is done.
1408
+
1409
+ **Skeleton.** A placeholder in the shape of what's loading, so a page keeps
1410
+ its layout and nothing moves when data lands.
1411
+
1412
+ ```tsx
1413
+ import { SkeletonRegion, SkeletonCard } from "@blakesteve/roster";
1414
+
1415
+ <SkeletonRegion loading={!trails} label="Loading trails" skeleton={<SkeletonCard media={140} lines={2} />}>
1416
+ {trails && <TrailCard trail={trails[0]} />}
1417
+ </SkeletonRegion>;
1418
+ ```
1419
+
1420
+ - A `line` is one line box of its text size: `sm` (12/16), `md` (14/20), `lg`
1421
+ (16/24), or `inherit`. It's a no-break space set in that size with a bar
1422
+ drawn over it, so `lines={3}` is exactly the height of three lines of that
1423
+ text. A `block` takes a `height`; a `circle` takes Avatar's sizes.
1424
+ - Presets draw the real component's box: `SkeletonCard` (a Card with optional
1425
+ media, a `text-base` title and `text-sm` body lines 8px under it),
1426
+ `SkeletonTableRow` (Table's own cells), `SkeletonAvatar`, `SkeletonStat`.
1427
+ - `SkeletonRegion` sets `aria-busy` while loading, hides the skeleton from
1428
+ screen readers and says `label` once (a polite status beside the region,
1429
+ empty until a moment after loading starts). For a grid of cards, wrap the
1430
+ grid in one region, or pass `announce={false}` to all but one. Skeletons are
1431
+ always `aria-hidden`. `SkeletonTableRow` is a `tr`, so it can't sit in a
1432
+ region: set `aria-busy` on the table yourself.
1433
+ - `SkeletonRegion` renders two siblings, the hidden status and then the
1434
+ region, so the status counts in a parent's `first:` and `nth-child`
1435
+ selectors. It takes no room.
1436
+ - The fill is `--roster-skeleton` (gray-200, gray-700 in dark), with
1437
+ `--roster-skeleton-highlight` crossing it. Neutral rather than the brand
1438
+ shimmer Countdown uses, and its own pair, so retinting one never recolors the
1439
+ other. Under reduced motion it holds still.
1440
+
1441
+ **Progress and ProgressField.** `ProgressField` is the form to reach for: the
1442
+ label and value on one baseline above the bar, an optional detail line below.
1443
+
1444
+ ```tsx
1445
+ <ProgressField label="Uploading photos" value={sent} max={total} detail={`${mb(sent)} of ${mb(total)}`} />
1446
+ <ProgressField label="Picks" variant="segmented" segments={["done", "done", "na", "current", "remaining"]} />
1447
+ ```
1448
+
1449
+ - Leave `value` out for indeterminate (a pill crossing the track). Set `busy`
1450
+ when a value stands but work goes on: the bar breathes in place.
1451
+ - Four statuses (`default`, `success`, `warning`, `error`), solid fills only:
1452
+ each clears 3:1 against the track and the page in both themes, where a
1453
+ translucent fill would fade into whatever is behind it. Two sizes, 6px and
1454
+ 10px.
1455
+ - Segments take `done`, `current`, `remaining` and `na` (not part of this
1456
+ one: dashed, and not counted). They're capsules unless `rounded={false}`.
1457
+ - The bar is `aria-hidden`. A status region beside it says the value as text,
1458
+ at most every 1.5 seconds and the end at once, because a changed
1459
+ `aria-label` isn't reliably announced and a `progressbar` may beep or speak
1460
+ on every update. `announce={false}` when something else already says it.
1461
+ - On dark glass in the light theme, set `--roster-skeleton` on that surface to
1462
+ `var(--roster-primary-200)`: the empty cells take it and the bar keeps its
1463
+ extent (primary-600 against primary-200 is 6.4:1). Not in the dark theme,
1464
+ where the fill is primary-400 and that pair is about 2:1; there the default
1465
+ gray-700 track keeps done against remaining at 3.8:1, though its edge is
1466
+ faint on the glass.
1467
+ - In forced colors the fill keeps the system highlight color, the track and
1468
+ empty cells an outline, and the current cell its border.
1469
+
1470
+ **StepProgress.** Named stages: `variant="stepper"` shows every step with a
1471
+ numbered marker and connectors that fill behind the current one;
1472
+ `"segmented"` is the compact form, always with "Step 2 of 5". The current
1473
+ step is `aria-current="step"`, finished ones say "done", and the position is
1474
+ said when it changes.
1475
+
1476
+ **LoadingDots.** Only for a model generating before its first token. It takes
1477
+ the surrounding text's size and color, rests at 1, 0.6 and 0.3 opacity (also
1478
+ what reduced motion shows), and is replaced by the text once it starts: never
1479
+ shown beside it.
1480
+
1481
+ Every string in all five is a prop with an English default.
1482
+
1384
1483
  ### Pagination
1385
1484
 
1386
1485
  Moves through pages of a list, built for a long list browsed on a phone.
@@ -1573,7 +1672,10 @@ function App() {
1573
1672
  | `AvatarStrip` | Stacked avatar row with overflow chip, dismiss button, trailing slot, and label area |
1574
1673
  | `CollapsibleSection` | Clamps any content (prose, chips, image grids) to a fixed height with a fade and expand/collapse toggle |
1575
1674
  | `Select` | Dropdown selector, on the same size scale as `Button` and `Input` |
1576
- | `SegmentBar` | Proportional horizontal bar divided into colored segments with optional legend |
1675
+ | `SegmentBar` | Proportional horizontal bar divided into colored segments with optional legend (a split, not progress) |
1676
+ | `Skeleton` | Line, block or circle placeholder at the content's exact size; `SkeletonRegion` sets `aria-busy` |
1677
+ | `Progress` | Continuous or segmented progress bar: determinate, indeterminate or busy; per-segment state |
1678
+ | `LoadingDots` | Three dots for a model generating before its first token; replaced by the text |
1577
1679
  | `Stat` | A figure with its label and, optionally, its source; a valid `<dl>` group, or `semantics="standalone"` |
1578
1680
  | `Spinner` | Loading indicator |
1579
1681
  | `Switch` | Toggle switch; its label toggles it, and every size is a 44px target |
@@ -1600,6 +1702,9 @@ function App() {
1600
1702
  | `MediaPreview` | Thumbnail that falls back to "Preview unavailable, open original" when it can't render |
1601
1703
  | `FileUpload` | Picks files and uploads each through the app's `upload`, with per-file progress, retry and rules |
1602
1704
  | `Pagination` | Page numbers with gaps, or a compact page picker on narrow screens; buttons or real links, 44px targets |
1705
+ | `SkeletonCard`, `SkeletonTableRow`, `SkeletonAvatar`, `SkeletonStat` | Skeleton presets in the real component's box |
1706
+ | `ProgressField` | Progress with a label and value on one baseline and a detail line: the form to reach for |
1707
+ | `StepProgress` | Named steps: a stepper with markers and connectors, or a compact segmented "Step x of y" |
1603
1708
  | `Pullquote` | A line lifted out of prose, as `<figure>` + `<blockquote>` + `<figcaption>` |
1604
1709
  | `RadioGroup` | Radio set reporting one `string`; descriptions, error, 44px targets |
1605
1710
  | `Toast` | Transient floating notice; `Toaster` + the imperative `toast` handle |
@@ -0,0 +1 @@
1
+ "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),a=require("react"),o=require("../../../lib/utils.cjs"),l=[{rest:"rst:opacity-100",delay:"0s"},{rest:"rst:opacity-60",delay:"0.2s"},{rest:"rst:opacity-30",delay:"0.4s"}];function u({label:s="Loading",announce:r=!0,className:i}){const[n,c]=a.useState("");return a.useEffect(()=>{const t=setTimeout(()=>c(s),50);return()=>clearTimeout(t)},[s]),e.jsxs("span",{role:r?"status":void 0,className:o.cn("rst:inline-flex rst:items-center rst:gap-[0.25em] rst:align-middle",i),"data-loading-dots":"",children:[l.map((t,d)=>e.jsx("span",{"aria-hidden":"true",className:o.cn("rst:block rst:h-[0.4em] rst:w-[0.4em] rst:rounded-full rst:bg-current rst:animate-dot","rst:forced-colors:forced-color-adjust-none rst:forced-colors:bg-[CanvasText]",t.rest),style:{animationDelay:t.delay,animationFillMode:"backwards"}},d)),e.jsx("span",{className:"rst:sr-only",children:r?n:s})]})}exports.LoadingDots=u;
@@ -0,0 +1,20 @@
1
+ export interface LoadingDotsProps {
2
+ /** What a screen reader hears: "Writing a reply". */
3
+ label?: string;
4
+ /**
5
+ * Say `label` in a polite status of its own (the default). Off inside a
6
+ * chat log or any live region that already announces what's added to it,
7
+ * where it would be said twice; the label is still there to be read.
8
+ */
9
+ announce?: boolean;
10
+ className?: string;
11
+ }
12
+ /**
13
+ * Three dots for a model that's generating and hasn't sent anything yet.
14
+ *
15
+ * It stands in for text, so it's sized and colored by the text around it (em
16
+ * and `currentColor`), and it goes the moment the first token arrives: render
17
+ * the text instead of it, never beside it.
18
+ */
19
+ declare function LoadingDots({ label, announce, className }: LoadingDotsProps): import("react/jsx-runtime").JSX.Element;
20
+ export { LoadingDots };
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ import { jsxs as c, jsx as e } from "react/jsx-runtime";
3
+ import { useState as l, useEffect as m } from "react";
4
+ import { cn as a } from "../../../lib/utils.js";
5
+ const u = [
6
+ { rest: "rst:opacity-100", delay: "0s" },
7
+ { rest: "rst:opacity-60", delay: "0.2s" },
8
+ { rest: "rst:opacity-30", delay: "0.4s" }
9
+ ];
10
+ function g({ label: s = "Loading", announce: r = !0, className: o }) {
11
+ const [i, n] = l("");
12
+ return m(() => {
13
+ const t = setTimeout(() => n(s), 50);
14
+ return () => clearTimeout(t);
15
+ }, [s]), /* @__PURE__ */ c(
16
+ "span",
17
+ {
18
+ role: r ? "status" : void 0,
19
+ className: a("rst:inline-flex rst:items-center rst:gap-[0.25em] rst:align-middle", o),
20
+ "data-loading-dots": "",
21
+ children: [
22
+ u.map((t, d) => /* @__PURE__ */ e(
23
+ "span",
24
+ {
25
+ "aria-hidden": "true",
26
+ className: a(
27
+ "rst:block rst:h-[0.4em] rst:w-[0.4em] rst:rounded-full rst:bg-current rst:animate-dot",
28
+ /* Forced colors drop backgrounds; these keep the text's color. */
29
+ "rst:forced-colors:forced-color-adjust-none rst:forced-colors:bg-[CanvasText]",
30
+ t.rest
31
+ ),
32
+ style: { animationDelay: t.delay, animationFillMode: "backwards" }
33
+ },
34
+ d
35
+ )),
36
+ /* @__PURE__ */ e("span", { className: "rst:sr-only", children: r ? i : s })
37
+ ]
38
+ }
39
+ );
40
+ }
41
+ export {
42
+ g as LoadingDots
43
+ };
@@ -0,0 +1 @@
1
+ "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("react/jsx-runtime"),u=require("react"),c=require("../../../lib/utils.cjs"),G=require("./progress-values.cjs"),H={percent:r=>`${r}%`,count:(r,s)=>`${r} of ${s}`,indeterminate:"Loading",status:(r,s)=>r?`${r}: ${s}`:s},b={default:"rst:bg-primary-600 rst:dark:bg-primary-400",success:"rst:bg-success-600 rst:dark:bg-success-400",warning:"rst:bg-amber-700 rst:dark:bg-amber-400",error:"rst:bg-error-600 rst:dark:bg-error-400"},O={default:"rst:border-primary-600 rst:dark:border-primary-400",success:"rst:border-success-600 rst:dark:border-success-400",warning:"rst:border-amber-700 rst:dark:border-amber-400",error:"rst:border-error-600 rst:dark:border-error-400"},p="rst:bg-[var(--roster-skeleton)]",R="rst:forced-colors:outline-solid rst:forced-colors:outline-1",h="rst:forced-colors:forced-color-adjust-none rst:forced-colors:bg-[Highlight]",T={sm:"rst:h-1.5",md:"rst:h-2.5"};function P(r,s,t){const[m,n]=u.useState(r),e=u.useRef(r),l=u.useRef(Number.NEGATIVE_INFINITY),a=u.useRef(r);return u.useEffect(()=>{if(a.current=r,r===e.current)return;const f=t?0:Math.max(0,l.current+s-Date.now()),g=setTimeout(()=>{a.current!==e.current&&(l.current=Date.now(),e.current=a.current,n(a.current))},f);return()=>clearTimeout(g)},[r,s,t]),m}function V({value:r,max:s=100,variant:t="continuous",segments:m,status:n="default",size:e="md",rounded:l=!0,busy:a=!1,label:f,valueText:g,announce:j=!0,announceEvery:y=1500,labels:v,className:x}){const i={...H,...v},{known:w,fraction:k,percent:I,states:E,done:L,applicable:S,complete:$}=G.progressValues({value:r,max:s,variant:t,segments:m}),F=g??(t==="segmented"?i.count(L,S):w?i.percent(I):i.indeterminate),q=$,A=P(i.status(f,F),y,q),N=a&&"rst:animate-pulse rst:motion-reduce:animate-none",C=t==="segmented"?o.jsx("div",{"aria-hidden":"true",className:c.cn("rst:flex rst:w-full",e==="sm"?"rst:gap-0.5":"rst:gap-1",T[e],N),"data-progress-track":"",children:E.map((d,D)=>o.jsx("span",{className:c.cn("rst:h-full rst:min-w-0 rst:flex-1",l?"rst:rounded-full":"rst:rounded-sm",d==="done"&&[b[n],h],d==="current"&&[p,"rst:border-2",O[n]],d==="remaining"&&[p,R],d==="na"&&"rst:border rst:border-dashed rst:border-gray-500 rst:dark:border-gray-400"),"data-segment":d},D))}):o.jsx("div",{"aria-hidden":"true",className:c.cn("rst:relative rst:w-full rst:overflow-hidden rst:rounded-full",T[e],p,R,N),"data-progress-track":"",children:w?o.jsx("div",{className:c.cn("rst:h-full rst:rounded-full rst:transition-[width] rst:motion-reduce:transition-none",b[n],h),style:{width:`${k*100}%`,minWidth:k>0?e==="sm"?"0.375rem":"0.625rem":void 0},"data-progress-fill":""}):o.jsx("div",{className:c.cn("rst:absolute rst:inset-y-0 rst:left-0 rst:w-[30%] rst:rounded-full rst:animate-progress-travel",b[n],h),"data-progress-fill":"indeterminate"})});return o.jsxs("div",{className:c.cn("rst:w-full",x),"data-progress":t,"data-status":n,children:[C,j&&o.jsx("span",{role:"status",className:"rst:sr-only","data-progress-status":"",children:A})]})}exports.Progress=V;
@@ -0,0 +1,61 @@
1
+ import { SegmentState } from './progress-values';
2
+ export type ProgressStatus = "default" | "success" | "warning" | "error";
3
+ /** A segment's state: finished, under way, still to come, or not part of this one. */
4
+ export type ProgressSegmentState = SegmentState;
5
+ /** Every word Progress says. Pass any of them in `labels` to replace it. */
6
+ export interface ProgressLabels {
7
+ /** A continuous value as said: "45%". */
8
+ percent: (percent: number) => string;
9
+ /** A segmented value as said: "3 of 8". Segments that don't apply aren't counted. */
10
+ count: (done: number, total: number) => string;
11
+ /** Said when there's no value to give. */
12
+ indeterminate: string;
13
+ /** The label and the value together: "Uploading photos: 45%". */
14
+ status: (label: string | undefined, value: string) => string;
15
+ }
16
+ export interface ProgressProps {
17
+ /**
18
+ * How far, from 0 to `max`. Leave it out for indeterminate: work under way
19
+ * with no figure, drawn as a pill crossing the track.
20
+ */
21
+ value?: number | null;
22
+ /** The whole: 100 by default; for segmented, the number of segments. */
23
+ max?: number;
24
+ /**
25
+ * - `"continuous"`: one filled track, for a fraction (bytes, percent).
26
+ * - `"segmented"`: one cell per unit, for a count of discrete things:
27
+ * "twelve of sixteen" reads without a number beside it.
28
+ */
29
+ variant?: "continuous" | "segmented";
30
+ /** Segmented: each segment's state, when there's more to say than done and not done. */
31
+ segments?: ProgressSegmentState[];
32
+ status?: ProgressStatus;
33
+ size?: "sm" | "md";
34
+ /** Segmented: round each cell into a capsule (the default), or keep them square. */
35
+ rounded?: boolean;
36
+ /**
37
+ * The value stands but work goes on (a server finishing what it was sent):
38
+ * the bar breathes in place. Stops under reduced motion.
39
+ */
40
+ busy?: boolean;
41
+ /** What's progressing, for the status a screen reader hears: "Uploading photos". */
42
+ label?: string;
43
+ /** The value as said, in place of the percent or the count: "3 of 8 files". */
44
+ valueText?: string;
45
+ /** Give the value to screen readers in a polite status region. Off when something else already says it. */
46
+ announce?: boolean;
47
+ /** The least time between two announcements, in milliseconds. */
48
+ announceEvery?: number;
49
+ labels?: Partial<ProgressLabels>;
50
+ className?: string;
51
+ }
52
+ /**
53
+ * How far through something is: a continuous bar, or a segmented one.
54
+ *
55
+ * The bar is a picture and is hidden from screen readers; a status region
56
+ * beside it carries the value as text, because a changed `aria-label` isn't
57
+ * reliably announced and a `progressbar` may beep or speak on every update.
58
+ * Most screens want ProgressField, which adds a visible label and value.
59
+ */
60
+ declare function Progress({ value, max, variant, segments, status, size, rounded, busy, label, valueText: valueOverride, announce, announceEvery, labels: overrides, className, }: ProgressProps): import("react/jsx-runtime").JSX.Element;
61
+ export { Progress };
@@ -0,0 +1,106 @@
1
+ "use client";
2
+ import { jsx as a, jsxs as H } from "react/jsx-runtime";
3
+ import { useState as _, useRef as g, useEffect as K } from "react";
4
+ import { cn as d } from "../../../lib/utils.js";
5
+ import { progressValues as O } from "./progress-values.js";
6
+ const V = {
7
+ percent: (r) => `${r}%`,
8
+ count: (r, s) => `${r} of ${s}`,
9
+ indeterminate: "Loading",
10
+ status: (r, s) => r ? `${r}: ${s}` : s
11
+ }, b = {
12
+ default: "rst:bg-primary-600 rst:dark:bg-primary-400",
13
+ success: "rst:bg-success-600 rst:dark:bg-success-400",
14
+ warning: "rst:bg-amber-700 rst:dark:bg-amber-400",
15
+ error: "rst:bg-error-600 rst:dark:bg-error-400"
16
+ }, B = {
17
+ default: "rst:border-primary-600 rst:dark:border-primary-400",
18
+ success: "rst:border-success-600 rst:dark:border-success-400",
19
+ warning: "rst:border-amber-700 rst:dark:border-amber-400",
20
+ error: "rst:border-error-600 rst:dark:border-error-400"
21
+ }, p = "rst:bg-[var(--roster-skeleton)]", T = "rst:forced-colors:outline-solid rst:forced-colors:outline-1", h = "rst:forced-colors:forced-color-adjust-none rst:forced-colors:bg-[Highlight]", y = { sm: "rst:h-1.5", md: "rst:h-2.5" };
22
+ function M(r, s, t) {
23
+ const [u, o] = _(r), e = g(r), l = g(Number.NEGATIVE_INFINITY), n = g(r);
24
+ return K(() => {
25
+ if (n.current = r, r === e.current) return;
26
+ const m = t ? 0 : Math.max(0, l.current + s - Date.now()), f = setTimeout(() => {
27
+ n.current !== e.current && (l.current = Date.now(), e.current = n.current, o(n.current));
28
+ }, m);
29
+ return () => clearTimeout(f);
30
+ }, [r, s, t]), u;
31
+ }
32
+ function J({
33
+ value: r,
34
+ max: s = 100,
35
+ variant: t = "continuous",
36
+ segments: u,
37
+ status: o = "default",
38
+ size: e = "md",
39
+ rounded: l = !0,
40
+ busy: n = !1,
41
+ label: m,
42
+ valueText: f,
43
+ announce: I = !0,
44
+ announceEvery: v = 1500,
45
+ labels: E,
46
+ className: L
47
+ }) {
48
+ const i = { ...V, ...E }, { known: w, fraction: k, percent: R, states: $, done: F, applicable: A, complete: C } = O({ value: r, max: s, variant: t, segments: u }), D = f ?? (t === "segmented" ? i.count(F, A) : w ? i.percent(R) : i.indeterminate), S = C, j = M(i.status(m, D), v, S), N = n && "rst:animate-pulse rst:motion-reduce:animate-none", x = t === "segmented" ? /* @__PURE__ */ a(
49
+ "div",
50
+ {
51
+ "aria-hidden": "true",
52
+ className: d("rst:flex rst:w-full", e === "sm" ? "rst:gap-0.5" : "rst:gap-1", y[e], N),
53
+ "data-progress-track": "",
54
+ children: $.map((c, G) => /* @__PURE__ */ a(
55
+ "span",
56
+ {
57
+ className: d(
58
+ "rst:h-full rst:min-w-0 rst:flex-1",
59
+ l ? "rst:rounded-full" : "rst:rounded-sm",
60
+ c === "done" && [b[o], h],
61
+ /* Under way: the track inside a border of the fill's color. */
62
+ c === "current" && [p, "rst:border-2", B[o]],
63
+ c === "remaining" && [p, T],
64
+ /* Not part of this one: a dashed outline, neither filled nor to
65
+ fill. Gray-500 on light and gray-400 on dark clear 3:1. */
66
+ c === "na" && "rst:border rst:border-dashed rst:border-gray-500 rst:dark:border-gray-400"
67
+ ),
68
+ "data-segment": c
69
+ },
70
+ G
71
+ ))
72
+ }
73
+ ) : /* @__PURE__ */ a(
74
+ "div",
75
+ {
76
+ "aria-hidden": "true",
77
+ className: d("rst:relative rst:w-full rst:overflow-hidden rst:rounded-full", y[e], p, T, N),
78
+ "data-progress-track": "",
79
+ children: w ? /* @__PURE__ */ a(
80
+ "div",
81
+ {
82
+ className: d(
83
+ "rst:h-full rst:rounded-full rst:transition-[width] rst:motion-reduce:transition-none",
84
+ b[o],
85
+ h
86
+ ),
87
+ style: { width: `${k * 100}%`, minWidth: k > 0 ? e === "sm" ? "0.375rem" : "0.625rem" : void 0 },
88
+ "data-progress-fill": ""
89
+ }
90
+ ) : /* @__PURE__ */ a(
91
+ "div",
92
+ {
93
+ className: d("rst:absolute rst:inset-y-0 rst:left-0 rst:w-[30%] rst:rounded-full rst:animate-progress-travel", b[o], h),
94
+ "data-progress-fill": "indeterminate"
95
+ }
96
+ )
97
+ }
98
+ );
99
+ return /* @__PURE__ */ H("div", { className: d("rst:w-full", L), "data-progress": t, "data-status": o, children: [
100
+ x,
101
+ I && /* @__PURE__ */ a("span", { role: "status", className: "rst:sr-only", "data-progress-status": "", children: j })
102
+ ] });
103
+ }
104
+ export {
105
+ J as Progress
106
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function u({value:t,max:c=100,variant:a="continuous",segments:h}){const s=Number.isFinite(c)?Math.max(0,c):100,e=t!=null&&Number.isFinite(t),n=e&&s>0?Math.min(1,Math.max(0,t/s)):0,m=n>=1?100:n<=0?0:Math.min(99,Math.max(1,Math.round(n*100))),r=a==="segmented"?h??Array.from({length:Math.floor(s)},(o,d)=>e&&d<Math.floor(t)?"done":"remaining"):[],l=r.filter(o=>o==="done").length,i=r.filter(o=>o!=="na").length,g=a==="segmented"?i>0&&l===i:e&&n>=1;return{known:e,fraction:n,percent:m,states:r,done:l,applicable:i,complete:g}}exports.progressValues=u;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The numbers a Progress shows and says, worked out once so the bar, the
3
+ * status and a ProgressField's visible value can't disagree.
4
+ */
5
+ export type SegmentState = "done" | "current" | "remaining" | "na";
6
+ export interface ProgressValues {
7
+ /** Whether there's a figure at all; without one, the bar is indeterminate. */
8
+ known: boolean;
9
+ /** 0 to 1. */
10
+ fraction: number;
11
+ /** The percent as said and shown: 1 to 99 while under way, never rounded to 0 or 100. */
12
+ percent: number;
13
+ /** Segmented: every segment's state. */
14
+ states: SegmentState[];
15
+ done: number;
16
+ /** Segments that count: all but `na`. */
17
+ applicable: number;
18
+ /** Nothing left: the value is at the whole, or every applicable segment is done. */
19
+ complete: boolean;
20
+ }
21
+ export declare function progressValues({ value, max, variant, segments, }: {
22
+ value?: number | null;
23
+ max?: number;
24
+ variant?: "continuous" | "segmented";
25
+ segments?: SegmentState[];
26
+ }): ProgressValues;
@@ -0,0 +1,12 @@
1
+ function f({
2
+ value: n,
3
+ max: c = 100,
4
+ variant: a = "continuous",
5
+ segments: m
6
+ }) {
7
+ const s = Number.isFinite(c) ? Math.max(0, c) : 100, o = n != null && Number.isFinite(n), t = o && s > 0 ? Math.min(1, Math.max(0, n / s)) : 0, l = t >= 1 ? 100 : t <= 0 ? 0 : Math.min(99, Math.max(1, Math.round(t * 100))), r = a === "segmented" ? m ?? Array.from({ length: Math.floor(s) }, (e, d) => o && d < Math.floor(n) ? "done" : "remaining") : [], h = r.filter((e) => e === "done").length, i = r.filter((e) => e !== "na").length, M = a === "segmented" ? i > 0 && h === i : o && t >= 1;
8
+ return { known: o, fraction: t, percent: l, states: r, done: h, applicable: i, complete: M };
9
+ }
10
+ export {
11
+ f as progressValues
12
+ };
@@ -0,0 +1 @@
1
+ "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),x=require("react"),c=require("../../../lib/utils.cjs"),k={sm:"rst:text-xs",md:"rst:text-sm",lg:"rst:text-base",inherit:""},f={xs:"rst:h-6 rst:w-6",sm:"rst:h-8 rst:w-8",md:"rst:h-10 rst:w-10",lg:"rst:h-12 rst:w-12",xl:"rst:h-16 rst:w-16"},b={sm:"rst:rounded-sm",md:"rst:rounded-md",lg:"rst:rounded-lg",xl:"rst:rounded-xl",full:"rst:rounded-full"},h="rst:animate-skeleton rst:forced-colors:outline-solid rst:forced-colors:outline-1";function S({shape:s="line",size:r,lines:a=1,width:e,height:u,radius:i,className:l}){if(s==="circle"){const o=r&&r in f?r:"md";return t.jsx("span",{"aria-hidden":"true",className:c.cn("rst:block rst:shrink-0 rst:rounded-full rst:border rst:border-transparent",f[o],h,l),"data-skeleton":"circle"})}if(s==="block")return t.jsx("span",{"aria-hidden":"true",className:c.cn("rst:block rst:w-full",b[i??"lg"],h,l),style:{height:u??"8rem",width:e},"data-skeleton":"block"});const m=r&&r in k?r:"md",n=Math.max(1,Math.floor(a)),j=o=>{const d=o===n-1;return t.jsxs("span",{className:c.cn("rst:relative rst:block",n>1&&!d&&"rst:w-full"),style:{width:n>1?d?e??"60%":void 0:e},children:[" ",t.jsx("span",{className:c.cn("rst:absolute rst:inset-x-0 rst:top-1/2 rst:h-[0.7em] rst:-translate-y-1/2",b[i??"sm"],h)})]},o)};return t.jsx("span",{"aria-hidden":"true",className:c.cn("rst:block",k[m],l),"data-skeleton":"line",children:Array.from({length:n},(o,d)=>j(d))})}function g({loading:s,skeleton:r,label:a="Loading",announce:e=!0,children:u,className:i}){const[l,m]=x.useState("");return x.useEffect(()=>{const n=setTimeout(()=>m(s&&e?a:""),s?50:0);return()=>clearTimeout(n)},[s,a,e]),t.jsxs(t.Fragment,{children:[t.jsx("span",{role:"status",className:"rst:sr-only","data-skeleton-status":"",children:l}),t.jsx("div",{"aria-busy":s||void 0,className:i,"data-skeleton-region":s?"loading":"ready",children:s?t.jsx("div",{"aria-hidden":"true",children:r}):u})]})}exports.Skeleton=S;exports.SkeletonRegion=g;
@@ -0,0 +1,79 @@
1
+ import { CSSProperties, ReactNode } from 'react';
2
+ /**
3
+ * A text size a line stands in for: its font size and line height, so one
4
+ * line of skeleton is exactly one line of the text that replaces it.
5
+ * `inherit` takes whatever the surrounding text is.
6
+ */
7
+ declare const LINE_SIZE: {
8
+ readonly sm: "rst:text-xs";
9
+ readonly md: "rst:text-sm";
10
+ readonly lg: "rst:text-base";
11
+ readonly inherit: "";
12
+ };
13
+ declare const CIRCLE_SIZE: {
14
+ readonly xs: "rst:h-6 rst:w-6";
15
+ readonly sm: "rst:h-8 rst:w-8";
16
+ readonly md: "rst:h-10 rst:w-10";
17
+ readonly lg: "rst:h-12 rst:w-12";
18
+ readonly xl: "rst:h-16 rst:w-16";
19
+ };
20
+ declare const RADIUS: {
21
+ readonly sm: "rst:rounded-sm";
22
+ readonly md: "rst:rounded-md";
23
+ readonly lg: "rst:rounded-lg";
24
+ readonly xl: "rst:rounded-xl";
25
+ readonly full: "rst:rounded-full";
26
+ };
27
+ export interface SkeletonProps {
28
+ /**
29
+ * - `"line"`: a line of text, one line box tall at its `size`.
30
+ * - `"block"`: an image, a chart, any box: `height` sets it.
31
+ * - `"circle"`: an avatar, at Avatar's sizes.
32
+ */
33
+ shape?: "line" | "block" | "circle";
34
+ /**
35
+ * For a line, the text it stands in for: `sm` (12px on 16), `md` (14px on
36
+ * 20), `lg` (16px on 24), or `inherit`. For a circle, Avatar's sizes.
37
+ */
38
+ size?: keyof typeof LINE_SIZE | keyof typeof CIRCLE_SIZE;
39
+ /** How many lines, for a paragraph. The last is shorter, as a last line is. */
40
+ lines?: number;
41
+ /** The width: a line's, or the last line's when there are several. */
42
+ width?: CSSProperties["width"];
43
+ /** A block's height. */
44
+ height?: CSSProperties["height"];
45
+ /** A block's corners, from the radius scale. */
46
+ radius?: keyof typeof RADIUS;
47
+ className?: string;
48
+ }
49
+ /**
50
+ * A placeholder in the shape of what's loading.
51
+ *
52
+ * Hidden from screen readers, always: a skeleton is a picture of content that
53
+ * isn't there yet. The region it stands in for says it's busy; use
54
+ * `SkeletonRegion`, which does that, or set `aria-busy` on it yourself.
55
+ */
56
+ declare function Skeleton({ shape, size, lines, width, height, radius, className }: SkeletonProps): import("react/jsx-runtime").JSX.Element;
57
+ export interface SkeletonRegionProps {
58
+ /** While true, the skeleton shows and the region says it's busy. */
59
+ loading: boolean;
60
+ /** What shows while loading. */
61
+ skeleton: ReactNode;
62
+ /** What a screen reader hears while loading: "Loading the schedule". */
63
+ label?: string;
64
+ /**
65
+ * Say `label` (the default). Off for all but one when several regions load
66
+ * together, a grid of cards say, or wrap them in one region instead.
67
+ */
68
+ announce?: boolean;
69
+ children?: ReactNode;
70
+ className?: string;
71
+ }
72
+ /**
73
+ * The region a skeleton stands in for: `aria-busy` while loading, the
74
+ * skeleton hidden from screen readers, and `label` said once, politely.
75
+ * When `loading` turns false the content replaces the skeleton in the same
76
+ * box, so a skeleton drawn at the content's size means nothing moves.
77
+ */
78
+ declare function SkeletonRegion({ loading, skeleton, label, announce, children, className }: SkeletonRegionProps): import("react/jsx-runtime").JSX.Element;
79
+ export { Skeleton, SkeletonRegion };
@@ -0,0 +1,80 @@
1
+ "use client";
2
+ import { jsx as s, jsxs as p, Fragment as g } from "react/jsx-runtime";
3
+ import { useState as y, useEffect as S } from "react";
4
+ import { cn as a } from "../../../lib/utils.js";
5
+ const f = {
6
+ sm: "rst:text-xs",
7
+ md: "rst:text-sm",
8
+ lg: "rst:text-base",
9
+ inherit: ""
10
+ }, k = {
11
+ xs: "rst:h-6 rst:w-6",
12
+ sm: "rst:h-8 rst:w-8",
13
+ md: "rst:h-10 rst:w-10",
14
+ lg: "rst:h-12 rst:w-12",
15
+ xl: "rst:h-16 rst:w-16"
16
+ }, x = {
17
+ sm: "rst:rounded-sm",
18
+ md: "rst:rounded-md",
19
+ lg: "rst:rounded-lg",
20
+ xl: "rst:rounded-xl",
21
+ full: "rst:rounded-full"
22
+ }, h = "rst:animate-skeleton rst:forced-colors:outline-solid rst:forced-colors:outline-1";
23
+ function E({ shape: t = "line", size: r, lines: d = 1, width: e, height: u, radius: c, className: o }) {
24
+ if (t === "circle") {
25
+ const l = r && r in k ? r : "md";
26
+ return /* @__PURE__ */ s(
27
+ "span",
28
+ {
29
+ "aria-hidden": "true",
30
+ className: a("rst:block rst:shrink-0 rst:rounded-full rst:border rst:border-transparent", k[l], h, o),
31
+ "data-skeleton": "circle"
32
+ }
33
+ );
34
+ }
35
+ if (t === "block")
36
+ return /* @__PURE__ */ s(
37
+ "span",
38
+ {
39
+ "aria-hidden": "true",
40
+ className: a("rst:block rst:w-full", x[c ?? "lg"], h, o),
41
+ style: { height: u ?? "8rem", width: e },
42
+ "data-skeleton": "block"
43
+ }
44
+ );
45
+ const m = r && r in f ? r : "md", n = Math.max(1, Math.floor(d)), b = (l) => {
46
+ const i = l === n - 1;
47
+ return /* @__PURE__ */ p(
48
+ "span",
49
+ {
50
+ className: a("rst:relative rst:block", n > 1 && !i && "rst:w-full"),
51
+ style: { width: n > 1 ? i ? e ?? "60%" : void 0 : e },
52
+ children: [
53
+ " ",
54
+ /* @__PURE__ */ s(
55
+ "span",
56
+ {
57
+ className: a("rst:absolute rst:inset-x-0 rst:top-1/2 rst:h-[0.7em] rst:-translate-y-1/2", x[c ?? "sm"], h)
58
+ }
59
+ )
60
+ ]
61
+ },
62
+ l
63
+ );
64
+ };
65
+ return /* @__PURE__ */ s("span", { "aria-hidden": "true", className: a("rst:block", f[m], o), "data-skeleton": "line", children: Array.from({ length: n }, (l, i) => b(i)) });
66
+ }
67
+ function L({ loading: t, skeleton: r, label: d = "Loading", announce: e = !0, children: u, className: c }) {
68
+ const [o, m] = y("");
69
+ return S(() => {
70
+ const n = setTimeout(() => m(t && e ? d : ""), t ? 50 : 0);
71
+ return () => clearTimeout(n);
72
+ }, [t, d, e]), /* @__PURE__ */ p(g, { children: [
73
+ /* @__PURE__ */ s("span", { role: "status", className: "rst:sr-only", "data-skeleton-status": "", children: o }),
74
+ /* @__PURE__ */ s("div", { "aria-busy": t || void 0, className: c, "data-skeleton-region": t ? "loading" : "ready", children: t ? /* @__PURE__ */ s("div", { "aria-hidden": "true", children: r }) : u })
75
+ ] });
76
+ }
77
+ export {
78
+ E as Skeleton,
79
+ L as SkeletonRegion
80
+ };
@@ -1 +1 @@
1
- "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("react/jsx-runtime"),u=require("react"),t=require("../../../lib/utils.cjs"),h=require("./stat-variants.cjs"),o=require("../Eyebrow/Eyebrow.cjs"),d="rst:block rst:font-mono rst:text-[0.53125rem] rst:font-normal rst:normal-nums rst:leading-none rst:tracking-[0.06em] rst:text-gray-500 rst:opacity-75 rst:dark:text-gray-400",x=u.forwardRef(({value:a,label:n,source:r,size:m,colorScheme:j,semantics:g="definition",className:c,...i},l)=>{const e=h.statValueVariants({size:m,colorScheme:j});return g==="standalone"?s.jsxs("div",{ref:l,className:t.cn("rst:flex rst:flex-col rst:gap-[3px]",c),...i,children:[s.jsx("span",{className:e,children:a}),s.jsx(o.Eyebrow,{size:"xs",children:n}),r&&s.jsx("span",{className:d,children:r})]}):s.jsxs("div",{ref:l,className:t.cn("rst:grid rst:content-start rst:gap-[3px]",c),...i,children:[s.jsx("dt",{className:"rst:relative rst:col-start-1 rst:row-start-2",children:s.jsx(o.Eyebrow,{size:"xs",children:n})}),r?s.jsxs("dd",{className:t.cn("rst:m-0",e,"rst:col-start-1 rst:row-span-3 rst:row-start-1 rst:grid rst:grid-rows-subgrid"),children:[s.jsx("span",{className:"rst:row-start-1",children:a}),s.jsx("span",{className:t.cn(d,"rst:row-start-3"),children:r})]}):s.jsx("dd",{className:t.cn("rst:m-0 rst:col-start-1 rst:row-start-1",e),children:a})]})});x.displayName="Stat";exports.Stat=x;
1
+ "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("react/jsx-runtime"),w=require("react"),t=require("../../../lib/utils.cjs"),x=require("./stat-variants.cjs"),d=require("../Eyebrow/Eyebrow.cjs"),o=x.statSourceClass,j=w.forwardRef(({value:a,label:n,source:r,size:m,colorScheme:u,semantics:h="definition",className:c,...i},l)=>{const e=x.statValueVariants({size:m,colorScheme:u});return h==="standalone"?s.jsxs("div",{ref:l,className:t.cn("rst:flex rst:flex-col rst:gap-[3px]",c),...i,children:[s.jsx("span",{className:e,children:a}),s.jsx(d.Eyebrow,{size:"xs",children:n}),r&&s.jsx("span",{className:o,children:r})]}):s.jsxs("div",{ref:l,className:t.cn("rst:grid rst:content-start rst:gap-[3px]",c),...i,children:[s.jsx("dt",{className:"rst:relative rst:col-start-1 rst:row-start-2",children:s.jsx(d.Eyebrow,{size:"xs",children:n})}),r?s.jsxs("dd",{className:t.cn("rst:m-0",e,"rst:col-start-1 rst:row-span-3 rst:row-start-1 rst:grid rst:grid-rows-subgrid"),children:[s.jsx("span",{className:"rst:row-start-1",children:a}),s.jsx("span",{className:t.cn(o,"rst:row-start-3"),children:r})]}):s.jsx("dd",{className:t.cn("rst:m-0 rst:col-start-1 rst:row-start-1",e),children:a})]})});j.displayName="Stat";exports.Stat=j;