@lessly/ui 4.1.1 → 4.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.
@@ -256,7 +256,11 @@ var SheetOverlay = React3.forwardRef(({ className, ...props }, ref) => /* @__PUR
256
256
  {
257
257
  ref,
258
258
  className: cn(
259
- "fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
259
+ // The scrim's state-scoped pair is the panel's below, so the two arrive as one — and an
260
+ // unprefixed 500 would slow the close as well as the open. Naming a duration at all is what
261
+ // makes `prefers-reduced-motion` reach the fade: an unset `--tw-duration` falls to
262
+ // tw-animate-css's own literal 0.15s, which no media query can collapse.
263
+ "fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:duration-300 data-[state=open]:duration-500",
260
264
  className
261
265
  ),
262
266
  ...props
@@ -613,13 +617,16 @@ function RailMark({ collapsed, className }) {
613
617
  }
614
618
  );
615
619
  }
620
+ function railToggleLabel(collapsed) {
621
+ return collapsed ? "Expand sidebar" : "Collapse sidebar";
622
+ }
616
623
  var RailToggle = React7.forwardRef(function RailToggle2({ collapsed, className, "aria-label": ariaLabel, ...rest }, ref) {
617
624
  return /* @__PURE__ */ jsx7(
618
625
  "button",
619
626
  {
620
627
  ref,
621
628
  type: "button",
622
- "aria-label": ariaLabel ?? (collapsed ? "Expand sidebar" : "Collapse sidebar"),
629
+ "aria-label": ariaLabel ?? railToggleLabel(collapsed),
623
630
  "aria-expanded": !collapsed,
624
631
  className: cn(
625
632
  RAIL_COLUMN_BOX,
@@ -632,6 +639,25 @@ var RailToggle = React7.forwardRef(function RailToggle2({ collapsed, className,
632
639
  );
633
640
  });
634
641
 
642
+ // src/hooks/use-sidebar-drawer.ts
643
+ import { createContext as createContext2, useContext as useContext2 } from "react";
644
+ var SidebarDrawerContext = createContext2(false);
645
+ function useSidebarDrawer() {
646
+ return useContext2(SidebarDrawerContext);
647
+ }
648
+ var NOT_IN_A_DRAWER = () => {
649
+ };
650
+ var SidebarDrawerDismissContext = createContext2(null);
651
+ function useSidebarDismiss() {
652
+ return useContext2(SidebarDrawerDismissContext) ?? NOT_IN_A_DRAWER;
653
+ }
654
+
655
+ // src/components/sidebar-drawer.tsx
656
+ import { jsx as jsx8 } from "react/jsx-runtime";
657
+ function SidebarDrawer({ children, onDismiss }) {
658
+ return /* @__PURE__ */ jsx8(SidebarDrawerContext.Provider, { value: true, children: /* @__PURE__ */ jsx8(SidebarDrawerDismissContext.Provider, { value: onDismiss ?? null, children }) });
659
+ }
660
+
635
661
  export {
636
662
  cn,
637
663
  buttonVariants,
@@ -657,5 +683,9 @@ export {
657
683
  useSidebar,
658
684
  NavRow,
659
685
  RAIL_COLUMN_BOX,
660
- RailToggle
686
+ railToggleLabel,
687
+ RailToggle,
688
+ useSidebarDrawer,
689
+ useSidebarDismiss,
690
+ SidebarDrawer
661
691
  };
package/dist/index.d.ts CHANGED
@@ -2551,6 +2551,11 @@ type NavRowProps = NavRowOwnProps & Omit<React$1.ComponentPropsWithoutRef<'butto
2551
2551
  * not an entity opening as a page, so the hover fill is the whole affordance. */
2552
2552
  declare const NavRow: React$1.ForwardRefExoticComponent<NavRowProps & React$1.RefAttributes<HTMLButtonElement>>;
2553
2553
 
2554
+ /** What the control answers to, from the state it is in: the name says what the press will do, so a
2555
+ * closed rail's control is `Expand sidebar`. It is a function rather than two strings at the call
2556
+ * site because the tooltip a call site composes has to say what the control is called — and two
2557
+ * hand-written copies of a pair like this are two copies to keep in step. */
2558
+ declare function railToggleLabel(collapsed: boolean): string;
2554
2559
  interface RailToggleProps extends React$1.ComponentPropsWithoutRef<'button'> {
2555
2560
  /** Which state the rail is in now, not what the press will do. The mark and the accessible name
2556
2561
  * are both read off it, so a rail cannot say one thing and draw another. */
@@ -2574,7 +2579,9 @@ interface RailToggleProps extends React$1.ComponentPropsWithoutRef<'button'> {
2574
2579
  * console gives the control the words its command palette already uses for it.
2575
2580
  *
2576
2581
  * No tooltip of its own. On a closed rail every neighbour has one, so the call site composes it —
2577
- * and the label it shows is then the same string this takes.
2582
+ * and the label it shows is `railToggleLabel(collapsed)`, the same resolver the accessible name
2583
+ * above falls back to, so the two cannot say different things. `AppSidebar` composes exactly that
2584
+ * at its foot.
2578
2585
  */
2579
2586
  declare const RailToggle: React$1.ForwardRefExoticComponent<RailToggleProps & React$1.RefAttributes<HTMLButtonElement>>;
2580
2587
 
@@ -2586,6 +2593,14 @@ interface QuickSearchRowProps {
2586
2593
  /**
2587
2594
  * The second way in, stated rather than implied — this is the one fact a person needs before they
2588
2595
  * stop reaching for the sidebar at all. `false` for a rail with no shortcut bound.
2596
+ *
2597
+ * Only an unset prop takes the default. `false` and `null` both mean this rail has no shortcut,
2598
+ * on either surface — `shortcut={binding ?? null}` is the shape a caller reaches for and it has
2599
+ * to mean what it says.
2600
+ *
2601
+ * Unset, it is `⌘K` on a rail and nothing at all inside a `<SidebarDrawer>`: a drawer is the
2602
+ * shape a phone gets, and a phone has no key to press. Set, it is what the row states on either
2603
+ * surface — a drawer opened on a tablet with a keyboard really does have one.
2589
2604
  */
2590
2605
  shortcut?: React$1.ReactNode;
2591
2606
  /**
@@ -2613,6 +2628,26 @@ declare namespace QuickSearchRow {
2613
2628
  var displayName: string;
2614
2629
  }
2615
2630
 
2631
+ /** Marks everything below as drawn inside a drawer — a rail, and every part it holds — and hands it
2632
+ * the way out. `AppShell` wraps its sheet's contents in it and passes its own close as `onDismiss`,
2633
+ * which is what lets a rail that navigates by state close the sheet it navigated in. A product that
2634
+ * builds its own rail inside its own sheet writes this itself; without it every such rail keeps
2635
+ * printing ⌘K on a phone.
2636
+ *
2637
+ * Its own module, and not beside `AppSidebar`. This ships from `@lessly/ui` while the rail ships
2638
+ * from `@lessly/ui/router`, and a module both entries reach lands in the chunk they share — so
2639
+ * written in `app-sidebar.tsx` this dragged `react-router` into `import { Button } from
2640
+ * '@lessly/ui'` and broke resolution for a consumer who skipped the optional peer (#878). Nothing
2641
+ * here imports anything but React and the contexts. */
2642
+ declare function SidebarDrawer({ children, onDismiss }: {
2643
+ children: React$1.ReactNode;
2644
+ onDismiss?: () => void;
2645
+ }): React$1.JSX.Element;
2646
+
2647
+ /** How a part inside a drawer closes it. Outside one it is a no-op, so a rail row can call it
2648
+ * unconditionally rather than asking which surface it is on first. */
2649
+ declare function useSidebarDismiss(): () => void;
2650
+
2616
2651
  type NavSectionLabelProps = React$1.HTMLAttributes<HTMLDivElement>;
2617
2652
  /** Section header for nav rails and menu popups. Insets match the 0.3.0 prototype
2618
2653
  * (4px 4px 2px 8px), so it hangs slightly left of the rows it labels. */
@@ -2754,6 +2789,15 @@ interface OrgProductSwitcherProps {
2754
2789
  * console offers it.
2755
2790
  */
2756
2791
  trigger?: 'stacked' | 'inline';
2792
+ /**
2793
+ * Whether the `stacked` trigger draws its own leading mark, or holds the place open and draws
2794
+ * nothing in it. `hold` is for a surface that draws that mark itself and can only carry one: a
2795
+ * rail stands the organization's mark on its glyph column at both widths, and a trigger drawing
2796
+ * a second puts two marks 2px apart in one band. The place is held rather than closed so the name
2797
+ * starts on the column it starts on either way, and the trigger's own box is untouched, so the
2798
+ * menu anchors where it did. The `inline` trigger has never drawn a mark and reads neither value.
2799
+ */
2800
+ mark?: 'draw' | 'hold';
2757
2801
  /** Accessible name for the trigger. Defaults to `"<org> / <product>"`, and to whatever the
2758
2802
  * trigger reads before an org resolves. */
2759
2803
  'aria-label'?: string;
@@ -2776,7 +2820,7 @@ interface OrgProductSwitcherProps {
2776
2820
  * `activeProductId` for the newly-selected org. Wiring org/product switches to routing is the
2777
2821
  * consumer's job.
2778
2822
  */
2779
- declare function OrgProductSwitcher({ organizations, activeOrgId, onOrgSelect, products, activeProductId, onProductSelect, actions, trigger, className, ...props }: OrgProductSwitcherProps): React$1.JSX.Element;
2823
+ declare function OrgProductSwitcher({ organizations, activeOrgId, onOrgSelect, products, activeProductId, onProductSelect, actions, trigger, mark, className, ...props }: OrgProductSwitcherProps): React$1.JSX.Element;
2780
2824
  declare namespace OrgProductSwitcher {
2781
2825
  var displayName: string;
2782
2826
  }
@@ -3780,4 +3824,4 @@ interface RatingScaleProps extends Omit<React$1.ComponentPropsWithoutRef<typeof
3780
3824
  }
3781
3825
  declare const RatingScale: React$1.ForwardRefExoticComponent<RatingScaleProps & React$1.RefAttributes<HTMLDivElement>>;
3782
3826
 
3783
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, AddPicker, type AddPickerItem, type AddPickerProps, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AspectRatio, AttachmentChip, type AttachmentChipProps, type AttachmentState, AutoHeight, type AutoHeightProps, Avatar, AvatarFallback, AvatarImage, BackLink, type BackLinkProps, Badge, type BadgeProps, type BeneficialOwnerPayload, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, COUNTRY_OPTIONS, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardNote, type CardNoteProps, type CardProps, CardTitle, type CardTitleProps, Carousel, type CarouselApi, CarouselContent, type CarouselContextProps, CarouselItem, CarouselNext, type CarouselOptions, type CarouselPlugin, CarouselPrevious, type CarouselProps, Checkbox, Code, CodeBlock, type CodeBlockProps, type CodeLang, type CodeProps, type CodeVariant, Col, type ColProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, type CommandDialogGroup, type CommandDialogItem, type CommandDialogProps, CommandEmpty, CommandGroup, CommandInput, CommandItem, type CommandItemProps, CommandList, CommandSeparator, CommandShortcut, ConfirmDialog, type ConfirmDialogProps, type ConfirmDialogStepUp, ConnectorCard, type ConnectorCardProps, type ConnectorOrigin, type ConnectorStatus, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, type CopyButtonProps, DatePicker, type DatePickerProps, DecorativeIcon, type DecorativeIconProps, type DeltaDirection, type DeltaTone, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DocsLink, type DocsLinkProps, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, EntityRow, type EntityRowOpens, type EntityRowOpensProps, type EntityRowProps, EntityTile, type EntityTileProps, ExtensionIcon, type ExtensionIconProps, FeedbackButton, type FeedbackButtonLabels, type FeedbackButtonProps, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, GlyphAvatar, type GlyphAvatarProps, type GrantRole, type GrantRoleNotes, GrantRolePicker, type GrantRolePickerProps, GrantRow, type GrantRowProps, Grid, GridOverlay, type GridOverlayProps, type GridProps, HoverCard, HoverCardContent, HoverCardTrigger, IconHint, type IconHintProps, ImageCropDialog, type ImageCropDialogProps, type ImageCropShape, type ImageCropType, ImageCropper, type ImageCropperProps, ImageUpload, type ImageUploadProps, type ImageUploadSize, InlineError, type InlineErrorProps, Input, InputAddon, type InputAddonProps, InputGroup, InputGroupInput, type InputGroupInputProps, type InputGroupProps, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, type InputProps, Label, ListCells, type ListCellsProps, type ListColumn, type ListColumnWidth, ListHeader, type ListHeaderProps, MISSING_TONE, Medallion, type MedallionProps, MenuDivider, MenuPopup, type MenuPopupProps, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, NavRow, type NavRowMark, type NavRowProps, type NavRowVariant, NavSectionLabel, type NavSectionLabelProps, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, type NotifClass, type NotificationAction, NotificationBell, type NotificationBellProps, NotificationCenter, type NotificationCenterProps, type NotificationClass, type NotificationDeepLink, type NotificationItem, type NotificationMute, type NotificationPreferences, type NotificationProduct, NotificationRow, type NotificationRowProps, NotificationSettings, type NotificationSettingsProps, type NotificationSeverity, type NotificationSubscription, type NotificationThreshold, NotificationToast, type NotificationToastDwell, type NotificationToastProps, NotificationToaster, type NotificationToasterPosition, type NotificationToasterProps, OptionCard, type OptionCardProps, OrgProductSwitcher, type OrgProductSwitcherAction, type OrgProductSwitcherItem, type OrgProductSwitcherProduct, type OrgProductSwitcherProps, type OrganizationOnboardingDefaults, OrganizationOnboardingForm, type OrganizationOnboardingFormProps, type OrganizationOnboardingPayload, PageColumn, type PageColumnProps, PageHeader, type PageHeaderProps, type PageHeaderSize, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, type PaginationLinkProps, PaginationNext, PaginationPrevious, PersonAvatar, type PersonAvatarProps, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, type ProductStatus, Progress, QuickSearchRow, type QuickSearchRowProps, RadioGroup, RadioGroupItem, RailToggle, type RailToggleProps, RatingScale, type RatingScaleProps, type RatingScaleSize, RemovableChip, type RemovableChipProps, RemoveButton, type RemoveButtonProps, RequestRow, type RequestRowProps, type RequestState, type RequestSurface, type ResolvedTheme, type Responsive, ScrollArea, ScrollBar, SecretReveal, type SecretRevealProps, SectionIntro, type SectionIntroProps, Select, type SelectOption, type SelectProps, Separator, SettingRow, type SettingRowProps, type Severity, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarBackHeader, type SidebarBackHeaderProps, SidebarProductHeader, type SidebarProductHeaderProps, Skeleton, Slider, StarRating, type StarRatingProps, type StarRatingSize, StatCard, type StatCardProps, StatusDot, type StatusDotProps, StepUpChallenge, type StepUpChallengeProps, type StepUpMethod, type StepUpProof, Switch, type SwitchProps, TOAST_DWELL, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, type TextareaProps, type Theme, ThemePicker, type ThemePickerProps, ThemeToggle, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, TopBar, type TopBarProps, TopBarUtilities, type TopBarUtilitiesProps, type TopBarUtility, type UseFreshHighlightResult, type UseSidebarOptions, type UseSidebarResult, UserMenu, type UserMenuItem, type UserMenuProps, type UserMenuUser, WEIGHT_TONE, alertVariants, attachmentChipVariants, badgeVariants, buttonVariants, cn, formatRelativeAge, grantRemoveLabel, isKnownExtensionIcon, isNotificationSettled, navigationMenuTriggerStyle, productStatusBadge, severityBadgeVariant, severityLabels, statusDotVariants, toggleVariants, useCarousel, useFormField, useFreshHighlight, useIsMobile, useSidebar, useTheme };
3827
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, AddPicker, type AddPickerItem, type AddPickerProps, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AspectRatio, AttachmentChip, type AttachmentChipProps, type AttachmentState, AutoHeight, type AutoHeightProps, Avatar, AvatarFallback, AvatarImage, BackLink, type BackLinkProps, Badge, type BadgeProps, type BeneficialOwnerPayload, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, COUNTRY_OPTIONS, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardNote, type CardNoteProps, type CardProps, CardTitle, type CardTitleProps, Carousel, type CarouselApi, CarouselContent, type CarouselContextProps, CarouselItem, CarouselNext, type CarouselOptions, type CarouselPlugin, CarouselPrevious, type CarouselProps, Checkbox, Code, CodeBlock, type CodeBlockProps, type CodeLang, type CodeProps, type CodeVariant, Col, type ColProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, type CommandDialogGroup, type CommandDialogItem, type CommandDialogProps, CommandEmpty, CommandGroup, CommandInput, CommandItem, type CommandItemProps, CommandList, CommandSeparator, CommandShortcut, ConfirmDialog, type ConfirmDialogProps, type ConfirmDialogStepUp, ConnectorCard, type ConnectorCardProps, type ConnectorOrigin, type ConnectorStatus, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, type CopyButtonProps, DatePicker, type DatePickerProps, DecorativeIcon, type DecorativeIconProps, type DeltaDirection, type DeltaTone, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DocsLink, type DocsLinkProps, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, EntityRow, type EntityRowOpens, type EntityRowOpensProps, type EntityRowProps, EntityTile, type EntityTileProps, ExtensionIcon, type ExtensionIconProps, FeedbackButton, type FeedbackButtonLabels, type FeedbackButtonProps, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, GlyphAvatar, type GlyphAvatarProps, type GrantRole, type GrantRoleNotes, GrantRolePicker, type GrantRolePickerProps, GrantRow, type GrantRowProps, Grid, GridOverlay, type GridOverlayProps, type GridProps, HoverCard, HoverCardContent, HoverCardTrigger, IconHint, type IconHintProps, ImageCropDialog, type ImageCropDialogProps, type ImageCropShape, type ImageCropType, ImageCropper, type ImageCropperProps, ImageUpload, type ImageUploadProps, type ImageUploadSize, InlineError, type InlineErrorProps, Input, InputAddon, type InputAddonProps, InputGroup, InputGroupInput, type InputGroupInputProps, type InputGroupProps, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, type InputProps, Label, ListCells, type ListCellsProps, type ListColumn, type ListColumnWidth, ListHeader, type ListHeaderProps, MISSING_TONE, Medallion, type MedallionProps, MenuDivider, MenuPopup, type MenuPopupProps, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, NavRow, type NavRowMark, type NavRowProps, type NavRowVariant, NavSectionLabel, type NavSectionLabelProps, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, type NotifClass, type NotificationAction, NotificationBell, type NotificationBellProps, NotificationCenter, type NotificationCenterProps, type NotificationClass, type NotificationDeepLink, type NotificationItem, type NotificationMute, type NotificationPreferences, type NotificationProduct, NotificationRow, type NotificationRowProps, NotificationSettings, type NotificationSettingsProps, type NotificationSeverity, type NotificationSubscription, type NotificationThreshold, NotificationToast, type NotificationToastDwell, type NotificationToastProps, NotificationToaster, type NotificationToasterPosition, type NotificationToasterProps, OptionCard, type OptionCardProps, OrgProductSwitcher, type OrgProductSwitcherAction, type OrgProductSwitcherItem, type OrgProductSwitcherProduct, type OrgProductSwitcherProps, type OrganizationOnboardingDefaults, OrganizationOnboardingForm, type OrganizationOnboardingFormProps, type OrganizationOnboardingPayload, PageColumn, type PageColumnProps, PageHeader, type PageHeaderProps, type PageHeaderSize, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, type PaginationLinkProps, PaginationNext, PaginationPrevious, PersonAvatar, type PersonAvatarProps, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, type ProductStatus, Progress, QuickSearchRow, type QuickSearchRowProps, RadioGroup, RadioGroupItem, RailToggle, type RailToggleProps, RatingScale, type RatingScaleProps, type RatingScaleSize, RemovableChip, type RemovableChipProps, RemoveButton, type RemoveButtonProps, RequestRow, type RequestRowProps, type RequestState, type RequestSurface, type ResolvedTheme, type Responsive, ScrollArea, ScrollBar, SecretReveal, type SecretRevealProps, SectionIntro, type SectionIntroProps, Select, type SelectOption, type SelectProps, Separator, SettingRow, type SettingRowProps, type Severity, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarBackHeader, type SidebarBackHeaderProps, SidebarDrawer, SidebarProductHeader, type SidebarProductHeaderProps, Skeleton, Slider, StarRating, type StarRatingProps, type StarRatingSize, StatCard, type StatCardProps, StatusDot, type StatusDotProps, StepUpChallenge, type StepUpChallengeProps, type StepUpMethod, type StepUpProof, Switch, type SwitchProps, TOAST_DWELL, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, type TextareaProps, type Theme, ThemePicker, type ThemePickerProps, ThemeToggle, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, TopBar, type TopBarProps, TopBarUtilities, type TopBarUtilitiesProps, type TopBarUtility, type UseFreshHighlightResult, type UseSidebarOptions, type UseSidebarResult, UserMenu, type UserMenuItem, type UserMenuProps, type UserMenuUser, WEIGHT_TONE, alertVariants, attachmentChipVariants, badgeVariants, buttonVariants, cn, formatRelativeAge, grantRemoveLabel, isKnownExtensionIcon, isNotificationSettled, navigationMenuTriggerStyle, productStatusBadge, railToggleLabel, severityBadgeVariant, severityLabels, statusDotVariants, toggleVariants, useCarousel, useFormField, useFreshHighlight, useIsMobile, useSidebar, useSidebarDismiss, useTheme };
package/dist/index.js CHANGED
@@ -15,15 +15,19 @@ import {
15
15
  SheetPortal,
16
16
  SheetTitle,
17
17
  SheetTrigger,
18
+ SidebarDrawer,
18
19
  Tooltip,
19
20
  TooltipContent,
20
21
  TooltipProvider,
21
22
  TooltipTrigger,
22
23
  buttonVariants,
23
24
  cn,
25
+ railToggleLabel,
24
26
  useIsMobile,
25
- useSidebar
26
- } from "./chunk-LTHOLX7I.js";
27
+ useSidebar,
28
+ useSidebarDismiss,
29
+ useSidebarDrawer
30
+ } from "./chunk-OMJRQ4JY.js";
27
31
 
28
32
  // src/components/auto-height.tsx
29
33
  import * as React from "react";
@@ -149,7 +153,10 @@ var DialogOverlay = React3.forwardRef(({ className, ...props }, ref) => /* @__PU
149
153
  {
150
154
  ref,
151
155
  className: cn(
152
- "fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
156
+ // The scrim's duration is the panel's, so the two arrive as one. Naming it is what makes
157
+ // `prefers-reduced-motion` reach the fade: `animate-in` reads `--tw-duration`, and an unset
158
+ // one falls to tw-animate-css's own literal 0.15s, which no media query can collapse.
159
+ "fixed inset-0 z-50 bg-black/50 duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
153
160
  className
154
161
  ),
155
162
  ...props
@@ -1174,7 +1181,10 @@ var AlertDialogOverlay = React21.forwardRef(({ className, ...props }, ref) => /*
1174
1181
  {
1175
1182
  ref,
1176
1183
  className: cn(
1177
- "fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
1184
+ // The scrim's duration is the panel's, so the two arrive as one. Naming it is what makes
1185
+ // `prefers-reduced-motion` reach the fade: `animate-in` reads `--tw-duration`, and an unset
1186
+ // one falls to tw-animate-css's own literal 0.15s, which no media query can collapse.
1187
+ "fixed inset-0 z-50 bg-black/50 duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
1178
1188
  className
1179
1189
  ),
1180
1190
  ...props
@@ -4534,14 +4544,17 @@ function useFreshHighlight() {
4534
4544
  // src/components/quick-search-row.tsx
4535
4545
  import { Search as Search2 } from "lucide-react";
4536
4546
  import { jsx as jsx74, jsxs as jsxs48 } from "react/jsx-runtime";
4547
+ var DEFAULT_SHORTCUT = "\u2318K";
4537
4548
  function QuickSearchRow({
4538
4549
  onOpen,
4539
4550
  label = "Quick search",
4540
- shortcut = "\u2318K",
4551
+ shortcut,
4541
4552
  collapsed = false,
4542
4553
  icon
4543
4554
  }) {
4544
- const name = shortcut ? `${label} ${shortcut}` : label;
4555
+ const drawer = useSidebarDrawer();
4556
+ const stated = shortcut === void 0 ? drawer ? false : DEFAULT_SHORTCUT : shortcut;
4557
+ const name = stated ? `${label} ${stated}` : label;
4545
4558
  if (!collapsed) {
4546
4559
  return /* @__PURE__ */ jsxs48(
4547
4560
  "button",
@@ -4563,7 +4576,7 @@ function QuickSearchRow({
4563
4576
  children: [
4564
4577
  /* @__PURE__ */ jsx74("span", { className: "flex size-4 shrink-0 items-center justify-center [&_svg]:size-4", "aria-hidden": "true", children: icon ?? /* @__PURE__ */ jsx74(Search2, {}) }),
4565
4578
  /* @__PURE__ */ jsx74("span", { className: "min-w-0 flex-1 truncate", children: label }),
4566
- shortcut && /* @__PURE__ */ jsx74("span", { className: "shrink-0 text-xs font-medium text-text-tertiary", children: shortcut })
4579
+ stated && /* @__PURE__ */ jsx74("span", { className: "shrink-0 text-xs font-medium text-text-tertiary", children: stated })
4567
4580
  ]
4568
4581
  }
4569
4582
  );
@@ -4703,12 +4716,15 @@ function ItemLabel({ item, kind }) {
4703
4716
  }
4704
4717
  var MARK_SIZE = { md: "sm", sm: "xs" };
4705
4718
  var ICON_BOX = { md: "size-8 rounded-md", sm: "size-6 rounded-sm" };
4719
+ function MarkSlot({ size }) {
4720
+ return /* @__PURE__ */ jsx78("span", { "aria-hidden": "true", className: cn("shrink-0", ICON_BOX[size]) });
4721
+ }
4706
4722
  function Mark2({ item, kind, size, reserve }) {
4707
4723
  if (item.icon) {
4708
4724
  return /* @__PURE__ */ jsx78("span", { className: cn("flex shrink-0 items-center justify-center overflow-hidden", ICON_BOX[size]), "aria-hidden": "true", children: item.icon });
4709
4725
  }
4710
4726
  const name = nameOf(item);
4711
- if (!name) return reserve ? /* @__PURE__ */ jsx78("span", { "aria-hidden": "true", className: cn("shrink-0", ICON_BOX[size]) }) : null;
4727
+ if (!name) return reserve ? /* @__PURE__ */ jsx78(MarkSlot, { size }) : null;
4712
4728
  return /* @__PURE__ */ jsx78(EntityTile, { name, tone: kind === "product" ? "auto" : "neutral", size: MARK_SIZE[size] });
4713
4729
  }
4714
4730
  function OrgProductSwitcher({
@@ -4720,6 +4736,7 @@ function OrgProductSwitcher({
4720
4736
  onProductSelect,
4721
4737
  actions,
4722
4738
  trigger = "stacked",
4739
+ mark = "draw",
4723
4740
  className,
4724
4741
  ...props
4725
4742
  }) {
@@ -4788,7 +4805,7 @@ function OrgProductSwitcher({
4788
4805
  /* @__PURE__ */ jsx78(ChevronsUpDown3, { size: 14, "aria-hidden": "true", className: "shrink-0 text-text-tertiary" })
4789
4806
  ] })
4790
4807
  ) : /* @__PURE__ */ jsxs51(DropdownMenuTrigger, { "aria-label": ariaLabel, className: cn(triggerBox, className), children: [
4791
- triggerMark && /* @__PURE__ */ jsx78(Mark2, { item: triggerMark, kind: markKind, size: "md" }),
4808
+ triggerMark && (mark === "hold" ? /* @__PURE__ */ jsx78(MarkSlot, { size: "md" }) : /* @__PURE__ */ jsx78(Mark2, { item: triggerMark, kind: markKind, size: "md" })),
4792
4809
  orgLevel ? /* @__PURE__ */ jsx78("span", { className: "flex min-w-0 flex-1 flex-col", children: /* @__PURE__ */ jsx78(NameLine, { className: orgName ? "font-semibold text-text-primary" : "text-text-tertiary", children: orgName ?? NO_ORG_LABEL }) }) : /* @__PURE__ */ jsxs51("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
4793
4810
  orgName && /* @__PURE__ */ jsx78("span", { className: "truncate text-xs leading-[normal] text-text-tertiary", children: orgName }),
4794
4811
  /* @__PURE__ */ jsx78(NameLine, { className: productName ? "font-semibold text-text-primary" : "text-text-tertiary", children: productLabel })
@@ -4882,7 +4899,7 @@ function SidebarBackHeader({
4882
4899
  ...rowProps,
4883
4900
  children: [
4884
4901
  /* @__PURE__ */ jsx79("span", { className: arrowBox, children: /* @__PURE__ */ jsx79("span", { className: cn(arrowPlate, arrowClassName), ...arrowRest, children: /* @__PURE__ */ jsx79(Arrow, {}) }) }),
4885
- /* @__PURE__ */ jsx79("span", { className: cn(titleBox, titleClassName), ...titleRest, children: title })
4902
+ /* @__PURE__ */ jsx79("span", { className: cn(titleBox, titleClassName), ...titleRest, children: /* @__PURE__ */ jsx79("span", { "data-slot": "nav-row-label", children: title }) })
4886
4903
  ]
4887
4904
  }
4888
4905
  );
@@ -4906,7 +4923,7 @@ function SidebarBackHeader({
4906
4923
  onClick: onTitleClick,
4907
4924
  className: cn(titleBox, focusRing, titleClassName),
4908
4925
  ...titleRest,
4909
- children: title
4926
+ children: /* @__PURE__ */ jsx79("span", { "data-slot": "nav-row-label", children: title })
4910
4927
  }
4911
4928
  )
4912
4929
  ] });
@@ -7980,6 +7997,7 @@ export {
7980
7997
  SheetTitle,
7981
7998
  SheetTrigger,
7982
7999
  SidebarBackHeader,
8000
+ SidebarDrawer,
7983
8001
  SidebarProductHeader,
7984
8002
  Skeleton,
7985
8003
  Slider,
@@ -8027,6 +8045,7 @@ export {
8027
8045
  isNotificationSettled,
8028
8046
  navigationMenuTriggerStyle,
8029
8047
  productStatusBadge,
8048
+ railToggleLabel,
8030
8049
  severityBadgeVariant,
8031
8050
  severityLabels,
8032
8051
  statusDotVariants,
@@ -8037,5 +8056,6 @@ export {
8037
8056
  useFreshHighlight,
8038
8057
  useIsMobile,
8039
8058
  useSidebar,
8059
+ useSidebarDismiss,
8040
8060
  useTheme
8041
8061
  };
package/dist/router.d.ts CHANGED
@@ -68,16 +68,37 @@ interface SidebarNavProps {
68
68
  declare function SidebarNav({ items, collapsed, basePath, LinkComponent, isItemActive, className, }: SidebarNavProps): React.JSX.Element;
69
69
 
70
70
  interface AppSidebarProps {
71
- items: NavItem[];
71
+ /** The rows in the middle, as links. Optional since #878: a rail whose middle is not a list of
72
+ * links writes it as `children` instead. */
73
+ items?: NavItem[];
74
+ /** The middle, written by the caller — panes, disclosures, a back row, anything `NavItem` has no
75
+ * field for. It stands exactly where the rows stand, and it owns its own scrolling: the box the
76
+ * rail gives it holds the rail's own inset and does not scroll, so an `overflow-y-auto` region
77
+ * inside it measures against the rail's own height instead of against a box the padding made
78
+ * taller.
79
+ *
80
+ * Like `railTop` it takes a function, for the same reason: the rail drives its own collapse, so
81
+ * this is the only way a caller's node can read a state it has no other route to.
82
+ *
83
+ * Passing this and `items` together is a caller error. This wins — a middle somebody wrote is
84
+ * the more specific instruction, where a list may have arrived through a wrapper's spread — and
85
+ * development says so out loud, because the alternative is rows that render nowhere in silence. */
86
+ children?: React.ReactNode | ((collapsed: boolean) => React.ReactNode);
72
87
  /** Expanded-slot mark/wordmark, rendered when `header` is absent. Also the narrow rail's fallback
73
88
  * when `logoCollapsed` isn't given. */
74
89
  logo?: React.ReactNode;
75
- /** Narrow-rail identity — a small mark, for the width where `header` has no room to be read.
76
- * Falls back to `logo`. */
90
+ /** The band's mark — a small one, standing on the glyph column at 32. It is drawn at both widths
91
+ * and never fades: at the narrow width it is the whole of the identity, and at the wide one it is
92
+ * the mark the node in `header` leads with. Falls back to `logo`. */
77
93
  logoCollapsed?: React.ReactNode;
78
94
  /** Identity slot above the rail — e.g. an <OrgProductSwitcher/> (#266). Falls back to `logo`.
79
- * Expanded-only: `logoCollapsed ?? logo` stands in it at the narrow width, because a switcher
80
- * clipped to the rail's own width reads as a tile with a severed word beside it. */
95
+ * Expanded-only: it fades out at the narrow width, because a switcher clipped to the rail's own
96
+ * width reads as a tile with a severed word beside it.
97
+ *
98
+ * A node here draws no mark of its own. `logoCollapsed ?? logo` is the band's one mark and the
99
+ * rail draws it at BOTH widths, so a second one stands 2px beside it, solid, in the same band.
100
+ * `OrgProductSwitcher` yields its own with `mark="hold"`, which leaves its box and its words
101
+ * exactly where they are. */
81
102
  header?: React.ReactNode;
82
103
  /** Top of the rail — e.g. a <QuickSearchRow/>. It sits between the header's rule and the rows,
83
104
  * and it is pinned: outside the scroll region, so it holds one place while the rows below it
@@ -94,7 +115,7 @@ interface AppSidebarProps {
94
115
  isItemActive?: (item: NavItem, pathname: string) => boolean;
95
116
  className?: string;
96
117
  }
97
- declare function AppSidebar({ items, logo, logoCollapsed, header, railTop, collapsed: controlled, defaultCollapsed, onCollapsedChange, basePath, LinkComponent, isItemActive, className, }: AppSidebarProps): React.JSX.Element;
118
+ declare function AppSidebar({ items, children, logo, logoCollapsed, header, railTop, collapsed: controlled, defaultCollapsed, onCollapsedChange, basePath, LinkComponent, isItemActive, className, }: AppSidebarProps): React.JSX.Element;
98
119
 
99
120
  interface AppShellProps {
100
121
  /** The sidebar, typically an <AppSidebar/>. Rendered in the desktop rail and, on mobile, inside a
package/dist/router.js CHANGED
@@ -9,14 +9,17 @@ import {
9
9
  SheetDescription,
10
10
  SheetTitle,
11
11
  SheetTrigger,
12
+ SidebarDrawer,
12
13
  Tooltip,
13
14
  TooltipContent,
14
15
  TooltipProvider,
15
16
  TooltipTrigger,
16
17
  cn,
18
+ railToggleLabel,
17
19
  useIsMobile,
18
- useSidebar
19
- } from "./chunk-LTHOLX7I.js";
20
+ useSidebar,
21
+ useSidebarDrawer
22
+ } from "./chunk-OMJRQ4JY.js";
20
23
 
21
24
  // src/components/extension-link.tsx
22
25
  import { Link } from "react-router";
@@ -99,19 +102,19 @@ function SidebarNav({
99
102
  }
100
103
 
101
104
  // src/components/app-sidebar.tsx
102
- import * as React from "react";
103
105
  import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
104
- var SidebarDrawerContext = React.createContext(false);
105
- function SidebarDrawer({ children }) {
106
- return /* @__PURE__ */ jsx3(SidebarDrawerContext.Provider, { value: true, children });
107
- }
108
106
  function Divider({ testId, className }) {
109
107
  return /* @__PURE__ */ jsx3("div", { "data-testid": testId, className: cn("h-px shrink-0 bg-border-subtle", className) });
110
108
  }
111
109
  var RAIL_WIDE = "w-[248px]";
112
110
  var RAIL_NARROW = "w-[64px]";
111
+ var footTooltipOffset = (collapsed) => collapsed ? 16 : 4;
112
+ function drawsNothing(node) {
113
+ return node == null || typeof node === "boolean" || node === "" || Array.isArray(node) && node.length === 0;
114
+ }
113
115
  function AppSidebar({
114
116
  items,
117
+ children,
115
118
  logo,
116
119
  logoCollapsed,
117
120
  header,
@@ -124,7 +127,7 @@ function AppSidebar({
124
127
  isItemActive,
125
128
  className
126
129
  }) {
127
- const drawer = React.useContext(SidebarDrawerContext);
130
+ const drawer = useSidebarDrawer();
128
131
  const { collapsed: railCollapsed, toggle } = useSidebar({
129
132
  collapsed: controlled,
130
133
  defaultCollapsed,
@@ -132,54 +135,135 @@ function AppSidebar({
132
135
  });
133
136
  const collapsed = drawer ? false : railCollapsed;
134
137
  const railTopNode = typeof railTop === "function" ? railTop(collapsed) : railTop;
135
- return /* @__PURE__ */ jsx3(
136
- "aside",
137
- {
138
- "data-collapsed": collapsed,
139
- className: cn(
140
- "flex h-full shrink-0 flex-col overflow-hidden motion-rail-collapse",
141
- // A nav row drops its own label at the narrow width. What this fades is a label whatever
142
- // stands in `railTop` keeps mounted at both — QuickSearchRow's, for one — so the closing
143
- // edge slides over the words instead of cutting through them.
144
- "[&_[data-slot=nav-row-label]]:transition-opacity [&_[data-slot=nav-row-label]]:duration-fast",
145
- collapsed ? RAIL_NARROW : RAIL_WIDE,
146
- collapsed && "[&_[data-slot=nav-row-label]]:opacity-0",
147
- className
148
- ),
149
- children: /* @__PURE__ */ jsxs2("div", { className: cn("flex h-full shrink-0 flex-col", RAIL_WIDE), children: [
150
- /* @__PURE__ */ jsx3("div", { className: cn("flex h-14 shrink-0 items-center gap-2", drawer ? "pl-3 pr-10" : "px-3"), children: collapsed ? /* @__PURE__ */ jsx3("div", { className: RAIL_COLUMN_BOX, children: logoCollapsed ?? logo }) : header ?? logo }),
151
- /* @__PURE__ */ jsx3(Divider, { testId: "sidebar-divider-header" }),
152
- railTopNode && /* @__PURE__ */ jsx3("div", { "data-testid": "sidebar-rail-top", className: "shrink-0 px-3 pt-3", children: railTopNode }),
153
- /* @__PURE__ */ jsx3(
154
- ScrollArea,
155
- {
156
- className: "min-h-0 flex-1",
157
- viewportClassName: cn("px-3 pb-2", railTopNode ? "pt-2" : "pt-3"),
158
- viewportProps: { "data-testid": "sidebar-scroll" },
159
- children: /* @__PURE__ */ jsx3(SidebarNav, { items, collapsed, basePath, LinkComponent, isItemActive })
160
- }
138
+ const separateMark = header != null || logoCollapsed != null;
139
+ const asked = typeof children === "function" ? children(collapsed) : children;
140
+ const written = drawsNothing(asked) ? null : asked;
141
+ if (process.env.NODE_ENV !== "production" && written !== null && items != null) {
142
+ console.warn(
143
+ "[AppSidebar] `children` and `items` are two middles and only one is drawn: `children` wins and the rows are dropped. Put the links inside the middle you wrote, or drop `children`."
144
+ );
145
+ }
146
+ return (
147
+ // ONE PROVIDER, ROUND THE WHOLE RAIL.
148
+ //
149
+ // The rail composes a tooltip at its foot and `SidebarNav` composes one per row at the narrow
150
+ // width, and a provider per instance is what `.storybook/preview.tsx` states the rule against:
151
+ // nested, the foot's control opened at Radix's own 700ms while every row beside it opened at
152
+ // 200, and it could never join their skip-delay group. Here rather than left to `AppShell`, so
153
+ // a rail rendered on its own still has one nesting inside the shell's is harmless.
154
+ /* @__PURE__ */ jsx3(TooltipProvider, { children: /* @__PURE__ */ jsx3(
155
+ "aside",
156
+ {
157
+ "data-collapsed": collapsed,
158
+ className: cn(
159
+ "flex h-full shrink-0 flex-col overflow-hidden motion-rail-collapse",
160
+ // A nav row drops its own label at the narrow width. What this fades is a label whatever
161
+ // stands in `railTop` keeps mounted at both — QuickSearchRow's, for one — so the closing
162
+ // edge slides over the words instead of cutting through them.
163
+ //
164
+ // The trailing mark goes on the same clock: a disclosure chevron is `nav-row-trailing`, it
165
+ // is as far past the closing edge as the words beside it, and left untimed it snapped out
166
+ // while they faded.
167
+ //
168
+ // `duration-fast` is the token scale rather than a typed number, so a `@lessly/tokens`
169
+ // release retunes it, and the step reaches the utility as `var(--motion-duration-fast,
170
+ // 100ms)` — the same variable `.motion-rail-collapse` reads, which the token layer's
171
+ // `prefers-reduced-motion` block takes to 0ms, so the words and the width stop together.
172
+ // `motion-reduce:transition-none` is what a consumer running the preset without
173
+ // `@lessly/ui/styles.css` stops on: they are left with the 100ms fallback, and no media
174
+ // query reaches a literal.
175
+ "[&_[data-slot=nav-row-label]]:transition-opacity [&_[data-slot=nav-row-label]]:duration-fast",
176
+ "[&_[data-slot=nav-row-label]]:motion-reduce:transition-none",
177
+ "[&_[data-slot=nav-row-trailing]]:transition-opacity [&_[data-slot=nav-row-trailing]]:duration-fast",
178
+ "[&_[data-slot=nav-row-trailing]]:motion-reduce:transition-none",
179
+ collapsed ? RAIL_NARROW : RAIL_WIDE,
180
+ collapsed && "[&_[data-slot=nav-row-label]]:opacity-0 [&_[data-slot=nav-row-trailing]]:opacity-0",
181
+ className
161
182
  ),
162
- !drawer && /* @__PURE__ */ jsxs2(Fragment, { children: [
163
- /* @__PURE__ */ jsx3(Divider, { testId: "sidebar-divider-foot" }),
164
- /* @__PURE__ */ jsx3("div", { className: "flex shrink-0 px-3 py-2", children: /* @__PURE__ */ jsx3(RailToggle, { collapsed, onClick: toggle }) })
183
+ children: /* @__PURE__ */ jsxs2("div", { className: cn("flex h-full shrink-0 flex-col", RAIL_WIDE), children: [
184
+ /* @__PURE__ */ jsx3("div", { className: cn("relative flex h-14 shrink-0 items-center", drawer ? "pl-3 pr-10" : "px-3"), children: separateMark ? /* @__PURE__ */ jsxs2(Fragment, { children: [
185
+ /* @__PURE__ */ jsx3(
186
+ "div",
187
+ {
188
+ "data-testid": "sidebar-identity-expanded",
189
+ "aria-hidden": collapsed || void 0,
190
+ inert: collapsed,
191
+ className: cn(
192
+ "flex w-full items-center gap-2 transition-opacity duration-normal motion-reduce:transition-none",
193
+ collapsed && "pointer-events-none opacity-0"
194
+ ),
195
+ children: header ?? logo
196
+ }
197
+ ),
198
+ /* @__PURE__ */ jsx3(
199
+ "div",
200
+ {
201
+ "data-testid": "sidebar-identity-collapsed",
202
+ "aria-hidden": !collapsed || void 0,
203
+ inert: !collapsed,
204
+ className: cn("absolute", RAIL_COLUMN_BOX, !collapsed && "pointer-events-none"),
205
+ children: logoCollapsed ?? logo
206
+ }
207
+ )
208
+ ] }) : (
209
+ /* ONE MARK, MOUNTED ONCE.
210
+ With neither `header` nor `logoCollapsed`, both slots resolve to the same `logo`, and
211
+ a node fading into an identical node is not a transition — it is one mark ghosted off
212
+ another. Mounted twice it was worse than a picture: one `id` inside an SVG logo
213
+ belongs to two elements and `getElementById` finds the hidden one, a `ref` fires
214
+ twice and measures the transparent copy, effects subscribe twice, and `inert` pauses
215
+ no player.
216
+ It stands in RAIL_COLUMN_BOX at BOTH widths, which is the box the closed mark and the
217
+ foot's toggle already stand in: centre 32, the column every row's glyph is on. That
218
+ is a class list that never changes, so there is no frame for it to move on. */
219
+ /* @__PURE__ */ jsx3("div", { "data-testid": "sidebar-identity-single", className: RAIL_COLUMN_BOX, children: logo })
220
+ ) }),
221
+ /* @__PURE__ */ jsx3(Divider, { testId: "sidebar-divider-header" }),
222
+ railTopNode && /* @__PURE__ */ jsx3("div", { "data-testid": "sidebar-rail-top", className: "shrink-0 px-3 pt-3", children: railTopNode }),
223
+ written !== null ? /* @__PURE__ */ jsx3(
224
+ "div",
225
+ {
226
+ "data-testid": "sidebar-body",
227
+ className: cn(
228
+ "flex min-h-0 flex-1 flex-col overflow-hidden px-3 pb-2",
229
+ railTopNode ? "pt-2" : "pt-3"
230
+ ),
231
+ children: written
232
+ }
233
+ ) : /* @__PURE__ */ jsx3(
234
+ ScrollArea,
235
+ {
236
+ className: "min-h-0 flex-1",
237
+ viewportClassName: cn("px-3 pb-2", railTopNode ? "pt-2" : "pt-3"),
238
+ viewportProps: { "data-testid": "sidebar-scroll" },
239
+ children: /* @__PURE__ */ jsx3(SidebarNav, { items: items ?? [], collapsed, basePath, LinkComponent, isItemActive })
240
+ }
241
+ ),
242
+ !drawer && /* @__PURE__ */ jsxs2(Fragment, { children: [
243
+ /* @__PURE__ */ jsx3(Divider, { testId: "sidebar-divider-foot" }),
244
+ /* @__PURE__ */ jsx3("div", { className: "flex shrink-0 px-3 py-2", children: /* @__PURE__ */ jsxs2(Tooltip, { children: [
245
+ /* @__PURE__ */ jsx3(TooltipTrigger, { asChild: true, "aria-describedby": void 0, children: /* @__PURE__ */ jsx3(RailToggle, { collapsed, onClick: toggle }) }),
246
+ /* @__PURE__ */ jsx3(TooltipContent, { side: "right", sideOffset: footTooltipOffset(collapsed), children: railToggleLabel(collapsed) })
247
+ ] }) })
248
+ ] })
165
249
  ] })
166
- ] })
167
- }
250
+ }
251
+ ) })
168
252
  );
169
253
  }
170
254
 
171
255
  // src/components/app-shell.tsx
172
- import * as React2 from "react";
256
+ import * as React from "react";
173
257
  import { Menu } from "lucide-react";
174
258
  import { useLocation as useLocation2 } from "react-router";
175
259
  import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
176
260
  function expandedSidebar(sidebar) {
177
- return React2.isValidElement(sidebar) ? React2.cloneElement(sidebar, { collapsed: false }) : sidebar;
261
+ return React.isValidElement(sidebar) ? React.cloneElement(sidebar, { collapsed: false }) : sidebar;
178
262
  }
179
263
  function AppShell({ sidebar, topBar, children, className }) {
180
264
  const { pathname } = useLocation2();
181
- const [mobileOpen, setMobileOpen] = React2.useState(false);
182
- React2.useEffect(() => {
265
+ const [mobileOpen, setMobileOpen] = React.useState(false);
266
+ React.useEffect(() => {
183
267
  setMobileOpen(false);
184
268
  }, [pathname]);
185
269
  const isMobile = useIsMobile();
@@ -196,7 +280,7 @@ function AppShell({ sidebar, topBar, children, className }) {
196
280
  /* @__PURE__ */ jsxs3(SheetContent, { side: "left", className: "w-[248px] bg-bg-primary p-0", children: [
197
281
  /* @__PURE__ */ jsx4(SheetTitle, { className: "sr-only", children: "Navigation" }),
198
282
  /* @__PURE__ */ jsx4(SheetDescription, { className: "sr-only", children: "Application navigation menu" }),
199
- /* @__PURE__ */ jsx4(SidebarDrawer, { children: expandedSidebar(sidebar) })
283
+ /* @__PURE__ */ jsx4(SidebarDrawer, { onDismiss: () => setMobileOpen(false), children: expandedSidebar(sidebar) })
200
284
  ] })
201
285
  ] }),
202
286
  /* @__PURE__ */ jsx4("div", { className: "flex items-center gap-2", children: topBar })
@@ -19,7 +19,7 @@
19
19
  --bg-elevated: #252936;
20
20
  --bg-overlay: #252936;
21
21
  --bg-sunken: #0e1015;
22
- --bg-selected: #1f222d;
22
+ --bg-selected: #252735;
23
23
  --bg-brand: #06297d;
24
24
  --bg-brand-subtle: #1f2541;
25
25
  --bg-brand-hover: #08349b;
@@ -103,12 +103,12 @@
103
103
  --grid-container-max: 1440px;
104
104
 
105
105
  /* typography text-styles — theme-invariant, :root only (see typographyVars in build.js) */
106
- --text-display-xl-font-family: "Instrument Serif", Georgia, "Times New Roman", serif;
106
+ --text-display-xl-font-family: "Sora", "Inter", system-ui, -apple-system, sans-serif;
107
107
  --text-display-xl-font-size: 3.5rem;
108
108
  --text-display-xl-line-height: 1.05;
109
109
  --text-display-xl-font-weight: 400;
110
110
  --text-display-xl-letter-spacing: -0.02em;
111
- --text-display-lg-font-family: "Instrument Serif", Georgia, "Times New Roman", serif;
111
+ --text-display-lg-font-family: "Sora", "Inter", system-ui, -apple-system, sans-serif;
112
112
  --text-display-lg-font-size: 3rem;
113
113
  --text-display-lg-line-height: 1.1;
114
114
  --text-display-lg-font-weight: 400;
@@ -283,7 +283,7 @@
283
283
  --bg-elevated: #edeef0;
284
284
  --bg-overlay: #ffffff;
285
285
  --bg-sunken: #e1e2e4;
286
- --bg-selected: #f2f3f4;
286
+ --bg-selected: #eceded;
287
287
  --bg-brand-subtle: #e4ecfe;
288
288
  --bg-brand-hover: #05226a;
289
289
  --bg-brand-bright-hover: #0b4cd5;
@@ -342,6 +342,27 @@
342
342
  --overlay-modal-bg: #ffffff;
343
343
  --focus-ring-color: #0940b8;
344
344
 
345
+ /* Same value as :root on purpose, re-declared so .light lists every colour token a
346
+ light consumer gets rather than leaving some to inheritance. Why, and what holds
347
+ it honest: docs/tokens.md, "Theme invariance". */
348
+ --bg-brand: #06297d;
349
+ --bg-brand-bright: #165ff2;
350
+ --bg-success: #13703c;
351
+ --bg-success-bright: #21be63;
352
+ --bg-danger: #78100b;
353
+ --text-on-brand: #f6f7f7;
354
+ --text-on-success: #f6f7f7;
355
+ --text-on-success-bright: #08090c;
356
+ --text-on-warning: #08090c;
357
+ --text-on-danger: #f6f7f7;
358
+ --border-selected: #737c95;
359
+ --border-brand: #165ff2;
360
+ --border-warning: #b3780e;
361
+ --border-danger: #e82020;
362
+ --field-border-hover: #737c95;
363
+ --field-border-filled: #737c95;
364
+ --field-border-error: #e82020;
365
+
345
366
  /* shadcn role bridge re-declared so .light on a nested element (not just <html>)
346
367
  re-resolves the aliases to light values. */
347
368
  --background: var(--bg-primary);
@@ -471,11 +492,15 @@
471
492
  --color-bg-surface: var(--bg-surface);
472
493
  --color-bg-elevated: var(--bg-elevated);
473
494
  --color-bg-sunken: var(--bg-sunken);
495
+ --color-bg-brand: var(--bg-brand);
474
496
  --color-bg-brand-subtle: var(--bg-brand-subtle);
475
497
  --color-bg-brand-hover: var(--bg-brand-hover);
498
+ --color-bg-brand-bright: var(--bg-brand-bright);
499
+ --color-bg-success: var(--bg-success);
476
500
  --color-bg-success-subtle: var(--bg-success-subtle);
477
501
  --color-bg-warning: var(--bg-warning);
478
502
  --color-bg-warning-subtle: var(--bg-warning-subtle);
503
+ --color-bg-danger: var(--bg-danger);
479
504
  --color-bg-danger-subtle: var(--bg-danger-subtle);
480
505
  --color-bg-teal-subtle: var(--bg-teal-subtle);
481
506
  --color-bg-violet-subtle: var(--bg-violet-subtle);
@@ -485,6 +510,10 @@
485
510
  --color-text-tertiary: var(--text-tertiary);
486
511
  --color-text-disabled: var(--text-disabled);
487
512
  --color-text-inverse: var(--text-inverse);
513
+ --color-text-on-brand: var(--text-on-brand);
514
+ --color-text-on-success: var(--text-on-success);
515
+ --color-text-on-warning: var(--text-on-warning);
516
+ --color-text-on-danger: var(--text-on-danger);
488
517
  --color-text-brand: var(--text-brand);
489
518
  --color-text-success: var(--text-success);
490
519
  --color-text-warning: var(--text-warning);
@@ -502,8 +531,11 @@
502
531
  --color-border-subtle: var(--border-subtle);
503
532
  --color-border-default: var(--border-default);
504
533
  --color-border-strong: var(--border-strong);
534
+ --color-border-brand: var(--border-brand);
505
535
  --color-border-focus: var(--border-focus);
506
536
  --color-border-success: var(--border-success);
537
+ --color-border-warning: var(--border-warning);
538
+ --color-border-danger: var(--border-danger);
507
539
  }
508
540
 
509
541
  /* === Fonts === */
@@ -606,8 +638,10 @@
606
638
 
607
639
  /* === Motion === */
608
640
  /* Backs the <AutoHeight> primitive: eases a container's height as its content changes size.
609
- Motion-token driven, so it inherits reduce-motion (the vars resolve to 0ms). A real class rather
610
- than a Tailwind arbitrary utility, which the content scanner can drop for a newly-used class.
641
+ A real class because `height` is a property no Tailwind transition utility names: the only way to
642
+ write it in a class list is the arbitrary `transition-[height]`, which the content scanner can
643
+ drop the first time a file writes it. (The duration is not the reason — `duration-normal` carries
644
+ the same variable this line does.)
611
645
  Overflow is NOT clipped here — the component clips only while the height is animating (so the
612
646
  settled box doesn't slice focus rings or tooltips on full-width children). */
613
647
  .motion-auto-height {
@@ -617,9 +651,11 @@
617
651
  }
618
652
 
619
653
  /* Backs ToggleGroup's `sliding` mark: one thumb that travels between equal-width slots instead of a
620
- fill appearing on each. A real class for the same reason as the one above, and `slide` because the
621
- thumb crosses the width of a slot `ui`, the hover/focus tint step, would clip it to near a snap.
622
- `standard` because it is on screen before and after and only moves in place. */
654
+ fill appearing on each. A named primitive rather than the three utilities that would now say the
655
+ same thing, so the thumb's timing is stated once and cannot drift between the call sites that
656
+ write it. `slide` because the thumb crosses the width of a slot `ui`, the hover/focus tint step,
657
+ would clip it to near a snap. `standard` because it is on screen before and after and only moves
658
+ in place. */
623
659
  .motion-segment-thumb {
624
660
  transition-property: transform;
625
661
  transition-duration: var(--motion-duration-slide);
@@ -630,9 +666,9 @@
630
666
  frame as it travels. One class on both, so the mark cannot be retuned away from the edge it
631
667
  answers for — the same reason two carets read one `caretMotion`. `transform` is here rather than
632
668
  in a class of its own because the bar is that edge said in miniature: retune one and the two stop
633
- arriving together. A real class for the reason the two above are, and
634
- token-driven because a Tailwind `duration-200` is a literal 200ms: under prefers-reduced-motion
635
- the rail kept sliding for its full 200ms while every token-driven motion beside it went instant. */
669
+ arriving together. A real class for the reason the first one above is, and more so: `width,
670
+ transform` is a pair no Tailwind utility names at all, so writing it in a class list means the
671
+ arbitrary `transition-[width,transform]` the form the scanner can drop. */
636
672
  .motion-rail-collapse {
637
673
  transition-property: width, transform;
638
674
  transition-duration: var(--motion-duration-normal);
package/dist/styles.css CHANGED
@@ -18,7 +18,7 @@
18
18
  --bg-elevated: #252936;
19
19
  --bg-overlay: #252936;
20
20
  --bg-sunken: #0e1015;
21
- --bg-selected: #1f222d;
21
+ --bg-selected: #252735;
22
22
  --bg-brand: #06297d;
23
23
  --bg-brand-subtle: #1f2541;
24
24
  --bg-brand-hover: #08349b;
@@ -102,12 +102,12 @@
102
102
  --grid-container-max: 1440px;
103
103
 
104
104
  /* typography text-styles — theme-invariant, :root only (see typographyVars in build.js) */
105
- --text-display-xl-font-family: "Instrument Serif", Georgia, "Times New Roman", serif;
105
+ --text-display-xl-font-family: "Sora", "Inter", system-ui, -apple-system, sans-serif;
106
106
  --text-display-xl-font-size: 3.5rem;
107
107
  --text-display-xl-line-height: 1.05;
108
108
  --text-display-xl-font-weight: 400;
109
109
  --text-display-xl-letter-spacing: -0.02em;
110
- --text-display-lg-font-family: "Instrument Serif", Georgia, "Times New Roman", serif;
110
+ --text-display-lg-font-family: "Sora", "Inter", system-ui, -apple-system, sans-serif;
111
111
  --text-display-lg-font-size: 3rem;
112
112
  --text-display-lg-line-height: 1.1;
113
113
  --text-display-lg-font-weight: 400;
@@ -282,7 +282,7 @@
282
282
  --bg-elevated: #edeef0;
283
283
  --bg-overlay: #ffffff;
284
284
  --bg-sunken: #e1e2e4;
285
- --bg-selected: #f2f3f4;
285
+ --bg-selected: #eceded;
286
286
  --bg-brand-subtle: #e4ecfe;
287
287
  --bg-brand-hover: #05226a;
288
288
  --bg-brand-bright-hover: #0b4cd5;
@@ -341,6 +341,27 @@
341
341
  --overlay-modal-bg: #ffffff;
342
342
  --focus-ring-color: #0940b8;
343
343
 
344
+ /* Same value as :root on purpose, re-declared so .light lists every colour token a
345
+ light consumer gets rather than leaving some to inheritance. Why, and what holds
346
+ it honest: docs/tokens.md, "Theme invariance". */
347
+ --bg-brand: #06297d;
348
+ --bg-brand-bright: #165ff2;
349
+ --bg-success: #13703c;
350
+ --bg-success-bright: #21be63;
351
+ --bg-danger: #78100b;
352
+ --text-on-brand: #f6f7f7;
353
+ --text-on-success: #f6f7f7;
354
+ --text-on-success-bright: #08090c;
355
+ --text-on-warning: #08090c;
356
+ --text-on-danger: #f6f7f7;
357
+ --border-selected: #737c95;
358
+ --border-brand: #165ff2;
359
+ --border-warning: #b3780e;
360
+ --border-danger: #e82020;
361
+ --field-border-hover: #737c95;
362
+ --field-border-filled: #737c95;
363
+ --field-border-error: #e82020;
364
+
344
365
  /* shadcn role bridge re-declared so .light on a nested element (not just <html>)
345
366
  re-resolves the aliases to light values. */
346
367
  --background: var(--bg-primary);
@@ -470,11 +491,15 @@
470
491
  --color-bg-surface: var(--bg-surface);
471
492
  --color-bg-elevated: var(--bg-elevated);
472
493
  --color-bg-sunken: var(--bg-sunken);
494
+ --color-bg-brand: var(--bg-brand);
473
495
  --color-bg-brand-subtle: var(--bg-brand-subtle);
474
496
  --color-bg-brand-hover: var(--bg-brand-hover);
497
+ --color-bg-brand-bright: var(--bg-brand-bright);
498
+ --color-bg-success: var(--bg-success);
475
499
  --color-bg-success-subtle: var(--bg-success-subtle);
476
500
  --color-bg-warning: var(--bg-warning);
477
501
  --color-bg-warning-subtle: var(--bg-warning-subtle);
502
+ --color-bg-danger: var(--bg-danger);
478
503
  --color-bg-danger-subtle: var(--bg-danger-subtle);
479
504
  --color-bg-teal-subtle: var(--bg-teal-subtle);
480
505
  --color-bg-violet-subtle: var(--bg-violet-subtle);
@@ -484,6 +509,10 @@
484
509
  --color-text-tertiary: var(--text-tertiary);
485
510
  --color-text-disabled: var(--text-disabled);
486
511
  --color-text-inverse: var(--text-inverse);
512
+ --color-text-on-brand: var(--text-on-brand);
513
+ --color-text-on-success: var(--text-on-success);
514
+ --color-text-on-warning: var(--text-on-warning);
515
+ --color-text-on-danger: var(--text-on-danger);
487
516
  --color-text-brand: var(--text-brand);
488
517
  --color-text-success: var(--text-success);
489
518
  --color-text-warning: var(--text-warning);
@@ -501,8 +530,11 @@
501
530
  --color-border-subtle: var(--border-subtle);
502
531
  --color-border-default: var(--border-default);
503
532
  --color-border-strong: var(--border-strong);
533
+ --color-border-brand: var(--border-brand);
504
534
  --color-border-focus: var(--border-focus);
505
535
  --color-border-success: var(--border-success);
536
+ --color-border-warning: var(--border-warning);
537
+ --color-border-danger: var(--border-danger);
506
538
  }
507
539
 
508
540
  /* === Fonts === */
@@ -647,8 +679,10 @@
647
679
 
648
680
  /* === Motion === */
649
681
  /* Backs the <AutoHeight> primitive: eases a container's height as its content changes size.
650
- Motion-token driven, so it inherits reduce-motion (the vars resolve to 0ms). A real class rather
651
- than a Tailwind arbitrary utility, which the content scanner can drop for a newly-used class.
682
+ A real class because `height` is a property no Tailwind transition utility names: the only way to
683
+ write it in a class list is the arbitrary `transition-[height]`, which the content scanner can
684
+ drop the first time a file writes it. (The duration is not the reason — `duration-normal` carries
685
+ the same variable this line does.)
652
686
  Overflow is NOT clipped here — the component clips only while the height is animating (so the
653
687
  settled box doesn't slice focus rings or tooltips on full-width children). */
654
688
  .motion-auto-height {
@@ -658,9 +692,11 @@
658
692
  }
659
693
 
660
694
  /* Backs ToggleGroup's `sliding` mark: one thumb that travels between equal-width slots instead of a
661
- fill appearing on each. A real class for the same reason as the one above, and `slide` because the
662
- thumb crosses the width of a slot `ui`, the hover/focus tint step, would clip it to near a snap.
663
- `standard` because it is on screen before and after and only moves in place. */
695
+ fill appearing on each. A named primitive rather than the three utilities that would now say the
696
+ same thing, so the thumb's timing is stated once and cannot drift between the call sites that
697
+ write it. `slide` because the thumb crosses the width of a slot `ui`, the hover/focus tint step,
698
+ would clip it to near a snap. `standard` because it is on screen before and after and only moves
699
+ in place. */
664
700
  .motion-segment-thumb {
665
701
  transition-property: transform;
666
702
  transition-duration: var(--motion-duration-slide);
@@ -671,9 +707,9 @@
671
707
  frame as it travels. One class on both, so the mark cannot be retuned away from the edge it
672
708
  answers for — the same reason two carets read one `caretMotion`. `transform` is here rather than
673
709
  in a class of its own because the bar is that edge said in miniature: retune one and the two stop
674
- arriving together. A real class for the reason the two above are, and
675
- token-driven because a Tailwind `duration-200` is a literal 200ms: under prefers-reduced-motion
676
- the rail kept sliding for its full 200ms while every token-driven motion beside it went instant. */
710
+ arriving together. A real class for the reason the first one above is, and more so: `width,
711
+ transform` is a pair no Tailwind utility names at all, so writing it in a class list means the
712
+ arbitrary `transition-[width,transform]` the form the scanner can drop. */
677
713
  .motion-rail-collapse {
678
714
  transition-property: width, transform;
679
715
  transition-duration: var(--motion-duration-normal);
@@ -1,6 +1,17 @@
1
1
  // src/tailwind-preset.ts
2
2
  import tokensPreset from "@lessly/tokens/tailwind-preset";
3
3
  var upstreamTheme = tokensPreset.theme;
4
+ var boundToMotionVar = (name, fallback) => `var(--motion-duration-${name}, ${fallback})`;
5
+ var NUMERIC_DURATION_ALIASES = { 200: "normal", 300: "slow", 500: "slower" };
6
+ var upstreamDuration = (name) => {
7
+ const value = upstreamTheme.transitionDuration[name];
8
+ if (value === void 0) {
9
+ throw new Error(
10
+ `@lessly/tokens no longer declares transitionDuration.${name}, which the numeric duration steps alias \u2014 repoint or drop NUMERIC_DURATION_ALIASES for it in src/tailwind-preset.ts.`
11
+ );
12
+ }
13
+ return value;
14
+ };
4
15
  var lesslyPreset = {
5
16
  // Spread first: darkMode must keep winning even if upstream ever adds one.
6
17
  ...tokensPreset,
@@ -30,11 +41,26 @@ var lesslyPreset = {
30
41
  // is not there. Four of these steps are an upstream token's own value under the name Tailwind
31
42
  // reads for it — 200ms is `normal`, 300ms `slow`, 500ms `slower`, 10 `raised`. Same deletion
32
43
  // contract as the three above.
44
+ //
45
+ // Every step reaches the utility as its motion variable, upstream's literal behind it as the
46
+ // fallback, and the numeric steps bind to the variable of the key they alias. Only a
47
+ // declaration naming a variable is one the token layer's `prefers-reduced-motion` block can
48
+ // zero. Only the fallback keeps a consumer who takes the preset without `@lessly/ui/styles.css`
49
+ // on the durations they have: a `var()` with nothing to resolve and no fallback is invalid at
50
+ // computed-value time, and `transition-duration` drops to its initial `0s`.
33
51
  transitionDuration: {
34
- ...upstreamTheme.transitionDuration,
35
- 200: "200ms",
36
- 300: "300ms",
37
- 500: "500ms"
52
+ ...Object.fromEntries(
53
+ Object.entries(upstreamTheme.transitionDuration).map(([name, value]) => [
54
+ name,
55
+ boundToMotionVar(name, value)
56
+ ])
57
+ ),
58
+ ...Object.fromEntries(
59
+ Object.entries(NUMERIC_DURATION_ALIASES).map(([step, name]) => [
60
+ step,
61
+ boundToMotionVar(name, upstreamDuration(name))
62
+ ])
63
+ )
38
64
  },
39
65
  zIndex: { ...upstreamTheme.zIndex, 10: "10", 20: "20", 50: "50" },
40
66
  boxShadow: { ...upstreamTheme.boxShadow, none: "none" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessly/ui",
3
- "version": "4.1.1",
3
+ "version": "4.2.0",
4
4
  "description": "Lessly design system — shared UI primitives, tokens, and theme",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.30.3",
@@ -73,7 +73,7 @@
73
73
  }
74
74
  },
75
75
  "dependencies": {
76
- "@lessly/tokens": "^0.7.0",
76
+ "@lessly/tokens": "^0.9.0",
77
77
  "@radix-ui/react-accordion": "^1.2.16",
78
78
  "@radix-ui/react-alert-dialog": "^1.1.19",
79
79
  "@radix-ui/react-aspect-ratio": "^1.1.11",