@sableui/react 0.2.0 → 0.3.1

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 (98) hide show
  1. package/AGENTS.md +3 -1
  2. package/CHANGELOG.md +84 -0
  3. package/CONTRIBUTING.md +8 -0
  4. package/README.md +1 -0
  5. package/bin/sable-lint.mjs +0 -0
  6. package/bin/sable.mjs +68 -26
  7. package/dist/blocks/index.d.ts +396 -0
  8. package/dist/blocks/index.js +1997 -0
  9. package/dist/chunk-6LCIBMMG.js +4908 -0
  10. package/dist/{chunk-AOWBYEQ6.js → chunk-CKNM7KSB.js} +1598 -6464
  11. package/dist/{chunk-PAOZBZME.js → chunk-NGBDP3BM.js} +1 -1
  12. package/dist/{chunk-5Z5KLOAU.js → chunk-TH563WLW.js} +14 -1
  13. package/dist/chunk-WCFLQVK5.js +5097 -0
  14. package/dist/icon/index.js +2 -2
  15. package/dist/icon-sets/reicon.js +1 -1
  16. package/dist/index.d.ts +471 -34
  17. package/dist/index.js +588 -2589
  18. package/dist/ios/index.d.ts +8 -1
  19. package/dist/ios/index.js +6 -5
  20. package/dist/ios/preview.d.ts +11 -1
  21. package/dist/ios/preview.js +13 -8
  22. package/dist/ios/styles.css +14 -0
  23. package/dist/{meta-6dUzgC6M.d.ts → meta-CcE_SeFu.d.ts} +37 -1
  24. package/dist/patterns/index.d.ts +1 -1
  25. package/dist/patterns/index.js +29 -30
  26. package/dist/presets/index.js +10 -1
  27. package/dist/themes/base/base.css +23 -0
  28. package/dist/themes/index.js +2 -2
  29. package/llms/blocks/api-keys-block.md +79 -0
  30. package/llms/blocks/chat-thread-block.md +51 -0
  31. package/llms/blocks/command-palette-block.md +48 -0
  32. package/llms/blocks/cta-band-block.md +62 -0
  33. package/llms/blocks/danger-zone-block.md +69 -0
  34. package/llms/blocks/device-comparison-block.md +91 -0
  35. package/llms/blocks/event-card-block.md +84 -0
  36. package/llms/blocks/faq-block.md +72 -0
  37. package/llms/blocks/feature-grid-block.md +75 -0
  38. package/llms/blocks/feedback-form-block.md +66 -0
  39. package/llms/blocks/goal-progress-block.md +54 -0
  40. package/llms/blocks/hero-block.md +65 -0
  41. package/llms/blocks/invite-team-block.md +61 -0
  42. package/llms/blocks/invoice-history-block.md +84 -0
  43. package/llms/blocks/kpi-sparkline-block.md +57 -0
  44. package/llms/blocks/media-player-block.md +100 -0
  45. package/llms/blocks/newsletter-block.md +64 -0
  46. package/llms/blocks/not-found-block.md +56 -0
  47. package/llms/blocks/notification-list-block.md +63 -0
  48. package/llms/blocks/notification-settings-block.md +54 -0
  49. package/llms/blocks/order-tracking-block.md +62 -0
  50. package/llms/blocks/pricing-card-block.md +82 -0
  51. package/llms/blocks/pricing-table-block.md +129 -0
  52. package/llms/blocks/product-card-block.md +69 -0
  53. package/llms/blocks/profile-card-block.md +103 -0
  54. package/llms/blocks/profile-form-block.md +114 -0
  55. package/llms/blocks/revenue-breakdown-block.md +70 -0
  56. package/llms/blocks/sign-in-block.md +64 -0
  57. package/llms/blocks/sign-up-block.md +78 -0
  58. package/llms/blocks/site-footer-block.md +96 -0
  59. package/llms/blocks/stat-row-block.md +59 -0
  60. package/llms/blocks/steps-block.md +73 -0
  61. package/llms/blocks/task-list-block.md +81 -0
  62. package/llms/blocks/team-members-block.md +91 -0
  63. package/llms/blocks/testimonial-block.md +78 -0
  64. package/llms/blocks/traffic-sources-block.md +63 -0
  65. package/llms/blocks/traffic-trend-block.md +95 -0
  66. package/llms/blocks/upload-list-block.md +66 -0
  67. package/llms/blocks/verify-code-block.md +59 -0
  68. package/llms/blocks/wallet-block.md +84 -0
  69. package/llms/components/area-chart.md +112 -0
  70. package/llms/components/bar-chart.md +131 -0
  71. package/llms/components/button.md +1 -1
  72. package/llms/components/card.md +2 -2
  73. package/llms/components/context-menu.md +1 -1
  74. package/llms/components/date-tile.md +79 -0
  75. package/llms/components/entity.md +4 -2
  76. package/llms/components/line-chart.md +101 -0
  77. package/llms/components/menu.md +1 -1
  78. package/llms/components/{dots-menu.md → overflow-menu.md} +9 -9
  79. package/llms/components/pie-chart.md +103 -0
  80. package/llms/components/radar-chart.md +106 -0
  81. package/llms/components/radial-chart.md +102 -0
  82. package/llms/components/segmented-bar.md +102 -0
  83. package/llms/components/settings-menu.md +2 -2
  84. package/llms/components/sparkline.md +80 -0
  85. package/llms/components/table.md +1 -1
  86. package/llms/patterns/auth-page.md +2 -2
  87. package/llms/patterns/dashboard-page.md +6 -7
  88. package/llms/patterns/feed-page.md +7 -7
  89. package/llms/patterns/list-detail-page.md +7 -15
  90. package/llms/patterns/settings-page.md +10 -21
  91. package/llms/tasks/building-an-ios-prototype.md +4 -1
  92. package/llms/tasks/choosing-a-component.md +2 -2
  93. package/llms-full.txt +4296 -277
  94. package/llms.txt +55 -3
  95. package/package.json +5 -1
  96. package/registry.json +6383 -3741
  97. package/targets/ios.json +90 -9
  98. package/dist/{chunk-L6Y46C62.js → chunk-MXJX3NZI.js} +1 -1
package/dist/index.js CHANGED
@@ -1,31 +1,30 @@
1
+ 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';
1
2
  export { Phone, phoneMeta } from './chunk-KFZ43RKU.js';
2
3
  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-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';
4
+ export { AppHeader, AppShell, BottomNav, BottomNavItem, Browser, ChatComposer, DataTable, EmptyState, Marker, MessageScroller, Meter, MeterIndicator, MeterLabel, MeterTrack, MeterValue, NotificationBadge, PageHeader, Presence, RelativeTime, Reveal, SearchInput, SettingsMenu, SettingsRow, SideNav, SideNavGroup, SideNavItem, Sidebar, SidebarFooter, SidebarHeader, SidebarItem, SidebarNav, Skeleton, StreamingText, Tabs, TabsPanel as TabsContent, TabsList, TabsPanel, TabsTrigger, TextReveal, Toolbar, ToolbarButton, ToolbarGroup, ToolbarInput, ToolbarLink, ToolbarSeparator, ViewTransitionName, appHeaderMeta, appShellMeta, bottomNavMeta, browserMeta, chatComposerMeta, dataTableMeta, emptyStateMeta, markerMeta, messageScrollerMeta, meterIndicatorVariants, meterMeta, meterTrackVariants, notificationBadgeMeta, notificationCountVariants, notificationDotVariants, pageHeaderMeta, relativeTimeMeta, searchInputMeta, settingsMenuMeta, sideNavMeta, sidebarMeta, skeletonMeta, startViewTransition, streamingTextMeta, supportsViewTransitions, tabsMeta, toolbarGroupVariants, toolbarMeta, toolbarSeparatorVariants, toolbarVariants, useInView, useSidebar } from './chunk-6LCIBMMG.js';
5
+ import { SELECTION_CARD_CHECKED, CartesianChart, resolveSeries, chartFormatters, categoryText, numberValue, niceTicks, ChartFrame, polar, riseTransition, stagger, pathStyle, morphTransition, ChartLegend, RADIO_DOT, RADIO_RING, SELECTION_CARD_TILE_HEAD, SELECTION_CARD_GLYPH, SELECTION_CARD_SHELL, SELECTION_CARD_LAYOUT, CheckboxMark, CHECK_BOX, SEGMENTED_STEP, SEGMENTED_ITEM, SEGMENTED_TRACK, ToggleGroup, Toggle, approxTextWidth, ChartTooltipContent } from './chunk-WCFLQVK5.js';
6
+ export { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, AreaChart, BarChart, CHART_SERIES_COUNT, Checkbox, CommandMenu, CommandMenuGroup, CommandMenuItem, CommandMenuPanel, CommandMenuTrigger, DateTile, Kbd, KbdGroup, OtpField, PieChart, RadialChart, SEGMENTED_BAR_TONES, Scroller, SegmentedBar, Slider, SliderControl, SliderIndicator, SliderRoot, SliderThumb, SliderTrack, SliderValue, Sparkline, Stepper, StepperItem, Toggle, ToggleGroup, Toggle as ToggleGroupItem, accordionMeta, areaChartMeta, barChartMeta, chartColor, toneColor as chartToneColor, checkboxMeta, commandMenuMeta, dateTileMeta, kbdMeta, otpFieldMeta, pieChartMeta, radialChartMeta, segmentedBarMeta, segmentedBarSegmentVariants, segmentedBarVariants, sliderMeta, sparklineMeta, stepperConnectorVariants, stepperIndicatorVariants, stepperMeta, toggleMeta, useCommandMenuShortcut } from './chunk-WCFLQVK5.js';
7
+ import { withPortalThemeScope, FIELD_SHELL_XS, useTypeTier, useDensity, FIELD_SHELL, MOBILE_FIELD_TIER, useMotion, HighlightGlide, useAnchoredDensity, anchoredScroll, useIsomorphicLayoutEffect, mergeRefs, buttonVariants, FIELD_INVALID, Select, SelectTrigger, SelectContent, SelectItem, Button, Spinner, Badge, anchoredListScroll, useResizeObserver, MENU_ITEM_DESTRUCTIVE, DensityProvider, Input, Textarea, Progress } from './chunk-CKNM7KSB.js';
8
+ export { AlertDialog, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, Attachment, Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, AvatarRoot, BADGE_APPEARANCES, BADGE_TONES, Badge, Bubble, BubbleReaction, BubbleReactions, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardList, CardTitle, Code, CodeBlock, CodeHighlightProvider, Density, DensityProvider, DestructiveActionModal, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Entity, Field, Form, Input, Label, Menu, MenuCheckboxItem, MenuContent, MenuGroup, MenuGroupLabel, MenuItem, MenuPortal, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuSubmenu, MenuSubmenuTrigger, MenuTrigger, Message, MotionProvider, OverflowMenu, Pill, Progress, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue, Select, SelectContent, SelectGroup, SelectGroupLabel, SelectItem, SelectPortal, SelectSeparator, SelectTrigger, SelectValue, Separator, SimpleTooltip, Spinner, StatusDot, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Textarea, Tooltip, TooltipArrow, TooltipContent, TooltipPortal, TooltipProvider, TooltipTrigger, attachmentMeta, avatarGroupVariants, avatarMeta, avatarVariants, badgeMeta, badgeVariants, bubbleMeta, buttonMeta, buttonVariants, cardMeta, codeBlockMeta, codeMeta, destructiveActionModalMeta, dialogMeta, entityMeta, formMeta, inputMeta, labelMeta, menuMeta, messageMeta, overflowMenuMeta, pillVariants, progressMeta, progressTrackVariants, selectMeta, separatorMeta, spinnerMeta, statusDotMeta, statusDotVariants, switchMeta, tableMeta, textareaMeta, tooltipMeta, useAnchoredDensity, useCopy, useDensity, useMotion, useReducedMotion, useScrollActivity, useTypeTier } from './chunk-CKNM7KSB.js';
6
9
  export { AspectBox, Bleed, Center, Col, Columns, Grid, GridOverlay, Page, Region, Row, SafeArea, Spacer, SplitPane, Stack } from './chunk-3IWZYBGH.js';
7
10
  import { cn } from './chunk-SRQWQUTW.js';
8
11
  export { cn } from './chunk-SRQWQUTW.js';
9
- import { Icon } from './chunk-PAOZBZME.js';
10
- export { Icon, IconProvider, composeIconSets, useIconSet } from './chunk-PAOZBZME.js';
11
- export { reiconIconSet as fallbackIconSet, reiconIconNames, reiconIconSet } from './chunk-5Z5KLOAU.js';
12
- import { featuredThemeManifests, defaultThemeId } from './chunk-L6Y46C62.js';
13
- export { DEFAULT_THEME_ID, SABLE_COLOR_STORAGE_KEY, SABLE_THEME_ATTRIBUTE, SABLE_THEME_STORAGE_KEY, SableThemeProvider, applySableColorMode, applySableTheme, defaultThemeId, getThemeManifest, isThemeId, themeIds, themeManifestById, themeManifests, useSableTheme } from './chunk-L6Y46C62.js';
12
+ import { Icon } from './chunk-NGBDP3BM.js';
13
+ export { Icon, IconProvider, composeIconSets, useIconSet } from './chunk-NGBDP3BM.js';
14
+ export { reiconIconSet as fallbackIconSet, reiconIconNames, reiconIconSet } from './chunk-TH563WLW.js';
15
+ import { featuredThemeManifests, defaultThemeId } from './chunk-MXJX3NZI.js';
16
+ export { DEFAULT_THEME_ID, SABLE_COLOR_STORAGE_KEY, SABLE_THEME_ATTRIBUTE, SABLE_THEME_STORAGE_KEY, SableThemeProvider, applySableColorMode, applySableTheme, defaultThemeId, getThemeManifest, isThemeId, themeIds, themeManifestById, themeManifests, useSableTheme } from './chunk-MXJX3NZI.js';
17
+ import './chunk-7MG4SHYX.js';
14
18
  import './chunk-KO3NAEBF.js';
15
19
  import './chunk-L4IEIQQA.js';
16
20
  import './chunk-EP55KSYA.js';
17
21
  import './chunk-ADLLDWLW.js';
18
22
  import './chunk-K7RUHUGC.js';
19
23
  import './chunk-YXBOYWBN.js';
20
- import './chunk-7MG4SHYX.js';
21
24
  import { NumberField as NumberField$1 } from '@base-ui/react/number-field';
22
- import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
23
- import * as OtpParts from '@base-ui/react/otp-field';
24
- import { createContext, Fragment, useId, useRef, useState, useEffect, useLayoutEffect, Children, isValidElement, useContext, useMemo, useCallback, useSyncExternalStore } from 'react';
25
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
26
+ import { createContext, useId, useRef, useState, useEffect, useLayoutEffect, useContext, useMemo, useCallback, useSyncExternalStore, Children } from 'react';
25
27
  import { cva } from 'class-variance-authority';
26
- import { Checkbox as Checkbox$1 } from '@base-ui/react/checkbox';
27
- import { Toggle as Toggle$1 } from '@base-ui/react/toggle';
28
- import { ToggleGroup as ToggleGroup$1 } from '@base-ui/react/toggle-group';
29
28
  import { Radio as Radio$1 } from '@base-ui/react/radio';
30
29
  import { RadioGroup as RadioGroup$1 } from '@base-ui/react/radio-group';
31
30
  import { Popover as Popover$1 } from '@base-ui/react/popover';
@@ -33,12 +32,11 @@ import { Drawer } from '@base-ui/react/drawer';
33
32
  import { DayPicker } from 'react-day-picker';
34
33
  import { format, setHours, setMinutes, setMilliseconds, setSeconds } from 'date-fns';
35
34
  import { Toast } from '@base-ui/react/toast';
36
- import { Accordion as Accordion$1 } from '@base-ui/react/accordion';
37
35
  import { Collapsible as Collapsible$1 } from '@base-ui/react/collapsible';
38
- import { Slider as Slider$1 } from '@base-ui/react/slider';
39
36
  import { Combobox as Combobox$1 } from '@base-ui/react/combobox';
40
37
  import { ContextMenu as ContextMenu$1 } from '@base-ui/react/context-menu';
41
38
  import { Fieldset as Fieldset$1 } from '@base-ui/react/fieldset';
39
+ import { Checkbox } from '@base-ui/react/checkbox';
42
40
  import { CheckboxGroup as CheckboxGroup$1 } from '@base-ui/react/checkbox-group';
43
41
  import { PreviewCard } from '@base-ui/react/preview-card';
44
42
 
