@blakesteve/roster 5.9.0 → 5.10.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/README.md CHANGED
@@ -269,7 +269,7 @@ changes nothing.
269
269
  | ----- | ------- | -------- |
270
270
  | `--roster-radius-sm` | `0.25rem` | `Button` `xs`, `Checkbox` `sm` and `md`, `InlineCode`, `Link`, `MultiSelect`'s chip dismiss, the dismiss and reveal buttons in `Alert`, `Toast` and `PasswordInput` |
271
271
  | `--roster-radius-md` | `0.375rem` | `Button`, `Input`, `Textarea`, `Select`, `MultiSelect`, `Badge`, `Avatar` and its popover, `Checkbox` `lg`, `CheckboxGroup`'s panel, `Disclosure`, `Dialog`'s and `Sheet`'s close buttons, `Navbar`'s links and menu, popup panels, `Table` |
272
- | `--roster-radius-lg` | `0.5rem` | `Alert`, `Toast`, `Tooltip`, `CallToAction`, `EmptyState`, `ErrorState`, `LiquidTabs`, `MatchupCard`, `Navbar`'s mobile panel |
272
+ | `--roster-radius-lg` | `0.5rem` | `Alert`, `Toast`, `Tooltip`, `CallToAction`, `EmptyState`, `ErrorState`, `LiquidTabs`, `MatchupCard`, `Navbar`'s mobile panel, `Skeleton` blocks (unless `--roster-skeleton-radius` is set) |
273
273
  | `--roster-radius-xl` | `0.75rem` | `Card`, `LiquidTabs`' `pill` strip |
274
274
  | `--roster-radius-2xl` | `1rem` | `Dialog`, the top of `Sheet` |
275
275
  | `--roster-radius-3xl` | `1.5rem` | nothing yet; wired for the first component that uses it |
@@ -607,10 +607,14 @@ its `label` (default "loading") says so.
607
607
  The loading system has its own: `--roster-skeleton` and
608
608
  `--roster-skeleton-highlight` are the skeleton's fill and the light that
609
609
  crosses it, and the empty part of every progress bar (gray-200 and gray-100,
610
- gray-700 and gray-600 in dark). Three durations, unset by default, change the
611
- pace: `--roster-skeleton-duration` (1.8s), `--roster-progress-travel-duration`
612
- (1.4s, the indeterminate pill) and `--roster-dots-duration` (1.2s). Under
613
- reduced motion all three stop on a resting frame.
610
+ gray-700 and gray-600 in dark). Four durations, unset by default, change the
611
+ pace: `--roster-skeleton-duration` (1.8s, the shimmer),
612
+ `--roster-skeleton-pulse-duration` (2s, the pulse),
613
+ `--roster-progress-travel-duration` (1.4s, the indeterminate pill) and
614
+ `--roster-dots-duration` (1.2s). `--roster-skeleton-pulse-opacity` (0.5) is
615
+ how far a pulse fades. Under reduced motion all of them stop on a resting
616
+ frame. `--roster-skeleton-radius` rounds skeleton blocks without touching the
617
+ shared radius scale (see Skeleton below).
614
618
 
615
619
  ### Scrollbars
616
620
 
@@ -1039,7 +1043,7 @@ rather than vanishing from a field that still reports it.
1039
1043
 
1040
1044
  #### CheckboxGroup
1041
1045
 
1042
- Three things it adds over the version mega-squad's squad form hand-rolled:
1046
+ Three things it adds over the version one app's form hand-rolled:
1043
1047
 
1044
1048
  - **Each option is a Headless UI `Field`**, so its label is wired to its own
1045
1049
  checkbox and clicking the text toggles it. The pointer cursor sits on the
@@ -1109,8 +1113,8 @@ silently drops any selected value whose option has not loaded yet.
1109
1113
 
1110
1114
  `CheckboxGroup` exists instead of a multi-select `Select`, and the reason is
1111
1115
  that neither multi-selection UI in the portfolio was ever a dropdown.
1112
- mega-squad's squad form picks sports from a grouped, scrollable checklist with
1113
- category headings; bb-memorial's share form picks tags from a flat row of
1116
+ One app's form picks sports from a grouped, scrollable checklist with
1117
+ category headings; another's share form picks tags from a flat row of
1114
1118
  toggles. Collapsing either into a menu would have hidden the options behind a
1115
1119
  click to save vertical space neither screen was short of, so the component
1116
1120
  follows the shape that already worked. A multi-select dropdown is still the
@@ -1205,7 +1209,7 @@ rather than a rewrite.
1205
1209
 
