@sableui/react 0.1.0 → 0.2.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 (61) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/{_contract-CYScOdf5.d.ts → _contract-o3tUc8Ig.d.ts} +1 -1
  3. package/dist/{chunk-J23ELQQZ.js → chunk-AOWBYEQ6.js} +4177 -1917
  4. package/dist/{chunk-RWP4XBSK.js → chunk-KFZ43RKU.js} +1 -1
  5. package/dist/{chunk-JI7WJP2L.js → chunk-X276MF2W.js} +5 -0
  6. package/dist/foundations/index.js +1 -1
  7. package/dist/index.d.ts +155 -101
  8. package/dist/index.js +582 -2778
  9. package/dist/ios/index.d.ts +6 -1
  10. package/dist/ios/index.js +4 -4
  11. package/dist/ios/preview.js +1 -1
  12. package/dist/ios/styles.css +20 -6
  13. package/dist/patterns/index.js +1683 -469
  14. package/dist/presets/index.d.ts +1 -1
  15. package/dist/presets/index.js +1 -1
  16. package/dist/styles.css +238 -40
  17. package/dist/{theme-provider-BWcizhAt.d.ts → theme-provider-BE-KqUG-.d.ts} +1 -1
  18. package/dist/themes/atelier/atelier.css +3 -0
  19. package/dist/themes/atelier/index.d.ts +1 -1
  20. package/dist/themes/base/base.css +11 -4
  21. package/dist/themes/base/index.d.ts +1 -1
  22. package/dist/themes/ember/ember.css +3 -0
  23. package/dist/themes/ember/index.d.ts +1 -1
  24. package/dist/themes/index.d.ts +2 -2
  25. package/dist/themes/meridian/index.d.ts +1 -1
  26. package/dist/themes/meridian/meridian.css +10 -5
  27. package/dist/themes/poppy/index.d.ts +1 -1
  28. package/dist/themes/poppy/poppy.css +16 -0
  29. package/dist/themes/prism/index.d.ts +1 -1
  30. package/dist/themes/prism/prism.css +4 -1
  31. package/dist/themes/verdant/index.d.ts +1 -1
  32. package/dist/themes/verdant/verdant.css +7 -2
  33. package/llms/components/bubble.md +5 -5
  34. package/llms/components/checkbox-card.md +1 -1
  35. package/llms/components/context-card.md +2 -2
  36. package/llms/components/dialog.md +1 -1
  37. package/llms/components/empty-state.md +4 -4
  38. package/llms/components/entity.md +1 -1
  39. package/llms/components/fieldset.md +1 -1
  40. package/llms/components/kbd.md +18 -6
  41. package/llms/components/pill.md +1 -1
  42. package/llms/components/side-nav.md +1 -1
  43. package/llms/components/skeleton.md +1 -1
  44. package/llms/components/theme-selector.md +1 -1
  45. package/llms/foundations/shape.md +1 -1
  46. package/llms/patterns/auth-page.md +132 -42
  47. package/llms/patterns/brand-landing-page.md +271 -175
  48. package/llms/patterns/chat-panel.md +120 -39
  49. package/llms/patterns/dashboard-page.md +277 -65
  50. package/llms/patterns/feed-page.md +245 -64
  51. package/llms/patterns/landing-page.md +473 -75
  52. package/llms/patterns/list-detail-page.md +385 -68
  53. package/llms/patterns/settings-page.md +279 -67
  54. package/llms/reference/token-intent.md +4 -2
  55. package/llms/reference/utilities.md +20 -5
  56. package/llms-full.txt +2244 -727
  57. package/llms.txt +14 -15
  58. package/package.json +1 -1
  59. package/registry.json +261 -238
  60. package/targets/ios.json +4 -15
  61. package/llms/components/description.md +0 -96
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- export { Phone, phoneMeta } from './chunk-RWP4XBSK.js';
1
+ export { Phone, phoneMeta } from './chunk-KFZ43RKU.js';
2
2
  import './chunk-NEQNXI2G.js';
3
- export { DENSITY_TOKENS, MOTION_TOKENS, RADIUS_TOKENS, SEMANTIC_TOKENS, TYPE_OVERLINE_TOKENS, TYPE_REFINEMENT_TOKENS, TYPE_SCALE_TOKENS, TYPE_UI_TOKENS, WORKSPACE_SURFACE_TOKENS } from './chunk-JI7WJP2L.js';
4
- import { withPortalThemeScope, FIELD_SHELL_XS, Button, useTypeTier, FIELD_SHELL, FIELD_INVALID, trimLabel, useMotion, HighlightGlide, buttonVariants, Input, useAnchoredDensity, anchoredScroll, useIsomorphicLayoutEffect, mergeRefs, Select, SelectTrigger, SelectContent, SelectItem, Spinner, Badge, anchoredListScroll, useResizeObserver, MENU_ITEM_DESTRUCTIVE, Card, DensityProvider, sideNavItemIconVariants, sideNavItemVariants, useScrollActivity, setRef, Menu, MenuTrigger, MenuContent, Textarea, Field, Progress } from './chunk-J23ELQQZ.js';
5
- export { AppHeader, AppShell, Attachment, Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, AvatarRoot, BADGE_APPEARANCES, BADGE_TONES, Badge, BottomNav, BottomNavItem, Bubble, BubbleReaction, BubbleReactions, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardList, CardTitle, ChatComposer, CodeBlock, CodeHighlightProvider, DataTable, Density, DensityProvider, Description, DescriptionDetail, DescriptionItem, DescriptionTerm, Entity, Field, Fieldset, FieldsetContent, FieldsetDescription, FieldsetFooter, FieldsetLegend, Form, Input, Label, Marker, Menu, MenuCheckboxItem, MenuContent, MenuGroup, MenuGroupLabel, MenuItem, MenuPortal, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuSubmenu, MenuSubmenuTrigger, MenuTrigger, Message, MessageScroller, MotionProvider, PageHeader, Pill, Presence, Progress, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue, RelativeTime, Reveal, SearchInput, Select, SelectContent, SelectGroup, SelectGroupLabel, SelectItem, SelectPortal, SelectSeparator, SelectTrigger, SelectValue, Separator, SettingsMenu, SettingsRow, Sidebar, SidebarFooter, SidebarHeader, SidebarItem, SidebarNav, SimpleTooltip, Spinner, StatusDot, StreamingText, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, TextReveal, Textarea, Tooltip, TooltipArrow, TooltipContent, TooltipPortal, TooltipProvider, TooltipTrigger, ViewTransitionName, appHeaderMeta, appShellMeta, attachmentMeta, avatarGroupVariants, avatarMeta, avatarVariants, badgeMeta, badgeVariants, bottomNavMeta, bubbleMeta, buttonMeta, buttonVariants, cardMeta, chatComposerMeta, codeBlockMeta, dataTableMeta, descriptionMeta, descriptionVariants, entityMeta, fieldsetMeta, formMeta, inputMeta, labelMeta, markerMeta, menuMeta, messageMeta, messageScrollerMeta, pageHeaderMeta, pillVariants, progressMeta, progressTrackVariants, relativeTimeMeta, searchInputMeta, selectMeta, separatorMeta, settingsMenuMeta, sidebarMeta, spinnerMeta, startViewTransition, statusDotMeta, statusDotVariants, streamingTextMeta, supportsViewTransitions, switchMeta, tableMeta, textareaMeta, tooltipMeta, useAnchoredDensity, useCopy, useDensity, useInView, useMotion, useReducedMotion, useScrollActivity, useSidebar, useTypeTier } from './chunk-J23ELQQZ.js';
3
+ export { DENSITY_TOKENS, MOTION_TOKENS, RADIUS_TOKENS, SEMANTIC_TOKENS, TYPE_OVERLINE_TOKENS, TYPE_REFINEMENT_TOKENS, TYPE_SCALE_TOKENS, TYPE_UI_TOKENS, WORKSPACE_SURFACE_TOKENS } from './chunk-X276MF2W.js';
4
+ import { withPortalThemeScope, FIELD_SHELL_XS, useTypeTier, useDensity, FIELD_SHELL, MOBILE_FIELD_TIER, FIELD_INVALID, useMotion, HighlightGlide, useAnchoredDensity, anchoredScroll, useIsomorphicLayoutEffect, mergeRefs, buttonVariants, Select, SelectTrigger, SelectContent, SelectItem, Button, Spinner, Badge, anchoredListScroll, useResizeObserver, MENU_ITEM_DESTRUCTIVE, DensityProvider, Input, useScrollActivity, setRef, Dialog, DialogContent, DialogTitle, DialogDescription, Textarea, Progress } from './chunk-AOWBYEQ6.js';
5
+ export { AlertDialog, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, AppHeader, AppShell, Attachment, Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, AvatarRoot, BADGE_APPEARANCES, BADGE_TONES, Badge, BottomNav, BottomNavItem, Browser, Bubble, BubbleReaction, BubbleReactions, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardList, CardTitle, ChatComposer, Code, CodeBlock, CodeHighlightProvider, DataTable, Density, DensityProvider, DestructiveActionModal, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DotsMenu, EmptyState, Entity, Field, Form, Input, Label, Marker, Menu, MenuCheckboxItem, MenuContent, MenuGroup, MenuGroupLabel, MenuItem, MenuPortal, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuSubmenu, MenuSubmenuTrigger, MenuTrigger, Message, MessageScroller, Meter, MeterIndicator, MeterLabel, MeterTrack, MeterValue, MotionProvider, NotificationBadge, PageHeader, Pill, Presence, Progress, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue, RelativeTime, Reveal, SearchInput, Select, SelectContent, SelectGroup, SelectGroupLabel, SelectItem, SelectPortal, SelectSeparator, SelectTrigger, SelectValue, Separator, SettingsMenu, SettingsRow, SideNav, SideNavGroup, SideNavItem, Sidebar, SidebarFooter, SidebarHeader, SidebarItem, SidebarNav, SimpleTooltip, Skeleton, Spinner, StatusDot, StreamingText, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsPanel as TabsContent, TabsList, TabsPanel, TabsTrigger, TextReveal, Textarea, Toolbar, ToolbarButton, ToolbarGroup, ToolbarInput, ToolbarLink, ToolbarSeparator, Tooltip, TooltipArrow, TooltipContent, TooltipPortal, TooltipProvider, TooltipTrigger, ViewTransitionName, appHeaderMeta, appShellMeta, attachmentMeta, avatarGroupVariants, avatarMeta, avatarVariants, badgeMeta, badgeVariants, bottomNavMeta, browserMeta, bubbleMeta, buttonMeta, buttonVariants, cardMeta, chatComposerMeta, codeBlockMeta, codeMeta, dataTableMeta, destructiveActionModalMeta, dialogMeta, dotsMenuMeta, emptyStateMeta, entityMeta, formMeta, inputMeta, labelMeta, markerMeta, menuMeta, messageMeta, messageScrollerMeta, meterIndicatorVariants, meterMeta, meterTrackVariants, notificationBadgeMeta, notificationCountVariants, notificationDotVariants, pageHeaderMeta, pillVariants, progressMeta, progressTrackVariants, relativeTimeMeta, searchInputMeta, selectMeta, separatorMeta, settingsMenuMeta, sideNavMeta, sidebarMeta, skeletonMeta, spinnerMeta, startViewTransition, statusDotMeta, statusDotVariants, streamingTextMeta, supportsViewTransitions, switchMeta, tableMeta, tabsMeta, textareaMeta, toolbarGroupVariants, toolbarMeta, toolbarSeparatorVariants, toolbarVariants, tooltipMeta, useAnchoredDensity, useCopy, useDensity, useInView, useMotion, useReducedMotion, useScrollActivity, useSidebar, useTypeTier } from './chunk-AOWBYEQ6.js';
6
6
  export { AspectBox, Bleed, Center, Col, Columns, Grid, GridOverlay, Page, Region, Row, SafeArea, Spacer, SplitPane, Stack } from './chunk-3IWZYBGH.js';
7
7
  import { cn } from './chunk-SRQWQUTW.js';
8
8
  export { cn } from './chunk-SRQWQUTW.js';
@@ -18,19 +18,14 @@ import './chunk-ADLLDWLW.js';
18
18
  import './chunk-K7RUHUGC.js';
19
19
  import './chunk-YXBOYWBN.js';
20
20
  import './chunk-7MG4SHYX.js';
21
- import { AlertDialog as AlertDialog$1 } from '@base-ui/react/alert-dialog';
22
- import { Dialog as Dialog$1 } from '@base-ui/react/dialog';
23
- import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
24
21
  import { NumberField as NumberField$1 } from '@base-ui/react/number-field';
22
+ import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
25
23
  import * as OtpParts from '@base-ui/react/otp-field';
26
- import { createContext, Fragment, useId, useRef, useState, useContext, useEffect, useLayoutEffect, Children, isValidElement, useMemo, useCallback, useSyncExternalStore } from 'react';
24
+ import { createContext, Fragment, useId, useRef, useState, useEffect, useLayoutEffect, Children, isValidElement, useContext, useMemo, useCallback, useSyncExternalStore } from 'react';
27
25
  import { cva } from 'class-variance-authority';
28
26
  import { Checkbox as Checkbox$1 } from '@base-ui/react/checkbox';
29
- import { Tabs as Tabs$1 } from '@base-ui/react/tabs';
30
- import { Meter as Meter$1 } from '@base-ui/react/meter';
31
27
  import { Toggle as Toggle$1 } from '@base-ui/react/toggle';
32
28
  import { ToggleGroup as ToggleGroup$1 } from '@base-ui/react/toggle-group';
33
- import { Toolbar as Toolbar$1 } from '@base-ui/react/toolbar';
34
29
  import { Radio as Radio$1 } from '@base-ui/react/radio';
35
30
  import { RadioGroup as RadioGroup$1 } from '@base-ui/react/radio-group';
36
31
  import { Popover as Popover$1 } from '@base-ui/react/popover';
@@ -43,7 +38,7 @@ import { Collapsible as Collapsible$1 } from '@base-ui/react/collapsible';
43
38
  import { Slider as Slider$1 } from '@base-ui/react/slider';
44
39
  import { Combobox as Combobox$1 } from '@base-ui/react/combobox';
45
40
  import { ContextMenu as ContextMenu$1 } from '@base-ui/react/context-menu';
46
- import { useRender } from '@base-ui/react/use-render';
41
+ import { Fieldset as Fieldset$1 } from '@base-ui/react/fieldset';
47
42
  import { CheckboxGroup as CheckboxGroup$1 } from '@base-ui/react/checkbox-group';
48
43
  import { PreviewCard } from '@base-ui/react/preview-card';
49
44
 