@@ -251,6 +249,7 @@ var componentGuidance = {
251
249
  var COMPONENT_GROUPS = [
252
250
  "Actions",
253
251
  "Badges & pills",
252
+ "Charts",
254
253
  "Chat & AI",
255
254
  "Containers",
256
255
  "Data display",
@@ -266,13 +265,13 @@ var COMPONENT_GROUPS = [
266
265
  ];
267
266
  var COMPONENT_TAXONOMY = {
268
267
  /* ── Actions ──────────────────────────────────────────────────────────────
269
- Things whose whole purpose is to DO something on activation. `DotsMenu`
268
+ Things whose whole purpose is to DO something on activation. `OverflowMenu`
270
269
  is here, not in Overlays: it is the "more actions" button a reader reaches
271
270
  for as an action (and the options half of Button's "main action with
272
271
  options" pattern) — that its list opens in a layer is how it works, not
273
272
  what it is. Copying is `useCopy` on a Button, not a component. */
274
273
  Button: ["Actions", "either"],
275
- DotsMenu: ["Actions", "either"],
274
+ OverflowMenu: ["Actions", "either"],
276
275
  Toolbar: ["Actions", "either"],
277
276
  /* ── Forms ────────────────────────────────────────────────────────────────
278
277
  Typing or entering a value, and the structure a form is built from.
@@ -354,15 +353,30 @@ var COMPONENT_TAXONOMY = {
354
353
  Collapsible: ["Containers", "either"],
355
354
  Scroller: ["Containers", "either"],
356
355
  Separator: ["Containers", "either"],
356
+ /* ── Charts ───────────────────────────────────────────────────────────────
357
+ Plotted data for dashboards, landing pages and decks: SVG on the theme's
358
+ tones, with arrival and data-change motion and a data table for assistive
359
+ tech. Data-heavy apps (streaming, zoom, thousands of points) belong on a
360
+ charting library. `SegmentedBar` and `Meter` stay in Data display: they
361
+ are readings, not plots. */
362
+ AreaChart: ["Charts", "page"],
363
+ BarChart: ["Charts", "page"],
364
+ LineChart: ["Charts", "page"],
365
+ PieChart: ["Charts", "page"],
366
+ RadarChart: ["Charts", "page"],
367
+ RadialChart: ["Charts", "page"],
368
+ Sparkline: ["Charts", "either"],
357
369
  /* ── Data display ─────────────────────────────────────────────────────────
358
370
  Showing records and readings. `Meter` (bar or ring) is here: it shows a
359
371
  value within a known range (a reading), which is not loading or progress. */
360
372
  Avatar: ["Data display", "either"],
361
373
  DataTable: ["Data display", "either"],
374
+ DateTile: ["Data display", "page"],
362
375
  Entity: ["Data display", "either"],
363
376
  GlidingHighlight: ["Data display", "either"],
364
377
  Meter: ["Data display", "either"],
365
378
  RelativeTime: ["Data display", "either"],
379
+ SegmentedBar: ["Data display", "either"],
366
380
  Table: ["Data display", "either"],
367
381
  /* ── Badges & pills ───────────────────────────────────────────────────────
368
382
  Small labelled shapes. `Badge` (with `dot`) / `StatusDot` belong HERE, not in
@@ -598,255 +612,6 @@ var numberFieldMeta = {
598
612
  }
599
613
  ]
600
614
  };
601
- var otpExports = Object.fromEntries(Object.entries(OtpParts));
602
- var Base2 = otpExports.OTPField ?? otpExports.OTPFieldPreview;
603
- var defaultSlotLabel = (index, length) => `Character ${index + 1} of ${length}`;
604
- function OtpField({
605
- className,
606
- length = 6,
607
- groupSize,
608
- getSlotLabel = defaultSlotLabel,
609
- "aria-invalid": ariaInvalid,
610
- ...root
611
- }) {
612
- const invalid = ariaInvalid === true || ariaInvalid === "true";
613
- return /* @__PURE__ */ jsx(
614
- Base2.Root,
615
- {
616
- length,
617
- "data-invalid": invalid ? "" : void 0,
618
- className: cn("inline-flex max-w-full items-center gap-2", className),
619
- ...root,
620
- children: Array.from({ length }, (_, index) => (
621
- // Slots are positional and never reorder, so the index is the identity.
622
- // biome-ignore lint/suspicious/noArrayIndexKey: positional slots
623
- /* @__PURE__ */ jsxs(Fragment, { children: [
624
- groupSize && index > 0 && index % groupSize === 0 ? /* @__PURE__ */ jsx(
625
- "span",
626
- {
627
- "aria-hidden": "true",
628
- "data-otp-gap": "",
629
- className: "h-px w-2 shrink-0 bg-border-strong"
630
- }
631
- ) : null,
632
- /* @__PURE__ */ jsx(
633
- Base2.Input,
634
- {
635
- "aria-label": index === 0 ? void 0 : getSlotLabel(index, length),
636
- "aria-invalid": invalid || void 0,
637
- className: cn(
638
- "h-(--control-lg) w-(--control-md) min-w-0 text-center text-mono-lg text-foreground caret-primary",
639
- // The shell Input wears — lib/field-shell — so a slot reads as
640
- // the same field; only the slot-specific states are added here.
641
- FIELD_SHELL,
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",
645
- FIELD_INVALID
646
- )
647
- }
648
- )
649
- ] }, index)
650
- ))
651
- }
652
- );
653
- }
654
- OtpField.displayName = "OtpField";
655
-
656
- // src/components/otp-field/otp-field.meta.ts
657
- var otpFieldMeta = {
658
- name: "OtpField",
659
- summary: "One-time-code entry over Base UI's OTP Field \u2014 one slot per character, SMS autofill on the first slot, paste fills every slot, and only one tab stop for the whole code.",
660
- status: "stable",
661
- keywords: [
662
- "otp",
663
- "one-time code",
664
- "one-time password",
665
- "verification code",
666
- "2fa",
667
- "two-factor",
668
- "mfa",
669
- "pin",
670
- "passcode",
671
- "input otp"
672
- ],
673
- usage: {
674
- description: "Use OtpField for a short, fixed-length code the user copies from somewhere else \u2014 an SMS or email verification code, an authenticator app, a device pairing PIN. The slots show how long the code is and where the user is in it, which a plain Input cannot.",
675
- bestPractices: [
676
- {
677
- guidance: "do",
678
- description: "Set `length` to the exact length the server sends, and verify in `onValueComplete` (or with `autoSubmit` inside a form). Most users paste or autofill the whole code at once, so the moment of completion is the natural submit."
679
- },
680
- {
681
- guidance: "do",
682
- description: 'Label it through a `Field` ("Verification code") and say where the code was sent in the description. The first slot takes the field\'s name; the rest are announced by position.'
683
- },
684
- {
685
- guidance: "do",
686
- description: "Use `groupSize` for codes of 6 or more so the user can check the code in chunks against the message they are reading from."
687
- },
688
- {
689
- guidance: "dont",
690
- description: 'Do not use OtpField for a password, an arbitrary-length PIN, or anything the user types from memory. Slots promise a fixed length; use Input with `type="password"` and `autocomplete="current-password"` instead.'
691
- },
692
- {
693
- guidance: "dont",
694
- description: "Do not intercept paste or clear the field on a wrong code before the user has seen the error. Blocking paste breaks SMS autofill and password managers, and wiping the slots erases the evidence of which digit was wrong."
695
- },
696
- {
697
- guidance: "dont",
698
- description: "Do not swap it for NumberField because the code is digits. A code is an identifier, not a quantity: NumberField strips leading zeros and offers steppers that make no sense here."
699
- }
700
- ],
701
- anatomy: [
702
- {
703
- name: "Root",
704
- required: true,
705
- description: "A role=group that holds the value, carries the field label and description, and reports completion."
706
- },
707
- {
708
- name: "Slot",
709
- required: true,
710
- description: 'One input per character. The first carries autocomplete="one-time-code" and accepts the whole code.'
711
- },
712
- {
713
- name: "Group gap",
714
- required: false,
715
- description: "A decorative dash inserted every `groupSize` slots; hidden from assistive tech."
716
- }
717
- ]
718
- },
719
- related: {
720
- components: ["Input", "Form", "NumberField"]
721
- },
722
- props: [
723
- {
724
- name: "length",
725
- type: "number",
726
- default: "6",
727
- description: "Number of character slots. Completion fires when every slot is filled."
728
- },
729
- {
730
- name: "groupSize",
731
- type: "number",
732
- description: "Insert a visual gap every N slots (3 renders 123 \u2013 456). The value stays one contiguous string."
733
- },
734
- {
735
- name: "value / defaultValue",
736
- type: "string",
737
- description: "Controlled / uncontrolled code."
738
- },
739
- {
740
- name: "onValueChange",
741
- type: "(value: string, details) => void",
742
- description: "Fires on every edit. `details.reason` is input-change, input-clear, input-paste or keyboard."
743
- },
744
- {
745
- name: "onValueComplete",
746
- type: "(value: string, details) => void",
747
- description: "Fires once every slot is filled \u2014 the place to verify the code. Also fires when a complete code is pasted over a complete value."
748
- },
749
- {
750
- name: "onValueInvalid",
751
- type: "(attempted: string, details) => void",
752
- description: "Fires when typed or pasted text contains characters `validationType` rejects, so you can explain the rejection rather than letting it vanish."
753
- },
754
- {
755
- name: "validationType",
756
- type: '"numeric" | "alpha" | "alphanumeric" | "none"',
757
- default: '"numeric"',
758
- description: "Which characters a slot accepts. Also picks the virtual keyboard (numeric \u2192 number pad)."
759
- },
760
- {
761
- name: "autoSubmit",
762
- type: "boolean",
763
- default: "false",
764
- description: "Submit the owning form when the code becomes complete."
765
- },
766
- {
767
- name: "mask",
768
- type: "boolean",
769
- default: "false",
770
- description: "Render slots as password inputs. Only for codes that are secret on screen, such as a card PIN."
771
- },
772
- {
773
- name: "getSlotLabel",
774
- type: "(index: number, length: number) => string",
775
- default: "Character N of L",
776
- description: "Accessible name for slots after the first. Override to localize."
777
- },
778
- {
779
- name: "aria-invalid",
780
- type: "boolean",
781
- description: "Marks every slot invalid (destructive border). `<Field error>` sets it for you."
782
- },
783
- {
784
- name: "disabled / readOnly / required / name / id / form",
785
- type: "\u2014",
786
- description: "Standard form-control props. `id` lands on the first slot, so `<Field>` and `<label htmlFor>` name the field."
787
- }
788
- ],
789
- tokensUsed: [
790
- "input",
791
- "card",
792
- "foreground",
793
- "muted-foreground",
794
- "border-strong",
795
- "primary",
796
- "ring",
797
- "destructive"
798
- ],
799
- motion: [
800
- {
801
- slot: "arrival",
802
- recipe: "none",
803
- part: "Root",
804
- why: "A code field is part of the form it sits in and appears with it. Animating it in would delay the one thing the user arrived to do \u2014 paste the code they just copied."
805
- },
806
- {
807
- slot: "response",
808
- recipe: "motion-interactive",
809
- part: "Slot",
810
- why: "Focus moves between slots on every keystroke. The eased ring and border are what let the eye follow the cursor across six identical boxes instead of hunting for it after each digit."
811
- },
812
- {
813
- slot: "transition",
814
- recipe: "motion-interactive",
815
- part: "Slot",
816
- why: "A filled slot strengthens its border, so a pasted code visibly lands in every slot at once. Easing it reads as the code arriving rather than the field flickering."
817
- },
818
- {
819
- slot: "activity",
820
- recipe: "none",
821
- part: "Root",
822
- why: "Verifying the code is the form's work, not the field's. Show it on the submit Button's `loading` state, where the user expects the answer to appear."
823
- }
824
- ],
825
- a11y: [
826
- 'The root is a role=group named by the field label; the first slot shares the label through `id`/`htmlFor`, and later slots are announced by position ("Character 2 of 6").',
827
- "Only one slot is in the tab order. Arrow keys, Home/End and Backspace move between slots; Tab leaves the whole field.",
828
- 'The first slot carries autocomplete="one-time-code" and the numeric virtual keyboard, so iOS and Android offer the SMS code above the keyboard.',
829
- "Slots render at 20px (`text-mono-lg`), above the 16px floor that stops iOS Safari zooming on focus."
830
- ],
831
- examples: [
832
- {
833
- label: "Six digits",
834
- code: '<Field label="Verification code"><OtpField length={6} onValueComplete={verify} /></Field>'
835
- },
836
- {
837
- label: "In a field, grouped",
838
- code: '<Field label="Verification code" description="Enter the 6-digit code we sent to your phone."><OtpField length={6} groupSize={3} /></Field>'
839
- },
840
- {
841
- label: "With an error",
842
- code: '<Field label="Verification code" error="That code has expired. Request a new code and try again."><OtpField length={6} groupSize={3} defaultValue="482913" /></Field>'
843
- },
844
- {
845
- label: "Alphanumeric",
846
- code: '<Field label="Pairing code"><OtpField length={8} groupSize={4} validationType="alphanumeric" /></Field>'
847
- }
848
- ]
849
- };
850
615
  var fileDropzoneVariants = cva(
851
616
  [
852
617
  "group flex w-full flex-col items-center justify-center gap-2 rounded-md border border-dashed border-border-strong bg-card text-center text-foreground motion-interactive",
@@ -1005,7 +770,7 @@ function FileDropzone({
1005
770
  }
1006
771
  ),
1007
772
  /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
1008
- /* @__PURE__ */ jsx("span", { className: tier.control, children: label ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [
773
+ /* @__PURE__ */ jsx("span", { className: tier.control, children: label ?? /* @__PURE__ */ jsxs(Fragment, { children: [
1009
774
  multiple ? "Drop files here or " : "Drop a file here or ",
1010
775
  /* @__PURE__ */ jsx("span", { className: "text-primary underline underline-offset-2", children: "browse" })
1011
776
  ] }) }),
@@ -1255,200 +1020,6 @@ var fileDropzoneMeta = {
1255
1020
  ]
1256
1021
  };
1257
1022
 
1258
- // src/lib/selection-card.ts
1259
- var SELECTION_CARD_SHELL = [
1260
- "group relative flex cursor-pointer border border-input bg-card p-4 text-start motion-interactive motion-press",
1261
- "hover:border-ring",
1262
- // Focus is an OUTLINE, not a ring: the checked state is an inset ring in the
1263
- // tone colour, and both rings write box-shadow, so a focused checked card
1264
- // used to lose its tone ring to the focus ring.
1265
- "focus-within:outline-2 focus-within:outline-solid focus-within:outline-offset-2 focus-within:outline-ring",
1266
- "data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:border-input"
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";
1274
- var SELECTION_CARD_CHECKED = {
1275
- neutral: "data-[checked]:border-transparent data-[checked]:ring-[1.5px] data-[checked]:ring-inset data-[checked]:ring-primary data-[checked]:bg-muted",
1276
- standard: "data-[checked]:border-transparent data-[checked]:ring-[1.5px] data-[checked]:ring-inset data-[checked]:ring-tone-standard",
1277
- collectible: "data-[checked]:border-transparent data-[checked]:ring-[1.5px] data-[checked]:ring-inset data-[checked]:ring-tone-collectible",
1278
- edition: "data-[checked]:border-transparent data-[checked]:ring-[1.5px] data-[checked]:ring-inset data-[checked]:ring-tone-edition",
1279
- info: "data-[checked]:border-transparent data-[checked]:ring-[1.5px] data-[checked]:ring-inset data-[checked]:ring-tone-info",
1280
- warning: "data-[checked]:border-transparent data-[checked]:ring-[1.5px] data-[checked]:ring-inset data-[checked]:ring-tone-warning"
1281
- };
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";
1285
- var CHECK_MARK_SIZE = "text-[length:--spacing(2.75)]";
1286
- function CheckboxMark({ className }) {
1287
- return /* @__PURE__ */ jsx(
1288
- Checkbox$1.Indicator,
1289
- {
1290
- className: cn(
1291
- "flex items-center justify-center text-current motion-interactive ease-spring data-[starting-style]:scale-0 data-[starting-style]:opacity-0 data-[ending-style]:scale-0 data-[ending-style]:opacity-0",
1292
- className
1293
- ),
1294
- render: (indicatorProps, state) => /* @__PURE__ */ jsx("span", { ...indicatorProps, children: /* @__PURE__ */ jsx(
1295
- Icon,
1296
- {
1297
- name: state.indeterminate ? "minus" : "check",
1298
- className: cn(CHECK_MARK_SIZE, "[&_path]:stroke-current [&_path]:[stroke-width:2] [&_path]:[stroke-linejoin:round] [&_path]:[stroke-linecap:round]")
1299
- }
1300
- ) })
1301
- }
1302
- );
1303
- }
1304
- CheckboxMark.displayName = "CheckboxMark";
1305
- function Checkbox({ className, children, ...props }) {
1306
- return /* @__PURE__ */ jsx(
1307
- Checkbox$1.Root,
1308
- {
1309
- className: cn(
1310
- CHECK_BOX,
1311
- "text-primary-foreground",
1312
- "data-[checked]:border-primary data-[checked]:bg-primary",
1313
- "data-[indeterminate]:border-primary data-[indeterminate]:bg-primary",
1314
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
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",
1318
- className
1319
- ),
1320
- ...props,
1321
- children: children ?? /* @__PURE__ */ jsx(CheckboxMark, {})
1322
- }
1323
- );
1324
- }
1325
- Checkbox.displayName = "Checkbox";
1326
-
1327
- // src/components/checkbox/checkbox.meta.ts
1328
- var checkboxMeta = {
1329
- name: "Checkbox",
1330
- summary: "Square toggle (18px at rest, on the spacing scale) over Base UI's Checkbox. Checked/indeterminate/disabled states; controlled or uncontrolled; form-ready via the hidden native input.",
1331
- status: "stable",
1332
- keywords: [
1333
- "checkbox",
1334
- "boolean",
1335
- "multi select",
1336
- "form control"
1337
- ],
1338
- usage: {
1339
- description: "Use Checkbox for independent boolean choices or for selecting multiple options in a set.",
1340
- bestPractices: [
1341
- {
1342
- guidance: "do",
1343
- description: "Use Checkbox when each option can be toggled independently."
1344
- },
1345
- {
1346
- guidance: "do",
1347
- description: "Attach a visible label that explains the consequence of checking the control."
1348
- },
1349
- {
1350
- guidance: "dont",
1351
- description: "Do not use Checkbox for mutually exclusive choices; use Radio or Select instead."
1352
- },
1353
- {
1354
- guidance: "dont",
1355
- description: 'Do not use it without a label or an aria-label. An 18px square with no accessible name is announced as just "checkbox", which tells a screen-reader user nothing about what they are agreeing to.'
1356
- },
1357
- {
1358
- guidance: "dont",
1359
- description: `Do not phrase the label as a negative, such as "Don't send read receipts". Paired with an unchecked box it becomes a double negative the reader has to solve before they can answer. Label the state being turned on.`
1360
- }
1361
- ]
1362
- },
1363
- related: {
1364
- components: [
1365
- "CheckboxCard",
1366
- "Switch",
1367
- "Radio"
1368
- ]
1369
- },
1370
- props: [
1371
- {
1372
- name: "checked",
1373
- type: "boolean",
1374
- description: "Controlled checked state. Pair with onCheckedChange. Omit for uncontrolled."
1375
- },
1376
- {
1377
- name: "defaultChecked",
1378
- type: "boolean",
1379
- default: "false",
1380
- description: "Initial checked state for an uncontrolled checkbox."
1381
- },
1382
- {
1383
- name: "onCheckedChange",
1384
- type: "(checked: boolean, eventDetails) => void",
1385
- description: "Fires when the checkbox is ticked or unticked. Note: Base UI passes a second eventDetails arg \u2014 not the common (checked) => void, so a single-arg handler must widen its signature."
1386
- },
1387
- {
1388
- name: "indeterminate",
1389
- type: "boolean",
1390
- default: "false",
1391
- description: 'Mixed state (neither checked nor unchecked). Renders a dash instead of the check. For a "select all" box inside a CheckboxGroup, use `parent` instead and the group sets this for you.'
1392
- },
1393
- {
1394
- name: "parent",
1395
- type: "boolean",
1396
- default: "false",
1397
- description: 'Makes this the "select all" box of a CheckboxGroup that has `allValues`. It checks or clears every child, and shows the dash when only some are checked. Only works inside a CheckboxGroup.'
1398
- },
1399
- {
1400
- name: "disabled",
1401
- type: "boolean",
1402
- default: "false",
1403
- description: "Ignores user interaction and dims the control."
1404
- },
1405
- {
1406
- name: "name",
1407
- type: "string",
1408
- description: "Field name for the hidden input on form submit."
1409
- },
1410
- {
1411
- name: "value",
1412
- type: "string",
1413
- description: "Value submitted with the form when checked. Inside a group-owned CheckboxGroup, it is the name the group's value array uses for this checkbox."
1414
- },
1415
- {
1416
- name: "render",
1417
- type: "ReactElement | (props, state) => ReactElement",
1418
- description: "Compose the root with another element through the Base UI render prop."
1419
- }
1420
- ],
1421
- tokensUsed: [
1422
- "card",
1423
- "input",
1424
- "primary",
1425
- "primary-foreground",
1426
- "ring"
1427
- ],
1428
- a11y: [
1429
- "Base UI renders a hidden native <input type=checkbox> for keyboard, focus, and form semantics.",
1430
- "Space toggles; focus-visible draws a 2px ring in the --ring token.",
1431
- "indeterminate sets aria-checked=mixed automatically; a `parent` checkbox in a CheckboxGroup gets aria-checked=mixed and aria-controls for its children from the group.",
1432
- `Label it with Field orientation="horizontal": it wires the label's htmlFor to the checkbox, so the label text joins the click target, and adds the description and error plumbing.`
1433
- ],
1434
- examples: [
1435
- { label: "Default", code: "<Checkbox defaultChecked />" },
1436
- {
1437
- label: "Controlled",
1438
- code: "<Checkbox checked={value} onCheckedChange={setValue} />"
1439
- },
1440
- { label: "Indeterminate", code: "<Checkbox indeterminate />" },
1441
- {
1442
- label: "Select all in a group",
1443
- code: '<CheckboxGroup allValues={["email", "sms"]} defaultValue={["email"]} className="gap-3"><Field label="All channels" orientation="horizontal"><Checkbox parent /></Field><Field label="Email" orientation="horizontal" className="ps-7"><Checkbox value="email" /></Field><Field label="SMS" orientation="horizontal" className="ps-7"><Checkbox value="sms" /></Field></CheckboxGroup>'
1444
- },
1445
- {
1446
- label: "With label",
1447
- code: '<Field label="Remember me" orientation="horizontal"><Checkbox /></Field>'
1448
- }
1449
- ]
1450
- };
1451
-
1452
1023
  // src/components/thinking-orb/thinking-orb.render.ts
1453
1024
  var ORB_PX = { sm: 20, md: 32, lg: 64 };
1454
1025
  var ORB_TUNING = {
@@ -2120,567 +1691,571 @@ var glidingHighlightMeta = {
2120
1691
  }
2121
1692
  ]
2122
1693
  };
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",
2125
- {
2126
- variants: {
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"
2132
- }
2133
- },
2134
- defaultVariants: {
2135
- status: "upcoming"
2136
- }
2137
- }
2138
- );
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"
2152
- }
2153
- }
2154
- );
2155
- var stepperDotConnectorVariants = cva("shrink-0 rounded-full motion-interactive", {
2156
- variants: {
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"
2164
- }
2165
- },
2166
- defaultVariants: {
2167
- orientation: "horizontal",
2168
- filled: false
2169
- }
2170
- });
2171
- var stepperConnectorVariants = cva("shrink-0 rounded-full motion-interactive", {
2172
- variants: {
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"
2180
- }
2181
- },
2182
- defaultVariants: {
2183
- orientation: "horizontal",
2184
- filled: false
2185
- }
2186
- });
2187
- var DEFAULT_STATUS_LABELS = {
2188
- complete: "Completed",
2189
- current: "Current step",
2190
- upcoming: "Not started",
2191
- error: "Needs attention"
2192
- };
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
2202
- }) {
2203
- const items = Children.toArray(children).filter(isValidElement);
2204
- const labels = { ...DEFAULT_STATUS_LABELS, ...statusLabels };
2205
- return /* @__PURE__ */ jsx(
2206
- "ol",
2207
- {
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
- ),
2216
- ...props,
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
- ))
2232
- }
2233
- );
2234
- }
2235
- Stepper.displayName = "Stepper";
2236
- function StepperItem({
2237
- title,
2238
- description,
2239
- status: statusProp,
2240
- onSelect,
2241
- className,
2242
- ...props
2243
- }) {
2244
- const ctx = useContext(StepContext);
2245
- const tier = useTypeTier();
2246
- const index = ctx?.index ?? 0;
2247
- const count2 = ctx?.count ?? 1;
2248
- const orientation = ctx?.orientation ?? "horizontal";
2249
- const dot = (ctx?.indicator ?? "number") === "dot";
2250
- const current = ctx?.current ?? 0;
2251
- const status = statusProp ?? (index < current ? "complete" : index === current ? "current" : "upcoming");
2252
- const isLast = index === count2 - 1;
2253
- const isCurrent = index === current;
2254
- const statusLabel = (ctx?.statusLabels ?? DEFAULT_STATUS_LABELS)[status];
2255
- const body = /* @__PURE__ */ jsxs(Fragment$1, { children: [
2256
- dot ? (
2257
- // One title line tall (the step carries the title's type), so the dot
2258
- // centres on the title's first line at any density.
2259
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "relative z-10 flex h-[1lh] shrink-0 items-center", children: /* @__PURE__ */ jsx("span", { className: stepperDotVariants({ status }) }) })
2260
- ) : /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: stepperIndicatorVariants({ status }), children: status === "complete" ? /* @__PURE__ */ jsx(Icon, { name: "check", className: "text-sm" }) : status === "error" ? /* @__PURE__ */ jsx(Icon, { name: "warning", className: "text-sm" }) : index + 1 }),
2261
- /* @__PURE__ */ jsxs(
2262
- "span",
2263
- {
2264
- className: cn(
2265
- "flex min-w-0 flex-col gap-0.5 text-start",
2266
- !dot && "min-h-(--control-xs) justify-center"
2267
- ),
2268
- children: [
2269
- /* @__PURE__ */ jsxs(
2270
- "span",
2271
- {
2272
- className: cn(
2273
- tier.control,
2274
- "underline-offset-2 motion-interactive group-hover/step:underline",
2275
- status === "upcoming" && "text-muted-foreground",
2276
- status === "error" && "text-destructive",
2277
- (status === "complete" || status === "current") && "text-foreground"
2278
- ),
2279
- children: [
2280
- title,
2281
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: ` (${statusLabel})` })
2282
- ]
2283
- }
2284
- ),
2285
- description ? /* @__PURE__ */ jsx("span", { className: cn("text-muted-foreground", tier.secondary), children: description }) : null
2286
- ]
2287
- }
2288
- )
2289
- ] });
2290
- return /* @__PURE__ */ jsxs(
2291
- "li",
2292
- {
2293
- "aria-current": isCurrent ? "step" : void 0,
2294
- "data-status": status,
2295
- className: cn(
2296
- orientation === "horizontal" ? cn("flex min-w-0 items-start gap-3", isLast ? "flex-none" : "flex-1") : cn("relative", !isLast && "pb-6"),
2297
- // The dot and its line are measured in the title's `lh` and `em`.
2298
- dot && tier.control,
2299
- className
2300
- ),
2301
- ...props,
2302
- children: [
2303
- onSelect ? /* @__PURE__ */ jsx(
2304
- "button",
2305
- {
2306
- type: "button",
2307
- onClick: onSelect,
2308
- className: "group/step flex min-w-0 items-start gap-3 rounded-sm motion-interactive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2309
- children: body
2310
- }
2311
- ) : /* @__PURE__ */ jsx("div", { className: "flex min-w-0 items-start gap-3", children: body }),
2312
- isLast ? null : /* @__PURE__ */ jsx(
2313
- "span",
2314
- {
2315
- "aria-hidden": "true",
2316
- className: (dot ? stepperDotConnectorVariants : stepperConnectorVariants)({
2317
- orientation,
2318
- filled: status === "complete"
2319
- })
2320
- }
2321
- )
2322
- ]
2323
- }
2324
- );
1694
+ function LineChart(props) {
1695
+ return /* @__PURE__ */ jsx(CartesianChart, { kind: "line", ...props });
2325
1696
  }