1206
1210
  ```tsx
1207
1211
  <RadioGroup
1208
- label="Who can see this squad"
1212
+ label="Who can see this group"
1209
1213
  helperText="You can change this at any time."
1210
1214
  value={visibility}
1211
1215
  onChange={setVisibility}
@@ -1459,7 +1463,21 @@ import { SkeletonRegion, SkeletonCard } from "@blakesteve/roster";
1459
1463
  - The fill is `--roster-skeleton` (gray-200, gray-700 in dark), with
1460
1464
  `--roster-skeleton-highlight` crossing it. Neutral rather than the brand
1461
1465
  shimmer Countdown uses, and its own pair, so retinting one never recolors the
1462
- other. Under reduced motion it holds still.
1466
+ other.
1467
+ - `animation` picks how it moves: `"shimmer"` (the default, a band of light
1468
+ crossing the fill), `"pulse"` (the fill fading and returning, nothing
1469
+ moving: quieter on a dense page) or `"none"`. Under reduced motion both
1470
+ movements stop on the flat fill, and in forced colors every shape keeps its
1471
+ outline (a pulse stops there, so the outline never fades). Pass it to
1472
+ `SkeletonRegion` and every skeleton inside takes it, presets included; a
1473
+ preset or a skeleton that names its own keeps it. A pulse runs
1474
+ `--roster-skeleton-pulse-duration` (2s) and fades to
1475
+ `--roster-skeleton-pulse-opacity` (0.5).
1476
+ - A block's corners are `--roster-skeleton-radius`, the `lg` step unless
1477
+ set. Set it on `:root` or on a wrapper (`14px`, say) and skeleton blocks
1478
+ follow, and nothing else does: the shared scale and every other component
1479
+ stay as they are. A block's `radius` prop still wins, a line keeps its
1480
+ small step, and a circle stays round.
1463
1481
 
1464
1482
  **Progress and ProgressField.** `ProgressField` is the form to reach for: the
1465
1483
  label and value on one baseline above the bar, an optional detail line below.
@@ -1833,7 +1851,7 @@ function App() {
1833
1851
  | `CollapsibleSection` | Clamps any content (prose, chips, image grids) to a fixed height with a fade and expand/collapse toggle |
1834
1852
  | `Select` | Dropdown selector, on the same size scale as `Button` and `Input` |
1835
1853
  | `SegmentBar` | Proportional horizontal bar divided into colored segments with optional legend (a split, not progress) |
1836
- | `Skeleton` | Line, block or circle placeholder at the content's exact size; `SkeletonRegion` sets `aria-busy` |
1854
+ | `Skeleton` | Line, block or circle placeholder at the content's exact size, shimmering, pulsing or still; `SkeletonRegion` sets `aria-busy` |
1837
1855
  | `Progress` | Continuous or segmented progress bar: determinate, indeterminate or busy; per-segment state |
1838
1856
  | `LoadingDots` | Three dots for a model generating before its first token; replaced by the text |
1839
1857
  | `Stat` | A figure with its label and, optionally, its source; a valid `<dl>` group, or `semantics="standalone"` |
@@ -2037,14 +2055,15 @@ anything new. It also fails when a listed class starts emitting, so a fix cannot
2037
2055
  leave a stale entry behind. Run it with `--verbose` to print the list.
2038
2056
 
2039
2057
  Story tests that need the user's own input, a wheel, a mouse drag or a pointer
2040
- resting on something, get it
2041
- from `.storybook/real-input.ts`: Vitest browser commands that drive
2042
- Playwright's mouse, whose events are trusted. A synthetic wheel or pointer
2043
- event never scrolls anything, so "a vertical wheel scrolls the page, not the
2044
- row" can't be tested without them. The commands fail loudly when the pointer
2045
- isn't over their target, rather than letting a wheel land on the page and a
2046
- test pass for the wrong reason. Storybook's own UI has no such input, so those
2047
- steps are skipped there.
2058
+ resting on something, get it from `.storybook/real-input.ts`: Vitest browser
2059
+ commands that drive Playwright's mouse, whose events are trusted. They run in
2060
+ Chromium and in WebKit, where pointer and scroll events differ: WebKit sends a
2061
+ pointermove when content scrolls under a resting pointer, and Chromium doesn't.
2062
+ A synthetic wheel or pointer event never scrolls anything, so "a vertical wheel
2063
+ scrolls the page, not the row" can't be tested without them. The commands fail
2064
+ loudly when the pointer isn't over their target, rather than letting a wheel
2065
+ land on the page and a test pass for the wrong reason. Storybook's own UI has
2066
+ no such input, so those steps are skipped there.
2048
2067
 
2049
2068
  `src/lib/utils.test.ts` pins `cn`. `tailwind-merge` has to be configured with the
2050
2069
  prefix or it stops recognising Roster's classes as utilities and silently
@@ -2073,9 +2092,9 @@ Output in `dist/`:
2073
2092
 
2074
2093
  ### What `meta.json` is for
2075
2094
 
2076
- Consumers read this package to describe it. blakeb.dev's case study takes the
2077
- version from `package.json` and counts components by parsing the exports out of
2078
- `dist/*.d.ts`, so both stay correct on their own.
2095
+ Consumers read this package to describe it. A portfolio site's case study takes
2096
+ the version from `package.json` and counts components by parsing the exports
2097
+ out of `dist/*.d.ts`, so both stay correct on their own.
2079
2098
 
2080
2099
  The test count has no such source. `files` is `["dist"]`, so the suite never
2081
2100
  leaves this repo, and the number on that page was a hand-typed literal that
@@ -65,7 +65,7 @@ const e = /* @__PURE__ */ r(
65
65
  // Translucent rather than a solid tint step. A ghost button sits directly
66
66
  // on the host's own background, and a solid `bg-*-50` silently disappears
67
67
  // wherever that background happens to equal the step it names — which is
68
- // exactly what happened to blakeb.dev, whose paper is the same value it
68
+ // exactly what happened to one app, whose paper is the same value it
69
69
  // maps `--roster-gray-100` to. An alpha wash darkens or lightens whatever
70
70
  // is actually behind it, so it reads on any surface.
71
71
  { variant: "ghost", colorScheme: "primary", className: "rst:text-primary-600 rst:hover:bg-primary-500/10 rst:dark:text-primary-400 rst:dark:hover:bg-primary-400/15" },
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The small uppercase label that sits above a heading, beside a rule, or as a
3
- * column head. Extracted from blakeb.dev, where it was hand-rolled as a CSS
4
- * class and used 26 times before anyone noticed it was a component.
3
+ * column head. Extracted from a portfolio site, where it was hand-rolled as a
4
+ * CSS class and used 26 times before anyone noticed it was a component.
5
5
  *
6
6
  * Monospace on purpose: at this size, tracked-out uppercase reads as chrome
7
7
  * rather than prose, which is the whole job.
@@ -49,7 +49,7 @@ const s = /* @__PURE__ */ r(
49
49
  },
50
50
  /* Deliberately the same three heights as Button: h-9 / h-10 / h-11.
51
51
  Input was a fixed 42px, which agreed with Button at no size at all, so
52
- a field beside a submit button never lined up. retrospect gave up and
52
+ a field beside a submit button never lined up. One app gave up and
53
53
  kept a raw <input> pinned to h-11. */
54
54
  size: {
55
55
  sm: "rst:h-9 rst:px-3",
@@ -1 +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;
1
+ "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),d=require("react"),i=require("../../../lib/utils.cjs"),b={sm:"rst:text-xs",md:"rst:text-sm",lg:"rst:text-base",inherit:""},j={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"},p={sm:"rst:rounded-sm",md:"rst:rounded-md",lg:"rst:rounded-lg",xl:"rst:rounded-xl",full:"rst:rounded-full"},v="rst:rounded-skeleton",y={shimmer:"rst:animate-skeleton rst:forced-colors:outline-solid rst:forced-colors:outline-1",pulse:"rst:animate-skeleton-pulse rst:forced-colors:outline-solid rst:forced-colors:outline-1 rst:forced-colors:animate-none",none:"rst:skeleton-fill rst:forced-colors:outline-solid rst:forced-colors:outline-1"},S=d.createContext(void 0);function C({shape:e="line",size:s,lines:a=1,width:r,height:h,radius:n,animation:x,className:o}){const f=d.useContext(S),l=y[x??f??"shimmer"];if(e==="circle"){const c=s&&s in j?s:"md";return t.jsx("span",{"aria-hidden":"true",className:i.cn("rst:block rst:shrink-0 rst:rounded-full rst:border rst:border-transparent",j[c],l,o),"data-skeleton":"circle"})}if(e==="block")return t.jsx("span",{"aria-hidden":"true",className:i.cn("rst:block rst:w-full",n?p[n]:v,l,o),style:{height:h??"8rem",width:r},"data-skeleton":"block"});const k=s&&s in b?s:"md",u=Math.max(1,Math.floor(a)),g=c=>{const m=c===u-1;return t.jsxs("span",{className:i.cn("rst:relative rst:block",u>1&&!m&&"rst:w-full"),style:{width:u>1?m?r??"60%":void 0:r},children:[" ",t.jsx("span",{className:i.cn("rst:absolute rst:inset-x-0 rst:top-1/2 rst:h-[0.7em] rst:-translate-y-1/2",p[n??"sm"],l)})]},c)};return t.jsx("span",{"aria-hidden":"true",className:i.cn("rst:block",b[k],o),"data-skeleton":"line",children:Array.from({length:u},(c,m)=>g(m))})}function I({loading:e,skeleton:s,label:a="Loading",announce:r=!0,animation:h,children:n,className:x}){const o=d.useContext(S),[f,l]=d.useState("");return d.useEffect(()=>{const k=setTimeout(()=>l(e&&r?a:""),e?50:0);return()=>clearTimeout(k)},[e,a,r]),t.jsxs(t.Fragment,{children:[t.jsx("span",{role:"status",className:"rst:sr-only","data-skeleton-status":"",children:f}),t.jsx("div",{"aria-busy":e||void 0,className:x,"data-skeleton-region":e?"loading":"ready",children:e?t.jsx(S.Provider,{value:h??o,children:t.jsx("div",{"aria-hidden":"true",children:s})}):n})]})}exports.Skeleton=C;exports.SkeletonRegion=I;
@@ -24,6 +24,8 @@ declare const RADIUS: {
24
24
  readonly xl: "rst:rounded-xl";
25
25
  readonly full: "rst:rounded-full";
26
26
  };
27
+ /** How a skeleton moves while it waits. */
28
+ export type SkeletonAnimation = "shimmer" | "pulse" | "none";
27
29
  export interface SkeletonProps {
28
30
  /**
29
31
  * - `"line"`: a line of text, one line box tall at its `size`.
@@ -42,8 +44,20 @@ export interface SkeletonProps {
42
44
  width?: CSSProperties["width"];
43
45
  /** A block's height. */
44
46
  height?: CSSProperties["height"];
45
- /** A block's corners, from the radius scale. */
47
+ /**
48
+ * A block's corners, from the radius scale. Without it a block reads
49
+ * `--roster-skeleton-radius`, which defaults to `lg`. A line's corners are
50
+ * `sm` unless this says otherwise; a circle is always round.
51
+ */
46
52
  radius?: keyof typeof RADIUS;
53
+ /**
54
+ * `"shimmer"` (the default): a highlight sweeps across the fill. `"pulse"`:
55
+ * the fill fades and returns, at `--roster-skeleton-pulse-duration` and down
56
+ * to `--roster-skeleton-pulse-opacity`. `"none"`: the fill holds still.
57
+ * Under reduced motion both movements stop. Inside a `SkeletonRegion`, it
58
+ * defaults to the region's.
59
+ */
60
+ animation?: SkeletonAnimation;
47
61
  className?: string;
48
62
  }
49
63
  /**
@@ -53,7 +67,7 @@ export interface SkeletonProps {
53
67
  * isn't there yet. The region it stands in for says it's busy; use
54
68
  * `SkeletonRegion`, which does that, or set `aria-busy` on it yourself.
55
69
  */
56
- declare function Skeleton({ shape, size, lines, width, height, radius, className }: SkeletonProps): import("react/jsx-runtime").JSX.Element;
70
+ declare function Skeleton({ shape, size, lines, width, height, radius, animation, className }: SkeletonProps): import("react/jsx-runtime").JSX.Element;
57
71
  export interface SkeletonRegionProps {
58
72
  /** While true, the skeleton shows and the region says it's busy. */
59
73
  loading: boolean;
@@ -66,6 +80,8 @@ export interface SkeletonRegionProps {
66
80
  * together, a grid of cards say, or wrap them in one region instead.
67
81
  */
68
82
  announce?: boolean;
83
+ /** How the skeletons inside move, unless one says otherwise: see `Skeleton`. */
84
+ animation?: SkeletonAnimation;
69
85
  children?: ReactNode;
70
86
  className?: string;
71
87
  }
@@ -75,5 +91,5 @@ export interface SkeletonRegionProps {
75
91
  * When `loading` turns false the content replaces the skeleton in the same
76
92
  * box, so a skeleton drawn at the content's size means nothing moves.
77
93
  */
78
- declare function SkeletonRegion({ loading, skeleton, label, announce, children, className }: SkeletonRegionProps): import("react/jsx-runtime").JSX.Element;
94
+ declare function SkeletonRegion({ loading, skeleton, label, announce, animation, children, className }: SkeletonRegionProps): import("react/jsx-runtime").JSX.Element;
79
95
  export { Skeleton, SkeletonRegion };
@@ -1,80 +1,85 @@
1
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 = {
2
+ import { jsx as e, jsxs as v, Fragment as g } from "react/jsx-runtime";
3
+ import { useContext as y, useState as w, useEffect as C, createContext as L } from "react";
4
+ import { cn as c } from "../../../lib/utils.js";
5
+ const p = {
6
6
  sm: "rst:text-xs",
7
7
  md: "rst:text-sm",
8
8
  lg: "rst:text-base",
9
9
  inherit: ""
10
- }, k = {
10
+ }, b = {
11
11
  xs: "rst:h-6 rst:w-6",
12
12
  sm: "rst:h-8 rst:w-8",
13
13
  md: "rst:h-10 rst:w-10",
14
14
  lg: "rst:h-12 rst:w-12",
15
15
  xl: "rst:h-16 rst:w-16"
16
- }, x = {
16
+ }, S = {
17
17
  sm: "rst:rounded-sm",
18
18
  md: "rst:rounded-md",
19
19
  lg: "rst:rounded-lg",
20
20
  xl: "rst:rounded-xl",
21
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 }) {
22
+ }, E = "rst:rounded-skeleton", N = {
23
+ shimmer: "rst:animate-skeleton rst:forced-colors:outline-solid rst:forced-colors:outline-1",
24
+ pulse: "rst:animate-skeleton-pulse rst:forced-colors:outline-solid rst:forced-colors:outline-1 rst:forced-colors:animate-none",
25
+ none: "rst:skeleton-fill rst:forced-colors:outline-solid rst:forced-colors:outline-1"
26
+ }, x = L(void 0);
27
+ function j({ shape: t = "line", size: r, lines: d = 1, width: s, height: m, radius: o, animation: h, className: n }) {
28
+ const f = y(x), l = N[h ?? f ?? "shimmer"];
24
29
  if (t === "circle") {
25
- const l = r && r in k ? r : "md";
26
- return /* @__PURE__ */ s(
30
+ const i = r && r in b ? r : "md";
31
+ return /* @__PURE__ */ e(
27
32
  "span",
28
33
  {
29
34
  "aria-hidden": "true",
30
- className: a("rst:block rst:shrink-0 rst:rounded-full rst:border rst:border-transparent", k[l], h, o),
35
+ className: c("rst:block rst:shrink-0 rst:rounded-full rst:border rst:border-transparent", b[i], l, n),
31
36
  "data-skeleton": "circle"
32
37
  }
33
38
  );
34
39
  }
35
40
  if (t === "block")
36
- return /* @__PURE__ */ s(
41
+ return /* @__PURE__ */ e(
37
42
  "span",
38
43
  {
39
44
  "aria-hidden": "true",
40
- className: a("rst:block rst:w-full", x[c ?? "lg"], h, o),
41
- style: { height: u ?? "8rem", width: e },
45
+ className: c("rst:block rst:w-full", o ? S[o] : E, l, n),
46
+ style: { height: m ?? "8rem", width: s },
42
47
  "data-skeleton": "block"
43
48
  }
44
49
  );
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(
50
+ const k = r && r in p ? r : "md", a = Math.max(1, Math.floor(d)), I = (i) => {
51
+ const u = i === a - 1;
52
+ return /* @__PURE__ */ v(
48
53
  "span",
49
54
  {
50
- className: a("rst:relative rst:block", n > 1 && !i && "rst:w-full"),
51
- style: { width: n > 1 ? i ? e ?? "60%" : void 0 : e },
55
+ className: c("rst:relative rst:block", a > 1 && !u && "rst:w-full"),
56
+ style: { width: a > 1 ? u ? s ?? "60%" : void 0 : s },
52
57
  children: [
53
58
  " ",
54
- /* @__PURE__ */ s(
59
+ /* @__PURE__ */ e(
55
60
  "span",
56
61
  {
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)
62
+ className: c("rst:absolute rst:inset-x-0 rst:top-1/2 rst:h-[0.7em] rst:-translate-y-1/2", S[o ?? "sm"], l)
58
63
  }
59
64
  )
60
65
  ]
61
66
  },
62
- l
67
+ i
63
68
  );
64
69
  };
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)) });
70
+ return /* @__PURE__ */ e("span", { "aria-hidden": "true", className: c("rst:block", p[k], n), "data-skeleton": "line", children: Array.from({ length: a }, (i, u) => I(u)) });
66
71
  }
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 })
72
+ function D({ loading: t, skeleton: r, label: d = "Loading", announce: s = !0, animation: m, children: o, className: h }) {
73
+ const n = y(x), [f, l] = w("");
74
+ return C(() => {
75
+ const k = setTimeout(() => l(t && s ? d : ""), t ? 50 : 0);
76
+ return () => clearTimeout(k);
77
+ }, [t, d, s]), /* @__PURE__ */ v(g, { children: [
78
+ /* @__PURE__ */ e("span", { role: "status", className: "rst:sr-only", "data-skeleton-status": "", children: f }),
79
+ /* @__PURE__ */ e("div", { "aria-busy": t || void 0, className: h, "data-skeleton-region": t ? "loading" : "ready", children: t ? /* @__PURE__ */ e(x.Provider, { value: m ?? n, children: /* @__PURE__ */ e("div", { "aria-hidden": "true", children: r }) }) : o })
75
80
  ] });
76
81
  }
