@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.
Files changed (44) hide show
  1. package/README.md +126 -23
  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/Skeleton/Skeleton.cjs +1 -1
  6. package/dist/components/atoms/Skeleton/Skeleton.d.ts +19 -3
  7. package/dist/components/atoms/Skeleton/Skeleton.js +38 -33
  8. package/dist/components/atoms/ThemeToggle/ThemeToggle.d.ts +1 -1
  9. package/dist/components/molecules/CheckboxGroup/CheckboxGroup.d.ts +1 -1
  10. package/dist/components/molecules/CheckboxGroup/checkbox-group-variants.d.ts +1 -1
  11. package/dist/components/molecules/Menu/Menu.cjs +1 -0
  12. package/dist/components/molecules/Menu/Menu.d.ts +169 -0
  13. package/dist/components/molecules/Menu/Menu.js +577 -0
  14. package/dist/components/molecules/Menu/menu-typeahead.cjs +1 -0
  15. package/dist/components/molecules/Menu/menu-typeahead.d.ts +7 -0
  16. package/dist/components/molecules/Menu/menu-typeahead.js +13 -0
  17. package/dist/components/molecules/SkeletonPresets/SkeletonPresets.cjs +1 -1
  18. package/dist/components/molecules/SkeletonPresets/SkeletonPresets.d.ts +13 -4
  19. package/dist/components/molecules/SkeletonPresets/SkeletonPresets.js +35 -35
  20. package/dist/components/molecules/Toast/Toaster.d.ts +1 -1
  21. package/dist/components/organisms/Navbar/Navbar.cjs +1 -1
  22. package/dist/components/organisms/Navbar/Navbar.d.ts +1 -1
  23. package/dist/components/organisms/Navbar/Navbar.js +275 -302
  24. package/dist/index.d.ts +1 -0
  25. package/dist/lib/utils.cjs +1 -1
  26. package/dist/lib/utils.js +4 -1
  27. package/dist/meta.json +3 -3
  28. package/dist/node_modules/@floating-ui/core/dist/floating-ui.core.js +7 -7
  29. package/dist/node_modules/@floating-ui/dom/dist/floating-ui.dom.js +2 -2
  30. package/dist/node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.js +5 -5
  31. package/dist/node_modules/@headlessui/react/dist/components/portal/portal.js +6 -6
  32. package/dist/node_modules/@headlessui/react/dist/utils/focus-management.cjs +1 -1
  33. package/dist/node_modules/@headlessui/react/dist/utils/focus-management.js +52 -61
  34. package/dist/node_modules/@radix-ui/react-popper/dist/index.js +6 -6
  35. package/dist/roster.cjs +1 -1
  36. package/dist/roster.css +1 -1
  37. package/dist/roster.js +127 -116
  38. package/package.json +2 -1
  39. package/dist/node_modules/@headlessui/react/dist/components/menu/menu-machine-glue.cjs +0 -1
  40. package/dist/node_modules/@headlessui/react/dist/components/menu/menu-machine-glue.js +0 -21
  41. package/dist/node_modules/@headlessui/react/dist/components/menu/menu-machine.cjs +0 -1
  42. package/dist/node_modules/@headlessui/react/dist/components/menu/menu-machine.js +0 -120
  43. package/dist/node_modules/@headlessui/react/dist/components/menu/menu.cjs +0 -1
  44. 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). Three durations, unset by default, change the
611
- pace: `--roster-skeleton-duration` (1.8s), `--roster-progress-travel-duration`
612
- (1.4s, the indeterminate pill) and `--roster-dots-duration` (1.2s). Under
613
- reduced motion all three stop on a resting frame.
610
+ gray-700 and gray-600 in dark). Four durations, unset by default, change the
611
+ pace: `--roster-skeleton-duration` (1.8s, the shimmer),
612
+ `--roster-skeleton-pulse-duration` (2s, the pulse),
613
+ `--roster-progress-travel-duration` (1.4s, the indeterminate pill) and
614
+ `--roster-dots-duration` (1.2s). `--roster-skeleton-pulse-opacity` (0.5) is
615
+ how far a pulse fades. Under reduced motion all of them stop on a resting
616
+ frame. `--roster-skeleton-radius` rounds skeleton blocks without touching the
617
+ shared radius scale (see Skeleton below).
614
618
 
615
619
  ### Scrollbars
616
620
 