2326
- StepperItem.displayName = "StepperItem";
1697
+ LineChart.displayName = "LineChart";
2327
1698
 
2328
- // src/components/stepper/stepper.meta.ts
2329
- var stepperMeta = {
2330
- name: "Stepper",
2331
- summary: 'Where the user is in a multi-step flow \u2014 an ordered list of numbered steps, horizontal or vertical, each complete, current, upcoming or in error, with aria-current="step" on the current one. `indicator="dot"` turns it into a timeline of events (an order\'s delivery) with dots instead of numbers.',
2332
- status: "stable",
1699
+ // src/components/line-chart/line-chart.meta.ts
1700
+ var lineChartMeta = {
1701
+ name: "LineChart",
1702
+ summary: "Lines over a sequence \u2014 for comparing how measures move. Smooth, linear or step curves, optional dots (coloured by series or by category) and value labels. Lines draw on as the chart scrolls into view.",
2333
1703
  keywords: [
2334
- "stepper",
2335
- "steps",
2336
- "wizard",
2337
- "multi-step form",
2338
- "checkout steps",
2339
- "onboarding progress",
2340
- "progress steps",
2341
- "timeline"
1704
+ "chart",
1705
+ "line",
1706
+ "trend",
1707
+ "time series",
1708
+ "graph",
1709
+ "compare",
1710
+ "analytics",
1711
+ "dashboard"
2342
1712
  ],
2343
1713
  usage: {
2344
- description: "Use Stepper above or beside a flow that is split into a known, small number of named steps \u2014 checkout, onboarding, an import wizard \u2014 so the user can see how far they have come and what is left. It reports position; the flow's own Back and Continue buttons move it.",
1714
+ description: "Use LineChart to compare how two or more measures change over the same sequence, or to show one trend precisely (with dots and labels). Unlike AreaChart, its value axis is fitted to the data rather than forced to zero, so small movements stay visible \u2014 say so if the axis is shown.",
2345
1715
  bestPractices: [
2346
1716
  {
2347
1717
  guidance: "do",
2348
- description: 'Drive it from one number (`current`) and override a single step\'s `status` only for what a number cannot say \u2014 a completed step that now needs attention is `status="error"`.'
2349
- },
2350
- {
2351
- guidance: "do",
2352
- description: 'Name steps with a word or two ("Shipping", "Payment"). Go vertical when steps need a description or there are more than five; horizontal titles truncate first.'
1718
+ description: "Turn on dots and labels for six or fewer points, when the exact values are the story."
2353
1719
  },
2354
1720
  {
2355
1721
  guidance: "do",
2356
- description: "Give completed steps `onSelect` only when going back to edit them actually works, and keep the user's later answers when they do."
1722
+ description: 'Use curve="step" for values that hold until they change \u2014 prices, plan tiers, headcount.'
2357
1723
  },
2358
1724
  {
2359
1725
  guidance: "dont",
2360
- description: "Do not use Stepper for a task with unknown length or for a percentage. That is Progress; a stepper promises a fixed set of named stops."
1726
+ description: "Plot unordered categories (channels, products) as a line \u2014 the line implies a path between them; use BarChart."
2361
1727
  },
2362
1728
  {
2363
1729
  guidance: "dont",
2364
- description: "Do not use it as navigation between independent sections. Sections the user can visit in any order are Tabs; a stepper implies each step depends on the one before."
2365
- },
2366
- {
2367
- guidance: "dont",
2368
- description: "Do not make upcoming steps pressable to skip ahead. A step that looks reachable but is not is worse than one that is plainly locked."
2369
- }
2370
- ],
2371
- anatomy: [
2372
- {
2373
- name: "Stepper",
2374
- required: true,
2375
- description: "The ordered list. Takes `current` and `orientation`, and an aria-label naming the flow."
2376
- },
2377
- {
2378
- name: "StepperItem",
2379
- required: true,
2380
- description: 'One step: a numbered disc (a check when complete, a warning mark on error) \u2014 or, with `indicator="dot"`, a dot centred on the title\'s first line \u2014 a title, and an optional description.'
2381
- },
2382
- {
2383
- name: "Connector",
2384
- required: false,
2385
- description: "The line to the next step, filled once this step is complete. Drawn automatically between items."
1730
+ description: "Use it for data-heavy views \u2014 streaming or real-time data, thousands of points, zoom and brushing, trading charts. Those belong on a charting library (Recharts, visx, ECharts); Sable's charts are for dashboards, landing pages and decks."
2386
1731
  }
2387
1732
  ]
2388
1733
  },
2389
1734
  related: {
2390
- components: ["Progress", "Tabs", "Form", "Button"]
1735
+ components: [
1736
+ "AreaChart",
1737
+ "BarChart",
1738
+ "Sparkline",
1739
+ "Card"
1740
+ ]
2391
1741
  },
1742
+ status: "beta",
2392
1743
  props: [
2393
1744
  {
2394
- name: "current",
2395
- type: "number",
2396
- default: "0",
2397
- description: "Zero-based index of the current step. Earlier steps derive complete, later ones upcoming."
1745
+ name: "data",
1746
+ type: "Record<string, unknown>[]",
1747
+ description: "The rows, in order along the category axis."
2398
1748
  },
2399
1749
  {
2400
- name: "orientation",
2401
- type: '"horizontal" | "vertical"',
2402
- default: '"horizontal"',
2403
- description: "Horizontal for a short flow across the top of a form; vertical for long flows, narrow columns or steps with descriptions."
1750
+ name: "index",
1751
+ type: "string",
1752
+ description: "The datum field naming each row's category (a month, a day, a channel)."
2404
1753
  },
2405
1754
  {
2406
- name: "indicator",
2407
- type: '"number" | "dot"',
2408
- default: '"number"',
2409
- description: "`number` for a flow the user moves through; `dot` for a timeline of events (delivery tracking, a document's history) where the position number adds nothing. The dot centres on the title's first line and scales with its type at every density."
1755
+ name: "series",
1756
+ type: "{ key: string; label: string; tone?: ChartTone; color?: string; icon?: string }[]",
1757
+ description: "The measures to plot: the datum field, its name in the legend/tooltip/table, and its colour (a tone by role, or any CSS colour). Omitted, series take the chart palette in order: --chart-1 \u2026 --chart-8, validated for colour-blind separation in light and dark; a theme may override the set."
2410
1758
  },
2411
1759
  {
2412
- name: "statusLabels",
2413
- type: "Partial<Record<StepStatus, string>>",
2414
- description: "Screen-reader status words appended to each title (Completed, Current step, Not started, Needs attention). Override to localize."
1760
+ name: "label",
1761
+ type: "string",
1762
+ description: "What the chart shows. Its accessible name and the caption of the data table assistive tech reads."
2415
1763
  },
2416
1764
  {
2417
- name: "aria-label",
1765
+ name: "className",
2418
1766
  type: "string",
2419
- description: 'Names the flow ("Checkout progress"). Every Stepper needs one.'
2420
- }
2421
- ],
2422
- subcomponents: [
1767
+ default: '"h-60"',
1768
+ description: "Sizes the chart (plot + legend). Set a height: `h-72`, `aspect-video h-auto`."
1769
+ },
1770
+ {
1771
+ name: "valueFormatter",
1772
+ type: "(value: number) => string",
1773
+ description: 'Format values everywhere (axis, labels, tooltip, table) \u2014 units like "$12,480". Default: compact on the axis, full elsewhere.'
1774
+ },
1775
+ {
1776
+ name: "indexFormatter",
1777
+ type: "(value: string) => string",
1778
+ description: 'Shorten category labels on the axis ("January" \u2192 "Jan"); the tooltip keeps the full value.'
1779
+ },
1780
+ {
1781
+ name: "locale",
1782
+ type: "string",
1783
+ default: "runtime locale",
1784
+ description: "BCP 47 locale for default number and date formatting."
1785
+ },
1786
+ {
1787
+ name: "tooltip",
1788
+ type: 'boolean | { indicator?: "dot" | "line" | "dashed" | "none"; hideLabel?: boolean; labelFormatter?: (label, datum) => ReactNode; showTotal?: boolean }',
1789
+ default: "true",
1790
+ description: "Show a tooltip on hover and keyboard focus, or configure it: the row marker, hiding the heading, rewriting it, adding a Total row."
1791
+ },
1792
+ {
1793
+ name: "legend",
1794
+ type: "boolean",
1795
+ default: "true when there is more than one series",
1796
+ description: "Show the legend under the chart."
1797
+ },
1798
+ {
1799
+ name: "grid",
1800
+ type: "boolean",
1801
+ default: "true (false for horizontal bars)",
1802
+ description: "Hairline gridlines along the value axis."
1803
+ },
1804
+ {
1805
+ name: "xAxis",
1806
+ type: "boolean",
1807
+ default: "true for columns; false for horizontal bars",
1808
+ description: "The horizontal axis."
1809
+ },
1810
+ {
1811
+ name: "yAxis",
1812
+ type: "boolean",
1813
+ default: "false for columns; true for horizontal bars",
1814
+ description: "The vertical axis."
1815
+ },
1816
+ {
1817
+ name: "animate",
1818
+ type: "boolean",
1819
+ default: "true",
1820
+ description: "Play the arrival once the chart scrolls into view. Reduced motion shows the finished chart."
1821
+ },
1822
+ {
1823
+ name: "activeIndex",
1824
+ type: "number | null",
1825
+ description: "The highlighted row (controlled); `null` for none."
1826
+ },
1827
+ {
1828
+ name: "defaultActiveIndex",
1829
+ type: "number | null",
1830
+ description: "The row highlighted at first \u2014 a tooltip already open before anyone hovers."
1831
+ },
1832
+ {
1833
+ name: "onActiveIndexChange",
1834
+ type: "(index: number | null) => void",
1835
+ description: "Called as the pointer or keyboard moves between rows."
1836
+ },
1837
+ {
1838
+ name: "curve",
1839
+ type: '"smooth" | "linear" | "step"',
1840
+ default: '"smooth"',
1841
+ description: "How the line runs between points. `smooth` is monotone: it never overshoots a value the data does not contain."
1842
+ },
1843
+ {
1844
+ name: "dots",
1845
+ type: "boolean",
1846
+ default: "false",
1847
+ description: "Mark every data point with a dot."
1848
+ },
1849
+ {
1850
+ name: "colorBy",
1851
+ type: '"series" | "index"',
1852
+ default: '"series"',
1853
+ description: "Colour the dots by series, or each by its category."
1854
+ },
2423
1855
  {
2424
- name: "StepperItem",
2425
- type: "{ title: ReactNode; description?: ReactNode; status?: StepStatus; onSelect?: () => void } + li props",
2426
- description: "One step. `status` overrides the derived state; `onSelect` makes the disc and title a button for returning to the step."
1856
+ name: "labels",
1857
+ type: "boolean",
1858
+ default: "false",
1859
+ description: "Print each point's value above it."
2427
1860
  }
2428
1861
  ],
2429
- variants: {
2430
- orientation: ["horizontal", "vertical"],
2431
- indicator: ["number", "dot"],
2432
- status: ["complete", "current", "upcoming", "error"]
2433
- },
2434
1862
  tokensUsed: [
1863
+ "tone-edition",
1864
+ "tone-collectible",
1865
+ "tone-standard",
1866
+ "tone-info",
1867
+ "tone-warning",
2435
1868
  "primary",
2436
- "primary-foreground",
2437
- "background",
2438
- "foreground",
2439
1869
  "muted-foreground",
2440
- "border-strong",
2441
- "destructive",
2442
- "destructive-foreground",
2443
- "ring"
1870
+ "border",
1871
+ "popover",
1872
+ "popover-foreground",
1873
+ "foreground",
1874
+ "background"
2444
1875
  ],
2445
1876
  motion: [
2446
1877
  {
2447
1878
  slot: "arrival",
2448
- recipe: "none",
2449
- part: "Stepper",
2450
- why: "The stepper frames the flow and is on screen before any step is. Animating it in would compete with the step content, which is what changed."
1879
+ recipe: "custom",
1880
+ part: "Line",
1881
+ why: "Once on screen each line draws on from the first point (stroke-dashoffset), series staggered, then the dots and labels fade in. On the motion tokens; reduced motion shows the finished chart."
2451
1882
  },
2452
1883
  {
2453
1884
  slot: "response",
2454
- recipe: "motion-interactive",
2455
- part: "StepperItem",
2456
- why: "Only steps with onSelect respond, and the eased underline is what tells a returning user that this completed step can be reopened while the upcoming ones cannot."
1885
+ recipe: "none",
1886
+ part: "Cursor + tooltip",
1887
+ why: "Hover and focus move the cursor and tooltip instantly \u2014 a lagging cursor reads as a slow chart."
2457
1888
  },
2458
1889
  {
2459
1890
  slot: "transition",
2460
- recipe: "motion-interactive",
2461
- part: "Indicator",
2462
- why: "When the user continues, the disc they leave fills and its connector darkens. Easing the colour makes that read as one step completing rather than the whole bar re-rendering."
1891
+ recipe: "custom",
1892
+ part: "Line + dots",
1893
+ why: "Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place."
2463
1894
  },
2464
1895
  {
2465
1896
  slot: "activity",
2466
1897
  recipe: "none",
2467
- part: "Stepper",
2468
- why: "A step is a place, not a process. Saving or validating between steps belongs on the Continue button's loading state; a pulsing disc would suggest the step itself is busy."
1898
+ part: "LineChart",
1899
+ why: "A chart has no indeterminate state; show a Skeleton while the data loads."
2469
1900
  }
2470
1901
  ],
2471
1902
  a11y: [
2472
- "Renders an ordered list, so assistive tech announces the step count and each step's position.",
2473
- 'The current step carries aria-current="step"; every title also gets a visually hidden status word (Completed, Not started, Needs attention), so state never depends on colour or the disc glyph.',
2474
- "Discs and connectors are aria-hidden \u2014 the number is already the list position.",
2475
- "Steps with onSelect render as native buttons with a visible focus ring; steps without it are not focusable."
1903
+ "The drawing is aria-hidden; the same numbers are in a visually hidden data table (captioned by `label`) that the chart is aria-describedby, so assistive tech reads values, not shapes.",
1904
+ 'With a tooltip, the chart is one tab stop (role=group, aria-roledescription="chart"): arrow keys, Home and End step through the data, Escape clears, and each step is announced in a polite live region.',
1905
+ "Colour is never the only key: series are named in the legend, tooltip and table."
2476
1906
  ],
2477
1907
  examples: [
2478
1908
  {
2479
- label: "Checkout",
2480
- code: `<Stepper current={1} aria-label="Checkout progress">
2481
- <StepperItem title="Cart" />
2482
- <StepperItem title="Shipping" />
2483
- <StepperItem title="Payment" />
2484
- <StepperItem title="Review" />
2485
- </Stepper>`
2486
- },
2487
- {
2488
- label: "Vertical with descriptions",
2489
- code: `<Stepper orientation="vertical" current={2} aria-label="Workspace setup">
2490
- <StepperItem title="Create account" description="Email verified" />
2491
- <StepperItem title="Invite team" description="3 people invited" />
2492
- <StepperItem title="Connect data" description="Choose a source to import from" />
2493
- <StepperItem title="Launch" />
2494
- </Stepper>`
1909
+ label: "Compare two series",
1910
+ code: '<LineChart\n label="Desktop and mobile visitors"\n data={visitors}\n index="month"\n series={[\n { key: "desktop", label: "Desktop", tone: "primary" },\n { key: "mobile", label: "Mobile", tone: "info" },\n ]}\n indexFormatter={(m) => m.slice(0, 3)}\n/>'
2495
1911
  },
2496
1912
  {
2497
- label: "Delivery timeline (dots)",
2498
- code: `<Stepper orientation="vertical" indicator="dot" current={2} aria-label="Delivery status">
2499
- <StepperItem title="Order placed" description="Oct 9, 10:14 AM" />
2500
- <StepperItem title="Printed and packed" description="Oct 10, 4:02 PM" />
2501
- <StepperItem title="Out for delivery" description="Arriving today by 6 PM" />
2502
- <StepperItem title="Delivered" />
2503
- </Stepper>`
2504
- },
2505
- {
2506
- label: "A completed step with an error",
2507
- code: `<Stepper current={2} aria-label="Checkout progress">
2508
- <StepperItem title="Cart" onSelect={() => console.log("cart")} />
2509
- <StepperItem title="Shipping" status="error" description="Address needs a postcode" onSelect={() => console.log("shipping")} />
2510
- <StepperItem title="Payment" />
2511
- </Stepper>`
1913
+ label: "Dots and labels",
1914
+ code: '<LineChart\n label="Desktop visitors"\n data={visitors}\n index="month"\n series={[{ key: "desktop", label: "Desktop" }]}\n curve="linear"\n dots\n labels\n grid={false}\n/>'
2512
1915
  }
2513
1916
  ]
2514
1917
  };
