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