@@ -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 mega-squad's squad form hand-rolled:
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
- mega-squad's squad form picks sports from a grouped, scrollable checklist with
1109
- category headings; bb-memorial's share form picks tags from a flat row of
1116
+ One app's form picks sports from a grouped, scrollable checklist with
1117
+ category headings; another's share form picks tags from a flat row of
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 squad"
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. Under reduced motion it holds still.
1466
+ other.
1467
+ - `animation` picks how it moves: `"shimmer"` (the default, a band of light
1468
+ crossing the fill), `"pulse"` (the fill fading and returning, nothing
1469
+ moving: quieter on a dense page) or `"none"`. Under reduced motion both
1470
+ movements stop on the flat fill, and in forced colors every shape keeps its
1471
+ outline (a pulse stops there, so the outline never fades). Pass it to
1472
+ `SkeletonRegion` and every skeleton inside takes it, presets included; a
1473
+ preset or a skeleton that names its own keeps it. A pulse runs
1474
+ `--roster-skeleton-pulse-duration` (2s) and fades to
1475
+ `--roster-skeleton-pulse-opacity` (0.5).
1476
+ - A block's corners are `--roster-skeleton-radius`, the `lg` step unless
1477
+ set. Set it on `:root` or on a wrapper (`14px`, say) and skeleton blocks
1478
+ follow, and nothing else does: the shared scale and every other component
1479
+ stay as they are. A block's `radius` prop still wins, a line keeps its
1480
+ small step, and a circle stays round.
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. A wide logo keeps its own proportions through `logoHeight` and `logoWidth`; its built-in words are in `labels` |
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 or a mouse drag, get it
1957
- from `.storybook/real-input.ts`: Vitest browser commands that drive
1958
- Playwright's mouse, whose events are trusted. A synthetic wheel or pointer
1959
- event never scrolls anything, so "a vertical wheel scrolls the page, not the
1960
- row" can't be tested without them. The commands fail loudly when the pointer
1961
- isn't over their target, rather than letting a wheel land on the page and a
1962
- test pass for the wrong reason. Storybook's own UI has no such input, so those
1963
- steps are skipped there.
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. blakeb.dev's case study takes the
1993
- version from `package.json` and counts components by parsing the exports out of
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 blakeb.dev, whose paper is the same value it
68
+ // exactly what happened to one app, whose paper is the same value it
69
69
  // maps `--roster-gray-100` to. An alpha wash darkens or lightens whatever
70
70
  // is actually behind it, so it reads on any surface.
71
71
  { variant: "ghost", colorScheme: "primary", className: "rst:text-primary-600 rst:hover:bg-primary-500/10 rst:dark:text-primary-400 rst:dark:hover:bg-primary-400/15" },
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The small uppercase label that sits above a heading, beside a rule, or as a
3
- * column head. Extracted from blakeb.dev, where it was hand-rolled as a CSS
4
- * class and used 26 times before anyone noticed it was a component.
3
+ * column head. Extracted from a portfolio site, where it was hand-rolled as a
4
+ * CSS class and used 26 times before anyone noticed it was a component.
5
5
  *
6
6
  * Monospace on purpose: at this size, tracked-out uppercase reads as chrome
7
7
  * rather than prose, which is the whole job.
@@ -49,7 +49,7 @@ const s = /* @__PURE__ */ r(
49
49
  },
50
50
  /* Deliberately the same three heights as Button: h-9 / h-10 / h-11.
51
51
  Input was a fixed 42px, which agreed with Button at no size at all, so
52
- a field beside a submit button never lined up. retrospect gave up and
52
+ a field beside a submit button never lined up. One app gave up and
53
53
  kept a raw <input> pinned to h-11. */