2515
-
2516
- // src/lib/segmented.ts
2517
- var SEGMENTED_TRACK = "relative inline-flex w-fit flex-row items-center gap-0.5 rounded-full border border-border bg-card p-0.5";
2518
- var SEGMENTED_ITEM = "relative z-10 inline-flex h-7 min-w-7 items-center justify-center rounded-full text-muted-foreground motion-interactive motion-press hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
2519
- var SEGMENTED_STEP = "calc(var(--spacing) * 7.5)";
2520
- var ToggleAppearanceContext = createContext("default");
2521
- function Toggle({ className, ...props }) {
2522
- const appearance = useContext(ToggleAppearanceContext);
1918
+ function RadarChart({
1919
+ data,
1920
+ index,
1921
+ series: rawSeries,
1922
+ label,
1923
+ className,
1924
+ grid = "polygon",
1925
+ gridFill = false,
1926
+ filled = true,
1927
+ dots = false,
1928
+ radiusAxis = false,
1929
+ legend,
1930
+ tooltip = true,
1931
+ valueFormatter,
1932
+ locale,
1933
+ animate = true,
1934
+ activeIndex,
1935
+ defaultActiveIndex,
1936
+ onActiveIndexChange
1937
+ }) {
1938
+ const series = resolveSeries(rawSeries);
1939
+ const fmt = chartFormatters(locale, valueFormatter);
1940
+ const n = data.length;
1941
+ const spokes = data.map((d) => categoryText(d[index], locale));
1942
+ const values = series.map((s) => data.map((d) => Math.max(0, numberValue(d[s.key]))));
1943
+ const ticks = niceTicks(0, Math.max(0, ...values.flat()), 4);
1944
+ const max = ticks[ticks.length - 1] || 1;
1945
+ const angleAt = (i) => i / Math.max(1, n) * Math.PI * 2;
1946
+ const tooltipOptions = typeof tooltip === "object" ? tooltip : {};
1947
+ const geometry = (size) => {
1948
+ const labelW = Math.max(0, ...spokes.map(approxTextWidth));
1949
+ const outer = Math.max(8, Math.min(size.width / 2 - labelW - 10, size.height / 2 - 22));
1950
+ return { cx: size.width / 2, cy: size.height / 2, outer };
1951
+ };
1952
+ const ringPoints = (g, r) => spokes.map((_, i) => polar(g.cx, g.cy, r, angleAt(i)));
1953
+ const toPath = (pts) => pts.length ? `M${pts.map(([x, y]) => `${x.toFixed(2)},${y.toFixed(2)}`).join("L")}Z` : "";
1954
+ const hitTest = (x, y, size) => {
1955
+ const g = geometry(size);
1956
+ if (Math.hypot(x - g.cx, y - g.cy) > g.outer + 24 || !n) return null;
1957
+ const angle = (Math.atan2(x - g.cx, -(y - g.cy)) + Math.PI * 2) % (Math.PI * 2);
1958
+ return Math.round(angle / (Math.PI * 2 / n)) % n;
1959
+ };
1960
+ const describe = (i) => `${spokes[i]}: ${series.map((s, k) => `${s.label} ${fmt.full(values[k][i])}`).join(", ")}`;
1961
+ const tooltipFor = (i, size) => {
1962
+ const g = geometry(size);
1963
+ const [x, y] = polar(g.cx, g.cy, g.outer, angleAt(i));
1964
+ return {
1965
+ x,
1966
+ y,
1967
+ content: /* @__PURE__ */ jsx(
1968
+ ChartTooltipContent,
1969
+ {
1970
+ indicator: tooltipOptions.indicator,
1971
+ label: tooltipOptions.hideLabel ? void 0 : tooltipOptions.labelFormatter ? tooltipOptions.labelFormatter(spokes[i], data[i]) : spokes[i],
1972
+ rows: series.map((s, k) => ({ key: s.key, label: s.label, value: fmt.full(values[k][i]), color: s.colorValue, icon: s.icon }))
1973
+ }
1974
+ )
1975
+ };
1976
+ };
2523
1977
  return /* @__PURE__ */ jsx(
2524
- Toggle$1,
2525
- {
2526
- className: cn(
2527
- appearance === "segmented" ? cn(SEGMENTED_ITEM, "cursor-pointer px-2 text-label-md data-[pressed]:hover:text-accent-foreground") : cn(
2528
- "inline-flex h-(--control-sm) min-w-(--control-sm) cursor-pointer items-center justify-center rounded-sm px-2.5 text-label-md text-muted-foreground motion-interactive motion-press",
2529
- // Hover is the surface's quiet step, NOT the pressed fill: with
2530
- // both on bg-accent an unpressed toggle looked pressed under the
2531
- // pointer, which is the one state a toggle must never fake.
2532
- "sable-surface-interactive",
2533
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
2534
- ),
2535
- "data-[pressed]:bg-accent data-[pressed]:text-accent-foreground",
2536
- "disabled:pointer-events-none disabled:opacity-50",
2537
- "[&_svg]:shrink-0",
2538
- className
2539
- ),
2540
- ...props
1978
+ ChartFrame,
1979
+ {
1980
+ label,
1981
+ className: cn("h-72", className),
1982
+ count: n,
1983
+ interactive: tooltip !== false,
1984
+ animate,
1985
+ activeIndex,
1986
+ defaultActiveIndex,
1987
+ onActiveIndexChange,
1988
+ hitTest,
1989
+ tooltip: tooltip === false ? void 0 : tooltipFor,
1990
+ describe,
1991
+ table: {
1992
+ headers: [index.charAt(0).toUpperCase() + index.slice(1), ...series.map((s) => s.label)],
1993
+ rows: data.map((_, i) => [spokes[i], ...series.map((_2, k) => fmt.full(values[k][i]))])
1994
+ },
1995
+ legend: legend ?? series.length > 1 ? /* @__PURE__ */ jsx(ChartLegend, { items: series.map((s) => ({ key: s.key, label: s.label, color: s.colorValue, icon: s.icon })) }) : null,
1996
+ children: ({ size, activeIndex: active }) => {
1997
+ const g = geometry(size);
1998
+ const rings = ticks.slice(1).map((t) => t / max * g.outer);
1999
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2000
+ gridFill ? grid === "circle" ? /* @__PURE__ */ jsx("circle", { cx: g.cx, cy: g.cy, r: g.outer, className: "fill-foreground/[0.04]" }) : /* @__PURE__ */ jsx("path", { d: toPath(ringPoints(g, g.outer)), className: "fill-foreground/[0.04]" }) : null,
2001
+ grid !== "none" ? /* @__PURE__ */ jsxs("g", { className: "fill-none stroke-foreground/10", children: [
2002
+ rings.map(
2003
+ (r) => grid === "circle" ? /* @__PURE__ */ jsx("circle", { cx: g.cx, cy: g.cy, r }, r) : /* @__PURE__ */ jsx("path", { d: toPath(ringPoints(g, r)) }, r)
2004
+ ),
2005
+ spokes.map((s, i) => {
2006
+ const [x, y] = polar(g.cx, g.cy, g.outer, angleAt(i));
2007
+ return /* @__PURE__ */ jsx("line", { x1: g.cx, y1: g.cy, x2: x, y2: y }, s);
2008
+ })
2009
+ ] }) : null,
2010
+ series.map((s, k) => {
2011
+ const pts = values[k].map((v, i) => polar(g.cx, g.cy, v / max * g.outer, angleAt(i)));
2012
+ const d = toPath(pts);
2013
+ return /* @__PURE__ */ jsxs("g", { children: [
2014
+ /* @__PURE__ */ jsx(
2015
+ "path",
2016
+ {
2017
+ d,
2018
+ strokeWidth: 2,
2019
+ strokeLinejoin: "round",
2020
+ className: "[[data-reveal=out]_&]:scale-0 [[data-reveal=out]_&]:opacity-0",
2021
+ style: {
2022
+ ...pathStyle(d),
2023
+ stroke: s.colorValue,
2024
+ fill: filled ? s.colorValue : "none",
2025
+ fillOpacity: filled ? 0.24 : 0,
2026
+ transformOrigin: `${g.cx}px ${g.cy}px`,
2027
+ transition: riseTransition(stagger(k, 120))
2028
+ }
2029
+ }
2030
+ ),
2031
+ dots || active != null ? pts.map(
2032
+ ([x, y], i) => dots || active === i ? /* @__PURE__ */ jsx(
2033
+ "circle",
2034
+ {
2035
+ cx: x,
2036
+ cy: y,
2037
+ r: active === i ? 5 : 4,
2038
+ strokeWidth: 2,
2039
+ className: "stroke-(--sable-surface-bg,var(--background)) [[data-reveal=out]_&]:opacity-0",
2040
+ style: { fill: s.colorValue, transition: morphTransition() }
2041
+ },
2042
+ spokes[i]
2043
+ ) : null
2044
+ ) : null
2045
+ ] }, s.key);
2046
+ }),
2047
+ spokes.map((s, i) => {
2048
+ const a = angleAt(i);
2049
+ const [x, y] = polar(g.cx, g.cy, g.outer + 12, a);
2050
+ const anchor = Math.sin(a) > 0.1 ? "start" : Math.sin(a) < -0.1 ? "end" : "middle";
2051
+ return /* @__PURE__ */ jsx("text", { x, y, dy: "0.32em", textAnchor: anchor, className: cn("text-caption", active === i ? "fill-foreground" : "fill-muted-foreground"), children: s }, s);
2052
+ }),
2053
+ radiusAxis ? ticks.slice(1, -1).map((t) => /* @__PURE__ */ jsx("text", { x: g.cx + 4, y: g.cy - t / max * g.outer, dy: "-0.2em", className: "fill-muted-foreground text-caption tabular-nums", children: fmt.axis(t) }, t)) : null
2054
+ ] });
2055
+ }
2541
2056
  }
2542
2057
  );
2543
2058
  }
2544
- Toggle.displayName = "Toggle";
2545
- function ToggleGroup({ className, appearance = "default", ...props }) {
2546
- return /* @__PURE__ */ jsx(ToggleAppearanceContext.Provider, { value: appearance, children: /* @__PURE__ */ jsx(
2547
- ToggleGroup$1,
2548
- {
2549
- "data-appearance": appearance,
2550
- className: cn(
2551
- appearance === "segmented" ? SEGMENTED_TRACK : "inline-flex items-center gap-1",
2552
- className
2553
- ),
2554
- ...props,
2555
- "aria-orientation": props.role === "toolbar" ? props.orientation ?? "horizontal" : void 0
2556
- }
2557
- ) });
2558
- }
2559
- ToggleGroup.displayName = "ToggleGroup";
2059
+ RadarChart.displayName = "RadarChart";
2560
2060
 
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].",
2565
- status: "stable",
2061
+ // src/components/radar-chart/radar-chart.meta.ts
2062
+ var radarChartMeta = {
2063
+ name: "RadarChart",
2064
+ summary: "Several measures compared around a centre: one polygon per series over a polygon or circular web, filled or outline only, with optional dots, a radius scale and a tinted web. Polygons grow from the centre as the chart scrolls into view.",
2566
2065
  keywords: [
2567
- "toggle",
2568
- "pressed",
2569
- "button state",
2570
- "toolbar",
2571
- "segmented control",
2572
- "segmented",
2573
- "toggle group",
2574
- "button group",
2575
- "view switcher"
2066
+ "chart",
2067
+ "radar",
2068
+ "spider",
2069
+ "web",
2070
+ "profile",
2071
+ "skills",
2072
+ "comparison",
2073
+ "polar"
2576
2074
  ],
2577
2075
  usage: {
2578
- description: "Use Toggle for button-like controls that can be pressed on or off within a toolbar or compact control set.",
2076
+ description: "Use RadarChart to compare the shape of a profile across five to eight dimensions \u2014 skills, product scores, a cycle of months \u2014 for one to three series. The shape is the message; for exact values or more than three series, BarChart is easier to read.",
2579
2077
  bestPractices: [
2580
2078
  {
2581
2079
  guidance: "do",
2582
- description: "Use it for transient UI modes such as bold, preview, pin, or favorite."
2583
- },
2584
- {
2585
- guidance: "do",
2586
- description: "Make the pressed state visually and programmatically clear."
2080
+ description: "Keep every spoke on the same scale and direction (more is better on all of them)."
2587
2081
  },
2588
2082
  {
2589
2083
  guidance: "do",
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.'
2084
+ description: "Use filled={false} when two polygons overlap heavily, so both outlines stay visible."
2591
2085
  },
2592
2086
  {
2593
2087
  guidance: "dont",
2594
- description: "Do not use Toggle for persistent application settings; use Switch."
2088
+ description: "Use fewer than five spokes \u2014 three or four make a triangle or square that says little; use BarChart."
2595
2089
  },
2596
2090
  {
2597
2091
  guidance: "dont",
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.'
2599
- }
2600
- ],
2601
- anatomy: [
2602
- {
2603
- name: "Toggle",
2604
- required: true,
2605
- description: "A single pressed or unpressed control."
2606
- },
2607
- {
2608
- name: "ToggleGroup",
2609
- required: false,
2610
- description: "A coordinated group of toggle choices."
2611
- },
2612
- {
2613
- name: "ToggleGroupItem",
2614
- required: false,
2615
- description: 'One toggle inside a group \u2014 an alias of Toggle, so <Toggle value="\u2026"> inside a ToggleGroup is the same thing.'
2092
+ description: "Use it for data-heavy views \u2014 streaming or real-time data, thousands of points, zoom and brushing, trading charts. Those belong on a charting library (Recharts, visx, ECharts); Sable's charts are for dashboards, landing pages and decks."
2616
2093
  }
2617
2094
  ]
2618
2095
  },
2619
2096
  related: {
2620
2097
  components: [
2621
- "Switch",
2622
- "Button",
2623
- "Tabs"
2098
+ "BarChart",
2099
+ "LineChart",
2100
+ "Card"
2624
2101
  ]
2625
2102
  },
2103
+ status: "beta",
2626
2104
  props: [
2627
2105
  {
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."
2106
+ name: "data",
2107
+ type: "Record<string, unknown>[]",
2108
+ description: "One row per spoke."
2632
2109
  },
2633
2110
  {
2634
- name: "multiple",
2635
- type: "boolean",
2636
- default: "false",
2637
- description: "On ToggleGroup: allow several toggles pressed at once. The default is single-select, which behaves like a radio group."
2111
+ name: "index",
2112
+ type: "string",
2113
+ description: "The datum field naming each spoke."
2638
2114
  },
2639
2115
  {
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."
2644
- }
2645
- ],
2646
- subcomponents: [
2647
- {
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."
2116
+ name: "series",
2117
+ type: "{ key: string; label: string; tone?: ChartTone; color?: string; icon?: string }[]",
2118
+ description: "The measures to plot: the datum field, its name in the legend/tooltip/table, and its colour (a tone by role, or any CSS colour). Omitted, series take the chart palette in order: --chart-1 \u2026 --chart-8, validated for colour-blind separation in light and dark; a theme may override the set."
2651
2119
  },
2652
2120
  {
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."
2656
- }
2121
+ name: "label",
2122
+ type: "string",
2123
+ description: "What the chart shows. Its accessible name and the caption of the data table assistive tech reads."
2124
+ },
2125
+ {
2126
+ name: "className",
2127
+ type: "string",
2128
+ default: '"h-72"',
2129
+ description: "Sizes the chart. Set a height."
2130
+ },
2131
+ {
2132
+ name: "grid",
2133
+ type: '"polygon" | "circle" | "none"',
2134
+ default: '"polygon"',
2135
+ description: "The web behind the polygons."
2136
+ },
2137
+ {
2138
+ name: "gridFill",
2139
+ type: "boolean",
2140
+ default: "false",
2141
+ description: "Tint the web's outer area."
2142
+ },
2143
+ {
2144
+ name: "filled",
2145
+ type: "boolean",
2146
+ default: "true",
2147
+ description: "Fill each polygon; `false` draws outlines only."
2148
+ },
2149
+ {
2150
+ name: "dots",
2151
+ type: "boolean",
2152
+ default: "false",
2153
+ description: "Mark every vertex with a dot."
2154
+ },
2155
+ {
2156
+ name: "radiusAxis",
2157
+ type: "boolean",
2158
+ default: "false",
2159
+ description: "Print the ring values along the top spoke."
2160
+ },
2161
+ {
2162
+ name: "legend",
2163
+ type: "boolean",
2164
+ default: "true when there is more than one series",
2165
+ description: "Show the legend."
2166
+ },
2167
+ {
2168
+ name: "tooltip",
2169
+ type: 'boolean | { indicator?: "dot" | "line" | "dashed" | "none"; hideLabel?: boolean; labelFormatter?: (label, datum) => ReactNode; showTotal?: boolean }',
2170
+ default: "true",
2171
+ description: "Show a tooltip on hover and keyboard focus, or configure it: the row marker, hiding the heading, rewriting it, adding a Total row."
2172
+ },
2173
+ {
2174
+ name: "valueFormatter",
2175
+ type: "(value: number) => string",
2176
+ description: 'Format values everywhere (axis, labels, tooltip, table) \u2014 units like "$12,480". Default: compact on the axis, full elsewhere.'
2177
+ },
2178
+ {
2179
+ name: "locale",
2180
+ type: "string",
2181
+ default: "runtime locale",
2182
+ description: "BCP 47 locale for default number and date formatting."
2183
+ },
2184
+ {
2185
+ name: "animate",
2186
+ type: "boolean",
2187
+ default: "true",
2188
+ description: "Grow the polygons from the centre when the chart scrolls into view."
2189
+ },
2190
+ {
2191
+ name: "activeIndex",
2192
+ type: "number | null",
2193
+ description: "The highlighted row (controlled); `null` for none."
2194
+ },
2195
+ {
2196
+ name: "defaultActiveIndex",
2197
+ type: "number | null",
2198
+ description: "The row highlighted at first \u2014 a tooltip already open before anyone hovers."
2199
+ },
2200
+ {
2201
+ name: "onActiveIndexChange",
2202
+ type: "(index: number | null) => void",
2203
+ description: "Called as the pointer or keyboard moves between rows."
2204
+ }
2657
2205
  ],