77
82
  export {
78
- E as Skeleton,
79
- L as SkeletonRegion
83
+ j as Skeleton,
84
+ D as SkeletonRegion
80
85
  };
@@ -13,7 +13,7 @@ export interface ThemeToggleProps extends Omit<ButtonProps, "onClick" | "childre
13
13
  * opposite, because a button's name should say what pressing it does.
14
14
  *
15
15
  * Both take nodes, so a product with its own vocabulary for the two states
16
- * ("Press sheet" / "Blueline", "Day" / "Night") can say so.
16
+ * ("Paper" / "Chalkboard", "Day" / "Night") can say so.
17
17
  */
18
18
  lightLabel?: React.ReactNode;
19
19
  /** Visible text when the page is dark, if `showLabel` is set. */
@@ -45,7 +45,7 @@ export interface CheckboxGroupProps extends Omit<React.HTMLAttributes<HTMLFieldS
45
45
  * Caps the options' height and scrolls past it. A number is pixels.
46
46
  *
47
47
  * Applied inline rather than as a class because the useful values here are
48
- * arbitrary — `240` is mega-squad's, yours will differ — and an arbitrary
48
+ * arbitrary — `240` is one app's, yours will differ — and an arbitrary
49
49
  * Tailwind value built from a prop is a class that does not exist at build
