@blakesteve/roster 5.9.0 → 5.11.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 (42) hide show
  1. package/README.md +206 -25
  2. package/dist/components/atoms/Button/button-variants.js +1 -1
  3. package/dist/components/atoms/Eyebrow/eyebrow-variants.d.ts +2 -2
  4. package/dist/components/atoms/Input/input-variants.js +1 -1
  5. package/dist/components/atoms/PasswordInput/PasswordInput.cjs +1 -1
  6. package/dist/components/atoms/PasswordInput/PasswordInput.js +5 -5
  7. package/dist/components/atoms/Select/select-variants.cjs +1 -1
  8. package/dist/components/atoms/Select/select-variants.js +11 -2
  9. package/dist/components/atoms/Skeleton/Skeleton.cjs +1 -1
  10. package/dist/components/atoms/Skeleton/Skeleton.d.ts +19 -3
  11. package/dist/components/atoms/Skeleton/Skeleton.js +38 -33
  12. package/dist/components/atoms/ThemeToggle/ThemeToggle.d.ts +1 -1
  13. package/dist/components/molecules/CheckboxGroup/CheckboxGroup.d.ts +1 -1
  14. package/dist/components/molecules/CheckboxGroup/checkbox-group-variants.d.ts +1 -1
  15. package/dist/components/molecules/Menu/Menu.cjs +1 -1
  16. package/dist/components/molecules/Menu/Menu.js +60 -53
  17. package/dist/components/molecules/RangeSlider/RangeSlider.cjs +1 -0
  18. package/dist/components/molecules/RangeSlider/RangeSlider.d.ts +74 -0
  19. package/dist/components/molecules/RangeSlider/RangeSlider.js +164 -0
  20. package/dist/components/molecules/SkeletonPresets/SkeletonPresets.cjs +1 -1
  21. package/dist/components/molecules/SkeletonPresets/SkeletonPresets.d.ts +13 -4
  22. package/dist/components/molecules/SkeletonPresets/SkeletonPresets.js +35 -35
  23. package/dist/components/molecules/Slider/Slider.cjs +1 -0
  24. package/dist/components/molecules/Slider/Slider.d.ts +87 -0
  25. package/dist/components/molecules/Slider/Slider.js +128 -0
  26. package/dist/components/molecules/Slider/track-parts.cjs +1 -0
  27. package/dist/components/molecules/Slider/track-parts.d.ts +64 -0
  28. package/dist/components/molecules/Slider/track-parts.js +121 -0
  29. package/dist/components/molecules/Slider/track.cjs +1 -0
  30. package/dist/components/molecules/Slider/track.d.ts +115 -0
  31. package/dist/components/molecules/Slider/track.js +152 -0
  32. package/dist/components/molecules/Toast/Toaster.d.ts +1 -1
  33. package/dist/components/organisms/Navbar/Navbar.cjs +1 -1
  34. package/dist/components/organisms/Navbar/Navbar.js +37 -37
  35. package/dist/index.d.ts +2 -0
  36. package/dist/lib/utils.cjs +1 -1
  37. package/dist/lib/utils.js +4 -1
  38. package/dist/meta.json +3 -3
  39. package/dist/roster.cjs +1 -1
  40. package/dist/roster.css +1 -1
  41. package/dist/roster.js +67 -63
  42. package/package.json +1 -1
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 |
@@ -543,6 +543,18 @@ The band matters more than it looks. Without it the ring sits directly on the
543
543
  fill, and a `primary-500` ring on a `primary-600` button is 1.37:1 — which is
544
544
  why the ring and its offset are a pair rather than two independent knobs.
545
545
 
546
+ **A row in a popup marks focus with the ring too.** The focused row in `Menu`,
547
+ `Select`, `Combobox` and `MultiSelect` keeps its soft primary tint and adds a
548
+ 2px inset ring in `--roster-ring`. The tint alone was the indicator until
549
+ 5.11.0, and it measured 1.17:1 against the panel in light and 1.03:1 in dark
550
+ on Roster's own defaults: a keyboard user couldn't see where they were. The
551
+ ring follows the token you already set against your own surfaces, so it holds
552
+ wherever the panel is: 6.37:1 and 5.61:1 on the defaults. If you set
553
+ `--roster-popover-bg` to a surface `--roster-ring` doesn't contrast with, set
554
+ the ring with it. In `Menu` the ring shows for keyboard focus only, because the
555
+ pointer moves focus there too. Headless UI's popups mark the keys' row and the
556
+ pointer's alike, so in theirs it shows on hover as well.
557
+
546
558
  ### Motion
547
559
 
548
560
  Two hand-rolled utilities, both of which stop for `prefers-reduced-motion`.
@@ -607,10 +619,14 @@ its `label` (default "loading") says so.
607
619
  The loading system has its own: `--roster-skeleton` and
608
620
  `--roster-skeleton-highlight` are the skeleton's fill and the light that