@@ -364,7 +359,6 @@ var COMPONENT_TAXONOMY = {
364
359
  value within a known range (a reading), which is not loading or progress. */
365
360
  Avatar: ["Data display", "either"],
366
361
  DataTable: ["Data display", "either"],
367
- Description: ["Data display", "either"],
368
362
  Entity: ["Data display", "either"],
369
363
  GlidingHighlight: ["Data display", "either"],
370
364
  Meter: ["Data display", "either"],
@@ -438,420 +432,6 @@ var LABEL_OVERRIDES = {
438
432
  function componentLabel(name) {
439
433
  return LABEL_OVERRIDES[name] ?? name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
440
434
  }
441
- var Dialog = Dialog$1.Root;
442
- var DialogTrigger = Dialog$1.Trigger;
443
- var DialogClose = Dialog$1.Close;
444
- var DialogPortal = withPortalThemeScope(Dialog$1.Portal);
445
- function DialogOverlay({ className, size = "default", ...props }) {
446
- return /* @__PURE__ */ jsx(
447
- Dialog$1.Backdrop,
448
- {
449
- "data-size": size,
450
- className: cn(
451
- "fixed inset-0 z-(--z-overlay) motion-fade",
452
- size === "fullscreen" ? "bg-overlay/95" : "bg-overlay/50",
453
- className
454
- ),
455
- ...props
456
- }
457
- );
458
- }
459
- function DialogContent({
460
- size = "default",
461
- showCloseButton,
462
- scroll,
463
- ...props
464
- }) {
465
- return size === "fullscreen" ? /* @__PURE__ */ jsx(FullscreenContent, { showCloseButton: showCloseButton ?? true, ...props }) : /* @__PURE__ */ jsx(
466
- CardContent2,
467
- {
468
- showCloseButton: showCloseButton ?? false,
469
- scroll: scroll ?? true,
470
- ...props
471
- }
472
- );
473
- }
474
- function FullscreenContent({
475
- className,
476
- children,
477
- showCloseButton,
478
- ...props
479
- }) {
480
- return /* @__PURE__ */ jsxs(DialogPortal, { children: [
481
- /* @__PURE__ */ jsx(DialogOverlay, { size: "fullscreen" }),
482
- /* @__PURE__ */ jsxs(
483
- Dialog$1.Popup,
484
- {
485
- "data-size": "fullscreen",
486
- className: cn(
487
- // No scale pop at full-bleed size — the popup IS the viewport, so a
488
- // scale reads as the whole screen lurching. Fade with the scrim.
489
- "fixed inset-0 z-(--z-overlay) flex items-center justify-center outline-none motion-fade",
490
- className
491
- ),
492
- ...props,
493
- children: [
494
- children,
495
- showCloseButton ? (
496
- // The floating Button: Sable's control over content. Its own
497
- // translucent popover chip and border keep it legible over bright
498
- // or dark media alike, with its hover built in — no restyled
499
- // ghost. It sits at the screen edge, so it clears the notch and
500
- // home-indicator insets.
501
- /* @__PURE__ */ jsx(
502
- Dialog$1.Close,
503
- {
504
- render: /* @__PURE__ */ jsx(
505
- Button,
506
- {
507
- variant: "floating",
508
- size: "icon",
509
- "aria-label": "Close",
510
- className: "absolute right-[max(--spacing(4),env(safe-area-inset-right))] top-[max(--spacing(4),env(safe-area-inset-top))] z-10"
511
- }
512
- ),
513
- children: /* @__PURE__ */ jsx(Icon, { name: "xmark" })
514
- }
515
- )
516
- ) : null
517
- ]
518
- }
519
- )
520
- ] });
521
- }
522
- function CardContent2({
523
- className,
524
- children,
525
- showCloseButton,
526
- scroll,
527
- ...props
528
- }) {
529
- return /* @__PURE__ */ jsxs(DialogPortal, { children: [
530
- /* @__PURE__ */ jsx(DialogOverlay, {}),
531
- /* @__PURE__ */ jsxs(
532
- Dialog$1.Popup,
533
- {
534
- "data-size": "default",
535
- className: cn(
536
- "fixed left-1/2 top-1/2 z-(--z-overlay) flex w-[calc(100%-2rem)] max-w-125 -translate-x-1/2 -translate-y-1/2 flex-col rounded-lg border border-border-floating bg-popover sable-surface-popover p-6 text-popover-foreground shadow-lg outline-none",
537
- // A dialog is viewport-anchored, so it caps against the viewport with
538
- // its own 1rem margin on each side accounted for. Without the cap a
539
- // tall dialog centred on the viewport overflows BOTH edges and the top
540
- // half is unreachable — Base UI locks the page scroll, so there is
541
- // nothing left to scroll. `dvh` so a mobile URL bar counts.
542
- "max-h-[calc(100dvh-2rem)]",
543
- // The close button is pinned over the popup's top-end corner,
544
- // inside the p-6 padding; the header gives way to it so a long
545
- // title wraps before the button instead of running under it.
546
- showCloseButton && "[&_[data-slot=dialog-header]]:pe-8",
547
- "motion-pop",
548
- className
549
- ),
550
- ...props,
551
- children: [
552
- scroll ? /* @__PURE__ */ jsx("div", { className: "-m-2 min-h-0 overflow-y-auto overscroll-contain p-2 scrollbar-quiet", children }) : children,
553
- showCloseButton ? /* @__PURE__ */ jsx(
554
- Dialog$1.Close,
555
- {
556
- render: /* @__PURE__ */ jsx(
557
- Button,
558
- {
559
- variant: "ghost",
560
- size: "icon-sm",
561
- "aria-label": "Close",
562
- className: "absolute end-4 top-4 text-muted-foreground"
563
- }
564
- ),
565
- children: /* @__PURE__ */ jsx(Icon, { name: "xmark" })
566
- }
567
- ) : null
568
- ]
569
- }
570
- )
571
- ] });
572
- }
573
- function DialogTitle({ className, ...props }) {
574
- return /* @__PURE__ */ jsx(
575
- Dialog$1.Title,
576
- {
577
- className: cn("text-title-lg text-popover-foreground", className),
578
- ...props
579
- }
580
- );
581
- }
582
- function DialogDescription({ className, ...props }) {
583
- return /* @__PURE__ */ jsx(
584
- Dialog$1.Description,
585
- {
586
- className: cn("text-body-sm text-muted-foreground", className),
587
- ...props
588
- }
589
- );
590
- }
591
- function DialogHeader({
592
- className,
593
- ...props
594
- }) {
595
- return /* @__PURE__ */ jsx("div", { "data-slot": "dialog-header", className: cn("mb-4 flex flex-col gap-1.5", className), ...props });
596
- }
597
- function DialogFooter({
598
- className,
599
- ...props
600
- }) {
601
- return /* @__PURE__ */ jsx(
602
- "div",
603
- {
604
- className: cn("mt-6 flex items-center justify-end gap-2", className),
605
- ...props
606
- }
607
- );
608
- }
609
- var AlertDialog = AlertDialog$1.Root;
610
- var AlertDialogTrigger = AlertDialog$1.Trigger;
611
- var AlertDialogClose = DialogClose;
612
- var AlertDialogContent = DialogContent;
613
- var AlertDialogOverlay = DialogOverlay;
614
- var AlertDialogTitle = DialogTitle;
615
- var AlertDialogDescription = DialogDescription;
616
- var AlertDialogHeader = DialogHeader;
617
- var AlertDialogFooter = DialogFooter;
618
-
619
- // src/components/dialog/dialog.meta.ts
620
- var dialogMeta = {
621
- name: "Dialog",
622
- summary: 'Modal surface over Base UI Dialog (focus trap, scroll lock, Esc/outside dismissal). Centered on desktop; max-width 500px. size="fullscreen" is the full-bleed media viewer (image, video, Carousel) over a near-opaque scrim.',
623
- keywords: [
624
- "dialog",
625
- "modal",
626
- "overlay",
627
- "confirmation",
628
- "alert",
629
- "form",
630
- "focus trap",
631
- "sheet",
632
- "lightbox",
633
- "fullscreen",
634
- "image viewer",
635
- "media overlay",
636
- "gallery"
637
- ],
638
- usage: {
639
- description: "Use Dialog for focused decisions, short modal forms, confirmations, and content that temporarily blocks the underlying page. Prefer Sheet for side-panel workflows, Popover for lightweight anchored content, and inline sections for content that should remain part of the page.",
640
- bestPractices: [
641
- {
642
- guidance: "do",
643
- description: "Always include DialogTitle and usually DialogDescription so the modal has an accessible name and context."
644
- },
645
- {
646
- guidance: "do",
647
- description: "Keep the footer actions explicit: a safe escape path plus the primary or destructive action."
648
- },
649
- {
650
- guidance: "do",
651
- description: "Use showCloseButton for dismissible informational dialogs; keep destructive confirmations action-led."
652
- },
653
- {
654
- guidance: "do",
655
- description: 'In a destructive confirmation, state the consequence in DialogDescription ("This is permanent.") and reserve the destructive button variant for actions that cannot be undone.'
656
- },
657
- {
658
- guidance: "dont",
659
- description: "Put large multipage workflows in a centered dialog. Use Sheet or a page route when the task needs space."
660
- },
661
- {
662
- guidance: "do",
663
- description: 'Use size="fullscreen" when an image, video, or gallery needs a larger view than the layout can give it. Give the popup an accessible name (aria-label on DialogContent, or a visually hidden DialogTitle) and alt text on the media.'
664
- },
665
- {
666
- guidance: "dont",
667
- description: `Use size="fullscreen" for forms, confirmations, or anything that isn't media. A dialog that happens to need room is a Sheet or a page; the fullscreen affordances (dark scrim, corner close, centred content) all assume something to look at.`
668
- },
669
- {
670
- guidance: "dont",
671
- description: `Hide the close button at size="fullscreen" without another visible way out. A full-bleed viewer that only Esc dismisses is a dead end for anyone who doesn't know the key.`
672
- },
673
- {
674
- guidance: "dont",
675
- description: "Open a dialog for passive status updates. Use Toast, Banner, or InlineStatus instead."
676
- },
677
- {
678
- guidance: "do",
679
- description: 'Use the AlertDialog family for a confirmation that must be answered (discard changes, leave without saving). It looks identical but is role="alertdialog" and a backdrop click does not dismiss it \u2014 only Cancel, Esc, or the action. For irreversible deletes, use DestructiveActionModal, which is built on it.'
680
- }
681
- ],
682
- anatomy: [
683
- {
684
- name: "Trigger",
685
- required: false,
686
- description: "The element that opens the dialog when using the composed trigger pattern."
687
- },
688
- {
689
- name: "Overlay",
690
- required: true,
691
- description: "The dimming backdrop behind the modal content."
692
- },
693
- {
694
- name: "Content",
695
- required: true,
696
- description: "The modal surface that receives focus trap, labelled-by, and described-by wiring."
697
- },
698
- {
699
- name: "Header",
700
- required: true,
701
- description: "Title and optional description that explain the decision or task."
702
- },
703
- {
704
- name: "Footer",
705
- required: false,
706
- description: "Action row for cancel, confirm, submit, or destructive choices."
707
- }
708
- ]
709
- },
710
- related: {
711
- components: ["Sheet", "Popover", "Toast", "Button", "Form", "Note", "Carousel", "Video"],
712
- patterns: ["SettingsPage", "AuthPage"]
713
- },
714
- status: "stable",
715
- props: [
716
- {
717
- name: "Dialog (Root)",
718
- type: "Base UI Dialog.Root props (open, defaultOpen, onOpenChange, modal)",
719
- description: "Controls open state. Uncontrolled by default."
720
- }
721
- ],
722
- variants: {
723
- size: ["default", "fullscreen"]
724
- },
725
- subcomponents: [
726
- {
727
- name: "DialogContent",
728
- type: 'Base UI Popup props + size?: "default" | "fullscreen" + showCloseButton?: boolean + scroll?: boolean + className',
729
- description: 'Backdrop + popup; portals to <body>. Composes DialogOverlay by default. size="fullscreen" is the media viewer: a near-opaque scrim (bg-overlay/95), an edge-to-edge popup that centres its children with no card chrome, motion-fade instead of motion-pop, and a safe-area-aware floating close button (Button variant="floating"); at that size showCloseButton defaults to true and scroll to false, and the media fits itself with max-h-full max-w-full object-contain. At the default size, showCloseButton (defaults to false) renders a built-in close (X) button in the top-end corner, and the header leaves room for it (Icon name="xmark"). The popup caps at the viewport height less a 1rem margin and wraps its children in a scrolling region, so a tall dialog scrolls instead of overflowing a viewport whose own scroll is locked; pass scroll={false} when the dialog owns its internal layout (a pinned header over a scrolling body) and supplies its own min-h-0 scroll region.'
730
- },
731
- {
732
- name: "DialogOverlay",
733
- type: 'Base UI Backdrop props + size?: "default" | "fullscreen" + className',
734
- description: 'The styleable scrim/backdrop \u2014 bg-overlay/50 by default, near-opaque bg-overlay/95 at size="fullscreen". Composed into DialogContent (at the matching size) by default; exported for manual styling.'
735
- },
736
- {
737
- name: "DialogTrigger",
738
- type: "Base UI Dialog.Trigger props (render)",
739
- description: "Element that opens the dialog. Compose via render prop."
740
- },
741
- {
742
- name: "DialogClose",
743
- type: "Base UI Dialog.Close props (render)",
744
- description: "Element that dismisses the dialog. Compose via render prop."
745
- },
746
- {
747
- name: "DialogHeader",
748
- type: 'ComponentProps<"div">',
749
- description: "Top section holding DialogTitle/DialogDescription."
750
- },
751
- {
752
- name: "DialogTitle",
753
- type: "Base UI Dialog.Title props + className",
754
- description: "The dialog's accessible name \u2014 wires aria-labelledby on the popup."
755
- },
756
- {
757
- name: "DialogDescription",
758
- type: "Base UI Dialog.Description props + className",
759
- description: "Muted supporting line; wires aria-describedby."
760
- },
761
- {
762
- name: "DialogFooter",
763
- type: 'ComponentProps<"div">',
764
- description: "Bottom action row (right-aligned buttons)."
765
- },
766
- {
767
- name: "AlertDialog (Root)",
768
- type: "Base UI AlertDialog.Root props (open, defaultOpen, onOpenChange)",
769
- description: 'Root for a confirmation that needs an answer. Always modal; the popup is role="alertdialog" and a click outside does not dismiss \u2014 only an AlertDialogClose, an action that sets open to false, or Esc.'
770
- },
771
- {
772
- name: "AlertDialogTrigger",
773
- type: "Base UI AlertDialog.Trigger props (render)",
774
- description: "Opens the alert dialog."
775
- },
776
- {
777
- name: "AlertDialogContent / AlertDialogOverlay / AlertDialogTitle / AlertDialogDescription / AlertDialogHeader / AlertDialogFooter / AlertDialogClose",
778
- type: "Same props as the matching Dialog part",
779
- description: "The Dialog parts under AlertDialog names \u2014 Base UI shares the popup, backdrop, title, description and close parts between the two, so the surface is identical. Pass initialFocus to AlertDialogContent (a ref to the Cancel button) to open focus on the safe action."
780
- }
781
- ],
782
- tokensUsed: [
783
- "popover",
784
- "popover-foreground",
785
- "border",
786
- "muted-foreground",
787
- "overlay",
788
- "overlay-foreground",
789
- "accent",
790
- "accent-foreground",
791
- "ring"
792
- ],
793
- motion: [
794
- {
795
- slot: "arrival",
796
- recipe: "motion-fade",
797
- part: "DialogOverlay",
798
- why: "The scrim is what tells the reader the page behind is no longer live. Appearing at full opacity in one frame reads as a page navigation \u2014 the surrounding context vanishes instead of receding."
799
- },
800
- {
801
- slot: "arrival",
802
- recipe: "motion-pop",
803
- part: "DialogContent",
804
- why: "The panel scales up as the scrim darkens, so the two read as one event. Overlay and panel arriving on separate timings is the most common way a modal feels cheap."
805
- },
806
- {
807
- slot: "response",
808
- recipe: "motion-interactive",
809
- part: "DialogClose",
810
- why: "The close affordance is a small icon in a corner and is often the only control a reader looks for. Its hover state has to be findable, not just present."
811
- },
812
- {
813
- slot: "transition",
814
- recipe: "motion-pop",
815
- part: "DialogContent",
816
- why: "Exit runs the same recipe in reverse so dismissal is legibly the inverse of opening. A modal that fades out having scaled in leaves the reader unsure whether it closed or navigated away."
817
- },
818
- {
819
- slot: "activity",
820
- recipe: "none",
821
- part: "DialogContent",
822
- why: "The dialog is a container. Work happening inside it is the content's to report \u2014 a dialog-level indicator would claim the whole surface is busy when usually one field is."
823
- }
824
- ],
825
- a11y: [
826
- "Base UI provides focus trapping, scroll lock, aria-modal, and labelled-by wiring (DialogTitle).",
827
- "Content taller than the viewport scrolls inside the dialog (thin scrollbar, contained overscroll) rather than overflowing off screen \u2014 the page behind is scroll-locked, so anything past the edge would be unreachable.",
828
- 'Esc and click-outside dismiss by default. AlertDialog keeps Esc but ignores click-outside, and its popup is role="alertdialog".',
829
- "Animate via data-[starting-style]/data-[ending-style] attributes.",
830
- 'size="fullscreen" has no title chrome, so name the popup yourself: aria-label on DialogContent (or a visually hidden DialogTitle), plus alt text on the media \u2014 the zoomed image is a separate image to assistive technology. Its built-in close button carries aria-label="Close" and clears the safe-area insets (notch, home indicator).'
831
- ],
832
- examples: [
833
- {
834
- label: "Fullscreen media",
835
- code: '<Dialog><DialogTrigger render={<Button variant="secondary">View full size</Button>} /><DialogContent size="fullscreen" aria-label="Studio, 2026"><img src={url} alt="Studio, 2026" className="max-h-full max-w-full object-contain p-8" /></DialogContent></Dialog>'
836
- },
837
- {
838
- label: "Fullscreen gallery",
839
- code: '<Dialog><DialogTrigger render={<Button>Open gallery</Button>} /><DialogContent size="fullscreen" aria-label="Gallery"><Carousel loop dots counter label="Gallery" className="w-full max-w-4xl px-8">{images.map((src) => <img key={src} src={src} alt="" className="size-full object-contain" />)}</Carousel></DialogContent></Dialog>'
840
- },
841
- {
842
- label: "Basic",
843
- code: '<Dialog><DialogTrigger render={<Button>Open</Button>} /><DialogContent><DialogHeader><DialogTitle>Edition details</DialogTitle><DialogDescription>A limited run of 100, stored permanently on Arweave.</DialogDescription></DialogHeader><DialogFooter><DialogClose render={<Button variant="secondary">Close</Button>} /><DialogClose render={<Button>Collect</Button>} /></DialogFooter></DialogContent></Dialog>'
844
- },
845
- {
846
- label: "Settings form",
847
- code: '<Dialog><DialogTrigger render={<Button variant="secondary">Rename collection</Button>} /><DialogContent showCloseButton><DialogHeader><DialogTitle>Rename collection</DialogTitle><DialogDescription>Update the display name shown to collectors.</DialogDescription></DialogHeader><Form><Field label="Collection name"><Input defaultValue="Midnight Archive" /></Field><DialogFooter><DialogClose render={<Button variant="secondary">Cancel</Button>} /><Button type="submit">Save changes</Button></DialogFooter></Form></DialogContent></Dialog>'
848
- },
849
- {
850
- label: "Destructive confirmation",
851
- code: '<AlertDialog><AlertDialogTrigger render={<Button variant="destructive">Delete edition</Button>} /><AlertDialogContent><AlertDialogHeader><AlertDialogTitle>Delete edition?</AlertDialogTitle><AlertDialogDescription>This removes the edition and cannot be undone.</AlertDialogDescription></AlertDialogHeader><AlertDialogFooter><AlertDialogClose render={<Button variant="secondary">Cancel</Button>} /><Button variant="destructive" onClick={deleteEdition}>Delete edition</Button></AlertDialogFooter></AlertDialogContent></AlertDialog>'
852
- }
853
- ]
854
- };
855
435
  function NumberField({
856
436
  className,
857
437
  steppers = true,
@@ -865,10 +445,11 @@ function NumberField({
865
445
  {
866
446
  variant: "ghost",
867
447
  size: "icon-sm",
868
- className: "mx-0.5 self-center text-muted-foreground"
448
+ className: "mx-[calc((var(--control-md)-var(--control-sm)-2px)/2)] shrink-0 self-center text-muted-foreground"
869
449
  }
870
450
  );
871
451
  const tier = useTypeTier();
452
+ const density = useDensity();
872
453
  return /* @__PURE__ */ jsx(NumberField$1.Root, { ...root, children: /* @__PURE__ */ jsxs(
873
454
  NumberField$1.Group,
874
455
  {
@@ -885,6 +466,7 @@ function NumberField({
885
466
  // Tailwind scan; keep in step with lib/field-shell.
886
467
  "has-[input[aria-invalid=true]]:border-destructive has-[input[aria-invalid=true]]:ring-destructive/30",
887
468
  tier.body,
469
+ density === "comfortable" && MOBILE_FIELD_TIER,
888
470
  className
889
471
  ),
890
472
  children: [
@@ -897,9 +479,9 @@ function NumberField({
897
479
  "aria-label": ariaLabel,
898
480
  className: cn(
899
481
  "w-full min-w-0 bg-transparent tabular-nums outline-none",
900
- // Centred between two steppers; left-aligned without them, so a
901
- // column of stepper-less fields reads as a column of values.
902
- steppers ? "px-2 text-center" : "px-2 text-left"
482
+ // Centred between two steppers; left-aligned without them, on
483
+ // Input's px-3 text column so a stack of fields shares one edge.
484
+ steppers ? "px-2 text-center" : "px-3 text-left"
903
485
  )
904
486
  }
905
487
  ),
@@ -1017,7 +599,7 @@ var numberFieldMeta = {
1017
599
  ]
1018
600
  };
1019
601
  var otpExports = Object.fromEntries(Object.entries(OtpParts));
1020
- var Base3 = otpExports.OTPField ?? otpExports.OTPFieldPreview;
602
+ var Base2 = otpExports.OTPField ?? otpExports.OTPFieldPreview;
1021
603
  var defaultSlotLabel = (index, length) => `Character ${index + 1} of ${length}`;
1022
604
  function OtpField({
1023
605
  className,
@@ -1029,7 +611,7 @@ function OtpField({
1029
611
  }) {
1030
612
  const invalid = ariaInvalid === true || ariaInvalid === "true";
1031
613
  return /* @__PURE__ */ jsx(
1032
- Base3.Root,
614
+ Base2.Root,
1033
615
  {
1034
616
  length,
1035
617
  "data-invalid": invalid ? "" : void 0,
@@ -1048,7 +630,7 @@ function OtpField({
1048
630
  }
1049
631
  ) : null,
1050
632
  /* @__PURE__ */ jsx(
1051
- Base3.Input,
633
+ Base2.Input,
1052
634
  {
1053
635
  "aria-label": index === 0 ? void 0 : getSlotLabel(index, length),
1054
636
  "aria-invalid": invalid || void 0,
@@ -1057,7 +639,9 @@ function OtpField({
1057
639
  // The shell Input wears — lib/field-shell — so a slot reads as
1058
640
  // the same field; only the slot-specific states are added here.
1059
641
  FIELD_SHELL,
1060
- "focus-visible:border-ring data-[filled]:border-border-strong",
642
+ // Focus is FIELD_SHELL's ring alone, like every other field — a ring-
643
+ // coloured border under it made a 3px edge only here.
644
+ "data-[filled]:border-border-strong",
1061
645
  FIELD_INVALID
1062
646
  )
1063
647
  }
@@ -1681,6 +1265,12 @@ var SELECTION_CARD_SHELL = [
1681
1265
  "focus-within:outline-2 focus-within:outline-solid focus-within:outline-offset-2 focus-within:outline-ring",
1682
1266
  "data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:border-input"
1683
1267
  ].join(" ");
1268
+ var SELECTION_CARD_LAYOUT = {
1269
+ row: "items-start gap-3 rounded-md",
1270
+ tile: "flex-col gap-3 rounded-lg"
1271
+ };
1272
+ var SELECTION_CARD_TILE_HEAD = "flex w-full items-start justify-between gap-3";
1273
+ var SELECTION_CARD_GLYPH = "shrink-0 text-2xl leading-none motion-interactive";
1684
1274
  var SELECTION_CARD_CHECKED = {
1685
1275
  neutral: "data-[checked]:border-transparent data-[checked]:ring-[1.5px] data-[checked]:ring-inset data-[checked]:ring-primary data-[checked]:bg-muted",
1686
1276
  standard: "data-[checked]:border-transparent data-[checked]:ring-[1.5px] data-[checked]:ring-inset data-[checked]:ring-tone-standard",
@@ -1689,7 +1279,9 @@ var SELECTION_CARD_CHECKED = {
1689
1279
  info: "data-[checked]:border-transparent data-[checked]:ring-[1.5px] data-[checked]:ring-inset data-[checked]:ring-tone-info",
1690
1280
  warning: "data-[checked]:border-transparent data-[checked]:ring-[1.5px] data-[checked]:ring-inset data-[checked]:ring-tone-warning"
1691
1281
  };
1692
- var CHECK_BOX = "flex size-4.5 shrink-0 items-center justify-center rounded-xs border border-input bg-card motion-interactive";
1282
+ var CHECK_BOX = "flex size-4.5 shrink-0 items-center justify-center rounded-xs border border-control-border bg-card motion-interactive";
1283
+ var RADIO_RING = "relative inline-flex size-4.5 shrink-0 rounded-full border border-control-border bg-card motion-interactive";
1284
+ var RADIO_DOT = "absolute left-1/2 top-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full motion-interactive ease-spring data-[starting-style]:scale-0 data-[ending-style]:scale-0";
1693
1285
  var CHECK_MARK_SIZE = "text-[length:--spacing(2.75)]";
1694
1286
  function CheckboxMark({ className }) {
1695
1287
  return /* @__PURE__ */ jsx(
@@ -1703,7 +1295,7 @@ function CheckboxMark({ className }) {
1703
1295
  Icon,
1704
1296
  {
1705
1297
  name: state.indeterminate ? "minus" : "check",
1706
- className: CHECK_MARK_SIZE
1298
+ className: cn(CHECK_MARK_SIZE, "[&_path]:stroke-current [&_path]:[stroke-width:2] [&_path]:[stroke-linejoin:round] [&_path]:[stroke-linecap:round]")
1707
1299
  }
1708
1300
  ) })
1709
1301
  }
@@ -1720,7 +1312,9 @@ function Checkbox({ className, children, ...props }) {
1720
1312
  "data-[checked]:border-primary data-[checked]:bg-primary",
1721
1313
  "data-[indeterminate]:border-primary data-[indeterminate]:bg-primary",
1722
1314
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1723
- "disabled:cursor-not-allowed disabled:opacity-50",
1315
+ // Base UI renders the root as a <span>, which `:disabled` never matches —
1316
+ // the state is the data attribute, as on Switch.
1317
+ "data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50",
1724
1318
  className
1725
1319
  ),
1726
1320
  ...props,
@@ -1854,264 +1448,6 @@ var checkboxMeta = {
1854
1448
  }
1855
1449
  ]
1856
1450
  };
1857
- var Tabs = Tabs$1.Root;
1858
- var TabsVariantContext = createContext("underline");
1859
- function TabsList({ className, variant = "underline", ...props }) {
1860
- return /* @__PURE__ */ jsx(TabsVariantContext.Provider, { value: variant, children: /* @__PURE__ */ jsx(
1861
- Tabs$1.List,
1862
- {
1863
- "data-variant": variant,
1864
- className: cn(
1865
- variant === "underline" ? "inline-flex items-center gap-1 border-b border-border" : "inline-flex items-center gap-1",
1866
- className
1867
- ),
1868
- ...props
1869
- }
1870
- ) });
1871
- }
1872
- function TabsTrigger({ className, children, ...props }) {
1873
- const variant = useContext(TabsVariantContext);
1874
- return /* @__PURE__ */ jsx(
1875
- Tabs$1.Tab,
1876
- {
1877
- className: cn(
1878
- "cursor-pointer text-label-lg text-muted-foreground motion-interactive hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[active]:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
1879
- variant === "underline" ? "-mb-px border-b-2 border-transparent px-3 py-2 data-[active]:border-foreground" : "inline-flex h-(--control-sm) items-center rounded-sm px-3 data-[active]:bg-muted",
1880
- className
1881
- ),
1882
- ...props,
1883
- children: variant === "pill" && typeof children !== "function" ? trimLabel(children) : children
1884
- }
1885
- );
1886
- }
1887
- function TabsPanel({ className, ...props }) {
1888
- return /* @__PURE__ */ jsx(
1889
- Tabs$1.Panel,
1890
- {
1891
- className: cn(
1892
- "pt-4 text-foreground focus-visible:outline-none",
1893
- className
1894
- ),
1895
- ...props
1896
- }
1897
- );
1898
- }
1899
-
1900
- // src/components/tabs/tabs.meta.ts
1901
- var tabsMeta = {
1902
- name: "Tabs",
1903
- summary: "Tabs over Base UI Tabs (roving focus, arrow-key nav, ARIA tablist wiring) in two styles: underline (page sections) or pill (compact switchers). Tabs (Root) + TabsList + TabsTrigger + TabsPanel.",
1904
- status: "stable",
1905
- keywords: [
1906
- "tabs",
1907
- "tablist",
1908
- "panels",
1909
- "segmented content"
1910
- ],
1911
- usage: {
1912
- description: "Use Tabs to switch between related panels within the same page context.",
1913
- bestPractices: [
1914
- {
1915
- guidance: "do",
1916
- description: "Use tabs when each panel is peer content and users may compare or switch often."
1917
- },
1918
- {
1919
- guidance: "do",
1920
- description: "Keep tab labels short and make the active tab unmistakable."
1921
- },
1922
- {
1923
- guidance: "do",
1924
- description: 'Use variant="pill" for a short switcher inside a card or toolbar (Preview / Code, List / Grid); keep the default underline for sections of a page.'
1925
- },
1926
- {
1927
- guidance: "dont",
1928
- description: "Do not use Tabs for unrelated pages or primary navigation that needs distinct URLs."
1929
- },
1930
- {
1931
- guidance: "dont",
1932
- description: "Do not use tabs for sequential steps. Tabs are peer views the user returns to in any order; a process with an order needs a stepper, or the user will skip ahead."
1933
- }
1934
- ],
1935
- anatomy: [
1936
- {
1937
- name: "TabsList",
1938
- required: true,
1939
- description: "The list of tab triggers."
1940
- },
1941
- {
1942
- name: "TabsTrigger",
1943
- required: true,
1944
- description: "One selectable tab."
1945
- },
1946
- {
1947
- name: "TabsPanel",
1948
- required: true,
1949
- description: "The content associated with a trigger."
1950
- },
1951
- {
1952
- name: "TabsContent",
1953
- required: false,
1954
- description: "Compatibility alias for panel content."
1955
- }
1956
- ]
1957
- },
1958
- related: {
1959
- components: [
1960
- "Accordion",
1961
- "BottomNav",
1962
- "SideNav"
1963
- ]
1964
- },
1965
- props: [
1966
- {
1967
- name: "Tabs (Root)",
1968
- type: "Base UI Tabs.Root props (value, defaultValue, onValueChange, orientation)",
1969
- default: "defaultValue: 0",
1970
- description: "Controls the active tab. Uncontrolled via defaultValue; controlled via value + onValueChange."
1971
- }
1972
- ],
1973
- subcomponents: [
1974
- {
1975
- name: "TabsList",
1976
- type: 'Base UI Tabs.List props (activateOnFocus, loopFocus) + variant?: "underline" | "pill" + className',
1977
- description: 'The tablist row. variant="underline" (default) draws a divider rule beneath the triggers; variant="pill" drops the rule and fills the active trigger. Triggers inherit the variant.'
1978
- },
1979
- {
1980
- name: "TabsTrigger",
1981
- type: "Base UI Tabs.Tab props (value, disabled) + className",
1982
- description: "A single tab button. value pairs it with the panel of the same value."
1983
- },
1984
- {
1985
- name: "TabsPanel",
1986
- type: "Base UI Tabs.Panel props (value, keepMounted) + className",
1987
- description: "The content shown when the matching trigger is active."
1988
- },
1989
- {
1990
- name: "TabsContent",
1991
- type: "Alias of TabsPanel",
1992
- description: "Re-export of TabsPanel for consumers that prefer this name."
1993
- }
1994
- ],
1995
- tokensUsed: ["border", "muted", "muted-foreground", "foreground", "ring"],
1996
- a11y: [
1997
- "Base UI provides the tablist/tab/tabpanel roles, aria-selected, and roving tabindex.",
1998
- "Arrow keys move between tabs; Home/End jump to first/last; panels are linked via aria-controls.",
1999
- "Active tab styling reads from the data-active attribute; disabled tabs from data-disabled. Disabled tabs are skipped by arrow-key navigation.",
2000
- "Keyboard focus shows a ring on the focused trigger in both variants; the panel remains focusable."
2001
- ],
2002
- examples: [
2003
- {
2004
- label: "Basic",
2005
- code: '<Tabs defaultValue="overview"><TabsList><TabsTrigger value="overview">Overview</TabsTrigger><TabsTrigger value="activity">Activity</TabsTrigger></TabsList><TabsPanel value="overview">Overview content</TabsPanel><TabsPanel value="activity">Activity content</TabsPanel></Tabs>'
2006
- },
2007
- {
2008
- label: "Pill switcher",
2009
- code: '<Tabs defaultValue="preview"><TabsList variant="pill"><TabsTrigger value="preview">Preview</TabsTrigger><TabsTrigger value="code">Code</TabsTrigger></TabsList><TabsPanel value="preview">Rendered example</TabsPanel><TabsPanel value="code">Source</TabsPanel></Tabs>'
2010
- },
2011
- {
2012
- label: "With a disabled tab",
2013
- code: '<Tabs defaultValue="overview"><TabsList><TabsTrigger value="overview">Overview</TabsTrigger><TabsTrigger value="holders" disabled>Holders</TabsTrigger></TabsList><TabsPanel value="overview">Overview content</TabsPanel></Tabs>'
2014
- }
2015
- ]
2016
- };
2017
- function Skeleton({ className, ...props }) {
2018
- return /* @__PURE__ */ jsx(
2019
- "div",
2020
- {
2021
- className: cn("motion-shimmer rounded-md bg-muted", className),
2022
- ...props
2023
- }
2024
- );
2025
- }
2026
- Skeleton.displayName = "Skeleton";
2027
-
2028
- // src/components/skeleton/skeleton.meta.ts
2029
- var skeletonMeta = {
2030
- name: "Skeleton",
2031
- summary: "Loading placeholder \u2014 a muted, shimmering block (rounded-md bg-muted motion-shimmer). Shape it with utility classes; rounded-full for circles.",
2032
- status: "stable",
2033
- keywords: [
2034
- "skeleton",
2035
- "loading",
2036
- "placeholder",
2037
- "pending"
2038
- ],
2039
- usage: {
2040
- description: "Use Skeleton to reserve space and hint at the shape of content while data is loading.",
2041
- bestPractices: [
2042
- {
2043
- guidance: "do",
2044
- description: "Match the approximate size and rhythm of the eventual content."
2045
- },
2046
- {
2047
- guidance: "do",
2048
- description: "Use it for content regions where layout stability matters during loading."
2049
- },
2050
- {
2051
- guidance: "dont",
2052
- description: "Do not leave Skeleton visible after an error or empty state; switch to EmptyState (tone='destructive' for an error)."
2053
- },
2054
- {
2055
- guidance: "dont",
2056
- description: "Do not use a skeleton whose shape differs from the content that replaces it. A mismatched placeholder causes a visible jump, which is worse than a brief spinner."
2057
- }
2058
- ]
2059
- },
2060
- related: {
2061
- components: [
2062
- "Spinner",
2063
- "PageLoader"
2064
- ]
2065
- },
2066
- props: [
2067
- {
2068
- name: "...props",
2069
- type: 'ComponentPropsWithRef<"div">',
2070
- description: "All native div props plus className. Size and shape the placeholder with utilities (h-*, w-*, size-*, rounded-full)."
2071
- }
2072
- ],
2073
- tokensUsed: ["muted"],
2074
- motion: [
2075
- {
2076
- slot: "arrival",
2077
- recipe: "none",
2078
- part: "Root",
2079
- why: "A skeleton stands in for content that has not arrived; fading the placeholder in adds a wait before the wait."
2080
- },
2081
- {
2082
- slot: "response",
2083
- recipe: "none",
2084
- part: "Root",
2085
- why: "Nothing here is interactive. A skeleton that reacted to the pointer would invite a click on a thing that cannot be clicked."
2086
- },
2087
- {
2088
- slot: "transition",
2089
- recipe: "none",
2090
- part: "Root",
2091
- why: "The skeleton has one state. Swapping it for real content is the parent's job, and the parent decides whether that swap is animated."
2092
- },
2093
- {
2094
- slot: "activity",
2095
- recipe: "motion-shimmer",
2096
- part: "Root",
2097
- why: "The sweep is the entire point of the component: it is what separates 'still loading' from 'loaded, and empty'. A static grey block is indistinguishable from a broken layout, which is why users reload the page."
2098
- }
2099
- ],
2100
- a11y: [
2101
- "Purely presentational; the shimmer sweep is decorative, and it stops under reduced motion because motion-shimmer follows --motion-scale.",
2102
- 'Mark the loading region with aria-busy="true" on a parent, and swap in real content (with the same layout) once loaded to avoid shift.'
2103
- ],
2104
- examples: [
2105
- {
2106
- label: "Shapes",
2107
- code: '<Skeleton className="h-4 w-40" /><Skeleton className="size-10 rounded-full" /><Skeleton className="h-40 w-full" />'
2108
- },
2109
- {
2110
- label: "Post-card composition",
2111
- code: '<div className="flex items-center gap-3"><Skeleton className="size-10 rounded-full" /><div className="space-y-2"><Skeleton className="h-3 w-32" /><Skeleton className="h-3 w-20" /></div></div>'
2112
- }
2113
- ]
2114
- };
2115
1451
 
2116
1452
  // src/components/thinking-orb/thinking-orb.render.ts
2117
1453
  var ORB_PX = { sm: 20, md: 32, lg: 64 };
@@ -2784,485 +2120,115 @@ var glidingHighlightMeta = {
2784
2120
  }
2785
2121
  ]
2786
2122
  };
2787
- var meterIndicatorVariants = cva(
2788
- "h-full rounded-full transition-[width,background-color] duration-(--motion-duration-slow) ease-decelerate",
2123
+ var stepperIndicatorVariants = cva(
2124
+ "relative z-10 flex size-(--control-xs) shrink-0 items-center justify-center rounded-full border text-label-sm motion-interactive",
2789
2125
  {
2790
2126
  variants: {
2791
- tone: {
2792
- primary: "bg-primary",
2793
- standard: "bg-tone-standard",
2794
- success: "bg-success",
2795
- warning: "bg-tone-warning",
2796
- destructive: "bg-destructive"
2127
+ status: {
2128
+ complete: "border-primary bg-primary text-primary-foreground",
2129
+ current: "border-2 border-primary bg-[var(--sable-surface-bg,var(--background))] text-primary",
2130
+ upcoming: "border-border-strong bg-[var(--sable-surface-bg,var(--background))] text-muted-foreground",
2131
+ error: "border-destructive bg-destructive text-destructive-foreground"
2797
2132
  }
2798
2133
  },
2799
2134
  defaultVariants: {
2800
- tone: "primary"
2135
+ status: "upcoming"
2801
2136
  }
2802
2137
  }
2803
2138
  );
2804
- var meterTrackVariants = cva("relative w-full overflow-hidden rounded-full bg-secondary", {
2805
- variants: {
2806
- size: {
2807
- sm: "h-1.5",
2808
- md: "h-2",
2809
- lg: "h-3"
2139
+ var stepperDotVariants = cva(
2140
+ "size-[0.75em] shrink-0 rounded-full motion-interactive",
2141
+ {
2142
+ variants: {
2143
+ status: {
2144
+ complete: "bg-primary",
2145
+ current: "bg-[var(--sable-surface-bg,var(--background))] ring-[0.1875em] ring-primary ring-inset",
2146
+ upcoming: "bg-border-strong",
2147
+ error: "bg-destructive"
2148
+ }
2149
+ },
2150
+ defaultVariants: {
2151
+ status: "upcoming"
2810
2152
  }
2811
- },
2812
- defaultVariants: {
2813
- size: "md"
2814
2153
  }
2815
- });
2816
- var meterRingValueVariants = cva("absolute inset-0 flex items-center justify-center text-foreground", {
2154
+ );
2155
+ var stepperDotConnectorVariants = cva("shrink-0 rounded-full motion-interactive", {
2817
2156
  variants: {
2818
- size: {
2819
- sm: "text-label-xs",
2820
- md: "text-label-md",
2821
- lg: "text-title-sm"
2157
+ orientation: {
2158
+ horizontal: "mt-[calc(0.5lh_-_1px)] h-0.5 min-w-4 flex-1",
2159
+ vertical: "absolute start-[0.375em] top-[0.5lh] -bottom-[0.5lh] w-0.5 -translate-x-1/2 rtl:translate-x-1/2"
2160
+ },
2161
+ filled: {
2162
+ true: "bg-primary",
2163
+ false: "bg-border-strong"
2822
2164
  }
2823
2165
  },
2824
2166
  defaultVariants: {
2825
- size: "md"
2167
+ orientation: "horizontal",
2168
+ filled: false
2826
2169
  }
2827
2170
  });
2828
- var meterRingVariants = cva("relative inline-flex shrink-0", {
2171
+ var stepperConnectorVariants = cva("shrink-0 rounded-full motion-interactive", {
2829
2172
  variants: {
2830
- size: {
2831
- sm: "size-10",
2832
- md: "size-16",
2833
- lg: "size-24"
2173
+ orientation: {
2174
+ horizontal: "mt-[calc(var(--control-xs)/2)] h-px min-w-4 flex-1",
2175
+ vertical: "absolute start-[calc(var(--control-xs)/2_-_0.5px)] top-[calc(var(--control-xs)_+_--spacing(1))] bottom-1 w-px"
2176
+ },
2177
+ filled: {
2178
+ true: "bg-primary",
2179
+ false: "bg-border-strong"
2834
2180
  }
2835
2181
  },
2836
2182
  defaultVariants: {
2837
- size: "md"
2183
+ orientation: "horizontal",
2184
+ filled: false
2838
2185
  }
2839
2186
  });
2840
- var ToneContext = createContext("primary");
2841
- var SizeContext = createContext(void 0);
2842
- var RING_TONE_STROKE = {
2843
- primary: "var(--primary)",
2844
- standard: "var(--tone-standard)",
2845
- success: "var(--success)",
2846
- warning: "var(--tone-warning)",
2847
- destructive: "var(--destructive)"
2187
+ var DEFAULT_STATUS_LABELS = {
2188
+ complete: "Completed",
2189
+ current: "Current step",
2190
+ upcoming: "Not started",
2191
+ error: "Needs attention"
2848
2192
  };
2849
- function MeterRing({
2850
- value,
2851
- min = 0,
2852
- max = 100,
2853
- strokeWidth,
2854
- showValue,
2855
- tone,
2856
- size
2193
+ var StepContext = createContext(null);
2194
+ function Stepper({
2195
+ current = 0,
2196
+ orientation = "horizontal",
2197
+ indicator = "number",
2198
+ statusLabels,
2199
+ className,
2200
+ children,
2201
+ ...props
2857
2202
  }) {
2858
- const ratio = value == null || max === min ? 0 : Math.min(1, Math.max(0, (value - min) / (max - min)));
2859
- const radius = 50 - strokeWidth;
2860
- const circumference = 2 * Math.PI * radius;
2861
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
2862
- /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 100 100", className: "size-full", "aria-hidden": "true", focusable: "false", children: [
2863
- /* @__PURE__ */ jsx("circle", { cx: 50, cy: 50, r: radius, fill: "none", strokeWidth, style: { stroke: "var(--secondary)" } }),
2864
- /* @__PURE__ */ jsx(
2865
- "circle",
2866
- {
2867
- cx: 50,
2868
- cy: 50,
2869
- r: radius,
2870
- fill: "none",
2871
- strokeWidth,
2872
- strokeLinecap: "round",
2873
- strokeDasharray: circumference,
2874
- strokeDashoffset: circumference * (1 - ratio),
2875
- transform: "rotate(-90 50 50)",
2876
- className: "transition-[stroke-dashoffset] duration-(--motion-duration-slow) ease-decelerate",
2877
- style: { stroke: RING_TONE_STROKE[tone] }
2878
- }
2879
- )
2880
- ] }),
2881
- showValue ? /* @__PURE__ */ jsx(MeterValue, { className: meterRingValueVariants({ size }) }) : null
2882
- ] });
2883
- }
2884
- function Meter({ className, children, tone = "primary", shape = "bar", size, strokeWidth = 6, showValue, ...props }) {
2885
- if (shape === "ring") {
2886
- return /* @__PURE__ */ jsx(ToneContext.Provider, { value: tone, children: /* @__PURE__ */ jsx(Meter$1.Root, { className: cn(meterRingVariants({ size }), className), ...props, children: children ?? /* @__PURE__ */ jsx(MeterRing, { value: props.value ?? null, min: props.min, max: props.max, strokeWidth, showValue, tone, size }) }) });
2887
- }
2888
- return /* @__PURE__ */ jsx(ToneContext.Provider, { value: tone, children: /* @__PURE__ */ jsx(SizeContext.Provider, { value: size, children: /* @__PURE__ */ jsx(Meter$1.Root, { className: cn("w-full", className), ...props, children: children ?? /* @__PURE__ */ jsx(MeterTrack, {}) }) }) });
2889
- }
2890
- Meter.displayName = "Meter";
2891
- function MeterTrack({ className, children, size, ...props }) {
2892
- const inherited = useContext(SizeContext);
2203
+ const items = Children.toArray(children).filter(isValidElement);
2204
+ const labels = { ...DEFAULT_STATUS_LABELS, ...statusLabels };
2893
2205
  return /* @__PURE__ */ jsx(
2894
- Meter$1.Track,
2206
+ "ol",
2895
2207
  {
2896
- className: cn(meterTrackVariants({ size: size ?? inherited }), className),
2208
+ "data-orientation": orientation,
2209
+ className: cn(
2210
+ // isolate: the indicators' z-10 (above the connector) stays inside the
2211
+ // stepper instead of rising over sticky headers and nav bars.
2212
+ "isolate flex w-full",
2213
+ orientation === "horizontal" ? "flex-row items-start gap-3" : "flex-col",
2214
+ className
2215
+ ),
2897
2216
  ...props,
2898
- children: children ?? /* @__PURE__ */ jsx(MeterIndicator, {})
2899
- }
2900
- );
2901
- }
2902
- MeterTrack.displayName = "MeterTrack";
2903
- function MeterIndicator({ className, tone, ...props }) {
2904
- const inherited = useContext(ToneContext);
2905
- return /* @__PURE__ */ jsx(
2906
- Meter$1.Indicator,
2907
- {
2908
- className: cn(meterIndicatorVariants({ tone: tone ?? inherited }), className),
2909
- ...props
2910
- }
2911
- );
2912
- }
2913
- MeterIndicator.displayName = "MeterIndicator";
2914
- function MeterLabel({ className, ...props }) {
2915
- return /* @__PURE__ */ jsx(
2916
- Meter$1.Label,
2917
- {
2918
- className: cn("text-label-md text-foreground", className),
2919
- ...props
2920
- }
2921
- );
2922
- }
2923
- MeterLabel.displayName = "MeterLabel";
2924
- function MeterValue({ className, ...props }) {
2925
- return /* @__PURE__ */ jsx(
2926
- Meter$1.Value,
2927
- {
2928
- className: cn("text-mono-sm text-muted-foreground", className),
2929
- ...props
2930
- }
2931
- );
2932
- }
2933
- MeterValue.displayName = "MeterValue";
2934
-
2935
- // src/components/meter/meter.meta.ts
2936
- var meterMeta = {
2937
- name: "Meter",
2938
- summary: "A role=meter reading over Base UI Meter, drawn as a bar or a ring \u2014 how full something is within a known range (storage, quota, strength), with a tone for good/caution/bad. Not task progress.",
2939
- status: "stable",
2940
- keywords: [
2941
- "meter",
2942
- "usage bar",
2943
- "storage",
2944
- "quota",
2945
- "capacity",
2946
- "limit",
2947
- "password strength",
2948
- "level"
2949
- ],
2950
- usage: {
2951
- description: "Use Meter to show a static measurement against a known maximum \u2014 storage used, seats taken, API quota, password strength. It is announced as a meter, not a progress bar, so a screen-reader user hears a level rather than a task underway.",
2952
- bestPractices: [
2953
- {
2954
- guidance: "do",
2955
- description: 'Label it and show the reading in units people use ("7.2 of 10 GB"), with MeterLabel and MeterValue. The bar shows proportion; the number is what gets acted on.'
2956
- },
2957
- {
2958
- guidance: "do",
2959
- description: "Pick `tone` from thresholds you own and apply them consistently across the page \u2014 warning near the limit, destructive at or over it. The component does not guess where good becomes bad."
2960
- },
2961
- {
2962
- guidance: "dont",
2963
- description: "Do not use Meter for a task in flight \u2014 an upload, an install, a step count. That is Progress; Meter tells assistive tech nothing is happening."
2964
- },
2965
- {
2966
- guidance: "dont",
2967
- description: "Do not use it for a value without a meaningful maximum (followers, total spend). A bar implies a full scale; an unbounded count is a stat."
2968
- },
2969
- {
2970
- guidance: "do",
2971
- description: 'Use shape="ring" when the reading is a score that belongs in a small, glanceable spot on a card. It is the same measurement drawn as a circular gauge, not a different component.'
2972
- }
2973
- ],
2974
- anatomy: [
2975
- {
2976
- name: "MeterTrack",
2977
- required: true,
2978
- description: "The rail that represents the full range."
2979
- },
2980
- {
2981
- name: "MeterIndicator",
2982
- required: true,
2983
- description: "The fill, sized from value and coloured by tone."
2984
- },
2985
- {
2986
- name: "MeterLabel",
2987
- required: false,
2988
- description: "The visible name; also names the meter for assistive tech."
2989
- },
2990
- {
2991
- name: "MeterValue",
2992
- required: false,
2993
- description: "The formatted reading."
2994
- }
2995
- ]
2996
- },
2997
- related: {
2998
- components: ["Progress", "Badge"]
2999
- },
3000
- props: [
3001
- {
3002
- name: "value",
3003
- type: "number",
3004
- description: "The current reading, between min and max. Required."
3005
- },
3006
- {
3007
- name: "min",
3008
- type: "number",
3009
- default: "0",
3010
- description: "The bottom of the range."
3011
- },
3012
- {
3013
- name: "max",
3014
- type: "number",
3015
- default: "100",
3016
- description: "The top of the range."
3017
- },
3018
- {
3019
- name: "tone",
3020
- type: '"primary" | "standard" | "success" | "warning" | "destructive"',
3021
- default: '"primary"',
3022
- description: "Fill colour, mapped to a semantic token. Choose it from your own thresholds."
3023
- },
3024
- {
3025
- name: "shape",
3026
- type: '"bar" | "ring"',
3027
- default: '"bar"',
3028
- description: "bar is a horizontal track; ring is a circular gauge for a small, glanceable reading \u2014 the same value, min, max and tones."
3029
- },
3030
- {
3031
- name: "size",
3032
- type: '"sm" | "md" | "lg"',
3033
- default: '"md"',
3034
- description: "Ring diameter (40 / 64 / 96px at the default spacing; the centre value steps with it), or bar thickness on the same sm / md / lg rails as Progress."
3035
- },
3036
- {
3037
- name: "showValue",
3038
- type: "boolean",
3039
- description: "Print the value in the ring's centre (formatted like MeterValue)."
3040
- },
3041
- {
3042
- name: "strokeWidth",
3043
- type: "number",
3044
- default: "6",
3045
- description: "Ring stroke width, in viewBox units of 100."
3046
- },
3047
- {
3048
- name: "format",
3049
- type: "Intl.NumberFormatOptions",
3050
- description: "Formatting for MeterValue and aria-valuetext (percent, unit, currency)."
3051
- },
3052
- {
3053
- name: "getAriaValueText",
3054
- type: "(formatted: string, value: number) => string",
3055
- description: 'Human-readable reading for assistive tech ("7.2 of 10 gigabytes used").'
3056
- },
3057
- {
3058
- name: "locale",
3059
- type: "Intl.LocalesArgument",
3060
- description: "Locale for formatting. Defaults to the runtime locale."
3061
- }
3062
- ],
3063
- subcomponents: [
3064
- {
3065
- name: "MeterTrack",
3066
- type: "Base UI Track props + className + size",
3067
- description: "The rail. Rendered automatically when Meter has no children; renders a MeterIndicator by default."
3068
- },
3069
- {
3070
- name: "MeterIndicator",
3071
- type: "Base UI Indicator props + className + tone",
3072
- description: "The fill. Inherits the Meter's tone unless given its own."
3073
- },
3074
- {
3075
- name: "MeterLabel",
3076
- type: "Base UI Label props + className",
3077
- description: "Visible label; wires aria-labelledby on the root."
3078
- },
3079
- {
3080
- name: "MeterValue",
3081
- type: "Base UI Value props + className",
3082
- description: "Formatted reading. Accepts a `(formatted, value) => ReactNode` child."
3083
- }
3084
- ],
3085
- variants: {
3086
- tone: ["primary", "standard", "success", "warning", "destructive"],
3087
- shape: ["bar", "ring"],
3088
- size: ["sm", "md", "lg"]
3089
- },
3090
- tokensUsed: [
3091
- "secondary",
3092
- "primary",
3093
- "success",
3094
- "tone-warning",
3095
- "destructive",
3096
- "foreground",
3097
- "muted-foreground"
3098
- ],
3099
- motion: [
3100
- {
3101
- slot: "arrival",
3102
- recipe: "none",
3103
- part: "Meter",
3104
- why: "A meter is a reading, present with the page that reports it. Animating the fill up from zero on load would act out a change that did not happen."
3105
- },
3106
- {
3107
- slot: "response",
3108
- recipe: "none",
3109
- part: "Meter",
3110
- why: "It reports and accepts no input. A bar that reacted to the pointer would read as a Slider the user could drag."
3111
- },
3112
- {
3113
- slot: "transition",
3114
- recipe: "custom",
3115
- part: "MeterIndicator",
3116
- why: "Width and colour ease on the motion tokens (no recipe animates width) when the reading changes \u2014 freeing space after a delete visibly shrinks the bar, so the user sees the cause and effect rather than a different bar."
3117
- },
3118
- {
3119
- slot: "activity",
3120
- recipe: "none",
3121
- part: "Meter",
3122
- why: "A meter has no indeterminate state; a reading is either known or not shown. Unknown-but-working is Progress with no value."
3123
- }
3124
- ],
3125
- a11y: [
3126
- "Base UI renders role=meter with aria-valuenow, aria-valuemin, aria-valuemax and aria-valuetext \u2014 distinct from Progress's role=progressbar.",
3127
- "Name it with MeterLabel or aria-label; an unnamed meter is announced as a bare number.",
3128
- 'Tone is colour only. Say the state in text too ("Almost full") when it matters, and use getAriaValueText to give units.'
3129
- ],
3130
- examples: [
3131
- {
3132
- label: "Ring",
3133
- code: '<Meter shape="ring" value={72} showValue aria-label="Storage used" />'
3134
- },
3135
- {
3136
- label: "Bare",
3137
- code: '<Meter value={64} aria-label="Storage used" />'
3138
- },
3139
- {
3140
- label: "Storage with label and reading",
3141
- code: `<Meter value={7.2} max={10} tone="warning" getAriaValueText={(_, v) => \`\${v} of 10 gigabytes used\`}>
3142
- <div className="mb-2 flex flex-wrap items-center justify-between gap-x-3 gap-y-1">
3143
- <MeterLabel>Storage</MeterLabel>
3144
- <MeterValue>{(_, value) => \`\${value} of 10 GB\`}</MeterValue>
3145
- </div>
3146
- <MeterTrack />
3147
- </Meter>`
3148
- },
3149
- {
3150
- label: "Tones",
3151
- code: `<div className="flex flex-col gap-3">
3152
- <Meter value={30} tone="success" aria-label="Seats used" />
3153
- <Meter value={82} tone="warning" aria-label="API quota used" />
3154
- <Meter value={100} tone="destructive" aria-label="Storage used" />
3155
- </div>`
3156
- }
3157
- ]
3158
- };
3159
- var stepperIndicatorVariants = cva(
3160
- "relative z-10 flex size-(--control-xs) shrink-0 items-center justify-center rounded-full border text-label-sm motion-interactive",
3161
- {
3162
- variants: {
3163
- status: {
3164
- complete: "border-primary bg-primary text-primary-foreground",
3165
- current: "border-2 border-primary bg-[var(--sable-surface-bg,var(--background))] text-primary",
3166
- upcoming: "border-border-strong bg-[var(--sable-surface-bg,var(--background))] text-muted-foreground",
3167
- error: "border-destructive bg-destructive text-destructive-foreground"
3168
- }
3169
- },
3170
- defaultVariants: {
3171
- status: "upcoming"
3172
- }
3173
- }
3174
- );
3175
- var stepperDotVariants = cva(
3176
- "size-[0.75em] shrink-0 rounded-full motion-interactive",
3177
- {
3178
- variants: {
3179
- status: {
3180
- complete: "bg-primary",
3181
- current: "bg-[var(--sable-surface-bg,var(--background))] ring-[0.1875em] ring-primary ring-inset",
3182
- upcoming: "bg-border-strong",
3183
- error: "bg-destructive"
3184
- }
3185
- },
3186
- defaultVariants: {
3187
- status: "upcoming"
3188
- }
3189
- }
3190
- );
3191
- var stepperDotConnectorVariants = cva("shrink-0 rounded-full motion-interactive", {
3192
- variants: {
3193
- orientation: {
3194
- horizontal: "mt-[calc(0.5lh_-_1px)] h-0.5 min-w-4 flex-1",
3195
- vertical: "absolute start-[0.375em] top-[0.5lh] -bottom-[0.5lh] w-0.5 -translate-x-1/2 rtl:translate-x-1/2"
3196
- },
3197
- filled: {
3198
- true: "bg-primary",
3199
- false: "bg-border-strong"
3200
- }
3201
- },
3202
- defaultVariants: {
3203
- orientation: "horizontal",
3204
- filled: false
3205
- }
3206
- });
3207
- var stepperConnectorVariants = cva("shrink-0 rounded-full motion-interactive", {
3208
- variants: {
3209
- orientation: {
3210
- horizontal: "mt-[calc(var(--control-xs)/2)] h-px min-w-4 flex-1",
3211
- vertical: "absolute start-[calc(var(--control-xs)/2_-_0.5px)] top-[calc(var(--control-xs)_+_--spacing(1))] bottom-1 w-px"
3212
- },
3213
- filled: {
3214
- true: "bg-primary",
3215
- false: "bg-border-strong"
3216
- }
3217
- },
3218
- defaultVariants: {
3219
- orientation: "horizontal",
3220
- filled: false
3221
- }
3222
- });
3223
- var DEFAULT_STATUS_LABELS = {
3224
- complete: "Completed",
3225
- current: "Current step",
3226
- upcoming: "Not started",
3227
- error: "Needs attention"
3228
- };
3229
- var StepContext = createContext(null);
3230
- function Stepper({
3231
- current = 0,
3232
- orientation = "horizontal",
3233
- indicator = "number",
3234
- statusLabels,
3235
- className,
3236
- children,
3237
- ...props
3238
- }) {
3239
- const items = Children.toArray(children).filter(isValidElement);
3240
- const labels = { ...DEFAULT_STATUS_LABELS, ...statusLabels };
3241
- return /* @__PURE__ */ jsx(
3242
- "ol",
3243
- {
3244
- "data-orientation": orientation,
3245
- className: cn(
3246
- "flex w-full",
3247
- orientation === "horizontal" ? "flex-row items-start gap-3" : "flex-col",
3248
- className
3249
- ),
3250
- ...props,
3251
- children: items.map((child, index) => /* @__PURE__ */ jsx(
3252
- StepContext.Provider,
3253
- {
3254
- value: {
3255
- index,
3256
- count: items.length,
3257
- current,
3258
- orientation,
3259
- indicator,
3260
- statusLabels: labels
3261
- },
3262
- children: child
3263
- },
3264
- child.key ?? index
3265
- ))
2217
+ children: items.map((child, index) => /* @__PURE__ */ jsx(
2218
+ StepContext.Provider,
2219
+ {
2220
+ value: {
2221
+ index,
2222
+ count: items.length,
2223
+ current,
2224
+ orientation,
2225
+ indicator,
2226
+ statusLabels: labels
2227
+ },
2228
+ children: child
2229
+ },
2230
+ child.key ?? index
2231
+ ))
3266
2232
  }
3267
2233
  );
3268
2234
  }
@@ -3588,471 +2554,133 @@ function ToggleGroup({ className, appearance = "default", ...props }) {
3588
2554
  ...props,
3589
2555
  "aria-orientation": props.role === "toolbar" ? props.orientation ?? "horizontal" : void 0
3590
2556
  }
3591
- ) });
3592
- }
3593
- ToggleGroup.displayName = "ToggleGroup";
3594
-
3595
- // src/components/toggle/toggle.meta.ts
3596
- var toggleMeta = {
3597
- name: "Toggle",
3598
- summary: "Pressed-state button over Base UI Toggle, plus ToggleGroup for shared single- or multi-select rows. Pressed styling reads from data-[pressed].",
3599
- status: "stable",
3600
- keywords: [
3601
- "toggle",
3602
- "pressed",
3603
- "button state",
3604
- "toolbar",
3605
- "segmented control",
3606
- "segmented",
3607
- "toggle group",
3608
- "button group",
3609
- "view switcher"
3610
- ],
3611
- usage: {
3612
- description: "Use Toggle for button-like controls that can be pressed on or off within a toolbar or compact control set.",
3613
- bestPractices: [
3614
- {
3615
- guidance: "do",
3616
- description: "Use it for transient UI modes such as bold, preview, pin, or favorite."
3617
- },
3618
- {
3619
- guidance: "do",
3620
- description: "Make the pressed state visually and programmatically clear."
3621
- },
3622
- {
3623
- guidance: "do",
3624
- description: 'Use ToggleGroup appearance="segmented" for a single-select view switch (List / Grid). It is the same capsule ThemeSwitcher wears \u2014 do not hand-build a track around a row of toggles.'
3625
- },
3626
- {
3627
- guidance: "dont",
3628
- description: "Do not use Toggle for persistent application settings; use Switch."
3629
- },
3630
- {
3631
- guidance: "dont",
3632
- description: 'Do not leave an icon-only Toggle without an accessible name. It announces as "button, pressed", which tells a screen-reader user the state but never what the state applies to.'
3633
- }
3634
- ],
3635
- anatomy: [
3636
- {
3637
- name: "Toggle",
3638
- required: true,
3639
- description: "A single pressed or unpressed control."
3640
- },
3641
- {
3642
- name: "ToggleGroup",
3643
- required: false,
3644
- description: "A coordinated group of toggle choices."
3645
- },
3646
- {
3647
- name: "ToggleGroupItem",
3648
- required: false,
3649
- description: 'One toggle inside a group \u2014 an alias of Toggle, so <Toggle value="\u2026"> inside a ToggleGroup is the same thing.'
3650
- }
3651
- ]
3652
- },
3653
- related: {
3654
- components: [
3655
- "Switch",
3656
- "Button",
3657
- "Tabs"
3658
- ]
3659
- },
3660
- props: [
3661
- {
3662
- name: "appearance",
3663
- type: '"default" | "segmented"',
3664
- default: '"default"',
3665
- description: "On ToggleGroup: `default` is a loose row of toggles; `segmented` draws the capsule segmented control (one track, the pressed option filled) shared with ThemeSwitcher. Toggles inside inherit it."
3666
- },
3667
- {
3668
- name: "multiple",
3669
- type: "boolean",
3670
- default: "false",
3671
- description: "On ToggleGroup: allow several toggles pressed at once. The default is single-select, which behaves like a radio group."
3672
- },
3673
- {
3674
- name: "Toggle",
3675
- type: "Base UI Toggle props (pressed, defaultPressed, onPressedChange, disabled, value) + className",
3676
- default: "defaultPressed: false",
3677
- description: "A two-state button. Uncontrolled via defaultPressed; controlled via pressed + onPressedChange. Pass value to identify it inside a ToggleGroup."
3678
- }
3679
- ],
3680
- subcomponents: [
3681
- {
3682
- name: "ToggleGroup",
3683
- type: 'Base UI ToggleGroup props (value, defaultValue, onValueChange, multiple, disabled, orientation, loopFocus) + appearance?: "default" | "segmented" + className',
3684
- description: "Groups child Toggles and shares pressed state across them \u2014 each Toggle inside carries a value. Defaults to multiple: false (single-select) \u2014 the fit for view-mode switches and segmented controls; pass multiple to allow several pressed at once, e.g. a row of formatting toggles."
3685
- },
3686
- {
3687
- name: "ToggleGroupItem",
3688
- type: "Alias of Toggle",
3689
- description: "Re-export of Toggle for group composition \u2014 a Toggle carrying a value is the group item."
3690
- }
3691
- ],
3692
- variants: {
3693
- appearance: ["default", "segmented"]
3694
- },
3695
- tokensUsed: ["muted-foreground", "foreground", "accent", "accent-foreground", "card", "border", "ring"],
3696
- a11y: [
3697
- "Base UI sets aria-pressed on each Toggle and manages the pressed/disabled states.",
3698
- "ToggleGroup provides arrow-key roving focus across its items; loopFocus wraps at the ends.",
3699
- "Pressed styling reads from the data-[pressed] attribute; disabled items are dimmed and non-interactive.",
3700
- "Always supply an aria-label (or visible text) when a Toggle holds only an icon."
3701
- ],
3702
- examples: [
3703
- {
3704
- label: "Standalone",
3705
- code: '<Toggle aria-label="Mute">Mute</Toggle>'
3706
- },
3707
- {
3708
- label: "Single-select group",
3709
- code: '<ToggleGroup defaultValue={["list"]}><Toggle value="list" aria-label="List view"><Icon name="list" /></Toggle><Toggle value="grid" aria-label="Grid view"><Icon name="grid" /></Toggle></ToggleGroup>'
3710
- },
3711
- {
3712
- label: "Segmented view switch",
3713
- code: '<ToggleGroup appearance="segmented" defaultValue={["list"]} aria-label="View"><Toggle value="list" aria-label="List view"><Icon name="list" /></Toggle><Toggle value="grid" aria-label="Grid view"><Icon name="grid" /></Toggle></ToggleGroup>'
3714
- },
3715
- {
3716
- label: "Multi-select group",
3717
- code: '<ToggleGroup multiple defaultValue={["bold"]}><Toggle value="bold">Bold</Toggle><Toggle value="italic">Italic</Toggle></ToggleGroup>'
3718
- }
3719
- ]
3720
- };
3721
- var toolbarVariants = cva(
3722
- "inline-flex items-center gap-1 data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch"
3723
- );
3724
- var toolbarGroupVariants = cva(
3725
- "inline-flex items-center gap-1 data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch"
3726
- );
3727
- var toolbarSeparatorVariants = cva(
3728
- "shrink-0 self-stretch bg-hairline-strong data-[orientation=vertical]:mx-1 data-[orientation=vertical]:my-1.5 data-[orientation=vertical]:w-px data-[orientation=horizontal]:mx-1.5 data-[orientation=horizontal]:my-1 data-[orientation=horizontal]:h-px"
3729
- );
3730
- function Toolbar({ className, ...props }) {
3731
- return /* @__PURE__ */ jsx(Toolbar$1.Root, { className: cn(toolbarVariants(), className), ...props });
3732
- }
3733
- Toolbar.displayName = "Toolbar";
3734
- function ToolbarButton({
3735
- variant = "ghost",
3736
- size = "sm",
3737
- className,
3738
- children,
3739
- ...props
3740
- }) {
3741
- const tier = useTypeTier();
3742
- return /* @__PURE__ */ jsx(
3743
- Toolbar$1.Button,
3744
- {
3745
- className: cn(buttonVariants({ variant, size }), tier.action, className),
3746
- ...props,
3747
- children: trimLabel(children)
3748
- }
3749
- );
3750
- }
3751
- ToolbarButton.displayName = "ToolbarButton";
3752
- function ToolbarLink({
3753
- variant = "ghost",
3754
- size = "sm",
3755
- className,
3756
- children,
3757
- ...props
3758
- }) {
3759
- const tier = useTypeTier();
3760
- return /* @__PURE__ */ jsx(
3761
- Toolbar$1.Link,
3762
- {
3763
- className: cn(buttonVariants({ variant, size }), tier.action, className),
3764
- ...props,
3765
- children: trimLabel(children)
3766
- }
3767
- );
3768
- }
3769
- ToolbarLink.displayName = "ToolbarLink";
3770
- function ToolbarInput({ className, ...props }) {
3771
- return /* @__PURE__ */ jsx(
3772
- Toolbar$1.Input,
3773
- {
3774
- render: /* @__PURE__ */ jsx(Input, {}),
3775
- className: cn("h-(--control-sm) w-40", className),
3776
- ...props
3777
- }
3778
- );
3779
- }
3780
- ToolbarInput.displayName = "ToolbarInput";
3781
- function ToolbarGroup({ className, ...props }) {
3782
- return /* @__PURE__ */ jsx(Toolbar$1.Group, { className: cn(toolbarGroupVariants(), className), ...props });
3783
- }
3784
- ToolbarGroup.displayName = "ToolbarGroup";
3785
- function ToolbarSeparator({ className, ...props }) {
3786
- return /* @__PURE__ */ jsx(Toolbar$1.Separator, { className: cn(toolbarSeparatorVariants(), className), ...props });
2557
+ ) });
3787
2558
  }
3788
- ToolbarSeparator.displayName = "ToolbarSeparator";
2559
+ ToggleGroup.displayName = "ToggleGroup";
3789
2560
 
3790
- // src/components/toolbar/toolbar.meta.ts
3791
- var toolbarMeta = {
3792
- name: "Toolbar",
3793
- summary: `A row or column of controls that is one tab stop, with arrow-key movement between its items (role="toolbar"), over Base UI Toolbar. Items reuse Button's ghost variants and Input, so they follow density like every other control.`,
2561
+ // src/components/toggle/toggle.meta.ts
2562
+ var toggleMeta = {
2563
+ name: "Toggle",
2564
+ summary: "Pressed-state button over Base UI Toggle, plus ToggleGroup for shared single- or multi-select rows. Pressed styling reads from data-[pressed].",
3794
2565
  status: "stable",
3795
2566
  keywords: [
2567
+ "toggle",
2568
+ "pressed",
2569
+ "button state",
3796
2570
  "toolbar",
3797
- "action bar",
3798
- "button row",
3799
- "icon buttons",
3800
- "formatting bar",
3801
- "roving focus",
3802
- "arrow keys",
3803
- "tool chrome",
3804
- "editor toolbar"
2571
+ "segmented control",
2572
+ "segmented",
2573
+ "toggle group",
2574
+ "button group",
2575
+ "view switcher"
3805
2576
  ],
3806
2577
  usage: {
3807
- description: "Use Toolbar when three or more related controls sit together and act on the same thing \u2014 a text selection, a canvas, a document, a table. Tab lands on the toolbar once and the arrow keys move along it, so a keyboard user crosses an editor's twelve buttons in one keystroke instead of twelve. It works in both registers: ghost sm buttons on a page, compact icon-xs buttons inside a Workspace.",
2578
+ description: "Use Toggle for button-like controls that can be pressed on or off within a toolbar or compact control set.",
3808
2579
  bestPractices: [
3809
2580
  {
3810
2581
  guidance: "do",
3811
- description: 'Give every Toolbar an aria-label that names what it acts on ("Text formatting", "Canvas"). With more than one toolbar on screen, that label is the only way a screen-reader user tells them apart.'
3812
- },
3813
- {
3814
- guidance: "do",
3815
- description: 'Use size="icon-sm" (page) or size="icon-xs" (workspace) with an aria-label for icon-only ToolbarButtons, and keep one size across the row so the controls line up.'
2582
+ description: "Use it for transient UI modes such as bold, preview, pin, or favorite."
3816
2583
  },
3817
2584
  {
3818
2585
  guidance: "do",
3819
- description: "Split a long row into ToolbarGroups with a ToolbarSeparator between them \u2014 undo/redo, text style, insert. Groups give the eye the same structure the arrow keys already cross."
2586
+ description: "Make the pressed state visually and programmatically clear."
3820
2587
  },
3821
2588
  {
3822
2589
  guidance: "do",
3823
- description: `Compose triggers and toggles through ToolbarButton's render prop \u2014 render={<MenuTrigger />}, render={<PopoverTrigger />}, or render={<Toggle value="bold" />} inside a ToggleGroup \u2014 so they join the toolbar's arrow-key order.`
3824
- },
3825
- {
3826
- guidance: "dont",
3827
- description: "Put a bare Button, Toggle or Input inside a Toolbar. It renders, but it is not a toolbar item: the arrow keys skip it and it takes its own tab stop, which is the inconsistency Toolbar exists to remove. Use ToolbarButton, ToolbarInput, or render through ToolbarButton."
3828
- },
3829
- {
3830
- guidance: "dont",
3831
- description: "Wrap one or two buttons, or a dialog's Cancel/Confirm pair, in a Toolbar. Those are independent actions; arrow-key movement there is a surprise, not a shortcut. A plain row of Buttons is right."
2590
+ description: 'Use ToggleGroup appearance="segmented" for a single-select view switch (List / Grid). It is the same capsule ThemeSwitcher wears \u2014 do not hand-build a track around a row of toggles.'
3832
2591
  },
3833
2592
  {
3834
2593
  guidance: "dont",
3835
- description: "Use Toolbar for navigation between pages or views. Links that go somewhere are Tabs, SideNav or Breadcrumbs; ToolbarLink is for the odd link that sits among actions, like Help."
2594
+ description: "Do not use Toggle for persistent application settings; use Switch."
3836
2595
  },
3837
2596
  {
3838
2597
  guidance: "dont",
3839
- description: "Give toolbar buttons primary or destructive fills by default. A toolbar is a quiet strip of tools; the ghost default keeps it from competing with the page's one primary action."
2598
+ description: 'Do not leave an icon-only Toggle without an accessible name. It announces as "button, pressed", which tells a screen-reader user the state but never what the state applies to.'
3840
2599
  }
3841
2600
  ],
3842
2601
  anatomy: [
3843
2602
  {
3844
- name: "Toolbar",
3845
- required: true,
3846
- description: 'The root (role="toolbar"). Owns orientation, loopFocus and disabled for everything inside it.'
3847
- },
3848
- {
3849
- name: "ToolbarButton",
2603
+ name: "Toggle",
3850
2604
  required: true,
3851
- description: "An action. Button's ghost variants by default; `render` composes a menu trigger, popover trigger or Toggle into the toolbar."
3852
- },
3853
- {
3854
- name: "ToolbarGroup",
3855
- required: false,
3856
- description: "A run of related items. `disabled` disables the whole run."
3857
- },
3858
- {
3859
- name: "ToolbarSeparator",
3860
- required: false,
3861
- description: "A hairline between groups, oriented across the toolbar. Never a focus stop."
2605
+ description: "A single pressed or unpressed control."
3862
2606
  },
3863
2607
  {
3864
- name: "ToolbarInput",
2608
+ name: "ToggleGroup",
3865
2609
  required: false,
3866
- description: "A compact Input that joins the arrow-key order \u2014 a search or find field in the bar."
2610
+ description: "A coordinated group of toggle choices."
3867
2611
  },
3868
2612
  {
3869
- name: "ToolbarLink",
2613
+ name: "ToggleGroupItem",
3870
2614
  required: false,
3871
- description: "An <a> styled like a ToolbarButton, for a link that sits among the actions."
2615
+ description: 'One toggle inside a group \u2014 an alias of Toggle, so <Toggle value="\u2026"> inside a ToggleGroup is the same thing.'
3872
2616
  }
3873
2617
  ]
3874
2618
  },
3875
2619
  related: {
3876
2620
  components: [
2621
+ "Switch",
3877
2622
  "Button",
3878
- "Toggle",
3879
- "Menu",
3880
- "Popover",
3881
- "Tooltip",
3882
- "Workspace",
3883
- "PanelSection",
3884
- "Separator"
2623
+ "Tabs"
3885
2624
  ]
3886
2625
  },
3887
2626
  props: [
3888
2627
  {
3889
- name: "orientation",
3890
- type: '"horizontal" | "vertical"',
3891
- default: '"horizontal"',
3892
- description: "Which arrow keys move focus (Left/Right or Up/Down) and which way the items stack. Separators turn to match."
3893
- },
3894
- {
3895
- name: "loopFocus",
3896
- type: "boolean",
3897
- default: "true",
3898
- description: "Arrowing past the last item wraps to the first, and back."
2628
+ name: "appearance",
2629
+ type: '"default" | "segmented"',
2630
+ default: '"default"',
2631
+ description: "On ToggleGroup: `default` is a loose row of toggles; `segmented` draws the capsule segmented control (one track, the pressed option filled) shared with ThemeSwitcher. Toggles inside inherit it."
3899
2632
  },
3900
2633
  {
3901
- name: "disabled",
2634
+ name: "multiple",
3902
2635
  type: "boolean",
3903
2636
  default: "false",
3904
- description: "Disables every item. Items stay focusable (aria-disabled) so the row keeps its shape under the keyboard."
3905
- },
3906
- {
3907
- name: "aria-label",
3908
- type: "string",
3909
- description: "Names the toolbar for assistive technology. Always set it."
2637
+ description: "On ToggleGroup: allow several toggles pressed at once. The default is single-select, which behaves like a radio group."
3910
2638
  },
3911
2639
  {
3912
- name: "className",
3913
- type: "string",
3914
- description: "Layout overrides \u2014 e.g. `flex-1` to let the toolbar fill a bar."
2640
+ name: "Toggle",
2641
+ type: "Base UI Toggle props (pressed, defaultPressed, onPressedChange, disabled, value) + className",
2642
+ default: "defaultPressed: false",
2643
+ description: "A two-state button. Uncontrolled via defaultPressed; controlled via pressed + onPressedChange. Pass value to identify it inside a ToggleGroup."
3915
2644
  }
3916
2645
  ],
3917
2646
  subcomponents: [
3918
2647
  {
3919
- name: "ToolbarButton",
3920
- type: '{ variant?: buttonVariants variant (default "ghost"); size?: buttonVariants size (default "sm"); disabled?: boolean; focusableWhenDisabled?: boolean (default true); render?: ReactElement } + Base UI Toolbar.Button props',
3921
- description: 'A toolbar action, styled exactly like <Button>. Disabled buttons stay focusable by default and carry aria-disabled. Pass size="icon-sm"/"icon-xs" plus aria-label for icon-only.'
3922
- },
3923
- {
3924
- name: "ToolbarLink",
3925
- type: '{ href?: string; variant?: buttonVariants variant (default "ghost"); size?: buttonVariants size (default "sm"); render?: ReactElement } + Base UI Toolbar.Link props',
3926
- description: "An <a> in the toolbar's arrow-key order, styled like a ToolbarButton."
3927
- },
3928
- {
3929
- name: "ToolbarInput",
3930
- type: "{ disabled?: boolean; focusableWhenDisabled?: boolean (default true) } + Base UI Toolbar.Input / native input props",
3931
- description: "Sable's Input at the sm control height (w-40 by default; override with className). Arrow keys leave the field so it does not trap the row."
3932
- },
3933
- {
3934
- name: "ToolbarGroup",
3935
- type: "{ disabled?: boolean } + Base UI Toolbar.Group props",
3936
- description: "Groups related items; `disabled` disables all of them."
2648
+ name: "ToggleGroup",
2649
+ type: 'Base UI ToggleGroup props (value, defaultValue, onValueChange, multiple, disabled, orientation, loopFocus) + appearance?: "default" | "segmented" + className',
2650
+ description: "Groups child Toggles and shares pressed state across them \u2014 each Toggle inside carries a value. Defaults to multiple: false (single-select) \u2014 the fit for view-mode switches and segmented controls; pass multiple to allow several pressed at once, e.g. a row of formatting toggles."
3937
2651
  },
3938
2652
  {
3939
- name: "ToolbarSeparator",
3940
- type: "Base UI Toolbar.Separator props + className",
3941
- description: `A hairline-strong rule across the toolbar's axis, exposed as role="separator" and skipped by the arrow keys.`
2653
+ name: "ToggleGroupItem",
2654
+ type: "Alias of Toggle",
2655
+ description: "Re-export of Toggle for group composition \u2014 a Toggle carrying a value is the group item."
3942
2656
  }
3943
2657
  ],
3944
2658
  variants: {
3945
- orientation: ["horizontal", "vertical"],
3946
- variant: ["ghost", "secondary", "outline", "primary", "destructive", "link"],
3947
- size: ["xs", "sm", "md", "icon", "icon-sm", "icon-xs"]
2659
+ appearance: ["default", "segmented"]
3948
2660
  },
3949
- tokensUsed: ["foreground", "accent", "accent-foreground", "ring", "hairline-strong", "input", "card"],
3950
- motion: [
3951
- {
3952
- slot: "arrival",
3953
- recipe: "none",
3954
- part: "Toolbar",
3955
- why: "A toolbar is fixed chrome. It is present when its surface is; animating it in would leave the tools briefly unusable for no reason the user can see."
3956
- },
2661
+ tokensUsed: ["muted-foreground", "foreground", "accent", "accent-foreground", "card", "border", "ring"],
2662
+ a11y: [
2663
+ "Base UI sets aria-pressed on each Toggle and manages the pressed/disabled states.",
2664
+ "ToggleGroup provides arrow-key roving focus across its items; loopFocus wraps at the ends.",
2665
+ "Pressed styling reads from the data-[pressed] attribute; disabled items are dimmed and non-interactive.",
2666
+ "Always supply an aria-label (or visible text) when a Toggle holds only an icon."
2667
+ ],
2668
+ examples: [
3957
2669
  {
3958
- slot: "response",
3959
- recipe: "motion-interactive",
3960
- part: "ToolbarButton",
3961
- why: "Inherited from buttonVariants: hover fill, focus ring and disabled dim ease together, so the item under the arrow keys reads as the one that moved."
2670
+ label: "Standalone",
2671
+ code: '<Toggle aria-label="Mute">Mute</Toggle>'
3962
2672
  },
3963
2673
  {
3964
- slot: "response",
3965
- recipe: "motion-press",
3966
- part: "ToolbarButton",
3967
- why: "Inherited from buttonVariants. Many toolbar actions change something off to the side (the canvas, the selection), so the settle on press is the confirmation that lands where the pointer is."
2674
+ label: "Single-select group",
2675
+ code: '<ToggleGroup defaultValue={["list"]}><Toggle value="list" aria-label="List view"><Icon name="list" /></Toggle><Toggle value="grid" aria-label="Grid view"><Icon name="grid" /></Toggle></ToggleGroup>'
3968
2676
  },
3969
2677
  {
3970
- slot: "transition",
3971
- recipe: "none",
3972
- part: "Toolbar",
3973
- why: "The toolbar has no state of its own. Pressed state belongs to a Toggle rendered through ToolbarButton, which carries its own transition."
2678
+ label: "Segmented view switch",
2679
+ code: '<ToggleGroup appearance="segmented" defaultValue={["list"]} aria-label="View"><Toggle value="list" aria-label="List view"><Icon name="list" /></Toggle><Toggle value="grid" aria-label="Grid view"><Icon name="grid" /></Toggle></ToggleGroup>'
3974
2680
  },
3975
2681
  {
3976
- slot: "activity",
3977
- recipe: "none",
3978
- part: "Toolbar",
3979
- why: "A toolbar never works on its own. Pending work belongs to the surface the action targets, or to a Spinner in the button that started it."
3980
- }
3981
- ],
3982
- a11y: [
3983
- 'Base UI renders role="toolbar" with aria-orientation, and a roving tabindex: the toolbar is one tab stop, the arrow keys move between items, Home/End jump to the ends.',
3984
- "Disabled items stay focusable by default and announce as disabled (aria-disabled), so arrowing along the row never silently skips a control. Set focusableWhenDisabled={false} on an item to opt out.",
3985
- `ToolbarSeparator is role="separator" with the orientation opposite the toolbar's, and is not a focus stop.`,
3986
- "ToolbarInput lets the arrow keys move the caret and only leaves the field at either end of its text.",
3987
- "Always set aria-label on the Toolbar, and on every icon-only ToolbarButton.",
3988
- "A Toggle rendered through ToolbarButton keeps aria-pressed; a MenuTrigger keeps aria-haspopup/aria-expanded."
3989
- ],
3990
- examples: [
3991
- {
3992
- label: "Formatting toolbar",
3993
- code: `<Toolbar aria-label="Text formatting">
3994
- <ToolbarGroup aria-label="History">
3995
- <ToolbarButton size="icon-sm" aria-label="Undo">
3996
- <Icon name="arrow-left" />
3997
- </ToolbarButton>
3998
- <ToolbarButton size="icon-sm" aria-label="Redo">
3999
- <Icon name="arrow-right" />
4000
- </ToolbarButton>
4001
- </ToolbarGroup>
4002
- <ToolbarSeparator />
4003
- <ToggleGroup multiple aria-label="Style">
4004
- <ToolbarButton size="icon-sm" aria-label="Bold" render={<Toggle value="bold" />}>
4005
- <Icon name="bold" />
4006
- </ToolbarButton>
4007
- <ToolbarButton size="icon-sm" aria-label="Italic" render={<Toggle value="italic" />}>
4008
- <Icon name="italic" />
4009
- </ToolbarButton>
4010
- </ToggleGroup>
4011
- <ToolbarSeparator />
4012
- <ToolbarLink href="/help">Help</ToolbarLink>
4013
- </Toolbar>`
4014
- },
4015
- {
4016
- label: "With a menu trigger and a search field",
4017
- code: `<Toolbar aria-label="Table">
4018
- <ToolbarButton>Export</ToolbarButton>
4019
- <Menu>
4020
- <ToolbarButton render={<MenuTrigger />}>View</ToolbarButton>
4021
- <MenuContent>
4022
- <MenuItem>Compact rows</MenuItem>
4023
- <MenuItem>Comfortable rows</MenuItem>
4024
- </MenuContent>
4025
- </Menu>
4026
- <ToolbarSeparator />
4027
- <ToolbarInput aria-label="Search rows" placeholder="Search" />
4028
- </Toolbar>`
4029
- },
4030
- {
4031
- label: "Vertical tool rail",
4032
- code: `<Toolbar orientation="vertical" aria-label="Tools">
4033
- <ToolbarButton size="icon-sm" aria-label="Select">
4034
- <Icon name="arrow-up" />
4035
- </ToolbarButton>
4036
- <ToolbarButton size="icon-sm" aria-label="Draw">
4037
- <Icon name="edit" />
4038
- </ToolbarButton>
4039
- <ToolbarSeparator />
4040
- <ToolbarButton size="icon-sm" aria-label="Delete" disabled>
4041
- <Icon name="trash" />
4042
- </ToolbarButton>
4043
- </Toolbar>`
4044
- },
4045
- {
4046
- label: "In a workspace bar",
4047
- code: `<WorkspaceBar>
4048
- <Toolbar aria-label="Document" className="flex-1">
4049
- <ToolbarButton size="xs">File</ToolbarButton>
4050
- <ToolbarButton size="xs">Edit</ToolbarButton>
4051
- <ToolbarButton size="icon-xs" aria-label="Share" className="ms-auto">
4052
- <Icon name="share" />
4053
- </ToolbarButton>
4054
- </Toolbar>
4055
- </WorkspaceBar>`
2682
+ label: "Multi-select group",
2683
+ code: '<ToggleGroup multiple defaultValue={["bold"]}><Toggle value="bold">Bold</Toggle><Toggle value="italic">Italic</Toggle></ToggleGroup>'
4056
2684
  }
4057
2685
  ]
4058
2686
  };
@@ -4073,14 +2701,14 @@ function Radio({ className, ...props }) {
4073
2701
  render: /* @__PURE__ */ jsx("button", { type: "button" }),
4074
2702
  nativeButton: true,
4075
2703
  className: cn(
4076
- "relative inline-flex size-[18px] shrink-0 rounded-full border border-input bg-card motion-interactive",
2704
+ RADIO_RING,
4077
2705
  "data-[checked]:border-primary",
4078
2706
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
4079
2707
  "disabled:cursor-not-allowed disabled:opacity-50",
4080
2708
  className
4081
2709
  ),
4082
2710
  ...props,
4083
- children: /* @__PURE__ */ jsx(Radio$1.Indicator, { className: "absolute left-1/2 top-1/2 size-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary data-[starting-style]:scale-0 data-[ending-style]:scale-0 motion-interactive ease-spring" })
2711
+ children: /* @__PURE__ */ jsx(Radio$1.Indicator, { className: cn(RADIO_DOT, "bg-primary") })
4084
2712
  }
4085
2713
  );
4086
2714
  }
@@ -4449,7 +3077,7 @@ function SheetContent({
4449
3077
  const popupRef = useRef(null);
4450
3078
  const mergedRef = useMemo(() => mergeRefs(popupRef, ref), [ref]);
4451
3079
  return /* @__PURE__ */ jsxs(SheetPortal, { children: [
4452
- /* @__PURE__ */ jsx(Drawer.Backdrop, { className: "fixed inset-0 z-(--z-overlay) bg-overlay/50 opacity-[calc(1_-_var(--drawer-swipe-progress))] motion-fade data-[swiping]:transition-none" }),
3080
+ /* @__PURE__ */ jsx(Drawer.Backdrop, { className: "fixed inset-0 z-(--z-overlay) bg-overlay/(--scrim-alpha) opacity-[calc(1_-_var(--drawer-swipe-progress))] motion-fade data-[swiping]:transition-none" }),
4453
3081
  /* @__PURE__ */ jsx(Drawer.Viewport, { className: "fixed inset-0 z-(--z-overlay)", children: /* @__PURE__ */ jsx(
4454
3082
  Drawer.Popup,
4455
3083
  {
@@ -4683,261 +3311,69 @@ var pillMeta = {
4683
3311
  ]
4684
3312
  },
4685
3313
  related: {
4686
- components: [
4687
- "Badge",
4688
- "ToggleGroup"
4689
- ]
4690
- },
4691
- props: [
4692
- {
4693
- name: "selected",
4694
- type: "boolean",
4695
- default: "false",
4696
- description: "Marks the pill as chosen. Solid lifts onto the primary surface; outline takes a primary tint and soft primary edge. Sets aria-pressed."
4697
- },
4698
- {
4699
- name: "appearance",
4700
- type: '"solid" | "outline"',
4701
- default: '"solid"',
4702
- description: "solid for a filter row; outline for quiet chips that annotate other content (a reaction under a message, a tag on a card), where a filled chip would compete with what it annotates."
4703
- },
4704
- {
4705
- name: "size",
4706
- type: '"md" | "sm"',
4707
- default: '"md"',
4708
- description: "sm sits on the density ladder's xs control height, for chips under or inside other content."
4709
- },
4710
- {
4711
- name: "icon",
4712
- type: "ReactNode",
4713
- description: 'Optional leading glyph (e.g. <Icon name="lock" />), rendered before the children with the base gap.'
4714
- }
4715
- ],
4716
- variants: {
4717
- appearance: ["solid", "outline"],
4718
- size: ["md", "sm"],
4719
- selected: ["true", "false"]
4720
- },
4721
- tokensUsed: [
4722
- "secondary",
4723
- "secondary-foreground",
4724
- "accent",
4725
- "accent-foreground",
4726
- "primary",
4727
- "primary-foreground",
4728
- "card",
4729
- "border",
4730
- "muted-foreground",
4731
- "foreground"
4732
- ],
4733
- a11y: [
4734
- 'Renders a native <button type="button"> for focus and keyboard activation.',
4735
- "Selected state is exposed via aria-pressed."
4736
- ],
4737
- examples: [
4738
- {
4739
- label: "Filter row",
4740
- code: "<Row gap={1}><Pill selected onClick={() => {}}>All</Pill><Pill onClick={() => {}}>Editions</Pill><Pill onClick={() => {}}>Collectibles</Pill></Row>"
4741
- },
4742
- {
4743
- label: "Outline, small (reactions)",
4744
- code: '<Row gap={1}><Pill appearance="outline" size="sm" selected onClick={() => {}}>\u{1F389} 3</Pill><Pill appearance="outline" size="sm" onClick={() => {}}>\u{1F440}</Pill></Row>'
4745
- },
4746
- {
4747
- label: "With icon",
4748
- code: '<Pill icon={<Icon name="lock" />} onClick={() => {}}>Locked</Pill>'
4749
- }
4750
- ]
4751
- };
4752
- var notificationDotVariants = cva("inline-block rounded-full", {
4753
- variants: {
4754
- tone: {
4755
- destructive: "bg-destructive",
4756
- primary: "bg-primary",
4757
- neutral: "bg-secondary-foreground"
4758
- },
4759
- size: {
4760
- sm: "size-1.5",
4761
- md: "size-2",
4762
- lg: "size-2.5"
4763
- },
4764
- // A dot is small enough to sit inside the corner; a count straddles it.
4765
- overlay: {
4766
- true: "absolute end-1 top-1",
4767
- false: ""
4768
- }
4769
- },
4770
- defaultVariants: {
4771
- tone: "destructive",
4772
- size: "md",
4773
- overlay: false
4774
- }
4775
- });
4776
- var notificationCountVariants = cva(
4777
- "inline-flex items-center justify-center rounded-full px-1 text-label-xs",
4778
- {
4779
- variants: {
4780
- tone: {
4781
- destructive: "bg-destructive text-destructive-foreground",
4782
- primary: "bg-primary text-primary-foreground",
4783
- neutral: "bg-secondary text-secondary-foreground"
4784
- },
4785
- size: {
4786
- sm: "h-4 min-w-4",
4787
- md: "h-4.5 min-w-4.5",
4788
- lg: "h-5 min-w-5"
4789
- },
4790
- overlay: {
4791
- true: "absolute -end-0.5 -top-0.5",
4792
- false: ""
4793
- }
4794
- },
4795
- defaultVariants: {
4796
- tone: "destructive",
4797
- size: "md",
4798
- overlay: false
4799
- }
4800
- }
4801
- );
4802
- function NotificationBadge({
4803
- dot,
4804
- count: count2,
4805
- max = 99,
4806
- tone,
4807
- size,
4808
- overlay = false,
4809
- className,
4810
- ...props
4811
- }) {
4812
- if (dot) {
4813
- return /* @__PURE__ */ jsx(
4814
- "span",
4815
- {
4816
- "data-slot": "notification-badge",
4817
- "aria-hidden": true,
4818
- className: cn(notificationDotVariants({ tone, size, overlay }), className),
4819
- ...props
4820
- }
4821
- );
4822
- }
4823
- if (count2 === void 0 || count2 <= 0) {
4824
- return null;
4825
- }
4826
- const label = count2 > max ? `${max}+` : String(count2);
4827
- return /* @__PURE__ */ jsx(
4828
- "span",
4829
- {
4830
- "data-slot": "notification-badge",
4831
- "aria-hidden": true,
4832
- className: cn(notificationCountVariants({ tone, size, overlay }), className),
4833
- ...props,
4834
- children: trimLabel(label)
4835
- }
4836
- );
4837
- }
4838
- NotificationBadge.displayName = "NotificationBadge";
4839
-
4840
- // src/components/notification-badge/notification-badge.meta.ts
4841
- var notificationBadgeMeta = {
4842
- name: "NotificationBadge",
4843
- summary: `An unread indicator. Renders a bare dot or a count pill (capped as "{max}+"), or nothing when there's nothing to show. overlay pins it to the corner of a relative host; it is aria-hidden, so the host's name carries the count.`,
4844
- status: "stable",
4845
- keywords: [
4846
- "notification",
4847
- "badge",
4848
- "count",
4849
- "unread"
4850
- ],
4851
- usage: {
4852
- description: "Use NotificationBadge to show a compact unread count, alert count, or attention marker.",
4853
- bestPractices: [
4854
- {
4855
- guidance: "do",
4856
- description: "Place it on or near the item that owns the notifications."
4857
- },
4858
- {
4859
- guidance: "do",
4860
- description: "Use compact counts and cap large numbers when exact precision is not useful."
4861
- },
4862
- {
4863
- guidance: "dont",
4864
- description: "Do not use NotificationBadge as a standalone status label or category marker."
4865
- },
4866
- {
4867
- guidance: "dont",
4868
- description: 'Do not show a bare dot when the count is meaningful. A dot says "something"; if the user can act on "how many", show the number.'
4869
- },
4870
- {
4871
- guidance: "dont",
4872
- description: "Do not put a neutral badge on a secondary host. Both are the secondary surface, so the badge disappears; use primary or destructive there."
4873
- }
4874
- ]
4875
- },
4876
- related: {
4877
- components: ["Badge", "StatusDot"]
3314
+ components: [
3315
+ "Badge",
3316
+ "ToggleGroup"
3317
+ ]
4878
3318
  },
4879
3319
  props: [
4880
3320
  {
4881
- name: "dot",
3321
+ name: "selected",
4882
3322
  type: "boolean",
4883
3323
  default: "false",
4884
- description: "Render a bare dot indicator with no number. Takes precedence over count."
4885
- },
4886
- {
4887
- name: "count",
4888
- type: "number",
4889
- description: "Unread count. Renders nothing when <= 0 and dot is unset."
4890
- },
4891
- {
4892
- name: "max",
4893
- type: "number",
4894
- default: "99",
4895
- description: 'Cap before showing "{max}+".'
3324
+ description: "Marks the pill as chosen. Solid lifts onto the primary surface; outline takes a primary tint and soft primary edge. Sets aria-pressed."
4896
3325
  },
4897
3326
  {
4898
- name: "tone",
4899
- type: '"destructive" | "primary" | "neutral"',
4900
- default: '"destructive"',
4901
- description: "Fill color. destructive for alarming counts; primary/neutral for non-alarming ones."
3327
+ name: "appearance",
3328
+ type: '"solid" | "outline"',
3329
+ default: '"solid"',
3330
+ description: "solid for a filter row; outline for quiet chips that annotate other content (a reaction under a message, a tag on a card), where a filled chip would compete with what it annotates."
4902
3331
  },
4903
3332
  {
4904
3333
  name: "size",
4905
- type: '"sm" | "md" | "lg"',
3334
+ type: '"md" | "sm"',
4906
3335
  default: '"md"',
4907
- description: "Scales the dot/pill on the spacing scale (count pill: sm 16px, md 18px, lg 20px at the default density)."
3336
+ description: "md sits on the density ladder's xs control height (28px); sm is 24px, for chips under or inside other content."
4908
3337
  },
4909
3338
  {
4910
- name: "overlay",
4911
- type: "boolean",
4912
- default: "false",
4913
- description: "Pin to the top-end corner of the nearest relative host (an icon button) \u2014 one placement for every caller instead of hand-tuned absolute offsets."
3339
+ name: "icon",
3340
+ type: "ReactNode",
3341
+ description: 'Optional leading glyph (e.g. <Icon name="lock" />), rendered before the children with the base gap.'
4914
3342
  }
4915
3343
  ],
4916
3344
  variants: {
4917
- tone: ["destructive", "primary", "neutral"],
4918
- size: ["sm", "md", "lg"],
4919
- overlay: ["true", "false"]
3345
+ appearance: ["solid", "outline"],
3346
+ size: ["md", "sm"],
3347
+ selected: ["true", "false"]
4920
3348
  },
4921
3349
  tokensUsed: [
4922
- "destructive",
4923
- "destructive-foreground",
3350
+ "secondary",
3351
+ "secondary-foreground",
3352
+ "accent",
3353
+ "accent-foreground",
4924
3354
  "primary",
4925
3355
  "primary-foreground",
4926
- "secondary",
4927
- "secondary-foreground"
3356
+ "card",
3357
+ "border",
3358
+ "muted-foreground",
3359
+ "foreground"
4928
3360
  ],
4929
3361
  a11y: [
4930
- 'The badge is aria-hidden: a count rendered on a control is not associated with it, so the host needs an accessible name that includes the count \u2014 "Notifications, 3 unread" \u2014 or a screen-reader user hears "Notifications, button" and never learns there is anything new. Pass aria-hidden={false} only for a standalone badge whose number is the information.',
4931
- "A dot carries no number and no words: on its own it tells a sighted user only that something changed, and tells everyone else nothing. Pair it with a label that says what is new, and never make its colour the only difference between two states."
3362
+ 'Renders a native <button type="button"> for focus and keyboard activation.',
3363
+ "Selected state is exposed via aria-pressed."
4932
3364
  ],
4933
3365
  examples: [
4934
3366
  {
4935
- label: "Dot + counts",
4936
- code: "<Row gap={3}><NotificationBadge dot /><NotificationBadge count={3} /><NotificationBadge count={128} /></Row>"
3367
+ label: "Filter row",
3368
+ code: "<Row gap={1}><Pill selected onClick={() => {}}>All</Pill><Pill onClick={() => {}}>Editions</Pill><Pill onClick={() => {}}>Collectibles</Pill></Row>"
3369
+ },
3370
+ {
3371
+ label: "Outline, small (reactions)",
3372
+ code: '<Row gap={1}><Pill appearance="outline" size="sm" selected onClick={() => {}}>\u{1F389} 3</Pill><Pill appearance="outline" size="sm" onClick={() => {}}>\u{1F440}</Pill></Row>'
4937
3373
  },
4938
3374
  {
4939
- label: "Overlay on an icon button",
4940
- code: '<Button variant="secondary" size="icon" aria-label="Notifications, 5 unread" className="relative"><Icon name="bell" /><NotificationBadge count={5} overlay /></Button>'
3375
+ label: "With icon",
3376
+ code: '<Pill icon={<Icon name="lock" />} onClick={() => {}}>Locked</Pill>'
4941
3377
  }
4942
3378
  ]
4943
3379
  };
@@ -5004,7 +3440,9 @@ function Calendar({
5004
3440
  // Today is a dot under the number, in the button's own ink — never a
5005
3441
  // ring, which is what keyboard focus draws and would be mistaken for it.
5006
3442
  today: "[&>button]:relative [&>button]:after:absolute [&>button]:after:bottom-1 [&>button]:after:size-1 [&>button]:after:rounded-full [&>button]:after:bg-current",
5007
- outside: "[&>button]:text-muted-foreground [&>button]:opacity-50",
3443
+ // Muted ink alone (4.5:1+): these are live, clickable days. At 50%
3444
+ // opacity they measured 2.2:1 and looked exactly like `disabled`.
3445
+ outside: "[&>button]:text-muted-foreground",
5008
3446
  disabled: "[&>button]:pointer-events-none [&>button]:text-muted-foreground [&>button]:opacity-50",
5009
3447
  hidden: "invisible",
5010
3448
  ...classNames
@@ -5192,6 +3630,8 @@ function DatePicker(props) {
5192
3630
  "aria-required": ariaRequired
5193
3631
  } = props;
5194
3632
  const timeLabelId = useId();
3633
+ const tier = useTypeTier();
3634
+ const density = useDensity();
5195
3635
  const [open, setOpen] = useState(false);
5196
3636
  const [uncontrolled, setUncontrolled] = useState(defaultValue);
5197
3637
  const controlled = "value" in props;
@@ -5255,7 +3695,11 @@ function DatePicker(props) {
5255
3695
  "aria-required": ariaRequired,
5256
3696
  disabled,
5257
3697
  className: cn(
5258
- "inline-flex h-(--control-md) items-center gap-2 ps-3 pe-3 text-start text-body-md text-foreground",
3698
+ "inline-flex h-(--control-md) items-center gap-2 ps-3 pe-3 text-start text-foreground",
3699
+ // Select's type: the density tier, plus the 16px phone tier
3700
+ // its form-row neighbours take (lib/field-shell).
3701
+ tier.body,
3702
+ density === "comfortable" && MOBILE_FIELD_TIER,
5259
3703
  FIELD_SHELL,
5260
3704
  FIELD_INVALID,
5261
3705
  className
@@ -5485,6 +3929,15 @@ var POSITION = {
5485
3929
  "bottom-center": "bottom-4 left-1/2 -translate-x-1/2 items-center",
5486
3930
  "bottom-right": "bottom-4 right-4 items-end"
5487
3931
  };
3932
+ var ENTER_MOTION = {
3933
+ "top-left": "motion-slide-right",
3934
+ "top-center": "motion-slide-down",
3935
+ "top-right": "motion-slide-left",
3936
+ "bottom-left": "motion-slide-right",
3937
+ "bottom-center": "motion-slide-up",
3938
+ "bottom-right": "motion-slide-left"
3939
+ };
3940
+ var ToastPositionContext = createContext("top-right");
5488
3941
  function persistsByDefault(type, options) {
5489
3942
  return type === "loading" || type === "error" || options?.actionProps !== void 0;
5490
3943
  }
@@ -5516,12 +3969,13 @@ var toast = Object.assign(
5516
3969
  update: toastManager.update
5517
3970
  }
5518
3971
  );
3972
+ var ToastPortal = withPortalThemeScope(Toast.Portal);
5519
3973
  function ToastViewport({
5520
3974
  className,
5521
3975
  position = "top-right",
5522
3976
  ...props
5523
3977
  }) {
5524
- return /* @__PURE__ */ jsx(
3978
+ return /* @__PURE__ */ jsx(ToastPositionContext.Provider, { value: position, children: /* @__PURE__ */ jsx(ToastPortal, { children: /* @__PURE__ */ jsx(
5525
3979
  Toast.Viewport,
5526
3980
  {
5527
3981
  className: cn(
@@ -5531,15 +3985,16 @@ function ToastViewport({
5531
3985
  ),
5532
3986
  ...props
5533
3987
  }
5534
- );
3988
+ ) }) });
5535
3989
  }
5536
3990
  function ToastRoot({ className, ...props }) {
3991
+ const position = useContext(ToastPositionContext);
5537
3992
  return /* @__PURE__ */ jsx(
5538
3993
  Toast.Root,
5539
3994
  {
5540
3995
  className: cn(
5541
3996
  "flex w-full gap-3 rounded-md border border-border-floating bg-popover p-4 text-popover-foreground shadow-xl",
5542
- "motion-slide-left",
3997
+ ENTER_MOTION[position],
5543
3998
  className
5544
3999
  ),
5545
4000
  ...props
@@ -5569,7 +4024,7 @@ function ToastClose({ className, children, ...props }) {
5569
4024
  Toast.Close,
5570
4025
  {
5571
4026
  className: cn(
5572
- "-m-1 ms-auto flex h-6 w-6 shrink-0 items-center justify-center rounded-concentric-md/3 text-muted-foreground outline-none motion-interactive",
4027
+ "-m-1 ms-auto flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-muted-foreground outline-none motion-interactive",
5573
4028
  hitTargetMin,
5574
4029
  "hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring",
5575
4030
  className
@@ -5898,7 +4353,7 @@ function AccordionTrigger({
5898
4353
  Accordion$1.Trigger,
5899
4354
  {
5900
4355
  className: cn(
5901
- "flex w-full items-start justify-between gap-3 py-4 text-start text-title-sm text-foreground motion-interactive hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
4356
+ "flex w-full items-start justify-between gap-3 py-4 text-start text-title-sm text-foreground motion-interactive hover:text-muted-foreground rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50",
5902
4357
  !indicator && "[&[data-panel-open]_[data-slot=accordion-chevron]]:rotate-180",
5903
4358
  className
5904
4359
  ),
@@ -6548,8 +5003,14 @@ var ComboboxItemIndicator = Combobox$1.ItemIndicator;
6548
5003
  var ComboboxGroup = Combobox$1.Group;
6549
5004
  var FIELD_SHELL2 = {
6550
5005
  xs: cn(FIELD_SHELL_XS, "pe-7 text-ui-md"),
6551
- md: "rounded-sm border border-input bg-card pe-9 text-body-md"
5006
+ md: "rounded-sm border border-input bg-card pe-9"
6552
5007
  };
5008
+ function useFieldType(xs) {
5009
+ const tier = useTypeTier();
5010
+ const density = useDensity();
5011
+ if (xs) return void 0;
5012
+ return cn(tier.body, density === "comfortable" && MOBILE_FIELD_TIER);
5013
+ }
6553
5014
  function FieldToggle({ xs }) {
6554
5015
  return /* @__PURE__ */ jsx(
6555
5016
  Combobox$1.Trigger,
@@ -6568,6 +5029,7 @@ function FieldToggle({ xs }) {
6568
5029
  }
6569
5030
  function ComboboxInput({ className, size = "md", ...props }) {
6570
5031
  const xs = size === "xs";
5032
+ const type = useFieldType(xs);
6571
5033
  return /* @__PURE__ */ jsxs(Combobox$1.InputGroup, { className: "relative inline-flex w-full items-center", children: [
6572
5034
  /* @__PURE__ */ jsx(
6573
5035
  Combobox$1.Input,
@@ -6575,6 +5037,7 @@ function ComboboxInput({ className, size = "md", ...props }) {
6575
5037
  className: cn(
6576
5038
  "flex w-full text-foreground motion-interactive",
6577
5039
  xs ? cn("h-(--control-xs) ps-1.5", FIELD_SHELL2.xs) : cn("h-(--control-md) ps-3", FIELD_SHELL2.md),
5040
+ type,
6578
5041
  "placeholder:text-muted-foreground",
6579
5042
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
6580
5043
  "disabled:cursor-not-allowed disabled:opacity-50",
@@ -6601,12 +5064,14 @@ function ComboboxChips({
6601
5064
  ...props
6602
5065
  }) {
6603
5066
  const xs = size === "xs";
5067
+ const type = useFieldType(xs);
6604
5068
  return /* @__PURE__ */ jsxs(
6605
5069
  Combobox$1.InputGroup,
6606
5070
  {
6607
5071
  className: cn(
6608
5072
  "relative flex w-full flex-wrap items-center text-foreground motion-interactive",
6609
5073
  xs ? cn("min-h-(--control-xs) gap-0.5 py-0.5 ps-0.5", FIELD_SHELL2.xs) : cn("min-h-(--control-md) gap-1 py-1 ps-1.5", FIELD_SHELL2.md),
5074
+ type,
6610
5075
  "has-[input:focus-visible]:ring-2 has-[input:focus-visible]:ring-ring",
6611
5076
  "has-[input[aria-invalid=true]]:border-destructive has-[input[aria-invalid=true]]:ring-destructive/30",
6612
5077
  "has-[input:disabled]:cursor-not-allowed has-[input:disabled]:opacity-50",
@@ -7779,88 +6244,6 @@ var paginationMeta = {
7779
6244
  }
7780
6245
  ]
7781
6246
  };
7782
- function Code({ className, ...props }) {
7783
- return /* @__PURE__ */ jsx(
7784
- "code",
7785
- {
7786
- className: cn(
7787
- "rounded-xs bg-muted px-1.5 py-0.5 text-mono-sm text-foreground",
7788
- className
7789
- ),
7790
- ...props
7791
- }
7792
- );
7793
- }
7794
- Code.displayName = "Code";
7795
-
7796
- // src/components/code/code.meta.ts
7797
- var codeMeta = {
7798
- name: "Code",
7799
- summary: "Inline code. A styled `<code>` for code spans, keys, and identifiers inside running text \u2014 soft muted fill, monospace.",
7800
- status: "stable",
7801
- keywords: [
7802
- "code",
7803
- "inline code",
7804
- "token",
7805
- "monospace"
7806
- ],
7807
- usage: {
7808
- description: "Use Code for short inline identifiers, commands, tokens, props, and file names inside prose.",
7809
- bestPractices: [
7810
- {
7811
- guidance: "do",
7812
- description: "Use it for brief technical literals that need to stand apart from body copy."
7813
- },
7814
- {
7815
- guidance: "do",
7816
- description: "Keep inline code short enough that it does not dominate the paragraph."
7817
- },
7818
- {
7819
- guidance: "dont",
7820
- description: "Do not use Code for multiline snippets; use CodeBlock instead."
7821
- },
7822
- {
7823
- guidance: "dont",
7824
- description: "Do not put a value the reader needs to copy inside it. Code has no copy affordance, so they will select it by hand and catch the surrounding punctuation; use CodeBlock with a prompt."
7825
- }
7826
- ]
7827
- },
7828
- related: {
7829
- components: [
7830
- "CodeBlock",
7831
- "Kbd"
7832
- ]
7833
- },
7834
- props: [
7835
- {
7836
- name: "...props",
7837
- type: 'ComponentPropsWithRef<"code">',
7838
- description: "All native `<code>` attributes (children, className, ref, \u2026) pass through."
7839
- }
7840
- ],
7841
- tokensUsed: ["muted", "foreground"],
7842
- a11y: [
7843
- "Renders a semantic `<code>` element so assistive tech announces it as code.",
7844
- "A `<code>` element is not announced distinctly by most screen readers \u2014 a code span in running prose is read as ordinary text. Where the exactness matters (a command to type verbatim, a case-sensitive identifier), say so in the sentence rather than relying on the styling to carry it."
7845
- ],
7846
- examples: [
7847
- {
7848
- label: "Inline",
7849
- code: "Run <Code>pnpm dev</Code> to start the server."
7850
- },
7851
- {
7852
- label: "Code vs CodeBlock",
7853
- code: `// Picking the wrong one is the usual mistake:
7854
- //
7855
- // Code inline, inside a sentence \u2192 \`useState\`
7856
- // CodeBlock multi-line, read-only \u2192 an example to study
7857
- // CodeBlock prompt="$" one line, meant to be COPIED \u2192 an install command
7858
-
7859
- Install with <Code>pnpm add @sableui/react</Code> {/* \u2717 they will want to copy it */}
7860
- <CodeBlock prompt="$" code="pnpm add @sableui/react" /> {/* \u2713 one line, with a copy button */}`
7861
- }
7862
- ]
7863
- };
7864
6247
  function detectApple() {
7865
6248
  if (typeof navigator === "undefined") return false;
7866
6249
  const nav = navigator;
@@ -7872,14 +6255,20 @@ var noopSubscribe = () => () => {
7872
6255
  function useIsApple() {
7873
6256
  return useSyncExternalStore(noopSubscribe, detectApple, () => false);
7874
6257
  }
7875
- function Kbd({ mod = false, className, children, ...props }) {
6258
+ var KBD_VARIANTS = {
6259
+ key: "rounded-xs border border-border border-b-border-strong bg-[var(--sable-surface-bg,var(--background))] text-muted-foreground shadow-[0_1px_0_0_var(--border)]",
6260
+ ghost: "text-current opacity-70"
6261
+ };
6262
+ function Kbd({ mod = false, variant = "key", className, children, ...props }) {
7876
6263
  const apple = useIsApple();
7877
6264
  const content = mod ? apple ? "\u2318" : "Ctrl" : children;
7878
6265
  return /* @__PURE__ */ jsx(
7879
6266
  "kbd",
7880
6267
  {
6268
+ "data-slot": "kbd",
7881
6269
  className: cn(
7882
- "inline-flex h-5 min-w-5 items-center justify-center rounded-xs border border-border bg-muted px-1.5 text-mono-sm text-muted-foreground",
6270
+ "inline-flex h-[1.6em] min-w-[1.6em] items-center justify-center px-[0.4em] align-middle font-sans text-[max(0.75rem,0.85em)] font-medium leading-none tabular-nums",
6271
+ KBD_VARIANTS[variant],
7883
6272
  className
7884
6273
  ),
7885
6274
  ...props,
@@ -7888,11 +6277,22 @@ function Kbd({ mod = false, className, children, ...props }) {
7888
6277
  );
7889
6278
  }
7890
6279
  Kbd.displayName = "Kbd";
6280
+ function KbdGroup({ className, ...props }) {
6281
+ return /* @__PURE__ */ jsx(
6282
+ "kbd",
6283
+ {
6284
+ "data-slot": "kbd-group",
6285
+ className: cn("inline-flex items-center gap-[0.2em] align-middle", className),
6286
+ ...props
6287
+ }
6288
+ );
6289
+ }
6290
+ KbdGroup.displayName = "KbdGroup";
7891
6291
 
7892
6292
  // src/components/kbd/kbd.meta.ts
7893
6293
  var kbdMeta = {
7894
6294
  name: "Kbd",
7895
- summary: "A keyboard key indicator. Renders a styled <kbd> key cap in monospace; compose several with a + between to show a shortcut combo. `mod` renders the platform modifier (\u2318 or Ctrl).",
6295
+ summary: 'A keyboard key indicator. Renders a raised <kbd> key cap sized from the surrounding text, so one Kbd fits body copy, menus and tooltips; KbdGroup sets one cap per key for a combo. `mod` renders the platform modifier (\u2318 or Ctrl); `variant="ghost"` is the bare label for menu rows and tooltips.',
7896
6296
  status: "stable",
7897
6297
  keywords: [
7898
6298
  "keyboard",
@@ -7909,12 +6309,20 @@ var kbdMeta = {
7909
6309
  },
7910
6310
  {
7911
6311
  guidance: "do",
7912
- description: "Keep multi-key shortcuts readable by separating individual keys."
6312
+ description: "Show a combo as one cap per key inside KbdGroup \u2014 `<KbdGroup><Kbd mod /><Kbd>K</Kbd></KbdGroup>` \u2014 not several keys typed into one cap."
6313
+ },
6314
+ {
6315
+ guidance: "do",
6316
+ description: 'Use `variant="ghost"` where the shortcut sits on a surface of its own \u2014 a menu row\'s trailing slot, a tooltip \u2014 so it reads as a hint, not a second control.'
7913
6317
  },
7914
6318
  {
7915
6319
  guidance: "dont",
7916
6320
  description: "Do not use Kbd as a decorative badge or button replacement."
7917
6321
  },
6322
+ {
6323
+ guidance: "dont",
6324
+ description: "Do not set a text size on a Kbd. It sizes itself from the text around it."
6325
+ },
7918
6326
  {
7919
6327
  guidance: "dont",
7920
6328
  description: "Do not hardcode \u2318 or Ctrl. The modifier differs by platform; use `<Kbd mod />`, which renders \u2318 on macOS/iOS and Ctrl elsewhere, or Windows users read Mac keys."
@@ -7935,6 +6343,12 @@ var kbdMeta = {
7935
6343
  default: "false",
7936
6344
  description: "Render the platform's shortcut modifier in place of children: \u2318 on macOS and iOS, Ctrl everywhere else. Server-rendered as Ctrl and corrected on hydration."
7937
6345
  },
6346
+ {
6347
+ name: "variant",
6348
+ type: '"key" | "ghost"',
6349
+ default: '"key"',
6350
+ description: "`key`: a raised cap on the surface it sits on, with a heavier bottom edge as the key's lip. `ghost`: the bare label, for menu rows, tooltips and other surfaces that are already a frame."
6351
+ },
7938
6352
  {
7939
6353
  name: "className",
7940
6354
  type: "string",
@@ -7946,10 +6360,11 @@ var kbdMeta = {
7946
6360
  description: "All native <kbd> attributes (children, ref, etc.)."
7947
6361
  }
7948
6362
  ],
7949
- tokensUsed: ["border", "muted", "muted-foreground"],
6363
+ tokensUsed: ["border", "border-strong", "background", "muted-foreground"],
7950
6364
  a11y: [
7951
6365
  "Uses the semantic <kbd> element so assistive tech announces keyboard input.",
7952
- "Monospace key text keeps single keys and symbols legible at small sizes."
6366
+ "KbdGroup is itself a <kbd> around the per-key caps \u2014 the HTML spelling of one key combination \u2014 so the combo is announced as a single piece of input.",
6367
+ "Sized in em from the surrounding text, so it scales with the user's text size and never shrinks below 12px."
7953
6368
  ],
7954
6369
  examples: [
7955
6370
  {
@@ -7958,7 +6373,11 @@ var kbdMeta = {
7958
6373
  },
7959
6374
  {
7960
6375
  label: "Combo",
7961
- code: "<Row gap={1}><Kbd mod /><span>+</span><Kbd>K</Kbd></Row>"
6376
+ code: "<KbdGroup><Kbd mod /><Kbd>K</Kbd></KbdGroup>"
6377
+ },
6378
+ {
6379
+ label: "In a menu row",
6380
+ code: '<MenuItem>Search<KbdGroup className="ms-auto"><Kbd variant="ghost" mod /><Kbd variant="ghost">K</Kbd></KbdGroup></MenuItem>'
7962
6381
  }
7963
6382
  ]
7964
6383
  };
@@ -8278,7 +6697,7 @@ var inlineStatusMeta = {
8278
6697
  ]
8279
6698
  };
8280
6699
  var bannerVariants = cva(
8281
- "relative flex w-full items-center gap-3 px-4 py-2.5 text-label-md",
6700
+ "relative flex min-h-(--control-md) w-full items-center gap-3 px-4 py-2 text-label-md",
8282
6701
  {
8283
6702
  variants: {
8284
6703
  tone: {
@@ -8521,229 +6940,31 @@ var bannerMeta = {
8521
6940
  examples: [
8522
6941
  {
8523
6942
  label: "Tones",
8524
- code: '<Banner tone="info">Maintenance window tonight at 02:00 UTC.</Banner>'
8525
- },
8526
- {
8527
- label: "Solid (loud)",
8528
- code: '<Banner tone="destructive" appearance="solid" live="assertive">Blockchain unavailable. Collect and buy are temporarily disabled.</Banner>'
8529
- },
8530
- {
8531
- label: "Success",
8532
- code: '<Banner tone="success" live="polite">Credits authorized for Studio.</Banner>'
8533
- },
8534
- {
8535
- label: "Action",
8536
- code: '<Banner sticky action={<Button variant="outline" size="sm">Refresh</Button>}>12 new posts</Banner>'
8537
- },
8538
- {
8539
- label: "Dismissible",
8540
- code: '<Banner tone="warning" dismissible>Your session expires soon.</Banner>'
8541
- },
8542
- {
8543
- label: "Recoverable warning",
8544
- code: '<Banner tone="warning" action={<Button variant="outline" size="sm">Reconnect</Button>}>Wallet connection paused.</Banner>'
8545
- },
8546
- {
8547
- label: "App-shell outage",
8548
- code: '<AppShell banners={<Banner tone="destructive" appearance="solid" live="assertive">Uploads are temporarily unavailable.</Banner>}><Region max="feed"><EmptyState title="Uploads paused" description="Existing posts are still visible while the service recovers." /></Region></AppShell>'
8549
- }
8550
- ]
8551
- };
8552
- function EmptyState({
8553
- icon,
8554
- title,
8555
- description,
8556
- action,
8557
- code,
8558
- tone = "neutral",
8559
- size = "md",
8560
- surface = "none",
8561
- className,
8562
- ...props
8563
- }) {
8564
- const destructive = tone === "destructive";
8565
- const glyph = icon ?? (destructive && code == null ? /* @__PURE__ */ jsx(Icon, { name: "warning" }) : null);
8566
- const Title = size === "lg" ? "h2" : "p";
8567
- const body = /* @__PURE__ */ jsxs(Fragment$1, { children: [
8568
- code != null ? /* @__PURE__ */ jsx("p", { className: "text-display-lg text-muted-foreground", children: code }) : null,
8569
- glyph != null ? /* @__PURE__ */ jsx(
8570
- "div",
8571
- {
8572
- className: cn(
8573
- "flex size-12 items-center justify-center rounded-full text-2xl",
8574
- destructive ? "bg-destructive/15 text-destructive" : "bg-muted text-muted-foreground"
8575
- ),
8576
- children: glyph
8577
- }
8578
- ) : null,
8579
- /* @__PURE__ */ jsx(Title, { className: size === "lg" ? "text-heading-2" : "text-title-lg", children: title }),
8580
- description != null ? /* @__PURE__ */ jsx("p", { className: "max-w-md text-body-md text-muted-foreground", children: description }) : null,
8581
- action != null ? /* @__PURE__ */ jsx("div", { className: "mt-2", children: action }) : null
8582
- ] });
8583
- const layout = cn(
8584
- "flex flex-col items-center justify-center gap-3 text-center",
8585
- size === "lg" ? "px-6 py-20" : "px-6 py-12"
8586
- );
8587
- const a11y = destructive ? { role: "alert" } : {};
8588
- if (surface === "card") {
8589
- return /* @__PURE__ */ jsx(Card, { className: cn(layout, "p-[var(--card-p)]", className), ...a11y, ...props, children: body });
8590
- }
8591
- return /* @__PURE__ */ jsx("div", { className: cn(layout, className), ...a11y, ...props, children: body });
8592
- }
8593
- EmptyState.displayName = "EmptyState";
8594
-
8595
- // src/components/empty-state/empty-state.meta.ts
8596
- var emptyStateMeta = {
8597
- name: "EmptyState",
8598
- summary: 'The centered empty / error block: optional icon disc or error code, title, description and an action. tone="destructive" makes it an announced error, size="lg" a page-level state, surface="card" a self-contained card.',
8599
- keywords: [
8600
- "empty",
8601
- "zero state",
8602
- "blank slate",
8603
- "no results",
8604
- "placeholder",
8605
- "onboarding",
8606
- "no data"
8607
- ],
8608
- usage: {
8609
- description: "Use EmptyState when a content area has no items, no search results, or no first-time setup yet. The title should name the empty condition and the description/action should give the user a next step.",
8610
- bestPractices: [
8611
- {
8612
- guidance: "do",
8613
- description: 'Explain what is empty in plain language, such as "No posts yet" or "No matching collectors".'
8614
- },
8615
- {
8616
- guidance: "do",
8617
- description: "Offer one next step when the user can recover, create, clear filters, or start setup."
8618
- },
8619
- {
8620
- guidance: "do",
8621
- description: "Keep the icon decorative and make the title carry the meaning."
8622
- },
8623
- {
8624
- guidance: "do",
8625
- description: `For a search or filter result, name the query back to the user and offer the way out, such as "No results for 'quarterly'" with a Clear filters action. An empty result that does not say what was searched leaves the user unsure whether the query or the data was wrong.`
8626
- },
8627
- {
8628
- guidance: "dont",
8629
- description: 'Use generic copy like "No data"; it gives users and agents no useful path forward.'
8630
- },
8631
- {
8632
- guidance: "dont",
8633
- description: "Park anything the user needs permanently here, such as setup instructions, billing notices, or keyboard shortcuts. An empty state is the one surface guaranteed to disappear the moment content exists, so information that matters after the first item is lost exactly when the user has learned enough to want it."
8634
- },
8635
- {
8636
- guidance: "dont",
8637
- description: `Use it for a field's validation error \u2014 that is Field error messaging. Reserve tone="destructive" for a view or block that failed to load.`
8638
- }
8639
- ],
8640
- anatomy: [
8641
- {
8642
- name: "Icon",
8643
- required: false,
8644
- description: "A decorative cue that reinforces the empty condition."
8645
- },
8646
- {
8647
- name: "Title",
8648
- required: true,
8649
- description: "The short message naming what is empty."
8650
- },
8651
- {
8652
- name: "Description",
8653
- required: false,
8654
- description: "Supporting copy that explains why the state exists or what to try next."
8655
- },
8656
- {
8657
- name: "Action",
8658
- required: false,
8659
- description: "One clear button or link that moves the user forward."
8660
- }
8661
- ]
8662
- },
8663
- related: {
8664
- components: ["Button", "Icon", "Card", "Banner", "DataTable"],
8665
- patterns: ["FeedPage", "ListDetailPage", "DashboardPage"]
8666
- },
8667
- status: "stable",
8668
- props: [
8669
- {
8670
- name: "icon",
8671
- type: "ReactNode",
8672
- description: "Optional leading glyph \u2014 pass an `<Icon />`. Rendered in a soft muted disc."
8673
- },
8674
- {
8675
- name: "title",
8676
- type: "ReactNode",
8677
- description: "The headline. Short and direct."
8678
- },
8679
- {
8680
- name: "description",
8681
- type: "ReactNode",
8682
- description: "Optional supporting line beneath the title."
8683
- },
8684
- {
8685
- name: "action",
8686
- type: "ReactNode",
8687
- description: "Optional action below the copy \u2014 typically a `<Button />`."
8688
- },
8689
- {
8690
- name: "code",
8691
- type: "ReactNode",
8692
- description: "A large figure above the title, such as an error code (404)."
8693
- },
8694
- {
8695
- name: "tone",
8696
- type: '"neutral" | "destructive"',
8697
- default: '"neutral"',
8698
- description: 'destructive is an error state: a destructive icon disc (a warning by default) and role="alert" so it is announced.'
8699
- },
8700
- {
8701
- name: "size",
8702
- type: '"md" | "lg"',
8703
- default: '"md"',
8704
- description: "lg is page-level: the title is an h2 and the block takes more room. md sits in a panel, list or card."
8705
- },
8706
- {
8707
- name: "surface",
8708
- type: '"none" | "card"',
8709
- default: '"none"',
8710
- description: "card draws it as a Card, for an error or empty block standing on its own in a page."
8711
- }
8712
- ],
8713
- variants: {
8714
- tone: ["neutral", "destructive"],
8715
- size: ["md", "lg"],
8716
- surface: ["none", "card"]
8717
- },
8718
- tokensUsed: ["muted", "muted-foreground", "foreground", "destructive"],
8719
- a11y: [
8720
- "Decorative icon discs should not carry meaning the title doesn't already convey.",
8721
- "Pair with an actionable next step so the zero-state is a path forward, not a dead end."
8722
- ],
8723
- examples: [
6943
+ code: '<Banner tone="info">Maintenance window tonight at 02:00 UTC.</Banner>'
6944
+ },
8724
6945
  {
8725
- label: "With icon and action",
8726
- code: '<EmptyState icon={<Icon name="info" />} title="No posts yet" description="Create your first." action={<Button>New post</Button>} />'
6946
+ label: "Solid (loud)",
6947
+ code: '<Banner tone="destructive" appearance="solid" live="assertive">Blockchain unavailable. Collect and buy are temporarily disabled.</Banner>'
8727
6948
  },
8728
6949
  {
8729
- label: "No search results",
8730
- code: '<EmptyState icon={<Icon name="search" />} title="No matching posts" description="Try a different keyword or clear your filters." action={<Button variant="secondary">Clear filters</Button>} />'
6950
+ label: "Success",
6951
+ code: '<Banner tone="success" live="polite">Credits authorized for Studio.</Banner>'
8731
6952
  },
8732
6953
  {
8733
- label: "First-run setup",
8734
- code: '<EmptyState surface="card" icon={<Icon name="sparkles" />} title="Start your collection" description="Create a first edition, import an existing drop, or invite collaborators." action={<Row gap={1}><Button>Create edition</Button><Button variant="ghost">Import</Button></Row>} />'
6954
+ label: "Action",
6955
+ code: '<Banner sticky action={<Button variant="outline" size="sm">Refresh</Button>}>12 new posts</Banner>'
8735
6956
  },
8736
6957
  {
8737
- label: "Page-level error",
8738
- code: '<EmptyState tone="destructive" size="lg" code="404" title="Page not found" description="The page you are looking for has moved or never existed." action={<Button>Go home</Button>} />'
6958
+ label: "Dismissible",
6959
+ code: '<Banner tone="warning" dismissible>Your session expires soon.</Banner>'
8739
6960
  },
8740
6961
  {
8741
- label: "Error card",
8742
- code: '<EmptyState tone="destructive" surface="card" title="Could not load this edition" description="Check your connection and try again." action={<Button variant="secondary">Retry</Button>} />'
6962
+ label: "Recoverable warning",
6963
+ code: '<Banner tone="warning" action={<Button variant="outline" size="sm">Reconnect</Button>}>Wallet connection paused.</Banner>'
8743
6964
  },
8744
6965
  {
8745
- label: "Empty table body",
8746
- code: '<DataTable data={[]} columns={[{ key: "name", header: "Collector" }, { key: "status", header: "Status" }]} empty={<EmptyState title="No collectors yet" description="Collectors appear here after the first mint." />} />'
6966
+ label: "App-shell outage",
6967
+ code: '<AppShell banners={<Banner tone="destructive" appearance="solid" live="assertive">Uploads are temporarily unavailable.</Banner>}><Region max="feed"><EmptyState title="Uploads paused" description="Existing posts are still visible while the service recovers." /></Region></AppShell>'
8747
6968
  }
8748
6969
  ]
8749
6970
  };
@@ -9354,13 +7575,28 @@ function PanelSection({
9354
7575
  children,
9355
7576
  ...props
9356
7577
  }) {
9357
- return /* @__PURE__ */ jsxs("section", { className: cn("flex flex-col border-t border-hairline", className), ...props, children: [
9358
- /* @__PURE__ */ jsxs("header", { className: "flex h-7 items-center justify-between gap-2 px-2", children: [
9359
- /* @__PURE__ */ jsx("h3", { className: "text-ui-section text-foreground", children: title }),
9360
- action
9361
- ] }),
9362
- /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5 px-2 pb-2", children })
9363
- ] });
7578
+ return (
7579
+ // Side padding and the first/last section's top/bottom come from a framing
7580
+ // sable-frame-tool, so rows stay concentric with a rounded frame's corners;
7581
+ // in a square Workspace column they fall back to the 8px tool inset.
7582
+ /* @__PURE__ */ jsxs(
7583
+ "section",
7584
+ {
7585
+ className: cn(
7586
+ "flex flex-col border-t border-hairline first:pt-[var(--panel-edge,0px)] last:pb-[var(--panel-edge,0px)]",
7587
+ className
7588
+ ),
7589
+ ...props,
7590
+ children: [
7591
+ /* @__PURE__ */ jsxs("header", { className: "flex h-7 items-center justify-between gap-2 px-[var(--panel-inset,--spacing(2))]", children: [
7592
+ /* @__PURE__ */ jsx("h3", { className: "text-ui-section text-foreground", children: title }),
7593
+ action
7594
+ ] }),
7595
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5 px-[var(--panel-inset,--spacing(2))] pb-2", children })
7596
+ ]
7597
+ }
7598
+ )
7599
+ );
9364
7600
  }
9365
7601
  PanelSection.displayName = "PanelSection";
9366
7602
  function PropertyRow({
@@ -11134,202 +9370,230 @@ const parsed = isTransparent(value) ? null : parseHex(value) // null = not a col
11134
9370
  }
11135
9371
  ]
11136
9372
  };
11137
- var SideNavContext = createContext({
11138
- size: "md",
11139
- activeVariant: "content"
11140
- });
11141
- function SideNav({
9373
+ function Fieldset({ className, ...props }) {
9374
+ return /* @__PURE__ */ jsx(
9375
+ Fieldset$1.Root,
9376
+ {
9377
+ className: cn(
9378
+ // min-w-0: a native <fieldset> defaults to min-inline-size: min-content
9379
+ // and would refuse to shrink below its widest control. overflow-hidden
9380
+ // keeps the footer's wash inside the rounded corners.
9381
+ // Publishes its surface like Card, so wells, stepper discs and
9382
+ // interactive rows inside derive from the card, not the page.
9383
+ "min-w-0 overflow-hidden rounded-lg border border-border bg-card text-card-foreground shadow-sm [--sable-surface-bg:var(--card)]",
9384
+ // No opacity of its own: a disabled fieldset disables every control
9385
+ // in it, and each already dims to 50% — dimming here as well stacked
9386
+ // to ~30%, against 50% everywhere else in the system.
9387
+ className
9388
+ ),
9389
+ ...props
9390
+ }
9391
+ );
9392
+ }
9393
+ Fieldset.displayName = "Fieldset";
9394
+ function FieldsetLegend({ className, ...props }) {
9395
+ return /* @__PURE__ */ jsx(
9396
+ Fieldset$1.Legend,
9397
+ {
9398
+ className: cn("px-6 pt-6 text-title-lg text-foreground", className),
9399
+ ...props
9400
+ }
9401
+ );
9402
+ }
9403
+ FieldsetLegend.displayName = "FieldsetLegend";
9404
+ function FieldsetDescription({
11142
9405
  className,
11143
- size = "md",
11144
- activeVariant = "content",
11145
9406
  ...props
11146
9407
  }) {
11147
- return /* @__PURE__ */ jsx(SideNavContext.Provider, { value: { size, activeVariant }, children: /* @__PURE__ */ jsx("nav", { className: cn("flex flex-col gap-0.5", className), ...props }) });
9408
+ return /* @__PURE__ */ jsx(
9409
+ "p",
9410
+ {
9411
+ "data-slot": "fieldset-description",
9412
+ className: cn("px-6 text-body-sm text-muted-foreground", className),
9413
+ ...props
9414
+ }
9415
+ );
11148
9416
  }
11149
- SideNav.displayName = "SideNav";
11150
- function SideNavGroup({
11151
- label,
9417
+ FieldsetDescription.displayName = "FieldsetDescription";
9418
+ function FieldsetContent({
11152
9419
  className,
11153
- children,
11154
9420
  ...props
11155
9421
  }) {
11156
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-0.5", className), ...props, children: [
11157
- label ? /* @__PURE__ */ jsx("div", { className: "px-3 pt-3 pb-1 text-caption text-muted-foreground", children: label }) : null,
11158
- children
11159
- ] });
9422
+ return /* @__PURE__ */ jsx("div", { className: cn("space-y-4 px-6 py-4 last:pb-6", className), ...props });
11160
9423
  }
11161
- SideNavGroup.displayName = "SideNavGroup";
11162
- function SideNavItem({
11163
- label,
11164
- icon,
11165
- active,
11166
- badge,
11167
- size,
11168
- activeVariant,
11169
- render,
9424
+ FieldsetContent.displayName = "FieldsetContent";
9425
+ function FieldsetFooter({
11170
9426
  className,
11171
9427
  ...props
11172
9428
  }) {
11173
- const context = useContext(SideNavContext);
11174
- const resolvedSize = size ?? context.size;
11175
- const resolvedActiveVariant = activeVariant ?? context.activeVariant;
11176
- return useRender({
11177
- render: render ?? /* @__PURE__ */ jsx("button", { type: "button" }),
11178
- props: {
11179
- ...props,
11180
- "data-active": active ? "" : void 0,
11181
- "aria-current": active ? "page" : void 0,
9429
+ return /* @__PURE__ */ jsx(
9430
+ "div",
9431
+ {
11182
9432
  className: cn(
11183
- sideNavItemVariants({
11184
- size: resolvedSize,
11185
- activeVariant: resolvedActiveVariant
11186
- }),
9433
+ "flex justify-end gap-2 border-t border-border bg-muted/30 px-6 py-4",
9434
+ "[[data-slot=fieldset-description]+&]:mt-6",
11187
9435
  className
11188
9436
  ),
11189
- children: /* @__PURE__ */ jsxs(Fragment$1, { children: [
11190
- icon ? /* @__PURE__ */ jsx("span", { className: sideNavItemIconVariants({ size: resolvedSize }), children: icon }) : null,
11191
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: label }),
11192
- badge ? /* @__PURE__ */ jsx("span", { className: "ms-auto shrink-0", children: badge }) : null
11193
- ] })
11194
- },
11195
- defaultTagName: "button"
11196
- });
9437
+ ...props
9438
+ }
9439
+ );
11197
9440
  }
11198
- SideNavItem.displayName = "SideNavItem";
9441
+ FieldsetFooter.displayName = "FieldsetFooter";
11199
9442
 
11200
- // src/components/side-nav/side-nav.meta.ts
11201
- var sideNavMeta = {
11202
- name: "SideNav",
11203
- summary: "A lightweight in-content vertical navigation list - the settings / docs sub-nav, distinct from the app-chrome Sidebar. Router-agnostic items, optional SideNavGroup sections, and an explicit active-state vocabulary for content rails or sidebar chrome.",
9443
+ // src/components/fieldset/fieldset.meta.ts
9444
+ var fieldsetMeta = {
9445
+ name: "Fieldset",
9446
+ summary: "Form section panel (Card-like bordered surface) over Base UI Fieldset, with Legend/Description/Content/Footer parts.",
9447
+ status: "stable",
11204
9448
  keywords: [
11205
- "side nav",
11206
- "navigation",
11207
- "settings nav",
11208
- "sidebar",
11209
- "rail",
11210
- "docs nav",
11211
- "active item",
11212
- "group"
9449
+ "fieldset",
9450
+ "form group",
9451
+ "legend",
9452
+ "fields"
11213
9453
  ],
11214
9454
  usage: {
11215
- description: "Use SideNav for vertical navigation inside a settings page, documentation area, dashboard rail, or sidebar chrome. It is router-agnostic: compose items with render={<Link />} and choose activeVariant based on whether the nav sits in content or app chrome.",
9455
+ description: "Use Fieldset to group related controls under a shared label or explanation.",
11216
9456
  bestPractices: [
11217
9457
  {
11218
9458
  guidance: "do",
11219
- description: "Use aria-label on SideNav when a page contains more than one nav landmark."
11220
- },
11221
- {
11222
- guidance: "do",
11223
- description: "Group related items with SideNavGroup when the rail has more than one conceptual area."
11224
- },
11225
- {
11226
- guidance: "do",
11227
- description: "Use size='lg' and activeVariant='sidebar' when the nav is part of persistent app chrome."
9459
+ description: "Use it for grouped form sections such as contact details, preferences, or permissions."
11228
9460
  },
11229
9461
  {
11230
9462
  guidance: "do",
11231
- description: "Inside a rounded, padded frame, give each item rounded-concentric-<outer>/<inset> (e.g. rounded-concentric-lg/2 in a rounded-lg p-2 box). The rows' own radius is fixed per size, and a same-radius row inset into a frame reads as a second system."
9463
+ description: "Keep the legend meaningful so assistive tech and agents understand the group purpose."
11232
9464
  },
11233
9465
  {
11234
9466
  guidance: "dont",
11235
- description: "Use SideNav for bottom mobile navigation; use BottomNav in AppShell's bottomNav slot."
9467
+ description: "Do not use Fieldset as a generic spacing wrapper outside forms."
11236
9468
  },
11237
9469
  {
11238
9470
  guidance: "dont",
11239
- description: "Mix content and sidebar active variants in the same rail unless individual items intentionally override the group."
9471
+ description: "Do not use a Legend that is really a heading. A legend names the group's question for assistive technology; using it as a section title puts a question mark where the outline should be."
11240
9472
  }
11241
9473
  ],
11242
9474
  anatomy: [
11243
9475
  {
11244
- name: "Nav",
9476
+ name: "Fieldset",
9477
+ required: true,
9478
+ description: "The bordered group: a native <fieldset> whose disabled state reaches every control inside it."
9479
+ },
9480
+ {
9481
+ name: "Legend",
11245
9482
  required: true,
11246
- description: "The landmark container for the vertical navigation list."
9483
+ description: "FieldsetLegend: the question the group's controls answer, announced before each of them."
11247
9484
  },
11248
9485
  {
11249
- name: "Group",
9486
+ name: "Description",
11250
9487
  required: false,
11251
- description: "Optional labelled cluster of related navigation items."
9488
+ description: "FieldsetDescription: muted copy under the legend."
11252
9489
  },
11253
9490
  {
11254
- name: "Item",
9491
+ name: "Content",
11255
9492
  required: true,
11256
- description: "Interactive row with label, optional icon, optional badge, and active state."
9493
+ description: "FieldsetContent: the stacked body, one Field per control."
11257
9494
  },
11258
9495
  {
11259
- name: "Badge",
9496
+ name: "Footer",
11260
9497
  required: false,
11261
- description: "Trailing count or state indicator aligned to the row end."
9498
+ description: "FieldsetFooter: the group's actions on a muted wash."
11262
9499
  }
11263
9500
  ]
11264
9501
  },
11265
9502
  related: {
11266
- components: ["Sidebar", "AppShell", "BottomNav", "Badge", "Icon"],
11267
- patterns: ["SettingsPage", "DashboardPage"]
9503
+ components: [
9504
+ "Form",
9505
+ "Label"
9506
+ ]
11268
9507
  },
11269
- status: "stable",
11270
9508
  props: [
11271
9509
  {
11272
- name: "size",
11273
- type: '"md" | "lg"',
11274
- description: 'Density shared by all items. "md" (default) = the lightweight in-content sub-nav; "lg" = chrome-weight (larger label + icon, roomier rows) for a rail that pairs with the app Sidebar.'
9510
+ name: "disabled",
9511
+ type: "boolean",
9512
+ description: "Disables the whole group. Renders <fieldset disabled> (native control disabling) and surfaces data-[disabled] for dimming."
9513
+ },
9514
+ {
9515
+ name: "render",
9516
+ type: "ReactElement | function",
9517
+ description: "Base UI render prop to replace or compose the underlying element."
11275
9518
  },
11276
9519
  {
11277
- name: "activeVariant",
11278
- type: '"content" | "sidebar"',
11279
- default: '"content"',
11280
- description: 'Active-state token vocabulary shared by all items. "content" uses bg-accent/text-accent-foreground for local rails inside the content area; "sidebar" uses bg-sidebar-accent/text-sidebar-accent-foreground for SideNavs rendered inside app chrome.'
9520
+ name: "className",
9521
+ type: "string",
9522
+ description: "Merged onto each part via cn() for one-off overrides."
11281
9523
  }
11282
9524
  ],
11283
9525
  subcomponents: [
11284
9526
  {
11285
- name: "SideNavItem",
11286
- type: '{ label: ReactNode; icon?: ReactNode; active?: boolean; badge?: ReactNode; size?: "md" | "lg"; activeVariant?: "content" | "sidebar"; render?: useRender.RenderProp } + button props',
11287
- description: 'A single row. label is the row text; icon is an optional leading icon; active marks the current sub-section (active fill + aria-current="page"); badge is a trailing node (e.g. a count) pushed to the row end; size and activeVariant override the enclosing SideNav per item; render composes another element, e.g. render={<Link to="/x" />}.'
9527
+ name: "FieldsetLegend",
9528
+ type: "ComponentPropsWithRef<typeof Fieldset.Legend>",
9529
+ description: "The group's name \u2014 the question its controls answer. Base UI renders it as a <div> wired to the <fieldset> through aria-labelledby, so a screen reader announces it before each control in the group. This is the whole reason to reach for Fieldset."
9530
+ },
9531
+ {
9532
+ name: "FieldsetDescription",
9533
+ type: 'ComponentPropsWithRef<"p">',
9534
+ description: "Muted supporting copy under the legend."
9535
+ },
9536
+ {
9537
+ name: "FieldsetContent",
9538
+ type: 'ComponentPropsWithRef<"div">',
9539
+ description: "The stacked body holding one Field per control."
11288
9540
  },
11289
9541
  {
11290
- name: "SideNavGroup",
11291
- type: "{ label?: ReactNode } + div props",
11292
- description: "A labelled section within a SideNav - groups settings into areas. label renders an optional sentence-case caption heading above the group's items."
9542
+ name: "FieldsetFooter",
9543
+ type: 'ComponentPropsWithRef<"div">',
9544
+ description: "Optional right-aligned action bar on a muted wash."
11293
9545
  }
11294
9546
  ],
11295
- a11y: [
11296
- "SideNav renders a <nav> landmark; pair multiple navs with aria-label to distinguish them.",
11297
- 'The active item carries aria-current="page".',
11298
- "Items expose a focus-visible ring; the active state is reinforced by weight + fill, not colour alone.",
11299
- 'Use activeVariant="content" for local in-page rails and activeVariant="sidebar" when a SideNav is placed on Sidebar chrome.'
11300
- ],
11301
9547
  tokensUsed: [
11302
- "muted",
11303
- "muted-foreground",
9548
+ "card",
9549
+ "card-foreground",
9550
+ "border",
11304
9551
  "foreground",
11305
- "accent",
11306
- "accent-foreground",
11307
- "ring",
11308
- "sidebar-foreground",
11309
- "sidebar-accent",
11310
- "sidebar-accent-foreground",
11311
- "sidebar-ring"
9552
+ "muted-foreground",
9553
+ "muted"
9554
+ ],
9555
+ a11y: [
9556
+ "Base.Root renders a native <fieldset>; Base.Legend renders an auto-associated label, so screen readers announce the group name.",
9557
+ "disabled propagates as native <fieldset disabled>, disabling all contained controls.",
9558
+ "Wrap each control inside FieldsetContent in a Field, which wires its own label, description and error to the control."
11312
9559
  ],
11313
9560
  examples: [
11314
9561
  {
11315
- label: "Settings sub-nav",
11316
- code: '<SideNav><SideNavItem label="Profile" active render={<Link to="/settings/profile" />} /><SideNavItem label="Account" render={<Link to="/settings/account" />} /><SideNavItem label="Notifications" render={<Link to="/settings/notifications" />} /></SideNav>'
11317
- },
11318
- {
11319
- label: "Grouped",
11320
- code: '<SideNav><SideNavGroup label="Account"><SideNavItem label="Profile" active render={<Link to="/settings/profile" />} /><SideNavItem label="Security" render={<Link to="/settings/security" />} /></SideNavGroup><SideNavGroup label="Workspace"><SideNavItem label="Members" render={<Link to="/settings/members" />} /></SideNavGroup></SideNav>'
11321
- },
11322
- {
11323
- label: "Chrome-weight rail (size=lg)",
11324
- code: '<SideNav size="lg"><SideNavItem label="Profile" active render={<Link to="/settings/profile" />} /><SideNavItem label="Wallets" render={<Link to="/settings/wallets" />} /><SideNavItem label="Security" render={<Link to="/settings/security" />} /></SideNav>'
11325
- },
11326
- {
11327
- label: "Inside Sidebar chrome",
11328
- code: '<SideNav size="lg" activeVariant="sidebar"><SideNavItem label="Overview" active render={<Link to="/" />} /><SideNavItem label="Reports" render={<Link to="/reports" />} /><SideNavItem label="Settings" render={<Link to="/settings" />} /></SideNav>'
9562
+ label: "A grouped question",
9563
+ code: `<Fieldset>
9564
+ <FieldsetLegend>Notify me about</FieldsetLegend>
9565
+ <FieldsetDescription>Pick what reaches your inbox.</FieldsetDescription>
9566
+ <FieldsetContent>
9567
+ <Field label="Mentions" orientation="horizontal"><Checkbox defaultChecked /></Field>
9568
+ <Field label="Replies" orientation="horizontal"><Checkbox /></Field>
9569
+ <Field label="Direct messages" orientation="horizontal"><Checkbox /></Field>
9570
+ </FieldsetContent>
9571
+ <FieldsetFooter>
9572
+ <Button>Save preferences</Button>
9573
+ </FieldsetFooter>
9574
+ </Fieldset>`
11329
9575
  },
11330
9576
  {
11331
- label: "Grouped with icons and badges",
11332
- code: '<SideNav aria-label="Settings sections"><SideNavGroup label="Account"><SideNavItem icon={<Icon name="user" />} label="Profile" active render={<Link to="/settings/profile" />} /><SideNavItem icon={<Icon name="lock" />} label="Security" badge={<Badge tone="warning" size="sm">2</Badge>} render={<Link to="/settings/security" />} /></SideNavGroup><SideNavGroup label="Workspace"><SideNavItem icon={<Icon name="bell" />} label="Notifications" render={<Link to="/settings/notifications" />} /></SideNavGroup></SideNav>'
9577
+ label: "Why a fieldset, and when it is wrong",
9578
+ code: `// Fieldset groups inputs that are ONE question with multiple parts, and gives
9579
+ // the group a single accessible name \u2014 which is the part you cannot do with a
9580
+ // div and a heading.
9581
+
9582
+ <Fieldset>
9583
+ <FieldsetLegend>Digest frequency</FieldsetLegend>
9584
+ <FieldsetContent>
9585
+ <RadioGroup defaultValue="weekly">
9586
+ <Field label="Daily" orientation="horizontal"><Radio value="daily" /></Field>
9587
+ <Field label="Weekly" orientation="horizontal"><Radio value="weekly" /></Field>
9588
+ </RadioGroup>
9589
+ </FieldsetContent>
9590
+ </Fieldset>
9591
+
9592
+ // \u2713 Required for radio groups: without a fieldset the options have no shared
9593
+ // name, so a screen reader announces "Weekly, radio" with no question.
9594
+ // \u2717 Not for visual grouping of unrelated fields. Use PanelSection on a panel or
9595
+ // a Card section on a page; a legend that is really a heading lies to
9596
+ // assistive technology about the form's structure.`
11333
9597
  }
11334
9598
  ]
11335
9599
  };
@@ -11388,14 +9652,14 @@ function RadioCard({
11388
9652
  "span",
11389
9653
  {
11390
9654
  className: cn(
11391
- "relative inline-flex size-4.5 shrink-0 rounded-full border border-input bg-card motion-interactive",
9655
+ RADIO_RING,
11392
9656
  t.marker
11393
9657
  ),
11394
9658
  children: /* @__PURE__ */ jsx(
11395
9659
  Radio$1.Indicator,
11396
9660
  {
11397
9661
  className: cn(
11398
- "absolute left-1/2 top-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full motion-interactive ease-spring data-[starting-style]:scale-0 data-[ending-style]:scale-0",
9662
+ RADIO_DOT,
11399
9663
  t.dot
11400
9664
  )
11401
9665
  }
@@ -11420,22 +9684,13 @@ function RadioCard({
11420
9684
  className: cn(
11421
9685
  SELECTION_CARD_SHELL,
11422
9686
  t.card,
11423
- icon != null ? "flex-col gap-3 rounded-lg" : "items-start gap-3 rounded-md",
9687
+ icon != null ? SELECTION_CARD_LAYOUT.tile : SELECTION_CARD_LAYOUT.row,
11424
9688
  className
11425
9689
  ),
11426
9690
  ...props,
11427
9691
  children: icon != null ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
11428
- /* @__PURE__ */ jsxs("span", { className: "flex w-full items-start justify-between gap-3", children: [
11429
- /* @__PURE__ */ jsx(
11430
- "span",
11431
- {
11432
- className: cn(
11433
- "shrink-0 text-2xl leading-none motion-interactive",
11434
- t.glyph
11435
- ),
11436
- children: icon
11437
- }
11438
- ),
9692
+ /* @__PURE__ */ jsxs("span", { className: SELECTION_CARD_TILE_HEAD, children: [
9693
+ /* @__PURE__ */ jsx("span", { className: cn(SELECTION_CARD_GLYPH, t.glyph), children: icon }),
11439
9694
  marker
11440
9695
  ] }),
11441
9696
  content
@@ -11670,19 +9925,30 @@ function CheckboxCard({
11670
9925
  ...props
11671
9926
  }) {
11672
9927
  const t = checkboxCardToneClasses[tone];
9928
+ const box = /* @__PURE__ */ jsx("span", { className: cn(CHECK_BOX, t.box), children: /* @__PURE__ */ jsx(CheckboxMark, { className: t.check }) });
11673
9929
  return /* @__PURE__ */ jsxs(
11674
9930
  Checkbox$1.Root,
11675
9931
  {
11676
9932
  className: cn(
11677
9933
  SELECTION_CARD_SHELL,
11678
- "items-start gap-3 rounded-md",
9934
+ icon != null ? SELECTION_CARD_LAYOUT.tile : SELECTION_CARD_LAYOUT.row,
11679
9935
  t.card,
11680
9936
  className
11681
9937
  ),
11682
9938
  ...props,
11683
9939
  children: [
11684
- /* @__PURE__ */ jsx("span", { className: "flex h-[1lh] shrink-0 items-center text-title-sm", children: /* @__PURE__ */ jsx("span", { className: cn(CHECK_BOX, t.box), children: /* @__PURE__ */ jsx(CheckboxMark, { className: t.check }) }) }),
11685
- icon != null ? /* @__PURE__ */ jsx("span", { className: "flex h-[1lh] shrink-0 items-center text-title-sm", children: /* @__PURE__ */ jsx("span", { className: cn("text-xl leading-none", t.glyph), children: icon }) }) : null,
9940
+ icon != null ? (
9941
+ // The tile, as RadioCard draws it (lib/selection-card).
9942
+ /* @__PURE__ */ jsxs("span", { className: SELECTION_CARD_TILE_HEAD, children: [
9943
+ /* @__PURE__ */ jsx("span", { className: cn(SELECTION_CARD_GLYPH, t.glyph), children: icon }),
9944
+ box
9945
+ ] })
9946
+ ) : (
9947
+ // The box sits on the title's first line — a box `1lh` tall in the
9948
+ // title's tier, centred — rather than nudged down by a fixed margin
9949
+ // that only matched one line height.
9950
+ /* @__PURE__ */ jsx("span", { className: "flex h-[1lh] shrink-0 items-center text-title-sm", children: box })
9951
+ ),
11686
9952
  /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
11687
9953
  /* @__PURE__ */ jsx("span", { className: "text-title-sm", children: title }),
11688
9954
  description ? /* @__PURE__ */ jsx("span", { className: "text-body-sm text-muted-foreground", children: description }) : null
@@ -11770,7 +10036,7 @@ var checkboxCardMeta = {
11770
10036
  {
11771
10037
  name: "icon",
11772
10038
  type: "ReactNode",
11773
- description: "Optional leading glyph (a solid <Icon>); takes the tone colour."
10039
+ description: "Optional glyph (a solid <Icon>) in the tone colour. Its presence switches to the two-dimensional card RadioCard uses \u2014 icon top-left, checkbox top-right, copy below \u2014 so the two cards match in one form."
11774
10040
  },
11775
10041
  {
11776
10042
  name: "tone",
@@ -11883,11 +10149,13 @@ function ThemeSelector({
11883
10149
  render: /* @__PURE__ */ jsx("button", { type: "button" }),
11884
10150
  nativeButton: true,
11885
10151
  className: cn(
11886
- // sable-frame-inner: concentric inside a sable-frame host (a popover, a
11887
- // docked panel); the theme's small radius anywhere else.
10152
+ // sable-frame-inner: concentric inside a sable-frame host (a frame-styled
10153
+ // popover, a docked panel); the theme's small radius anywhere else.
11888
10154
  "flex h-(--control-sm) w-full items-center gap-2 px-1.5 text-start motion-interactive sable-frame-inner",
11889
- "hover:bg-muted focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-ring",
11890
- "aria-checked:bg-muted"
10155
+ // Menu / Select rows' fills and ink, so a theme list beside a
10156
+ // menu reads as the same kind of row.
10157
+ "hover:bg-accent hover:text-accent-foreground focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-ring",
10158
+ "aria-checked:bg-accent aria-checked:text-accent-foreground"
11891
10159
  ),
11892
10160
  children: [
11893
10161
  /* @__PURE__ */ jsxs(
@@ -12039,7 +10307,7 @@ var themeSelectorMeta = {
12039
10307
  bestPractices: [
12040
10308
  {
12041
10309
  guidance: "do",
12042
- description: 'Use layout="list" in a popover, a menu, or a docked panel. Rows sit on the --control-sm ladder and still show each theme\'s swatch strip, so the choice stays visible rather than described. Rows take `sable-frame-inner`, so host the list in a sable-frame (a Popover, or `sable-frame sable-surface sable-surface-popover sable-frame-popover`) and their corners nest concentrically.'
10310
+ description: 'Use layout="list" in a popover, a menu, or a docked panel. Rows sit on the --control-sm ladder and still show each theme\'s swatch strip, so the choice stays visible rather than described. Rows take `sable-frame-inner`, so host the list in a sable-frame (`sable-frame sable-surface sable-surface-popover sable-frame-popover` \u2014 PopoverContent is not one; give it that class list, or its p-4 leaves the rows unnested) and their corners nest concentrically.'
12043
10311
  },
12044
10312
  {
12045
10313
  guidance: "do",
@@ -12386,7 +10654,7 @@ function ShowMore({
12386
10654
  {
12387
10655
  "aria-hidden": true,
12388
10656
  className: cn(
12389
- "pointer-events-none absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-background to-transparent motion-interactive",
10657
+ "pointer-events-none absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-[var(--sable-surface-bg,var(--background))] to-transparent motion-interactive",
12390
10658
  expanded && "opacity-0"
12391
10659
  )
12392
10660
  }
@@ -13044,7 +11312,7 @@ function CommandMenuItem({
13044
11312
  onClick: handleSelect,
13045
11313
  onMouseMove: () => ctx?.setActiveId(id),
13046
11314
  className: cn(
13047
- `flex items-center gap-2 rounded-[max(0px,calc(var(--radius-lg)-var(--cm-inset,--spacing(1))))] px-(--cm-pad,--spacing(2)) py-2 ${tier.body} outline-none motion-interactive aria-selected:bg-accent aria-selected:text-accent-foreground [&_svg]:shrink-0`,
11315
+ `flex items-center gap-2 rounded-[max(0px,calc(var(--radius-lg)-var(--cm-inset,--spacing(1))-1px))] px-(--cm-pad,--spacing(2)) py-2 ${tier.body} outline-none motion-interactive aria-selected:bg-accent aria-selected:text-accent-foreground [&_svg]:shrink-0`,
13048
11316
  disabled ? "cursor-default text-muted-foreground" : "cursor-pointer text-foreground",
13049
11317
  className
13050
11318
  ),
@@ -13163,7 +11431,12 @@ function CommandMenuTrigger({
13163
11431
  keyLabel ? (
13164
11432
  // aria-hidden: the shortcut is announced once, through
13165
11433
  // aria-keyshortcuts, rather than read out as part of the name.
13166
- /* @__PURE__ */ jsx(Kbd, { "aria-hidden": true, className: "shrink-0", children: apple ? `\u2318${keyLabel}` : `Ctrl ${keyLabel}` })
11434
+ // One cap per key (Kbd's own guidance) — "Ctrl K" in a single cap
11435
+ // read as one key.
11436
+ /* @__PURE__ */ jsxs(KbdGroup, { "aria-hidden": true, className: "shrink-0", children: [
11437
+ /* @__PURE__ */ jsx(Kbd, { children: apple ? "\u2318" : "Ctrl" }),
11438
+ /* @__PURE__ */ jsx(Kbd, { children: keyLabel })
11439
+ ] })
13167
11440
  ) : null
13168
11441
  ]
13169
11442
  }
@@ -13367,199 +11640,83 @@ var commandMenuMeta = {
13367
11640
  {
13368
11641
  name: "CommandMenuTrigger",
13369
11642
  type: '{ onOpen?: () => void; placeholder?: string; shortcut?: string | false; size?: "sm" | "md"; disabled?: boolean }',
13370
- description: `The search field in a header that is really a button. Wears SearchInput's shell (border-input, card surface, leading magnifier, muted placeholder) on the control ladder, with a trailing Kbd: \u2318K on macOS, Ctrl K elsewhere. onOpen runs on click and on the shortcut, which it listens for page-wide (useCommandMenuShortcut). shortcut is the key (default "k"); false drops both the hint and the listener. size is sm in a header beside small buttons, md (default) beside Input and SearchInput. It sits outside CommandMenu, so the Dialog returns focus to it on close.`
13371
- },
13372
- {
13373
- name: "useCommandMenuShortcut",
13374
- type: "(onShortcut: (event: KeyboardEvent) => void, key?: string | false) => void",
13375
- description: `The page-wide \u2318K / Ctrl+K listener CommandMenuTrigger installs, for a palette with no visible trigger. Ignored while focus is in a text field, textarea, select or contenteditable (Ctrl+K belongs to the field there) except the palette's own input, so the callback may toggle. It calls preventDefault() on a match and skips an event already prevented, so two listeners on one page fire once. key defaults to "k"; false turns it off. Removed on unmount.`
13376
- },
13377
- {
13378
- name: "CommandMenuPanel",
13379
- type: "{ items?: CommandItem[]; query?: string; onQueryChange?: (query: string) => void; filter?: CommandFilter; listClassName?: string; placeholder?: string; emptyMessage?: string; title?: string; scope?: ReactNode; inputProps?: ComponentProps<'input'>; className?: string; children?: ReactNode }",
13380
- description: "The palette rendered in place: the same search header, filtered and grouped listbox, keyboard model and result-count live region, on a static card (border-border, bg-card, no shadow) instead of in a Dialog. Takes every CommandMenu prop except open, onOpenChange, finalFocus and description; className lands on the card, title names the listbox. Focus is not trapped and the input does not take focus on mount (inputProps.autoFocus opts in). Esc clears the query, and passes through once it is empty, so a Popover around the panel still closes. A chosen row runs its onSelect and the panel stays as it was. The page-wide shortcut does not fire from its input."
13381
- },
13382
- {
13383
- name: "CommandMenuGroup",
13384
- type: "{ label: string; children }",
13385
- description: "A labelled group of command rows."
13386
- },
13387
- {
13388
- name: "CommandMenuItem",
13389
- type: "{ onSelect?: () => void; icon?: string; detail?: ReactNode; hint?: ReactNode; disabled?: boolean; id?: string; children }",
13390
- description: "A selectable row. Self-filters by its text (unless the root was given a filter), runs onSelect, then closes the palette. detail is secondary text after the label; hint is the end-aligned trailing slot \u2014 a shortcut, a count, or a disabled row's reason. A disabled row stays listed, stays announced, and stays reachable by arrow key so its reason can be read, but is inert to Enter and to a click."
13391
- }
13392
- ],
13393
- tokensUsed: [
13394
- "card",
13395
- "card-foreground",
13396
- "border",
13397
- "foreground",
13398
- "muted-foreground",
13399
- "accent",
13400
- "accent-foreground"
13401
- ],
13402
- a11y: [
13403
- "CommandMenuTrigger is a real <button> named by its visible placeholder, with aria-haspopup=dialog and aria-keyshortcuts (Meta+K on macOS, Control+K elsewhere). Its Kbd hint is aria-hidden, so the shortcut is announced once rather than read as part of the name.",
13404
- "The shortcut never fires from inside a text field, textarea, select or contenteditable (Ctrl+K is delete-to-end-of-line in macOS text views), except the palette's own input.",
13405
- "CommandMenuPanel is the same combobox and listbox in the page, with no dialog: it does not trap or steal focus, and Esc clears a non-empty query (stopping there) instead of dismissing. With an empty query Esc is left alone for whatever contains the panel.",
13406
- "Built on Base UI Dialog, so focus trap, scroll lock, and Esc/outside dismissal come for free; the dialog carries a visually-hidden title + description so it is announced.",
13407
- "Follows the ARIA combobox pattern: the search input is role=combobox (aria-autocomplete=list) that owns the role=listbox popup via aria-controls.",
13408
- "ArrowDown/ArrowUp/Home/End and PageUp/PageDown move a virtual aria-activedescendant highlight while focus stays in the input; Enter runs the highlighted command. Arrow keys wrap; page jumps clamp.",
13409
- "Groups expose role=group labelled by their heading; items are role=option and never enter the tab order.",
13410
- "A polite role=status live region announces the result count as the query narrows, so the size of the list is available without reading it.",
13411
- "A disabled row carries aria-disabled and is skipped when seeding the highlight, but stays in the arrow-key walk so its reason can be reached and read.",
13412
- "The component keeps ownership of aria-activedescendant even when a caller claims a key through inputProps.onKeyDown. Skipping a move is coherent (they took responsibility for that key), but nothing may leave the active id naming a row that is not painted as active \u2014 so the opt-out returns before any highlight change rather than after it.",
13413
- "Exactly one row is ever painted as active: the pointer takes the highlight over (moving onto a row makes it the aria-activedescendant) rather than adding a hover fill of its own, so what looks highlighted is always what Enter will run."
13414
- ],
13415
- examples: [
13416
- {
13417
- label: "Data-driven",
13418
- code: "<CommandMenu open={open} onOpenChange={setOpen} items={[{ group: 'Navigation', label: 'Go to feed', icon: 'home', onSelect: () => {} }, { group: 'Actions', label: 'New post', onSelect: () => {} }]} />"
13419
- },
13420
- {
13421
- label: "Declarative",
13422
- code: "<CommandMenu open={open} onOpenChange={setOpen}><CommandMenuGroup label='Navigation'><CommandMenuItem icon='home' onSelect={() => {}}>Go to feed</CommandMenuItem></CommandMenuGroup></CommandMenu>"
13423
- },
13424
- {
13425
- label: "Grouped launcher",
13426
- code: '<CommandMenu open={open} onOpenChange={setOpen}><CommandMenuGroup label="Create"><CommandMenuItem icon="plus" onSelect={() => createPost()}>New post</CommandMenuItem><CommandMenuItem icon="sparkles" onSelect={() => createEdition()}>New edition</CommandMenuItem></CommandMenuGroup><CommandMenuGroup label="Navigation"><CommandMenuItem icon="home" onSelect={() => navigate("/")}>Home</CommandMenuItem><CommandMenuItem icon="gear" onSelect={() => navigate("/settings")}>Settings</CommandMenuItem></CommandMenuGroup></CommandMenu>'
13427
- },
13428
- {
13429
- label: "Consumer-owned ranking",
13430
- code: "<CommandMenu open={open} onOpenChange={setOpen} query={query} onQueryChange={setQuery} filter={(items, q) => rank(items, q)} items={items} />"
13431
- },
13432
- {
13433
- label: "Listed but unavailable",
13434
- code: "<CommandMenu open={open} onOpenChange={setOpen} items={[{ group: 'Layers', label: 'Align left', disabled: true, hint: 'Select another layer to align to', onSelect: () => {} }]} />"
13435
- },
13436
- {
13437
- label: "Inline panel",
13438
- code: `<CommandMenuPanel className="w-80" items={[{ group: 'Create', label: 'New project', icon: 'plus', hint: <Kbd>N</Kbd>, onSelect: () => {} }, { group: 'Settings', label: 'Open settings', icon: 'gear', onSelect: () => {} }]} />`
13439
- },
13440
- {
13441
- label: "Header search trigger",
13442
- code: '<CommandMenuTrigger size="sm" placeholder="Search docs" onOpen={() => setOpen(true)} className="w-56" />\n<CommandMenu open={open} onOpenChange={setOpen} items={items} />'
13443
- },
13444
- {
13445
- label: "Keyboard shortcut with no visible trigger",
13446
- code: "useCommandMenuShortcut(() => setOpen((value) => !value))\n<CommandMenu open={open} onOpenChange={setOpen} items={items} />"
13447
- }
13448
- ]
13449
- };
13450
- function DotsMenu({
13451
- children,
13452
- align = "end",
13453
- label = "More actions",
13454
- icon = "ellipsis",
13455
- size = "icon"
13456
- }) {
13457
- return /* @__PURE__ */ jsxs(Menu, { children: [
13458
- /* @__PURE__ */ jsx(
13459
- MenuTrigger,
13460
- {
13461
- render: /* @__PURE__ */ jsx(Button, { variant: "ghost", size, "aria-label": label, children: /* @__PURE__ */ jsx(Icon, { name: icon }) })
13462
- }
13463
- ),
13464
- /* @__PURE__ */ jsx(MenuContent, { align, children })
13465
- ] });
13466
- }
13467
- DotsMenu.displayName = "DotsMenu";
13468
-
13469
- // src/components/dots-menu/dots-menu.meta.ts
13470
- var dotsMenuMeta = {
13471
- name: "DotsMenu",
13472
- summary: "An overflow ellipsis icon button that opens a Menu. Composes our Menu + Button + Icon \u2014 pass MenuItems as children and it wires the ghost icon trigger, accessible label, and portal-rendered content. All keyboard, focus, and dismissal behavior comes through Menu.",
13473
- status: "stable",
13474
- keywords: [
13475
- "dots menu",
13476
- "more actions",
13477
- "overflow",
13478
- "kebab"
13479
- ],
13480
- usage: {
13481
- description: "Use DotsMenu for compact overflow actions when the primary action is already visible elsewhere.",
13482
- bestPractices: [
13483
- {
13484
- guidance: "do",
13485
- description: "Use it for secondary commands that belong to a row, card, or toolbar item."
13486
- },
13487
- {
13488
- guidance: "do",
13489
- description: "Keep labels explicit so the icon-only trigger does not obscure the command meaning."
13490
- },
13491
- {
13492
- guidance: "dont",
13493
- description: "Do not hide the most important or most frequent action inside DotsMenu."
13494
- },
13495
- {
13496
- guidance: "dont",
13497
- description: "Do not use it as the only affordance on a touch surface. It is revealed on hover in most layouts, and hover does not exist on touch \u2014 pair it with a visible action or make it always-visible there."
13498
- }
13499
- ]
13500
- },
13501
- related: {
13502
- components: [
13503
- "Menu",
13504
- "ContextMenu"
13505
- ]
13506
- },
13507
- props: [
13508
- {
13509
- name: "children",
13510
- type: "ReactNode",
13511
- default: "\u2014",
13512
- description: "The menu rows \u2014 MenuItem / MenuSeparator / MenuGroup, etc. Rendered inside MenuContent."
11643
+ description: `The search field in a header that is really a button. Wears SearchInput's shell (border-input, card surface, leading magnifier, muted placeholder) on the control ladder, with a trailing Kbd: \u2318K on macOS, Ctrl K elsewhere. onOpen runs on click and on the shortcut, which it listens for page-wide (useCommandMenuShortcut). shortcut is the key (default "k"); false drops both the hint and the listener. size is sm in a header beside small buttons, md (default) beside Input and SearchInput. It sits outside CommandMenu, so the Dialog returns focus to it on close.`
13513
11644
  },
13514
11645
  {
13515
- name: "align",
13516
- type: "'start' | 'center' | 'end'",
13517
- default: "end",
13518
- description: "Alignment of the popup along its side. Overflow menus typically right-align under the trigger; 'start' anchors the popup's left edge to the trigger instead."
11646
+ name: "useCommandMenuShortcut",
11647
+ type: "(onShortcut: (event: KeyboardEvent) => void, key?: string | false) => void",
11648
+ description: `The page-wide \u2318K / Ctrl+K listener CommandMenuTrigger installs, for a palette with no visible trigger. Ignored while focus is in a text field, textarea, select or contenteditable (Ctrl+K belongs to the field there) except the palette's own input, so the callback may toggle. It calls preventDefault() on a match and skips an event already prevented, so two listeners on one page fire once. key defaults to "k"; false turns it off. Removed on unmount.`
13519
11649
  },
13520
11650
  {
13521
- name: "label",
13522
- type: "string",
13523
- default: "More actions",
13524
- description: "Accessible label (aria-label) for the icon-only trigger button."
11651
+ name: "CommandMenuPanel",
11652
+ type: "{ items?: CommandItem[]; query?: string; onQueryChange?: (query: string) => void; filter?: CommandFilter; listClassName?: string; placeholder?: string; emptyMessage?: string; title?: string; scope?: ReactNode; inputProps?: ComponentProps<'input'>; className?: string; children?: ReactNode }",
11653
+ description: "The palette rendered in place: the same search header, filtered and grouped listbox, keyboard model and result-count live region, on a static card (border-border, bg-card, no shadow) instead of in a Dialog. Takes every CommandMenu prop except open, onOpenChange, finalFocus and description; className lands on the card, title names the listbox. Focus is not trapped and the input does not take focus on mount (inputProps.autoFocus opts in). Esc clears the query, and passes through once it is empty, so a Popover around the panel still closes. A chosen row runs its onSelect and the panel stays as it was. The page-wide shortcut does not fire from its input."
13525
11654
  },
13526
11655
  {
13527
- name: "icon",
13528
- type: "string",
13529
- default: "ellipsis",
13530
- description: "Semantic icon name rendered in the trigger."
11656
+ name: "CommandMenuGroup",
11657
+ type: "{ label: string; children }",
11658
+ description: "A labelled group of command rows."
13531
11659
  },
13532
11660
  {
13533
- name: "size",
13534
- type: "'icon' | 'icon-sm' | 'icon-xs'",
13535
- default: "icon",
13536
- description: "The trigger's rung on the icon-button ladder. 'icon' for a card or page header; 'icon-sm' or 'icon-xs' in a list row or dense toolbar, so the trigger does not set the row height."
11661
+ name: "CommandMenuItem",
11662
+ type: "{ onSelect?: () => void; icon?: string; detail?: ReactNode; hint?: ReactNode; disabled?: boolean; id?: string; children }",
11663
+ description: "A selectable row. Self-filters by its text (unless the root was given a filter), runs onSelect, then closes the palette. detail is secondary text after the label; hint is the end-aligned trailing slot \u2014 a shortcut, a count, or a disabled row's reason. A disabled row stays listed, stays announced, and stays reachable by arrow key so its reason can be read, but is inert to Enter and to a click."
13537
11664
  }
13538
11665
  ],
13539
- variants: {
13540
- size: ["icon", "icon-sm", "icon-xs"]
13541
- },
13542
11666
  tokensUsed: [
13543
- "popover",
13544
- "popover-foreground",
11667
+ "card",
11668
+ "card-foreground",
13545
11669
  "border",
11670
+ "foreground",
11671
+ "muted-foreground",
13546
11672
  "accent",
13547
- "accent-foreground",
13548
- "muted-foreground"
11673
+ "accent-foreground"
13549
11674
  ],
13550
11675
  a11y: [
13551
- "The icon-only trigger is labeled via aria-label (label prop, default 'More actions').",
13552
- "Menu provides role=menu/menuitem, roving tabindex, arrow-key navigation, and typeahead via Base UI.",
13553
- "Esc and outside-press dismiss; focus returns to the trigger on close."
11676
+ "CommandMenuTrigger is a real <button> named by its visible placeholder, with aria-haspopup=dialog and aria-keyshortcuts (Meta+K on macOS, Control+K elsewhere). Its Kbd hint is aria-hidden, so the shortcut is announced once rather than read as part of the name.",
11677
+ "The shortcut never fires from inside a text field, textarea, select or contenteditable (Ctrl+K is delete-to-end-of-line in macOS text views), except the palette's own input.",
11678
+ "CommandMenuPanel is the same combobox and listbox in the page, with no dialog: it does not trap or steal focus, and Esc clears a non-empty query (stopping there) instead of dismissing. With an empty query Esc is left alone for whatever contains the panel.",
11679
+ "Built on Base UI Dialog, so focus trap, scroll lock, and Esc/outside dismissal come for free; the dialog carries a visually-hidden title + description so it is announced.",
11680
+ "Follows the ARIA combobox pattern: the search input is role=combobox (aria-autocomplete=list) that owns the role=listbox popup via aria-controls.",
11681
+ "ArrowDown/ArrowUp/Home/End and PageUp/PageDown move a virtual aria-activedescendant highlight while focus stays in the input; Enter runs the highlighted command. Arrow keys wrap; page jumps clamp.",
11682
+ "Groups expose role=group labelled by their heading; items are role=option and never enter the tab order.",
11683
+ "A polite role=status live region announces the result count as the query narrows, so the size of the list is available without reading it.",
11684
+ "A disabled row carries aria-disabled and is skipped when seeding the highlight, but stays in the arrow-key walk so its reason can be reached and read.",
11685
+ "The component keeps ownership of aria-activedescendant even when a caller claims a key through inputProps.onKeyDown. Skipping a move is coherent (they took responsibility for that key), but nothing may leave the active id naming a row that is not painted as active \u2014 so the opt-out returns before any highlight change rather than after it.",
11686
+ "Exactly one row is ever painted as active: the pointer takes the highlight over (moving onto a row makes it the aria-activedescendant) rather than adding a hover fill of its own, so what looks highlighted is always what Enter will run."
13554
11687
  ],
13555
11688
  examples: [
13556
11689
  {
13557
- label: "Basic",
13558
- code: '<DotsMenu label="Post actions"><MenuItem>Edit</MenuItem><MenuItem>Share</MenuItem><MenuSeparator /><MenuItem variant="destructive">Delete</MenuItem></DotsMenu>'
11690
+ label: "Data-driven",
11691
+ code: "<CommandMenu open={open} onOpenChange={setOpen} items={[{ group: 'Navigation', label: 'Go to feed', icon: 'home', onSelect: () => {} }, { group: 'Actions', label: 'New post', onSelect: () => {} }]} />"
11692
+ },
11693
+ {
11694
+ label: "Declarative",
11695
+ code: "<CommandMenu open={open} onOpenChange={setOpen}><CommandMenuGroup label='Navigation'><CommandMenuItem icon='home' onSelect={() => {}}>Go to feed</CommandMenuItem></CommandMenuGroup></CommandMenu>"
11696
+ },
11697
+ {
11698
+ label: "Grouped launcher",
11699
+ code: '<CommandMenu open={open} onOpenChange={setOpen}><CommandMenuGroup label="Create"><CommandMenuItem icon="plus" onSelect={() => createPost()}>New post</CommandMenuItem><CommandMenuItem icon="sparkles" onSelect={() => createEdition()}>New edition</CommandMenuItem></CommandMenuGroup><CommandMenuGroup label="Navigation"><CommandMenuItem icon="home" onSelect={() => navigate("/")}>Home</CommandMenuItem><CommandMenuItem icon="gear" onSelect={() => navigate("/settings")}>Settings</CommandMenuItem></CommandMenuGroup></CommandMenu>'
11700
+ },
11701
+ {
11702
+ label: "Consumer-owned ranking",
11703
+ code: "<CommandMenu open={open} onOpenChange={setOpen} query={query} onQueryChange={setQuery} filter={(items, q) => rank(items, q)} items={items} />"
11704
+ },
11705
+ {
11706
+ label: "Listed but unavailable",
11707
+ code: "<CommandMenu open={open} onOpenChange={setOpen} items={[{ group: 'Layers', label: 'Align left', disabled: true, hint: 'Select another layer to align to', onSelect: () => {} }]} />"
11708
+ },
11709
+ {
11710
+ label: "Inline panel",
11711
+ code: `<CommandMenuPanel className="w-80" items={[{ group: 'Create', label: 'New project', icon: 'plus', hint: <Kbd>N</Kbd>, onSelect: () => {} }, { group: 'Settings', label: 'Open settings', icon: 'gear', onSelect: () => {} }]} />`
11712
+ },
11713
+ {
11714
+ label: "Header search trigger",
11715
+ code: '<CommandMenuTrigger size="sm" placeholder="Search docs" onOpen={() => setOpen(true)} className="w-56" />\n<CommandMenu open={open} onOpenChange={setOpen} items={items} />'
13559
11716
  },
13560
11717
  {
13561
- label: "With leading icons",
13562
- code: '<DotsMenu><MenuItem><Icon name="check" className="text-sm" />Mark as read</MenuItem><MenuSeparator /><MenuItem variant="destructive"><Icon name="xmark" className="text-sm" />Delete</MenuItem></DotsMenu>'
11718
+ label: "Keyboard shortcut with no visible trigger",
11719
+ code: "useCommandMenuShortcut(() => setOpen((value) => !value))\n<CommandMenu open={open} onOpenChange={setOpen} items={items} />"
13563
11720
  }
13564
11721
  ]
13565
11722
  };
@@ -13711,7 +11868,7 @@ var contextCardMeta = {
13711
11868
  },
13712
11869
  {
13713
11870
  label: "With stat row",
13714
- code: "<ContextCard><ContextCardTrigger render={<a href='/u/nyx' className='text-title-sm underline-offset-2 hover:underline'>@nyx</a>} /><ContextCardContent><Entity className='[--entity-px:0] [--entity-py:0]' leading={<Avatar fallback='NY' size='lg' />} title='Nyx' subtitle='@nyx' /><Description cols='2' className='mt-4 border-t border-border pt-3'><DescriptionItem term='Posts' detail='128' /><DescriptionItem term='Followers' detail='4.2k' /></Description></ContextCardContent></ContextCard>"
11871
+ code: "<ContextCard><ContextCardTrigger render={<a href='/u/nyx' className='text-title-sm underline-offset-2 hover:underline'>@nyx</a>} /><ContextCardContent><Entity className='[--entity-px:0] [--entity-py:0]' leading={<Avatar fallback='NY' size='lg' />} title='Nyx' subtitle='@nyx' /><div className='@container mt-4 border-t border-border pt-3'><dl className='sable-list-dl sable-list-dl-2'><dt>Posts</dt><dd>128</dd><dt>Followers</dt><dd>4.2k</dd></dl></div></ContextCardContent></ContextCard>"
13715
11872
  }
13716
11873
  ]
13717
11874
  };
@@ -14115,359 +12272,6 @@ var scrollerMeta = {
14115
12272
  }
14116
12273
  ]
14117
12274
  };
14118
- function DestructiveActionModal({
14119
- open,
14120
- onOpenChange,
14121
- title,
14122
- description,
14123
- confirmLabel = "Delete",
14124
- cancelLabel = "Cancel",
14125
- onConfirm,
14126
- pending,
14127
- confirmingLabel,
14128
- children,
14129
- confirmText
14130
- }) {
14131
- const [typed, setTyped] = useState("");
14132
- const [internalBusy, setInternalBusy] = useState(false);
14133
- const inFlight = useRef(false);
14134
- const cancelRef = useRef(null);
14135
- const controlledPending = pending !== void 0;
14136
- const busy = pending ?? internalBusy;
14137
- useEffect(() => {
14138
- if (!open) {
14139
- setTyped("");
14140
- }
14141
- }, [open]);
14142
- const requiresMatch = typeof confirmText === "string" && confirmText.length > 0;
14143
- const matched = !requiresMatch || typed === confirmText;
14144
- async function handleConfirm() {
14145
- if (!matched || busy || inFlight.current) {
14146
- return;
14147
- }
14148
- inFlight.current = true;
14149
- try {
14150
- const result = onConfirm();
14151
- if (controlledPending) {
14152
- return;
14153
- }
14154
- if (result instanceof Promise) {
14155
- setInternalBusy(true);
14156
- try {
14157
- await result;
14158
- setInternalBusy(false);
14159
- onOpenChange(false);
14160
- } catch {
14161
- setInternalBusy(false);
14162
- }
14163
- } else {
14164
- onOpenChange(false);
14165
- }
14166
- } finally {
14167
- inFlight.current = false;
14168
- }
14169
- }
14170
- function handleOpenChange(next) {
14171
- if (busy && !next) {
14172
- return;
14173
- }
14174
- onOpenChange(next);
14175
- }
14176
- return /* @__PURE__ */ jsx(AlertDialog, { open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxs(AlertDialogContent, { initialFocus: cancelRef, children: [
14177
- /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
14178
- /* @__PURE__ */ jsx(AlertDialogTitle, { children: title }),
14179
- description ? /* @__PURE__ */ jsx(AlertDialogDescription, { children: description }) : null
14180
- ] }),
14181
- children != null || requiresMatch ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
14182
- children,
14183
- requiresMatch ? /* @__PURE__ */ jsx(
14184
- Field,
14185
- {
14186
- label: /* @__PURE__ */ jsxs(Fragment$1, { children: [
14187
- "Type ",
14188
- /* @__PURE__ */ jsx(Code, { children: confirmText }),
14189
- " to confirm"
14190
- ] }),
14191
- children: /* @__PURE__ */ jsx(
14192
- Input,
14193
- {
14194
- value: typed,
14195
- onChange: (event) => setTyped(event.target.value),
14196
- placeholder: confirmText,
14197
- autoComplete: "off",
14198
- autoCapitalize: "off",
14199
- spellCheck: false
14200
- }
14201
- )
14202
- }
14203
- ) : null
14204
- ] }) : null,
14205
- /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [
14206
- /* @__PURE__ */ jsx(
14207
- AlertDialogClose,
14208
- {
14209
- render: /* @__PURE__ */ jsx(Button, { ref: cancelRef, variant: "secondary", disabled: busy, children: cancelLabel })
14210
- }
14211
- ),
14212
- /* @__PURE__ */ jsx(
14213
- Button,
14214
- {
14215
- variant: "destructive",
14216
- loading: busy,
14217
- loadingLabel: confirmingLabel ?? confirmLabel,
14218
- disabled: !matched,
14219
- onClick: handleConfirm,
14220
- children: confirmLabel
14221
- }
14222
- )
14223
- ] })
14224
- ] }) });
14225
- }
14226
- DestructiveActionModal.displayName = "DestructiveActionModal";
14227
-
14228
- // src/components/destructive-action-modal/destructive-action-modal.meta.ts
14229
- var destructiveActionModalMeta = {
14230
- name: "DestructiveActionModal",
14231
- summary: "Confirmation modal for irreversible actions, built on AlertDialog (role=alertdialog; a backdrop click does not dismiss). Pairs a secondary Cancel with a destructive confirm; awaits an async onConfirm (spinner + stays open on failure) or takes an external pending flag; optional body slot and type-to-confirm guard for high-stakes deletions.",
14232
- status: "stable",
14233
- keywords: [
14234
- "destructive",
14235
- "modal",
14236
- "confirmation",
14237
- "danger"
14238
- ],
14239
- usage: {
14240
- description: "Use DestructiveActionModal to confirm irreversible or high-risk actions before they run.",
14241
- bestPractices: [
14242
- {
14243
- guidance: "do",
14244
- description: "Name the affected object and the consequence of the action in plain language."
14245
- },
14246
- {
14247
- guidance: "do",
14248
- description: "Make the destructive action visually distinct from the cancel path."
14249
- },
14250
- {
14251
- guidance: "dont",
14252
- description: "Do not use it for routine low-risk actions where undo or inline confirmation would be faster."
14253
- },
14254
- {
14255
- guidance: "dont",
14256
- description: "Do not put the destructive action where Cancel usually sits, or make it the default focus. A confirmation dialog exists to add friction; autofocusing Delete removes the friction it was added for."
14257
- }
14258
- ]
14259
- },
14260
- related: {
14261
- components: [
14262
- "Dialog",
14263
- "Button",
14264
- "Banner"
14265
- ]
14266
- },
14267
- props: [
14268
- {
14269
- name: "open",
14270
- type: "boolean",
14271
- description: "Controlled open state."
14272
- },
14273
- {
14274
- name: "onOpenChange",
14275
- type: "(open: boolean) => void",
14276
- description: "Called when the open state should change (Esc, Cancel, or confirm). A backdrop click does not dismiss."
14277
- },
14278
- {
14279
- name: "title",
14280
- type: "ReactNode",
14281
- description: "Modal heading \u2014 names the irreversible action."
14282
- },
14283
- {
14284
- name: "description",
14285
- type: "ReactNode",
14286
- description: "Optional supporting copy explaining the consequence."
14287
- },
14288
- {
14289
- name: "confirmLabel",
14290
- type: "string",
14291
- default: '"Delete"',
14292
- description: "Label for the destructive confirm button."
14293
- },
14294
- {
14295
- name: "cancelLabel",
14296
- type: "string",
14297
- default: '"Cancel"',
14298
- description: "Label for the dismissing secondary button."
14299
- },
14300
- {
14301
- name: "onConfirm",
14302
- type: "() => void | Promise<void>",
14303
- description: "Called when the user confirms. If it returns a Promise the modal shows an in-flight spinner, closes on resolve, and stays open on reject; if void it closes immediately (unless pending is controlled)."
14304
- },
14305
- {
14306
- name: "pending",
14307
- type: "boolean",
14308
- description: "Externally-driven in-flight state. Overrides the internal await tracking; the parent owns closing (the modal stays open on a synchronous confirm)."
14309
- },
14310
- {
14311
- name: "confirmingLabel",
14312
- type: "string",
14313
- default: "confirmLabel",
14314
- description: "Confirm-button label shown while the action is in flight."
14315
- },
14316
- {
14317
- name: "children",
14318
- type: "ReactNode",
14319
- description: "Body content rendered between the description and the type-to-confirm Input / footer \u2014 e.g. a warning <Note>."
14320
- },
14321
- {
14322
- name: "confirmText",
14323
- type: "string",
14324
- description: "When set, renders an Input the user must type matching to enable the destructive confirm button."
14325
- }
14326
- ],
14327
- tokensUsed: [
14328
- "card",
14329
- "card-foreground",
14330
- "border",
14331
- "muted-foreground",
14332
- "foreground",
14333
- "input",
14334
- "destructive",
14335
- "destructive-foreground",
14336
- "secondary",
14337
- "secondary-foreground"
14338
- ],
14339
- a11y: [
14340
- `Built on AlertDialog (Base UI): role="alertdialog", focus trapping, scroll lock, and aria-modal. Esc dismisses; a click on the backdrop does not, so a stray click can't throw away the decision.`,
14341
- "Focus opens on Cancel \u2014 the safe action \u2014 even when a type-to-confirm Input comes first, so a reflexive Enter never fires the destructive action.",
14342
- "The title labels the modal; the description describes it.",
14343
- "The type-to-confirm Input is wired to its label via htmlFor; the destructive Button is disabled (not just hidden) until the text matches.",
14344
- "While a confirm is in flight (awaited onConfirm or pending), both buttons disable, the confirm Button sets aria-busy, and Esc dismissal is blocked so the action can't be interrupted or double-fired."
14345
- ],
14346
- examples: [
14347
- {
14348
- label: "Basic",
14349
- code: "<DestructiveActionModal open={open} onOpenChange={setOpen} title='Delete post?' description='This permanently removes the post. This cannot be undone.' onConfirm={handleDelete} />"
14350
- },
14351
- {
14352
- label: "Async confirm (awaited spinner)",
14353
- code: "<DestructiveActionModal open={open} onOpenChange={setOpen} title='Block user?' description='They can no longer interact with you.' confirmLabel='Block' confirmingLabel='Blocking\u2026' onConfirm={() => blockUser(id)} />"
14354
- },
14355
- {
14356
- label: "Body slot + type-to-confirm",
14357
- code: "<DestructiveActionModal open={open} onOpenChange={setOpen} title='Delete this post?' confirmText='gold rush' confirmLabel='Delete forever' onConfirm={handleDelete}><Note tone='warning'>Collectors who minted this edition keep their copies.</Note></DestructiveActionModal>"
14358
- }
14359
- ]
14360
- };
14361
- function Browser({ url, className, children, ...props }) {
14362
- return /* @__PURE__ */ jsxs(
14363
- "div",
14364
- {
14365
- className: cn("overflow-hidden rounded-xl border border-border bg-card shadow-sm", className),
14366
- ...props,
14367
- children: [
14368
- /* @__PURE__ */ jsxs("div", { "aria-hidden": true, className: "grid h-11 grid-cols-[1fr_minmax(0,28rem)_1fr] items-center gap-3 border-b border-border px-3", children: [
14369
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
14370
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
14371
- /* @__PURE__ */ jsx("span", { className: "size-3 rounded-full bg-muted" }),
14372
- /* @__PURE__ */ jsx("span", { className: "size-3 rounded-full bg-muted" }),
14373
- /* @__PURE__ */ jsx("span", { className: "size-3 rounded-full bg-muted" })
14374
- ] }),
14375
- /* @__PURE__ */ jsxs("div", { className: "hidden items-center gap-2.5 text-muted-foreground sm:flex", children: [
14376
- /* @__PURE__ */ jsx(Icon, { name: "chevron-left", className: "text-sm" }),
14377
- /* @__PURE__ */ jsx(Icon, { name: "chevron-right", className: "text-sm opacity-50" }),
14378
- /* @__PURE__ */ jsx(Icon, { name: "refresh", className: "text-sm" })
14379
- ] })
14380
- ] }),
14381
- /* @__PURE__ */ jsxs("div", { className: "flex h-7 min-w-0 items-center justify-center gap-1.5 rounded-md bg-muted px-3 text-muted-foreground", children: [
14382
- url ? /* @__PURE__ */ jsx(Icon, { name: "lock", className: "shrink-0 text-xs" }) : null,
14383
- /* @__PURE__ */ jsx("span", { className: "truncate text-body-sm", children: url ?? "" })
14384
- ] }),
14385
- /* @__PURE__ */ jsx("div", {})
14386
- ] }),
14387
- /* @__PURE__ */ jsx("div", { className: "bg-background", children })
14388
- ]
14389
- }
14390
- );
14391
- }
14392
- Browser.displayName = "Browser";
14393
-
14394
- // src/components/browser/browser.meta.ts
14395
- var browserMeta = {
14396
- name: "Browser",
14397
- summary: "A generic, current browser window: one compact toolbar (window controls, back/forward/reload, a centered address field) around page content.",
14398
- status: "stable",
14399
- keywords: [
14400
- "browser",
14401
- "mockup",
14402
- "preview",
14403
- "website"
14404
- ],
14405
- usage: {
14406
- description: "Use Browser to frame a website or product surface preview when the browser chrome is part of the story.",
14407
- bestPractices: [
14408
- {
14409
- guidance: "do",
14410
- description: "Use it for documentation, product examples, and visual previews where the page context matters."
14411
- },
14412
- {
14413
- guidance: "do",
14414
- description: "Keep the framed content simple enough to remain legible at the rendered size."
14415
- },
14416
- {
14417
- guidance: "dont",
14418
- description: "Do not use Browser for real navigation, real forms, or arbitrary card decoration."
14419
- },
14420
- {
14421
- guidance: "dont",
14422
- description: "Do not put a real, working URL in the address pill. It reads as a link, is not one, and invites a click that does nothing."
14423
- }
14424
- ]
14425
- },
14426
- related: {
14427
- components: [
14428
- "Phone",
14429
- "Card"
14430
- ]
14431
- },
14432
- props: [
14433
- {
14434
- name: "url",
14435
- type: "string",
14436
- description: "Address shown in the centered address pill. Omit for a blank pill."
14437
- },
14438
- {
14439
- name: "children",
14440
- type: "ReactNode",
14441
- description: "Page content rendered inside the content region."
14442
- }
14443
- ],
14444
- tokensUsed: ["card", "background", "border", "muted", "muted-foreground"],
14445
- a11y: [
14446
- "Purely presentational chrome; the framed content carries its own semantics.",
14447
- "The toolbar is drawn, not interactive, and hidden from assistive technology; the address is decorative text."
14448
- ],
14449
- examples: [
14450
- {
14451
- label: "Framing a page",
14452
- code: '<Browser url="studio.example/explore"><div className="p-8"><PageHeader title="Explore" description="Trending posts from every chain." /></div></Browser>'
14453
- },
14454
- {
14455
- label: "Mockup, not chrome",
14456
- code: `// Browser is DECORATIVE framing for a screenshot or a demo \u2014 it belongs on a
14457
- // marketing page, never around your actual app.
14458
-
14459
- <Region max="wide">
14460
- <Browser url="studio.example">
14461
- <img src="/screenshots/editor.png" alt="The editor" />
14462
- </Browser>
14463
- </Region>
14464
-
14465
- // \u2717 Wrapping live UI in it. The frame implies "this is a picture of software";
14466
- // putting real controls inside makes users hunt for a browser that isn't there.
14467
- // \u2717 Using it to fake a URL bar for credibility. Show the real product.`
14468
- }
14469
- ]
14470
- };
14471
12275
  var PLAY_MARK = "flex size-14 items-center justify-center rounded-full bg-overlay/85 text-overlay-foreground shadow-sm";
14472
12276
  function Video({
14473
12277
  src,
@@ -14809,7 +12613,7 @@ function Marquee({
14809
12613
  size: "icon-xs",
14810
12614
  onClick: togglePlay,
14811
12615
  "aria-label": isPaused ? "Play" : "Pause",
14812
- className: "absolute right-1 top-1 z-10",
12616
+ className: "absolute end-2 top-2 z-10",
14813
12617
  children: /* @__PURE__ */ jsx(PlayPauseGlyph, { playing: !isPaused })
14814
12618
  }
14815
12619
  ),
@@ -15457,6 +13261,6 @@ var carouselMeta = {
15457
13261
  };
15458
13262
 
15459
13263
  // src/index.ts
15460
- var SABLE_UI_VERSION = "0.1.0";
13264
+ var SABLE_UI_VERSION = "0.2.0";
15461
13265
 
15462
- export { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, AlertDialog, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, Banner, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, Breadcrumbs, Browser, COMPONENT_GROUPS, COMPONENT_TAXONOMY, Calendar, Carousel, Checkbox, CheckboxCard, CheckboxGroup, Code, Collapsible, CollapsibleIndicator, CollapsiblePanel, CollapsibleTrigger, ColorField, Combobox, ComboboxChips, ComboboxClear, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, ComboboxItemIndicator, ComboboxList, ComboboxPortal, ComboboxValue, ComboboxVirtualList, CommandMenu, CommandMenuGroup, CommandMenuItem, CommandMenuPanel, CommandMenuTrigger, ContextCard, ContextCardArrow, ContextCardContent, ContextCardPortal, ContextCardTrigger, ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuGroupLabel, ContextMenuItem, ContextMenuPortal, ContextMenuSeparator, ContextMenuTrigger, DatePicker, DestructiveActionModal, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DotsMenu, EmptyState, Feedback, FileDropzone, GlidingHighlight, InlineStatus, Kbd, Marquee, Meter, MeterIndicator, MeterLabel, MeterTrack, MeterValue, MiddleTruncate, Note, NotificationBadge, NumberField, OtpField, PageLoader, Pagination, Panel, PanelGroup, PanelResizer, PanelSection, Popover, PopoverArrow, PopoverClose, PopoverContent, PopoverDescription, PopoverPortal, PopoverTitle, PopoverTrigger, PropertyPair, PropertyRow, Radio, RadioCard, RadioGroup, SABLE_UI_VERSION, STATUS_TONES, STATUS_TONE_ICON, Scroller, ScrubField, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetPortal, SheetTitle, SheetTrigger, ShowMore, SideNav, SideNavGroup, SideNavItem, Skeleton, Slider, SliderControl, SliderIndicator, SliderRoot, SliderThumb, SliderTrack, SliderValue, StatusBar, StatusBarGroup, StatusBarSeparator, Stepper, StepperItem, TRANSPARENT, Tabs, TabsPanel as TabsContent, TabsList, TabsPanel, TabsTrigger, ThemeSelector, ThemeSwitcher, ThinkingOrb, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastRoot, ToastTitle, ToastViewport, Toggle, ToggleGroup, Toggle as ToggleGroupItem, Toolbar, ToolbarButton, ToolbarGroup, ToolbarInput, ToolbarLink, ToolbarSeparator, Tree, TreeAddRow, TreeEmpty, TreeRow, TreeSection, Video, Workspace, WorkspaceBar, WorkspaceCanvas, accordionMeta, bannerMeta, bannerVariants, breadcrumbsMeta, browserMeta, calendarMeta, carouselMeta, checkboxCardMeta, checkboxMeta, codeMeta, collapsibleMeta, colorFieldMeta, comboboxMeta, commandMenuMeta, componentGuidance, componentLabel, componentSlug, componentsInGroup, contextCardMeta, contextMenuMeta, datePickerMeta, destructiveActionModalMeta, dialogMeta, dotsMenuMeta, emptyStateMeta, feedbackMeta, fileDropzoneMeta, fileDropzoneVariants, formatFileSize, formatHex, glidingHighlightMeta, hsvToRgb, hueColor, inlineStatusMeta, isTransparent, kbdMeta, marqueeMeta, meterIndicatorVariants, meterMeta, meterTrackVariants, middleTruncateMeta, noteMeta, noteVariants, notificationBadgeMeta, notificationCountVariants, notificationDotVariants, numberFieldMeta, otpFieldMeta, pageLoaderMeta, paginationMeta, panelGroupMeta, panelSectionMeta, parseHex, pillMeta, popoverMeta, radioCardMeta, radioMeta, rgbToHsv, scrollerMeta, scrubFieldMeta, sheetMeta, sheetVariants, showMoreMeta, sideNavMeta, skeletonMeta, sliderMeta, statusBarMeta, stepperConnectorVariants, stepperIndicatorVariants, stepperMeta, tabsMeta, themeSelectorMeta, themeSwitcherMeta, thinkingOrbMeta, toast, toastMeta, toggleMeta, toolbarGroupVariants, toolbarMeta, toolbarSeparatorVariants, toolbarVariants, treeIndent, treeMeta, useComboboxFilteredItems, useCommandMenuShortcut, usePanelWidth, useToast, useToastManager, videoMeta, workspaceMeta };
13266
+ export { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, Banner, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, Breadcrumbs, COMPONENT_GROUPS, COMPONENT_TAXONOMY, Calendar, Carousel, Checkbox, CheckboxCard, CheckboxGroup, Collapsible, CollapsibleIndicator, CollapsiblePanel, CollapsibleTrigger, ColorField, Combobox, ComboboxChips, ComboboxClear, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, ComboboxItemIndicator, ComboboxList, ComboboxPortal, ComboboxValue, ComboboxVirtualList, CommandMenu, CommandMenuGroup, CommandMenuItem, CommandMenuPanel, CommandMenuTrigger, ContextCard, ContextCardArrow, ContextCardContent, ContextCardPortal, ContextCardTrigger, ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuGroupLabel, ContextMenuItem, ContextMenuPortal, ContextMenuSeparator, ContextMenuTrigger, DatePicker, Feedback, Fieldset, FieldsetContent, FieldsetDescription, FieldsetFooter, FieldsetLegend, FileDropzone, GlidingHighlight, InlineStatus, Kbd, KbdGroup, Marquee, MiddleTruncate, Note, NumberField, OtpField, PageLoader, Pagination, Panel, PanelGroup, PanelResizer, PanelSection, Popover, PopoverArrow, PopoverClose, PopoverContent, PopoverDescription, PopoverPortal, PopoverTitle, PopoverTrigger, PropertyPair, PropertyRow, Radio, RadioCard, RadioGroup, SABLE_UI_VERSION, STATUS_TONES, STATUS_TONE_ICON, Scroller, ScrubField, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetPortal, SheetTitle, SheetTrigger, ShowMore, Slider, SliderControl, SliderIndicator, SliderRoot, SliderThumb, SliderTrack, SliderValue, StatusBar, StatusBarGroup, StatusBarSeparator, Stepper, StepperItem, TRANSPARENT, ThemeSelector, ThemeSwitcher, ThinkingOrb, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastRoot, ToastTitle, ToastViewport, Toggle, ToggleGroup, Toggle as ToggleGroupItem, Tree, TreeAddRow, TreeEmpty, TreeRow, TreeSection, Video, Workspace, WorkspaceBar, WorkspaceCanvas, accordionMeta, bannerMeta, bannerVariants, breadcrumbsMeta, calendarMeta, carouselMeta, checkboxCardMeta, checkboxMeta, collapsibleMeta, colorFieldMeta, comboboxMeta, commandMenuMeta, componentGuidance, componentLabel, componentSlug, componentsInGroup, contextCardMeta, contextMenuMeta, datePickerMeta, feedbackMeta, fieldsetMeta, fileDropzoneMeta, fileDropzoneVariants, formatFileSize, formatHex, glidingHighlightMeta, hsvToRgb, hueColor, inlineStatusMeta, isTransparent, kbdMeta, marqueeMeta, middleTruncateMeta, noteMeta, noteVariants, numberFieldMeta, otpFieldMeta, pageLoaderMeta, paginationMeta, panelGroupMeta, panelSectionMeta, parseHex, pillMeta, popoverMeta, radioCardMeta, radioMeta, rgbToHsv, scrollerMeta, scrubFieldMeta, sheetMeta, sheetVariants, showMoreMeta, sliderMeta, statusBarMeta, stepperConnectorVariants, stepperIndicatorVariants, stepperMeta, themeSelectorMeta, themeSwitcherMeta, thinkingOrbMeta, toast, toastMeta, toggleMeta, treeIndent, treeMeta, useComboboxFilteredItems, useCommandMenuShortcut, usePanelWidth, useToast, useToastManager, videoMeta, workspaceMeta };