50
50
  * time, which is exactly what `check-classes-emit` fails the build over.
51
51
  */
@@ -5,7 +5,7 @@
5
5
  * short list of three options wants to sit on the form the way a Select's
6
6
  * label and trigger do, with no chrome of its own.
7
7
  *
8
- * `panel` is the long-list case — the grouped, scrollable list mega-squad
8
+ * `panel` is the long-list case — the grouped, scrollable list one app
9
9
  * hand-rolled for its sports picker. It deliberately does NOT read
10
10
  * `--roster-popover-*`: that family is documented as "a panel that floats over
11
11
  * the page", and this one is in the flow.
@@ -1 +1 @@
1
- "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),l=require("../../../lib/utils.cjs"),x=require("../../atoms/Card/Card.cjs"),m=require("../../atoms/Eyebrow/Eyebrow.cjs"),c=require("../../atoms/Stat/stat-variants.cjs"),h=require("../../organisms/Table/Table.cjs"),t=require("../../atoms/Skeleton/Skeleton.cjs");function j({media:r,lines:a=3,variant:s,padding:i,className:n}){return e.jsxs(x.Card,{variant:s,padding:i,className:n,"aria-hidden":"true","data-skeleton":"card",children:[r!==void 0&&e.jsx(t.Skeleton,{shape:"block",height:r,className:"rst:mb-4"}),e.jsx(t.Skeleton,{size:"lg",width:"45%"}),a>0&&e.jsx(t.Skeleton,{size:"md",lines:a,className:"rst:mt-2"})]})}function S({columns:r,className:a}){const s=["70%","50%","60%","40%"];return e.jsx(h.TableRow,{"aria-hidden":"true",className:a,"data-skeleton":"table-row",children:Array.from({length:Math.max(1,r)},(i,n)=>e.jsx(h.TableCell,{children:e.jsx(t.Skeleton,{size:"inherit",width:s[n%s.length]})},n))})}function k({size:r="md",withName:a=!1,className:s}){return a?e.jsxs("span",{"aria-hidden":"true",className:l.cn("rst:flex rst:items-center rst:gap-3",s),"data-skeleton":"avatar",children:[e.jsx(t.Skeleton,{shape:"circle",size:r}),e.jsx(t.Skeleton,{size:"md",width:"8rem"})]}):e.jsx(t.Skeleton,{shape:"circle",size:r,className:s})}function w({size:r="md",semantics:a="definition",source:s=!1,className:i}){const n=c.statValueVariants({size:r,colorScheme:"neutral"}),o=e.jsx(t.Skeleton,{size:"inherit",width:"2.5em"}),d=e.jsx(m.Eyebrow,{size:"xs",children:e.jsx(t.Skeleton,{size:"inherit",width:"9em"})}),u=e.jsx("span",{className:c.statSourceClass,children:e.jsx(t.Skeleton,{size:"inherit",width:"12em"})});return a==="standalone"?e.jsxs("span",{"aria-hidden":"true",className:l.cn("rst:flex rst:flex-col rst:gap-[3px]",i),"data-skeleton":"stat",children:[e.jsx("span",{className:n,children:o}),d,s&&u]}):e.jsxs("div",{"aria-hidden":"true",className:l.cn("rst:grid rst:content-start rst:gap-[3px]",i),"data-skeleton":"stat",children:[e.jsx("dt",{className:"rst:relative rst:col-start-1 rst:row-start-2 rst:flex",children:d}),s?e.jsxs("dd",{className:l.cn("rst:m-0",n,"rst:col-start-1 rst:row-span-3 rst:row-start-1 rst:grid rst:grid-rows-subgrid"),children:[e.jsx("span",{className:"rst:row-start-1",children:o}),e.jsx("span",{className:l.cn(c.statSourceClass,"rst:row-start-3"),children:e.jsx(t.Skeleton,{size:"inherit",width:"12em"})})]}):e.jsx("dd",{className:l.cn("rst:m-0 rst:col-start-1 rst:row-start-1",n),children:o})]})}exports.SkeletonAvatar=k;exports.SkeletonCard=j;exports.SkeletonStat=w;exports.SkeletonTableRow=S;
1
+ "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),c=require("../../../lib/utils.cjs"),j=require("../../atoms/Card/Card.cjs"),S=require("../../atoms/Eyebrow/Eyebrow.cjs"),d=require("../../atoms/Stat/stat-variants.cjs"),u=require("../../organisms/Table/Table.cjs"),s=require("../../atoms/Skeleton/Skeleton.cjs");function k({media:a,lines:n=3,variant:r,padding:t,animation:i,className:l}){return e.jsxs(j.Card,{variant:r,padding:t,className:l,"aria-hidden":"true","data-skeleton":"card",children:[a!==void 0&&e.jsx(s.Skeleton,{shape:"block",height:a,animation:i,className:"rst:mb-4"}),e.jsx(s.Skeleton,{size:"lg",width:"45%",animation:i}),n>0&&e.jsx(s.Skeleton,{size:"md",lines:n,animation:i,className:"rst:mt-2"})]})}function m({columns:a,animation:n,className:r}){const t=["70%","50%","60%","40%"];return e.jsx(u.TableRow,{"aria-hidden":"true",className:r,"data-skeleton":"table-row",children:Array.from({length:Math.max(1,a)},(i,l)=>e.jsx(u.TableCell,{children:e.jsx(s.Skeleton,{size:"inherit",width:t[l%t.length],animation:n})},l))})}function w({size:a="md",withName:n=!1,animation:r,className:t}){return n?e.jsxs("span",{"aria-hidden":"true",className:c.cn("rst:flex rst:items-center rst:gap-3",t),"data-skeleton":"avatar",children:[e.jsx(s.Skeleton,{shape:"circle",size:a,animation:r}),e.jsx(s.Skeleton,{size:"md",width:"8rem",animation:r})]}):e.jsx(s.Skeleton,{shape:"circle",size:a,animation:r,className:t})}function p({size:a="md",semantics:n="definition",source:r=!1,animation:t,className:i}){const l=d.statValueVariants({size:a,colorScheme:"neutral"}),o=e.jsx(s.Skeleton,{size:"inherit",width:"2.5em",animation:t}),h=e.jsx(S.Eyebrow,{size:"xs",children:e.jsx(s.Skeleton,{size:"inherit",width:"9em",animation:t})}),x=e.jsx("span",{className:d.statSourceClass,children:e.jsx(s.Skeleton,{size:"inherit",width:"12em",animation:t})});return n==="standalone"?e.jsxs("span",{"aria-hidden":"true",className:c.cn("rst:flex rst:flex-col rst:gap-[3px]",i),"data-skeleton":"stat",children:[e.jsx("span",{className:l,children:o}),h,r&&x]}):e.jsxs("div",{"aria-hidden":"true",className:c.cn("rst:grid rst:content-start rst:gap-[3px]",i),"data-skeleton":"stat",children:[e.jsx("dt",{className:"rst:relative rst:col-start-1 rst:row-start-2 rst:flex",children:h}),r?e.jsxs("dd",{className:c.cn("rst:m-0",l,"rst:col-start-1 rst:row-span-3 rst:row-start-1 rst:grid rst:grid-rows-subgrid"),children:[e.jsx("span",{className:"rst:row-start-1",children:o}),e.jsx("span",{className:c.cn(d.statSourceClass,"rst:row-start-3"),children:e.jsx(s.Skeleton,{size:"inherit",width:"12em",animation:t})})]}):e.jsx("dd",{className:c.cn("rst:m-0 rst:col-start-1 rst:row-start-1",l),children:o})]})}exports.SkeletonAvatar=w;exports.SkeletonCard=k;exports.SkeletonStat=p;exports.SkeletonTableRow=m;
@@ -1,20 +1,25 @@
1
1
  import { CSSProperties } from 'react';