54
54
  size: {
55
55
  sm: "rst:h-9 rst:px-3",
@@ -1 +1 @@
1
- "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),x=require("react"),c=require("../../../lib/utils.cjs"),k={sm:"rst:text-xs",md:"rst:text-sm",lg:"rst:text-base",inherit:""},f={xs:"rst:h-6 rst:w-6",sm:"rst:h-8 rst:w-8",md:"rst:h-10 rst:w-10",lg:"rst:h-12 rst:w-12",xl:"rst:h-16 rst:w-16"},b={sm:"rst:rounded-sm",md:"rst:rounded-md",lg:"rst:rounded-lg",xl:"rst:rounded-xl",full:"rst:rounded-full"},h="rst:animate-skeleton rst:forced-colors:outline-solid rst:forced-colors:outline-1";function S({shape:s="line",size:r,lines:a=1,width:e,height:u,radius:i,className:l}){if(s==="circle"){const o=r&&r in f?r:"md";return t.jsx("span",{"aria-hidden":"true",className:c.cn("rst:block rst:shrink-0 rst:rounded-full rst:border rst:border-transparent",f[o],h,l),"data-skeleton":"circle"})}if(s==="block")return t.jsx("span",{"aria-hidden":"true",className:c.cn("rst:block rst:w-full",b[i??"lg"],h,l),style:{height:u??"8rem",width:e},"data-skeleton":"block"});const m=r&&r in k?r:"md",n=Math.max(1,Math.floor(a)),j=o=>{const d=o===n-1;return t.jsxs("span",{className:c.cn("rst:relative rst:block",n>1&&!d&&"rst:w-full"),style:{width:n>1?d?e??"60%":void 0:e},children:[" ",t.jsx("span",{className:c.cn("rst:absolute rst:inset-x-0 rst:top-1/2 rst:h-[0.7em] rst:-translate-y-1/2",b[i??"sm"],h)})]},o)};return t.jsx("span",{"aria-hidden":"true",className:c.cn("rst:block",k[m],l),"data-skeleton":"line",children:Array.from({length:n},(o,d)=>j(d))})}function g({loading:s,skeleton:r,label:a="Loading",announce:e=!0,children:u,className:i}){const[l,m]=x.useState("");return x.useEffect(()=>{const n=setTimeout(()=>m(s&&e?a:""),s?50:0);return()=>clearTimeout(n)},[s,a,e]),t.jsxs(t.Fragment,{children:[t.jsx("span",{role:"status",className:"rst:sr-only","data-skeleton-status":"",children:l}),t.jsx("div",{"aria-busy":s||void 0,className:i,"data-skeleton-region":s?"loading":"ready",children:s?t.jsx("div",{"aria-hidden":"true",children:r}):u})]})}exports.Skeleton=S;exports.SkeletonRegion=g;
1
+ "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),d=require("react"),i=require("../../../lib/utils.cjs"),b={sm:"rst:text-xs",md:"rst:text-sm",lg:"rst:text-base",inherit:""},j={xs:"rst:h-6 rst:w-6",sm:"rst:h-8 rst:w-8",md:"rst:h-10 rst:w-10",lg:"rst:h-12 rst:w-12",xl:"rst:h-16 rst:w-16"},p={sm:"rst:rounded-sm",md:"rst:rounded-md",lg:"rst:rounded-lg",xl:"rst:rounded-xl",full:"rst:rounded-full"},v="rst:rounded-skeleton",y={shimmer:"rst:animate-skeleton rst:forced-colors:outline-solid rst:forced-colors:outline-1",pulse:"rst:animate-skeleton-pulse rst:forced-colors:outline-solid rst:forced-colors:outline-1 rst:forced-colors:animate-none",none:"rst:skeleton-fill rst:forced-colors:outline-solid rst:forced-colors:outline-1"},S=d.createContext(void 0);function C({shape:e="line",size:s,lines:a=1,width:r,height:h,radius:n,animation:x,className:o}){const f=d.useContext(S),l=y[x??f??"shimmer"];if(e==="circle"){const c=s&&s in j?s:"md";return t.jsx("span",{"aria-hidden":"true",className:i.cn("rst:block rst:shrink-0 rst:rounded-full rst:border rst:border-transparent",j[c],l,o),"data-skeleton":"circle"})}if(e==="block")return t.jsx("span",{"aria-hidden":"true",className:i.cn("rst:block rst:w-full",n?p[n]:v,l,o),style:{height:h??"8rem",width:r},"data-skeleton":"block"});const k=s&&s in b?s:"md",u=Math.max(1,Math.floor(a)),g=c=>{const m=c===u-1;return t.jsxs("span",{className:i.cn("rst:relative rst:block",u>1&&!m&&"rst:w-full"),style:{width:u>1?m?r??"60%":void 0:r},children:[" ",t.jsx("span",{className:i.cn("rst:absolute rst:inset-x-0 rst:top-1/2 rst:h-[0.7em] rst:-translate-y-1/2",p[n??"sm"],l)})]},c)};return t.jsx("span",{"aria-hidden":"true",className:i.cn("rst:block",b[k],o),"data-skeleton":"line",children:Array.from({length:u},(c,m)=>g(m))})}function I({loading:e,skeleton:s,label:a="Loading",announce:r=!0,animation:h,children:n,className:x}){const o=d.useContext(S),[f,l]=d.useState("");return d.useEffect(()=>{const k=setTimeout(()=>l(e&&r?a:""),e?50:0);return()=>clearTimeout(k)},[e,a,r]),t.jsxs(t.Fragment,{children:[t.jsx("span",{role:"status",className:"rst:sr-only","data-skeleton-status":"",children:f}),t.jsx("div",{"aria-busy":e||void 0,className:x,"data-skeleton-region":e?"loading":"ready",children:e?t.jsx(S.Provider,{value:h??o,children:t.jsx("div",{"aria-hidden":"true",children:s})}):n})]})}exports.Skeleton=C;exports.SkeletonRegion=I;
@@ -24,6 +24,8 @@ declare const RADIUS: {
24
24
  readonly xl: "rst:rounded-xl";
25
25
  readonly full: "rst:rounded-full";
26
26
  };
27
+ /** How a skeleton moves while it waits. */
28
+ export type SkeletonAnimation = "shimmer" | "pulse" | "none";
27
29
  export interface SkeletonProps {
28
30
  /**
29
31
  * - `"line"`: a line of text, one line box tall at its `size`.
@@ -42,8 +44,20 @@ export interface SkeletonProps {
42
44
  width?: CSSProperties["width"];
43
45
  /** A block's height. */
44
46
  height?: CSSProperties["height"];
45
- /** A block's corners, from the radius scale. */
47
+ /**
48
+ * A block's corners, from the radius scale. Without it a block reads
49
+ * `--roster-skeleton-radius`, which defaults to `lg`. A line's corners are
50
+ * `sm` unless this says otherwise; a circle is always round.
51
+ */
46
52
  radius?: keyof typeof RADIUS;
53
+ /**
54
+ * `"shimmer"` (the default): a highlight sweeps across the fill. `"pulse"`:
55
+ * the fill fades and returns, at `--roster-skeleton-pulse-duration` and down
56
+ * to `--roster-skeleton-pulse-opacity`. `"none"`: the fill holds still.
57
+ * Under reduced motion both movements stop. Inside a `SkeletonRegion`, it
58
+ * defaults to the region's.
59
+ */
60
+ animation?: SkeletonAnimation;
47
61
  className?: string;
48
62
  }
49
63
  /**
@@ -53,7 +67,7 @@ export interface SkeletonProps {
53
67
  * isn't there yet. The region it stands in for says it's busy; use
54
68
  * `SkeletonRegion`, which does that, or set `aria-busy` on it yourself.
55
69
  */
56
- declare function Skeleton({ shape, size, lines, width, height, radius, className }: SkeletonProps): import("react/jsx-runtime").JSX.Element;
70
+ declare function Skeleton({ shape, size, lines, width, height, radius, animation, className }: SkeletonProps): import("react/jsx-runtime").JSX.Element;
57
71
  export interface SkeletonRegionProps {
58
72
  /** While true, the skeleton shows and the region says it's busy. */
59
73
  loading: boolean;
@@ -66,6 +80,8 @@ export interface SkeletonRegionProps {
66
80
  * together, a grid of cards say, or wrap them in one region instead.
67
81
  */
68
82
  announce?: boolean;
83
+ /** How the skeletons inside move, unless one says otherwise: see `Skeleton`. */
84
+ animation?: SkeletonAnimation;
69
85
  children?: ReactNode;
70
86
  className?: string;
71
87
  }
@@ -75,5 +91,5 @@ export interface SkeletonRegionProps {
75
91
  * When `loading` turns false the content replaces the skeleton in the same
76
92
  * box, so a skeleton drawn at the content's size means nothing moves.
77
93
  */
78
- declare function SkeletonRegion({ loading, skeleton, label, announce, children, className }: SkeletonRegionProps): import("react/jsx-runtime").JSX.Element;
94
+ declare function SkeletonRegion({ loading, skeleton, label, announce, animation, children, className }: SkeletonRegionProps): import("react/jsx-runtime").JSX.Element;
79
95
  export { Skeleton, SkeletonRegion };
@@ -1,80 +1,85 @@
1
1
  "use client";
2
- import { jsx as s, jsxs as p, Fragment as g } from "react/jsx-runtime";
3
- import { useState as y, useEffect as S } from "react";
4
- import { cn as a } from "../../../lib/utils.js";
5
- const f = {
2
+ import { jsx as e, jsxs as v, Fragment as g } from "react/jsx-runtime";
3
+ import { useContext as y, useState as w, useEffect as C, createContext as L } from "react";
4
+ import { cn as c } from "../../../lib/utils.js";
5
+ const p = {
6
6
  sm: "rst:text-xs",
7
7
  md: "rst:text-sm",
8
8
  lg: "rst:text-base",
9
9
  inherit: ""
10
- }, k = {
10
+ }, b = {
11
11
  xs: "rst:h-6 rst:w-6",
12
12
  sm: "rst:h-8 rst:w-8",
13
13
  md: "rst:h-10 rst:w-10",
14
14
  lg: "rst:h-12 rst:w-12",
15
15
  xl: "rst:h-16 rst:w-16"
16
- }, x = {
16
+ }, S = {
17
17
  sm: "rst:rounded-sm",
18
18
  md: "rst:rounded-md",
19
19
  lg: "rst:rounded-lg",
20
20
  xl: "rst:rounded-xl",
21
21
  full: "rst:rounded-full"
22
- }, h = "rst:animate-skeleton rst:forced-colors:outline-solid rst:forced-colors:outline-1";
23
- function E({ shape: t = "line", size: r, lines: d = 1, width: e, height: u, radius: c, className: o }) {
22
+ }, E = "rst:rounded-skeleton", N = {
23
+ shimmer: "rst:animate-skeleton rst:forced-colors:outline-solid rst:forced-colors:outline-1",
24
+ pulse: "rst:animate-skeleton-pulse rst:forced-colors:outline-solid rst:forced-colors:outline-1 rst:forced-colors:animate-none",
25
+ none: "rst:skeleton-fill rst:forced-colors:outline-solid rst:forced-colors:outline-1"
26
+ }, x = L(void 0);
27
+ function j({ shape: t = "line", size: r, lines: d = 1, width: s, height: m, radius: o, animation: h, className: n }) {
28
+ const f = y(x), l = N[h ?? f ?? "shimmer"];
24
29
  if (t === "circle") {
25
- const l = r && r in k ? r : "md";
26
- return /* @__PURE__ */ s(
30
+ const i = r && r in b ? r : "md";
31
+ return /* @__PURE__ */ e(
27
32
  "span",
28
33
  {
29
34
  "aria-hidden": "true",
30
- className: a("rst:block rst:shrink-0 rst:rounded-full rst:border rst:border-transparent", k[l], h, o),
35
+ className: c("rst:block rst:shrink-0 rst:rounded-full rst:border rst:border-transparent", b[i], l, n),
31
36
  "data-skeleton": "circle"
32
37
  }
33
38
  );
34
39
  }
35
40
  if (t === "block")
36
- return /* @__PURE__ */ s(
41
+ return /* @__PURE__ */ e(
37
42
  "span",
38
43
  {
39
44
  "aria-hidden": "true",
40
- className: a("rst:block rst:w-full", x[c ?? "lg"], h, o),
41
- style: { height: u ?? "8rem", width: e },
45
+ className: c("rst:block rst:w-full", o ? S[o] : E, l, n),
46
+ style: { height: m ?? "8rem", width: s },
42
47
  "data-skeleton": "block"
43
48
  }
44
49
  );
45
- const m = r && r in f ? r : "md", n = Math.max(1, Math.floor(d)), b = (l) => {
46
- const i = l === n - 1;
47
- return /* @__PURE__ */ p(
50
+ const k = r && r in p ? r : "md", a = Math.max(1, Math.floor(d)), I = (i) => {
51
+ const u = i === a - 1;
52
+ return /* @__PURE__ */ v(
48
53
  "span",
49
54
  {
50
- className: a("rst:relative rst:block", n > 1 && !i && "rst:w-full"),
51
- style: { width: n > 1 ? i ? e ?? "60%" : void 0 : e },
55
+ className: c("rst:relative rst:block", a > 1 && !u && "rst:w-full"),
56
+ style: { width: a > 1 ? u ? s ?? "60%" : void 0 : s },
52
57
  children: [
53
58
  " ",
54
- /* @__PURE__ */ s(
59
+ /* @__PURE__ */ e(
55
60
  "span",
56
61
  {
57
- className: a("rst:absolute rst:inset-x-0 rst:top-1/2 rst:h-[0.7em] rst:-translate-y-1/2", x[c ?? "sm"], h)
62
+ className: c("rst:absolute rst:inset-x-0 rst:top-1/2 rst:h-[0.7em] rst:-translate-y-1/2", S[o ?? "sm"], l)
58
63
  }
59
64
  )
60
65
  ]
61
66
  },
62
- l
67
+ i
63
68
  );
64
69
  };
65
- return /* @__PURE__ */ s("span", { "aria-hidden": "true", className: a("rst:block", f[m], o), "data-skeleton": "line", children: Array.from({ length: n }, (l, i) => b(i)) });
70
+ return /* @__PURE__ */ e("span", { "aria-hidden": "true", className: c("rst:block", p[k], n), "data-skeleton": "line", children: Array.from({ length: a }, (i, u) => I(u)) });
66
71
  }
67
- function L({ loading: t, skeleton: r, label: d = "Loading", announce: e = !0, children: u, className: c }) {
68
- const [o, m] = y("");
69
- return S(() => {
70
- const n = setTimeout(() => m(t && e ? d : ""), t ? 50 : 0);
71
- return () => clearTimeout(n);
72
- }, [t, d, e]), /* @__PURE__ */ p(g, { children: [
73
- /* @__PURE__ */ s("span", { role: "status", className: "rst:sr-only", "data-skeleton-status": "", children: o }),
74
- /* @__PURE__ */ s("div", { "aria-busy": t || void 0, className: c, "data-skeleton-region": t ? "loading" : "ready", children: t ? /* @__PURE__ */ s("div", { "aria-hidden": "true", children: r }) : u })
72
+ function D({ loading: t, skeleton: r, label: d = "Loading", announce: s = !0, animation: m, children: o, className: h }) {
73
+ const n = y(x), [f, l] = w("");
74
+ return C(() => {
75
+ const k = setTimeout(() => l(t && s ? d : ""), t ? 50 : 0);
76
+ return () => clearTimeout(k);
77
+ }, [t, d, s]), /* @__PURE__ */ v(g, { children: [
78
+ /* @__PURE__ */ e("span", { role: "status", className: "rst:sr-only", "data-skeleton-status": "", children: f }),
79
+ /* @__PURE__ */ e("div", { "aria-busy": t || void 0, className: h, "data-skeleton-region": t ? "loading" : "ready", children: t ? /* @__PURE__ */ e(x.Provider, { value: m ?? n, children: /* @__PURE__ */ e("div", { "aria-hidden": "true", children: r }) }) : o })
75
80
  ] });
76
81
  }
77
82
  export {
78
- E as Skeleton,
79
- L as SkeletonRegion
83
+ j as Skeleton,
84
+ D as SkeletonRegion
80
85
  };
@@ -13,7 +13,7 @@ export interface ThemeToggleProps extends Omit<ButtonProps, "onClick" | "childre
13
13
  * opposite, because a button's name should say what pressing it does.
14
14
  *
15
15
  * Both take nodes, so a product with its own vocabulary for the two states
16
- * ("Press sheet" / "Blueline", "Day" / "Night") can say so.
16
+ * ("Paper" / "Chalkboard", "Day" / "Night") can say so.
17
17
  */
18
18
  lightLabel?: React.ReactNode;
19
19
  /** Visible text when the page is dark, if `showLabel` is set. */
@@ -45,7 +45,7 @@ export interface CheckboxGroupProps extends Omit<React.HTMLAttributes<HTMLFieldS
45
45
  * Caps the options' height and scrolls past it. A number is pixels.
46
46
  *
47
47
  * Applied inline rather than as a class because the useful values here are
48
- * arbitrary — `240` is mega-squad's, yours will differ — and an arbitrary
48
+ * arbitrary — `240` is one app's, yours will differ — and an arbitrary
49
49
  * Tailwind value built from a prop is a class that does not exist at build
50
50
  * time, which is exactly what `check-classes-emit` fails the build over.
51
51
  */
@@ -5,7 +5,7 @@
5
5
  * short list of three options wants to sit on the form the way a Select's
6
6
  * label and trigger do, with no chrome of its own.
7
7
  *
8
- * `panel` is the long-list case — the grouped, scrollable list mega-squad
8
+ * `panel` is the long-list case — the grouped, scrollable list one app
9
9
  * hand-rolled for its sports picker. It deliberately does NOT read
10
10
  * `--roster-popover-*`: that family is documented as "a panel that floats over
11
11
  * the page", and this one is in the flow.
@@ -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;