609
621
  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.
622
+ gray-700 and gray-600 in dark). Four durations, unset by default, change the
623
+ pace: `--roster-skeleton-duration` (1.8s, the shimmer),
624
+ `--roster-skeleton-pulse-duration` (2s, the pulse),
625
+ `--roster-progress-travel-duration` (1.4s, the indeterminate pill) and
626
+ `--roster-dots-duration` (1.2s). `--roster-skeleton-pulse-opacity` (0.5) is
627
+ how far a pulse fades. Under reduced motion all of them stop on a resting
628
+ frame. `--roster-skeleton-radius` rounds skeleton blocks without touching the
629
+ shared radius scale (see Skeleton below).
614
630
 
615
631
  ### Scrollbars
616
632
 
@@ -769,6 +785,8 @@ Every field therefore has a second prop, **named for the element it reaches**:
769
785
  | `Combobox` | `className` | `inputClassName` | `optionsClassName` |
770
786
  | `CheckboxGroup`| `className` | — | `optionsClassName` |
771
787
  | `RadioGroup` | `className` | `radioClassName` | `optionsClassName`, `optionClassName`, `labelClassName`, `optionLabelClassName`, `descriptionClassName`, `messageClassName` |
788
+ | `Slider` | `className` | `trackClassName` | `thumbClassName`, `chipClassName`, `labelClassName`, `messageClassName` |
789
+ | `RangeSlider` | `className` | `thumbClassName` | `trackClassName`, `chipClassName`, `labelClassName`, `messageClassName` |
772
790
 
773
791
  `CheckboxGroup` and `RadioGroup` have no single "control" to reach, since they
774
792
  hold one per option, so `RadioGroup`'s `radioClassName` lands on every radio
@@ -924,6 +942,10 @@ look like oversights until you know why:
924
942
  page does not reach it. All three components copy the nearest `.dark` onto
925
943
  the panel to fix that. Custom properties are not carried the same way — set
926
944
  `--roster-popover-*` at `:root`.