2658
- variants: {
2659
- appearance: ["default", "segmented"]
2660
- },
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."
2206
+ tokensUsed: [
2207
+ "tone-edition",
2208
+ "tone-collectible",
2209
+ "tone-standard",
2210
+ "tone-info",
2211
+ "tone-warning",
2212
+ "primary",
2213
+ "muted-foreground",
2214
+ "border",
2215
+ "popover",
2216
+ "popover-foreground",
2217
+ "foreground",
2218
+ "background"
2667
2219
  ],
2668
- examples: [
2220
+ motion: [
2221
+ {
2222
+ slot: "arrival",
2223
+ recipe: "custom",
2224
+ part: "Polygon",
2225
+ why: "Once on screen each polygon grows from the centre and fades in, series staggered, on the emphasized easing. On the motion tokens; reduced motion shows the finished chart."
2226
+ },
2669
2227
  {
2670
- label: "Standalone",
2671
- code: '<Toggle aria-label="Mute">Mute</Toggle>'
2228
+ slot: "response",
2229
+ recipe: "none",
2230
+ part: "Active spoke",
2231
+ why: "The hovered spoke's vertices and label highlight at once."
2672
2232
  },
2673
2233
  {
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>'
2234
+ slot: "transition",
2235
+ recipe: "custom",
2236
+ part: "Polygon",
2237
+ why: "Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place."
2676
2238
  },
2677
2239
  {
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>'
2240
+ slot: "activity",
2241
+ recipe: "none",
2242
+ part: "RadarChart",
2243
+ why: "A chart has no indeterminate state; show a Skeleton while the data loads."
2244
+ }
2245
+ ],
2246
+ a11y: [
2247
+ "The drawing is aria-hidden; the same numbers are in a visually hidden data table (captioned by `label`) that the chart is aria-describedby, so assistive tech reads values, not shapes.",
2248
+ 'With a tooltip, the chart is one tab stop (role=group, aria-roledescription="chart"): arrow keys, Home and End step through the data, Escape clears, and each step is announced in a polite live region.',
2249
+ "Colour is never the only key: series are named in the legend, tooltip and table."
2250
+ ],
2251
+ examples: [
2252
+ {
2253
+ label: "Two profiles",
2254
+ code: 'const scores = [\n { skill: "Speed", a: 86, b: 65 },\n { skill: "Quality", a: 72, b: 90 },\n { skill: "Support", a: 64, b: 80 },\n { skill: "Price", a: 90, b: 55 },\n { skill: "Docs", a: 70, b: 85 },\n { skill: "Ecosystem", a: 60, b: 75 },\n]\n\n<RadarChart\n label="Product scores compared"\n data={scores}\n index="skill"\n series={[\n { key: "a", label: "Atlas" },\n { key: "b", label: "Beacon" },\n ]}\n/>'
2680
2255
  },
2681
2256
  {
2682
- label: "Multi-select group",
2683
- code: '<ToggleGroup multiple defaultValue={["bold"]}><Toggle value="bold">Bold</Toggle><Toggle value="italic">Italic</Toggle></ToggleGroup>'
2257
+ label: "Circular web, outlines only",
2258
+ code: '<RadarChart\n label="Atlas scores"\n data={scores}\n index="skill"\n series={[{ key: "a", label: "Atlas" }]}\n grid="circle"\n gridFill\n filled={false}\n dots\n/>'
2684
2259
  }
2685
2260
  ]
2686
2261
  };
@@ -4333,211 +3908,6 @@ var toastMeta = {
4333
3908
  }
4334
3909
  ]
4335
3910
  };
4336
- var Accordion = Accordion$1.Root;
4337
- function AccordionItem({ className, ...props }) {
4338
- return /* @__PURE__ */ jsx(
4339
- Accordion$1.Item,
4340
- {
4341
- className: cn("border-b border-border", className),
4342
- ...props
4343
- }
4344
- );
4345
- }
4346
- function AccordionTrigger({
4347
- className,
4348
- children,
4349
- indicator,
4350
- ...props
4351
- }) {
4352
- return /* @__PURE__ */ jsx(Accordion$1.Header, { children: /* @__PURE__ */ jsxs(
4353
- Accordion$1.Trigger,
4354
- {
4355
- className: cn(
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",
4357
- !indicator && "[&[data-panel-open]_[data-slot=accordion-chevron]]:rotate-180",
4358
- className
4359
- ),
4360
- ...props,
4361
- children: [
4362
- children,
4363
- /* @__PURE__ */ jsx(
4364
- "span",
4365
- {
4366
- "data-slot": "accordion-chevron",
4367
- className: "flex h-[1lh] shrink-0 items-center motion-interactive",
4368
- children: indicator ?? /* @__PURE__ */ jsx(Icon, { name: "chevron-down", className: "text-xs text-muted-foreground" })
4369
- }
4370
- )
4371
- ]
4372
- }
4373
- ) });
4374
- }
4375
- function AccordionPanel({ className, children, ...props }) {
4376
- return /* @__PURE__ */ jsx(
4377
- Accordion$1.Panel,
4378
- {
4379
- className: cn(
4380
- "h-[var(--accordion-panel-height)] text-body-md text-muted-foreground motion-disclosure data-[ending-style]:h-0 data-[starting-style]:h-0",
4381
- className
4382
- ),
4383
- ...props,
4384
- children: /* @__PURE__ */ jsx("div", { className: "pb-4", children })
4385
- }
4386
- );
4387
- }
4388
-
4389
- // src/components/accordion/accordion.meta.ts
4390
- var accordionMeta = {
4391
- name: "Accordion",
4392
- summary: "Stacked, collapsible disclosure sections over Base UI Accordion \u2014 roving focus, arrow-key navigation, ARIA region/heading/button wiring, and an animated height transition. Each item pairs a full-width trigger (with a rotating chevron) and a panel.",
4393
- status: "stable",
4394
- keywords: [
4395
- "accordion",
4396
- "disclosure",
4397
- "collapse",
4398
- "faq"
4399
- ],
4400
- usage: {
4401
- description: "Use Accordion to reveal a vertical set of related sections while keeping the surrounding page scannable.",
4402
- bestPractices: [
4403
- {
4404
- guidance: "do",
4405
- description: "Use concise trigger text that describes the content revealed below it."
4406
- },
4407
- {
4408
- guidance: "do",
4409
- description: "Keep section content focused so expanding one item does not feel like navigating to a new page."
4410
- },
4411
- {
4412
- guidance: "dont",
4413
- description: "Do not hide required fields, primary actions, or critical errors inside collapsed sections."
4414
- },
4415
- {
4416
- guidance: "dont",
4417
- description: "Do not nest an Accordion inside an Accordion. Two layers of roving focus fight each other, and the arrow keys become ambiguous \u2014 flatten the hierarchy or use a Tree."
4418
- }
4419
- ],
4420
- anatomy: [
4421
- {
4422
- name: "AccordionItem",
4423
- required: true,
4424
- description: "One expandable section in the group."
4425
- },
4426
- {
4427
- name: "AccordionTrigger",
4428
- required: true,
4429
- description: "The button-like control that opens the section."
4430
- },
4431
- {
4432
- name: "AccordionPanel",
4433
- required: true,
4434
- description: "The content revealed by the trigger."
4435
- }
4436
- ]
4437
- },
4438
- related: {
4439
- components: [
4440
- "Collapsible",
4441
- "Tabs"
4442
- ]
4443
- },
4444
- props: [
4445
- {
4446
- name: "Accordion (Root)",
4447
- type: "Base UI Accordion.Root props (value, defaultValue, multiple, disabled, onValueChange, orientation, loopFocus, keepMounted, hiddenUntilFound)",
4448
- default: "\u2014",
4449
- description: "Groups all items and owns the open value(s). Uncontrolled via defaultValue, controlled via value."
4450
- },
4451
- {
4452
- name: "defaultValue",
4453
- type: "Value[]",
4454
- default: "[]",
4455
- description: "Uncontrolled set of item values that start expanded (e.g. ['faq-1'])."
4456
- },
4457
- {
4458
- name: "value",
4459
- type: "Value[]",
4460
- default: "\u2014",
4461
- description: "Controlled set of expanded item values; pair with onValueChange."
4462
- },
4463
- {
4464
- name: "onValueChange",
4465
- type: "(value: Value[]) => void",
4466
- description: "Called with the new set of expanded item values when an item opens or closes."
4467
- },
4468
- {
4469
- name: "multiple",
4470
- type: "boolean",
4471
- default: "false",
4472
- description: "Whether more than one item can be open at the same time. When false, opening one closes the others."
4473
- }
4474
- ],
4475
- subcomponents: [
4476
- {
4477
- name: "AccordionItem",
4478
- type: "Base UI Accordion.Item props + className (value, disabled, onOpenChange)",
4479
- description: "Pairs a header with its panel. Give each a stable `value` to control or set initial open state."
4480
- },
4481
- {
4482
- name: "AccordionTrigger",
4483
- type: "Base UI Accordion.Trigger props + className + indicator?: ReactNode",
4484
- description: "The header button (Header + Trigger composed). Renders children plus a chevron that rotates via data-[panel-open] by default; pass `indicator` to replace the icon and its open-state behavior entirely (e.g. a plus that becomes an \xD7 for a FAQ-style toggle)."
4485
- },
4486
- {
4487
- name: "AccordionPanel",
4488
- type: "Base UI Accordion.Panel props + className (keepMounted, hiddenUntilFound)",
4489
- description: "The collapsible body. Animates height using the --accordion-panel-height CSS var; fades on data-[starting-style]/data-[ending-style]."
4490
- }
4491
- ],
4492
- tokensUsed: ["border", "foreground", "muted-foreground", "ring"],
4493
- motion: [
4494
- {
4495
- slot: "arrival",
4496
- recipe: "none",
4497
- part: "Root",
4498
- why: "An accordion is page furniture that is present from first paint. Animating it in would stage content the reader is already scanning past."
4499
- },
4500
- {
4501
- slot: "response",
4502
- recipe: "motion-interactive",
4503
- part: "AccordionTrigger",
4504
- why: "The whole header row is the hit target, and it looks like a heading rather than a control. The hover shift is what tells the reader it can be clicked before they click it."
4505
- },
4506
- {
4507
- slot: "transition",
4508
- recipe: "motion-disclosure",
4509
- part: "AccordionPanel",
4510
- why: "Panel height is the only cue that content came from THIS header rather than appearing at the bottom of the page. Snapping it open moves everything below by a screenful with nothing connecting cause to effect."
4511
- },
4512
- {
4513
- slot: "activity",
4514
- recipe: "none",
4515
- part: "Root",
4516
- why: "Nothing here is indeterminate \u2014 the panel content is already resident. A panel that loads on expand should put its own loader inside the panel."
4517
- }
4518
- ],
4519
- a11y: [
4520
- "Base UI renders the trigger inside an <h3> heading and wires aria-expanded / aria-controls to the panel region.",
4521
- "Arrow keys move roving focus between triggers; Home/End jump to the first/last; loopFocus wraps by default.",
4522
- "Each trigger toggles its panel with Enter/Space; the panel is announced as a labelled region.",
4523
- "A disabled AccordionItem dims and is skipped by keyboard focus while neighbouring items stay interactive.",
4524
- "The chevron rotation is driven by data-[panel-open] and the focus ring by focus-visible \u2014 both honor reduced-motion via the consumer's transitions."
4525
- ],
4526
- examples: [
4527
- {
4528
- label: "Single (default)",
4529
- code: '<Accordion defaultValue={["a"]}><AccordionItem value="a"><AccordionTrigger>Is it permanent?</AccordionTrigger><AccordionPanel>Yes \u2014 stored on Arweave.</AccordionPanel></AccordionItem><AccordionItem value="b"><AccordionTrigger>How much?</AccordionTrigger><AccordionPanel>A one-time mint fee.</AccordionPanel></AccordionItem></Accordion>'
4530
- },
4531
- {
4532
- label: "Multiple open",
4533
- code: '<Accordion multiple defaultValue={["a", "b"]}><AccordionItem value="a"><AccordionTrigger>First</AccordionTrigger><AccordionPanel>One.</AccordionPanel></AccordionItem><AccordionItem value="b"><AccordionTrigger>Second</AccordionTrigger><AccordionPanel>Two.</AccordionPanel></AccordionItem></Accordion>'
4534
- },
4535
- {
4536
- label: "Custom indicator (plus-becomes-\xD7)",
4537
- code: '<Accordion defaultValue={["a"]}><AccordionItem value="a"><AccordionTrigger indicator={<Icon name="plus" className="text-xs text-muted-foreground transition-transform [[data-panel-open]_&]:rotate-45" />}>Is it permanent?</AccordionTrigger><AccordionPanel>Yes \u2014 stored on Arweave.</AccordionPanel></AccordionItem></Accordion>'
4538
- }
4539
- ]
4540
- };
4541
3911
  var Collapsible = Collapsible$1.Root;
4542
3912
  var CollapsibleTrigger = Collapsible$1.Trigger;
4543
3913
  function CollapsibleIndicator({ className }) {
@@ -4680,320 +4050,45 @@ var collapsibleMeta = {
4680
4050
  }
4681
4051
  ]
4682
4052
  };
