@platned/ui 0.3.0 → 0.6.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sidebar/index.tsx","../src/components/avatar/avatar.tsx","../src/components/chrome/nav-item.tsx","../src/sidebar/nav-tabs.tsx"],"sourcesContent":["import { type ReactNode, useEffect, useRef, useState } from 'react';\nimport { NavLink } from 'react-router-dom';\nimport clsx from 'clsx';\nimport {\n LuBell,\n LuBuilding,\n LuChevronLeft,\n LuChevronRight,\n LuClipboardList,\n LuEllipsisVertical,\n LuFileText,\n LuGlobe,\n LuLayers,\n LuLayoutGrid,\n LuLogOut,\n LuRss,\n LuServer,\n LuSettings,\n LuUsers,\n} from 'react-icons/lu';\n\nimport { Avatar, NavItem } from '../index';\n\n/** Every primary nav glyph renders at 16px. */\nconst NAV_ICON = 'size-4';\n\n// ── Logo ────────────────────────────────────────────────────────────────────\n\nfunction SidebarLogo({ collapsed }: { collapsed: boolean }) {\n return (\n <div\n className={clsx(\n 'flex shrink-0 items-center overflow-hidden p-4',\n collapsed ? 'justify-center' : 'gap-2.5',\n )}\n >\n <div className=\"flex shrink-0 size-10 items-center justify-center rounded-xl bg-brand-hover overflow-hidden\">\n <div className=\"grid grid-cols-2 gap-[3px]\">\n <div className=\"size-2 rounded-[2px] bg-white\" />\n <div className=\"size-2 rounded-[2px] bg-white/50\" />\n <div className=\"size-2 rounded-[2px] bg-white/50\" />\n <div className=\"size-2 rounded-[2px] bg-white\" />\n </div>\n </div>\n {!collapsed && (\n <span className=\"text-heading font-semibold text-default tracking-tight whitespace-nowrap\">\n SCIC\n </span>\n )}\n </div>\n );\n}\n\n// ── NavLink item ─────────────────────────────────────────────────────────────\n\ninterface NavLinkItemProps {\n to: string;\n icon: ReactNode;\n label: string;\n indicator?: boolean;\n collapsed: boolean;\n end?: boolean;\n}\n\nfunction NavLinkItem({ to, icon, label, indicator, collapsed, end }: NavLinkItemProps) {\n return (\n <NavLink to={to} end={end} className=\"block w-full no-underline\">\n {({ isActive }) => (\n <NavItem\n icon={icon}\n label={label}\n isActive={isActive}\n indicator={indicator}\n collapsed={collapsed}\n />\n )}\n </NavLink>\n );\n}\n\n// ── User card ────────────────────────────────────────────────────────────────\n\nfunction UserCard({ collapsed, name, role, onLogout }: { collapsed: boolean; name: string; role: string; onLogout: () => void }) {\n const [menuOpen, setMenuOpen] = useState(false);\n const menuRef = useRef<HTMLDivElement>(null);\n const initial = name.trim().charAt(0);\n\n useEffect(() => {\n if (!menuOpen) return;\n function handleClickOutside(e: MouseEvent) {\n if (menuRef.current && !menuRef.current.contains(e.target as Node)) {\n setMenuOpen(false);\n }\n }\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, [menuOpen]);\n\n const menuItems = (\n <button\n type=\"button\"\n onClick={() => { setMenuOpen(false); onLogout(); }}\n className=\"flex w-full items-center gap-2 px-3 py-2 text-body-sm text-default hover:bg-default-hover transition-colors\"\n >\n <LuLogOut className=\"size-3.5\" aria-hidden />\n Sign out\n </button>\n );\n\n return (\n <div\n className={clsx(\n 'flex shrink-0 items-center p-4',\n collapsed ? 'justify-center' : 'gap-2.5',\n )}\n >\n {collapsed ? (\n <div className=\"relative\" ref={menuRef}>\n <button\n type=\"button\"\n aria-label=\"User options\"\n aria-haspopup=\"menu\"\n aria-expanded={menuOpen}\n onClick={() => setMenuOpen(v => !v)}\n className=\"rounded-full\"\n >\n <Avatar initial={initial} size=\"md\" shape=\"circle\" />\n </button>\n {menuOpen && (\n <div className=\"absolute bottom-0 left-full ml-2 w-36 rounded-lg border border-default bg-default shadow-md py-1 z-20\">\n {menuItems}\n </div>\n )}\n </div>\n ) : (\n <>\n <Avatar initial={initial} size=\"md\" shape=\"circle\" />\n <div className=\"flex flex-1 min-w-0 flex-col gap-0.5\">\n <span className=\"truncate text-body-sm font-semibold text-default leading-snug\">\n {name}\n </span>\n <span className=\"truncate text-body-tiny font-regular text-default-secondary leading-none\">\n {role}\n </span>\n </div>\n <div className=\"relative shrink-0\" ref={menuRef}>\n <button\n type=\"button\"\n aria-label=\"User options\"\n aria-haspopup=\"menu\"\n aria-expanded={menuOpen}\n onClick={() => setMenuOpen(v => !v)}\n className=\"flex size-6 items-center justify-center rounded-md text-default-secondary hover:bg-default-hover hover:text-default transition-colors\"\n >\n <LuEllipsisVertical className=\"size-4\" aria-hidden />\n </button>\n {menuOpen && (\n <div className=\"absolute bottom-full right-0 mb-1 w-36 rounded-lg border border-default bg-default shadow-md py-1 z-20\">\n {menuItems}\n </div>\n )}\n </div>\n </>\n )}\n </div>\n );\n}\n\n// ── Main sidebar ─────────────────────────────────────────────────────────────\n\ninterface AppSidebarProps {\n collapsed: boolean;\n onToggle: () => void;\n isAdmin?: boolean;\n userName: string;\n userRole: string;\n onLogout: () => void;\n}\n\nexport function AppSidebar({ collapsed, onToggle, isAdmin = false, userName, userRole, onLogout }: AppSidebarProps) {\n\n return (\n <aside\n className={clsx(\n 'relative flex flex-col shrink-0 transition-all duration-200',\n 'bg-default-secondary border-r border-default rounded-xl',\n 'h-full',\n collapsed ? 'w-20' : 'w-80',\n )}\n >\n {/* Toggle button */}\n <button\n type=\"button\"\n aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}\n onClick={onToggle}\n className={clsx(\n 'absolute -right-3 top-5 z-10 flex size-6 items-center justify-center rounded-full',\n 'bg-default border border-default text-default-secondary',\n 'hover:bg-default-hover shadow-sm transition-colors',\n )}\n >\n {collapsed ? (\n <LuChevronRight className=\"size-3\" strokeWidth={2.5} aria-hidden />\n ) : (\n <LuChevronLeft className=\"size-3\" strokeWidth={2.5} aria-hidden />\n )}\n </button>\n\n {/* Top — logo + primary nav */}\n <div className=\"flex flex-1 min-h-0 flex-col gap-1\">\n <SidebarLogo collapsed={collapsed} />\n\n <nav className=\"flex flex-col gap-3 overflow-y-auto px-4 pt-2 pb-3\">\n <NavLinkItem to=\"/dashboard\" icon={<LuLayoutGrid className={NAV_ICON} aria-hidden />} label=\"Dashboard\" collapsed={collapsed} end />\n <NavLinkItem to=\"/entities\" icon={<LuLayers className={NAV_ICON} aria-hidden />} label=\"Entities\" collapsed={collapsed} indicator />\n <NavLinkItem to=\"/suppliers\" icon={<LuBuilding className={NAV_ICON} aria-hidden />} label=\"Suppliers\" collapsed={collapsed} />\n <NavLinkItem to=\"/customers\" icon={<LuUsers className={NAV_ICON} aria-hidden />} label=\"Customers\" collapsed={collapsed} />\n <NavLinkItem to=\"/sources\" icon={<LuFileText className={NAV_ICON} aria-hidden />} label=\"Sources\" collapsed={collapsed} />\n <NavLinkItem to=\"/articles\" icon={<LuRss className={NAV_ICON} aria-hidden />} label=\"Intelligence\" collapsed={collapsed} />\n <NavLinkItem to=\"/alerts\" icon={<LuBell className={NAV_ICON} aria-hidden />} label=\"Alerts\" collapsed={collapsed} />\n <NavLinkItem to=\"/website-scraper\" icon={<LuGlobe className={NAV_ICON} aria-hidden />} label=\"Website Intel\" collapsed={collapsed} />\n <NavLinkItem to=\"/review\" icon={<LuClipboardList className={NAV_ICON} aria-hidden />} label=\"Review queue\" collapsed={collapsed} />\n {isAdmin && (\n <NavLinkItem to=\"/queues\" icon={<LuServer className={NAV_ICON} aria-hidden />} label=\"Queues\" collapsed={collapsed} />\n )}\n </nav>\n </div>\n\n {/* Bottom — settings + user card */}\n <div className=\"flex flex-col\">\n {isAdmin && (\n <div className=\"px-4 pb-2\">\n <NavLinkItem\n to=\"/settings\"\n icon={<LuSettings className={NAV_ICON} aria-hidden />}\n label=\"Settings\"\n collapsed={collapsed}\n />\n </div>\n )}\n <div className=\"border-t border-default\">\n <UserCard collapsed={collapsed} name={userName} role={userRole} onLogout={onLogout} />\n </div>\n </div>\n </aside>\n );\n}\n\n// ── Route-driven navigation ────────────────────────────────────────────────────\n// Lives on this entry point (not the main one) because it needs react-router-dom.\n\nexport { NavTabs } from './nav-tabs';\nexport type { NavTabsProps, NavTabItem } from './nav-tabs';\n","import clsx from 'clsx';\n\nexport type AvatarSize = 'sm' | 'md' | 'lg';\nexport type AvatarShape = 'circle' | 'square';\n\nexport interface AvatarInitialProps {\n type?: 'initial';\n /** Single character — Figma spec: one letter only to avoid overflow */\n initial: string;\n size?: AvatarSize;\n shape?: AvatarShape;\n className?: string;\n}\n\nexport interface AvatarImageProps {\n type: 'image';\n src: string;\n alt?: string;\n size?: AvatarSize;\n shape?: AvatarShape;\n className?: string;\n}\n\nexport type AvatarProps = AvatarInitialProps | AvatarImageProps;\n\n// Figma sizes: lg=40px, md=32px, sm=24px\nconst sizeStyles: Record<AvatarSize, string> = {\n lg: 'size-10',\n md: 'size-8',\n sm: 'size-6',\n};\n\n// Figma text sizes for initials: body-medium at lg, scales down proportionally\nconst initialTextStyles: Record<AvatarSize, string> = {\n lg: 'text-body font-semibold',\n md: 'text-body-xs font-semibold',\n sm: 'text-body-tiny font-semibold',\n};\n\n// Figma shape radii: circle=full, square: lg/md=8px (rounded-lg), sm=4px (rounded)\nfunction getShapeClass(shape: AvatarShape, size: AvatarSize): string {\n if (shape === 'circle') return 'rounded-full';\n return size === 'sm' ? 'rounded' : 'rounded-lg';\n}\n\nexport function Avatar(props: AvatarProps) {\n const { size = 'lg', shape = 'circle', className } = props;\n\n const base = clsx(\n 'shrink-0 overflow-hidden',\n sizeStyles[size],\n getShapeClass(shape, size),\n className,\n );\n\n if (props.type === 'image') {\n return (\n <div className={base}>\n <img\n src={props.src}\n alt={props.alt ?? ''}\n className=\"size-full object-cover\"\n />\n </div>\n );\n }\n\n return (\n <div\n className={clsx(base, 'flex items-center justify-center')}\n style={{\n background:\n 'linear-gradient(135deg, var(--color-graphic-blue) 30%, var(--color-graphic-red) 100%)',\n }}\n >\n <span\n className={clsx(\n 'select-none leading-none text-brand-on-brand',\n initialTextStyles[size],\n )}\n >\n {props.initial.charAt(0).toUpperCase()}\n </span>\n </div>\n );\n}\n","import { type ReactNode } from 'react';\nimport clsx from 'clsx';\n\nexport type NavItemSize = 'sm' | 'md';\n\nexport interface NavItemProps {\n icon?: ReactNode;\n label: string;\n isActive?: boolean;\n /** Red indicator dot at top-right corner (e.g. unread count) */\n indicator?: boolean;\n /** Collapsed mode — icon only, label hidden */\n collapsed?: boolean;\n /** Row height. `sm` suits a dense secondary nav or a nested group. */\n size?: NavItemSize;\n className?: string;\n}\n\nconst SIZE: Record<NavItemSize, { box: string; label: string }> = {\n sm: { box: 'h-9 gap-2 rounded-lg px-2.5 py-1.5', label: 'text-body-xs' },\n md: { box: 'h-11 gap-3 rounded-xl px-3.5 py-2.5', label: 'text-body-sm' },\n};\n\nexport function NavItem({\n icon,\n label,\n isActive = false,\n indicator = false,\n collapsed = false,\n size = 'md',\n className,\n}: NavItemProps) {\n const s = SIZE[size];\n return (\n <span\n className={clsx(\n 'relative flex w-full items-center transition-colors',\n s.box,\n collapsed && 'justify-center',\n isActive\n ? 'bg-brand text-brand-on-brand'\n : [\n 'border border-default bg-default text-default-secondary',\n 'hover:bg-brand-tertiary-hover hover:border-brand-tertiary hover:text-brand',\n ],\n className,\n )}\n >\n {icon !== undefined && (\n <span className=\"shrink-0 [&_svg]:size-4\">{icon}</span>\n )}\n {!collapsed && (\n <span className={clsx('min-w-0 flex-1 truncate font-regular leading-snug', s.label)}>\n {label}\n </span>\n )}\n {indicator && (\n <span className=\"absolute -right-px -top-px size-2 rounded-full bg-danger\" />\n )}\n </span>\n );\n}\n","import { NavLink } from 'react-router-dom';\nimport clsx from 'clsx';\n\nexport interface NavTabItem {\n to: string;\n label: string;\n /** Match this route exactly — needed for an index route like `/account`. */\n end?: boolean;\n}\n\nexport interface NavTabsProps {\n items: NavTabItem[];\n className?: string;\n}\n\n/** Route-driven tab strip, backed by react-router's `NavLink`.\n *\n * Use this when each tab is its own route. For state-driven tabs that do not change\n * the URL, use `Tabs` from the main entry point instead — it has no router dependency.\n *\n * The active underline and label use `brand-emphasis` (#0C2D58), the brand fill colour\n * Figma specifies for a tab rule, rather than the lighter `brand` border token. */\nexport function NavTabs({ items, className }: NavTabsProps) {\n return (\n <div className={clsx('flex gap-6 overflow-x-auto border-b border-default', className)}>\n {items.map((tab) => (\n <NavLink\n key={tab.to}\n to={tab.to}\n end={tab.end}\n className={({ isActive }) =>\n clsx(\n 'shrink-0 whitespace-nowrap border-b-2 pb-3 text-body-sm transition-colors',\n isActive\n ? 'border-brand-emphasis font-semibold text-brand-emphasis'\n : 'border-transparent font-medium text-default-secondary hover:text-default',\n )\n }\n >\n {tab.label}\n </NavLink>\n ))}\n </div>\n );\n}\n"],"mappings":"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","names":["import_react_router_dom","import_clsx","clsx","import_clsx","import_jsx_runtime","clsx","import_clsx","import_jsx_runtime","clsx","import_jsx_runtime","clsx"]}
1
+ {"version":3,"sources":["../src/sidebar/index.tsx","../src/lib/slot.tsx","../src/components/forms/control-size.tsx","../src/components/forms/icon-button.tsx","../src/components/avatar/avatar.tsx","../src/lib/cn.ts","../src/lib/use-controllable-state.ts","../src/lib/use-roving-focus.ts","../src/components/overlays/floating.tsx","../src/components/overlays/menu-styles.ts","../src/lib/scrollbar.ts","../src/components/overlays/overlay.ts","../src/components/overlays/overlay-stack.ts","../src/components/overlays/portal.tsx","../src/components/overlays/tooltip.tsx","../src/components/overlays/menu-core.tsx","../src/components/cards/scroll-area.tsx","../src/components/overlays/dropdown-menu.tsx","../src/components/chrome/nav-item.tsx","../src/sidebar/nav-tabs.tsx"],"sourcesContent":["import { type HTMLAttributes, type ReactNode } from 'react';\nimport { NavLink } from 'react-router-dom';\nimport { LuChevronLeft, LuChevronRight, LuEllipsisVertical, LuLogOut } from 'react-icons/lu';\n\nimport {\n Avatar,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n NavItem,\n ScrollArea,\n Tooltip,\n cn,\n} from '../index';\nimport type { NavItemSize, NavItemVariant } from '../index';\n\nexport interface SidebarNavItem {\n to: string;\n label: string;\n /** Optional — a collapsed row without one falls back to a monogram of its label. */\n icon?: ReactNode;\n /** Red dot on the row — something new behind this destination. */\n indicator?: boolean;\n /** Match the path exactly rather than as a prefix. Needed for an index route. */\n end?: boolean;\n}\n\nexport interface SidebarNavGroup {\n /**\n * Uppercase heading above the group. Omit for an ungrouped run of rows.\n *\n * Kept in the accessibility tree when the rail is collapsed rather than dropped: it is the\n * only thing saying what the run of icons below it has in common.\n */\n title?: string;\n items: SidebarNavItem[];\n}\n\nexport interface AppSidebarProps {\n /** The scrolling nav. One untitled group is a flat list. */\n groups: SidebarNavGroup[];\n /** Rows pinned below the scrolling nav — settings, help, a sign-out link. */\n footerNav?: SidebarNavItem[];\n /**\n * Brand lockup. Given a function it receives `collapsed`, so the caller can swap a full\n * lockup for a mark alone rather than having one squeezed into an 80px rail.\n */\n brand?: ReactNode | ((state: { collapsed: boolean }) => ReactNode);\n /** Bottom slot, below a divider — a user card, a version pill, nothing. */\n footer?: ReactNode;\n /**\n * Whether the rail can collapse at all.\n *\n * `false` locks it open: no toggle button, and `collapsed` is ignored rather than half\n * honoured. A collapsed rail is 80px of icons, so it only earns its place in a nav short\n * enough and iconographic enough to survive the loss of its labels — a long text nav\n * (the docs site's own, for one) is better off never offering it.\n */\n collapsible?: boolean;\n collapsed?: boolean;\n /** Omit and the collapse toggle is not rendered, though `collapsed` still applies. */\n onToggle?: () => void;\n size?: NavItemSize;\n variant?: NavItemVariant;\n /** Expanded width, as a Tailwind class. */\n width?: string;\n collapsedWidth?: string;\n className?: string;\n}\n\n/**\n * The application nav rail: brand, a grouped list of destinations, and a footer.\n *\n * Lives on its own entry point (`@platned/ui/sidebar`) because it uses `NavLink`, and\n * `react-router-dom` is an optional peer dependency.\n *\n * Through 0.4.0 this was not a library component at all. It hardcoded ten product routes,\n * their labels and their icons, an `SCIC` wordmark, and an `isAdmin` flag gating two of the\n * rows — so the only app it could render was the one it was extracted from, and the docs\n * site kept a second, unrelated sidebar of its own rather than use it. Everything\n * app-specific is now a prop or a slot, and the docs site runs on this component.\n *\n * Width is an explicit prop rather than something `className` overrides: the width is\n * conditional on `collapsed`, so a `className=\"w-64\"` would tailwind-merge away the\n * collapsed width too and break the collapse. Everything else about the shell is fair game\n * for `className` — `rounded-none` to flatten it for a full-bleed layout, for instance.\n */\nexport function AppSidebar({\n groups,\n footerNav,\n brand,\n footer,\n collapsible = true,\n collapsed: collapsedProp = false,\n onToggle,\n size = 'md',\n variant = 'card',\n width = 'w-80',\n collapsedWidth = 'w-20',\n className,\n}: AppSidebarProps) {\n // One switch turns the whole behaviour off, rather than leaving a caller to remember that\n // `collapsible={false}` also means never passing `collapsed`.\n const collapsed = collapsible && collapsedProp;\n\n return (\n <aside\n className={cn(\n 'relative flex h-full shrink-0 flex-col py-2 transition-all duration-200',\n 'rounded-xl border-r border-default bg-default-secondary',\n collapsed ? collapsedWidth : width,\n className,\n )}\n >\n {collapsible && onToggle && (\n <button\n type=\"button\"\n aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}\n aria-expanded={!collapsed}\n onClick={onToggle}\n className={cn(\n 'absolute -right-3 top-5 z-raised flex size-6 items-center justify-center rounded-full',\n 'border border-default bg-default text-default-secondary',\n // Not `hover:bg-default-hover` — that is #F5F5F5, exactly the rail colour, and\n // this button straddles the rail edge, so half of it dissolved into the rail on\n // hover. Same hover vocabulary as the rows instead.\n 'shadow-100 transition-colors hover:bg-brand-tertiary-hover hover:text-brand',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-1 focus-visible:ring-offset-default',\n )}\n >\n {collapsed ? (\n <LuChevronRight className=\"size-3\" strokeWidth={2.5} aria-hidden />\n ) : (\n <LuChevronLeft className=\"size-3\" strokeWidth={2.5} aria-hidden />\n )}\n </button>\n )}\n\n {brand && (\n <div\n className={cn(\n 'flex shrink-0 items-center overflow-hidden p-4',\n collapsed ? 'justify-center' : 'gap-2.5',\n )}\n >\n {typeof brand === 'function' ? brand({ collapsed }) : brand}\n </div>\n )}\n\n <ScrollArea axis=\"vertical\" label=\"Sidebar navigation\" className=\"min-h-0 flex-1\">\n <nav className=\"flex flex-col gap-4 px-4 pb-3 pt-2\">\n {groups.map((group, groupIndex) => (\n <SidebarGroup\n key={group.title ?? `group-${groupIndex}`}\n group={group}\n // A rule above every group but the first, expanded or collapsed. In the rail\n // it is also standing in for the heading, which has no room at 80px and goes\n // screen-reader-only — so the grouping survives for both kinds of reader.\n showSeparator={groupIndex > 0}\n collapsed={collapsed}\n size={size}\n variant={variant}\n />\n ))}\n </nav>\n </ScrollArea>\n\n {(footerNav?.length || footer) && (\n <div className=\"flex shrink-0 flex-col\">\n {footerNav && footerNav.length > 0 && (\n <nav className=\"flex flex-col gap-3 px-4 pt-2\">\n {footerNav.map((item) => (\n <SidebarLink\n key={item.to}\n item={item}\n collapsed={collapsed}\n size={size}\n variant={variant}\n />\n ))}\n </nav>\n )}\n {/* No rule above the footer — the design separates it by whitespace alone. */}\n {footer}\n </div>\n )}\n </aside>\n );\n}\n\nfunction SidebarGroup({\n group,\n showSeparator,\n collapsed,\n size,\n variant,\n}: {\n group: SidebarNavGroup;\n showSeparator: boolean;\n collapsed: boolean;\n size: NavItemSize;\n variant: NavItemVariant;\n}) {\n return (\n // 12px between rows, per the design — the rows read as separate pills rather than a\n // stack. Through 0.5.0 this was `gap-1`, which packed them into a near-continuous block.\n // The same gap spaces the separator and the heading, so neither needs a margin of its own.\n <div className=\"flex flex-col gap-3\">\n {showSeparator && <SidebarSeparator />}\n {group.title && (\n <p\n className={cn(\n 'text-body-tiny font-semibold uppercase tracking-wider text-neutral-tertiary',\n // Flush with the rows rather than inset a further 12px — the heading lines up\n // with the left edge of the pills below it, not with their labels.\n // `sr-only` is out of flow, so a collapsed group's heading costs no gap.\n collapsed ? 'sr-only' : 'px-0',\n )}\n >\n {group.title}\n </p>\n )}\n {group.items.map((item) => (\n <SidebarLink\n key={item.to}\n item={item}\n collapsed={collapsed}\n size={size}\n variant={variant}\n />\n ))}\n </div>\n );\n}\n\nfunction SidebarLink({\n item,\n collapsed,\n size,\n variant,\n}: {\n item: SidebarNavItem;\n collapsed: boolean;\n size: NavItemSize;\n variant: NavItemVariant;\n}) {\n const link = (\n <NavLink\n to={item.to}\n end={item.end}\n // A collapsed row is a 44px square but the link still fills the 48px track, so it\n // needs to centre its child — otherwise the square sits 2px left of centre in the rail.\n className={cn('block w-full no-underline', collapsed && 'flex justify-center')}\n >\n {({ isActive }) => (\n <NavItem\n icon={item.icon}\n label={item.label}\n isActive={isActive}\n indicator={item.indicator}\n collapsed={collapsed}\n size={size}\n variant={variant}\n />\n )}\n </NavLink>\n );\n\n // Only when collapsed. Expanded, the label is already on screen and a tooltip repeating it\n // is noise — and mounting one per row would cost the expanded rail a portal it never shows.\n //\n // It wraps the `NavLink`, not the `NavItem`: `NavItem` renders a non-focusable `<span>`, so\n // anchoring to it would give a tooltip that opens on hover and is unreachable by keyboard —\n // the exact failure `Tooltip` documents itself as existing to prevent. The default\n // `describedby` association is right here; `labelledby` would replace the row's existing\n // `sr-only` name with a duplicate of itself and keep every row's portal permanently mounted.\n if (!collapsed) return link;\n\n return (\n <Tooltip title={item.label} side=\"right\">\n {link}\n </Tooltip>\n );\n}\n\n// ── SidebarSeparator ───────────────────────────────────────────────────────────\n\nexport type SidebarSeparatorProps = HTMLAttributes<HTMLDivElement>;\n\n/**\n * The rule between nav groups. `AppSidebar` draws one above every group after the first;\n * this is exported so the same line can be used in the `footer` slot or in a hand-composed\n * rail without anyone re-deriving `h-px bg-divider` and inevitably picking a different inset.\n *\n * Decorative by default. The grouping it marks is already carried semantically by each\n * group's heading — which stays in the accessibility tree even when the rail is collapsed and\n * the heading is visually hidden — so announcing the rule as well is redundant. Pass\n * `role=\"separator\"` through if you are using it somewhere that has no heading to lean on.\n */\nexport function SidebarSeparator({ className, ...rest }: SidebarSeparatorProps) {\n return <div aria-hidden className={cn('mx-1 h-px shrink-0 bg-divider', className)} {...rest} />;\n}\n\n// ── SidebarUserCard ────────────────────────────────────────────────────────────\n\nexport interface SidebarUserCardProps {\n name: string;\n role: string;\n onLogout: () => void;\n collapsed?: boolean;\n /** Extra menu entries above Sign out. */\n menu?: ReactNode;\n className?: string;\n}\n\n/**\n * The signed-in user block, for `AppSidebar`'s `footer` slot.\n *\n * Was private, and its props were on `AppSidebar` itself — which meant every consumer of the\n * sidebar had to have a user, a role and a logout handler whether it had a concept of any\n * of them or not. The docs site has none, and passes no footer.\n *\n * Its menu is `DropdownMenu` rather than the hand-rolled click-outside popover it was: that\n * one had no keyboard contract and, being absolutely positioned, was clipped by the\n * collapsed rail. `DropdownMenu` portals.\n */\nexport function SidebarUserCard({\n name,\n role,\n onLogout,\n collapsed = false,\n menu,\n className,\n}: SidebarUserCardProps) {\n const initial = name.trim().charAt(0);\n\n const items = (\n <>\n {menu}\n <DropdownMenuItem\n icon={<LuLogOut className=\"size-3.5\" aria-hidden />}\n onSelect={onLogout}\n >\n Sign out\n </DropdownMenuItem>\n </>\n );\n\n if (collapsed) {\n return (\n <div className={cn('flex shrink-0 items-center justify-center p-4', className)}>\n <DropdownMenu>\n <DropdownMenuTrigger asChild label=\"User options\">\n <button type=\"button\" aria-label={`${name} — user options`} className=\"rounded-full\">\n <Avatar initial={initial} size=\"lg\" shape=\"circle\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent side=\"right\" align=\"end\">\n {items}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n );\n }\n\n return (\n <div className={cn('flex shrink-0 items-center gap-2.5 p-4', className)}>\n <Avatar initial={initial} size=\"lg\" shape=\"circle\" />\n <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"truncate text-body-sm font-semibold leading-snug text-default\">\n {name}\n </span>\n <span className=\"truncate text-body-tiny font-regular leading-none text-default-secondary\">\n {role}\n </span>\n </div>\n <DropdownMenu>\n <DropdownMenuTrigger asChild label=\"User options\">\n <button\n type=\"button\"\n aria-label={`${name} — user options`}\n // `hover:bg-default-hover` here was #F5F5F5 on a #F5F5F5 rail — no hover at all.\n className=\"flex size-6 shrink-0 items-center justify-center rounded-md text-default-secondary transition-colors hover:bg-brand-tertiary-hover hover:text-brand\"\n >\n <LuEllipsisVertical className=\"size-5\" aria-hidden />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent side=\"top\" align=\"end\">\n {items}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n );\n}\n\n// ── Route-driven navigation ────────────────────────────────────────────────────\n// Lives on this entry point (not the main one) because it needs react-router-dom.\n\nexport { NavTabs } from './nav-tabs';\nexport type { NavTabsProps, NavTabItem } from './nav-tabs';\n","import {\n cloneElement,\n forwardRef,\n isValidElement,\n type CSSProperties,\n type HTMLAttributes,\n type ReactElement,\n type ReactNode,\n type Ref,\n} from 'react';\nimport clsx from 'clsx';\n\n/**\n * `Slot` renders its own props onto its single child element instead of emitting a\n * wrapper of its own. It is what makes `asChild` work.\n *\n * Why the library needs it: before 0.4.0 there was no way to render a `Button` as a\n * router `<Link>`, or to use any component as a tooltip/popover anchor, because every\n * component hardcoded its host element. The usual workaround — a `component`/`as` prop —\n * multiplies generics across the whole surface and still cannot forward a ref through\n * two levels. Delegating to the child instead keeps the type surface flat.\n *\n * <Button asChild><Link to=\"/x\">Go</Link></Button>\n *\n * Merge rules, in order of who wins:\n * - `className` — concatenated, slot first, so the child's classes come last\n * - `style` — shallow-merged, child wins on conflicting keys\n * - `on*` — both run, slot's handler first; the child's runs even if the slot's\n * called `preventDefault`, matching normal DOM listener behaviour\n * - everything else — child wins, so a child's explicit `type` or `role` is never\n * clobbered by a default the slot happens to carry\n */\nexport interface SlotProps extends HTMLAttributes<HTMLElement> {\n /** The single element to merge into. */\n children?: ReactNode;\n /**\n * Rebuilds the child's children, so a component that decorates its content can keep\n * doing so through `asChild`.\n *\n * `Button` renders `leadingIcon`, a padded label span and `trailingIcon` around whatever\n * it is given. Without this, `<Button asChild><Link>Go</Link></Button>` would either drop\n * the icons (cloning the Link untouched) or drop \"Go\" (replacing the Link's children\n * outright). It receives the child's own children — \"Go\" — and returns the decorated\n * tree to place inside the child.\n */\n wrapChildren?: (childChildren: ReactNode) => ReactNode;\n}\n\nexport const Slot = forwardRef<HTMLElement, SlotProps>(function Slot(\n { children, wrapChildren, ...slotProps },\n forwardedRef,\n) {\n // A bare string, an array, or nothing at all — there is no element to merge into.\n // Rendering the children plain would silently drop every prop the slot carries\n // (including the ref and the ARIA wiring a trigger depends on), so this renders\n // nothing rather than something subtly broken.\n if (!isValidElement(children)) return null;\n\n const child = children as ReactElement<{ children?: ReactNode }>;\n\n const merged = {\n ...mergeProps(slotProps, child.props as Record<string, unknown>),\n ref: composeRefs(forwardedRef, getElementRef(child)),\n } as Record<string, unknown>;\n\n return wrapChildren\n ? cloneElement(child, merged, wrapChildren(child.props.children))\n : cloneElement(child, merged);\n});\n\nfunction mergeProps(\n slotProps: Record<string, unknown>,\n childProps: Record<string, unknown>,\n): Record<string, unknown> {\n const merged: Record<string, unknown> = { ...slotProps };\n\n for (const key in childProps) {\n const slotValue = slotProps[key];\n const childValue = childProps[key];\n\n if (/^on[A-Z]/.test(key)) {\n // Both handlers run. The slot's fires first so a component can set up state\n // before the consumer's own handler observes it.\n if (typeof slotValue === 'function' && typeof childValue === 'function') {\n merged[key] = (...args: unknown[]) => {\n (slotValue as (...a: unknown[]) => void)(...args);\n (childValue as (...a: unknown[]) => void)(...args);\n };\n } else {\n merged[key] = childValue ?? slotValue;\n }\n } else if (key === 'className') {\n merged[key] = clsx(slotValue as string, childValue as string);\n } else if (key === 'style') {\n merged[key] = { ...(slotValue as CSSProperties), ...(childValue as CSSProperties) };\n } else {\n merged[key] = childValue;\n }\n }\n\n return merged;\n}\n\n/**\n * React 19 exposes a `ref` on `props`; React 18 keeps it on the element. The peer range\n * is `>=18`, so both have to be read.\n */\nfunction getElementRef(element: ReactElement): Ref<unknown> | undefined {\n const asRecord = element as unknown as {\n ref?: Ref<unknown>;\n props?: { ref?: Ref<unknown> };\n };\n return asRecord.props?.ref ?? asRecord.ref;\n}\n\n/** Fan one node out to several refs, callback or object. */\nexport function composeRefs<T>(...refs: (Ref<T> | undefined)[]) {\n return (node: T | null) => {\n for (const ref of refs) {\n if (!ref) continue;\n if (typeof ref === 'function') {\n ref(node);\n } else {\n (ref as { current: T | null }).current = node;\n }\n }\n };\n}\n","import { createContext, useContext, type ReactNode } from 'react';\nimport type { InputSize } from './input';\n\n/**\n * A default `size` for the form controls inside a container.\n *\n * `FilterBar size=\"sm\"` used to set only the gap between its children, so its own \"Sizes\"\n * example put a 24px Input beside a 36px Select and nothing in the API said the caller had\n * to size each control by hand. A container that has a size prop should mean it.\n *\n * Deliberately narrow: the context carries an `InputSize` and nothing else. It is not a\n * general \"component config\" channel — that is where implicit inheritance stops being\n * predictable. And an explicit `size` prop always wins, so the escape hatch is one prop on\n * the one control that needs to differ.\n *\n * `cloneElement` would have been the alternative and cannot work here: it never reaches a\n * control wrapped for layout (`<div className=\"w-56\"><Input /></div>`) or one nested inside\n * a consumer's own composite, and it would inject `size` into whatever element happens to\n * be the child — including a `<span>`, which React then warns about. Context is also how\n * the library already solves this shape, in `FieldContext`, `RadioGroupContext` and\n * `ToggleGroupContext`.\n */\nconst ControlSizeContext = createContext<InputSize | null>(null);\n\nexport interface ControlSizeProviderProps {\n size: InputSize;\n children: ReactNode;\n}\n\nexport function ControlSizeProvider({ size, children }: ControlSizeProviderProps) {\n return <ControlSizeContext.Provider value={size}>{children}</ControlSizeContext.Provider>;\n}\n\n/**\n * Resolve a control's size: an explicit prop, else the enclosing container's default,\n * else `md`.\n *\n * Call it unconditionally at the top of the component — it is a hook.\n */\nexport function useControlSize(explicit?: InputSize): InputSize {\n const inherited = useContext(ControlSizeContext);\n return explicit ?? inherited ?? 'md';\n}\n","import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react';\nimport clsx from 'clsx';\nimport { Slot } from '../../lib/slot';\nimport { type ButtonSize } from './button';\nimport { useControlSize } from './control-size';\n\nexport type IconButtonVariant = 'primary' | 'neutral' | 'subtle';\n\nexport interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: IconButtonVariant;\n size?: ButtonSize;\n /** Required for accessibility — no visible text label */\n 'aria-label': string;\n /** Render the single child element instead of a `<button>` — e.g. a router `Link`. */\n asChild?: boolean;\n children?: ReactNode;\n}\n\nconst variantStyles: Record<IconButtonVariant, string> = {\n primary: clsx(\n 'bg-brand border border-brand text-brand-on-brand',\n 'hover:bg-brand-hover',\n 'disabled:bg-disabled disabled:border-disabled disabled:text-disabled-on-disabled',\n ),\n // Neutral uses secondary background (light gray) for visual weight without a text label\n neutral: clsx(\n 'bg-default-secondary border border-default text-default',\n // icon turns brand blue on hover (Figma spec)\n 'hover:bg-default-secondary-hover hover:text-brand',\n 'disabled:bg-disabled disabled:border-disabled disabled:text-disabled-on-disabled',\n ),\n subtle: clsx(\n 'bg-transparent text-neutral',\n // hover uses background-default-default-hover (#f5f5f5), icon turns brand blue (Figma spec)\n 'hover:bg-default-hover hover:text-brand',\n // disabled needs explicit border since there is no base border\n 'disabled:bg-disabled disabled:border disabled:border-disabled disabled:text-disabled-on-disabled',\n ),\n};\n\nconst sizeStyles: Record<ButtonSize, string> = {\n lg: 'size-12 rounded-2xl [&_svg]:size-5',\n md: 'size-9 rounded-lg [&_svg]:size-5',\n sm: 'size-6 rounded-full [&_svg]:size-4',\n};\n\nexport const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(function IconButton(\n { variant = 'primary', size: sizeProp, asChild = false, type, children, className, ...props },\n ref,\n) {\n const size = useControlSize(sizeProp);\n const rootProps = {\n className: clsx(\n 'inline-flex shrink-0 items-center justify-center transition-colors',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-1 focus-visible:ring-offset-default',\n 'disabled:cursor-not-allowed',\n variantStyles[variant],\n sizeStyles[size],\n className,\n ),\n ...props,\n };\n\n // No `wrapChildren` here: an icon button has no label span or icon slots to rebuild, so\n // the child's own content is already the whole thing.\n if (asChild) {\n return (\n <Slot ref={ref} {...rootProps}>\n {children}\n </Slot>\n );\n }\n\n return (\n <button\n ref={ref}\n // Without an explicit type, a button inside a <form> defaults to `submit` and an\n // icon button meant to reveal a password or open a menu submits the form instead.\n type={type ?? 'button'}\n {...rootProps}\n >\n {children}\n </button>\n );\n});\n","import clsx from 'clsx';\n\nexport type AvatarSize = 'sm' | 'md' | 'lg';\nexport type AvatarShape = 'circle' | 'square';\n\nexport interface AvatarInitialProps {\n type?: 'initial';\n /** Single character — Figma spec: one letter only to avoid overflow */\n initial: string;\n size?: AvatarSize;\n shape?: AvatarShape;\n className?: string;\n}\n\nexport interface AvatarImageProps {\n type: 'image';\n src: string;\n alt?: string;\n size?: AvatarSize;\n shape?: AvatarShape;\n className?: string;\n}\n\nexport type AvatarProps = AvatarInitialProps | AvatarImageProps;\n\n// Figma sizes: lg=40px, md=32px, sm=24px\nconst sizeStyles: Record<AvatarSize, string> = {\n lg: 'size-10',\n md: 'size-8',\n sm: 'size-6',\n};\n\n// Figma text sizes for initials: body-medium at lg, scales down proportionally\nconst initialTextStyles: Record<AvatarSize, string> = {\n lg: 'text-body font-semibold',\n md: 'text-body-xs font-semibold',\n sm: 'text-body-tiny font-semibold',\n};\n\n// Figma shape radii: circle=full, square: lg/md=8px (rounded-lg), sm=4px (rounded)\nfunction getShapeClass(shape: AvatarShape, size: AvatarSize): string {\n if (shape === 'circle') return 'rounded-full';\n return size === 'sm' ? 'rounded' : 'rounded-lg';\n}\n\nexport function Avatar(props: AvatarProps) {\n const { size = 'lg', shape = 'circle', className } = props;\n\n const base = clsx(\n 'shrink-0 overflow-hidden',\n sizeStyles[size],\n getShapeClass(shape, size),\n className,\n );\n\n if (props.type === 'image') {\n return (\n <div className={base}>\n <img\n src={props.src}\n alt={props.alt ?? ''}\n className=\"size-full object-cover\"\n />\n </div>\n );\n }\n\n return (\n <div\n className={clsx(base, 'flex items-center justify-center')}\n style={{\n background:\n 'linear-gradient(135deg, var(--color-graphic-blue) 30%, var(--color-graphic-red) 100%)',\n }}\n >\n <span\n className={clsx(\n 'select-none leading-none text-brand-on-brand',\n initialTextStyles[size],\n )}\n >\n {props.initial.charAt(0).toUpperCase()}\n </span>\n </div>\n );\n}\n","import { type ClassValue, clsx } from 'clsx';\nimport { extendTailwindMerge } from 'tailwind-merge';\n\n/**\n * Every `fontSize` key in `tailwind-preset.cjs`, in the same order.\n *\n * `tailwind-merge` decides whether a `text-*` class is a **font size** or a **text colour**\n * by pattern: it recognises t-shirt sizes (`text-sm`, `text-2xl`) and bare lengths, and files\n * everything else under colour. Every size in this design system is a word — `text-body-sm`,\n * `text-heading`, `text-scale-07` — so without this list twMerge calls them colours, and then\n * drops them when a real colour follows in the same string:\n *\n * cn('text-body-micro text-neutral-tertiary') // → 'text-neutral-tertiary'\n *\n * The size vanishes and the element inherits its parent's. That is not hypothetical: it is\n * why the sidebar's group headings rendered at the 16px body default for two releases while\n * the source said `text-body-tiny`, and why chasing the number in the source never moved it.\n *\n * Keep in sync with the preset — `cn.test.ts` fails if they drift.\n */\nconst FONT_SIZES = [\n 'body-micro',\n 'body-tiny',\n 'body-xs',\n 'body-sm',\n 'body',\n 'body-lg',\n 'heading-sm',\n 'heading',\n 'heading-lg',\n 'subheading-sm',\n 'subheading',\n 'subheading-lg',\n 'subtitle-sm',\n 'subtitle',\n 'subtitle-lg',\n 'title-sm',\n 'title',\n 'title-lg',\n 'title-hero',\n 'scale-01',\n 'scale-02',\n 'scale-03',\n 'scale-04',\n 'scale-05',\n 'scale-06',\n 'scale-07',\n 'scale-08',\n 'scale-09',\n 'scale-10',\n 'scale-12',\n] as const;\n\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [{ text: [...FONT_SIZES] }],\n },\n },\n});\n\n/**\n * `clsx` + `tailwind-merge`, taught this system's type scale.\n *\n * Use this instead of bare `clsx` in any component whose `className` prop is meant to\n * *override* a base utility — the composable `Card*` and `Modal*` parts, and anything\n * else built to be reshaped by its caller.\n *\n * Why it is necessary: `clsx` only concatenates. When two conflicting Tailwind\n * utilities both end up in the attribute, the winner is decided by *stylesheet order*,\n * not attribute order. Tailwind emits `flex-row` before `flex-col` and `gap-3` before\n * `gap-4`, so under plain `clsx`:\n *\n * <CardBody className=\"flex-row gap-3\" /> // base is 'flex flex-col gap-4 p-4'\n *\n * silently stays a column with a 4-unit gap. `twMerge` drops the losing class instead,\n * so the caller's intent wins.\n *\n * Everywhere else in the library `className` is purely additive, and plain `clsx`\n * remains correct — and cheaper. Do not sweep it out of those files.\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import { useCallback, useRef, useState } from 'react';\n\nexport interface UseControllableStateOptions<T> {\n /** The controlled value. Passing anything other than `undefined` takes control. */\n value?: T;\n /** Initial value when uncontrolled. */\n defaultValue: T;\n /** Fired on every change, controlled or not. */\n onChange?: (value: T) => void;\n}\n\n/**\n * One state value that works controlled or uncontrolled, the way every Radix and\n * shadcn/ui control does.\n *\n * Before 0.4.0 nothing in this library supported an uncontrolled mode — `Checkbox`,\n * `Toggle`, `Tabs`, `SegmentedControl`, `ButtonGroup` and `MultiSelectCard` all *required*\n * a value plus a change handler, so the simplest possible use site still needed a\n * `useState` in the consumer. `DropdownMenu` had the opposite problem: its open state was\n * entirely internal, so a caller could neither open it programmatically nor observe it.\n *\n * Control is decided per render by whether `value` is `undefined`. A component that\n * starts controlled and later passes `undefined` will fall back to its internal value —\n * the same caveat React itself has with inputs, and for the same reason.\n */\nexport function useControllableState<T>({\n value,\n defaultValue,\n onChange,\n}: UseControllableStateOptions<T>): [T, (next: T | ((prev: T) => T)) => void] {\n const [internal, setInternal] = useState<T>(defaultValue);\n\n const isControlled = value !== undefined;\n const current = isControlled ? value : internal;\n\n // Kept in a ref so `setValue` stays referentially stable across renders. Without this\n // every consumer that puts it in a dependency array re-subscribes on each render.\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n\n const currentRef = useRef(current);\n currentRef.current = current;\n\n const isControlledRef = useRef(isControlled);\n isControlledRef.current = isControlled;\n\n const setValue = useCallback((next: T | ((prev: T) => T)) => {\n const resolved =\n typeof next === 'function' ? (next as (prev: T) => T)(currentRef.current) : next;\n\n if (resolved === currentRef.current) return;\n\n // A controlled component must not move its own value — the parent owns it and will\n // pass the new one back down. Only the notification goes out.\n if (!isControlledRef.current) setInternal(resolved);\n onChangeRef.current?.(resolved);\n }, []);\n\n return [current, setValue];\n}\n","import { useCallback, useEffect, useRef, useState, type KeyboardEvent } from 'react';\n\nexport type RovingOrientation = 'horizontal' | 'vertical' | 'both';\n\nexport interface UseRovingFocusOptions {\n /** Which arrow keys move focus. `both` accepts all four. Default `horizontal`. */\n orientation?: RovingOrientation;\n /** Wrap past the ends. Default `true`. */\n loop?: boolean;\n /** Jump to an item by typing the start of its label. Default `false`. */\n typeahead?: boolean;\n /** Item to start on. Default `0`. */\n defaultIndex?: number;\n /** Fired when the composite is exited with Escape — menus use it to close. */\n onEscape?: () => void;\n}\n\nexport interface RovingItemOptions {\n disabled?: boolean;\n /** Typeahead match target. Only read when `typeahead` is on. */\n label?: string;\n}\n\nexport interface RovingItemProps {\n ref: (node: HTMLElement | null) => void;\n tabIndex: 0 | -1;\n onFocus: () => void;\n 'data-roving-index': number;\n}\n\n/**\n * Arrow-key navigation with a roving tabindex, for any composite widget that owns a\n * single-stop tab sequence: `menu`, `tablist`, `listbox`, `radiogroup`, `toolbar`.\n *\n * Before 0.4.0 this library had **no arrow-key handling anywhere** — `ArrowDown`,\n * `Home` and `End` appeared in no file. `Tabs`, `DropdownMenu`, `ButtonGroup`,\n * `SegmentedControl` and `RoleComboBox` all announced a composite role to assistive tech\n * while behaving like a plain row of buttons: every item natively tabbable, arrows inert.\n * That is worse than shipping no role at all, because a screen reader tells the user\n * \"tab 3 of 5\" and then the keys it implies do nothing.\n *\n * Keyboard handling lives on the container and is delegated, rather than being attached\n * per item — one listener, and the item list can change length without re-binding.\n *\n * const roving = useRovingFocus({ orientation: 'vertical', loop: true, typeahead: true });\n *\n * <div role=\"menu\" {...roving.containerProps}>\n * {items.map((item, i) => (\n * <button\n * key={item.id}\n * role=\"menuitem\"\n * {...roving.itemProps(i, { disabled: item.disabled, label: item.label })}\n * />\n * ))}\n * </div>\n */\nexport function useRovingFocus({\n orientation = 'horizontal',\n loop = true,\n typeahead = false,\n defaultIndex = 0,\n onEscape,\n}: UseRovingFocusOptions = {}) {\n const [activeIndex, setActiveIndex] = useState(defaultIndex);\n\n const items = useRef<(HTMLElement | null)[]>([]);\n const meta = useRef<RovingItemOptions[]>([]);\n\n const search = useRef('');\n const searchTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(\n () => () => {\n if (searchTimer.current) clearTimeout(searchTimer.current);\n },\n [],\n );\n\n const focusIndex = useCallback((index: number) => {\n const node = items.current[index];\n if (!node) return;\n setActiveIndex(index);\n node.focus();\n }, []);\n\n /** Walk from `start` in `step` until a registered, enabled item turns up. */\n const move = useCallback(\n (start: number, step: number) => {\n const total = items.current.length;\n if (total === 0) return;\n\n for (let i = 1; i <= total; i++) {\n let next = start + step * i;\n if (loop) {\n next = ((next % total) + total) % total;\n } else if (next < 0 || next >= total) {\n return;\n }\n if (items.current[next] && !meta.current[next]?.disabled) {\n focusIndex(next);\n return;\n }\n }\n },\n [loop, focusIndex],\n );\n\n const moveToEdge = useCallback(\n (from: 'start' | 'end') => {\n const total = items.current.length;\n if (total === 0) return;\n // Start just outside the range so `move` lands on the first enabled item inward.\n move(from === 'start' ? -1 : total, from === 'start' ? 1 : -1);\n },\n [move],\n );\n\n const runTypeahead = useCallback(\n (char: string) => {\n if (searchTimer.current) clearTimeout(searchTimer.current);\n search.current += char.toLowerCase();\n searchTimer.current = setTimeout(() => {\n search.current = '';\n }, 500);\n\n const total = items.current.length;\n const query = search.current;\n\n // Search forward from the item after the active one so repeated presses of the\n // same letter cycle through the matches instead of sticking on the first.\n for (let i = 1; i <= total; i++) {\n const index = (activeIndex + i) % total;\n if (meta.current[index]?.disabled) continue;\n const label = meta.current[index]?.label?.toLowerCase();\n if (label?.startsWith(query)) {\n focusIndex(index);\n return;\n }\n }\n },\n [activeIndex, focusIndex],\n );\n\n const onKeyDown = useCallback(\n (event: KeyboardEvent<HTMLElement>) => {\n const { key } = event;\n const vertical = orientation === 'vertical' || orientation === 'both';\n const horizontal = orientation === 'horizontal' || orientation === 'both';\n\n const next =\n (vertical && key === 'ArrowDown') || (horizontal && key === 'ArrowRight');\n const prev = (vertical && key === 'ArrowUp') || (horizontal && key === 'ArrowLeft');\n\n if (next || prev) {\n event.preventDefault();\n move(activeIndex, next ? 1 : -1);\n return;\n }\n\n if (key === 'Home') {\n event.preventDefault();\n moveToEdge('start');\n return;\n }\n\n if (key === 'End') {\n event.preventDefault();\n moveToEdge('end');\n return;\n }\n\n if (key === 'Escape' && onEscape) {\n onEscape();\n return;\n }\n\n // Single printable character, no modifier — a typeahead jump, not a shortcut.\n if (\n typeahead &&\n key.length === 1 &&\n key !== ' ' &&\n !event.ctrlKey &&\n !event.metaKey &&\n !event.altKey\n ) {\n event.preventDefault();\n runTypeahead(key);\n }\n },\n [orientation, activeIndex, move, moveToEdge, onEscape, typeahead, runTypeahead],\n );\n\n const itemProps = useCallback(\n (index: number, options: RovingItemOptions = {}): RovingItemProps => {\n meta.current[index] = options;\n return {\n ref: (node: HTMLElement | null) => {\n items.current[index] = node;\n },\n // The roving part: exactly one item is in the tab sequence, so Tab enters the\n // composite once and Tab again leaves it.\n tabIndex: index === activeIndex ? 0 : -1,\n onFocus: () => setActiveIndex(index),\n 'data-roving-index': index,\n };\n },\n [activeIndex],\n );\n\n return {\n activeIndex,\n setActiveIndex,\n focusIndex,\n moveToEdge,\n itemProps,\n containerProps: { onKeyDown },\n };\n}\n","import { useRef } from 'react';\nimport {\n arrow,\n autoUpdate,\n flip,\n offset,\n shift,\n size,\n useFloating,\n type Middleware,\n type Placement,\n} from '@floating-ui/react';\n\nexport type FloatingSide = 'top' | 'right' | 'bottom' | 'left';\nexport type FloatingAlign = 'start' | 'center' | 'end';\n\nexport interface UseFloatingPanelOptions {\n open: boolean;\n onOpenChange?: (open: boolean) => void;\n /** Preferred edge of the trigger to sit on. Flips when there is no room. Default `bottom`. */\n side?: FloatingSide;\n /** Alignment along that edge. Default `center`. */\n align?: FloatingAlign;\n /** Gap between trigger and panel, in px. Default `6`. */\n sideOffset?: number;\n /** Nudge along the alignment axis, in px. Default `0`. */\n alignOffset?: number;\n /** Minimum gap to keep from the viewport edge, in px. Default `8`. */\n collisionPadding?: number;\n /** Render an arrow and position it. Default `false`. */\n withArrow?: boolean;\n /**\n * Constrain the panel to the space available and expose the trigger's width as\n * `--panel-anchor-width`, so a listbox can match its input. Default `false`.\n */\n matchAnchorWidth?: boolean;\n}\n\n/**\n * The one floating-panel configuration, shared by every popover-shaped component —\n * `DropdownMenu`, `Tooltip`, `Popover`, `HoverCard`, `ContextMenu`, `Combobox`, `Select`.\n *\n * Everything here is behaviour the hand-rolled version had none of. Panels were plain\n * `absolute top-full` with a static `left-0`/`right-0`, so there was no flipping when a\n * panel ran off the bottom of the viewport, no shifting when it ran off the side, no\n * repositioning on scroll or resize (nothing listened for either), and no way to ask for\n * anything other than \"below, aligned left or right\".\n *\n * `side`/`align` is the shadcn/Radix vocabulary rather than Floating UI's single\n * `placement` string, because it keeps the two axes independent at the call site and\n * `align=\"center\"` does not have to be spelled as an empty suffix.\n */\nexport function useFloatingPanel({\n open,\n onOpenChange,\n side = 'bottom',\n align = 'center',\n sideOffset = 6,\n alignOffset = 0,\n collisionPadding = 8,\n withArrow = false,\n matchAnchorWidth = false,\n}: UseFloatingPanelOptions) {\n const arrowRef = useRef<HTMLElement | null>(null);\n\n const placement: Placement = (align === 'center' ? side : `${side}-${align}`) as Placement;\n\n const floating = useFloating({\n open,\n onOpenChange,\n placement,\n // Only track while the panel is actually on screen. `autoUpdate` attaches scroll and\n // resize listeners plus a ResizeObserver, so leaving it running on a closed panel\n // would cost every consumer a listener per menu on the page.\n whileElementsMounted: autoUpdate,\n middleware: [\n offset({ mainAxis: sideOffset, crossAxis: alignOffset }),\n // `flip` before `shift`: try the opposite edge first, and only slide along the\n // current one if neither edge fits. The reverse order produces panels that cling to\n // the viewport edge when they had room to simply flip.\n flip({ padding: collisionPadding }),\n shift({ padding: collisionPadding }),\n matchAnchorWidth &&\n size({\n padding: collisionPadding,\n apply({ rects, elements, availableHeight }) {\n elements.floating.style.setProperty(\n '--panel-anchor-width',\n `${rects.reference.width}px`,\n );\n elements.floating.style.setProperty(\n '--panel-available-height',\n `${availableHeight}px`,\n );\n },\n }),\n withArrow && arrow({ element: arrowRef, padding: 4 }),\n ].filter(Boolean) as Middleware[],\n });\n\n return { ...floating, arrowRef };\n}\n","import clsx from 'clsx';\nimport { SCROLLBAR_CLASSES } from '../../lib/scrollbar';\nimport type { InputSize } from '../forms/input';\n\n/**\n * The one option-list look, shared by every component that pops a panel of choices:\n * `DropdownMenu`, `ContextMenu`, `Menubar`, `Combobox`, `Command` and `Select`.\n *\n * Before this existed the four implementations had drifted: menus used `py-1` panels with\n * full-bleed square `px-3` items, while `Combobox` and `Command` used `p-1` panels with\n * inset `rounded-md px-2.5` ones. Menus moved, not the other two — a rounded highlight\n * needs the panel's own inset padding or its corners collide with the panel border on the\n * first and last item, and the inset form was already 2 of the 3 sites and the 2 newest.\n *\n * Strings, not components, and deliberately in its own module: `forms/` consumes these,\n * and importing `menu-core` from there would pull the whole menu component tree into the\n * bundle graph — tsup runs with no code splitting.\n */\n\n// ── Panel ──────────────────────────────────────────────────────────────────────\n\nexport const OVERLAY_PANEL = 'rounded-lg border border-default bg-default p-1 shadow-300';\n\n/** Menus need a floor width; anchored lists take their width from the trigger. */\nexport const OVERLAY_PANEL_MENU = clsx(OVERLAY_PANEL, 'min-w-[168px]');\n\n/**\n * Scroll cap for an anchored list. Prefers the collision-aware height `useFloatingPanel`\n * writes to `--panel-available-height` when `matchAnchorWidth` is set, falling back to\n * 16rem so the class is safe on a panel that never measured.\n */\nexport const OVERLAY_PANEL_SCROLL = clsx(\n 'max-h-[min(16rem,var(--panel-available-height,16rem))] overflow-y-auto',\n // A floating panel is already its own scroll container and keeps focus on the input or a\n // roving option, so it needs the scrollbar styling but not `ScrollArea`'s tab stop.\n SCROLLBAR_CLASSES,\n);\n\n// ── Item ───────────────────────────────────────────────────────────────────────\n\n/**\n * Carries no padding and no text size — those live in `OVERLAY_ITEM_SIZE`.\n *\n * `clsx` only concatenates, so a base carrying `px-3` beside a size carrying `px-2.5`\n * would resolve by stylesheet order rather than attribute order. Keeping each utility in\n * exactly one of the two strings makes the composition order-independent.\n */\nexport const OVERLAY_ITEM_BASE =\n 'flex w-full cursor-pointer select-none items-start gap-2 rounded-md text-left outline-none transition-colors';\n\nexport const OVERLAY_ITEM_SIZE: Record<InputSize, string> = {\n lg: 'px-3 py-2.5 text-body',\n md: 'px-2.5 py-2 text-body-sm',\n sm: 'px-2 py-1.5 text-body-tiny',\n};\n\nexport type OverlayItemTone = 'default' | 'danger';\n\n/** Hover and keyboard focus share one background — there is no separate active token. */\nexport function overlayItemTone(tone: OverlayItemTone, disabled: boolean) {\n if (disabled) return 'cursor-not-allowed text-disabled';\n return tone === 'danger'\n ? 'text-danger hover:bg-danger-tertiary focus:bg-danger-tertiary'\n : 'text-default hover:bg-default-secondary focus:bg-default-secondary';\n}\n\n/** Highlight driven by state rather than `:hover` — for lists using aria-activedescendant. */\nexport const OVERLAY_ITEM_ACTIVE = 'bg-default-secondary';\n\n// ── Group heading and separator ────────────────────────────────────────────────\n\nexport const OVERLAY_HEADING =\n 'px-2.5 py-1.5 text-body-tiny font-semibold uppercase tracking-wide text-default-tertiary';\n\n/** `-mx-1` cancels the panel's `p-1` so the rule still spans edge to edge. */\nexport const OVERLAY_SEPARATOR = '-mx-1 my-1 h-px bg-divider';\n","import clsx from 'clsx';\n\n/**\n * Styled native scrollbars, for the components that must own their own overflow and so\n * cannot delegate to `ScrollArea`.\n *\n * Two kinds qualify. A table wrapper, a tab strip or a stepper, where inserting another\n * element would break the layout or the `<table>` element relationship; and a floating panel\n * that is already its own scroll container. Both hold focusable children, so a keyboard user\n * can already reach and scroll them — which is the one thing `ScrollArea` adds beyond these\n * classes, and the one thing they do not need.\n *\n * Prefer `ScrollArea` for a scrolling *region* of content. Reach for this only when a wrapper\n * element is not an option, and say why at the call site.\n *\n * A plain string module rather than an export from `scroll-area.tsx`: `menu-styles.ts` needs\n * it, and importing a component module from a style module would pull the component into the\n * bundle graph for the sake of a class list.\n */\nexport const SCROLLBAR_CLASSES = clsx(\n // Firefox and the standard property.\n '[scrollbar-color:var(--color-background-default-tertiary)_transparent] [scrollbar-width:thin]',\n // WebKit/Blink.\n '[&::-webkit-scrollbar]:size-2',\n '[&::-webkit-scrollbar-track]:bg-transparent',\n '[&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-default-tertiary',\n '[&::-webkit-scrollbar-thumb:hover]:bg-neutral-secondary',\n '[&::-webkit-scrollbar-corner]:bg-transparent',\n);\n","import { useEffect, useRef, type RefObject } from 'react';\nimport { tabbable } from 'tabbable';\nimport { useOverlayLayer } from './overlay-stack';\n\n/** The one scrim for every overlay primitive.\n *\n * Modal and Drawer both reference this constant, so there is a single place to change\n * the dimming behind a dialog. Backed by `--color-background-utilities-overlay`. */\nexport const OVERLAY_SCRIM = 'bg-overlay';\n\nexport interface UseOverlayDismissOptions {\n /** Called when the overlay should close. */\n onDismiss: () => void;\n /** The panel element — not the scrim. */\n panelRef: RefObject<HTMLElement | null>;\n /** Set `false` to suspend every behaviour without unmounting. Default `true`. */\n active?: boolean;\n /** Default `true`. */\n closeOnEscape?: boolean;\n /** Default `true`. */\n closeOnOutsidePress?: boolean;\n /** Keep Tab inside the panel. Default `true`. Turn off for non-modal surfaces. */\n trapFocus?: boolean;\n /** Default `true`. */\n lockScroll?: boolean;\n /** Mark sibling content `inert`. Default `true`. */\n inertBackground?: boolean;\n /**\n * Move focus into the panel on mount. Default `true`.\n *\n * Set `false` when the overlay manages its own entry point — a menu with a roving\n * tabindex exposes exactly one focusable item at a time, so a generic\n * \"focus the first tabbable element\" scan would fight it.\n */\n autoFocus?: boolean;\n /** Focus this instead of the first tabbable element. */\n initialFocus?: RefObject<HTMLElement | null>;\n /**\n * Restore focus here instead of wherever it was when the overlay opened.\n *\n * Passing the ref at all takes over the restore: a `current` of `null` means \"leave\n * focus alone\", not \"fall back to the previously focused element\". `Menubar` depends on\n * that — while it is switching from one menu to a sibling, focus already belongs to the\n * sibling and pulling it back to the closing menu's trigger reopens the menu.\n */\n returnFocus?: RefObject<HTMLElement | null>;\n /** Call `preventDefault()` to veto the close — e.g. a form with unsaved input. */\n onEscapeKeyDown?: (event: KeyboardEvent) => void;\n /** Call `preventDefault()` to veto the close. */\n onInteractOutside?: (event: PointerEvent) => void;\n}\n\n/** Esc-to-close, a Tab focus trap, scroll lock, an inert background, and focus restore\n * for a mounted overlay. Pass the ref of the panel element (not the scrim).\n *\n * Rewritten in 0.4.0. The previous version handled Esc, Tab and focus restore, and had\n * the following defects — each is now covered by a test:\n *\n * - **One Escape closed every open overlay.** Each overlay put its own listener on\n * `document` and called `stopPropagation`, which stops propagation to other *nodes*,\n * not to sibling listeners on the same node. Dismissal is now gated on `isTopmost`\n * from the shared layer stack.\n * - **A text-selection drag dismissed the dialog.** Outside-press used `click`, whose\n * target for a drag is the nearest common ancestor — the scrim. Now `pointerdown`.\n * - **Forward Tab could walk out of the panel.** The trap only compared against the\n * last tabbable element; once focus was outside by any other route, Tab roamed the\n * background freely. A `focusin` guard now pulls focus back.\n * - **Fixed-position elements were excluded from the trap.** The visibility filter was\n * `offsetParent !== null`, which is `null` for `position: fixed`. Element collection\n * is delegated to `tabbable`, which also honours real tab order (a positive\n * `tabindex` reorders the sequence, so DOM order was the wrong answer), `inert`,\n * `[aria-disabled]`, `contenteditable`, `<summary>`, media with controls, and\n * descendants of a disabled `<fieldset>`.\n * - **Initial focus was a silent no-op for a content-only dialog.** It fell back to\n * focusing the panel, which does nothing without `tabIndex=\"-1\"`. The hook now sets\n * that itself, and retries once content arrives asynchronously.\n * - **Focus restore could land on a detached node**, resetting focus to `<body>`.\n * Now checked with `isConnected`.\n * - No opt-out existed for either dismissal route. Both are now vetoable.\n */\nexport function useOverlayDismiss({\n onDismiss,\n panelRef,\n active = true,\n closeOnEscape = true,\n closeOnOutsidePress = true,\n trapFocus = true,\n lockScroll = true,\n inertBackground = true,\n autoFocus = true,\n initialFocus,\n returnFocus,\n onEscapeKeyDown,\n onInteractOutside,\n}: UseOverlayDismissOptions): void {\n const { isTopmost } = useOverlayLayer(active, {\n lockScroll,\n inertBackground,\n panelRef,\n });\n\n // Read through refs so the listener effects do not re-subscribe on every render of a\n // consumer that passes inline callbacks — which is all of them.\n const handlers = useRef({ onDismiss, onEscapeKeyDown, onInteractOutside });\n handlers.current = { onDismiss, onEscapeKeyDown, onInteractOutside };\n\n const isTopmostRef = useRef(isTopmost);\n isTopmostRef.current = isTopmost;\n\n // ── Escape ───────────────────────────────────────────────────────────────────\n useEffect(() => {\n if (!active || !closeOnEscape) return;\n\n function onKeyDown(event: KeyboardEvent) {\n if (event.key !== 'Escape') return;\n if (!isTopmostRef.current) return;\n\n handlers.current.onEscapeKeyDown?.(event);\n if (event.defaultPrevented) return;\n\n handlers.current.onDismiss();\n }\n\n document.addEventListener('keydown', onKeyDown);\n return () => document.removeEventListener('keydown', onKeyDown);\n }, [active, closeOnEscape]);\n\n // ── Outside press ────────────────────────────────────────────────────────────\n useEffect(() => {\n if (!active || !closeOnOutsidePress) return;\n\n function onPointerDown(event: PointerEvent) {\n if (!isTopmostRef.current) return;\n const panel = panelRef.current;\n if (!panel || panel.contains(event.target as Node)) return;\n\n handlers.current.onInteractOutside?.(event);\n if (event.defaultPrevented) return;\n\n handlers.current.onDismiss();\n }\n\n document.addEventListener('pointerdown', onPointerDown);\n return () => document.removeEventListener('pointerdown', onPointerDown);\n }, [active, closeOnOutsidePress, panelRef]);\n\n // ── Focus trap ───────────────────────────────────────────────────────────────\n useEffect(() => {\n if (!active || !trapFocus) return;\n\n function onKeyDown(event: KeyboardEvent) {\n if (event.key !== 'Tab' || !isTopmostRef.current) return;\n const panel = panelRef.current;\n if (!panel) return;\n\n const items = tabbable(panel);\n if (items.length === 0) {\n // Nothing to cycle between, but Tab must still not escape the dialog.\n event.preventDefault();\n panel.focus({ preventScroll: true });\n return;\n }\n\n const first = items[0];\n const last = items[items.length - 1];\n const activeElement = document.activeElement;\n const inside = panel.contains(activeElement);\n\n if (event.shiftKey && (activeElement === first || !inside)) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (activeElement === last || !inside)) {\n event.preventDefault();\n first.focus();\n }\n }\n\n // The keydown boundary check above only catches the moment focus is *about* to leave.\n // Focus can also arrive outside by a route Tab never sees — a click on the scrim, a\n // programmatic `.focus()`, or a round trip through the browser chrome. This pulls it\n // back whenever that happens.\n function onFocusIn(event: FocusEvent) {\n if (!isTopmostRef.current) return;\n const panel = panelRef.current;\n if (!panel) return;\n if (panel.contains(event.target as Node)) return;\n\n const items = tabbable(panel);\n (items[0] ?? panel).focus({ preventScroll: true });\n }\n\n document.addEventListener('keydown', onKeyDown);\n document.addEventListener('focusin', onFocusIn);\n return () => {\n document.removeEventListener('keydown', onKeyDown);\n document.removeEventListener('focusin', onFocusIn);\n };\n }, [active, trapFocus, panelRef]);\n\n // ── Initial focus and restore ────────────────────────────────────────────────\n useEffect(() => {\n if (!active) return;\n\n const previouslyFocused = document.activeElement as HTMLElement | null;\n const panel = panelRef.current;\n let observer: MutationObserver | null = null;\n\n if (panel && autoFocus) {\n // A content-only dialog has nothing tabbable, and `focus()` on an element without\n // a tabindex silently does nothing. Making the panel itself focusable is what turns\n // the fallback below into a real focus move.\n if (!panel.hasAttribute('tabindex')) panel.setAttribute('tabindex', '-1');\n\n const focusTarget = () => {\n const explicit = initialFocus?.current;\n if (explicit) {\n explicit.focus({ preventScroll: true });\n return true;\n }\n const items = tabbable(panel);\n if (items.length > 0) {\n items[0].focus({ preventScroll: true });\n return true;\n }\n return false;\n };\n\n if (!focusTarget()) {\n panel.focus({ preventScroll: true });\n\n // Panel content can arrive after mount — a fetch, a Suspense boundary. Watch for\n // the first tabbable element and move focus onto it once, then stop.\n observer = new MutationObserver(() => {\n if (focusTarget()) {\n observer?.disconnect();\n observer = null;\n }\n });\n observer.observe(panel, { childList: true, subtree: true });\n }\n }\n\n return () => {\n observer?.disconnect();\n // Reading `returnFocus.current` at cleanup time is the intent, not an oversight:\n // the element to return focus to may not exist when the overlay mounts, and the\n // caller may repoint it while the overlay is open.\n //\n // A caller that passes the ref owns the decision outright, `null` included — `??`\n // here instead of the ternary made `{ current: null }` mean \"restore to whatever\n // was focused before\", the exact opposite of what the one caller passing it wanted.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const target = returnFocus ? returnFocus.current : previouslyFocused;\n // A detached node's `focus()` is a silent no-op that leaves focus on <body>, which\n // strands keyboard users at the top of the page. Better to leave focus where the\n // browser put it than to pretend.\n if (target?.isConnected) target.focus({ preventScroll: true });\n };\n }, [active, panelRef, initialFocus, returnFocus, autoFocus]);\n}\n","import { useEffect, useRef, useState, type RefObject } from 'react';\n\n/**\n * A process-wide stack of the overlays currently mounted, plus the two document-level\n * side effects that belong to \"there is a dialog open\": scroll lock and an inert\n * background.\n *\n * Why a stack rather than per-overlay listeners. Before 0.4.0 every overlay attached its\n * own `keydown` handler to `document`. `useOverlayDismiss` called `e.stopPropagation()`\n * on Escape, which stops propagation *to other nodes* — it does not stop the other\n * listeners already registered on the same node. All of them were on `document`. So a\n * `DropdownMenu` open inside a `Modal` closed both on one Escape, and two stacked modals\n * closed together. Ordering has to be decided centrally, by asking \"am I topmost?\",\n * which is what this module answers.\n */\n\ntype Layer = { id: symbol };\n\nconst layers: Layer[] = [];\nconst listeners = new Set<() => void>();\n\nfunction notify() {\n for (const listener of listeners) listener();\n}\n\nfunction topmost(): symbol | undefined {\n return layers[layers.length - 1]?.id;\n}\n\n// ── Scroll lock ────────────────────────────────────────────────────────────────\n\nlet scrollLocks = 0;\nlet restoreBody: (() => void) | null = null;\n\nfunction lockScroll() {\n scrollLocks += 1;\n if (scrollLocks > 1 || typeof document === 'undefined') return;\n\n const { body, documentElement } = document;\n const previousOverflow = body.style.overflow;\n const previousPaddingRight = body.style.paddingRight;\n\n // Removing the scrollbar reflows the page a few pixels narrower, which reads as the\n // whole layout twitching sideways as the dialog appears. Pad by exactly the width the\n // scrollbar occupied to hold everything still.\n const scrollbarWidth = window.innerWidth - documentElement.clientWidth;\n if (scrollbarWidth > 0) {\n const current = parseFloat(window.getComputedStyle(body).paddingRight) || 0;\n body.style.paddingRight = `${current + scrollbarWidth}px`;\n }\n body.style.overflow = 'hidden';\n\n restoreBody = () => {\n body.style.overflow = previousOverflow;\n body.style.paddingRight = previousPaddingRight;\n };\n}\n\nfunction unlockScroll() {\n scrollLocks = Math.max(0, scrollLocks - 1);\n if (scrollLocks === 0 && restoreBody) {\n restoreBody();\n restoreBody = null;\n }\n}\n\n// ── Inert background ───────────────────────────────────────────────────────────\n\nconst inerted = new Set<HTMLElement>();\n\n/**\n * Marks every top-level sibling of the overlay's own subtree `inert`, which removes it\n * from the accessibility tree *and* from the tab sequence in one attribute.\n *\n * `aria-modal=\"true\"` alone — all this library had before — is honoured by modern screen\n * readers for virtual-cursor browsing but does nothing about focus, and older AT ignores\n * it entirely. `inert` is the actual guarantee.\n */\nfunction applyInert(node: HTMLElement | null) {\n if (typeof document === 'undefined' || !node) return;\n\n // Walk up to whichever direct child of <body> contains this overlay, so the overlay's\n // own portal container is the one subtree left interactive.\n let root: HTMLElement = node;\n while (root.parentElement && root.parentElement !== document.body) {\n root = root.parentElement;\n }\n\n for (const child of Array.from(document.body.children)) {\n if (child === root || !(child instanceof HTMLElement)) continue;\n if (child.hasAttribute('inert')) continue;\n child.setAttribute('inert', '');\n inerted.add(child);\n }\n}\n\nfunction releaseInert() {\n for (const el of inerted) el.removeAttribute('inert');\n inerted.clear();\n}\n\n// ── Hook ───────────────────────────────────────────────────────────────────────\n\nexport interface UseOverlayLayerOptions {\n /** Prevent the page behind from scrolling. Default `true`. */\n lockScroll?: boolean;\n /**\n * Mark sibling content `inert`. Default `true`. Turn it off for non-modal surfaces —\n * a tooltip or a popover must leave the rest of the page usable.\n */\n inertBackground?: boolean;\n /**\n * Ref to the panel element, used to work out which subtree to leave interactive.\n *\n * Deliberately the ref object rather than the node: a node read during render is\n * always `null` on the first pass, and nothing re-renders the consumer once the ref\n * is attached, so the inert pass would never run. Effects read `.current` after the\n * DOM is committed, where it is populated.\n */\n panelRef?: RefObject<HTMLElement | null>;\n}\n\n/**\n * Registers a mounted overlay with the stack.\n *\n * Returns `isTopmost`, which every dismissal handler must gate on so only the frontmost\n * layer reacts to Escape or an outside pointer press.\n */\nexport function useOverlayLayer(\n active: boolean,\n {\n lockScroll: shouldLockScroll = true,\n inertBackground = true,\n panelRef,\n }: UseOverlayLayerOptions = {},\n): { isTopmost: boolean } {\n const id = useRef<symbol>();\n if (!id.current) id.current = Symbol('overlay-layer');\n\n const [isTopmost, setIsTopmost] = useState(false);\n\n useEffect(() => {\n if (!active) return;\n const self = id.current!;\n\n layers.push({ id: self });\n\n const sync = () => setIsTopmost(topmost() === self);\n listeners.add(sync);\n sync();\n notify();\n\n if (shouldLockScroll) lockScroll();\n\n return () => {\n const index = layers.findIndex((layer) => layer.id === self);\n if (index !== -1) layers.splice(index, 1);\n listeners.delete(sync);\n notify();\n if (shouldLockScroll) unlockScroll();\n };\n }, [active, shouldLockScroll]);\n\n useEffect(() => {\n if (!active || !inertBackground) return;\n // Only the bottom-most modal layer needs to set this: it already hides everything\n // outside the overlay tree, and every overlay above it portals into the same root.\n if (inerted.size === 0) applyInert(panelRef?.current ?? null);\n return () => {\n if (layers.length <= 1) releaseInert();\n };\n }, [active, inertBackground, panelRef]);\n\n return { isTopmost };\n}\n","import { useEffect, useState, type ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\n\nexport interface PortalProps {\n children: ReactNode;\n /**\n * Where to mount. Defaults to `document.body`.\n *\n * Pass a container when the app renders inside a shadow root or a scoped theme\n * wrapper — the portal must stay inside whatever element the design tokens are\n * declared on, or every `var(--color-*)` resolves to nothing.\n */\n container?: HTMLElement | null;\n}\n\n/**\n * Renders `children` outside the parent DOM hierarchy.\n *\n * Until 0.4.0 this library never called `createPortal` — `Modal`, `Drawer`,\n * `DropdownMenu`, `RoleComboBox` and the sidebar menu all rendered inline. Two concrete\n * failures came out of that:\n *\n * - `DropdownMenu` was positioned `absolute` inside a `relative` wrapper, so it was\n * clipped by `Card`'s `overflow-hidden` and by `DataTable`'s `overflow-x-auto` — the\n * two places its own doc comment said it was for.\n * - `position: fixed` resolves against the nearest ancestor with `transform`, `filter`,\n * `backdrop-filter`, `perspective`, `will-change` or `contain`, so a `Modal` opened\n * from inside any transformed ancestor was positioned against that ancestor rather\n * than the viewport, and `z-50` was scoped to a stacking context it did not control.\n *\n * Mounting is deferred to an effect so the server render and the first client render\n * agree — reaching for `document` during render breaks SSR and hydration.\n */\nexport function Portal({ children, container }: PortalProps) {\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => setMounted(true), []);\n\n if (!mounted) return null;\n\n const target = container ?? (typeof document === 'undefined' ? null : document.body);\n if (!target) return null;\n\n return createPortal(children, target);\n}\n","import {\n cloneElement,\n isValidElement,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport clsx from 'clsx';\nimport { composeRefs } from '../../lib/slot';\nimport { useControllableState } from '../../lib/use-controllable-state';\nimport { useFloatingPanel, type FloatingAlign, type FloatingSide } from './floating';\nimport { Portal } from './portal';\n\nexport interface TooltipProps {\n /**\n * The trigger. Must be a single focusable element — a tooltip that can only be reached\n * with a pointer is invisible to keyboard and touch users, so this deliberately does not\n * wrap its child in a span to make it hoverable.\n */\n children: ReactElement;\n /** Bold first line. Matches Figma's `Title` property. */\n title?: ReactNode;\n /** Supporting line. Matches Figma's `Body` / `Has Body`. */\n body?: ReactNode;\n side?: FloatingSide;\n align?: FloatingAlign;\n /** Delay before opening, in ms. Default `150`. */\n openDelay?: number;\n /** Delay before closing, in ms. Default `0`. */\n closeDelay?: number;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /**\n * `describedby` — the trigger keeps its own name and the tooltip adds detail. This is\n * almost always what you want. `labelledby` makes the tooltip *be* the trigger's name,\n * for an icon-only button with no other text. Default `describedby`.\n *\n * Deliberately not called `as`: everywhere else in this library `as` chooses the rendered\n * element, and reusing it for something else here would be a trap (it also makes\n * `jsx-a11y`'s polymorphic-component checking treat the value as a tag name).\n */\n association?: 'describedby' | 'labelledby';\n className?: string;\n}\n\n/**\n * Tooltip. Matches Figma's `Tooltip` set — `Placement: Top | Left | Right | Bottom`, with\n * a title and an optional body.\n *\n * Things this gets right that a naive tooltip does not:\n *\n * - **Keyboard and touch reachable.** Opens on `focus` as well as hover, and closes on\n * Escape. A hover-only tooltip is inaccessible to half the people using it.\n * - **Referenced, not floating text.** The panel is wired to the trigger with\n * `aria-describedby` (or `aria-labelledby`), so it is announced *with* the control\n * rather than being an orphan region a screen reader may never reach.\n * - **`role=\"tooltip\"` and no interactive content.** A tooltip cannot be hovered into or\n * focused, so anything interactive inside it is unreachable — use `Popover` for that.\n * - Not modal: no focus trap, no scroll lock, no inert background.\n */\nexport function Tooltip({\n children,\n title,\n body,\n side = 'top',\n align = 'center',\n openDelay = 150,\n closeDelay = 0,\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n association = 'describedby',\n className,\n}: TooltipProps) {\n const [open, setOpen] = useControllableState({\n value: openProp,\n defaultValue: defaultOpen,\n onChange: onOpenChange,\n });\n\n const tooltipId = useId();\n const [triggerNode, setTriggerNode] = useState<HTMLElement | null>(null);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearTimer = useCallback(() => {\n if (timer.current) {\n clearTimeout(timer.current);\n timer.current = null;\n }\n }, []);\n\n const schedule = useCallback(\n (next: boolean, delay: number) => {\n clearTimer();\n if (delay === 0) {\n setOpen(next);\n return;\n }\n timer.current = setTimeout(() => setOpen(next), delay);\n },\n [clearTimer, setOpen],\n );\n\n useEffect(() => clearTimer, [clearTimer]);\n\n const { refs, floatingStyles } = useFloatingPanel({\n open,\n onOpenChange: setOpen,\n side,\n align,\n sideOffset: 6,\n });\n\n useEffect(() => {\n refs.setReference(triggerNode);\n }, [refs, triggerNode]);\n\n // Escape must dismiss a tooltip even though it never holds focus, so this listens at the\n // document rather than on the trigger.\n useEffect(() => {\n if (!open) return;\n function onKeyDown(event: KeyboardEvent) {\n if (event.key === 'Escape') setOpen(false);\n }\n document.addEventListener('keydown', onKeyDown);\n return () => document.removeEventListener('keydown', onKeyDown);\n }, [open, setOpen]);\n\n if (!isValidElement(children)) return null;\n\n const child = children as ReactElement<Record<string, unknown>>;\n const childProps = child.props;\n const describedBy = association === 'describedby' ? tooltipId : undefined;\n const labelledBy = association === 'labelledby' ? tooltipId : undefined;\n\n const trigger = cloneElement(child, {\n ref: composeRefs<HTMLElement>(\n (node) => setTriggerNode(node),\n (child as unknown as { ref?: React.Ref<HTMLElement> }).ref,\n ),\n 'aria-describedby':\n [childProps['aria-describedby'], open ? describedBy : undefined].filter(Boolean).join(' ') ||\n undefined,\n 'aria-labelledby':\n [childProps['aria-labelledby'], labelledBy].filter(Boolean).join(' ') || undefined,\n onPointerEnter: (event: React.PointerEvent) => {\n (childProps.onPointerEnter as ((e: React.PointerEvent) => void) | undefined)?.(event);\n schedule(true, openDelay);\n },\n onPointerLeave: (event: React.PointerEvent) => {\n (childProps.onPointerLeave as ((e: React.PointerEvent) => void) | undefined)?.(event);\n schedule(false, closeDelay);\n },\n // Focus, not just hover — this is the difference between a tooltip a keyboard user can\n // read and one they cannot.\n onFocus: (event: React.FocusEvent) => {\n (childProps.onFocus as ((e: React.FocusEvent) => void) | undefined)?.(event);\n schedule(true, 0);\n },\n onBlur: (event: React.FocusEvent) => {\n (childProps.onBlur as ((e: React.FocusEvent) => void) | undefined)?.(event);\n schedule(false, 0);\n },\n } as Record<string, unknown>);\n\n return (\n <>\n {trigger}\n {/* Always rendered under `association=\"labelledby\"` so the id it points at exists\n even while closed — an `aria-labelledby` that resolves to nothing leaves the\n trigger unnamed, which is worse than having no tooltip at all. */}\n {(open || association === 'labelledby') && (\n <Portal>\n <div\n ref={refs.setFloating}\n id={tooltipId}\n role=\"tooltip\"\n style={floatingStyles}\n className={clsx(\n 'z-tooltip max-w-[260px] rounded-lg bg-neutral px-3 py-2 shadow-300',\n 'text-body-xs text-neutral-on-neutral',\n !open && 'sr-only',\n className,\n )}\n >\n {title && <p className=\"font-semibold\">{title}</p>}\n {body && <p className={clsx(title && 'mt-0.5 opacity-80')}>{body}</p>}\n </div>\n </Portal>\n )}\n </>\n );\n}\n","import {\n createContext,\n useCallback,\n useContext,\n useId,\n useMemo,\n useRef,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\nimport clsx from 'clsx';\nimport { LuCheck } from 'react-icons/lu';\nimport { useRovingFocus } from '../../lib/use-roving-focus';\nimport {\n OVERLAY_HEADING,\n OVERLAY_ITEM_BASE,\n OVERLAY_ITEM_SIZE,\n OVERLAY_PANEL_MENU,\n OVERLAY_SEPARATOR,\n overlayItemTone,\n type OverlayItemTone,\n} from './menu-styles';\n\n/**\n * The shared machinery behind `DropdownMenu`, `ContextMenu` and `Menubar`.\n *\n * All three are the same WAI-ARIA menu: identical items, identical keyboard contract,\n * identical ARIA. Only how they *open* differs — a trigger button, a right-click at pointer\n * coordinates, or a horizontal bar of triggers. Extracted in 0.4.0 so there is one\n * implementation of the contract rather than three; the version this library shipped\n * through 0.3.0 implemented it zero times, and copying that mistake three ways would have\n * been worse than copying it once.\n *\n * Roots build their context with `useMenuState()` and provide it; items read it and need to\n * know nothing about which kind of menu they are in.\n */\n\nexport interface MenuContextValue {\n open: boolean;\n setOpen: (open: boolean) => void;\n menuId: string;\n triggerId: string;\n /** Items claim a roving index in render order, which is their visual order. */\n claimIndex: () => number;\n /** Called by the panel before its children render, so indices restart from zero. */\n resetIndices: () => void;\n /**\n * Which end of the list to land on when the menu opens. ArrowUp on a trigger means \"last\n * item\", per the APG. A ref handoff rather than the trigger focusing directly: the panel\n * does not exist yet when the trigger handles the key, and `Portal` adds a further commit\n * before the items mount.\n */\n pendingEdge: { current: 'start' | 'end' };\n roving: ReturnType<typeof useRovingFocus>;\n}\n\nexport const MenuContext = createContext<MenuContextValue | null>(null);\n\nexport function useMenu(part: string): MenuContextValue {\n const context = useContext(MenuContext);\n if (!context) {\n throw new Error(`<${part}> must be used inside a menu root (DropdownMenu, ContextMenu, Menubar)`);\n }\n return context;\n}\n\nexport interface UseMenuStateOptions {\n open: boolean;\n setOpen: (open: boolean) => void;\n}\n\n/** Builds the context value a menu root provides. */\nexport function useMenuState({ open, setOpen }: UseMenuStateOptions): MenuContextValue {\n const baseId = useId();\n const cursor = useRef(0);\n const pendingEdge = useRef<'start' | 'end'>('start');\n\n const roving = useRovingFocus({\n orientation: 'vertical',\n loop: true,\n typeahead: true,\n onEscape: () => setOpen(false),\n });\n\n const claimIndex = useCallback(() => cursor.current++, []);\n const resetIndices = useCallback(() => {\n cursor.current = 0;\n }, []);\n\n return useMemo(\n () => ({\n open,\n setOpen,\n menuId: `${baseId}-menu`,\n triggerId: `${baseId}-trigger`,\n claimIndex,\n resetIndices,\n pendingEdge,\n roving,\n }),\n [open, setOpen, baseId, claimIndex, resetIndices, roving],\n );\n}\n\n/**\n * Panel chrome, shared so the three menus cannot drift apart visually.\n *\n * Now an alias of the library-wide `OVERLAY_PANEL_MENU` — the same chrome backs the\n * Combobox, Command and Select lists. See `menu-styles.ts` for why menus adopted the\n * inset `p-1` panel rather than the other three adopting `py-1`.\n */\nexport const MENU_PANEL_CLASSES = OVERLAY_PANEL_MENU;\n\nconst ITEM_BASE = clsx(OVERLAY_ITEM_BASE, OVERLAY_ITEM_SIZE.md);\n\nexport type MenuItemTone = OverlayItemTone;\n\nconst itemToneClasses = overlayItemTone;\n\n// ── Item ───────────────────────────────────────────────────────────────────────\n\nexport interface MenuItemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onSelect'> {\n children: ReactNode;\n onSelect?: () => void;\n tone?: MenuItemTone;\n disabled?: boolean;\n /** Leading glyph. Marked decorative automatically. Matches Figma's `Icon` / `Has Icon`. */\n icon?: ReactNode;\n /** Second line. Matches Figma's `Description` / `Has Description`. */\n description?: ReactNode;\n /** Trailing shortcut hint. Matches Figma's `Shortcut` / `Has Shortcut`. */\n shortcut?: ReactNode;\n /** Typeahead match text, when `children` is not a plain string. */\n textValue?: string;\n}\n\nexport function MenuItem({\n children,\n onSelect,\n tone = 'default',\n disabled = false,\n icon,\n description,\n shortcut,\n textValue,\n className,\n ...rest\n}: MenuItemProps) {\n const menu = useMenu('MenuItem');\n const index = menu.claimIndex();\n const descriptionId = `${menu.menuId}-desc-${index}`;\n\n const label = textValue ?? (typeof children === 'string' ? children : undefined);\n const itemProps = menu.roving.itemProps(index, { disabled, label });\n\n const activate = () => {\n if (disabled) return;\n menu.setOpen(false);\n onSelect?.();\n };\n\n return (\n <div\n // `role=\"menuitem\"` on a div rather than a <button>: the roving tabindex owns\n // focusability, and a native button would re-enter the tab sequence.\n role=\"menuitem\"\n aria-disabled={disabled || undefined}\n aria-describedby={description ? descriptionId : undefined}\n {...itemProps}\n // Restated after the spread so jsx-a11y can see it — it cannot follow a tabIndex\n // that arrives through an object spread.\n tabIndex={itemProps.tabIndex}\n className={clsx(ITEM_BASE, itemToneClasses(tone, disabled), className)}\n onClick={(event) => {\n event.stopPropagation();\n activate();\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n activate();\n }\n }}\n {...rest}\n >\n {icon && (\n <span aria-hidden className=\"mt-0.5 flex size-4 shrink-0 items-center justify-center\">\n {icon}\n </span>\n )}\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate\">{children}</span>\n {description && (\n <span id={descriptionId} className=\"mt-0.5 block text-body-tiny text-default-secondary\">\n {description}\n </span>\n )}\n </span>\n {shortcut && (\n <span className=\"ml-auto shrink-0 pl-4 text-body-tiny text-default-tertiary\">\n {shortcut}\n </span>\n )}\n </div>\n );\n}\n\n// ── CheckboxItem ───────────────────────────────────────────────────────────────\n\nexport interface MenuCheckboxItemProps extends Omit<MenuItemProps, 'onSelect' | 'icon'> {\n checked: boolean;\n onCheckedChange?: (checked: boolean) => void;\n}\n\nexport function MenuCheckboxItem({\n children,\n checked,\n onCheckedChange,\n disabled = false,\n tone = 'default',\n shortcut,\n textValue,\n className,\n ...rest\n}: MenuCheckboxItemProps) {\n const menu = useMenu('MenuCheckboxItem');\n const index = menu.claimIndex();\n const label = textValue ?? (typeof children === 'string' ? children : undefined);\n const itemProps = menu.roving.itemProps(index, { disabled, label });\n\n const toggle = () => {\n if (disabled) return;\n onCheckedChange?.(!checked);\n // Deliberately stays open — a checkbox item is for multi-select, and closing after each\n // toggle would make selecting three things take three round trips.\n };\n\n return (\n <div\n role=\"menuitemcheckbox\"\n aria-checked={checked}\n aria-disabled={disabled || undefined}\n {...itemProps}\n tabIndex={itemProps.tabIndex}\n className={clsx(ITEM_BASE, 'items-center', itemToneClasses(tone, disabled), className)}\n onClick={(event) => {\n event.stopPropagation();\n toggle();\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n toggle();\n }\n }}\n {...rest}\n >\n <span aria-hidden className=\"flex size-4 shrink-0 items-center justify-center\">\n {checked && <LuCheck className=\"size-3.5\" strokeWidth={3} />}\n </span>\n <span className=\"min-w-0 flex-1 truncate\">{children}</span>\n {shortcut && (\n <span className=\"ml-auto shrink-0 pl-4 text-body-tiny text-default-tertiary\">\n {shortcut}\n </span>\n )}\n </div>\n );\n}\n\n// ── RadioGroup / RadioItem ─────────────────────────────────────────────────────\n\ninterface MenuRadioContextValue {\n value: string | undefined;\n onValueChange?: (value: string) => void;\n}\n\nconst MenuRadioContext = createContext<MenuRadioContextValue | null>(null);\n\nexport interface MenuRadioGroupProps {\n children: ReactNode;\n value?: string;\n onValueChange?: (value: string) => void;\n}\n\nexport function MenuRadioGroup({ children, value, onValueChange }: MenuRadioGroupProps) {\n const context = useMemo(() => ({ value, onValueChange }), [value, onValueChange]);\n return (\n <MenuRadioContext.Provider value={context}>\n <div role=\"group\">{children}</div>\n </MenuRadioContext.Provider>\n );\n}\n\nexport interface MenuRadioItemProps extends Omit<MenuItemProps, 'onSelect' | 'icon'> {\n value: string;\n}\n\nexport function MenuRadioItem({\n children,\n value,\n disabled = false,\n tone = 'default',\n textValue,\n className,\n ...rest\n}: MenuRadioItemProps) {\n const menu = useMenu('MenuRadioItem');\n const group = useContext(MenuRadioContext);\n const index = menu.claimIndex();\n const label = textValue ?? (typeof children === 'string' ? children : undefined);\n const itemProps = menu.roving.itemProps(index, { disabled, label });\n\n const checked = group?.value === value;\n\n const select = () => {\n if (disabled) return;\n group?.onValueChange?.(value);\n menu.setOpen(false);\n };\n\n return (\n <div\n role=\"menuitemradio\"\n aria-checked={checked}\n aria-disabled={disabled || undefined}\n {...itemProps}\n tabIndex={itemProps.tabIndex}\n className={clsx(ITEM_BASE, 'items-center', itemToneClasses(tone, disabled), className)}\n onClick={(event) => {\n event.stopPropagation();\n select();\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n select();\n }\n }}\n {...rest}\n >\n <span aria-hidden className=\"flex size-4 shrink-0 items-center justify-center\">\n {checked && <span className=\"size-1.5 rounded-full bg-current\" />}\n </span>\n <span className=\"min-w-0 flex-1 truncate\">{children}</span>\n </div>\n );\n}\n\n// ── Structure ──────────────────────────────────────────────────────────────────\n\nconst HEADING_CLASSES = OVERLAY_HEADING;\n\nexport interface MenuGroupProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n /** Names the group. Rendered as a heading when set. */\n label?: string;\n}\n\nexport function MenuGroup({ children, label, className, ...rest }: MenuGroupProps) {\n const labelId = useId();\n return (\n <div role=\"group\" aria-labelledby={label ? labelId : undefined} className={className} {...rest}>\n {label && (\n <div id={labelId} className={HEADING_CLASSES}>\n {label}\n </div>\n )}\n {children}\n </div>\n );\n}\n\nexport interface MenuLabelProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n}\n\n/** A non-interactive heading inside the menu. Matches Figma's `Menu Heading`. */\nexport function MenuLabel({ children, className, ...rest }: MenuLabelProps) {\n return (\n <div className={clsx(HEADING_CLASSES, className)} {...rest}>\n {children}\n </div>\n );\n}\n\nexport interface MenuSeparatorProps extends HTMLAttributes<HTMLDivElement> {}\n\n/** Matches Figma's `Menu Separator`. Was a bare unlabelled `div` before 0.4.0. */\nexport function MenuSeparator({ className, ...rest }: MenuSeparatorProps) {\n return (\n <div\n role=\"separator\"\n aria-orientation=\"horizontal\"\n className={clsx(OVERLAY_SEPARATOR, className)}\n {...rest}\n />\n );\n}\n\nexport interface MenuShortcutProps extends HTMLAttributes<HTMLSpanElement> {\n children: ReactNode;\n}\n\n/** Trailing keyboard hint, for use outside an item's `shortcut` prop. */\nexport function MenuShortcut({ children, className, ...rest }: MenuShortcutProps) {\n return (\n <span\n className={clsx('ml-auto pl-4 text-body-tiny text-default-tertiary', className)}\n {...rest}\n >\n {children}\n </span>\n );\n}\n","import { forwardRef, useEffect, useRef, useState, type HTMLAttributes, type ReactNode } from 'react';\nimport clsx from 'clsx';\nimport { SCROLLBAR_CLASSES } from '../../lib/scrollbar';\nimport { composeRefs } from '../../lib/slot';\n\nexport type ScrollAreaAxis = 'vertical' | 'horizontal' | 'both';\n\nexport interface ScrollAreaProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n axis?: ScrollAreaAxis;\n /**\n * Accessible name for the scrollable region. Strongly recommended — a scroll container\n * that becomes focusable (see below) needs to announce what it is.\n */\n label?: string;\n}\n\nconst OVERFLOW: Record<ScrollAreaAxis, string> = {\n vertical: 'overflow-y-auto overflow-x-hidden',\n horizontal: 'overflow-x-auto overflow-y-hidden',\n both: 'overflow-auto',\n};\n\nexport { SCROLLBAR_CLASSES } from '../../lib/scrollbar';\n\n/**\n * A scrollable region with styled scrollbars.\n *\n * **Native scrolling, deliberately.** shadcn/Radix replaces the scrollbar with a rendered\n * one, which looks tidier and costs real accessibility: an overlay scrollbar has to\n * reimplement keyboard scrolling, momentum, trackpad and pen behaviour, and the OS\n * \"click the track to page\" convention — and it drops out entirely if the JS fails. Styling\n * `::-webkit-scrollbar` plus `scrollbar-width`/`scrollbar-color` gets most of the look with\n * none of that risk, and degrades to a normal scrollbar where unsupported.\n *\n * The one thing this *does* add is the piece browsers get wrong: a scroll container is not\n * keyboard focusable by default in Chrome or Safari, so a keyboard-only user cannot scroll\n * an overflowing `<div>` at all unless something inside it is focusable. This measures its\n * own content and sets `tabIndex={0}` only while it actually overflows, so the region is\n * scrollable with the arrow keys without adding a pointless tab stop when it does not.\n */\nexport const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>(function ScrollArea(\n { children, axis = 'vertical', label, className, ...rest },\n ref,\n) {\n const localRef = useRef<HTMLDivElement>(null);\n const [overflows, setOverflows] = useState(false);\n\n useEffect(() => {\n const node = localRef.current;\n if (!node) return;\n\n const measure = () => {\n const vertical = node.scrollHeight > node.clientHeight + 1;\n const horizontal = node.scrollWidth > node.clientWidth + 1;\n setOverflows(\n axis === 'vertical' ? vertical : axis === 'horizontal' ? horizontal : vertical || horizontal,\n );\n };\n\n measure();\n\n // Feature-detected: `ResizeObserver` is absent in jsdom, in Safari before 13.1, and\n // during any server render, and an unguarded `new ResizeObserver` throws outright. The\n // measurement above has already run, so without the observer the tab stop is simply\n // decided once at mount rather than tracked — degraded, not broken.\n if (typeof ResizeObserver === 'undefined') return;\n\n // Content and box size both matter, so both are observed. On the element it catches the\n // box; on the first child it catches content growth.\n const observer = new ResizeObserver(measure);\n observer.observe(node);\n if (node.firstElementChild) observer.observe(node.firstElementChild);\n return () => observer.disconnect();\n }, [axis, children]);\n\n return (\n <div\n ref={composeRefs<HTMLDivElement>(localRef, ref)}\n // `role=\"region\"` only once it is focusable and named, so an ordinary non-overflowing\n // wrapper does not add a landmark to the page for nothing.\n role={overflows && label ? 'region' : undefined}\n aria-label={overflows ? label : undefined}\n tabIndex={overflows ? 0 : undefined}\n data-slot=\"scroll-area\"\n className={clsx(\n OVERFLOW[axis],\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-1 focus-visible:ring-offset-default',\n SCROLLBAR_CLASSES,\n className,\n )}\n {...rest}\n >\n {children}\n </div>\n );\n});\n","import {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type KeyboardEvent as ReactKeyboardEvent,\n type MouseEvent as ReactMouseEvent,\n type ReactNode,\n type Ref,\n} from 'react';\nimport clsx from 'clsx';\nimport { LuEllipsis } from 'react-icons/lu';\n\nimport { Slot, composeRefs } from '../../lib/slot';\nimport { useControllableState } from '../../lib/use-controllable-state';\nimport { IconButton } from '../forms/icon-button';\nimport { useFloatingPanel, type FloatingAlign, type FloatingSide } from './floating';\nimport { MENU_PANEL_CLASSES, MenuContext, useMenu, useMenuState } from './menu-core';\nimport { useOverlayDismiss } from './overlay';\nimport { Portal } from './portal';\n\n/**\n * Overflow / row-actions menu, rebuilt in 0.4.0.\n *\n * The previous version declared `role=\"menu\"` and `role=\"menuitem\"` and implemented none of\n * the WAI-ARIA menu contract. Announcing a pattern you do not implement is worse than\n * announcing nothing, because a screen reader tells the user which keys should work. Fixed:\n * arrow keys, Home/End and typeahead; a roving tabindex (every item used to be a natively\n * tabbable `<button>`, so Tab walked all of them); focus moving into the menu on open and\n * back to the trigger on close; a portal, so the panel is no longer clipped by `Card`'s\n * `overflow-hidden` or `DataTable`'s `overflow-x-auto` — both named in its own doc comment\n * as the reason it exists; collision handling instead of a static `left`/`right`; correct\n * ARIA ownership; and `open`/`onOpenChange`, where open state used to be unreachable.\n *\n * The items and keyboard model now live in `menu-core`, shared with `ContextMenu` and\n * `Menubar`.\n *\n * Not implemented: submenus. Doing them properly needs hover intent and a safe-travel\n * polygon between trigger and panel, and a half-built version is worse than none — nest a\n * second `DropdownMenu` in the meantime.\n */\n\nexport interface DropdownMenuProps {\n children: ReactNode;\n /** Controlled open state. Omit for uncontrolled. */\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n}\n\n/**\n * The trigger element, for Floating UI to measure.\n *\n * Kept out of the shared menu context because only the trigger-anchored menus have one — a\n * `ContextMenu` positions at pointer coordinates and has no trigger element at all.\n */\ninterface TriggerNodeContextValue {\n triggerNode: HTMLElement | null;\n setTriggerNode: (node: HTMLElement | null) => void;\n}\n\nconst TriggerNodeContext = createContext<TriggerNodeContextValue | null>(null);\n\nfunction useTriggerNode(): TriggerNodeContextValue {\n const value = useContext(TriggerNodeContext);\n if (!value) throw new Error('DropdownMenu parts must be used inside <DropdownMenu>');\n return value;\n}\n\nexport function DropdownMenu({\n children,\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n}: DropdownMenuProps) {\n const [open, setOpen] = useControllableState({\n value: openProp,\n defaultValue: defaultOpen,\n onChange: onOpenChange,\n });\n\n const menu = useMenuState({ open, setOpen });\n // State, not a ref: `DropdownMenuContent` must re-render once the trigger exists so\n // Floating UI can measure it. A ref write would leave the panel positioned at 0,0.\n const [triggerNode, setTriggerNode] = useState<HTMLElement | null>(null);\n const anchor = useMemo(() => ({ triggerNode, setTriggerNode }), [triggerNode]);\n\n return (\n <MenuContext.Provider value={menu}>\n <TriggerNodeContext.Provider value={anchor}>{children}</TriggerNodeContext.Provider>\n </MenuContext.Provider>\n );\n}\n\n// ── Trigger ────────────────────────────────────────────────────────────────────\n\nexport interface DropdownMenuTriggerProps extends HTMLAttributes<HTMLElement> {\n children?: ReactNode;\n /** Render the child element instead of the default overflow button. */\n asChild?: boolean;\n /** Accessible name for the default `⋯` button. Ignored when `asChild` is set. */\n label?: string;\n}\n\nexport function DropdownMenuTrigger({\n children,\n asChild = false,\n label = 'Open menu',\n ...rest\n}: DropdownMenuTriggerProps) {\n const menu = useMenu('DropdownMenuTrigger');\n const anchor = useTriggerNode();\n\n const triggerProps = {\n id: menu.triggerId,\n 'aria-haspopup': 'menu' as const,\n 'aria-expanded': menu.open,\n 'aria-controls': menu.open ? menu.menuId : undefined,\n onClick: (event: ReactMouseEvent) => {\n event.stopPropagation();\n menu.setOpen(!menu.open);\n },\n onKeyDown: (event: ReactKeyboardEvent) => {\n // ArrowDown/Up open the menu and land on the near or far end, per the APG.\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n menu.pendingEdge.current = event.key === 'ArrowUp' ? 'end' : 'start';\n menu.setOpen(true);\n }\n },\n // Stopped at the trigger so the menu works inside a clickable table row — the\n // behaviour the 0.2.0 `RowMenu` had and the reason this component exists.\n onMouseDown: (event: ReactMouseEvent) => event.stopPropagation(),\n ...rest,\n };\n\n if (asChild) {\n return (\n <Slot ref={composeRefs(anchor.setTriggerNode)} {...triggerProps}>\n {children}\n </Slot>\n );\n }\n\n return (\n <IconButton\n ref={composeRefs(anchor.setTriggerNode) as Ref<HTMLButtonElement>}\n variant=\"neutral\"\n size=\"sm\"\n aria-label={label}\n {...triggerProps}\n >\n {children ?? <LuEllipsis className=\"size-4\" aria-hidden />}\n </IconButton>\n );\n}\n\n// ── Content ────────────────────────────────────────────────────────────────────\n\nexport interface DropdownMenuContentProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n side?: FloatingSide;\n align?: FloatingAlign;\n sideOffset?: number;\n}\n\nexport function DropdownMenuContent({\n children,\n side = 'bottom',\n align = 'end',\n sideOffset = 6,\n className,\n ...rest\n}: DropdownMenuContentProps) {\n const menu = useMenu('DropdownMenuContent');\n const anchor = useTriggerNode();\n const panelRef = useRef<HTMLDivElement | null>(null);\n const [panelNode, setPanelNode] = useState<HTMLDivElement | null>(null);\n\n // Must run before any item's render body, so indices are reassigned from zero on every\n // pass. Content is the direct parent of every item, so its render body is guaranteed to\n // run immediately before theirs.\n menu.resetIndices();\n\n const { refs, floatingStyles } = useFloatingPanel({\n open: menu.open,\n onOpenChange: menu.setOpen,\n side,\n align,\n sideOffset,\n });\n\n // Set in an effect rather than during render: `setReference` mutates Floating UI's\n // internal state, and calling it mid-render breaks under StrictMode double-invocation.\n useEffect(() => {\n refs.setReference(anchor.triggerNode);\n }, [refs, anchor.triggerNode]);\n\n useOverlayDismiss({\n onDismiss: () => menu.setOpen(false),\n panelRef,\n active: menu.open,\n // A menu is not modal: the page behind stays readable and interactive, so none of the\n // dialog-grade side effects apply. Only Escape, outside-press and focus restore.\n trapFocus: false,\n lockScroll: false,\n inertBackground: false,\n // The menu owns its own initial focus below; a generic \"first tabbable element\" scan\n // would fight the roving tabindex, which exposes exactly one item at a time.\n autoFocus: false,\n });\n\n // Keyed on the panel node rather than on `open`: `Portal` gates its children behind a\n // mounted flag for SSR safety, so on the render where the menu opens the items do not\n // exist yet, and an effect watching `open` alone fires against an empty item list.\n const wasOpen = useRef(false);\n useEffect(() => {\n if (menu.open && panelNode && !wasOpen.current) {\n menu.roving.moveToEdge(menu.pendingEdge.current);\n menu.pendingEdge.current = 'start';\n wasOpen.current = true;\n }\n if (!menu.open) wasOpen.current = false;\n }, [menu.open, panelNode, menu.roving, menu.pendingEdge]);\n\n if (!menu.open) return null;\n\n return (\n <Portal>\n <div\n ref={composeRefs<HTMLDivElement>(\n panelRef,\n (node) => setPanelNode(node),\n refs.setFloating,\n )}\n id={menu.menuId}\n role=\"menu\"\n // Programmatically focusable but out of the tab sequence: the fallback focus target\n // for an empty menu, and it keeps key events on the panel.\n tabIndex={-1}\n aria-orientation=\"vertical\"\n aria-labelledby={menu.triggerId}\n style={floatingStyles}\n className={clsx('z-dropdown', MENU_PANEL_CLASSES, className)}\n {...menu.roving.containerProps}\n onKeyDown={(event) => {\n menu.roving.containerProps.onKeyDown(event);\n // Tab must leave the menu rather than walk it, so it closes first and lets the\n // browser move focus on from the trigger.\n if (event.key === 'Tab') menu.setOpen(false);\n }}\n {...rest}\n >\n {children}\n </div>\n </Portal>\n );\n}\n\n// ── Items, re-exported under the DropdownMenu* names ───────────────────────────\n// The implementations live in `menu-core` and are shared with ContextMenu and Menubar.\n\nexport {\n MenuItem as DropdownMenuItem,\n MenuCheckboxItem as DropdownMenuCheckboxItem,\n MenuRadioGroup as DropdownMenuRadioGroup,\n MenuRadioItem as DropdownMenuRadioItem,\n MenuGroup as DropdownMenuGroup,\n MenuLabel as DropdownMenuLabel,\n MenuSeparator as DropdownMenuSeparator,\n MenuShortcut as DropdownMenuShortcut,\n} from './menu-core';\n\nexport type {\n MenuItemProps as DropdownMenuItemProps,\n MenuItemTone as DropdownMenuItemTone,\n MenuCheckboxItemProps as DropdownMenuCheckboxItemProps,\n MenuRadioGroupProps as DropdownMenuRadioGroupProps,\n MenuRadioItemProps as DropdownMenuRadioItemProps,\n MenuGroupProps as DropdownMenuGroupProps,\n MenuLabelProps as DropdownMenuLabelProps,\n MenuSeparatorProps as DropdownMenuSeparatorProps,\n MenuShortcutProps as DropdownMenuShortcutProps,\n} from './menu-core';\n","import { type HTMLAttributes, type ReactNode } from 'react';\nimport clsx from 'clsx';\nimport { Slot } from '../../lib/slot';\n\nexport type NavItemSize = 'sm' | 'md';\nexport type NavItemVariant = 'card' | 'plain';\n\nexport interface NavItemProps extends Omit<HTMLAttributes<HTMLElement>, 'children'> {\n /**\n * Leading glyph. Optional, but when `collapsed` it is the only thing left to look at, so\n * a row without one falls back to a monogram taken from the label.\n */\n icon?: ReactNode;\n label: string;\n /**\n * The row you are on — the current route. Paints the brand fill and, under `asChild`, sets\n * `aria-current=\"page\"`; it also reflects to `data-state=\"active\"`.\n *\n * Not the same thing as focus, though Figma's `MenuButton` names this variant\n * `state=focussed`. That is a misnomer to fix on the design side: keyboard focus is the\n * ring below, it is transient and can land on any row, and if it painted the brand fill\n * too there would be no way to see which row is the current one while tabbing past it.\n */\n isActive?: boolean;\n /** Red indicator dot at top-right corner (e.g. unread count) */\n indicator?: boolean;\n /** Collapsed mode — label is visually hidden but stays in the accessibility tree. */\n collapsed?: boolean;\n /** Row height. `sm` suits a dense secondary nav or a nested group. */\n size?: NavItemSize;\n /**\n * Row weight.\n *\n * `card` gives every inactive row its own border and surface — right for a short primary\n * rail of eight or ten destinations, where each row is a target in its own right.\n * `plain` drops both, so an inactive row is just text: right for a long grouped nav,\n * where thirty bordered pills read as thirty cards rather than a list.\n *\n * A variant decides what a row looks like **at rest, and nothing else**. Hover and the\n * current-row fill are one vocabulary shared by both — see `STATE`. Through 0.5.0 each\n * variant defined its own, and `plain`'s was invisible against the rail it was written for.\n */\n variant?: NavItemVariant;\n /**\n * Render the single child element as the row, so the row itself is the link or button.\n *\n * `NavItem` is presentational by default — a `<span>` with hover styling. That is fine\n * inside a router `NavLink`, which is how `src/sidebar/` uses it, but on its own it is a\n * nav row that looks interactive and cannot be focused or activated. `asChild` is the\n * fix for anyone using it outside that wrapper:\n *\n * <NavItem asChild icon={<LuHouse />} label=\"Home\">\n * <NavLink to=\"/\" />\n * </NavItem>\n */\n asChild?: boolean;\n children?: ReactNode;\n className?: string;\n}\n\n/**\n * `md` is Figma's `MenuButton` in every dimension: 44px tall, 12px radius, 12px gap, 14px\n * padding-x. `square` is the collapsed footprint — the design does not stretch a collapsed row\n * to the rail width, it centres a 44×44 button, which is exactly `px-3.5` either side of a\n * 16px icon.\n *\n * The label is the one deliberate departure. Figma specifies Body Base (16px); this is\n * `text-body-sm` (14px), because 16px rows read as oversized against everything else in a\n * product shell. The design file is the authority on the box and the colours, not on this.\n *\n * `sm` has no counterpart in the design file; it is a code-only density for long navs.\n */\nconst SIZE: Record<NavItemSize, { box: string; label: string; square: string }> = {\n sm: { box: 'h-9 gap-2 rounded-lg px-3 py-1.5', label: 'text-body-xs', square: 'w-9' },\n md: { box: 'h-11 gap-3 rounded-xl px-3.5 py-2.5', label: 'text-body-sm', square: 'w-11' },\n};\n\n/**\n * Up to two characters standing in for a missing icon in the collapsed rail.\n *\n * Two rather than one because any nav long enough to be worth collapsing collides on first\n * letters — the docs' own sidebar has Button beside Badges and Input beside Icons. Multiple\n * words give one initial each (\"Color Tokens\" → \"CT\"); a single word gives its first two\n * (\"Button\" → \"Bu\"), which stays readable rather than shouting. Words that open with\n * punctuation are skipped, so \"Cards & Containers\" is \"CC\" and not \"C&\".\n */\nfunction monogram(label: string): string {\n const words = label.split(/\\s+/).filter((word) => /^[\\p{L}\\p{N}]/u.test(word));\n const [first, second] = words;\n // A label of nothing but symbols has no initials to take; show its first character.\n if (!first) return Array.from(label.trim())[0]?.toUpperCase() ?? '';\n if (second) return (Array.from(first)[0] + Array.from(second)[0]).toUpperCase();\n const [a, b] = Array.from(first);\n return a.toUpperCase() + (b ?? '').toLowerCase();\n}\n\n/**\n * What a row looks like when you are on it, and when you hover one you are not — shared by\n * both variants and declared nowhere else. Selection means one thing in this library.\n *\n * Through 0.5.0 each variant carried its own pair, and `plain`'s hover was\n * `hover:bg-default-hover` — a generic app-surface token resolving to `--prim-gray-100`,\n * which is the same #F5F5F5 as the `bg-default-secondary` rail a sidebar row sits on. Plain\n * rows therefore had **no visible hover at all** in light mode, and `plain`'s active\n * `bg-brand-tertiary` (#F1F7FD) was a 1.02:1 step against that same rail. The bug survived\n * because the docs demo every variant on `bg-default` — white — where the collision does\n * not occur.\n */\nconst STATE = {\n // Figma MenuButton `state=hover`. No border change: Figma holds it at #D9D9D9 through\n // hover and moves only the fill and the text.\n hover: 'hover:bg-brand-tertiary-hover hover:text-brand',\n // Figma MenuButton `state=focussed`, which is that file's name for selected — see the\n // `isActive` doc comment. Hovering the current row darkens it rather than reverting it to\n // the pale hover tint, which would read as the row losing its selection under the cursor.\n active: 'bg-brand text-brand-on-brand hover:bg-brand-hover',\n} as const;\n\n/** The resting appearance — the whole of the difference between the two variants. */\nconst VARIANT: Record<NavItemVariant, { active: string; inactive: string }> = {\n card: {\n // `border-transparent` holds the 1px so the icon and label do not shift outward when a\n // row becomes the current one — the same trick, for the same reason, as `button.tsx`.\n // `bg-brand` still paints edge to edge, since backgrounds clip to the border box.\n active: clsx('border border-transparent', STATE.active),\n inactive: clsx('border border-default bg-default text-default-secondary', STATE.hover),\n },\n plain: {\n active: STATE.active,\n inactive: clsx('text-default-secondary', STATE.hover),\n },\n};\n\nexport function NavItem({\n icon,\n label,\n isActive = false,\n indicator = false,\n collapsed = false,\n size = 'md',\n variant = 'card',\n asChild = false,\n children,\n className,\n ...rest\n}: NavItemProps) {\n const s = SIZE[size];\n const v = VARIANT[variant];\n\n const rootProps = {\n // Only meaningful when `asChild` renders a real link — a decorative span with\n // aria-current would be a lie.\n 'aria-current': asChild && isActive ? ('page' as const) : undefined,\n // Same attribute the disclosure components and `TableRow` use, so the state is\n // addressable from a consumer's CSS. `active` rather than `selected`: a nav row is where\n // you *are* (`aria-current=\"page\"`), which is a different thing from a row you have\n // *picked* (`aria-selected`) — `TableRow` is the latter and says `selected`.\n 'data-state': isActive ? ('active' as const) : undefined,\n className: clsx(\n 'relative flex items-center transition-colors',\n // Unconditional: a row can also become focusable by the caller putting it inside a\n // link, which is how `src/sidebar/` uses it. A ring that only appears under `asChild`\n // leaves that case with no visible focus at all.\n // The offset is `default-secondary`, not `default`: a nav row's home is the sidebar\n // rail, and `ring-offset-default` is #FFFFFF, so a focused `plain` row drew a 1px white\n // halo on the #F5F5F5 rail. The offset itself is load-bearing — in dark mode\n // `ring-brand` and `bg-brand` are both #F1F7FD, so without it a focused *active* row\n // would have no visible ring at its inner edge.\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-1 focus-visible:ring-offset-default-secondary',\n s.box,\n // Expanded rows fill the nav; a collapsed one is a centred square, not a stretched row.\n collapsed ? clsx('justify-center', s.square) : 'w-full',\n isActive ? v.active : v.inactive,\n className,\n ),\n ...rest,\n };\n\n const content = (\n <>\n {icon != null ? (\n <span aria-hidden className=\"shrink-0 [&_svg]:size-4\">\n {icon}\n </span>\n ) : (\n // Icons are optional, but the collapsed rail hides the label — so a row with neither\n // renders as an empty box. Through 0.5.0 that is exactly what a nav without icons\n // collapsed to, the docs site's own 36-row sidebar included. The monogram is\n // decorative in the same way the icon is: the `sr-only` label below still names the\n // row.\n collapsed && (\n <span\n aria-hidden\n // `min-w-4` rather than `size-4`: the box matches the 16px an icon would take,\n // but a wide pair like \"MW\" is allowed to outgrow it rather than be clipped.\n className=\"flex h-4 min-w-4 shrink-0 items-center justify-center text-body-tiny font-semibold leading-none tracking-tight\"\n >\n {monogram(label)}\n </span>\n )\n )}\n {/* `collapsed` hides the label visually but keeps it in the accessibility tree.\n Through 0.3.0 the span was not rendered at all and no `aria-label` replaced it,\n so a collapsed sidebar was nine unlabelled icon links — the icon itself carries\n no name, being decorative. */}\n <span\n className={clsx(\n 'min-w-0 truncate font-regular leading-snug',\n s.label,\n collapsed ? 'sr-only' : 'flex-1',\n )}\n >\n {label}\n </span>\n {indicator && (\n <span\n // The dot is the only carrier of \"there is something new here\", so it needs a\n // name of its own rather than being purely decorative.\n role=\"status\"\n aria-label=\"Unread\"\n className=\"absolute -right-px -top-px size-2 rounded-full bg-danger\"\n />\n )}\n </>\n );\n\n if (asChild) {\n // The child element becomes the row itself, so the icon/label/indicator go inside it\n // and its own children (usually nothing, since the label comes from `label`) are\n // discarded in favour of the composed content.\n return (\n <Slot wrapChildren={() => content} {...rootProps}>\n {children}\n </Slot>\n );\n }\n\n return <span {...rootProps}>{content}</span>;\n}\n","import { NavLink } from 'react-router-dom';\nimport clsx from 'clsx';\n\nimport { SCROLLBAR_CLASSES } from '../lib/scrollbar';\n\nexport interface NavTabItem {\n to: string;\n label: string;\n /** Match this route exactly — needed for an index route like `/account`. */\n end?: boolean;\n}\n\nexport interface NavTabsProps {\n items: NavTabItem[];\n className?: string;\n}\n\n/** Route-driven tab strip, backed by react-router's `NavLink`.\n *\n * Use this when each tab is its own route. For state-driven tabs that do not change\n * the URL, use `Tabs` from the main entry point instead — it has no router dependency.\n *\n * The active underline and label use `brand-emphasis` (#0C2D58), the brand fill colour\n * Figma specifies for a tab rule, rather than the lighter `brand` border token. */\nexport function NavTabs({ items, className }: NavTabsProps) {\n return (\n // The links inside are the tab stops.\n <div\n className={clsx(\n 'flex gap-6 overflow-x-auto border-b border-default',\n SCROLLBAR_CLASSES,\n className,\n )}\n >\n {items.map((tab) => (\n <NavLink\n key={tab.to}\n to={tab.to}\n end={tab.end}\n className={({ isActive }) =>\n clsx(\n 'shrink-0 whitespace-nowrap border-b-2 pb-3 text-body-sm transition-colors',\n isActive\n ? 'border-brand-emphasis font-semibold text-brand-emphasis'\n : 'border-transparent font-medium text-default-secondary hover:text-default',\n )\n }\n >\n {tab.label}\n </NavLink>\n ))}\n </div>\n );\n}\n"],"mappings":"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","names":["import_react_router_dom","import_lu","Slot","clsx","import_react","import_react","import_clsx","import_jsx_runtime","clsx","IconButton","size","import_clsx","import_jsx_runtime","sizeStyles","size","clsx","import_clsx","import_react","import_react","import_react","import_clsx","import_clsx","clsx","clsx","import_react","import_react","lockScroll","import_react","import_react","import_clsx","import_jsx_runtime","clsx","import_react","import_clsx","import_jsx_runtime","clsx","import_react","import_clsx","import_jsx_runtime","ScrollArea","clsx","import_react","import_clsx","import_lu","import_jsx_runtime","clsx","import_clsx","import_jsx_runtime","clsx","size","import_clsx","import_jsx_runtime","clsx","import_jsx_runtime","size"]}
@@ -1,4 +1,8 @@
1
1
  import * as react from 'react';
