@blakesteve/roster 5.8.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 +126 -23
- package/dist/components/atoms/Button/button-variants.js +1 -1
- package/dist/components/atoms/Eyebrow/eyebrow-variants.d.ts +2 -2
- package/dist/components/atoms/Input/input-variants.js +1 -1
- package/dist/components/atoms/Skeleton/Skeleton.cjs +1 -1
- package/dist/components/atoms/Skeleton/Skeleton.d.ts +19 -3
- package/dist/components/atoms/Skeleton/Skeleton.js +38 -33
- package/dist/components/atoms/ThemeToggle/ThemeToggle.d.ts +1 -1
- package/dist/components/molecules/CheckboxGroup/CheckboxGroup.d.ts +1 -1
- package/dist/components/molecules/CheckboxGroup/checkbox-group-variants.d.ts +1 -1
- package/dist/components/molecules/Menu/Menu.cjs +1 -0
- package/dist/components/molecules/Menu/Menu.d.ts +169 -0
- package/dist/components/molecules/Menu/Menu.js +577 -0
- package/dist/components/molecules/Menu/menu-typeahead.cjs +1 -0
- package/dist/components/molecules/Menu/menu-typeahead.d.ts +7 -0
- package/dist/components/molecules/Menu/menu-typeahead.js +13 -0
- package/dist/components/molecules/SkeletonPresets/SkeletonPresets.cjs +1 -1
- package/dist/components/molecules/SkeletonPresets/SkeletonPresets.d.ts +13 -4
- package/dist/components/molecules/SkeletonPresets/SkeletonPresets.js +35 -35
- package/dist/components/molecules/Toast/Toaster.d.ts +1 -1
- package/dist/components/organisms/Navbar/Navbar.cjs +1 -1
- package/dist/components/organisms/Navbar/Navbar.d.ts +1 -1
- package/dist/components/organisms/Navbar/Navbar.js +275 -302
- package/dist/index.d.ts +1 -0
- package/dist/lib/utils.cjs +1 -1
- package/dist/lib/utils.js +4 -1
- package/dist/meta.json +3 -3
- package/dist/node_modules/@floating-ui/core/dist/floating-ui.core.js +7 -7
- package/dist/node_modules/@floating-ui/dom/dist/floating-ui.dom.js +2 -2
- package/dist/node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.js +5 -5
- package/dist/node_modules/@headlessui/react/dist/components/portal/portal.js +6 -6
- package/dist/node_modules/@headlessui/react/dist/utils/focus-management.cjs +1 -1
- package/dist/node_modules/@headlessui/react/dist/utils/focus-management.js +52 -61
- package/dist/node_modules/@radix-ui/react-popper/dist/index.js +6 -6
- package/dist/roster.cjs +1 -1
- package/dist/roster.css +1 -1
- package/dist/roster.js +127 -116
- package/package.json +2 -1
- package/dist/node_modules/@headlessui/react/dist/components/menu/menu-machine-glue.cjs +0 -1
- package/dist/node_modules/@headlessui/react/dist/components/menu/menu-machine-glue.js +0 -21
- package/dist/node_modules/@headlessui/react/dist/components/menu/menu-machine.cjs +0 -1
- package/dist/node_modules/@headlessui/react/dist/components/menu/menu-machine.js +0 -120
- package/dist/node_modules/@headlessui/react/dist/components/menu/menu.cjs +0 -1
- package/dist/node_modules/@headlessui/react/dist/components/menu/menu.js +0 -197
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).
|
|
611
|
-
pace: `--roster-skeleton-duration` (1.8s),
|
|
612
|
-
|
|
613
|
-
|
|
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
|
|
|
@@ -924,6 +928,10 @@ look like oversights until you know why:
|
|
|
924
928
|
page does not reach it. All three components copy the nearest `.dark` onto
|
|
925
929
|
the panel to fix that. Custom properties are not carried the same way — set
|
|
926
930
|
`--roster-popover-*` at `:root`.
|
|
931
|
+
- **`Menu` shares the panel, not the anchoring.** Its panel takes the same
|
|
932
|
+
surface, tokens and dark carry, but `Menu` places itself (see
|
|
933
|
+
[Menu](#menu)), writing its own inline `max-height`. It reads the same
|
|
934
|
+
`--anchor-max-height`, so the hatch above works on `MenuItems` too.
|
|
927
935
|
- **`variant="slate"` opens on the dark palette**, in light mode too. That
|
|
928
936
|
variant paints a dark trigger, and its panel was rendering white: a dark
|
|
929
937
|
control opening a white sheet, which is the mismatch `--roster-popover-*`
|
|
@@ -1035,7 +1043,7 @@ rather than vanishing from a field that still reports it.
|
|
|
1035
1043
|
|
|
1036
1044
|
#### CheckboxGroup
|
|
1037
1045
|
|
|
1038
|
-
Three things it adds over the version
|
|
1046
|
+
Three things it adds over the version one app's form hand-rolled:
|
|
1039
1047
|
|
|
1040
1048
|
- **Each option is a Headless UI `Field`**, so its label is wired to its own
|
|
1041
1049
|
checkbox and clicking the text toggles it. The pointer cursor sits on the
|
|
@@ -1105,8 +1113,8 @@ silently drops any selected value whose option has not loaded yet.
|
|
|
1105
1113
|
|
|
1106
1114
|
`CheckboxGroup` exists instead of a multi-select `Select`, and the reason is
|
|
1107
1115
|
that neither multi-selection UI in the portfolio was ever a dropdown.
|
|
1108
|
-
|
|
1109
|
-
category headings;
|
|
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
|
|
1110
1118
|
toggles. Collapsing either into a menu would have hidden the options behind a
|
|
1111
1119
|
click to save vertical space neither screen was short of, so the component
|
|
1112
1120
|
follows the shape that already worked. A multi-select dropdown is still the
|
|
@@ -1201,7 +1209,7 @@ rather than a rewrite.
|
|
|
1201
1209
|
|
|
1202
1210
|
```tsx
|
|
1203
1211
|
<RadioGroup
|
|
1204
|
-
label="Who can see this
|
|
1212
|
+
label="Who can see this group"
|
|
1205
1213
|
helperText="You can change this at any time."
|
|
1206
1214
|
value={visibility}
|
|
1207
1215
|
onChange={setVisibility}
|
|
@@ -1455,7 +1463,21 @@ import { SkeletonRegion, SkeletonCard } from "@blakesteve/roster";
|
|
|
1455
1463
|
- The fill is `--roster-skeleton` (gray-200, gray-700 in dark), with
|
|
1456
1464
|
`--roster-skeleton-highlight` crossing it. Neutral rather than the brand
|
|
1457
1465
|
shimmer Countdown uses, and its own pair, so retinting one never recolors the
|
|
1458
|
-
other.
|
|
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.
|
|
1459
1481
|
|
|
1460
1482
|
**Progress and ProgressField.** `ProgressField` is the form to reach for: the
|
|
1461
1483
|
label and value on one baseline above the bar, an optional detail line below.
|
|
@@ -1499,6 +1521,84 @@ shown beside it.
|
|
|
1499
1521
|
|
|
1500
1522
|
Every string in all five is a prop with an English default.
|
|
1501
1523
|
|
|
1524
|
+
### Menu
|
|
1525
|
+
|
|
1526
|
+
A button that opens a panel of actions: the menu button pattern. Compose it:
|
|
1527
|
+
|
|
1528
|
+
```tsx
|
|
1529
|
+
import { Menu, MenuButton, MenuItems, MenuItem, MenuSeparator } from "@blakesteve/roster";
|
|
1530
|
+
|
|
1531
|
+
<Menu>
|
|
1532
|
+
<MenuButton variant="ghost" size="icon" aria-label="More for Banded agate">⋯</MenuButton>
|
|
1533
|
+
<MenuItems align="end">
|
|
1534
|
+
<MenuItem icon={<EditIcon />} shortcut="⌘E" onSelect={edit}>Edit</MenuItem>
|
|
1535
|
+
<MenuItem href="/rocks/agate/share">Share</MenuItem>
|
|
1536
|
+
<MenuSeparator />
|
|
1537
|
+
<MenuItem destructive onSelect={confirmDelete}>Delete</MenuItem>
|
|
1538
|
+
</MenuItems>
|
|
1539
|
+
</Menu>;
|
|
1540
|
+
```
|
|
1541
|
+
|
|
1542
|
+
- **Items.** `MenuItem` is an action, or a link with `href` (rendered through
|
|
1543
|
+
the menu's `linkComponent`; pass `next/link` from a client component, as with
|
|
1544
|
+
Breadcrumbs). Each takes a leading `icon`, a `shortcut` to show (display
|
|
1545
|
+
only: your app handles the keys; `keyshortcuts` tells assistive technology),
|
|
1546
|
+
a `description` line, a trailing `badge`, `destructive` and `disabled`.
|
|
1547
|
+
`MenuCheckboxItem` is an on/off choice, `MenuRadioGroup` with
|
|
1548
|
+
`MenuRadioItem` one of several, `MenuGroup` puts items under an optional
|
|
1549
|
+
label, `MenuSeparator` draws a rule, and `MenuSubmenu` opens a menu beside
|
|
1550
|
+
its item. Submenus nest.
|
|
1551
|
+
- **Keyboard.** Enter, Space or Down open it on the first item and Up on the
|
|
1552
|
+
last; a click opens it on the panel. The arrows move, Home and End jump, and
|
|
1553
|
+
typing jumps to the next item starting with what's typed, a space included
|
|
1554
|
+
("save a"). Escape closes it and returns focus to the button; Tab closes it
|
|
1555
|
+
and moves on from the button, to what the browser's own Tab would reach. A
|
|
1556
|
+
submenu opens with the arrow away from its parent, Enter, Space, a click or
|
|
1557
|
+
the pointer resting on it, and closes with the arrow back or Escape. A click
|
|
1558
|
+
only ever opens it.
|
|
1559
|
+
- **Choosing.** A click or Enter chooses and closes, with focus back on the
|
|
1560
|
+
button, so a choice that opens a Dialog hands focus to it and gets it back.
|
|
1561
|
+
Space on a checkbox or radio item chooses and keeps the menu open, so
|
|
1562
|
+
several can be set in one visit. `closeOnSelect={false}` keeps it open on
|
|
1563
|
+
any choice. A disabled item can be reached, so it can be read, but not
|
|
1564
|
+
chosen. A press outside closes it without moving the page, and focus stays
|
|
1565
|
+
where the press put it.
|
|
1566
|
+
- **Events.** Keys inside the panel stay there: the panel is portaled, and
|
|
1567
|
+
React would otherwise carry them to the components around the menu, so
|
|
1568
|
+
Enter on Delete would also open the row whose overflow menu it is. A key
|
|
1569
|
+
with Ctrl, Meta or Alt goes on, for the app's shortcuts. Clicks go on to the
|
|
1570
|
+
page, where analytics listen, and so React carries an item's click to that
|
|
1571
|
+
row too: a row with its own `onClick` checks that the click landed inside
|
|
1572
|
+
it (`event.currentTarget.contains(event.target as Node)`). The button is
|
|
1573
|
+
in your markup, and its keys and clicks reach the row like any other.
|
|
1574
|
+
- **Placement.** Below the button, lined up with its start (`align="end"` for
|
|
1575
|
+
the other edge); flipped above when there's more room there, shifted to stay
|
|
1576
|
+
on screen, and capped at the room there is. A taller menu scrolls with
|
|
1577
|
+
Roster's scrollbar and a shadow at whichever edge has more. Under
|
|
1578
|
+
`dir="rtl"` it lines up with the right edge and submenus open to the left.
|
|
1579
|
+
It takes its button's direction, not the page's.
|
|
1580
|
+
- **Inside other layers.** The panel opens in a portal, so nothing it sits in
|
|
1581
|
+
clips it, a card with `overflow: hidden` included. A `Dialog`, a `Sheet` or
|
|
1582
|
+
Navbar's mobile panel counts it as inside: choosing from it leaves them
|
|
1583
|
+
open.
|
|
1584
|
+
- **Styling.** `className` on `MenuItems` and on each item merges last, so a
|
|
1585
|
+
fill such as `bg-gray-100` takes; the scroll shadows are drawn over it. Its
|
|
1586
|
+
ring and separators follow `--roster-border-width`. Items show the pointer
|
|
1587
|
+
cursor, a disabled one `not-allowed`. In forced colors the focused item gets
|
|
1588
|
+
an outline, since its fill is dropped.
|
|
1589
|
+
- **Targets.** Every item is at least 44px tall, and `MenuButton` at least 44
|
|
1590
|
+
by 44 whatever its size. `MenuButton plain` renders a bare `<button>` for a
|
|
1591
|
+
trigger drawn its own way; keep its target at least 44 by 44 with a hit
|
|
1592
|
+
area, as Navbar's avatar does.
|
|
1593
|
+
|
|
1594
|
+
Built on Roster's own popup panel and on Floating UI for placement, which
|
|
1595
|
+
Roster already ships through its other popups, rather than on Headless UI's
|
|
1596
|
+
`Menu`: that has no submenus, and no checkbox or radio items. Navbar's user
|
|
1597
|
+
menu is built on it since 5.9.0. It looks as it did, in every state, except
|
|
1598
|
+
that its rows are 44px tall rather than 36, and that with neither an inbox nor
|
|
1599
|
+
a theme row it no longer draws an empty band and rule at the top. It keeps its
|
|
1600
|
+
own white fill, whatever `--roster-popover-bg` says.
|
|
1601
|
+
|
|
1502
1602
|
### Pagination
|
|
1503
1603
|
|
|
1504
1604
|
Moves through pages of a list, built for a long list browsed on a phone.
|
|
@@ -1751,7 +1851,7 @@ function App() {
|
|
|
1751
1851
|
| `CollapsibleSection` | Clamps any content (prose, chips, image grids) to a fixed height with a fade and expand/collapse toggle |
|
|
1752
1852
|
| `Select` | Dropdown selector, on the same size scale as `Button` and `Input` |
|
|
1753
1853
|
| `SegmentBar` | Proportional horizontal bar divided into colored segments with optional legend (a split, not progress) |
|
|
1754
|
-
| `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` |
|
|
1755
1855
|
| `Progress` | Continuous or segmented progress bar: determinate, indeterminate or busy; per-segment state |
|
|
1756
1856
|
| `LoadingDots` | Three dots for a model generating before its first token; replaced by the text |
|
|
1757
1857
|
| `Stat` | A figure with its label and, optionally, its source; a valid `<dl>` group, or `semantics="standalone"` |
|
|
@@ -1778,6 +1878,7 @@ function App() {
|
|
|
1778
1878
|
| `LiquidTabs` | Tab strip with a sliding pill; arrow keys, roving focus, 44px `lg` size |
|
|
1779
1879
|
| `MatchupCard` | Head-to-head comparison card |
|
|
1780
1880
|
| `MediaPreview` | Thumbnail that falls back to "Preview unavailable, open original" when it can't render |
|
|
1881
|
+
| `Menu` | A button's menu of actions: icons, shortcuts, checkbox and radio items, links, groups, submenus; the full keyboard pattern, 44px targets (see [Menu](#menu)) |
|
|
1781
1882
|
| `FileUpload` | Picks files and uploads each through the app's `upload`, with per-file progress, retry and rules |
|
|
1782
1883
|
| `Pagination` | Page numbers with gaps, or a compact page picker on narrow screens; buttons or real links, 44px targets |
|
|
1783
1884
|
| `SkeletonCard`, `SkeletonTableRow`, `SkeletonAvatar`, `SkeletonStat` | Skeleton presets in the real component's box |
|
|
@@ -1799,7 +1900,7 @@ function App() {
|
|
|
1799
1900
|
| `ModerationQueue` | Items waiting for a decision: busy per action, confirm, reason and edit steps, focus after an item leaves, a live region. Never fetches (see [ModerationQueue](#moderationqueue)) |
|
|
1800
1901
|
| `QueueEditDialog` | A ModerationQueue edit step in a Dialog: pass it as an edit's `surface`. A separate export, so a queue that never uses it doesn't ship it |
|
|
1801
1902
|
| `QueueEditSheet` | A ModerationQueue edit step in a Sheet, for a long form: pass it as an edit's `surface`. A separate export, like `QueueEditDialog` |
|
|
1802
|
-
| `Navbar` | Responsive navigation bar with mobile slide-out
|
|
1903
|
+
| `Navbar` | Responsive navigation bar with mobile slide-out; its user menu is a `Menu`. A wide logo keeps its own proportions through `logoHeight` and `logoWidth`; its built-in words are in `labels` |
|
|
1803
1904
|
| `Sheet` | Bottom sheet: drag, Escape or backdrop to dismiss; only its content scrolls; returns focus, even from a deep link; the title may be a node |
|
|
1804
1905
|
| `Table` | Static data table |
|
|
1805
1906
|
|
|
@@ -1953,14 +2054,16 @@ reason and the build goes green on the state it inherited while still failing on
|
|
|
1953
2054
|
anything new. It also fails when a listed class starts emitting, so a fix cannot
|
|
1954
2055
|
leave a stale entry behind. Run it with `--verbose` to print the list.
|
|
1955
2056
|
|
|
1956
|
-
Story tests that need the user's own input, a wheel
|
|
1957
|
-
from `.storybook/real-input.ts`: Vitest browser
|
|
1958
|
-
Playwright's mouse, whose events are trusted.
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
2057
|
+
Story tests that need the user's own input, a wheel, a mouse drag or a pointer
|
|
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.
|
|
1964
2067
|
|
|
1965
2068
|
`src/lib/utils.test.ts` pins `cn`. `tailwind-merge` has to be configured with the
|
|
1966
2069
|
prefix or it stops recognising Roster's classes as utilities and silently
|
|
@@ -1989,9 +2092,9 @@ Output in `dist/`:
|
|
|
1989
2092
|
|
|
1990
2093
|
### What `meta.json` is for
|
|
1991
2094
|
|
|
1992
|
-
Consumers read this package to describe it.
|
|
1993
|
-
version from `package.json` and counts components by parsing the exports
|
|
1994
|
-
`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.
|
|
1995
2098
|
|
|
1996
2099
|
The test count has no such source. `files` is `["dist"]`, so the suite never
|
|
1997
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
|
|
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
|
|
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.
|
|
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"),
|
|
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
|
-
/**
|
|
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
|
|
3
|
-
import {
|
|
4
|
-
import { cn as
|
|
5
|
-
const
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
23
|
-
|
|
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
|
|
26
|
-
return /* @__PURE__ */
|
|
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:
|
|
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__ */
|
|
41
|
+
return /* @__PURE__ */ e(
|
|
37
42
|
"span",
|
|
38
43
|
{
|
|
39
44
|
"aria-hidden": "true",
|
|
40
|
-
className:
|
|
41
|
-
style: { height:
|
|
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
|
|
46
|
-
const
|
|
47
|
-
return /* @__PURE__ */
|
|
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:
|
|
51
|
-
style: { width:
|
|
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__ */
|
|
59
|
+
/* @__PURE__ */ e(
|
|
55
60
|
"span",
|
|
56
61
|
{
|
|
57
|
-
className:
|
|
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
|
-
|
|
67
|
+
i
|
|
63
68
|
);
|
|
64
69
|
};
|
|
65
|
-
return /* @__PURE__ */
|
|
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
|
|
68
|
-
const [
|
|
69
|
-
return
|
|
70
|
-
const
|
|
71
|
-
return () => clearTimeout(
|
|
72
|
-
}, [t, d,
|
|
73
|
-
/* @__PURE__ */
|
|
74
|
-
/* @__PURE__ */
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
* ("
|
|
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
|
|
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
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("react/jsx-runtime"),ge=require("react"),W=require("../../../node_modules/@headlessui/react/dist/components/portal/portal.cjs"),A=require("../../../node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.cjs"),E=require("../../../lib/utils.cjs"),he=require("../../../internal/popup.cjs"),be=require("../../atoms/Button/Button.cjs"),xe=require("./menu-typeahead.cjs"),ye=require("../../../node_modules/@floating-ui/dom/dist/floating-ui.dom.cjs");function ve(e){const r=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const n in e)if(n!=="default"){const o=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(r,n,o.get?o:{enumerable:!0,get:()=>e[n]})}}return r.default=e,Object.freeze(r)}const i=ve(ge);function we({href:e,to:r,children:n,...o}){return c.jsx("a",{href:e,...o,children:n})}const G=i.createContext(null),_=i.createContext(null),V=i.createContext(null);function R(e){const r=i.useContext(G);if(!r)throw new Error(`<${e}> must be inside a <Menu>.`);return r}function K(e){const r=i.useContext(_);if(!r)throw new Error(`<${e}> must be inside <MenuItems> or a <MenuSubmenu>.`);return r}const ke="[data-menu-item]",Y=100,Ie=75,Ee='a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), summary, [tabindex]:not([tabindex="-1"]), [contenteditable]:not([contenteditable="false"])';function L(e){return e?[...e.querySelectorAll(ke)].filter(r=>r.closest("[data-menu-panel]")===e):[]}function S(e){e&&(e.focus({preventScroll:!0}),e.scrollIntoView?.({block:"nearest"}))}function $(e){return!!e&&getComputedStyle(e).direction==="rtl"}function Se(e,r){const n=[...document.querySelectorAll(Ee)].filter(t=>t===e||t.tabIndex>=0&&!t.matches(":disabled")&&!t.closest("[data-menu-panel], [inert]")&&t.getClientRects().length>0&&getComputedStyle(t).visibility!=="hidden"&&!Me(t,r)),o=n.indexOf(e);((r?n[o-1]:n[o+1])??e).focus()}function Me(e,r){if(!(e instanceof HTMLInputElement)||e.type!=="radio"||!e.name)return!1;const n=[...e.ownerDocument.querySelectorAll(`input[type="radio"][name="${CSS.escape(e.name)}"]`)].filter(a=>a.form===e.form&&!a.disabled);return(n.find(a=>a.checked)??(r?n[n.length-1]:n[0]))!==e}const D={x:Number.NaN,y:Number.NaN};let H=!1;function je(){H||typeof window>"u"||(H=!0,window.addEventListener("pointermove",e=>{D.x=e.clientX,D.y=e.clientY},{passive:!0}))}function X(e){return!Number.isNaN(D.x)&&(e.clientX!==D.x||e.clientY!==D.y)}const U=e=>`radial-gradient(farthest-side at 50% ${e==="top"?"0":"100%"}, var(--menu-edge), transparent)`,J={backgroundImage:"var(--menu-edge-top, none), var(--menu-edge-bottom, none)",backgroundPosition:"top, bottom",backgroundSize:"100% 0.5rem",backgroundRepeat:"no-repeat"};function Pe(e){i.useLayoutEffect(()=>{if(!e)return;const r=()=>{const o=e.scrollTop>8,a=e.scrollTop+e.clientHeight<e.scrollHeight-8;e.toggleAttribute("data-more-above",o),e.toggleAttribute("data-more-below",a),e.style.setProperty("--menu-edge-top",o?U("top"):"none"),e.style.setProperty("--menu-edge-bottom",a?U("bottom"):"none")};r(),e.addEventListener("scroll",r,{passive:!0});const n=typeof ResizeObserver>"u"?null:new ResizeObserver(r);return n?.observe(e),()=>{e.removeEventListener("scroll",r),n?.disconnect()}},[e])}function Q(e,r){const[n,o]=e.split("-"),a=r?"right":"left",t=r?"left":"right";return n==="top"||n==="bottom"?`${n==="bottom"?"top":"bottom"} ${o==="end"?t:o==="start"?a:"center"}`:`${o==="end"?"bottom":"top"} ${n==="right"?"left":"right"}`}const Z=E.cn("rst:font-ui",he.POPUP_PANEL,"rst:w-max rst:min-w-48 rst:max-w-[min(20rem,calc(100vw-1rem))] rst:overflow-y-auto rst:overscroll-contain rst:custom-scrollbar","rst:[--menu-edge:rgb(0_0_0/0.18)] rst:dark:[--menu-edge:rgb(0_0_0/0.6)]","rst:transition-[opacity,scale] rst:duration-100 rst:ease-out rst:data-closed:opacity-0 rst:data-closed:scale-95 rst:data-leaving:duration-75 rst:data-leaving:ease-in rst:data-closed:pointer-events-none rst:motion-reduce:transition-none");function ee(e){const[r,n]=i.useState(e),[o,a]=i.useState(!0);return i.useLayoutEffect(()=>{if(e){n(!0);const s=requestAnimationFrame(()=>a(!1));return()=>cancelAnimationFrame(s)}a(!0);const t=window.matchMedia?.("(prefers-reduced-motion: reduce)").matches,d=window.setTimeout(()=>n(!1),t?0:Ie);return()=>window.clearTimeout(d)},[e]),{mounted:r,closed:o,leaving:r&&!e}}function te(e,r,n,o,a,t=!1){const[d,s]=i.useState(null),u=A.useFloating({open:r,strategy:"fixed",transform:!1,placement:n,elements:{reference:e},whileElementsMounted:ye.autoUpdate,middleware:[A.offset(o),A.flip({padding:8}),A.shift({padding:8,crossAxis:t}),A.size({padding:8,apply({availableHeight:f,elements:l}){l.floating.style.maxHeight=`min(var(--anchor-max-height, 100vh), ${Math.max(f,96)}px)`}})]}),{setFloating:p}=u.refs,m=i.useCallback(f=>{a.current=f,s(f),p(f)},[a,p]);return Pe(d),{...u,setPanel:m,panelEl:d}}const re=500;function ne(){const e=i.useRef({text:"",at:0}),r=i.useCallback(()=>e.current.text!==""&&performance.now()-e.current.at<=re,[]);return{typedRef:e,typingAhead:r}}function oe({panelRef:e,typedRef:r,onEscape:n,onBack:o}){const a=R("MenuItems");return t=>{const d=e.current;if(!d||!d.contains(t.target)||(!t.ctrlKey&&!t.metaKey&&!t.altKey&&t.stopPropagation(),t.defaultPrevented))return;const s=L(d),u=s.indexOf(document.activeElement),p=$(d);switch(t.key){case"ArrowDown":t.preventDefault(),t.stopPropagation(),S(s[(u+1)%s.length]);return;case"ArrowUp":t.preventDefault(),t.stopPropagation(),S(s[u<=0?s.length-1:u-1]);return;case"Home":case"PageUp":t.preventDefault(),t.stopPropagation(),S(s[0]);return;case"End":case"PageDown":t.preventDefault(),t.stopPropagation(),S(s[s.length-1]);return;case"Escape":t.preventDefault(),t.stopPropagation(),n();return;case"Tab":{t.preventDefault(),t.stopPropagation();const m=a.triggerRef.current;a.close(!1),m&&Se(m,t.shiftKey);return}case"ArrowLeft":case"ArrowRight":o&&t.key===(p?"ArrowRight":"ArrowLeft")&&(t.preventDefault(),t.stopPropagation(),o());return}if(t.key.length===1&&!t.ctrlKey&&!t.metaKey&&!t.altKey){const m=performance.now(),f=r.current.text!==""&&m-r.current.at<=re;if(t.key===" "&&!f)return;t.preventDefault(),r.current={text:f?r.current.text+t.key:t.key,at:m};const l=s.map(w=>w.dataset.menuText??w.textContent??""),b=xe.nextByTypeahead(l,u,r.current.text);b!==-1&&S(s[b])}}}function Te({children:e,open:r,onOpenChange:n,linkComponent:o=we}){const[a,t]=i.useState(!1),d=r??a,s=i.useId(),u=i.useRef(null),[p,m]=i.useState(null),f=i.useCallback(g=>{u.current=g,m(g)},[]),l=i.useRef("panel"),b=i.useCallback(g=>{r===void 0&&t(g),n?.(g)},[r,n]),w=i.useMemo(()=>({treeId:s,open:d,openWith:g=>{l.current=g,b(!0)},close:g=>{b(!1),g&&u.current?.focus()},triggerRef:u,triggerEl:p,registerTrigger:f,triggerId:`${s}-button`,panelId:`${s}-menu`,focusOnOpen:l,linkComponent:o}),[s,d,b,o,p,f]);return i.useEffect(()=>{if(!d)return;je();const g=I=>{I.target?.closest?.(`[data-menu-tree="${s}"]`)||(b(!1),requestAnimationFrame(()=>{(document.activeElement===document.body||!document.activeElement)&&u.current?.focus({preventScroll:!0})}))};return document.addEventListener("pointerdown",g,!0),()=>document.removeEventListener("pointerdown",g,!0)},[d,s,b]),c.jsx(G.Provider,{value:w,children:e})}const Re=i.forwardRef(function({plain:r=!1,className:n,onClick:o,onKeyDown:a,children:t,...d},s){const{variant:u,colorScheme:p,size:m,isLoading:f,loadingLabel:l,startIcon:b,endIcon:w,surface:g,...I}=d,h=R("MenuButton"),{registerTrigger:M}=h,j=i.useCallback(x=>{M(x),typeof s=="function"?s(x):s&&(s.current=x)},[M,s]),P={id:h.triggerId,"aria-haspopup":"menu","aria-expanded":h.open,"aria-controls":h.open?h.panelId:void 0,"data-menu-tree":h.treeId,onClick:x=>{o?.(x),!x.defaultPrevented&&(h.open?h.close(!1):h.openWith("panel"))},onKeyDown:x=>{if(a?.(x),x.defaultPrevented)return;const y=x.key==="Enter"||x.key===" "||x.key==="ArrowDown"?"first":x.key==="ArrowUp"?"last":null;if(!y)return;x.preventDefault();const k=h.open?document.getElementById(h.panelId):null;if(k){const C=L(k);S(y==="last"?C[C.length-1]:C[0]);return}h.openWith(y)}};return r?c.jsx("button",{ref:j,type:"button",className:n,...I,...P,children:t}):c.jsx(be.Button,{ref:j,className:E.cn("rst:min-h-11 rst:min-w-11",n),variant:u,colorScheme:p,size:m,isLoading:f,loadingLabel:l,startIcon:b,endIcon:w,surface:g,...I,...P,children:t})});function Ce({children:e,align:r="start",offset:n=4,className:o,...a}){const t=R("MenuItems"),{mounted:d,closed:s,leaving:u}=ee(t.open),p=i.useRef(null),m=$(t.triggerEl),{setPanel:f,panelEl:l,floatingStyles:b,placement:w,isPositioned:g}=te(t.triggerEl,d,`bottom-${r}`,n,p),I=se(),{typedRef:h,typingAhead:M}=ne(),j=oe({panelRef:p,typedRef:h,onEscape:()=>t.close(!0)});i.useLayoutEffect(()=>{if(!t.open||!l)return;const y=L(l),k=t.focusOnOpen.current;k==="first"&&y[0]?S(y[0]):k==="last"&&y.length?S(y[y.length-1]):l.focus({preventScroll:!0})},[t.open,l,t.focusOnOpen]),i.useLayoutEffect(()=>{if(!g||!l)return;const y=document.activeElement;y&&y!==l&&l.contains(y)&&y.scrollIntoView?.({block:"nearest"})},[g,l]),i.useLayoutEffect(()=>{!t.open&&l?.contains(document.activeElement)&&t.triggerRef.current?.focus({preventScroll:!0})},[t.open,l,t.triggerRef]);const P=y=>{const k=y.relatedTarget;k&&!k.closest(`[data-menu-tree="${t.treeId}"]`)&&t.close(!1)};if(!d)return null;const x=!!t.triggerEl?.closest(".dark");return c.jsx(W.Portal,{children:c.jsx(_.Provider,{value:{...I,panelRef:p,typingAhead:M},children:c.jsx("div",{ref:f,id:t.panelId,dir:m?"rtl":"ltr",role:"menu","aria-labelledby":a["aria-label"]?void 0:t.triggerId,"aria-label":a["aria-label"],tabIndex:-1,"data-menu-panel":"","data-menu-tree":t.treeId,"data-closed":s?"":void 0,"data-leaving":u?"":void 0,onKeyDown:j,onBlur:P,style:{...b,...J,transformOrigin:Q(w,m)},className:E.cn(x&&"dark",Z,o),children:e})})})}function se(){const[e,r]=i.useState(null),n=i.useRef(!1),o=i.useRef(void 0);return i.useEffect(()=>()=>window.clearTimeout(o.current),[]),{openSubmenu:e,focusFirstInSubmenu:n,setOpenSubmenu:(a,t=!1)=>{window.clearTimeout(o.current),n.current=t,r(a)},hoverItem:a=>{window.clearTimeout(o.current),a===null&&(o.current=window.setTimeout(()=>r(null),Y))}}}const O=E.cn("rst:relative rst:flex rst:w-full rst:min-h-11 rst:items-center rst:gap-2.5 rst:border-0 rst:bg-transparent rst:px-3 rst:py-2 rst:text-start rst:text-sm rst:text-inherit rst:no-underline rst:[font-family:inherit] rst:cursor-pointer rst:select-none rst:outline-none rst:transition-colors","rst:focus:bg-primary-100 rst:focus:text-primary-900 rst:dark:focus:bg-primary-900/30 rst:dark:focus:text-primary-100 rst:forced-colors:focus:outline-solid rst:forced-colors:focus:outline-2 rst:forced-colors:focus:-outline-offset-2","rst:aria-disabled:opacity-50 rst:aria-disabled:cursor-not-allowed"),q="rst:text-error-600 rst:dark:text-error-400 rst:focus:bg-error-50 rst:focus:text-error-700 rst:dark:focus:bg-error-900/30 rst:dark:focus:text-error-300",Ae=()=>c.jsx("svg",{viewBox:"0 0 16 16",width:"16",height:"16",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:c.jsx("path",{d:"M3.5 8.5 6.5 11.5 12.5 4.5"})}),De=()=>c.jsx("svg",{viewBox:"0 0 16 16",width:"16",height:"16","aria-hidden":"true",children:c.jsx("circle",{cx:"8",cy:"8",r:"3.5",fill:"currentColor"})}),Le=()=>c.jsx("svg",{viewBox:"0 0 16 16",width:"16",height:"16",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",className:"rst:rtl:rotate-180",children:c.jsx("path",{d:"M6 3.5 10.5 8 6 12.5"})});function T({icon:e,shortcut:r,description:n,badge:o,children:a},t,d,s){const u=t??e;return c.jsxs(c.Fragment,{children:[u!=null&&c.jsx("span",{"aria-hidden":"true",className:E.cn("rst:flex rst:size-4 rst:shrink-0 rst:items-center rst:justify-center",n!=null&&"rst:self-start rst:mt-0.5"),"data-menu-lead":"",children:u}),c.jsxs("span",{className:"rst:min-w-0 rst:flex-1 rst:break-words","data-menu-label":"",children:[a,n!=null&&c.jsx("span",{id:s,"aria-hidden":"true",className:"rst:block rst:text-xs rst:opacity-75","data-menu-description":"",children:n})]}),o!=null&&c.jsx("span",{className:"rst:flex rst:shrink-0 rst:items-center","data-menu-badge":"",children:o}),r&&c.jsx("span",{"aria-hidden":"true",className:"rst:ms-auto rst:shrink-0 rst:ps-4 rst:text-xs rst:tracking-wide rst:opacity-70","data-menu-shortcut":"",children:r}),d]})}function z({disabled:e,closeOnSelect:r,onChoose:n,spaceKeepsOpen:o}){const a=R("MenuItem"),t=K("MenuItem"),d=(s,u)=>{if(e){s.preventDefault();return}n(s),u&&a.close(!0)};return{onClick:s=>d(s,r),onKeyDown:s=>{if(s.key===" "){if(t.typingAhead())return;s.preventDefault(),d(s,r&&!o)}},onPointerMove:s=>{X(s)&&(t.hoverItem(null),document.activeElement!==s.currentTarget&&s.currentTarget.focus({preventScroll:!0}))}}}function Oe({onSelect:e,href:r,closeOnSelect:n=!0,disabled:o,destructive:a,keyshortcuts:t,textValue:d,className:s,...u}){const p=R("MenuItem"),m=K("MenuItem"),f=i.useId(),l=z({disabled:o,closeOnSelect:n,onChoose:g=>e?.(g)}),b=E.cn(O,a&&q,s),w={role:"menuitem",tabIndex:-1,"aria-disabled":o?!0:void 0,"aria-keyshortcuts":t,"aria-describedby":u.description!=null?f:void 0,"data-menu-item":"","data-menu-text":d,"data-destructive":a?"":void 0};if(r!==void 0){const g=p.linkComponent;return c.jsx(g,{href:r,to:r,className:b,...w,onClick:l.onClick,onPointerMove:l.onPointerMove,onKeyDown:I=>{if(I.key===" "){if(m.typingAhead())return;I.preventDefault(),o||I.currentTarget.click()}},children:T(u,void 0,void 0,f)})}return c.jsx("button",{type:"button",className:b,...w,...l,children:T(u,void 0,void 0,f)})}function Ne({checked:e,onCheckedChange:r,indicator:n,closeOnSelect:o=!0,disabled:a,destructive:t,keyshortcuts:d,textValue:s,className:u,...p}){const m=z({disabled:a,closeOnSelect:o,spaceKeepsOpen:!0,onChoose:()=>r(!e)}),f=i.useId();return c.jsx("button",{type:"button",role:"menuitemcheckbox","aria-checked":e,"aria-describedby":p.description!=null?f:void 0,tabIndex:-1,"aria-disabled":a||void 0,"aria-keyshortcuts":d,"data-menu-item":"","data-menu-text":s,className:E.cn(O,t&&q,u),...m,children:n===void 0?T(p,c.jsx("span",{className:e?void 0:"rst:invisible","data-menu-check":"",children:c.jsx(Ae,{})}),void 0,f):T(p,void 0,c.jsx("span",{"aria-hidden":"true",className:"rst:ms-auto rst:flex rst:shrink-0 rst:items-center",children:n}),f)})}function Be({value:e,onValueChange:r,label:n,children:o,className:a}){return c.jsx(V.Provider,{value:{value:e,onValueChange:r},children:c.jsx(ae,{label:n,className:a,children:o})})}function _e({value:e,closeOnSelect:r=!0,disabled:n,destructive:o,keyshortcuts:a,textValue:t,className:d,...s}){const u=i.useContext(V);if(!u)throw new Error("<MenuRadioItem> must be inside a <MenuRadioGroup>.");const p=u.value===e,m=z({disabled:n,closeOnSelect:r,spaceKeepsOpen:!0,onChoose:()=>u.onValueChange?.(e)}),f=i.useId();return c.jsx("button",{type:"button",role:"menuitemradio","aria-checked":p,"aria-describedby":s.description!=null?f:void 0,tabIndex:-1,"aria-disabled":n||void 0,"aria-keyshortcuts":a,"data-menu-item":"","data-menu-text":t,className:E.cn(O,o&&q,d),...m,children:T(s,c.jsx("span",{className:p?void 0:"rst:invisible","data-menu-check":"",children:c.jsx(De,{})}),void 0,f)})}function ae({label:e,children:r,className:n}){const o=i.useId();return c.jsxs("div",{role:"group","aria-labelledby":e?o:void 0,className:n,children:[e&&c.jsx("div",{id:o,className:"rst:px-3 rst:pt-2 rst:pb-1 rst:text-xs rst:font-semibold rst:text-gray-600 rst:dark:text-gray-400","data-menu-heading":"",children:e}),r]})}function Ke({className:e}){return c.jsx("div",{role:"separator",className:E.cn("rst:my-1 rst:h-[var(--roster-border-width,1px)] rst:bg-[var(--roster-popover-border)]",e)})}function $e({label:e,children:r,icon:n,disabled:o,textValue:a,className:t,panelClassName:d}){const s=R("MenuSubmenu"),u=K("MenuSubmenu"),p=i.useId(),m=`${p}-item`,f=`${p}-menu`,l=u.openSubmenu===m,b=i.useRef(null),[w,g]=i.useState(null),I=i.useCallback(v=>{b.current=v,g(v)},[]),h=i.useRef(null),M=i.useRef(void 0),j=i.useRef("mouse"),{mounted:P,closed:x,leaving:y}=ee(l),k=$(w),{setPanel:C,panelEl:N,floatingStyles:ie,placement:ue}=te(w,P,k?"left-start":"right-start",{mainAxis:0,alignmentAxis:-4},h,!0),ce=se();i.useEffect(()=>()=>window.clearTimeout(M.current),[]);const F=()=>{u.setOpenSubmenu(null),b.current?.focus({preventScroll:!0})},{typedRef:le,typingAhead:de}=ne(),fe=oe({panelRef:h,typedRef:le,onEscape:F,onBack:F});i.useLayoutEffect(()=>{!l||!N||u.focusFirstInSubmenu.current&&S(L(N)[0])},[l,N,u.focusFirstInSubmenu]);const B=v=>{if(!o){if(l&&v&&h.current){S(L(h.current)[0]);return}u.setOpenSubmenu(m,v)}},me=!!s.triggerEl?.closest(".dark");return c.jsxs(c.Fragment,{children:[c.jsx("button",{ref:I,id:m,type:"button",role:"menuitem",tabIndex:-1,"aria-haspopup":"menu","aria-expanded":l,"aria-controls":l?f:void 0,"aria-disabled":o||void 0,"data-menu-item":"","data-menu-text":a,"data-menu-tree":s.treeId,className:E.cn(O,l&&"rst:bg-gray-100 rst:dark:bg-gray-700/50",t),onPointerDown:v=>{j.current=v.pointerType},onClick:()=>l&&j.current!=="mouse"?u.setOpenSubmenu(null):B(!1),onKeyDown:v=>{const pe=k?"ArrowLeft":"ArrowRight";v.key===" "&&u.typingAhead()||(v.key===pe||v.key==="Enter"||v.key===" ")&&(v.preventDefault(),B(!0))},onPointerMove:v=>{X(v)&&(u.hoverItem(m),document.activeElement!==v.currentTarget&&v.currentTarget.focus({preventScroll:!0}),!l&&!o&&(window.clearTimeout(M.current),M.current=window.setTimeout(()=>B(!1),Y)))},onPointerLeave:()=>window.clearTimeout(M.current),children:T({icon:n,children:e},void 0,c.jsx("span",{"aria-hidden":"true",className:"rst:ms-auto rst:flex rst:shrink-0 rst:items-center rst:opacity-70","data-menu-chevron":"",children:c.jsx(Le,{})}))}),P&&c.jsx(W.Portal,{children:c.jsx(_.Provider,{value:{...ce,panelRef:h,typingAhead:de},children:c.jsx("div",{ref:C,id:f,dir:k?"rtl":"ltr",role:"menu","aria-labelledby":m,tabIndex:-1,"data-menu-panel":"","data-menu-tree":s.treeId,"data-closed":x?"":void 0,"data-leaving":y?"":void 0,onKeyDown:fe,onPointerEnter:()=>u.hoverItem(m),style:{...ie,...J,transformOrigin:Q(ue,k)},className:E.cn(me&&"dark",Z,d),children:r})})})]})}exports.Menu=Te;exports.MenuButton=Re;exports.MenuCheckboxItem=Ne;exports.MenuGroup=ae;exports.MenuItem=Oe;exports.MenuItems=Ce;exports.MenuRadioGroup=Be;exports.MenuRadioItem=_e;exports.MenuSeparator=Ke;exports.MenuSubmenu=$e;
|