4683
- var SliderRoot = Slider$1.Root;
4684
- var SliderValue = Slider$1.Value;
4685
- function SliderControl({ className, ...props }) {
4686
- return /* @__PURE__ */ jsx(
4687
- Slider$1.Control,
4688
- {
4689
- className: cn(
4690
- "relative flex h-5 w-full items-center data-[disabled]:opacity-50",
4691
- // Vertical: the same lane turned on its side. Give the slider a
4692
- // height (h-40, flex-1) — a vertical track has no intrinsic length.
4693
- "data-[orientation=vertical]:h-full data-[orientation=vertical]:w-5 data-[orientation=vertical]:justify-center",
4694
- className
4695
- ),
4696
- ...props
4697
- }
4053
+ var useComboboxFilteredItems = Combobox$1.useFilteredItems;
4054
+ var ComboboxVirtualContext = createContext(null);
4055
+ function Combobox({
4056
+ onItemHighlighted,
4057
+ ...props
4058
+ }) {
4059
+ const revealRef = useRef(null);
4060
+ const highlightRef = useRef(onItemHighlighted);
4061
+ highlightRef.current = onItemHighlighted;
4062
+ const handleHighlight = useCallback(
4063
+ (value, details) => {
4064
+ highlightRef.current?.(
4065
+ value,
4066
+ details
4067
+ );
4068
+ const reveal = revealRef.current;
4069
+ if (reveal && details.index >= 0) {
4070
+ queueMicrotask(() => reveal(details.index));
4071
+ }
4072
+ },
4073
+ []
4698
4074
  );
4699
- }
4700
- function SliderTrack({ className, ...props }) {
4701
- return /* @__PURE__ */ jsx(
4702
- Slider$1.Track,
4075
+ const bridge = useMemo(
4076
+ () => ({
4077
+ register(reveal) {
4078
+ revealRef.current = reveal;
4079
+ },
4080
+ value: props.value,
4081
+ isItemEqualToValue: props.isItemEqualToValue
4082
+ }),
4083
+ [props.value, props.isItemEqualToValue]
4084
+ );
4085
+ return /* @__PURE__ */ jsx(ComboboxVirtualContext.Provider, { value: bridge, children: /* @__PURE__ */ jsx(
4086
+ Combobox$1.Root,
4703
4087
  {
4704
- className: cn("h-1.5 w-full rounded-full bg-secondary data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5", className),
4705
- ...props
4088
+ ...props,
4089
+ onItemHighlighted: handleHighlight
4706
4090
  }
4707
- );
4708
- }
4709
- function SliderIndicator({ className, ...props }) {
4710
- return /* @__PURE__ */ jsx(
4711
- Slider$1.Indicator,
4712
- {
4713
- className: cn("h-full rounded-full bg-primary data-[orientation=vertical]:w-full", className),
4714
- ...props
4715
- }
4716
- );
4717
- }
4718
- function SliderThumb({ className, children, ...props }) {
4719
- return /* @__PURE__ */ jsxs(
4720
- Slider$1.Thumb,
4721
- {
4722
- className: cn(
4723
- // The hit target: fixed box, no paint, no transform. Never resize this.
4724
- "group/thumb size-4 rounded-full motion-interactive",
4725
- "has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring",
4726
- "data-[disabled]:pointer-events-none",
4727
- className
4728
- ),
4729
- ...props,
4730
- children: [
4731
- /* @__PURE__ */ jsx(
4732
- "span",
4733
- {
4734
- "aria-hidden": true,
4735
- className: cn(
4736
- "pointer-events-none absolute inset-0 rounded-full border-2 border-primary bg-background shadow-sm",
4737
- // The only thing that resizes. Scale (not width/height) so the box
4738
- // metrics — and the row height around them — never move.
4739
- "scale-75 motion-interactive ease-spring",
4740
- "group-hover/thumb:scale-100 group-has-[:focus-visible]/thumb:scale-100",
4741
- "group-data-[dragging]/thumb:scale-110"
4742
- )
4743
- }
4744
- ),
4745
- children
4746
- ]
4747
- }
4748
- );
4749
- }
4750
- function Slider({
4751
- className,
4752
- ...props
4753
- }) {
4754
- const current = props.value ?? props.defaultValue;
4755
- const thumbCount = Array.isArray(current) ? current.length : 1;
4756
- return /* @__PURE__ */ jsx(
4757
- Slider$1.Root,
4758
- {
4759
- className: cn("w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-auto", className),
4760
- ...props,
4761
- children: /* @__PURE__ */ jsx(SliderControl, { children: /* @__PURE__ */ jsxs(SliderTrack, { children: [
4762
- /* @__PURE__ */ jsx(SliderIndicator, {}),
4763
- Array.from({ length: thumbCount }, (_, i) => /* @__PURE__ */ jsx(SliderThumb, { index: thumbCount > 1 ? i : void 0 }, i))
4764
- ] }) })
4765
- }
4766
- );
4767
- }
4768
- Slider.displayName = "Slider";
4769
-
4770
- // src/components/slider/slider.meta.ts
4771
- var sliderMeta = {
4772
- name: "Slider",
4773
- summary: "Slider over Base UI Slider \u2014 pointer + keyboard control, ARIA slider wiring, and range support via multiple thumbs. The default export composes Root + Control + Track + Indicator + Thumb; pass an array value for a range.",
4774
- status: "stable",
4775
- keywords: [
4776
- "slider",
4777
- "range",
4778
- "value",
4779
- "input"
4780
- ],
4781
- usage: {
4782
- description: "Use Slider for bounded numeric choices where approximate adjustment is faster than typing.",
4783
- bestPractices: [
4784
- {
4785
- guidance: "do",
4786
- description: "Use visible labels, min/max context, and value output when precision matters."
4787
- },
4788
- {
4789
- guidance: "do",
4790
- description: "Choose steps that match the real granularity of the setting."
4791
- },
4792
- {
4793
- guidance: "dont",
4794
- description: "Do not use Slider when users must enter an exact arbitrary number; use NumberField."
4795
- },
4796
- {
4797
- guidance: "dont",
4798
- description: "Do not use it without showing the value. A slider with no numeric readout makes any precise choice guesswork \u2014 pair it with a ScrubField or a NumberField."
4799
- },
4800
- {
4801
- guidance: "dont",
4802
- description: "Do not resize the thumb's box on hover, focus, or drag. The thumb is a fixed-size hit target wrapping a decorative dot; only the dot scales. A target that grows under the cursor is a target nobody can learn, and WCAG 2.5.8 measures target size at rest. Restyle the dot, not the box."
4803
- }
4804
- ],
4805
- anatomy: [
4806
- {
4807
- name: "SliderRoot",
4808
- required: true,
4809
- description: "The controlled slider wrapper."
4810
- },
4811
- {
4812
- name: "SliderControl",
4813
- required: true,
4814
- description: "The interactive slider control region."
4815
- },
4816
- {
4817
- name: "SliderTrack",
4818
- required: true,
4819
- description: "The visible value range."
4820
- },
4821
- {
4822
- name: "SliderIndicator",
4823
- required: false,
4824
- description: "The filled portion of the track."
4825
- },
4826
- {
4827
- name: "SliderThumb",
4828
- required: true,
4829
- description: "The draggable handle."
4830
- },
4831
- {
4832
- name: "SliderValue",
4833
- required: false,
4834
- description: "A readable value display."
4835
- }
4836
- ]
4837
- },
4838
- related: {
4839
- components: [
4840
- "NumberField",
4841
- "Progress"
4842
- ]
4843
- },
4844
- props: [
4845
- {
4846
- name: "value",
4847
- type: "number | number[]",
4848
- default: "\u2014",
4849
- description: "Controlled value. An array renders a range slider with one thumb per entry."
4850
- },
4851
- {
4852
- name: "defaultValue",
4853
- type: "number | number[]",
4854
- default: "\u2014",
4855
- description: "Uncontrolled initial value. An array renders a range slider."
4856
- },
4857
- {
4858
- name: "onValueChange",
4859
- type: "(value, eventDetails) => void",
4860
- default: "\u2014",
4861
- description: "Fires on every value change. value is a number (or number[] for a range)."
4862
- },
4863
- {
4864
- name: "min",
4865
- type: "number",
4866
- default: "0",
4867
- description: "Minimum allowed value."
4868
- },
4869
- {
4870
- name: "max",
4871
- type: "number",
4872
- default: "100",
4873
- description: "Maximum allowed value."
4874
- },
4875
- {
4876
- name: "step",
4877
- type: "number",
4878
- default: "1",
4879
- description: "Granularity the slider steps through. Snaps to multiples."
4880
- },
4881
- {
4882
- name: "disabled",
4883
- type: "boolean",
4884
- default: "false",
4885
- description: "Ignore user interaction and dim the control."
4886
- },
4887
- {
4888
- name: "orientation",
4889
- type: "'horizontal' | 'vertical'",
4890
- default: "horizontal",
4891
- description: "Axis the slider tracks along. A vertical slider has no intrinsic length \u2014 give it a height (h-40, or flex-1 in a column)."
4892
- }
4893
- ],
4894
- subcomponents: [
4895
- {
4896
- name: "SliderRoot",
4897
- type: "Base UI Slider.Root props",
4898
- description: "The unstyled Base UI Root for custom composition \u2014 owns the value state and forwards value / defaultValue / onValueChange / min / max / step. Drag state surfaces via data-[dragging]. The default Slider export wires all the parts for you."
4899
- },
4900
- {
4901
- name: "SliderControl",
4902
- type: "Base UI Control props + className",
4903
- description: "The interactive track lane \u2014 the positioning context the indicator and thumbs sit in; dims when disabled."
4904
- },
4905
- {
4906
- name: "SliderTrack",
4907
- type: "Base UI Track props + className",
4908
- description: "The full-range rail beneath the indicator."
4909
- },
4910
- {
4911
- name: "SliderIndicator",
4912
- type: "Base UI Indicator props + className",
4913
- description: "The filled portion from min (or the lower thumb) to the current value."
4914
- },
4915
- {
4916
- name: "SliderThumb",
4917
- type: "Base UI Thumb props + className",
4918
- description: "A draggable handle: a fixed-size hit target wrapping a decorative dot. The dot rests small and scales to full size on hover, keyboard focus, and drag; the box never changes, so row metrics and the tap target hold. Range sliders render one per value; pass index for SSR. Interaction state surfaces via data-[dragging]."
4919
- },
4920
- {
4921
- name: "SliderValue",
4922
- type: "Base UI Value props",
4923
- description: "Formatted value readout for custom compositions (e.g. a live number beside the control)."
4924
- }
4925
- ],
4926
- tokensUsed: ["secondary", "primary", "background", "ring"],
4927
- a11y: [
4928
- "Base UI provides role=slider, aria-valuemin/max/now, and arrow-key / Page Up-Down / Home-End control.",
4929
- "Each thumb is a focusable input[type=range] nested in the thumb element; keyboard focus raises a 2px ring on the thumb via has-[:focus-visible] (a bare focus-visible: on the thumb never fires, since the div itself is not focusable).",
4930
- "The thumb's hit area is a fixed 16px box at every state. Only the decorative inner dot resizes \u2014 rest 12px, full 16px on hover, keyboard focus, and drag \u2014 so the target a pointer or finger must land on never moves or changes size. Keyboard users, who never hover, get the enlarged dot from focus-visible.",
4931
- "The rest-to-full transition uses the motion duration/easing tokens, so it collapses to an instant state change under prefers-reduced-motion or MotionProvider off. The size change itself still happens: it reports interaction state, it is not decoration.",
4932
- "Range sliders get one thumb per value with minStepsBetweenValues honored.",
4933
- "Pass an aria-label (or getAriaLabel on a thumb) so each handle has an accessible name."
4934
- ],
4935
- examples: [
4936
- {
4937
- label: "Single value",
4938
- code: '<Slider defaultValue={50} aria-label="Volume" />'
4939
- },
4940
- {
4941
- label: "Range",
4942
- code: 'const [range, setRange] = useState([20, 60])\n<Slider value={range} onValueChange={setRange} aria-label="Price range" />'
4943
- },
4944
- {
4945
- label: "Stepped",
4946
- code: "<Slider defaultValue={40} min={0} max={100} step={10} />"
4947
- },
4948
- {
4949
- label: "Vertical",
4950
- code: '<div className="h-40"><Slider orientation="vertical" defaultValue={40} aria-label="Level" /></div>'
4951
- },
4952
- {
4953
- label: "Composed with a value readout",
4954
- code: '<Field label="Volume"><SliderRoot defaultValue={40} className="flex items-center gap-3"><SliderControl><SliderTrack><SliderIndicator /><SliderThumb /></SliderTrack></SliderControl><SliderValue className="text-mono-sm" /></SliderRoot></Field>'
4955
- }
4956
- ]
4957
- };
4958
- var useComboboxFilteredItems = Combobox$1.useFilteredItems;
4959
- var ComboboxVirtualContext = createContext(null);
4960
- function Combobox({
4961
- onItemHighlighted,
4962
- ...props
4963
- }) {
4964
- const revealRef = useRef(null);
4965
- const highlightRef = useRef(onItemHighlighted);
4966
- highlightRef.current = onItemHighlighted;
4967
- const handleHighlight = useCallback(
4968
- (value, details) => {
4969
- highlightRef.current?.(
4970
- value,
4971
- details
4972
- );
4973
- const reveal = revealRef.current;
4974
- if (reveal && details.index >= 0) {
4975
- queueMicrotask(() => reveal(details.index));
4976
- }
4977
- },
4978
- []
4979
- );
4980
- const bridge = useMemo(
4981
- () => ({
4982
- register(reveal) {
4983
- revealRef.current = reveal;
4984
- },
4985
- value: props.value,
4986
- isItemEqualToValue: props.isItemEqualToValue
4987
- }),
4988
- [props.value, props.isItemEqualToValue]
4989
- );
4990
- return /* @__PURE__ */ jsx(ComboboxVirtualContext.Provider, { value: bridge, children: /* @__PURE__ */ jsx(
4991
- Combobox$1.Root,
4992
- {
4993
- ...props,
4994
- onItemHighlighted: handleHighlight
4995
- }
4996
- ) });
4091
+ ) });
4997
4092
  }
4998
4093
  var ComboboxValue = Combobox$1.Value;
4999
4094
  var ComboboxClear = Combobox$1.Clear;
@@ -5755,7 +4850,7 @@ var contextMenuMeta = {
5755
4850
  related: {
5756
4851
  components: [
5757
4852
  "Menu",
5758
- "DotsMenu"
4853
+ "OverflowMenu"
5759
4854
  ]
5760
4855
  },
5761
4856
  props: [
@@ -6244,199 +5339,62 @@ var paginationMeta = {
6244
5339
  }
6245
5340
  ]
6246
5341
  };
6247
- function detectApple() {
6248
- if (typeof navigator === "undefined") return false;
6249
- const nav = navigator;
6250
- const platform = nav.userAgentData?.platform || nav.platform || nav.userAgent || "";
6251
- return /mac|iphone|ipad|ipod/i.test(platform);
6252
- }
6253
- var noopSubscribe = () => () => {
5342
+
5343
+ // src/lib/status-tone.ts
5344
+ var STATUS_TONES = ["neutral", "info", "success", "warning", "destructive"];
5345
+ var STATUS_TONE_ICON = {
5346
+ neutral: { name: "info", className: "text-muted-foreground" },
5347
+ info: { name: "info", className: "text-tone-info" },
5348
+ success: { name: "circle-check", className: "text-success" },
5349
+ warning: { name: "warning", className: "text-tone-warning" },
5350
+ destructive: { name: "warning", className: "text-destructive" }
6254
5351
  };
6255
- function useIsApple() {
6256
- return useSyncExternalStore(noopSubscribe, detectApple, () => false);
6257
- }
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"
5352
+ var noteVariants = cva(
5353
+ "flex gap-3 rounded-md border p-4 text-body-sm text-foreground",
5354
+ {
5355
+ variants: {
5356
+ tone: {
5357
+ info: "border-tone-info/30 bg-tone-info/10",
5358
+ warning: "border-tone-warning/30 bg-tone-warning/10",
5359
+ success: "border-success/30 bg-success/10",
5360
+ destructive: "border-destructive/30 bg-destructive/10",
5361
+ neutral: "border-border bg-muted/40"
5362
+ }
5363
+ },
5364
+ defaultVariants: {
5365
+ tone: "info"
5366
+ }
5367
+ }
5368
+ );
5369
+ var toneAction = {
5370
+ info: "text-tone-info",
5371
+ warning: "text-tone-warning",
5372
+ success: "text-success",
5373
+ destructive: "text-destructive",
5374
+ neutral: "text-foreground"
6261
5375
  };
6262
- function Kbd({ mod = false, variant = "key", className, children, ...props }) {
6263
- const apple = useIsApple();
6264
- const content = mod ? apple ? "\u2318" : "Ctrl" : children;
6265
- return /* @__PURE__ */ jsx(
6266
- "kbd",
5376
+ function Note({
5377
+ tone = "info",
5378
+ title,
5379
+ icon,
5380
+ action,
5381
+ className,
5382
+ children,
5383
+ ...props
5384
+ }) {
5385
+ const resolved = tone ?? "info";
5386
+ const { name, className: iconClassName } = STATUS_TONE_ICON[resolved];
5387
+ const glyph = icon === void 0 ? /* @__PURE__ */ jsx(Icon, { name, className: cn("text-base", iconClassName) }) : icon === null ? null : typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, className: cn("text-base", iconClassName) }) : icon;
5388
+ const leadingIcon = glyph ? /* @__PURE__ */ jsx(
5389
+ "span",
6267
5390
  {
6268
- "data-slot": "kbd",
6269
- className: cn(
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],
6272
- className
6273
- ),
6274
- ...props,
6275
- children: content
5391
+ "data-slot": "note-icon",
5392
+ className: cn("flex h-[1lh] shrink-0 items-center", title ? "text-title-sm" : "text-body-sm"),
5393
+ children: glyph
6276
5394
  }
6277
- );
6278
- }
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";
6291
-
6292
- // src/components/kbd/kbd.meta.ts
6293
- var kbdMeta = {
6294
- name: "Kbd",
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.',
6296
- status: "stable",
6297
- keywords: [
6298
- "keyboard",
6299
- "shortcut",
6300
- "key",
6301
- "kbd"
6302
- ],
6303
- usage: {
6304
- description: "Use Kbd to display keyboard keys or shortcuts in help text, menus, and docs.",
6305
- bestPractices: [
6306
- {
6307
- guidance: "do",
6308
- description: "Use it only for literal keyboard input such as Cmd, Enter, or Esc."
6309
- },
6310
- {
6311
- guidance: "do",
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.'
6317
- },
6318
- {
6319
- guidance: "dont",
6320
- description: "Do not use Kbd as a decorative badge or button replacement."
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
- },
6326
- {
6327
- guidance: "dont",
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."
6329
- }
6330
- ]
6331
- },
6332
- related: {
6333
- components: [
6334
- "Code",
6335
- "Menu",
6336
- "CommandMenu"
6337
- ]
6338
- },
6339
- props: [
6340
- {
6341
- name: "mod",
6342
- type: "boolean",
6343
- default: "false",
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."
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
- },
6352
- {
6353
- name: "className",
6354
- type: "string",
6355
- description: "Merge additional classes onto the key cap."
6356
- },
6357
- {
6358
- name: "...props",
6359
- type: 'ComponentPropsWithRef<"kbd">',
6360
- description: "All native <kbd> attributes (children, ref, etc.)."
6361
- }
6362
- ],
6363
- tokensUsed: ["border", "border-strong", "background", "muted-foreground"],
6364
- a11y: [
6365
- "Uses the semantic <kbd> element so assistive tech announces keyboard input.",
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."
6368
- ],
6369
- examples: [
6370
- {
6371
- label: "Single keys",
6372
- code: "<Kbd>Esc</Kbd>"
6373
- },
6374
- {
6375
- label: "Combo",
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>'
6381
- }
6382
- ]
6383
- };
6384
-
6385
- // src/lib/status-tone.ts
6386
- var STATUS_TONES = ["neutral", "info", "success", "warning", "destructive"];
6387
- var STATUS_TONE_ICON = {
6388
- neutral: { name: "info", className: "text-muted-foreground" },
6389
- info: { name: "info", className: "text-tone-info" },
6390
- success: { name: "circle-check", className: "text-success" },
6391
- warning: { name: "warning", className: "text-tone-warning" },
6392
- destructive: { name: "warning", className: "text-destructive" }
6393
- };
6394
- var noteVariants = cva(
6395
- "flex gap-3 rounded-md border p-4 text-body-sm text-foreground",
6396
- {
6397
- variants: {
6398
- tone: {
6399
- info: "border-tone-info/30 bg-tone-info/10",
6400
- warning: "border-tone-warning/30 bg-tone-warning/10",
6401
- success: "border-success/30 bg-success/10",
6402
- destructive: "border-destructive/30 bg-destructive/10",
6403
- neutral: "border-border bg-muted/40"
6404
- }
6405
- },
6406
- defaultVariants: {
6407
- tone: "info"
6408
- }
6409
- }
6410
- );
6411
- var toneAction = {
6412
- info: "text-tone-info",
6413
- warning: "text-tone-warning",
6414
- success: "text-success",
6415
- destructive: "text-destructive",
6416
- neutral: "text-foreground"
6417
- };
6418
- function Note({
6419
- tone = "info",
6420
- title,
6421
- icon,
6422
- action,
6423
- className,
6424
- children,
6425
- ...props
6426
- }) {
6427
- const resolved = tone ?? "info";
6428
- const { name, className: iconClassName } = STATUS_TONE_ICON[resolved];
6429
- const glyph = icon === void 0 ? /* @__PURE__ */ jsx(Icon, { name, className: cn("text-base", iconClassName) }) : icon === null ? null : typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, className: cn("text-base", iconClassName) }) : icon;
6430
- const leadingIcon = glyph ? /* @__PURE__ */ jsx(
6431
- "span",
6432
- {
6433
- "data-slot": "note-icon",
6434
- className: cn("flex h-[1lh] shrink-0 items-center", title ? "text-title-sm" : "text-body-sm"),
6435
- children: glyph
6436
- }
6437
- ) : null;
6438
- return /* @__PURE__ */ jsxs(
6439
- "div",
5395
+ ) : null;
5396
+ return /* @__PURE__ */ jsxs(
5397
+ "div",
6440
5398
  {
6441
5399
  role: "note",
6442
5400
  className: cn(noteVariants({ tone: resolved }), className),
@@ -8470,7 +7428,7 @@ function TreeRow({
8470
7428
  }
8471
7429
  );
8472
7430
  if (subtree === void 0) return row;
8473
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
7431
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
8474
7432
  row,
8475
7433
  expanded ? /* @__PURE__ */ jsx(TreeDepthContext.Provider, { value: depth + 1, children: subtree }) : null
8476
7434
  ] });
@@ -9688,13 +8646,13 @@ function RadioCard({
9688
8646
  className
9689
8647
  ),
9690
8648
  ...props,
9691
- children: icon != null ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
8649
+ children: icon != null ? /* @__PURE__ */ jsxs(Fragment, { children: [
9692
8650
  /* @__PURE__ */ jsxs("span", { className: SELECTION_CARD_TILE_HEAD, children: [
9693
8651
  /* @__PURE__ */ jsx("span", { className: cn(SELECTION_CARD_GLYPH, t.glyph), children: icon }),
9694
8652
  marker
9695
8653
  ] }),
9696
8654
  content
9697
- ] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
8655
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
9698
8656
  /* @__PURE__ */ jsx("span", { className: cn("flex h-[1lh] shrink-0 items-center text-title-sm", titleClassName), children: marker }),
9699
8657
  content
9700
8658
  ] })