2
+ import { ReactNode, HTMLAttributes } from 'react';
3
+
4
+ type NavItemSize = 'sm' | 'md';
5
+ type NavItemVariant = 'card' | 'plain';
2
6
 
3
7
  interface NavTabItem {
4
8
  to: string;
@@ -19,14 +23,109 @@ interface NavTabsProps {
19
23
  * Figma specifies for a tab rule, rather than the lighter `brand` border token. */
20
24
  declare function NavTabs({ items, className }: NavTabsProps): react.JSX.Element;
21
25
 
26
+ interface SidebarNavItem {
27
+ to: string;
28
+ label: string;
29
+ /** Optional — a collapsed row without one falls back to a monogram of its label. */
30
+ icon?: ReactNode;
31
+ /** Red dot on the row — something new behind this destination. */
32
+ indicator?: boolean;
33
+ /** Match the path exactly rather than as a prefix. Needed for an index route. */
34
+ end?: boolean;
35
+ }
36
+ interface SidebarNavGroup {
37
+ /**
38
+ * Uppercase heading above the group. Omit for an ungrouped run of rows.
39
+ *
40
+ * Kept in the accessibility tree when the rail is collapsed rather than dropped: it is the
41
+ * only thing saying what the run of icons below it has in common.
42
+ */
43
+ title?: string;
44
+ items: SidebarNavItem[];
45
+ }
22
46
  interface AppSidebarProps {
23
- collapsed: boolean;
24
- onToggle: () => void;
25
- isAdmin?: boolean;
26
- userName: string;
27
- userRole: string;
47
+ /** The scrolling nav. One untitled group is a flat list. */
48
+ groups: SidebarNavGroup[];
49
+ /** Rows pinned below the scrolling nav — settings, help, a sign-out link. */
50
+ footerNav?: SidebarNavItem[];
51
+ /**
52
+ * Brand lockup. Given a function it receives `collapsed`, so the caller can swap a full
53
+ * lockup for a mark alone rather than having one squeezed into an 80px rail.
54
+ */
55
+ brand?: ReactNode | ((state: {
56
+ collapsed: boolean;
57
+ }) => ReactNode);
58
+ /** Bottom slot, below a divider — a user card, a version pill, nothing. */
59
+ footer?: ReactNode;
60
+ /**
61
+ * Whether the rail can collapse at all.
62
+ *
63
+ * `false` locks it open: no toggle button, and `collapsed` is ignored rather than half
64
+ * honoured. A collapsed rail is 80px of icons, so it only earns its place in a nav short
65
+ * enough and iconographic enough to survive the loss of its labels — a long text nav
66
+ * (the docs site's own, for one) is better off never offering it.
67
+ */
68
+ collapsible?: boolean;
69
+ collapsed?: boolean;
70
+ /** Omit and the collapse toggle is not rendered, though `collapsed` still applies. */
71
+ onToggle?: () => void;
72
+ size?: NavItemSize;
73
+ variant?: NavItemVariant;
74
+ /** Expanded width, as a Tailwind class. */
75
+ width?: string;
76
+ collapsedWidth?: string;
77
+ className?: string;
78
+ }
79
+ /**
80
+ * The application nav rail: brand, a grouped list of destinations, and a footer.
81
+ *
82
+ * Lives on its own entry point (`@platned/ui/sidebar`) because it uses `NavLink`, and
83
+ * `react-router-dom` is an optional peer dependency.
84
+ *
85
+ * Through 0.4.0 this was not a library component at all. It hardcoded ten product routes,
86
+ * their labels and their icons, an `SCIC` wordmark, and an `isAdmin` flag gating two of the
87
+ * rows — so the only app it could render was the one it was extracted from, and the docs
88
+ * site kept a second, unrelated sidebar of its own rather than use it. Everything
89
+ * app-specific is now a prop or a slot, and the docs site runs on this component.
90
+ *
91
+ * Width is an explicit prop rather than something `className` overrides: the width is
92
+ * conditional on `collapsed`, so a `className="w-64"` would tailwind-merge away the
93
+ * collapsed width too and break the collapse. Everything else about the shell is fair game
94
+ * for `className` — `rounded-none` to flatten it for a full-bleed layout, for instance.
95
+ */
96
+ declare function AppSidebar({ groups, footerNav, brand, footer, collapsible, collapsed: collapsedProp, onToggle, size, variant, width, collapsedWidth, className, }: AppSidebarProps): react.JSX.Element;
97
+ type SidebarSeparatorProps = HTMLAttributes<HTMLDivElement>;
98
+ /**
99
+ * The rule between nav groups. `AppSidebar` draws one above every group after the first;
100
+ * this is exported so the same line can be used in the `footer` slot or in a hand-composed
101
+ * rail without anyone re-deriving `h-px bg-divider` and inevitably picking a different inset.
102
+ *
103
+ * Decorative by default. The grouping it marks is already carried semantically by each
104
+ * group's heading — which stays in the accessibility tree even when the rail is collapsed and
105
+ * the heading is visually hidden — so announcing the rule as well is redundant. Pass
106
+ * `role="separator"` through if you are using it somewhere that has no heading to lean on.
107
+ */
108
+ declare function SidebarSeparator({ className, ...rest }: SidebarSeparatorProps): react.JSX.Element;
109
+ interface SidebarUserCardProps {
110
+ name: string;
111
+ role: string;
28
112
  onLogout: () => void;
113
+ collapsed?: boolean;
114
+ /** Extra menu entries above Sign out. */
115
+ menu?: ReactNode;
116
+ className?: string;
29
117
  }
30
- declare function AppSidebar({ collapsed, onToggle, isAdmin, userName, userRole, onLogout }: AppSidebarProps): react.JSX.Element;
118
+ /**
119
+ * The signed-in user block, for `AppSidebar`'s `footer` slot.
120
+ *
121
+ * Was private, and its props were on `AppSidebar` itself — which meant every consumer of the
122
+ * sidebar had to have a user, a role and a logout handler whether it had a concept of any
123
+ * of them or not. The docs site has none, and passes no footer.
124
+ *
125
+ * Its menu is `DropdownMenu` rather than the hand-rolled click-outside popover it was: that
126
+ * one had no keyboard contract and, being absolutely positioned, was clipped by the
127
+ * collapsed rail. `DropdownMenu` portals.
128
+ */
129
+ declare function SidebarUserCard({ name, role, onLogout, collapsed, menu, className, }: SidebarUserCardProps): react.JSX.Element;
31
130
 
32
- export { AppSidebar, type NavTabItem, NavTabs, type NavTabsProps };
131
+ export { AppSidebar, type AppSidebarProps, type NavTabItem, NavTabs, type NavTabsProps, type SidebarNavGroup, type SidebarNavItem, SidebarSeparator, type SidebarSeparatorProps, SidebarUserCard, type SidebarUserCardProps };