2
2
  import { CardProps } from '../../atoms/Card/Card';
3
+ import { SkeletonAnimation } from '../../atoms/Skeleton/Skeleton';
3
4
  export interface SkeletonCardProps extends Pick<CardProps, "variant" | "padding"> {
4
5
  /** A media block above the text, at this height (an image, a chart). */
5
6
  media?: CSSProperties["height"];
6
7
  /** Body lines under the title. */
7
8
  lines?: number;
9
+ /** How its skeletons move: see `Skeleton`. Inside a `SkeletonRegion`, the region's. */
10
+ animation?: SkeletonAnimation;
8
11
  className?: string;
9
12
  }
10
13
  /**
11
14
  * A Card loading. Stands in for a Card holding an optional media block (with
12
15
  * 16px below it), a `text-base` title, and `text-sm` body lines 8px under it.
13
16
  */
14
- declare function SkeletonCard({ media, lines, variant, padding, className }: SkeletonCardProps): import("react/jsx-runtime").JSX.Element;
17
+ declare function SkeletonCard({ media, lines, variant, padding, animation, className }: SkeletonCardProps): import("react/jsx-runtime").JSX.Element;
15
18
  export interface SkeletonTableRowProps {
16
19
  /** How many cells. */
17
20
  columns: number;
21
+ /** How its skeletons move: see `Skeleton`. Inside a `SkeletonRegion`, the region's. */
22
+ animation?: SkeletonAnimation;
18
23
  className?: string;
19
24
  }