@@ -9927,7 +8885,7 @@ function CheckboxCard({
9927
8885
  const t = checkboxCardToneClasses[tone];
9928
8886
  const box = /* @__PURE__ */ jsx("span", { className: cn(CHECK_BOX, t.box), children: /* @__PURE__ */ jsx(CheckboxMark, { className: t.check }) });
9929
8887
  return /* @__PURE__ */ jsxs(
9930
- Checkbox$1.Root,
8888
+ Checkbox.Root,
9931
8889
  {
9932
8890
  className: cn(
9933
8891
  SELECTION_CARD_SHELL,
@@ -10761,965 +9719,6 @@ var showMoreMeta = {
10761
9719
  }
10762
9720
  ]
10763
9721
  };
10764
- var LINE_HEIGHT = 16;
10765
- var PAGE_HEIGHT = 400;
10766
- function toPixels(delta, mode) {
10767
- if (mode === 1) return delta * LINE_HEIGHT;
10768
- if (mode === 2) return delta * PAGE_HEIGHT;
10769
- return delta;
10770
- }
10771
- function useWheelToHorizontal(enabled = true) {
10772
- const nodeRef = useRef(null);
10773
- const onWheel = useCallback((event) => {
10774
- const el = nodeRef.current;
10775
- if (!el) return;
10776
- if (event.shiftKey) return;
10777
- if (Math.abs(event.deltaX) > Math.abs(event.deltaY)) return;
10778
- if (event.deltaY === 0) return;
10779
- const delta = toPixels(event.deltaY, event.deltaMode);
10780
- const max = el.scrollWidth - el.clientWidth;
10781
- if (max <= 0) return;
10782
- const atStart = Math.round(el.scrollLeft) <= 0;
10783
- const atEnd = Math.round(el.scrollLeft) >= max;
10784
- if (delta < 0 && atStart || delta > 0 && atEnd) return;
10785
- event.preventDefault();
10786
- el.scrollLeft += delta;
10787
- }, []);
10788
- return useCallback(
10789
- (node) => {
10790
- const previous = nodeRef.current;
10791
- if (previous === node) return;
10792
- previous?.removeEventListener("wheel", onWheel);
10793
- nodeRef.current = node;
10794
- if (node && enabled) {
10795
- node.addEventListener("wheel", onWheel, { passive: false });
10796
- }
10797
- },
10798
- [enabled, onWheel]
10799
- );
10800
- }
10801
- function scrollRegionKeyDown(event, orientation) {
10802
- const el = event.currentTarget;
10803
- const horizontal = orientation === "horizontal";
10804
- const page = horizontal ? el.clientWidth : el.clientHeight;
10805
- const step = Math.max(40, page * 0.2);
10806
- const back = horizontal ? "ArrowLeft" : "ArrowUp";
10807
- const forward = horizontal ? "ArrowRight" : "ArrowDown";
10808
- const axis = horizontal ? "scrollLeft" : "scrollTop";
10809
- const max = horizontal ? el.scrollWidth - el.clientWidth : el.scrollHeight - el.clientHeight;
10810
- let next = null;
10811
- if (event.key === back) next = el[axis] - step;
10812
- else if (event.key === forward) next = el[axis] + step;
10813
- else if (event.key === "Home") next = 0;
10814
- else if (event.key === "End") next = max;
10815
- else if (event.key === "PageUp") next = el[axis] - page;
10816
- else if (event.key === "PageDown") next = el[axis] + page;
10817
- if (next === null) return;
10818
- event.preventDefault();
10819
- el[axis] = Math.max(0, Math.min(max, next));
10820
- }
10821
- function Scroller({
10822
- orientation = "vertical",
10823
- wheelScroll = true,
10824
- label,
10825
- className,
10826
- viewportClassName,
10827
- children,
10828
- ref,
10829
- style,
10830
- onScroll,
10831
- onKeyDown,
10832
- ...props
10833
- }) {
10834
- const regionRef = useRef(null);
10835
- const isHorizontal = orientation === "horizontal";
10836
- const trackActivity = useScrollActivity();
10837
- const trackWheel = useWheelToHorizontal(isHorizontal && wheelScroll);
10838
- const setRegion = useCallback(
10839
- (node) => {
10840
- setRef(ref, node);
10841
- trackActivity(node);
10842
- trackWheel(node);
10843
- regionRef.current = node;
10844
- },
10845
- [ref, trackActivity, trackWheel]
10846
- );
10847
- const handleKeyDown = useCallback(
10848
- (event) => {
10849
- onKeyDown?.(event);
10850
- if (event.defaultPrevented) return;
10851
- scrollRegionKeyDown(event, orientation);
10852
- },
10853
- [onKeyDown, orientation]
10854
- );
10855
- const [showStart, setShowStart] = useState(false);
10856
- const [showEnd, setShowEnd] = useState(false);
10857
- const sync = useCallback(
10858
- (el) => {
10859
- if (orientation === "horizontal") {
10860
- const { scrollLeft, scrollWidth, clientWidth } = el;
10861
- setShowStart(scrollLeft > 0);
10862
- setShowEnd(scrollLeft < scrollWidth - clientWidth - 1);
10863
- } else {
10864
- const { scrollTop, scrollHeight, clientHeight } = el;
10865
- setShowStart(scrollTop > 0);
10866
- setShowEnd(scrollTop < scrollHeight - clientHeight - 1);
10867
- }
10868
- },
10869
- [orientation]
10870
- );
10871
- useLayoutEffect(() => {
10872
- if (regionRef.current) sync(regionRef.current);
10873
- }, [sync]);
10874
- const handleScroll = useCallback(
10875
- (event) => {
10876
- sync(event.currentTarget);
10877
- onScroll?.(event);
10878
- },
10879
- [sync, onScroll]
10880
- );
10881
- const isVertical = orientation === "vertical";
10882
- const fade = "calc(var(--spacing) * 7)";
10883
- const mask = showStart || showEnd ? `linear-gradient(to ${isVertical ? "bottom" : "right"}, ${showStart ? "transparent" : "#000"}, #000 ${showStart ? fade : "0"}, #000 ${showEnd ? `calc(100% - ${fade})` : "100%"}, ${showEnd ? "transparent" : "#000"})` : void 0;
10884
- return (
10885
- // The chrome box. Carries the consumer's border/background/radius and the
10886
- // sizing, and is NOT masked — which is the entire point of the split. A
10887
- // flex column so the scroller below can be `flex-1 min-h-0`, which bounds
10888
- // off `h-*`, `max-h-*` and `flex-1` alike with no percentage to resolve.
10889
- /* @__PURE__ */ jsx("div", { style, className: cn("flex flex-col", className), children: /* @__PURE__ */ jsx(
10890
- "div",
10891
- {
10892
- ref: setRegion,
10893
- onScroll: handleScroll,
10894
- onKeyDown: handleKeyDown,
10895
- tabIndex: 0,
10896
- role: "group",
10897
- "aria-label": label,
10898
- "data-orientation": orientation,
10899
- style: mask ? { maskImage: mask, WebkitMaskImage: mask } : void 0,
10900
- className: cn(
10901
- // Fill the chrome box without a percentage height. `min-h-0` /
10902
- // `min-w-0` because a flex item's default `min-size: auto` refuses to
10903
- // shrink below its content — which is exactly how a scroll region
10904
- // silently stops scrolling.
10905
- "min-h-0 min-w-0 flex-1",
10906
- // Inherit the wrapper's radius so content clips to the same curve the
10907
- // border draws; a scroll container already clips to its own padding
10908
- // box, so this needs no `overflow-hidden` fighting the scroll axis.
10909
- "rounded-[inherit]",
10910
- "motion-interactive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
10911
- // Contained overscroll: a Scroller is almost always a bounded region
10912
- // inside a page or a popup, and a wheel gesture that reaches its end
10913
- // should stop rather than start scrolling whatever is behind it.
10914
- "overscroll-contain",
10915
- isVertical ? (
10916
- // Vertical: a thin bar, revealed while scrolling. The fade says
10917
- // "more below"; the bar says how much and where you are — a long
10918
- // list needs both.
10919
- "scrollbar-quiet overflow-y-auto overflow-x-hidden"
10920
- ) : (
10921
- // Horizontal: no bar at all. The edge fades this component already
10922
- // draws ARE the affordance, and they say the same thing better —
10923
- // which is the carousel read a media strip or chip row wants. A
10924
- // 4px rail under a row of tiles adds chrome and no information.
10925
- "scrollbar-hidden overflow-x-auto overflow-y-hidden"
10926
- ),
10927
- viewportClassName
10928
- ),
10929
- ...props,
10930
- children
10931
- }
10932
- ) })
10933
- );
10934
- }
10935
- Scroller.displayName = "Scroller";
10936
- function defaultFilter(items, query) {
10937
- const q = query.trim().toLowerCase();
10938
- if (!q) return items;
10939
- return items.filter((item) => item.label.toLowerCase().includes(q));
10940
- }
10941
- function useCommandQuery(queryProp, onQueryChange) {
10942
- const [ownQuery, setOwnQuery] = useState("");
10943
- const query = queryProp ?? ownQuery;
10944
- const setQuery = (next) => {
10945
- if (queryProp === void 0) setOwnQuery(next);
10946
- onQueryChange?.(next);
10947
- };
10948
- return [query, setQuery];
10949
- }
10950
- var COMMAND_GEOMETRY = "[--cm-inset:max(--spacing(1),calc(var(--radius-lg)/3))] [--cm-pad:max(--spacing(2),calc(--spacing(4)-var(--cm-inset)))]";
10951
- function CommandMenuBody({
10952
- query,
10953
- setQuery,
10954
- items,
10955
- filter,
10956
- listClassName,
10957
- placeholder,
10958
- emptyMessage,
10959
- title,
10960
- scope,
10961
- inputProps,
10962
- children,
10963
- close,
10964
- open,
10965
- inline
10966
- }) {
10967
- const [activeId, setActiveId] = useState(null);
10968
- const listRef = useRef(null);
10969
- const listboxId = useId();
10970
- const tier = useTypeTier();
10971
- const grouped = useMemo(() => {
10972
- if (!items) return [];
10973
- const visible = filter === null ? items : filter ? filter(items, query) : defaultFilter(items, query);
10974
- const map = /* @__PURE__ */ new Map();
10975
- for (const item of visible) {
10976
- const bucket = map.get(item.group);
10977
- if (bucket) bucket.push(item);
10978
- else map.set(item.group, [item]);
10979
- }
10980
- return Array.from(map, ([group, groupItems]) => ({ group, groupItems }));
10981
- }, [items, query, filter]);
10982
- const q = filter === void 0 ? query.trim().toLowerCase() : "";
10983
- const options = () => Array.from(
10984
- listRef.current?.querySelectorAll('[role="option"]') ?? []
10985
- );
10986
- const seedActive = useCallback(() => {
10987
- const all = options();
10988
- const runnable = all.find(
10989
- (option) => option.getAttribute("aria-disabled") !== "true"
10990
- );
10991
- setActiveId((runnable ?? all[0])?.id ?? null);
10992
- }, []);
10993
- const [resultCount, setResultCount] = useState(0);
10994
- useEffect(() => {
10995
- const all = options();
10996
- const next = all.length;
10997
- setResultCount((current) => current === next ? current : next);
10998
- if (inline && (activeId ? !all.some((option) => option.id === activeId) : next > 0)) {
10999
- seedActive();
11000
- }
11001
- });
11002
- const attachList = useCallback(
11003
- (node) => {
11004
- listRef.current = node;
11005
- if (node) seedActive();
11006
- },
11007
- [seedActive]
11008
- );
11009
- useEffect(() => {
11010
- if (open) seedActive();
11011
- }, [open, query]);
11012
- const onInputKeyDown = (event) => {
11013
- inputProps?.onKeyDown?.(event);
11014
- if (event.defaultPrevented) return;
11015
- if (inline && event.key === "Escape") {
11016
- if (query) {
11017
- event.preventDefault();
11018
- event.stopPropagation();
11019
- setQuery("");
11020
- }
11021
- return;
11022
- }
11023
- const opts = options();
11024
- if (opts.length === 0) return;
11025
- const index = opts.findIndex((o) => o.id === activeId);
11026
- const move = (next) => {
11027
- if (!next) return;
11028
- event.preventDefault();
11029
- setActiveId(next.id);
11030
- next.scrollIntoView({ block: "nearest" });
11031
- };
11032
- switch (event.key) {
11033
- case "ArrowDown":
11034
- move(index < 0 ? opts[0] : opts[(index + 1) % opts.length]);
11035
- break;
11036
- case "ArrowUp":
11037
- move(
11038
- index < 0 ? opts[opts.length - 1] : opts[(index - 1 + opts.length) % opts.length]
11039
- );
11040
- break;
11041
- case "Home":
11042
- move(opts[0]);
11043
- break;
11044
- case "End":
11045
- move(opts[opts.length - 1]);
11046
- break;
11047
- // A screenful, near enough. Rows are a fixed height, so "ten" is the
11048
- // same distance every time — measuring the listbox would be more exact
11049
- // and less predictable. Clamped rather than wrapped: a page jump that
11050
- // wraps to the far end is a jump nobody asked for.
11051
- case "PageDown":
11052
- move(opts[Math.min(opts.length - 1, (index < 0 ? 0 : index) + 10)]);
11053
- break;
11054
- case "PageUp":
11055
- move(opts[Math.max(0, (index < 0 ? 0 : index) - 10)]);
11056
- break;
11057
- case "Enter": {
11058
- const active = opts.find((o) => o.id === activeId) ?? opts[0];
11059
- if (active) {
11060
- event.preventDefault();
11061
- if (active.getAttribute("aria-disabled") !== "true") active.click();
11062
- }
11063
- break;
11064
- }
11065
- }
11066
- };
11067
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
11068
- /* @__PURE__ */ jsxs("div", { className: cn("flex shrink-0 items-center gap-2 border-b border-border px-[calc(var(--cm-inset)+var(--cm-pad))] py-3", COMMAND_GEOMETRY), children: [
11069
- /* @__PURE__ */ jsx(Icon, { name: "search", className: "text-base text-muted-foreground" }),
11070
- scope ? /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center", children: scope }) : null,
11071
- /* @__PURE__ */ jsx(
11072
- "input",
11073
- {
11074
- autoFocus: !inline,
11075
- type: "text",
11076
- ...inputProps,
11077
- "data-command-menu-input": inline ? void 0 : "",
11078
- role: "combobox",
11079
- "aria-expanded": true,
11080
- "aria-controls": listboxId,
11081
- "aria-activedescendant": activeId ?? void 0,
11082
- "aria-autocomplete": "list",
11083
- value: query,
11084
- onChange: (event) => setQuery(event.target.value),
11085
- onKeyDown: onInputKeyDown,
11086
- placeholder,
11087
- "aria-label": inputProps?.["aria-label"] ?? "Command search",
11088
- className: cn(
11089
- `w-full border-0 bg-transparent ${tier.body} text-foreground outline-none placeholder:text-muted-foreground`,
11090
- inputProps?.className
11091
- )
11092
- }
11093
- )
11094
- ] }),
11095
- /* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", className: "sr-only", children: resultCount === 0 ? "No results" : `${resultCount} result${resultCount === 1 ? "" : "s"}` }),
11096
- /* @__PURE__ */ jsxs(
11097
- Scroller,
11098
- {
11099
- ref: attachList,
11100
- role: "listbox",
11101
- id: listboxId,
11102
- "aria-label": title,
11103
- className: cn("min-h-0 max-h-80 p-(--cm-inset)", COMMAND_GEOMETRY, listClassName),
11104
- children: [
11105
- /* @__PURE__ */ jsx(
11106
- CommandQueryContext.Provider,
11107
- {
11108
- value: { query: children ? q : "", close, activeId, setActiveId },
11109
- children: children ?? grouped.map(({ group, groupItems }) => /* @__PURE__ */ jsx(CommandMenuGroup, { label: group, children: groupItems.map((item) => /* @__PURE__ */ jsx(
11110
- CommandMenuItem,
11111
- {
11112
- id: item.id ? `${listboxId}-${item.id}` : void 0,
11113
- icon: item.icon,
11114
- detail: item.detail,
11115
- hint: item.hint,
11116
- disabled: item.disabled,
11117
- onSelect: () => {
11118
- item.onSelect();
11119
- close();
11120
- },
11121
- children: item.label
11122
- },
11123
- item.id ?? `${group}:${item.label}`
11124
- )) }, group))
11125
- }
11126
- ),
11127
- (children ? resultCount === 0 : grouped.length === 0) && /* @__PURE__ */ jsx("div", { className: "px-(--cm-pad) py-6 text-center text-body-sm text-muted-foreground", children: emptyMessage })
11128
- ]
11129
- }
11130
- )
11131
- ] });
11132
- }
11133
- function CommandMenu({
11134
- open,
11135
- onOpenChange,
11136
- items,
11137
- query: queryProp,
11138
- onQueryChange,
11139
- filter,
11140
- listClassName,
11141
- placeholder = "Type a command or search\u2026",
11142
- emptyMessage = "No results found.",
11143
- title = "Command menu",
11144
- description = "Search for a command and press Enter to run it.",
11145
- finalFocus,
11146
- scope,
11147
- inputProps,
11148
- children,
11149
- className,
11150
- ...props
11151
- }) {
11152
- const [query, setQuery] = useCommandQuery(queryProp, onQueryChange);
11153
- const anchoredDensity = useAnchoredDensity();
11154
- const handleOpenChange = (next) => {
11155
- if (!next) setQuery("");
11156
- onOpenChange(next);
11157
- };
11158
- const close = () => handleOpenChange(false);
11159
- return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxs(
11160
- DialogContent,
11161
- {
11162
- scroll: false,
11163
- ...anchoredDensity,
11164
- finalFocus,
11165
- className: cn(
11166
- // Top-anchored rather than centred, so the viewport cap DialogContent
11167
- // applies (which assumes a 1rem margin top and bottom) has to be
11168
- // restated for the 12vh offset: everything below the offset, less the
11169
- // bottom margin.
11170
- "top-[12vh] max-h-[calc(88dvh-1rem)] max-w-140 -translate-y-0 overflow-hidden p-0",
11171
- className
11172
- ),
11173
- ...props,
11174
- children: [
11175
- /* @__PURE__ */ jsx(DialogTitle, { className: "sr-only", children: title }),
11176
- /* @__PURE__ */ jsx(DialogDescription, { className: "sr-only", children: description }),
11177
- /* @__PURE__ */ jsx(
11178
- CommandMenuBody,
11179
- {
11180
- query,
11181
- setQuery,
11182
- items,
11183
- filter,
11184
- listClassName,
11185
- placeholder,
11186
- emptyMessage,
11187
- title,
11188
- scope,
11189
- inputProps,
11190
- close,
11191
- open,
11192
- inline: false,
11193
- children
11194
- }
11195
- )
11196
- ]
11197
- }
11198
- ) });
11199
- }
11200
- var noop = () => {
11201
- };
11202
- function CommandMenuPanel({
11203
- items,
11204
- query: queryProp,
11205
- onQueryChange,
11206
- filter,
11207
- listClassName,
11208
- placeholder = "Type a command or search\u2026",
11209
- emptyMessage = "No results found.",
11210
- title = "Command menu",
11211
- scope,
11212
- inputProps,
11213
- children,
11214
- className,
11215
- ...props
11216
- }) {
11217
- const [query, setQuery] = useCommandQuery(queryProp, onQueryChange);
11218
- return /* @__PURE__ */ jsx(
11219
- "div",
11220
- {
11221
- "data-slot": "command-menu-panel",
11222
- className: cn(
11223
- "flex min-h-0 flex-col overflow-hidden rounded-lg border border-border bg-card text-card-foreground",
11224
- className
11225
- ),
11226
- ...props,
11227
- children: /* @__PURE__ */ jsx(
11228
- CommandMenuBody,
11229
- {
11230
- query,
11231
- setQuery,
11232
- items,
11233
- filter,
11234
- listClassName,
11235
- placeholder,
11236
- emptyMessage,
11237
- title,
11238
- scope,
11239
- inputProps,
11240
- close: noop,
11241
- open: true,
11242
- inline: true,
11243
- children
11244
- }
11245
- )
11246
- }
11247
- );
11248
- }
11249
- var CommandQueryContext = createContext(
11250
- null
11251
- );
11252
- function CommandMenuGroup({
11253
- label,
11254
- className,
11255
- children,
11256
- ...props
11257
- }) {
11258
- const labelId = useId();
11259
- const tier = useTypeTier();
11260
- return /* @__PURE__ */ jsxs(
11261
- "div",
11262
- {
11263
- role: "group",
11264
- "aria-labelledby": labelId,
11265
- className: cn("py-1", className),
11266
- ...props,
11267
- children: [
11268
- /* @__PURE__ */ jsx(
11269
- "div",
11270
- {
11271
- id: labelId,
11272
- className: `px-(--cm-pad,--spacing(2)) py-1 ${tier.label} text-muted-foreground`,
11273
- children: label
11274
- }
11275
- ),
11276
- children
11277
- ]
11278
- }
11279
- );
11280
- }
11281
- function CommandMenuItem({
11282
- onSelect,
11283
- icon,
11284
- detail,
11285
- hint,
11286
- id: idProp,
11287
- className,
11288
- children,
11289
- disabled,
11290
- ...props
11291
- }) {
11292
- const ctx = useContext(CommandQueryContext);
11293
- const tier = useTypeTier();
11294
- const autoId = useId();
11295
- const id = idProp ?? autoId;
11296
- if (ctx?.query) {
11297
- const text = textOf(children).toLowerCase();
11298
- if (!text.includes(ctx.query)) return null;
11299
- }
11300
- const handleSelect = () => {
11301
- if (disabled) return;
11302
- onSelect?.();
11303
- ctx?.close();
11304
- };
11305
- return /* @__PURE__ */ jsxs(
11306
- "div",
11307
- {
11308
- id,
11309
- role: "option",
11310
- "aria-selected": ctx?.activeId === id,
11311
- "aria-disabled": disabled || void 0,
11312
- onClick: handleSelect,
11313
- onMouseMove: () => ctx?.setActiveId(id),
11314
- className: cn(
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`,
11316
- disabled ? "cursor-default text-muted-foreground" : "cursor-pointer text-foreground",
11317
- className
11318
- ),
11319
- ...props,
11320
- children: [
11321
- icon && /* @__PURE__ */ jsx(Icon, { name: icon, className: "text-base text-muted-foreground" }),
11322
- /* @__PURE__ */ jsx("span", { className: "truncate", children }),
11323
- detail && /* @__PURE__ */ jsx("span", { className: "truncate text-body-sm text-muted-foreground", children: detail }),
11324
- hint && /* @__PURE__ */ jsx("span", { className: "ms-auto flex min-w-0 max-w-1/2 items-center ps-4 text-body-sm text-muted-foreground", children: /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: hint }) })
11325
- ]
11326
- }
11327
- );
11328
- }
11329
- function textOf(node) {
11330
- if (node == null || typeof node === "boolean") return "";
11331
- if (typeof node === "string" || typeof node === "number") return String(node);
11332
- if (Array.isArray(node)) return node.map(textOf).join("");
11333
- if (typeof node === "object" && "props" in node) {
11334
- const props = node.props;
11335
- return textOf(props?.children);
11336
- }
11337
- return "";
11338
- }
11339
- var COMMAND_MENU_INPUT_ATTR = "data-command-menu-input";
11340
- var NON_TEXT_INPUTS = /* @__PURE__ */ new Set([
11341
- "button",
11342
- "checkbox",
11343
- "color",
11344
- "file",
11345
- "image",
11346
- "radio",
11347
- "range",
11348
- "reset",
11349
- "submit"
11350
- ]);
11351
- function isEditable(target) {
11352
- if (!(target instanceof Element)) return false;
11353
- if (target.closest(`[${COMMAND_MENU_INPUT_ATTR}]`)) return false;
11354
- if (target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement)
11355
- return true;
11356
- if (target instanceof HTMLInputElement)
11357
- return !NON_TEXT_INPUTS.has(target.type);
11358
- if (target instanceof HTMLElement && target.isContentEditable) return true;
11359
- return target.closest('[contenteditable]:not([contenteditable="false"])') !== null;
11360
- }
11361
- function detectApple2() {
11362
- if (typeof navigator === "undefined") return false;
11363
- const nav = navigator;
11364
- const platform = nav.userAgentData?.platform || nav.platform || nav.userAgent || "";
11365
- return /mac|iphone|ipad|ipod/i.test(platform);
11366
- }
11367
- var noopSubscribe2 = () => () => {
11368
- };
11369
- function useIsApple2() {
11370
- return useSyncExternalStore(noopSubscribe2, detectApple2, () => false);
11371
- }
11372
- function useCommandMenuShortcut(onShortcut, key = "k") {
11373
- const handler = useRef(onShortcut);
11374
- useIsomorphicLayoutEffect(() => {
11375
- handler.current = onShortcut;
11376
- });
11377
- useEffect(() => {
11378
- if (key === false || typeof window === "undefined") return;
11379
- const wanted = key.toLowerCase();
11380
- const apple = detectApple2();
11381
- function onKeyDown(event) {
11382
- if (event.defaultPrevented || event.isComposing) return;
11383
- if (typeof event.key !== "string" || event.key.toLowerCase() !== wanted) return;
11384
- const mod = apple ? event.metaKey : event.ctrlKey;
11385
- const other = apple ? event.ctrlKey : event.metaKey;
11386
- if (!mod || other || event.altKey || event.shiftKey) return;
11387
- if (isEditable(event.target)) return;
11388
- event.preventDefault();
11389
- handler.current(event);
11390
- }
11391
- window.addEventListener("keydown", onKeyDown);
11392
- return () => window.removeEventListener("keydown", onKeyDown);
11393
- }, [key]);
11394
- }
11395
- function CommandMenuTrigger({
11396
- onOpen,
11397
- onClick,
11398
- placeholder = "Search",
11399
- shortcut = "k",
11400
- size = "md",
11401
- className,
11402
- type,
11403
- ...props
11404
- }) {
11405
- const tier = useTypeTier();
11406
- const apple = useIsApple2();
11407
- useCommandMenuShortcut(() => onOpen?.(), props.disabled ? false : shortcut);
11408
- const keyLabel = shortcut === false ? null : shortcut.toUpperCase();
11409
- return /* @__PURE__ */ jsxs(
11410
- "button",
11411
- {
11412
- type: type ?? "button",
11413
- "aria-haspopup": "dialog",
11414
- "aria-keyshortcuts": keyLabel ? `${apple ? "Meta" : "Control"}+${keyLabel}` : void 0,
11415
- onClick: (event) => {
11416
- onClick?.(event);
11417
- if (!event.defaultPrevented) onOpen?.();
11418
- },
11419
- className: cn(
11420
- // FIELD_SHELL carries motion-interactive too; it is restated here so
11421
- // the hover it eases is visibly paired with its recipe.
11422
- "flex w-full items-center gap-2 text-start text-muted-foreground motion-interactive hover:text-foreground",
11423
- FIELD_SHELL,
11424
- size === "sm" ? `h-(--control-sm) ps-2.5 pe-1.5 ${tier.supporting}` : `h-(--control-md) ps-3 pe-2 ${tier.body}`,
11425
- className
11426
- ),
11427
- ...props,
11428
- children: [
11429
- /* @__PURE__ */ jsx(Icon, { name: "search", className: "shrink-0 text-sm" }),
11430
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: placeholder }),
11431
- keyLabel ? (
11432
- // aria-hidden: the shortcut is announced once, through
11433
- // aria-keyshortcuts, rather than read out as part of the name.
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
- ] })
11440
- ) : null
11441
- ]
11442
- }
11443
- );
11444
- }
11445
- CommandMenuTrigger.displayName = "CommandMenuTrigger";
11446
-
11447
- // src/components/command-menu/command-menu.meta.ts
11448
- var commandMenuMeta = {
11449
- name: "CommandMenu",
11450
- summary: "A command palette (Cmd+K) built on our Dialog: a top-aligned wide surface with a search input and a scrollable, JS-filtered list of grouped commands. CommandMenuPanel renders the same palette in place, on a card in the page.",
11451
- keywords: [
11452
- "command",
11453
- "command menu",
11454
- "palette",
11455
- "launcher",
11456
- "cmd k",
11457
- "search",
11458
- "quick action",
11459
- "spotlight",
11460
- "navigation",
11461
- "search trigger",
11462
- "header search",
11463
- "keyboard shortcut",
11464
- "inline command menu",
11465
- "embedded palette",
11466
- "settings search"
11467
- ],
11468
- usage: {
11469
- description: "Use CommandMenu for global or contextual keyboard-driven actions: navigation, creation, quick settings, or searchable commands. It should feel curated and fast; use Select, Menu, or Combobox for ordinary inline choices.",
11470
- bestPractices: [
11471
- {
11472
- guidance: "do",
11473
- description: "Open the palette from a header search with CommandMenuTrigger: a button wearing SearchInput's field shell, with the \u2318K / Ctrl K hint and the page-wide shortcut built in. A palette with no visible trigger still takes useCommandMenuShortcut rather than a hand-written keydown listener."
11474
- },
11475
- {
11476
- guidance: "dont",
11477
- description: "Hand-style a <button> to look like a search field, or put a SearchInput in the header that opens a dialog when focused. The first drifts from the field it imitates; the second is a text box you cannot type in. A header search that opens the palette is CommandMenuTrigger."
11478
- },
11479
- {
11480
- guidance: "do",
11481
- description: "Group items by task area, such as Navigation, Create, or Settings, so users can scan before typing."
11482
- },
11483
- {
11484
- guidance: "do",
11485
- description: "Keep command labels action-oriented and unique enough to search."
11486
- },
11487
- {
11488
- guidance: "do",
11489
- description: "Provide useful bootstrap items so the menu is valuable before the user types."
11490
- },
11491
- {
11492
- guidance: "dont",
11493
- description: "Use CommandMenu as a replacement for every dropdown. Simple choices belong in Select, Menu, or Tabs."
11494
- },
11495
- {
11496
- guidance: "do",
11497
- description: "Pass a filter when the palette searches more than one source. Ranking (exact prefix over substring over subsequence) is the caller's to own, and the returned order is kept verbatim \u2014 including group order, which follows each group's first surviving row."
11498
- },
11499
- {
11500
- guidance: "do",
11501
- description: "List a command the user cannot currently run as disabled with the reason in hint, rather than filtering it away. A palette that hides what you cannot do teaches you nothing about why."
11502
- },
11503
- {
11504
- guidance: "do",
11505
- description: "Resize the list with listClassName rather than reaching past className into the palette's DOM. className is the dialog surface; listClassName is the Scroller that actually scrolls."
11506
- },
11507
- {
11508
- guidance: "do",
11509
- description: "Use CommandMenuPanel when the palette lives in the page: a sidebar launcher, a settings search, a demo. It takes the same items, filter, query and children, and keeps the same keyboard model; it is a static card, so it does not trap focus or take it on mount, and Esc clears the query instead of closing anything."
11510
- },
11511
- {
11512
- guidance: "dont",
11513
- description: "Hand-build a palette mock from a search glyph, a row of buttons and a highlight. That is CommandMenuPanel, and the hand-built one has no combobox, no filtering and no keyboard model."
11514
- },
11515
- {
11516
- guidance: "dont",
11517
- description: "Dump every app route and action into one uncurated list. Keep the palette opinionated."
11518
- },
11519
- {
11520
- guidance: "dont",
11521
- description: "Rebuild the combobox over Dialog to get consumer-owned ranking. query/onQueryChange and filter are that seam; the keyboard model, activedescendant wiring, and result-count live region come with them."
11522
- }
11523
- ],
11524
- anatomy: [
11525
- {
11526
- name: "Trigger",
11527
- required: false,
11528
- description: "CommandMenuTrigger \u2014 the search-field-shaped button in a header that opens the palette on click or on \u2318K / Ctrl+K. It sits outside CommandMenu; wire its onOpen to the palette's open state."
11529
- },
11530
- {
11531
- name: "Dialog surface",
11532
- required: true,
11533
- description: "The modal shell that traps focus and positions the launcher. CommandMenuPanel replaces it with a static card in the page (border, card fill, no shadow)."
11534
- },
11535
- {
11536
- name: "Search input",
11537
- required: true,
11538
- description: "The combobox input that filters commands while focus remains in the field."
11539
- },
11540
- {
11541
- name: "Groups",
11542
- required: false,
11543
- description: "Labelled command sections that keep results scannable."
11544
- },
11545
- {
11546
- name: "Items",
11547
- required: true,
11548
- description: "Keyboard-selectable command rows that run onSelect and close the palette."
11549
- }
11550
- ]
11551
- },
11552
- related: {
11553
- components: ["Dialog", "SearchInput", "Kbd", "Menu", "Select", "Combobox"],
11554
- patterns: ["DashboardPage", "ListDetailPage"]
11555
- },
11556
- status: "stable",
11557
- props: [
11558
- {
11559
- name: "scope",
11560
- type: "ReactNode",
11561
- description: 'A scope chip in the header, between the search glyph and the input \u2014 "In: Backdrop". A scope belongs beside the field rather than in its value: put it in the value and the user deletes prose to widen their search, and your filter parses it back out. Pair with inputProps.onKeyDown handling Backspace on an empty query, or the only way out of a scope is the mouse.'
11562
- },
11563
- {
11564
- name: "inputProps",
11565
- type: "Omit<ComponentProps<'input'>, 'role' | 'value' | 'onChange' | aria-*>",
11566
- description: "Extra props for the search input. `onKeyDown` runs BEFORE the palette's own handling and opts out of a key by calling preventDefault() \u2014 which is what makes a scope chip Backspace-dismissable. Running first is deliberate: Backspace happens not to be a key the palette claims today, so a plain passthrough would work until the palette grows a 'Backspace clears the query' affordance, at which point the consumer's behaviour breaks silently. The combobox wiring (role, aria relationships, controlled value) is not overridable; aria-label and className compose."
11567
- },
11568
- {
11569
- name: "open",
11570
- type: "boolean",
11571
- description: "Controlled open state of the palette."
11572
- },
11573
- {
11574
- name: "onOpenChange",
11575
- type: "(open: boolean) => void",
11576
- description: "Called on open/close (select, Esc, outside click)."
11577
- },
11578
- {
11579
- name: "items",
11580
- type: "CommandItem[]",
11581
- description: "Data-driven commands ({ group, label, onSelect, id?, icon?, detail?, hint?, disabled? }). Ignored when children are provided. id is optional identity: filter is handed these same objects, so it is how a caller that ranks over its own richer model maps the array back onto it \u2014 and it becomes the row's DOM id (namespaced to the palette) and React key, so two rows may share a label."
11582
- },
11583
- {
11584
- name: "query",
11585
- type: "string",
11586
- description: "Controlled query. Leave undefined to let the palette own it; either way it resets on close (a controlled caller gets onQueryChange(''))."
11587
- },
11588
- {
11589
- name: "onQueryChange",
11590
- type: "(query: string) => void",
11591
- description: "Called on every query change. Required to control query, optional otherwise."
11592
- },
11593
- {
11594
- name: "filter",
11595
- type: "((items: CommandItem[], query: string) => CommandItem[]) | null",
11596
- default: "case-insensitive substring match on label",
11597
- description: "Replaces or disables the built-in match. A function ranks and filters items itself and its order is kept verbatim; null renders items exactly as given. Either also stops declarative CommandMenuItem children self-filtering. The query arrives raw \u2014 trimming and case are policy, and this is the prop that hands policy over."
11598
- },
11599
- {
11600
- name: "listClassName",
11601
- type: "string",
11602
- description: `Classes for the scrolling list. className lands on the dialog surface, so this is the seam for the list's own height cap \u2014 listClassName="max-h-[60vh]". The list is a Scroller, so the cap lands on the element that scrolls and the edge fades come free.`
11603
- },
11604
- {
11605
- name: "placeholder",
11606
- type: "string",
11607
- default: '"Type a command or search\u2026"',
11608
- description: "Placeholder for the search input."
11609
- },
11610
- {
11611
- name: "emptyMessage",
11612
- type: "string",
11613
- default: '"No results found."',
11614
- description: "Copy shown when the query matches nothing."
11615
- },
11616
- {
11617
- name: "title",
11618
- type: "string",
11619
- default: '"Command menu"',
11620
- description: "Accessible name for the palette dialog (visually hidden)."
11621
- },
11622
- {
11623
- name: "description",
11624
- type: "string",
11625
- default: '"Search for a command and press Enter to run it."',
11626
- description: "Accessible description for the palette dialog (visually hidden)."
11627
- },
11628
- {
11629
- name: "finalFocus",
11630
- type: "RefObject<HTMLElement> | ((closeType) => HTMLElement | false) | false",
11631
- description: "Where focus goes when the palette closes. The Dialog default returns it to the trigger \u2014 the CommandMenuTrigger, when there is one. A palette opened by the keystroke alone has none, so pass a resolver to restore whatever had focus, or false to restore nothing."
11632
- },
11633
- {
11634
- name: "children",
11635
- type: "ReactNode",
11636
- description: "Declarative commands via CommandMenuGroup / CommandMenuItem. Takes precedence over items."
11637
- }
11638
- ],
11639
- subcomponents: [
11640
- {
11641
- name: "CommandMenuTrigger",
11642
- type: '{ onOpen?: () => void; placeholder?: string; shortcut?: string | false; size?: "sm" | "md"; disabled?: boolean }',
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.`
11644
- },
11645
- {
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.`
11649
- },
11650
- {
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."
11654
- },
11655
- {
11656
- name: "CommandMenuGroup",
11657
- type: "{ label: string; children }",
11658
- description: "A labelled group of command rows."
11659
- },
11660
- {
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."
11664
- }
11665
- ],
11666
- tokensUsed: [
11667
- "card",
11668
- "card-foreground",
11669
- "border",
11670
- "foreground",
11671
- "muted-foreground",
11672
- "accent",
11673
- "accent-foreground"
11674
- ],
11675
- a11y: [
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."
11687
- ],
11688
- examples: [
11689
- {
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} />'
11716
- },
11717
- {
11718
- label: "Keyboard shortcut with no visible trigger",
11719
- code: "useCommandMenuShortcut(() => setOpen((value) => !value))\n<CommandMenu open={open} onOpenChange={setOpen} items={items} />"
11720
- }
11721
- ]
11722
- };
11723
9722
  var ContextCard = PreviewCard.Root;