945
+ - **A row is a click target, and focus is a ring.** Every option row shows
946
+ the pointer cursor, as `Menu`'s items do (a disabled one, `not-allowed`).
947
+ A focused row keeps its tint and adds an inset `--roster-ring`, which is
948
+ what reaches 3:1 against the panel ([Focus](#focus)).
927
949
  - **`Menu` shares the panel, not the anchoring.** Its panel takes the same
928
950
  surface, tokens and dark carry, but `Menu` places itself (see
929
951
  [Menu](#menu)), writing its own inline `max-height`. It reads the same
@@ -1039,7 +1061,7 @@ rather than vanishing from a field that still reports it.
1039
1061
 
1040
1062
  #### CheckboxGroup
1041
1063
 
1042
- Three things it adds over the version mega-squad's squad form hand-rolled:
1064
+ Three things it adds over the version one app's form hand-rolled:
1043
1065
 
1044
1066
  - **Each option is a Headless UI `Field`**, so its label is wired to its own
1045
1067
  checkbox and clicking the text toggles it. The pointer cursor sits on the
@@ -1109,8 +1131,8 @@ silently drops any selected value whose option has not loaded yet.
1109
1131
 
1110
1132
  `CheckboxGroup` exists instead of a multi-select `Select`, and the reason is
1111
1133
  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
1134
+ One app's form picks sports from a grouped, scrollable checklist with
1135
+ category headings; another's share form picks tags from a flat row of
1114
1136
  toggles. Collapsing either into a menu would have hidden the options behind a
1115
1137
  click to save vertical space neither screen was short of, so the component
1116
1138
  follows the shape that already worked. A multi-select dropdown is still the
@@ -1205,7 +1227,7 @@ rather than a rewrite.
1205
1227
 
1206
1228
  ```tsx
1207
1229
  <RadioGroup
1208
- label="Who can see this squad"
1230
+ label="Who can see this group"
1209
1231
  helperText="You can change this at any time."
1210
1232
  value={visibility}
1211
1233
  onChange={setVisibility}
@@ -1459,7 +1481,21 @@ import { SkeletonRegion, SkeletonCard } from "@blakesteve/roster";
1459
1481
  - The fill is `--roster-skeleton` (gray-200, gray-700 in dark), with
1460
1482
  `--roster-skeleton-highlight` crossing it. Neutral rather than the brand
1461
1483
  shimmer Countdown uses, and its own pair, so retinting one never recolors the
1462
- other. Under reduced motion it holds still.
1484
+ other.
1485
+ - `animation` picks how it moves: `"shimmer"` (the default, a band of light
1486
+ crossing the fill), `"pulse"` (the fill fading and returning, nothing
1487
+ moving: quieter on a dense page) or `"none"`. Under reduced motion both
1488
+ movements stop on the flat fill, and in forced colors every shape keeps its
1489
+ outline (a pulse stops there, so the outline never fades). Pass it to
1490
+ `SkeletonRegion` and every skeleton inside takes it, presets included; a
1491
+ preset or a skeleton that names its own keeps it. A pulse runs
1492
+ `--roster-skeleton-pulse-duration` (2s) and fades to
1493
+ `--roster-skeleton-pulse-opacity` (0.5).
1494
+ - A block's corners are `--roster-skeleton-radius`, the `lg` step unless
1495
+ set. Set it on `:root` or on a wrapper (`14px`, say) and skeleton blocks
1496
+ follow, and nothing else does: the shared scale and every other component
1497
+ stay as they are. A block's `radius` prop still wins, a line keeps its
1498
+ small step, and a circle stays round.
1463
1499
 
1464
1500
  **Progress and ProgressField.** `ProgressField` is the form to reach for: the
1465
1501
  label and value on one baseline above the bar, an optional detail line below.
@@ -1566,8 +1602,10 @@ import { Menu, MenuButton, MenuItems, MenuItem, MenuSeparator } from "@blakestev
1566
1602
  - **Styling.** `className` on `MenuItems` and on each item merges last, so a
1567
1603
  fill such as `bg-gray-100` takes; the scroll shadows are drawn over it. Its
1568
1604
  ring and separators follow `--roster-border-width`. Items show the pointer
1569
- cursor, a disabled one `not-allowed`. In forced colors the focused item gets
1570
- an outline, since its fill is dropped.
1605
+ cursor, a disabled one `not-allowed`. Keyboard focus adds an inset ring in
1606
+ `--roster-ring` to the item's tint, which alone doesn't reach 3:1 against
1607
+ the panel ([Focus](#focus)). In forced colors the focused item gets an
1608
+ outline, since its fill and ring are dropped.
1571
1609
  - **Targets.** Every item is at least 44px tall, and `MenuButton` at least 44
1572
1610
  by 44 whatever its size. `MenuButton plain` renders a bare `<button>` for a
1573
1611
  trigger drawn its own way; keep its target at least 44 by 44 with a hit
@@ -1581,6 +1619,120 @@ that its rows are 44px tall rather than 36, and that with neither an inbox nor
1581
1619
  a theme row it no longer draws an empty band and rule at the top. It keeps its
1582
1620
  own white fill, whatever `--roster-popover-bg` says.
1583
1621
 
1622
+ ### Slider
1623
+
1624
+ A value picked along a track: tapped, dragged, stepped with the keys, or set
1625
+ from a named snap point.
1626
+
1627
+ ```tsx
1628
+ import { Slider } from "@blakesteve/roster";
1629
+
1630
+ <Slider
1631
+ label="How sure are you?"
1632
+ value={sure}
1633
+ onChange={setSure}
1634
+ snapPoints={[{ value: 100, label: "Certain" }, { value: 75, label: "Likely" }, { value: 50, label: "Toss-up" }]}
1635
+ formatValue={(v) => `${v}%`}
1636
+ />;
1637
+ ```
1638
+
1639
+ - **It starts with a thumb**, in the middle of the range by default.
1640
+ `startValue` moves where it starts, and `defaultValue` sets it outright:
1641
+ `defaultValue={0}` starts it at the low end.
1642
+ - **Not set** is opt-in: `defaultValue={null}`, or `value={null}` when
1643
+ controlled, leaves the track empty with no thumb until the first input. A
1644
+ form takes it as a valid answer: under `name` it submits an empty string.
1645
+ Controlled, `value` can go back to `null`, which is how a consumer offers a
1646
+ clear.
1647
+ - **Snap points** are named stops, any values and labels: a selectable
1648
+ `Chip` under the track for each, in the track's order, moves the thumb
1649
+ there, and a bead on the rail shows where. They're shortcuts, so dragging
1650
+ still lands anywhere between them. A chip shows as pressed (solid rather
1651
+ than outline) while the value is exactly its value, and pressing it again
1652
+ leaves it there. A point outside `min` to `max` is left out.
1653
+ - **The number** shows beside the label (`formatValue` writes it). A screen
1654
+ reader hears it with the nearest snap point's name, "72%, Likely", and
1655
+ "Not set" while there's no value, never a number. Every string is in
1656
+ `labels`.
1657
+ - **In range.** A value outside `min` to `max` shows, and is said, at the
1658
+ end it's past. A controlled owner that turns a change down (a clamp, say)
1659
+ leaves the next key stepping from the value it kept.
1660
+ - **Pointer.** A tap on the track sets the value there; a press on the thumb
1661
+ takes it without a jump, and commits only if the drag moved it. On touch
1662
+ the track waits to see which way a finger goes: across is a drag, down the
1663
+ page scrolls it and sets nothing (and takes no focus), and a tap sets on
1664
+ lift. A pointermove with no press, which WebKit sends when content scrolls
1665
+ under a resting pointer, sets nothing. A second finger is left alone.
1666
+ - **Keys.** The arrows step by `step`, PageUp and PageDown by `pageStep` (a
1667
+ tenth of the range by default), Home and End go to the ends. **On an empty
1668
+ slider an arrow or Page key places the thumb at `startValue`, the middle
1669
+ by default**; it doesn't step from anywhere. A key at an end does nothing.
1670
+ From a value between steps, a key goes to the next step that way. Keys
1671
+ always step from the value last set, never from where the thumb is drawn
1672
+ while it eases there, so a burst of presses counts every one. A key the
1673
+ consumer's own `onKeyDown` handled (`preventDefault`) is left to it.
1674
+ - **Focus.** By keyboard, the ring is round the thumb; an empty slider rings
1675
+ the track. A disabled slider takes no focus at all.
1676
+ - **Form.** Under `name` it submits its value, or an empty string while not
1677
+ set; disabled, nothing, as a disabled input submits nothing. A form's
1678
+ reset puts an uncontrolled slider back where it started.
1679
+ - **Targets.** The track is 44px tall and its rail stops 22px short of each
1680
+ end, so the thumb's 44 by 44 target stays on it at every value. Each chip
1681
+ has Chip's 44px-tall target and is at least 44 wide, and the chip row sits
1682
+ 8px under the track so its targets never take the thumb's. Under reduced
1683
+ motion the thumb moves without easing. Under `dir="rtl"` the track runs
1684
+ from the right.
1685
+ - **Look.** The rail is a light surface edged in `--roster-control-border`,
1686
+ which carries a control edge's 3:1 when there's no thumb to show the
1687
+ slider. Snap points are beads set in the rail, the edge's color on the bare
1688
+ rail and the page's on the fill.
1689
+
1690
+ One thumb. Two, for a range, is [RangeSlider](#rangeslider), on the same
1691
+ track, so `value` here stays a number.
1692
+
1693
+ ### RangeSlider
1694
+
1695
+ A low and a high along one track: `Slider`'s track, with two thumbs.
1696
+
1697
+ ```tsx
1698
+ import { RangeSlider } from "@blakesteve/roster";
1699
+
1700
+ <RangeSlider
1701
+ label="Price"
1702
+ min={0}
1703
+ max={500}
1704
+ step={5}
1705
+ value={price}
1706
+ onChange={setPrice}
1707
+ formatValue={(v) => `$${v}`}
1708
+ />;
1709
+ ```
1710
+
1711
+ - **It starts at the whole range**, `[min, max]`, unless `defaultValue`
1712
+ says otherwise. `value` is `[low, high]`, always low first: a pair given
1713
+ the wrong way round is shown the right way. There's no not-set state; the
1714
+ whole range is what "any" looks like.
1715
+ - **Each thumb is a slider** to the keyboard and a screen reader: its own
1716
+ tab stop, named for the slider and its end ("Price Minimum", "Price
1717
+ Maximum", both in `labels`), whose `aria-valuemin` and `aria-valuemax`
1718
+ run to the other thumb. The thumbs meet and never cross: End on the low
1719
+ thumb takes it to the high one. The whole is a group named by the label.
1720
+ - **Pointer.** A press takes the nearer thumb: a tap on the track moves it
1721
+ there, a drag carries it. Two thumbs on one spot part by the way the drag
1722
+ first moves, up the range taking the high one. Touch, the resting
1723
+ pointer, the second finger and the keys work as `Slider`'s do.
1724
+ - **Snap points** put a bead on the rail and a `Chip` under it, which does
1725
+ what a tap on its mark does: moves the nearer thumb there (halfway, the
1726
+ low one). A chip shows as pressed while either thumb is on it.
1727
+ - **The number** beside the label shows both, "$120 – $340"
1728
+ (`labels.range`). Each thumb says its own value with the nearest snap
1729
+ point's name.
1730
+ - **Form.** Under `name` both values are submitted, low then high, as a
1731
+ multiple select's are: read them with `formData.getAll(name)`.
1732
+ - **Focus, targets, motion, RTL and look** as `Slider`'s: the ring is round
1733
+ the focused thumb, each thumb's 44 by 44 target stays on the track at
1734
+ every value.
1735
+
1584
1736
  ### Pagination
1585
1737
 
1586
1738
  Moves through pages of a list, built for a long list browsed on a phone.
@@ -1833,7 +1985,7 @@ function App() {
1833
1985
  | `CollapsibleSection` | Clamps any content (prose, chips, image grids) to a fixed height with a fade and expand/collapse toggle |
1834
1986
  | `Select` | Dropdown selector, on the same size scale as `Button` and `Input` |
1835
1987
  | `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` |
1988
+ | `Skeleton` | Line, block or circle placeholder at the content's exact size, shimmering, pulsing or still; `SkeletonRegion` sets `aria-busy` |
1837
1989
  | `Progress` | Continuous or segmented progress bar: determinate, indeterminate or busy; per-segment state |
1838
1990
  | `LoadingDots` | Three dots for a model generating before its first token; replaced by the text |
1839
1991
  | `Stat` | A figure with its label and, optionally, its source; a valid `<dl>` group, or `semantics="standalone"` |
@@ -1868,6 +2020,8 @@ function App() {
1868
2020
  | `StepProgress` | Named steps: a stepper with markers and connectors, or a compact segmented "Step x of y" |
1869
2021
  | `Pullquote` | A line lifted out of prose, as `<figure>` + `<blockquote>` + `<figcaption>` |
1870
2022
  | `RadioGroup` | Radio set reporting one `string`; descriptions, error, 44px targets |
2023
+ | `Slider` | A value along a track, starting in the middle or not set on request; named snap-point chips, a visible number, the full keyboard, 44px targets (see [Slider](#slider)) |
2024
+ | `RangeSlider` | A low and a high on one track: two thumbs that meet and never cross, each its own slider to the keyboard (see [RangeSlider](#rangeslider)) |
1871
2025
  | `Toast` | Transient floating notice; `Toaster` + the imperative `toast` handle |
1872
2026
 
1873
2027
  ### Organisms
@@ -1979,6 +2133,32 @@ such as `AvatarStrip`'s `ringClass`, stays exactly as they wrote it.
1979
2133
 
1980
2134
  `npm run build` fails if any unprefixed class reaches `dist/roster.css`.
1981
2135
 
2136
+ ## Authoring components: cursors
2137
+
2138
+ **If it's clickable, it's the pointer.** Every element that does something
2139
+ when it's clicked shows `cursor: pointer`, and says so in its classes. A
2140
+ browser gives a button the arrow; Tailwind v3's preflight gave it the pointer
2141
+ and v4's doesn't, so a `<button>` with no cursor class shows the arrow. Until 5.11.0 the option rows
2142
+ in `Select`, `Combobox` and `MultiSelect` showed the arrow while `Menu`'s
2143
+ showed the pointer, and `PasswordInput`'s reveal button showed the arrow too.
2144
+
2145
+ - A disabled control shows `not-allowed`.
2146
+ - A busy one may say so: `Button`'s `loading` shows `progress`.
2147
+ - A text field's label keeps the arrow, as a native one does: clicking it puts
2148
+ the caret in the field rather than doing something.
2149
+
2150
+ Three things keep another cursor, because they aren't click targets:
2151
+
2152
+ - **`Breadcrumbs`' current crumb** is the page you're on, not a link:
2153
+ `default`.
2154
+ - **`Tooltip`'s trigger wrapper** isn't necessarily clickable: `default`. A
2155
+ control inside it brings its own pointer.
2156
+ - **A drag surface** says what it does: `Carousel`'s track and `Sheet`'s drag
2157
+ handle show `grab`, then `grabbing`.
2158
+
2159
+ `src/internal/popups.checks.stories.tsx` reads the computed cursor on a row in
2160
+ each popup, with a disabled row as its twin.
2161
+
1982
2162
  ## Development
1983
2163
 
1984
2164
  Storybook is the component playground. Each component has dedicated stories covering all variants, props, and light/dark mode.
@@ -2037,14 +2217,15 @@ anything new. It also fails when a listed class starts emitting, so a fix cannot
2037
2217
  leave a stale entry behind. Run it with `--verbose` to print the list.
2038
2218
 
2039
2219
  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.
2220
+ resting on something, get it from `.storybook/real-input.ts`: Vitest browser
2221
+ commands that drive Playwright's mouse, whose events are trusted. They run in
2222
+ Chromium and in WebKit, where pointer and scroll events differ: WebKit sends a
2223
+ pointermove when content scrolls under a resting pointer, and Chromium doesn't.
2224
+ A synthetic wheel or pointer event never scrolls anything, so "a vertical wheel
2225
+ scrolls the page, not the row" can't be tested without them. The commands fail
2226
+ loudly when the pointer isn't over their target, rather than letting a wheel
2227
+ land on the page and a test pass for the wrong reason. Storybook's own UI has
2228
+ no such input, so those steps are skipped there.
2048
2229
 
2049
2230
  `src/lib/utils.test.ts` pins `cn`. `tailwind-merge` has to be configured with the
2050
2231
  prefix or it stops recognising Roster's classes as utilities and silently
@@ -2073,9 +2254,9 @@ Output in `dist/`:
2073
2254
 
2074
2255
  ### What `meta.json` is for
2075
2256
 
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.
2257
+ Consumers read this package to describe it. A portfolio site's case study takes
2258
+ the version from `package.json` and counts components by parsing the exports
2259
+ out of `dist/*.d.ts`, so both stay correct on their own.
2079
2260
 
2080
2261
  The test count has no such source. `files` is `["dist"]`, so the suite never
2081
2262
  leaves this repo, and the number on that page was a hand-typed literal that
@@ -2109,7 +2290,7 @@ like a file count but counts `describe` blocks — 166 against 92 real files.
2109
2290
  Pull requests are welcome. Please:
2110
2291
 
2111
2292
  1. Open an issue or discussion first for significant changes
2112
- 2. Follow the existing code style (CVA variants, forwardRef, Storybook stories, unit tests)
2293
+ 2. Follow the existing code style (CVA variants, forwardRef, Storybook stories, unit tests) and the authoring rules above: the `rst:` prefix and cursors
2113
2294
  3. Fill out the pull request template checklist before requesting review
2114
2295
 
2115
2296
  ## License
@@ -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 s=require("react/jsx-runtime"),i=require("react"),j=require("../../../node_modules/@fortawesome/react-fontawesome/dist/index.cjs"),c=require("../../../node_modules/@fortawesome/free-solid-svg-icons/index.cjs"),a=require("../../../lib/utils.cjs"),S=require("../Input/Input.cjs"),u=i.forwardRef(({revealable:l=!0,defaultRevealed:d=!1,revealed:r,onRevealChange:p,showLabel:y="Show password",hideLabel:f="Hide password",disabled:e,variant:m,error:w,errorMessage:h,className:v,...x},g)=>{const[I,q]=i.useState(d),o=r!==void 0,t=o?r:I,b=()=>{const n=!t;o||q(n),p?.(n)};return s.jsx(S.Input,{ref:g,type:t?"text":"password",disabled:e,variant:m,error:w,errorMessage:h,className:a.cn("rst:[&_input::-ms-reveal]:hidden",v),endIcon:l?s.jsx("button",{type:"button",onClick:b,disabled:e,"aria-label":t?f:y,"aria-pressed":t,className:a.cn("rst:-m-1 rst:flex rst:items-center rst:rounded-sm rst:p-1 rst:transition-opacity","rst:opacity-70 rst:hover:opacity-100","rst:focus:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring",e&&"rst:cursor-not-allowed rst:opacity-40 rst:hover:opacity-40"),children:s.jsx(j.FontAwesomeIcon,{icon:t?c.faEyeSlash:c.faEye,className:"rst:h-4 rst:w-4","aria-hidden":"true"})}):void 0,...x})});u.displayName="PasswordInput";exports.PasswordInput=u;
1
+ "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),i=require("react"),j=require("../../../node_modules/@fortawesome/react-fontawesome/dist/index.cjs"),c=require("../../../node_modules/@fortawesome/free-solid-svg-icons/index.cjs"),a=require("../../../lib/utils.cjs"),S=require("../Input/Input.cjs"),u=i.forwardRef(({revealable:l=!0,defaultRevealed:d=!1,revealed:r,onRevealChange:p,showLabel:y="Show password",hideLabel:f="Hide password",disabled:s,variant:m,error:w,errorMessage:h,className:v,...x},g)=>{const[I,q]=i.useState(d),o=r!==void 0,t=o?r:I,b=()=>{const n=!t;o||q(n),p?.(n)};return e.jsx(S.Input,{ref:g,type:t?"text":"password",disabled:s,variant:m,error:w,errorMessage:h,className:a.cn("rst:[&_input::-ms-reveal]:hidden",v),endIcon:l?e.jsx("button",{type:"button",onClick:b,disabled:s,"aria-label":t?f:y,"aria-pressed":t,className:a.cn("rst:-m-1 rst:flex rst:items-center rst:rounded-sm rst:p-1 rst:transition-opacity","rst:opacity-70 rst:hover:opacity-100","rst:focus:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring",s?"rst:cursor-not-allowed rst:opacity-40 rst:hover:opacity-40":"rst:cursor-pointer"),children:e.jsx(j.FontAwesomeIcon,{icon:t?c.faEyeSlash:c.faEye,className:"rst:h-4 rst:w-4","aria-hidden":"true"})}):void 0,...x})});u.displayName="PasswordInput";exports.PasswordInput=u;
@@ -10,8 +10,8 @@ const C = i.forwardRef(
10
10
  revealable: c = !0,
11
11
  defaultRevealed: l = !1,
12
12
  revealed: s,
13
- onRevealChange: d,
14
- showLabel: p = "Show password",
13
+ onRevealChange: p,
14
+ showLabel: d = "Show password",
15
15
  hideLabel: m = "Hide password",
16
16
  disabled: r,
17
17
  variant: u,
@@ -22,7 +22,7 @@ const C = i.forwardRef(
22
22
  }, v) => {
23
23
  const [g, x] = i.useState(l), e = s !== void 0, t = e ? s : g, I = () => {
24
24
  const n = !t;
25
- e || x(n), d?.(n);
25
+ e || x(n), p?.(n);
26
26
  };
27
27
  return /* @__PURE__ */ o(
28
28
  _,
@@ -40,13 +40,13 @@ const C = i.forwardRef(
40
40
  type: "button",
41
41
  onClick: I,
42
42
  disabled: r,
43
- "aria-label": t ? m : p,
43
+ "aria-label": t ? m : d,
44
44
  "aria-pressed": t,
45
45
  className: a(
46
46
  "rst:-m-1 rst:flex rst:items-center rst:rounded-sm rst:p-1 rst:transition-opacity",
47
47
  "rst:opacity-70 rst:hover:opacity-100",
48
48
  "rst:focus:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring",
49
- r && "rst:cursor-not-allowed rst:opacity-40 rst:hover:opacity-40"
49
+ r ? "rst:cursor-not-allowed rst:opacity-40 rst:hover:opacity-40" : "rst:cursor-pointer"
50
50
  ),
51
51
  children: /* @__PURE__ */ o(
52
52
  N,
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("../../../node_modules/class-variance-authority/dist/index.cjs"),t=r.cva("rst:font-ui rst:relative rst:flex rst:w-full rst:cursor-pointer rst:disabled:cursor-not-allowed rst:disabled:opacity-50 rst:items-center rst:rounded-md rst:h-10 rst:pl-4 rst:pr-10 rst:text-left rst:text-sm rst:font-medium rst:elevation-control rst:ring rst:ring-inset rst:transition-all rst:focus:outline-hidden rst:focus-visible:ring-[length:calc(var(--roster-border-width,1px)+1px)] rst:focus-visible:ring-ring",{variants:{variant:{white:"rst:bg-white rst:text-gray-900 rst:ring-gray-300 rst:hover:bg-gray-50 rst:dark:bg-gray-800 rst:dark:text-gray-100 rst:dark:ring-gray-700 rst:dark:hover:bg-gray-700",soft:"rst:bg-gray-100 rst:text-gray-900 rst:ring-[var(--roster-control-border)] rst:hover:bg-gray-200 rst:dark:bg-gray-800 rst:dark:text-gray-100 rst:dark:hover:bg-gray-700",slate:"rst:bg-gray-700 rst:text-gray-100 rst:ring-transparent rst:hover:bg-gray-600 rst:dark:bg-gray-900 rst:dark:text-gray-100 rst:dark:hover:bg-gray-800",outline:"rst:bg-[var(--roster-control-bg)] rst:text-[var(--roster-control-text)] rst:ring-[color:var(--roster-control-border)]",ghost:"rst:bg-transparent rst:text-gray-700 rst:ring-transparent rst:hover:bg-gray-100 rst:hover:text-gray-900 rst:dark:text-gray-300 rst:dark:hover:bg-gray-800 rst:dark:hover:text-gray-100"},size:{sm:"rst:h-9 rst:pl-3 rst:pr-9",default:"rst:h-10 rst:pl-4 rst:pr-10",lg:"rst:h-11 rst:pl-4 rst:pr-10"},error:{true:"rst:ring-error-500 rst:text-error-600 rst:focus-visible:ring-error-500 rst:dark:ring-error-500 rst:dark:text-error-400 rst:dark:focus-visible:ring-error-400",false:""}},defaultVariants:{variant:"outline",size:"default",error:!1}}),s=r.cva("rst:text-sm rst:group rst:relative rst:cursor-default rst:select-none rst:py-2.5 rst:pl-4 rst:pr-9 rst:transition-colors rst:text-inherit rst:data-focus:bg-primary-100 rst:data-focus:text-primary-900 rst:dark:data-focus:bg-primary-900/30 rst:dark:data-focus:text-primary-100 rst:data-selected:bg-gray-50 rst:dark:data-selected:bg-gray-700/50 rst:data-disabled:opacity-50 rst:data-disabled:cursor-not-allowed",{variants:{size:{sm:"rst:py-2 rst:pl-3 rst:pr-8",default:"rst:py-2.5 rst:pl-4 rst:pr-9",lg:"rst:py-2.5 rst:pl-4 rst:pr-9"}},defaultVariants:{size:"default"}});exports.selectOptionVariants=s;exports.selectTriggerVariants=t;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("../../../node_modules/class-variance-authority/dist/index.cjs"),t=r.cva("rst:font-ui rst:relative rst:flex rst:w-full rst:cursor-pointer rst:disabled:cursor-not-allowed rst:disabled:opacity-50 rst:items-center rst:rounded-md rst:h-10 rst:pl-4 rst:pr-10 rst:text-left rst:text-sm rst:font-medium rst:elevation-control rst:ring rst:ring-inset rst:transition-all rst:focus:outline-hidden rst:focus-visible:ring-[length:calc(var(--roster-border-width,1px)+1px)] rst:focus-visible:ring-ring",{variants:{variant:{white:"rst:bg-white rst:text-gray-900 rst:ring-gray-300 rst:hover:bg-gray-50 rst:dark:bg-gray-800 rst:dark:text-gray-100 rst:dark:ring-gray-700 rst:dark:hover:bg-gray-700",soft:"rst:bg-gray-100 rst:text-gray-900 rst:ring-[var(--roster-control-border)] rst:hover:bg-gray-200 rst:dark:bg-gray-800 rst:dark:text-gray-100 rst:dark:hover:bg-gray-700",slate:"rst:bg-gray-700 rst:text-gray-100 rst:ring-transparent rst:hover:bg-gray-600 rst:dark:bg-gray-900 rst:dark:text-gray-100 rst:dark:hover:bg-gray-800",outline:"rst:bg-[var(--roster-control-bg)] rst:text-[var(--roster-control-text)] rst:ring-[color:var(--roster-control-border)]",ghost:"rst:bg-transparent rst:text-gray-700 rst:ring-transparent rst:hover:bg-gray-100 rst:hover:text-gray-900 rst:dark:text-gray-300 rst:dark:hover:bg-gray-800 rst:dark:hover:text-gray-100"},size:{sm:"rst:h-9 rst:pl-3 rst:pr-9",default:"rst:h-10 rst:pl-4 rst:pr-10",lg:"rst:h-11 rst:pl-4 rst:pr-10"},error:{true:"rst:ring-error-500 rst:text-error-600 rst:focus-visible:ring-error-500 rst:dark:ring-error-500 rst:dark:text-error-400 rst:dark:focus-visible:ring-error-400",false:""}},defaultVariants:{variant:"outline",size:"default",error:!1}}),s=r.cva("rst:data-focus:ring-2 rst:data-focus:ring-inset rst:data-focus:ring-ring rst:forced-colors:data-focus:outline-solid rst:forced-colors:data-focus:outline-2 rst:forced-colors:data-focus:-outline-offset-2 rst:text-sm rst:group rst:relative rst:cursor-pointer rst:select-none rst:py-2.5 rst:pl-4 rst:pr-9 rst:transition-colors rst:text-inherit rst:data-focus:bg-primary-100 rst:data-focus:text-primary-900 rst:dark:data-focus:bg-primary-900/30 rst:dark:data-focus:text-primary-100 rst:data-selected:bg-gray-50 rst:dark:data-selected:bg-gray-700/50 rst:data-disabled:opacity-50 rst:data-disabled:cursor-not-allowed",{variants:{size:{sm:"rst:py-2 rst:pl-3 rst:pr-8",default:"rst:py-2.5 rst:pl-4 rst:pr-9",lg:"rst:py-2.5 rst:pl-4 rst:pr-9"}},defaultVariants:{size:"default"}});exports.selectOptionVariants=s;exports.selectTriggerVariants=t;
@@ -76,8 +76,17 @@ const s = /* @__PURE__ */ r(
76
76
  `text-sm` lives here rather than on the popup, and carries no `sm:`. The
77
77
  popup used to set `text-base sm:text-sm`, which made a menu row 44px on
78
78
  mobile and 40px from `sm` up: the exact breakpoint-dependent geometry this
79
- change removed from the trigger, still alive one element away. */
80
- "rst:text-sm rst:group rst:relative rst:cursor-default rst:select-none rst:py-2.5 rst:pl-4 rst:pr-9 rst:transition-colors rst:text-inherit rst:data-focus:bg-primary-100 rst:data-focus:text-primary-900 rst:dark:data-focus:bg-primary-900/30 rst:dark:data-focus:text-primary-100 rst:data-selected:bg-gray-50 rst:dark:data-selected:bg-gray-700/50 rst:data-disabled:opacity-50 rst:data-disabled:cursor-not-allowed",
79
+ change removed from the trigger, still alive one element away.
80
+
81
+ `cursor-pointer`: a row is a click target, as Menu's are.
82
+
83
+ Focus is the tint and an inset ring in the focus token. The tint alone
84
+ measures under 1.2:1 against the panel in both themes, on the default
85
+ surface and a consumer's; the ring follows the token a consumer tunes
86
+ against their own surfaces, and holds 3:1 on any of them. Headless UI's
87
+ `data-focus` comes from the keys and the pointer alike, so the ring shows
88
+ on hover too. In forced colors, which drop both, focus is an outline. */
89
+ "rst:data-focus:ring-2 rst:data-focus:ring-inset rst:data-focus:ring-ring rst:forced-colors:data-focus:outline-solid rst:forced-colors:data-focus:outline-2 rst:forced-colors:data-focus:-outline-offset-2 rst:text-sm rst:group rst:relative rst:cursor-pointer rst:select-none rst:py-2.5 rst:pl-4 rst:pr-9 rst:transition-colors rst:text-inherit rst:data-focus:bg-primary-100 rst:data-focus:text-primary-900 rst:dark:data-focus:bg-primary-900/30 rst:dark:data-focus:text-primary-100 rst:data-selected:bg-gray-50 rst:dark:data-selected:bg-gray-700/50 rst:data-disabled:opacity-50 rst:data-disabled:cursor-not-allowed",
81
90
  {
82
91
  variants: {
83
92
  size: {
@@ -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 };