20
25
  /**
@@ -22,16 +27,18 @@ export interface SkeletonTableRowProps {
22
27
  * size, each holding one line in the cell's text size. Stands in for a row of
23
28
  * one-line cells.
24
29
  */
25
- declare function SkeletonTableRow({ columns, className }: SkeletonTableRowProps): import("react/jsx-runtime").JSX.Element;
30
+ declare function SkeletonTableRow({ columns, animation, className }: SkeletonTableRowProps): import("react/jsx-runtime").JSX.Element;
26
31
  export interface SkeletonAvatarProps {
27
32
  /** Avatar's sizes. */
28
33
  size?: "xs" | "sm" | "md" | "lg" | "xl";
29
34
  /** A name line beside it, in `text-sm`. */
30
35
  withName?: boolean;
36
+ /** How its skeletons move: see `Skeleton`. Inside a `SkeletonRegion`, the region's. */
37
+ animation?: SkeletonAnimation;
31
38
  className?: string;
32
39
  }
33
40
  /** An Avatar loading, and optionally the name beside it. */
34
- declare function SkeletonAvatar({ size, withName, className }: SkeletonAvatarProps): import("react/jsx-runtime").JSX.Element;
41
+ declare function SkeletonAvatar({ size, withName, animation, className }: SkeletonAvatarProps): import("react/jsx-runtime").JSX.Element;
35
42
  export interface SkeletonStatProps {
36
43
  /** Stat's sizes. */
37
44
  size?: "sm" | "md" | "lg";
@@ -43,6 +50,8 @@ export interface SkeletonStatProps {
43
50
  semantics?: "definition" | "standalone";
44
51
  /** A source line under the figure, as a Stat with `source` has. */
45
52
  source?: boolean;
53
+ /** How its skeletons move: see `Skeleton`. Inside a `SkeletonRegion`, the region's. */
54
+ animation?: SkeletonAnimation;
46
55
  className?: string;
47
56
  }
48
57
  /**
@@ -51,5 +60,5 @@ export interface SkeletonStatProps {
51
60
  * the label is the real Eyebrow, a flex item where Stat's is, so it makes the
52
61
  * same line.
53
62
  */
54
- declare function SkeletonStat({ size, semantics, source, className }: SkeletonStatProps): import("react/jsx-runtime").JSX.Element;
63
+ declare function SkeletonStat({ size, semantics, source, animation, className }: SkeletonStatProps): import("react/jsx-runtime").JSX.Element;
55
64
  export { SkeletonCard, SkeletonTableRow, SkeletonAvatar, SkeletonStat };
@@ -1,45 +1,45 @@
1
1
  "use client";
2
2
  import { jsx as t, jsxs as o } from "react/jsx-runtime";
3
- import { cn as n } from "../../../lib/utils.js";
4
- import { Card as p } from "../../atoms/Card/Card.js";
5
- import { Eyebrow as u } from "../../atoms/Eyebrow/Eyebrow.js";
6
- import { statValueVariants as f, statSourceClass as h } from "../../atoms/Stat/stat-variants.js";
7
- import { TableRow as w, TableCell as g } from "../../organisms/Table/Table.js";
8
- import { Skeleton as r } from "../../atoms/Skeleton/Skeleton.js";
9
- function C({ media: s, lines: a = 3, variant: e, padding: l, className: i }) {
10
- return /* @__PURE__ */ o(p, { variant: e, padding: l, className: i, "aria-hidden": "true", "data-skeleton": "card", children: [
11
- s !== void 0 && /* @__PURE__ */ t(r, { shape: "block", height: s, className: "rst:mb-4" }),
12
- /* @__PURE__ */ t(r, { size: "lg", width: "45%" }),
13
- a > 0 && /* @__PURE__ */ t(r, { size: "md", lines: a, className: "rst:mt-2" })
3
+ import { cn as d } from "../../../lib/utils.js";
4
+ import { Card as u } from "../../atoms/Card/Card.js";
5
+ import { Eyebrow as f } from "../../atoms/Eyebrow/Eyebrow.js";
6
+ import { statValueVariants as w, statSourceClass as m } from "../../atoms/Stat/stat-variants.js";
7
+ import { TableRow as g, TableCell as k } from "../../organisms/Table/Table.js";
8
+ import { Skeleton as e } from "../../atoms/Skeleton/Skeleton.js";
9
+ function T({ media: a, lines: l = 3, variant: s, padding: r, animation: n, className: i }) {
10
+ return /* @__PURE__ */ o(u, { variant: s, padding: r, className: i, "aria-hidden": "true", "data-skeleton": "card", children: [
11
+ a !== void 0 && /* @__PURE__ */ t(e, { shape: "block", height: a, animation: n, className: "rst:mb-4" }),
12
+ /* @__PURE__ */ t(e, { size: "lg", width: "45%", animation: n }),
13
+ l > 0 && /* @__PURE__ */ t(e, { size: "md", lines: l, animation: n, className: "rst:mt-2" })
14
14
  ] });
15
15
  }
16
- function T({ columns: s, className: a }) {
17
- const e = ["70%", "50%", "60%", "40%"];
18
- return /* @__PURE__ */ t(w, { "aria-hidden": "true", className: a, "data-skeleton": "table-row", children: Array.from({ length: Math.max(1, s) }, (l, i) => /* @__PURE__ */ t(g, { children: /* @__PURE__ */ t(r, { size: "inherit", width: e[i % e.length] }) }, i)) });
16
+ function j({ columns: a, animation: l, className: s }) {
17
+ const r = ["70%", "50%", "60%", "40%"];
18
+ return /* @__PURE__ */ t(g, { "aria-hidden": "true", className: s, "data-skeleton": "table-row", children: Array.from({ length: Math.max(1, a) }, (n, i) => /* @__PURE__ */ t(k, { children: /* @__PURE__ */ t(e, { size: "inherit", width: r[i % r.length], animation: l }) }, i)) });
19
19
  }
20
- function j({ size: s = "md", withName: a = !1, className: e }) {
21
- return a ? /* @__PURE__ */ o("span", { "aria-hidden": "true", className: n("rst:flex rst:items-center rst:gap-3", e), "data-skeleton": "avatar", children: [
22
- /* @__PURE__ */ t(r, { shape: "circle", size: s }),
23
- /* @__PURE__ */ t(r, { size: "md", width: "8rem" })
24
- ] }) : /* @__PURE__ */ t(r, { shape: "circle", size: s, className: e });
20
+ function y({ size: a = "md", withName: l = !1, animation: s, className: r }) {
21
+ return l ? /* @__PURE__ */ o("span", { "aria-hidden": "true", className: d("rst:flex rst:items-center rst:gap-3", r), "data-skeleton": "avatar", children: [
22
+ /* @__PURE__ */ t(e, { shape: "circle", size: a, animation: s }),
23
+ /* @__PURE__ */ t(e, { size: "md", width: "8rem", animation: s })
24
+ ] }) : /* @__PURE__ */ t(e, { shape: "circle", size: a, animation: s, className: r });
25
25
  }
26
- function y({ size: s = "md", semantics: a = "definition", source: e = !1, className: l }) {
27
- const i = f({ size: s, colorScheme: "neutral" }), d = /* @__PURE__ */ t(r, { size: "inherit", width: "2.5em" }), c = /* @__PURE__ */ t(u, { size: "xs", children: /* @__PURE__ */ t(r, { size: "inherit", width: "9em" }) }), m = /* @__PURE__ */ t("span", { className: h, children: /* @__PURE__ */ t(r, { size: "inherit", width: "12em" }) });
28
- return a === "standalone" ? /* @__PURE__ */ o("span", { "aria-hidden": "true", className: n("rst:flex rst:flex-col rst:gap-[3px]", l), "data-skeleton": "stat", children: [
29
- /* @__PURE__ */ t("span", { className: i, children: d }),
30
- c,
31
- e && m
32
- ] }) : /* @__PURE__ */ o("div", { "aria-hidden": "true", className: n("rst:grid rst:content-start rst:gap-[3px]", l), "data-skeleton": "stat", children: [
33
- /* @__PURE__ */ t("dt", { className: "rst:relative rst:col-start-1 rst:row-start-2 rst:flex", children: c }),
34
- e ? /* @__PURE__ */ o("dd", { className: n("rst:m-0", i, "rst:col-start-1 rst:row-span-3 rst:row-start-1 rst:grid rst:grid-rows-subgrid"), children: [
35
- /* @__PURE__ */ t("span", { className: "rst:row-start-1", children: d }),
36
- /* @__PURE__ */ t("span", { className: n(h, "rst:row-start-3"), children: /* @__PURE__ */ t(r, { size: "inherit", width: "12em" }) })
37
- ] }) : /* @__PURE__ */ t("dd", { className: n("rst:m-0 rst:col-start-1 rst:row-start-1", i), children: d })
26
+ function A({ size: a = "md", semantics: l = "definition", source: s = !1, animation: r, className: n }) {
27
+ const i = w({ size: a, colorScheme: "neutral" }), c = /* @__PURE__ */ t(e, { size: "inherit", width: "2.5em", animation: r }), h = /* @__PURE__ */ t(f, { size: "xs", children: /* @__PURE__ */ t(e, { size: "inherit", width: "9em", animation: r }) }), p = /* @__PURE__ */ t("span", { className: m, children: /* @__PURE__ */ t(e, { size: "inherit", width: "12em", animation: r }) });
28
+ return l === "standalone" ? /* @__PURE__ */ o("span", { "aria-hidden": "true", className: d("rst:flex rst:flex-col rst:gap-[3px]", n), "data-skeleton": "stat", children: [
29
+ /* @__PURE__ */ t("span", { className: i, children: c }),
30
+ h,
31
+ s && p
32
+ ] }) : /* @__PURE__ */ o("div", { "aria-hidden": "true", className: d("rst:grid rst:content-start rst:gap-[3px]", n), "data-skeleton": "stat", children: [
33
+ /* @__PURE__ */ t("dt", { className: "rst:relative rst:col-start-1 rst:row-start-2 rst:flex", children: h }),
34
+ s ? /* @__PURE__ */ o("dd", { className: d("rst:m-0", i, "rst:col-start-1 rst:row-span-3 rst:row-start-1 rst:grid rst:grid-rows-subgrid"), children: [
35
+ /* @__PURE__ */ t("span", { className: "rst:row-start-1", children: c }),
36
+ /* @__PURE__ */ t("span", { className: d(m, "rst:row-start-3"), children: /* @__PURE__ */ t(e, { size: "inherit", width: "12em", animation: r }) })
37
+ ] }) : /* @__PURE__ */ t("dd", { className: d("rst:m-0 rst:col-start-1 rst:row-start-1", i), children: c })
38
38
  ] });
39
39
  }
40
40
  export {
41
- j as SkeletonAvatar,
42
- C as SkeletonCard,
43
- y as SkeletonStat,
44
- T as SkeletonTableRow
41
+ y as SkeletonAvatar,
42
+ T as SkeletonCard,
43
+ A as SkeletonStat,
44
+ j as SkeletonTableRow
45
45
  };
@@ -21,7 +21,7 @@ export interface ToasterProps extends Pick<HotToasterProps, "position" | "gutter
21
21
  * already depend on it at the same version and already write
22
22
  * `toast.success(...)`. Wrapping means their call sites do not move; what
23
23
  * changes is that the toast stops being styled with hardcoded hex in each app
24
- * — game-verdict pinned `#1a1f2e` and `#10b981`, mega-squad `#413f3c` and
24
+ * — one app pinned `#1a1f2e` and `#10b981`, the other `#413f3c` and
25
25
  * `#f3eee3`, neither of which is in either app's palette any more.
26
26
  *
27
27
  * The `children` render prop is used rather than `toastOptions`, so the body