11724
9723
  var ContextCardTrigger = PreviewCard.Trigger;
11725
9724
  var ContextCardPortal = withPortalThemeScope(PreviewCard.Portal);
@@ -12073,7 +10072,7 @@ function MiddleTruncate({
12073
10072
  title: fits ? void 0 : text,
12074
10073
  className: cn("text-mono-sm tabular-nums", className),
12075
10074
  ...props,
12076
- children: fits ? text : /* @__PURE__ */ jsxs(Fragment$1, { children: [
10075
+ children: fits ? text : /* @__PURE__ */ jsxs(Fragment, { children: [
12077
10076
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: `${text.slice(0, startChars)}\u2026${text.slice(-endChars)}` }),
12078
10077
  /* @__PURE__ */ jsx("span", { className: "sr-only", children: text })
12079
10078
  ] })
@@ -13074,7 +11073,7 @@ function Carousel({
13074
11073
  )
13075
11074
  }
13076
11075
  ),
13077
- showArrows && /* @__PURE__ */ jsxs(Fragment$1, { children: [
11076
+ showArrows && /* @__PURE__ */ jsxs(Fragment, { children: [
13078
11077
  /* @__PURE__ */ jsx(
13079
11078
  CarouselArrowButton,
13080
11079
  {
@@ -13261,6 +11260,6 @@ var carouselMeta = {
13261
11260
  };
13262
11261
 
13263
11262
  // src/index.ts
13264
- var SABLE_UI_VERSION = "0.2.0";
11263
+ var SABLE_UI_VERSION = "0.3.1";
13265
11264
 
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 };
11265
+ export { Banner, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, Breadcrumbs, COMPONENT_GROUPS, COMPONENT_TAXONOMY, Calendar, Carousel, CheckboxCard, CheckboxGroup, Collapsible, CollapsibleIndicator, CollapsiblePanel, CollapsibleTrigger, ColorField, Combobox, ComboboxChips, ComboboxClear, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, ComboboxItemIndicator, ComboboxList, ComboboxPortal, ComboboxValue, ComboboxVirtualList, ContextCard, ContextCardArrow, ContextCardContent, ContextCardPortal, ContextCardTrigger, ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuGroupLabel, ContextMenuItem, ContextMenuPortal, ContextMenuSeparator, ContextMenuTrigger, DatePicker, Feedback, Fieldset, FieldsetContent, FieldsetDescription, FieldsetFooter, FieldsetLegend, FileDropzone, GlidingHighlight, InlineStatus, LineChart, Marquee, MiddleTruncate, Note, NumberField, PageLoader, Pagination, Panel, PanelGroup, PanelResizer, PanelSection, Popover, PopoverArrow, PopoverClose, PopoverContent, PopoverDescription, PopoverPortal, PopoverTitle, PopoverTrigger, PropertyPair, PropertyRow, RadarChart, Radio, RadioCard, RadioGroup, SABLE_UI_VERSION, STATUS_TONES, STATUS_TONE_ICON, ScrubField, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetPortal, SheetTitle, SheetTrigger, ShowMore, StatusBar, StatusBarGroup, StatusBarSeparator, TRANSPARENT, ThemeSelector, ThemeSwitcher, ThinkingOrb, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastRoot, ToastTitle, ToastViewport, Tree, TreeAddRow, TreeEmpty, TreeRow, TreeSection, Video, Workspace, WorkspaceBar, WorkspaceCanvas, bannerMeta, bannerVariants, breadcrumbsMeta, calendarMeta, carouselMeta, checkboxCardMeta, collapsibleMeta, colorFieldMeta, comboboxMeta, componentGuidance, componentLabel, componentSlug, componentsInGroup, contextCardMeta, contextMenuMeta, datePickerMeta, feedbackMeta, fieldsetMeta, fileDropzoneMeta, fileDropzoneVariants, formatFileSize, formatHex, glidingHighlightMeta, hsvToRgb, hueColor, inlineStatusMeta, isTransparent, lineChartMeta, marqueeMeta, middleTruncateMeta, noteMeta, noteVariants, numberFieldMeta, pageLoaderMeta, paginationMeta, panelGroupMeta, panelSectionMeta, parseHex, pillMeta, popoverMeta, radarChartMeta, radioCardMeta, radioMeta, rgbToHsv, scrollerMeta, scrubFieldMeta, sheetMeta, sheetVariants, showMoreMeta, statusBarMeta, themeSelectorMeta, themeSwitcherMeta, thinkingOrbMeta, toast, toastMeta, treeIndent, treeMeta, useComboboxFilteredItems, usePanelWidth, useToast, useToastManager, videoMeta, workspaceMeta };