@misoto22/design 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/agent/Accordion.md +1 -1
  2. package/dist/agent/Alert.md +1 -1
  3. package/dist/agent/AppShell.md +1 -1
  4. package/dist/agent/ArchitectureFigure.md +1 -1
  5. package/dist/agent/AreaChart.md +1 -1
  6. package/dist/agent/Article.md +1 -1
  7. package/dist/agent/AspectRatio.md +1 -1
  8. package/dist/agent/Avatar.md +1 -1
  9. package/dist/agent/Badge.md +1 -1
  10. package/dist/agent/BarChart.md +1 -1
  11. package/dist/agent/BarList.md +1 -1
  12. package/dist/agent/BigNumber.md +1 -1
  13. package/dist/agent/BoxPlot.md +1 -1
  14. package/dist/agent/Breadcrumb.md +1 -1
  15. package/dist/agent/BulletChart.md +1 -1
  16. package/dist/agent/Button.md +1 -1
  17. package/dist/agent/Calendar.md +1 -1
  18. package/dist/agent/Card.md +1 -1
  19. package/dist/agent/Checkbox.md +1 -1
  20. package/dist/agent/Code.md +1 -1
  21. package/dist/agent/CodeBlock.md +1 -1
  22. package/dist/agent/Collapsible.md +1 -1
  23. package/dist/agent/ColorPicker.md +1 -1
  24. package/dist/agent/Combobox.md +1 -1
  25. package/dist/agent/Command.md +1 -1
  26. package/dist/agent/ComposedChart.md +1 -1
  27. package/dist/agent/ContextMenu.md +1 -1
  28. package/dist/agent/DataflowFigure.md +1 -1
  29. package/dist/agent/DatePicker.md +1 -1
  30. package/dist/agent/DescriptionList.md +1 -1
  31. package/dist/agent/Diagram.md +1 -1
  32. package/dist/agent/DiagramCanvas.md +1 -1
  33. package/dist/agent/DiagramExportMenu.md +1 -1
  34. package/dist/agent/DiagramInspector.md +1 -1
  35. package/dist/agent/DiagramLegend.md +1 -1
  36. package/dist/agent/DiagramMinimap.md +1 -1
  37. package/dist/agent/DiagramToolbar.md +1 -1
  38. package/dist/agent/Dialog.md +1 -1
  39. package/dist/agent/DropdownMenu.md +1 -1
  40. package/dist/agent/EmptyState.md +1 -1
  41. package/dist/agent/ErrorState.md +1 -1
  42. package/dist/agent/Facet.md +1 -1
  43. package/dist/agent/Field.md +1 -1
  44. package/dist/agent/FigureBand.md +1 -1
  45. package/dist/agent/FloatingIconButton.md +1 -1
  46. package/dist/agent/FunnelChart.md +1 -1
  47. package/dist/agent/Heading.md +1 -1
  48. package/dist/agent/Heatmap.md +1 -1
  49. package/dist/agent/Histogram.md +1 -1
  50. package/dist/agent/Input.md +1 -1
  51. package/dist/agent/Kbd.md +2 -2
  52. package/dist/agent/LifecycleFigure.md +1 -1
  53. package/dist/agent/LineChart.md +1 -1
  54. package/dist/agent/LinkArrow.md +1 -1
  55. package/dist/agent/Markdown.md +1 -1
  56. package/dist/agent/NativeSelect.md +1 -1
  57. package/dist/agent/NavItem.md +1 -1
  58. package/dist/agent/NumberField.md +1 -1
  59. package/dist/agent/Pagination.md +1 -1
  60. package/dist/agent/PieChart.md +1 -1
  61. package/dist/agent/Popover.md +1 -1
  62. package/dist/agent/Progress.md +1 -1
  63. package/dist/agent/RadarChart.md +1 -1
  64. package/dist/agent/RadialChart.md +1 -1
  65. package/dist/agent/RadioGroup.md +1 -1
  66. package/dist/agent/SankeyChart.md +1 -1
  67. package/dist/agent/ScatterChart.md +1 -1
  68. package/dist/agent/ScrollArea.md +1 -1
  69. package/dist/agent/SearchableMenu.md +1 -1
  70. package/dist/agent/Select.md +1 -1
  71. package/dist/agent/Separator.md +1 -1
  72. package/dist/agent/SequenceFigure.md +1 -1
  73. package/dist/agent/Sheet.md +1 -1
  74. package/dist/agent/Sidebar.md +2 -2
  75. package/dist/agent/Skeleton.md +1 -1
  76. package/dist/agent/Slider.md +1 -1
  77. package/dist/agent/Sparkline.md +1 -1
  78. package/dist/agent/Spinner.md +1 -1
  79. package/dist/agent/StatusDot.md +1 -1
  80. package/dist/agent/StatusPill.md +1 -1
  81. package/dist/agent/Steps.md +1 -1
  82. package/dist/agent/Switch.md +1 -1
  83. package/dist/agent/Table.md +1 -1
  84. package/dist/agent/Tabs.md +1 -1
  85. package/dist/agent/Tag.md +1 -1
  86. package/dist/agent/Text.md +1 -1
  87. package/dist/agent/Textarea.md +1 -1
  88. package/dist/agent/Timestamp.md +1 -1
  89. package/dist/agent/Toast.md +1 -1
  90. package/dist/agent/ToggleGroup.md +1 -1
  91. package/dist/agent/Toolbar.md +1 -1
  92. package/dist/agent/Tooltip.md +1 -1
  93. package/dist/agent/TreemapChart.md +1 -1
  94. package/dist/agent/WaterfallChart.md +1 -1
  95. package/dist/agent/WorkflowFigure.md +1 -1
  96. package/dist/agent/catalog.json +2 -1
  97. package/dist/agent/index.md +2 -2
  98. package/dist/charts/lib/chart.js +16 -1
  99. package/dist/charts/lib/chart.js.map +1 -1
  100. package/dist/charts/lib/empty.js +12 -1
  101. package/dist/charts/lib/empty.js.map +1 -1
  102. package/dist/components/AppShell/AppShell.js +2 -2
  103. package/dist/components/AppShell/AppShell.js.map +1 -1
  104. package/dist/components/Button/Button.js +3 -3
  105. package/dist/components/Button/Button.js.map +1 -1
  106. package/dist/components/Kbd/Kbd.d.ts +7 -0
  107. package/dist/components/Kbd/Kbd.js +1 -1
  108. package/dist/components/Kbd/Kbd.js.map +1 -1
  109. package/dist/components/Sidebar/Sidebar.d.ts +8 -0
  110. package/dist/components/Sidebar/Sidebar.js +2 -2
  111. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  112. package/dist/semantic.css +4 -2
  113. package/dist/styles.css +1 -1
  114. package/dist/themes.css +91 -0
  115. package/dist/tokens.css +67 -1
  116. package/dist/tokens.d.ts +2 -0
  117. package/dist/tokens.js +14 -3
  118. package/dist/tokens.json +14 -3
  119. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Button/Button.tsx"],"sourcesContent":["import { Slot } from '@radix-ui/react-slot'\nimport type { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { DEV, warn } from '../../lib/warn'\nimport { Spinner } from '../Spinner/Spinner'\n\nexport type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger'\nexport type ButtonSize = 'sm' | 'md' | 'lg'\n\n/**\n * The base every variant shares.\n *\n * It reads `--radius`, the CONTROL step, which is the same corner an `Input`,\n * a `Select` trigger and a `Textarea` draw. It used to be a pill, and a pill\n * beside an 8px field is two different ideas of what a control is — the one\n * inconsistency in the system that every reader noticed and none of the\n * component pages could explain. Everything on the control line now agrees,\n * and the capsule is kept for the things that genuinely are one: a badge, a\n * status pill, a segmented strip, a progress track.\n *\n * It does not move on hover. An earlier base lifted a pixel and asked for a\n * shadow the White Reset does not have — `--shadow` resolves to `none`, so that\n * hover state was invisible on every button in the system while still costing a\n * transition. Opacity carries the filled hover; the border carries the outlined\n * one.\n *\n * The transition names its properties rather than using `all`, so a layout\n * change (a width that grows when the label becomes \"Saving…\") is instant\n * instead of sliding.\n */\nconst BASE =\n 'inline-flex items-center justify-center gap-(--control-gap) rounded-(--radius) font-sans transition-[opacity,color,border-color,background-color] duration-(--duration-fast) ease-(--ease-out-expo) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none aria-disabled:opacity-(--disabled-opacity) aria-disabled:pointer-events-none'\n\nconst VARIANT: Record<ButtonVariant, string> = {\n primary: 'bg-(--accent) text-(--accent-foreground) border border-(--accent) hover:opacity-85',\n secondary: 'border border-(--rule-2) text-(--ink) bg-transparent hover:border-(--ink)',\n ghost: 'border border-transparent text-(--ink-2) hover:text-(--ink) hover:bg-(--stone)',\n /* The one place chroma is allowed on a control, and only because the action\n is destructive — that is a state, not a brand colour. */\n danger: 'bg-(--danger) text-(--paper) border border-(--danger) hover:opacity-85',\n}\n\n/**\n * `min-h` rather than `h`, so a button that wraps to two lines grows instead of\n * clipping.\n *\n * The heights come from the density tokens rather than from a literal, so a\n * subtree marked `data-density=\"compact\"` shrinks its controls without a single\n * call site being told. At the default density `md` is 44px — the pointer\n * target WCAG 2.5.5 asks for.\n */\nconst SIZE: Record<ButtonSize, string> = {\n sm: 'min-h-(--control-h-sm) px-(--control-px-sm) py-(--control-py-sm) text-[13px]',\n md: 'min-h-(--control-h-md) px-(--control-px-md) py-(--control-py-md) text-sm',\n lg: 'min-h-(--control-h-lg) px-(--control-px-lg) py-(--control-py-lg) text-[15px]',\n}\n\n/** Square, because an icon has no label to give the box its width. */\nconst ICON_SIZE: Record<ButtonSize, string> = {\n sm: 'size-(--control-h-sm) p-0',\n md: 'size-(--control-h-md) p-0',\n lg: 'size-(--control-h-lg) p-0',\n}\n\nconst SPINNER_SIZE: Record<ButtonSize, 'sm' | 'md'> = { sm: 'sm', md: 'sm', lg: 'md' }\n\ninterface CommonProps {\n children?: ReactNode\n /**\n * Which action this is. `primary` is the one thing the screen wants you to\n * do, so there is at most one per view; `danger` is reserved for destructive\n * actions and is the only place chroma is allowed on a control.\n */\n variant?: ButtonVariant\n /** 36 / 44 / 48px tall. `md` meets the pointer-target floor on its own. */\n size?: ButtonSize\n /** Optional mono keycap glyph rendered after the label (e.g. \"P\"). */\n keycap?: string\n /**\n * Swaps the leading content for a spinner and blocks interaction. The label\n * stays put — a button that empties out while it works loses its width, and\n * the page reflows under the pointer that just clicked it.\n */\n loading?: boolean\n /**\n * Square control with no label. REQUIRES `aria-label`: an icon-only button\n * with no accessible name is invisible to a screen reader, and this is the\n * single most common way a design system ships an unusable control.\n */\n iconOnly?: boolean\n /**\n * Render the child element instead of a `<button>`, keeping these styles.\n * Use it to hand the styling to a router's `Link` — `asChild` is what keeps\n * this package free of any one router.\n *\n * The decoration slots (`keycap`, `loading`) are not injected into a slotted\n * child; compose them inside it yourself.\n *\n * @example\n * <Button asChild><Link href=\"/work\">Work</Link></Button>\n */\n asChild?: boolean\n className?: string\n}\n\ntype ButtonAsLink = CommonProps &\n Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className' | 'children'> & { href: string }\n\ntype ButtonAsButton = CommonProps &\n Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'children'> & { href?: undefined }\n\nexport type ButtonProps = ButtonAsLink | ButtonAsButton\n\nfunction Keycap({ glyph }: { glyph: string }) {\n return (\n <span className=\"rounded-(--radius-sm) border border-current px-2 py-0.5 font-mono text-[11px] opacity-55\">\n {glyph}\n </span>\n )\n}\n\n/**\n * The system's action.\n *\n * Renders a `<button>`, or an `<a>` when given `href`, or whatever you hand it\n * via `asChild`. No router is imported, so the package stays framework-agnostic\n * and a Next or React Router app wires its own `Link` at the call site.\n *\n * Server-component friendly: there is no client boundary here, so it renders in\n * a static page as well as an interactive one.\n *\n * @example\n * <Button variant=\"secondary\" keycap=\"P\">View projects</Button>\n * @example\n * <Button loading disabled>Saving…</Button>\n * @example\n * <Button iconOnly aria-label=\"Close\"><X size={16} /></Button>\n */\nexport function Button(props: ButtonProps) {\n const {\n children,\n variant = 'primary',\n size = 'md',\n keycap,\n loading = false,\n iconOnly = false,\n asChild = false,\n className,\n } = props\n\n const cls = cn(BASE, VARIANT[variant], iconOnly ? ICON_SIZE[size] : SIZE[size], className)\n\n if (DEV && iconOnly) {\n // The control renders correctly and is announced as \"button\" and nothing\n // else. Nothing about the rendered page says so, which is why this is the\n // single most common way a design system ships an unusable control.\n const named =\n typeof props['aria-label'] === 'string' && props['aria-label'].trim() !== ''\n const described = typeof props['aria-labelledby'] === 'string'\n if (!named && !described) {\n warn({\n code: 'BUTTON_ICON_ONLY_UNNAMED',\n problem:\n 'An iconOnly Button has no text, so without an accessible name a screen reader announces it as \"button\" and nothing else.',\n field: 'aria-label',\n fix: 'Add aria-label with the action the button performs, e.g. aria-label=\"Delete invoice\".',\n component: 'Button',\n })\n }\n }\n\n // A filled ground needs a spinner in the inherited colour; an outlined one\n // reads better against the page's own rule scale.\n const spinnerTone = variant === 'primary' || variant === 'danger' ? 'current' : 'default'\n const decoration = (\n <>\n {loading && <Spinner size={SPINNER_SIZE[size]} tone={spinnerTone} label={null} />}\n {children}\n {keycap && <Keycap glyph={keycap} />}\n </>\n )\n\n if (asChild) {\n const { children: _c, variant: _v, size: _s, keycap: _k, loading: _l, iconOnly: _i, asChild: _a, className: _cn, ...rest } = props\n return (\n <Slot className={cls} {...rest}>\n {children}\n </Slot>\n )\n }\n\n if (props.href !== undefined) {\n const { children: _c, variant: _v, size: _s, keycap: _k, loading: _l, iconOnly: _i, asChild: _a, className: _cn, href, ...rest } = props\n return (\n <a\n href={href}\n className={cls}\n // A link cannot be `disabled`; `aria-disabled` plus the pointer-events\n // rule in BASE is the accessible equivalent.\n aria-disabled={loading || undefined}\n {...rest}\n >\n {decoration}\n </a>\n )\n }\n\n const { children: _c, variant: _v, size: _s, keycap: _k, loading: _l, iconOnly: _i, asChild: _a, className: _cn, ...rest } = props\n return (\n <button\n type={rest.type ?? 'button'}\n aria-busy={loading || undefined}\n disabled={rest.disabled || loading}\n className={cls}\n {...rest}\n >\n {decoration}\n </button>\n )\n}\n\nexport default Button\n"],"mappings":"AAmHI,SA4DA,UA5DA,KA4DA,YA5DA;AAnHJ,SAAS,YAAY;AAErB,SAAS,UAAU;AACnB,SAAS,KAAK,YAAY;AAC1B,SAAS,eAAe;AA0BxB,MAAM,OACJ;AAEF,MAAM,UAAyC;AAAA,EAC7C,SAAS;AAAA,EACT,WAAW;AAAA,EACX,OAAO;AAAA;AAAA;AAAA,EAGP,QAAQ;AACV;AAWA,MAAM,OAAmC;AAAA,EACvC,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAGA,MAAM,YAAwC;AAAA,EAC5C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,eAAgD,EAAE,IAAI,MAAM,IAAI,MAAM,IAAI,KAAK;AAiDrF,SAAS,OAAO,EAAE,MAAM,GAAsB;AAC5C,SACE,oBAAC,UAAK,WAAU,4FACb,iBACH;AAEJ;AAmBO,SAAS,OAAO,OAAoB;AACzC,QAAM;AAAA,IACJ;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,IACP;AAAA,IACA,UAAU;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV;AAAA,EACF,IAAI;AAEJ,QAAM,MAAM,GAAG,MAAM,QAAQ,OAAO,GAAG,WAAW,UAAU,IAAI,IAAI,KAAK,IAAI,GAAG,SAAS;AAEzF,MAAI,OAAO,UAAU;AAInB,UAAM,QACJ,OAAO,MAAM,YAAY,MAAM,YAAY,MAAM,YAAY,EAAE,KAAK,MAAM;AAC5E,UAAM,YAAY,OAAO,MAAM,iBAAiB,MAAM;AACtD,QAAI,CAAC,SAAS,CAAC,WAAW;AACxB,WAAK;AAAA,QACH,MAAM;AAAA,QACN,SACE;AAAA,QACF,OAAO;AAAA,QACP,KAAK;AAAA,QACL,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAAA,EACF;AAIA,QAAM,cAAc,YAAY,aAAa,YAAY,WAAW,YAAY;AAChF,QAAM,aACJ,iCACG;AAAA,eAAW,oBAAC,WAAQ,MAAM,aAAa,IAAI,GAAG,MAAM,aAAa,OAAO,MAAM;AAAA,IAC9E;AAAA,IACA,UAAU,oBAAC,UAAO,OAAO,QAAQ;AAAA,KACpC;AAGF,MAAI,SAAS;AACX,UAAM,EAAE,UAAUA,KAAI,SAASC,KAAI,MAAMC,KAAI,QAAQC,KAAI,SAASC,KAAI,UAAUC,KAAI,SAASC,KAAI,WAAWC,MAAK,GAAGC,MAAK,IAAI;AAC7H,WACE,oBAAC,QAAK,WAAW,KAAM,GAAGA,OACvB,UACH;AAAA,EAEJ;AAEA,MAAI,MAAM,SAAS,QAAW;AAC5B,UAAM,EAAE,UAAUR,KAAI,SAASC,KAAI,MAAMC,KAAI,QAAQC,KAAI,SAASC,KAAI,UAAUC,KAAI,SAASC,KAAI,WAAWC,MAAK,MAAM,GAAGC,MAAK,IAAI;AACnI,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,WAAW;AAAA,QAGX,iBAAe,WAAW;AAAA,QACzB,GAAGA;AAAA,QAEH;AAAA;AAAA,IACH;AAAA,EAEJ;AAEA,QAAM,EAAE,UAAU,IAAI,SAAS,IAAI,MAAM,IAAI,QAAQ,IAAI,SAAS,IAAI,UAAU,IAAI,SAAS,IAAI,WAAW,KAAK,GAAG,KAAK,IAAI;AAC7H,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,KAAK,QAAQ;AAAA,MACnB,aAAW,WAAW;AAAA,MACtB,UAAU,KAAK,YAAY;AAAA,MAC3B,WAAW;AAAA,MACV,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,iBAAQ;","names":["_c","_v","_s","_k","_l","_i","_a","_cn","rest"]}
1
+ {"version":3,"sources":["../../../src/components/Button/Button.tsx"],"sourcesContent":["import { Slot } from '@radix-ui/react-slot'\nimport type { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { DEV, warn } from '../../lib/warn'\nimport { Spinner } from '../Spinner/Spinner'\n\nexport type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger'\nexport type ButtonSize = 'sm' | 'md' | 'lg'\n\n/**\n * The base every variant shares.\n *\n * It reads `--radius`, the CONTROL step, which is the same corner an `Input`,\n * a `Select` trigger and a `Textarea` draw. It used to be a pill, and a pill\n * beside an 8px field is two different ideas of what a control is — the one\n * inconsistency in the system that every reader noticed and none of the\n * component pages could explain. Everything on the control line now agrees,\n * and the capsule is kept for the things that genuinely are one: a badge, a\n * status pill, a segmented strip, a progress track.\n *\n * It does not move on hover. An earlier base lifted a pixel and asked for a\n * shadow the White Reset does not have — `--shadow` resolves to `none`, so that\n * hover state was invisible on every button in the system while still costing a\n * transition. Opacity carries the filled hover; the border carries the outlined\n * one.\n *\n * The transition names its properties rather than using `all`, so a layout\n * change (a width that grows when the label becomes \"Saving…\") is instant\n * instead of sliding.\n */\nconst BASE =\n 'inline-flex items-center justify-center gap-(--control-gap) rounded-(--radius) font-sans transition-[opacity,color,border-color,background-color] duration-(--duration-fast) ease-(--ease-out-expo) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none aria-disabled:opacity-(--disabled-opacity) aria-disabled:pointer-events-none'\n\nconst VARIANT: Record<ButtonVariant, string> = {\n primary: 'bg-(--accent) text-(--accent-foreground) border border-(--accent) hover:opacity-85',\n secondary: 'border border-(--rule-2) text-(--ink) bg-transparent hover:border-(--ink)',\n ghost: 'border border-transparent text-(--ink-2) hover:text-(--ink) hover:bg-(--stone)',\n /* The one place chroma is allowed on a control, and only because the action\n is destructive — that is a state, not a brand colour. */\n danger: 'bg-(--danger) text-(--paper) border border-(--danger) hover:opacity-85',\n}\n\n/**\n * `min-h` rather than `h`, so a button that wraps to two lines grows instead of\n * clipping.\n *\n * The heights come from the density tokens rather than from a literal, so a\n * subtree marked `data-density=\"compact\"` shrinks its controls without a single\n * call site being told. At the default density `md` is 44px — the pointer\n * target WCAG 2.5.5 asks for.\n *\n * `--control-lh` is what makes those numbers true rather than aspirational. A\n * floor only binds while the box is under it, and the box is the line plus the\n * padding plus the border — so a label inheriting the body's 1.6 leading put\n * `sm` at 39px against its own 36px token, `md` at 46 against 44 and `lg` at 50\n * against 48. Every size was two to three pixels taller than the number\n * documenting it, which is also how a masthead sized from `--control-h-sm`\n * ended up with a search field standing proud of every icon beside it.\n */\nconst SIZE: Record<ButtonSize, string> = {\n sm: 'min-h-(--control-h-sm) px-(--control-px-sm) py-(--control-py-sm) text-[13px] leading-(--control-lh)',\n md: 'min-h-(--control-h-md) px-(--control-px-md) py-(--control-py-md) text-sm leading-(--control-lh)',\n lg: 'min-h-(--control-h-lg) px-(--control-px-lg) py-(--control-py-lg) text-[15px] leading-(--control-lh)',\n}\n\n/** Square, because an icon has no label to give the box its width. */\nconst ICON_SIZE: Record<ButtonSize, string> = {\n sm: 'size-(--control-h-sm) p-0',\n md: 'size-(--control-h-md) p-0',\n lg: 'size-(--control-h-lg) p-0',\n}\n\nconst SPINNER_SIZE: Record<ButtonSize, 'sm' | 'md'> = { sm: 'sm', md: 'sm', lg: 'md' }\n\ninterface CommonProps {\n children?: ReactNode\n /**\n * Which action this is. `primary` is the one thing the screen wants you to\n * do, so there is at most one per view; `danger` is reserved for destructive\n * actions and is the only place chroma is allowed on a control.\n */\n variant?: ButtonVariant\n /** 36 / 44 / 48px tall. `md` meets the pointer-target floor on its own. */\n size?: ButtonSize\n /** Optional mono keycap glyph rendered after the label (e.g. \"P\"). */\n keycap?: string\n /**\n * Swaps the leading content for a spinner and blocks interaction. The label\n * stays put — a button that empties out while it works loses its width, and\n * the page reflows under the pointer that just clicked it.\n */\n loading?: boolean\n /**\n * Square control with no label. REQUIRES `aria-label`: an icon-only button\n * with no accessible name is invisible to a screen reader, and this is the\n * single most common way a design system ships an unusable control.\n */\n iconOnly?: boolean\n /**\n * Render the child element instead of a `<button>`, keeping these styles.\n * Use it to hand the styling to a router's `Link` — `asChild` is what keeps\n * this package free of any one router.\n *\n * The decoration slots (`keycap`, `loading`) are not injected into a slotted\n * child; compose them inside it yourself.\n *\n * @example\n * <Button asChild><Link href=\"/work\">Work</Link></Button>\n */\n asChild?: boolean\n className?: string\n}\n\ntype ButtonAsLink = CommonProps &\n Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className' | 'children'> & { href: string }\n\ntype ButtonAsButton = CommonProps &\n Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'children'> & { href?: undefined }\n\nexport type ButtonProps = ButtonAsLink | ButtonAsButton\n\nfunction Keycap({ glyph }: { glyph: string }) {\n return (\n <span className=\"rounded-(--radius-sm) border border-current px-2 py-0.5 font-mono text-[11px] opacity-55\">\n {glyph}\n </span>\n )\n}\n\n/**\n * The system's action.\n *\n * Renders a `<button>`, or an `<a>` when given `href`, or whatever you hand it\n * via `asChild`. No router is imported, so the package stays framework-agnostic\n * and a Next or React Router app wires its own `Link` at the call site.\n *\n * Server-component friendly: there is no client boundary here, so it renders in\n * a static page as well as an interactive one.\n *\n * @example\n * <Button variant=\"secondary\" keycap=\"P\">View projects</Button>\n * @example\n * <Button loading disabled>Saving…</Button>\n * @example\n * <Button iconOnly aria-label=\"Close\"><X size={16} /></Button>\n */\nexport function Button(props: ButtonProps) {\n const {\n children,\n variant = 'primary',\n size = 'md',\n keycap,\n loading = false,\n iconOnly = false,\n asChild = false,\n className,\n } = props\n\n const cls = cn(BASE, VARIANT[variant], iconOnly ? ICON_SIZE[size] : SIZE[size], className)\n\n if (DEV && iconOnly) {\n // The control renders correctly and is announced as \"button\" and nothing\n // else. Nothing about the rendered page says so, which is why this is the\n // single most common way a design system ships an unusable control.\n const named =\n typeof props['aria-label'] === 'string' && props['aria-label'].trim() !== ''\n const described = typeof props['aria-labelledby'] === 'string'\n if (!named && !described) {\n warn({\n code: 'BUTTON_ICON_ONLY_UNNAMED',\n problem:\n 'An iconOnly Button has no text, so without an accessible name a screen reader announces it as \"button\" and nothing else.',\n field: 'aria-label',\n fix: 'Add aria-label with the action the button performs, e.g. aria-label=\"Delete invoice\".',\n component: 'Button',\n })\n }\n }\n\n // A filled ground needs a spinner in the inherited colour; an outlined one\n // reads better against the page's own rule scale.\n const spinnerTone = variant === 'primary' || variant === 'danger' ? 'current' : 'default'\n const decoration = (\n <>\n {loading && <Spinner size={SPINNER_SIZE[size]} tone={spinnerTone} label={null} />}\n {children}\n {keycap && <Keycap glyph={keycap} />}\n </>\n )\n\n if (asChild) {\n const { children: _c, variant: _v, size: _s, keycap: _k, loading: _l, iconOnly: _i, asChild: _a, className: _cn, ...rest } = props\n return (\n <Slot className={cls} {...rest}>\n {children}\n </Slot>\n )\n }\n\n if (props.href !== undefined) {\n const { children: _c, variant: _v, size: _s, keycap: _k, loading: _l, iconOnly: _i, asChild: _a, className: _cn, href, ...rest } = props\n return (\n <a\n href={href}\n className={cls}\n // A link cannot be `disabled`; `aria-disabled` plus the pointer-events\n // rule in BASE is the accessible equivalent.\n aria-disabled={loading || undefined}\n {...rest}\n >\n {decoration}\n </a>\n )\n }\n\n const { children: _c, variant: _v, size: _s, keycap: _k, loading: _l, iconOnly: _i, asChild: _a, className: _cn, ...rest } = props\n return (\n <button\n type={rest.type ?? 'button'}\n aria-busy={loading || undefined}\n disabled={rest.disabled || loading}\n className={cls}\n {...rest}\n >\n {decoration}\n </button>\n )\n}\n\nexport default Button\n"],"mappings":"AA2HI,SA4DA,UA5DA,KA4DA,YA5DA;AA3HJ,SAAS,YAAY;AAErB,SAAS,UAAU;AACnB,SAAS,KAAK,YAAY;AAC1B,SAAS,eAAe;AA0BxB,MAAM,OACJ;AAEF,MAAM,UAAyC;AAAA,EAC7C,SAAS;AAAA,EACT,WAAW;AAAA,EACX,OAAO;AAAA;AAAA;AAAA,EAGP,QAAQ;AACV;AAmBA,MAAM,OAAmC;AAAA,EACvC,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAGA,MAAM,YAAwC;AAAA,EAC5C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,eAAgD,EAAE,IAAI,MAAM,IAAI,MAAM,IAAI,KAAK;AAiDrF,SAAS,OAAO,EAAE,MAAM,GAAsB;AAC5C,SACE,oBAAC,UAAK,WAAU,4FACb,iBACH;AAEJ;AAmBO,SAAS,OAAO,OAAoB;AACzC,QAAM;AAAA,IACJ;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,IACP;AAAA,IACA,UAAU;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV;AAAA,EACF,IAAI;AAEJ,QAAM,MAAM,GAAG,MAAM,QAAQ,OAAO,GAAG,WAAW,UAAU,IAAI,IAAI,KAAK,IAAI,GAAG,SAAS;AAEzF,MAAI,OAAO,UAAU;AAInB,UAAM,QACJ,OAAO,MAAM,YAAY,MAAM,YAAY,MAAM,YAAY,EAAE,KAAK,MAAM;AAC5E,UAAM,YAAY,OAAO,MAAM,iBAAiB,MAAM;AACtD,QAAI,CAAC,SAAS,CAAC,WAAW;AACxB,WAAK;AAAA,QACH,MAAM;AAAA,QACN,SACE;AAAA,QACF,OAAO;AAAA,QACP,KAAK;AAAA,QACL,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAAA,EACF;AAIA,QAAM,cAAc,YAAY,aAAa,YAAY,WAAW,YAAY;AAChF,QAAM,aACJ,iCACG;AAAA,eAAW,oBAAC,WAAQ,MAAM,aAAa,IAAI,GAAG,MAAM,aAAa,OAAO,MAAM;AAAA,IAC9E;AAAA,IACA,UAAU,oBAAC,UAAO,OAAO,QAAQ;AAAA,KACpC;AAGF,MAAI,SAAS;AACX,UAAM,EAAE,UAAUA,KAAI,SAASC,KAAI,MAAMC,KAAI,QAAQC,KAAI,SAASC,KAAI,UAAUC,KAAI,SAASC,KAAI,WAAWC,MAAK,GAAGC,MAAK,IAAI;AAC7H,WACE,oBAAC,QAAK,WAAW,KAAM,GAAGA,OACvB,UACH;AAAA,EAEJ;AAEA,MAAI,MAAM,SAAS,QAAW;AAC5B,UAAM,EAAE,UAAUR,KAAI,SAASC,KAAI,MAAMC,KAAI,QAAQC,KAAI,SAASC,KAAI,UAAUC,KAAI,SAASC,KAAI,WAAWC,MAAK,MAAM,GAAGC,MAAK,IAAI;AACnI,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,WAAW;AAAA,QAGX,iBAAe,WAAW;AAAA,QACzB,GAAGA;AAAA,QAEH;AAAA;AAAA,IACH;AAAA,EAEJ;AAEA,QAAM,EAAE,UAAU,IAAI,SAAS,IAAI,MAAM,IAAI,QAAQ,IAAI,SAAS,IAAI,UAAU,IAAI,SAAS,IAAI,WAAW,KAAK,GAAG,KAAK,IAAI;AAC7H,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,KAAK,QAAQ;AAAA,MACnB,aAAW,WAAW;AAAA,MACtB,UAAU,KAAK,YAAY;AAAA,MAC3B,WAAW;AAAA,MACV,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,iBAAQ;","names":["_c","_v","_s","_k","_l","_i","_a","_cn","rest"]}
@@ -12,6 +12,13 @@ interface KbdProps extends HTMLAttributes<HTMLElement> {
12
12
  * whatever type it sits beside rather than fixing itself at one px value, which
13
13
  * is how the same shortcut ended up three different sizes across a page.
14
14
  *
15
+ * Every dimension here tracked its context except the leading, which was a
16
+ * literal `1.6` — the body's READING leading, on a chip that is never a
17
+ * paragraph. Inside a control it therefore stood proud of the label beside it
18
+ * and pushed the whole box past its own height token: the masthead's search
19
+ * field measured 39px against a 36px `sm`, and it was this keycap doing it.
20
+ * `--control-lh` is the leading a one-line box gets, and this is one.
21
+ *
15
22
  * @example
16
23
  * Press <Kbd>⌘</Kbd> <Kbd>K</Kbd> to open the palette.
17
24
  */
@@ -5,7 +5,7 @@ function Kbd({ children, className, ...rest }) {
5
5
  "kbd",
6
6
  {
7
7
  className: cn(
8
- "inline-flex min-w-[1.6em] items-center justify-center rounded-(--radius-sm) border border-(--rule-2) px-[0.4em] py-[0.1em] font-mono text-[0.8em] leading-[1.6] text-(--ink-2)",
8
+ "inline-flex min-w-[1.6em] items-center justify-center rounded-(--radius-sm) border border-(--rule-2) px-[0.4em] py-[0.1em] font-mono text-[0.8em] leading-(--control-lh) text-(--ink-2)",
9
9
  className
10
10
  ),
11
11
  ...rest,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Kbd/Kbd.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface KbdProps extends HTMLAttributes<HTMLElement> {\n children: ReactNode\n}\n\n/**\n * A key on a keyboard, set as one.\n *\n * Renders a `<kbd>`, which is the element that means this — a `<span>` styled\n * to look like a key tells a screen reader nothing. Sized in `em` so it tracks\n * whatever type it sits beside rather than fixing itself at one px value, which\n * is how the same shortcut ended up three different sizes across a page.\n *\n * @example\n * Press <Kbd>⌘</Kbd> <Kbd>K</Kbd> to open the palette.\n */\nexport function Kbd({ children, className, ...rest }: KbdProps) {\n return (\n <kbd\n className={cn(\n 'inline-flex min-w-[1.6em] items-center justify-center rounded-(--radius-sm) border border-(--rule-2) px-[0.4em] py-[0.1em] font-mono text-[0.8em] leading-[1.6] text-(--ink-2)',\n className,\n )}\n {...rest}\n >\n {children}\n </kbd>\n )\n}\n\nexport default Kbd\n"],"mappings":"AAoBI;AAnBJ,SAAS,UAAU;AAiBZ,SAAS,IAAI,EAAE,UAAU,WAAW,GAAG,KAAK,GAAa;AAC9D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,cAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Kbd/Kbd.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface KbdProps extends HTMLAttributes<HTMLElement> {\n children: ReactNode\n}\n\n/**\n * A key on a keyboard, set as one.\n *\n * Renders a `<kbd>`, which is the element that means this — a `<span>` styled\n * to look like a key tells a screen reader nothing. Sized in `em` so it tracks\n * whatever type it sits beside rather than fixing itself at one px value, which\n * is how the same shortcut ended up three different sizes across a page.\n *\n * Every dimension here tracked its context except the leading, which was a\n * literal `1.6` — the body's READING leading, on a chip that is never a\n * paragraph. Inside a control it therefore stood proud of the label beside it\n * and pushed the whole box past its own height token: the masthead's search\n * field measured 39px against a 36px `sm`, and it was this keycap doing it.\n * `--control-lh` is the leading a one-line box gets, and this is one.\n *\n * @example\n * Press <Kbd>⌘</Kbd> <Kbd>K</Kbd> to open the palette.\n */\nexport function Kbd({ children, className, ...rest }: KbdProps) {\n return (\n <kbd\n className={cn(\n 'inline-flex min-w-[1.6em] items-center justify-center rounded-(--radius-sm) border border-(--rule-2) px-[0.4em] py-[0.1em] font-mono text-[0.8em] leading-(--control-lh) text-(--ink-2)',\n className,\n )}\n {...rest}\n >\n {children}\n </kbd>\n )\n}\n\nexport default Kbd\n"],"mappings":"AA2BI;AA1BJ,SAAS,UAAU;AAwBZ,SAAS,IAAI,EAAE,UAAU,WAAW,GAAG,KAAK,GAAa;AAC9D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,cAAQ;","names":[]}
@@ -211,6 +211,14 @@ declare function SidebarInset({ className, children, ...rest }: ComponentProps<'
211
211
  * It is also where `SidebarTrigger` belongs, and the layout assumes one:
212
212
  * anything passed as `children` takes the space and the trigger sits at the
213
213
  * inline end of the row.
214
+ *
215
+ * `--bar-h` is the floor, not a number of its own. This block and the masthead
216
+ * beside it meet at a corner, and their two bottom rules are read as one line —
217
+ * so a floor typed here is a floor that has to be retyped there, and the two
218
+ * were 56px and 64px for exactly that reason. The token also survives a rail
219
+ * that pins its own density: `--bar-h` is declared on the root, so its
220
+ * `var(--control-h-md)` resolves there and inherits as one answer, which is
221
+ * what keeps a compact rail's head level with a comfortable page's bar.
214
222
  */
215
223
  declare function SidebarHeader({ className, children, ...rest }: ComponentProps<'div'>): react.JSX.Element;
216
224
  /** The scrolling middle. Everything that is a list of places goes here. */
@@ -257,7 +257,7 @@ function SidebarHeader({ className, children, ...rest }) {
257
257
  "div",
258
258
  {
259
259
  className: cn(
260
- "flex min-h-14 shrink-0 items-center gap-2 border-b border-(--rule) px-3",
260
+ "flex min-h-(--bar-h) shrink-0 items-center gap-2 border-b border-(--rule) px-3",
261
261
  className
262
262
  ),
263
263
  ...rest,
@@ -371,7 +371,7 @@ function SidebarGroup({
371
371
  ] })
372
372
  );
373
373
  }
374
- const MOVING_HIGHLIGHT = "group-hover/rows:not-hover:bg-transparent group-hover/rows:not-hover:font-normal group-hover/rows:not-hover:text-(--ink-3-aa)";
374
+ const MOVING_HIGHLIGHT = "group-hover/rows:not-hover:bg-transparent group-hover/rows:not-hover:text-(--ink-3-aa) group-hover/rows:not-hover:aria-[current=page]:font-normal";
375
375
  function SidebarBranch({
376
376
  label,
377
377
  icon: Icon,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":["'use client'\n\nimport {\n ChevronRight,\n PanelLeftClose,\n PanelLeftOpen,\n PanelRightClose,\n PanelRightOpen,\n type LucideIcon,\n} from 'lucide-react'\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useState,\n type ComponentProps,\n type MouseEvent,\n type ReactNode,\n} from 'react'\nimport { cn } from '../../lib/cn'\nimport { warnBlankName } from '../../lib/warn'\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../Collapsible/Collapsible'\nimport { NavItem, type NavItemProps } from '../NavItem/NavItem'\nimport { Tooltip, TooltipProvider } from '../Tooltip/Tooltip'\n\n/** What happens to the rail when it is closed. See {@link SidebarProviderProps.collapsible}. */\nexport type SidebarCollapsible = 'icon' | 'offcanvas' | 'none'\n\n/** Which edge of the layout the rail is on. Logical, so it follows `dir`. */\nexport type SidebarSide = 'start' | 'end'\n\n/** How the rail meets the page. See {@link SidebarProviderProps.variant}. */\nexport type SidebarVariant = 'flush' | 'inset' | 'floating'\n\n/** Where the rail stops being a column. See {@link SidebarProviderProps.breakpoint}. */\nexport type SidebarBreakpoint = 'sm' | 'md' | 'lg' | 'xl'\n\n/**\n * The drawer, written as CSS rather than decided in JavaScript.\n *\n * The first version asked `matchMedia` and rendered a different tree from the\n * answer. That answer does not exist until an effect runs, so the server sent —\n * and a phone painted — a 256px column beside a 390px screen, and the page\n * scrolled sideways until React caught up. A layout that is wrong for a frame\n * is wrong: it is the first frame, it is the one a reader sees on a slow phone,\n * and on this site's own suite it was a real failure on every route.\n *\n * So the element is the same in both states and the media query decides, which\n * is what a media query is. JavaScript is left with the two things CSS cannot\n * say — `inert`, and whether to render the scrim — and being a frame late on\n * either of those changes nothing anyone can see.\n *\n * The breakpoints are NAMED rather than numeric because these are literal class\n * strings: Tailwind emits what it can read in the source, and a class built\n * from a runtime number is a class that does not exist. Naming them also puts\n * the rail on the same four steps as everything else in the page around it,\n * which a number never quite lands on.\n *\n * It slides on `inset-inline-start`, not on `translate-x`. A transform is a\n * PHYSICAL axis, so the closed drawer needed an `rtl:` twin of every offset —\n * which is the physical-with-a-patch shape this system forbids everywhere else,\n * and which cost 3.6 kB of stylesheet on its own: each `rtl:` selector carries\n * the whole `:lang()` list, four breakpoints deep. A logical inset needs no\n * twin, is one property in each direction, and puts the rail back under the\n * same rule as every `ps-` and `me-` in the package.\n */\nconst DRAWER: Record<\n SidebarBreakpoint,\n { query: string; frame: string; fixed: string; absolute: string; start: string; end: string; openStart: string; openEnd: string; shutStart: string; shutEnd: string }\n> = {\n sm: {\n query: '(max-width: 39.99rem)',\n frame: 'max-sm:inset-y-0 max-sm:z-(--z-modal) max-sm:w-(--sidebar-w) max-sm:m-0 max-sm:rounded-none max-sm:border-0 max-sm:transition-[inset-inline-start,inset-inline-end]',\n fixed: 'max-sm:fixed',\n absolute: 'max-sm:absolute',\n start: 'max-sm:border-e max-sm:border-(--rule)',\n end: 'max-sm:border-s max-sm:border-(--rule)',\n openStart: 'max-sm:start-0',\n openEnd: 'max-sm:end-0',\n shutStart: 'max-sm:-start-(--sidebar-w)',\n shutEnd: 'max-sm:-end-(--sidebar-w)',\n },\n md: {\n query: '(max-width: 47.99rem)',\n frame: 'max-md:inset-y-0 max-md:z-(--z-modal) max-md:w-(--sidebar-w) max-md:m-0 max-md:rounded-none max-md:border-0 max-md:transition-[inset-inline-start,inset-inline-end]',\n fixed: 'max-md:fixed',\n absolute: 'max-md:absolute',\n start: 'max-md:border-e max-md:border-(--rule)',\n end: 'max-md:border-s max-md:border-(--rule)',\n openStart: 'max-md:start-0',\n openEnd: 'max-md:end-0',\n shutStart: 'max-md:-start-(--sidebar-w)',\n shutEnd: 'max-md:-end-(--sidebar-w)',\n },\n lg: {\n query: '(max-width: 63.99rem)',\n frame: 'max-lg:inset-y-0 max-lg:z-(--z-modal) max-lg:w-(--sidebar-w) max-lg:m-0 max-lg:rounded-none max-lg:border-0 max-lg:transition-[inset-inline-start,inset-inline-end]',\n fixed: 'max-lg:fixed',\n absolute: 'max-lg:absolute',\n start: 'max-lg:border-e max-lg:border-(--rule)',\n end: 'max-lg:border-s max-lg:border-(--rule)',\n openStart: 'max-lg:start-0',\n openEnd: 'max-lg:end-0',\n shutStart: 'max-lg:-start-(--sidebar-w)',\n shutEnd: 'max-lg:-end-(--sidebar-w)',\n },\n xl: {\n query: '(max-width: 79.99rem)',\n frame: 'max-xl:inset-y-0 max-xl:z-(--z-modal) max-xl:w-(--sidebar-w) max-xl:m-0 max-xl:rounded-none max-xl:border-0 max-xl:transition-[inset-inline-start,inset-inline-end]',\n fixed: 'max-xl:fixed',\n absolute: 'max-xl:absolute',\n start: 'max-xl:border-e max-xl:border-(--rule)',\n end: 'max-xl:border-s max-xl:border-(--rule)',\n openStart: 'max-xl:start-0',\n openEnd: 'max-xl:end-0',\n shutStart: 'max-xl:-start-(--sidebar-w)',\n shutEnd: 'max-xl:-end-(--sidebar-w)',\n },\n}\n\ninterface SidebarState {\n /** Whether the rail is showing — the docked column, or the open drawer. */\n open: boolean\n setOpen: (open: boolean) => void\n toggle: () => void\n collapsible: SidebarCollapsible\n /** True only in the state where labels are hidden and rows are icons. */\n iconOnly: boolean\n side: SidebarSide\n variant: SidebarVariant\n /**\n * True while the viewport is under the provider's breakpoint, where the rail\n * is a drawer over the page rather than a column beside it.\n */\n mobile: boolean\n /** The drawer covers its positioned ancestor rather than the window. */\n contained: boolean\n /** The step below which the rail is a drawer, or `null` for never. */\n breakpoint: SidebarBreakpoint | null\n}\n\nconst SidebarContext = createContext<SidebarState | null>(null)\n\n/**\n * What the parts read when nobody wrapped them: open, and not collapsible.\n *\n * A rail without a provider used to THROW, which is defensible for a hook a\n * consumer called by hand and wrong for the component itself — `<Sidebar>` on\n * its own is the first thing anybody writes, and the documentation site's own\n * props panel renders exactly that. A component that cannot draw without a\n * wrapper is not strict, it is unusable; so the parts fall back to the state a\n * rail with no controls would be in, and the button that would toggle it does\n * nothing because there is nothing holding the answer.\n */\nconst UNWRAPPED: SidebarState = {\n open: true,\n setOpen: () => {},\n toggle: () => {},\n collapsible: 'none',\n iconOnly: false,\n side: 'start',\n variant: 'flush',\n mobile: false,\n contained: false,\n breakpoint: null,\n}\n\n/** The state the parts read, wrapped or not. */\nfunction useSidebarState(): SidebarState {\n return useContext(SidebarContext) ?? UNWRAPPED\n}\n\n/**\n * The rail's own state, for anything that has to answer to it.\n *\n * A page beside the rail needs it to reserve the right width; a control inside\n * needs to know whether its label is being drawn.\n *\n * This one THROWS outside a provider, and the parts above do not, and the\n * difference is who made the mistake. A part rendered on its own is somebody\n * writing `<Sidebar>` to see what it looks like; a call to this hook is code\n * asking for state that nothing is keeping, and returning a plausible default\n * there is a layout that is wrong in one state and right in the other with\n * nothing to say which.\n */\nexport function useSidebar(): SidebarState {\n const state = useContext(SidebarContext)\n if (!state) throw new Error('useSidebar must be used inside a <SidebarProvider>')\n return state\n}\n\nexport interface SidebarProviderProps {\n /** Controlled open state. Leave off to let the provider own it. */\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n /** What closing does to the rail. See {@link SidebarProps.collapsible}. */\n collapsible?: SidebarCollapsible\n /**\n * The keyboard shortcut that toggles the rail, as a single letter.\n *\n * `b`, with the platform's own modifier, because that is what every editor\n * and every application shell already uses for the same thing. Pass `null`\n * to bind nothing — an app that already owns that chord should not have it\n * taken twice, and a rail that cannot be closed does not need a shortcut for\n * closing it.\n */\n shortcut?: string | null\n /** Which edge the rail is on. Logical: `end` is the right in LTR, the left in RTL. */\n side?: SidebarSide\n /**\n * How the rail meets the page.\n *\n * `flush` is a column with a hairline down its inner edge — the rail and the\n * page are one surface divided by a line. `floating` lifts the rail off that\n * surface as its own bordered panel with the page ground showing around it.\n * `inset` is the same gesture the other way up: the rail sits on the page\n * ground and `SidebarInset` draws the CONTENT as the panel.\n *\n * `floating` and `inset` both need a ground to sit on — put `bg-(--stone)` on\n * the element holding the provider, or they are a panel on the same colour as\n * the thing behind it.\n */\n variant?: SidebarVariant\n /**\n * The step below which the rail becomes a drawer over the page.\n *\n * A rail is a column when there is a column's worth of room and an overlay\n * when there is not; below this the rail is `fixed`, covers the page from its\n * own edge, and closes onto a scrim. `null` pins it as a column at every\n * width, for a layout that is never narrow — an embedded console, a preview\n * frame.\n *\n * Named, not a number: the switch is a media query in the stylesheet, and a\n * class built from a runtime number is a class the compiler never emitted. It\n * also puts the rail on the same four steps as the page around it.\n */\n breakpoint?: SidebarBreakpoint | null\n /**\n * A `localStorage` key under which the DOCKED state is remembered.\n *\n * A reader who put the rail away did not mean \"until the next page\". Only the\n * docked state is kept: an open drawer is a thing a reader did to this screen,\n * and restoring it on the next visit is a page that opens with its navigation\n * over the top of itself.\n *\n * Read after mount, never during render — a value from storage in the first\n * pass is a hydration mismatch, and the markup the server sent is the one the\n * client has to agree with.\n */\n persist?: string | null\n /**\n * The drawer covers its nearest positioned ancestor rather than the window.\n *\n * For a rail inside a bounded frame — a device preview, an embedded console,\n * an example on a documentation page. `fixed` resolves against the viewport\n * wherever the markup sits, so without this the drawer inside a 400px preview\n * opens across the whole page it is previewed on. Give the frame `relative`.\n */\n contained?: boolean\n children: ReactNode\n}\n\n/**\n * Holds whether the rail is open, and binds the shortcut that changes it.\n *\n * Separate from `Sidebar` itself because the answer is needed on BOTH sides of\n * the layout: the rail draws itself from it, and the content beside the rail\n * reserves width from it. A state that lived inside the rail could only ever\n * be read downwards.\n */\nexport function SidebarProvider({\n open: controlled,\n defaultOpen = true,\n onOpenChange,\n collapsible = 'icon',\n shortcut = 'b',\n side = 'start',\n variant = 'flush',\n breakpoint = 'md',\n persist = null,\n contained = false,\n children,\n}: SidebarProviderProps) {\n const [uncontrolled, setUncontrolled] = useState(defaultOpen)\n // The drawer is its own answer, not the docked column's. They are different\n // questions — \"keep the rail beside the page\" and \"show me the rail now\" —\n // and one state answering both is a phone that loads with its navigation\n // covering the page, because `defaultOpen` was written for the column.\n const [drawer, setDrawer] = useState(false)\n const [mobile, setMobile] = useState(false)\n\n // The same query the classes carry, asked in JavaScript for the two things a\n // class cannot answer: whether the closed rail is `inert`, and whether there\n // is a scrim. The LAYOUT is not waiting on this — see `DRAWER`.\n useEffect(() => {\n if (breakpoint === null) return\n const query = window.matchMedia(DRAWER[breakpoint].query)\n const sync = () => setMobile(query.matches)\n sync()\n query.addEventListener('change', sync)\n return () => query.removeEventListener('change', sync)\n }, [breakpoint])\n\n // Crossing INTO drawer width closes it. A rail that was docked a moment ago\n // would otherwise become an overlay sitting open over the page nobody asked\n // to have covered.\n useEffect(() => {\n if (mobile) setDrawer(false)\n }, [mobile])\n\n useEffect(() => {\n if (!persist) return\n try {\n const stored = window.localStorage.getItem(persist)\n if (stored === 'open' || stored === 'closed') setUncontrolled(stored === 'open')\n } catch {\n // A storage-blocked context just starts at `defaultOpen` every time.\n }\n }, [persist])\n\n useEffect(() => {\n if (!persist) return\n try {\n window.localStorage.setItem(persist, uncontrolled ? 'open' : 'closed')\n } catch {\n // Same.\n }\n }, [persist, uncontrolled])\n\n const docked = controlled ?? uncontrolled\n const open = mobile ? drawer : docked\n\n const setOpen = useCallback(\n (next: boolean) => {\n if (mobile) setDrawer(next)\n else if (controlled === undefined) setUncontrolled(next)\n onOpenChange?.(next)\n },\n [mobile, controlled, onOpenChange],\n )\n\n const toggle = useCallback(() => setOpen(!open), [open, setOpen])\n\n useEffect(() => {\n if (!shortcut || collapsible === 'none') return\n const onKey = (event: KeyboardEvent) => {\n // `metaKey` OR `ctrlKey`, not the platform's own: a Mac keyboard on a\n // Linux browser and a PC keyboard on a Mac both exist, and a shortcut\n // that works for one of them is a shortcut with a bug report attached.\n if (event.key.toLowerCase() !== shortcut.toLowerCase()) return\n if (!event.metaKey && !event.ctrlKey) return\n event.preventDefault()\n toggle()\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [shortcut, collapsible, toggle])\n\n const value = useMemo<SidebarState>(\n () => ({\n open,\n setOpen,\n toggle,\n collapsible,\n // Never at drawer width: closed there means the rail is off the page, and\n // a column of icons is the answer for a layout that still has a column.\n iconOnly: !mobile && !open && collapsible === 'icon',\n side,\n variant,\n mobile,\n contained,\n breakpoint,\n }),\n [open, setOpen, toggle, collapsible, side, variant, mobile, contained, breakpoint],\n )\n\n // The tooltip provider is supplied here rather than demanded of the app. A\n // collapsed rail is rows of icons, every one of which names itself through a\n // tooltip — so without it the component does not degrade, it throws, and only\n // in the state a reader reaches by pressing the button on it. Radix scopes\n // nested providers, so an app that already has one keeps its own settings for\n // everything outside this subtree.\n return (\n <SidebarContext.Provider value={value}>\n <TooltipProvider>{children}</TooltipProvider>\n </SidebarContext.Provider>\n )\n}\n\nexport interface SidebarProps extends Omit<ComponentProps<'nav'>, 'children'> {\n /**\n * Names the landmark. Required: a page with two navigations in it announces\n * two things called \"navigation\" unless each of them says which it is.\n */\n label: string\n /**\n * What the scrim behind the open drawer announces.\n *\n * It is a button — tapping beside a drawer closes it, and that has to be a\n * control a keyboard and a screen reader can reach, not a decorated `<div>`.\n */\n scrimLabel?: string\n children: ReactNode\n}\n\n/**\n * A navigation rail down the side of an application.\n *\n * A `<nav>`, not an `<aside>`. The element decides the landmark, and a rail of\n * links announced as \"complementary\" is not the one a screen reader user jumps\n * to when they go looking for the navigation.\n *\n * Composed rather than configured: a rail is a header, a scrolling middle and a\n * footer, and every product wants different things in all three. What this owns\n * is the part that is the same everywhere — the width, the edge, the scroll\n * behaviour, and what happens when it closes.\n *\n * **Closing has three shapes**, and `collapsible` on the provider picks one.\n * `icon` keeps the rail and drops the labels, which is right when the rows are\n * a fixed set a reader learns the shape of. `offcanvas` takes the whole rail\n * away, which is right when the rows are a long index nobody memorises.\n * `none` is a rail that does not close.\n *\n * The trigger belongs INSIDE the rail — `SidebarHeader` places it — rather than\n * out in an application's masthead. A control that hides a thing should live on\n * the thing: in the masthead it is one more anonymous icon in a row of them,\n * and nothing connects it to the column it operates.\n *\n * **Under the provider's `breakpoint` it is a drawer**, not a column: `fixed`\n * against its own edge, over a scrim, and `inert` while closed. Merely\n * translating it off-screen is not closed — it keeps focus and it is still read\n * aloud, so a shut drawer puts its whole index between the reader and the page\n * they were on. The scrim is a real button, because tapping beside a drawer is\n * how a drawer is closed and that gesture has to exist for a keyboard too.\n *\n * `fixed` resolves against the viewport unless an ancestor has a `transform`,\n * `filter` or `perspective`, which makes that ancestor the containing block\n * instead. Do not put one on an element wrapping the provider, or the drawer\n * opens inside it.\n *\n * @example\n * <SidebarProvider>\n * <Sidebar label=\"Documentation\">\n * <SidebarHeader>Acme</SidebarHeader>\n * <SidebarContent>\n * <SidebarGroup label=\"Guide\" count={2}>\n * <SidebarItem href=\"/start\" icon={Home}>Getting started</SidebarItem>\n * </SidebarGroup>\n * </SidebarContent>\n * </Sidebar>\n * </SidebarProvider>\n */\nexport function Sidebar({\n label,\n scrimLabel = 'Close the navigation',\n className,\n children,\n ...rest\n}: SidebarProps) {\n const { open, setOpen, collapsible, iconOnly, side, variant, mobile, contained, breakpoint } =\n useSidebarState()\n warnBlankName('Sidebar', 'label', label, 'the rail is announced as an unnamed navigation')\n\n const gone = !open && collapsible === 'offcanvas'\n const drawer = breakpoint === null ? null : DRAWER[breakpoint]\n\n return (\n <>\n {/* Only while it is actually an overlay. On a column there is nothing to\n close onto, and a full-page button over a docked rail is a page that\n cannot be clicked. */}\n {drawer && mobile && open && (\n <button\n type=\"button\"\n aria-label={scrimLabel}\n onClick={() => setOpen(false)}\n data-m22-animated\n className={cn(\n 'inset-0 z-(--z-scrim) bg-(--scrim) animate-[m22-fade-in_var(--duration-fast)_var(--ease)]',\n contained ? 'absolute' : 'fixed',\n )}\n />\n )}\n <nav\n aria-label={label}\n data-state={open ? 'open' : 'collapsed'}\n data-collapsible={collapsible}\n data-side={side}\n data-variant={variant}\n // Off-screen is not closed. Without this the drawer keeps focus and\n // keeps being read aloud while it sits beyond the edge of the page.\n // JavaScript decides it, and a frame's delay costs nothing: the\n // POSITION is CSS, so nothing has moved.\n inert={(drawer && mobile && !open) || undefined}\n // `group`, so every descendant can answer the rail's state without\n // being handed a prop through four levels of composition.\n className={cn(\n 'group/sidebar flex shrink-0 flex-col overflow-hidden bg-(--paper) transition-[width] duration-(--duration-base) ease-(--ease-out-expo) motion-reduce:transition-none',\n gone ? 'w-0' : iconOnly ? 'w-(--sidebar-w-icon)' : 'w-(--sidebar-w)',\n // `inset` gives the border to `SidebarInset`, which is the panel in\n // that arrangement; the rail is the ground it sits on and draws no\n // edge of its own, or the two lines run parallel a few pixels apart.\n variant === 'floating' && 'my-2 rounded-(--radius-frame) border border-(--rule)',\n variant === 'floating' && (side === 'end' ? 'me-2' : 'ms-2'),\n variant === 'flush' &&\n !gone &&\n (side === 'end' ? 'border-s border-(--rule)' : 'border-e border-(--rule)'),\n // The drawer, below the step. Everything above is the column; these\n // override it inside one media query, so the first paint is already\n // right and no effect has to run for the page to be the right width.\n drawer && [\n drawer.frame,\n contained ? drawer.absolute : drawer.fixed,\n side === 'end' ? drawer.end : drawer.start,\n side === 'end'\n ? open\n ? drawer.openEnd\n : drawer.shutEnd\n : open\n ? drawer.openStart\n : drawer.shutStart,\n ],\n className,\n )}\n {...rest}\n >\n {/* The inner column is the full width at every state, so the rows\n inside do not reflow while the rail animates — they are clipped by\n the rail instead, which is what makes the motion read as a panel\n sliding rather than as a column of text reflowing under a wipe. */}\n <div className={cn('flex h-full flex-col', gone ? 'w-(--sidebar-w)' : 'w-full')}>\n {children}\n </div>\n </nav>\n </>\n )\n}\n\n/**\n * The page beside the rail.\n *\n * Every layout built on this component was writing the same\n * `flex min-w-0 flex-1 flex-col` by hand, and `min-w-0` is the half everybody\n * forgets: a flex child's floor is its content, so one wide table inside pushes\n * the whole page past the viewport and takes the rail's width with it.\n *\n * It is also the other half of `variant=\"inset\"`. There the rail is the ground\n * and this is the panel — a bordered `--paper` surface with the ground showing\n * around it — which is why the variant lives on the provider rather than on\n * either piece: one setting, two components, and no way to set half of it.\n *\n * A `<div>`, not a `<main>`. What goes in here is usually a masthead AND the\n * page under it, and only one of those is the main landmark.\n */\nexport function SidebarInset({ className, children, ...rest }: ComponentProps<'div'>) {\n const { variant } = useSidebarState()\n\n return (\n <div\n className={cn(\n 'flex min-w-0 flex-1 flex-col',\n variant === 'inset' &&\n 'my-2 me-2 overflow-hidden rounded-(--radius-frame) border border-(--rule) bg-(--paper) ms-0',\n className,\n )}\n {...rest}\n >\n {children}\n </div>\n )\n}\n\n/**\n * The block at the top of the rail: a brand, a workspace, a switcher.\n *\n * It is also where `SidebarTrigger` belongs, and the layout assumes one:\n * anything passed as `children` takes the space and the trigger sits at the\n * inline end of the row.\n */\nexport function SidebarHeader({ className, children, ...rest }: ComponentProps<'div'>) {\n return (\n <div\n className={cn(\n 'flex min-h-14 shrink-0 items-center gap-2 border-b border-(--rule) px-3',\n className,\n )}\n {...rest}\n >\n {children}\n </div>\n )\n}\n\n/** The scrolling middle. Everything that is a list of places goes here. */\nexport function SidebarContent({ className, children, ...rest }: ComponentProps<'div'>) {\n return (\n <div\n className={cn('flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto p-2 scroll-slim', className)}\n {...rest}\n >\n {children}\n </div>\n )\n}\n\n/**\n * The block at the bottom: the utilities, the account, the thing a rail ends on.\n *\n * A separate landmark from the content above it because it is a separate kind\n * of thing — a reader scanning the index does not want Trash and Help in it.\n */\nexport function SidebarFooter({ className, children, ...rest }: ComponentProps<'div'>) {\n return (\n <div\n className={cn('flex shrink-0 flex-col gap-1 border-t border-(--rule) p-2', className)}\n {...rest}\n >\n {children}\n </div>\n )\n}\n\n/** A rule between blocks of rows, inset to the rail's own padding. */\nexport function SidebarSeparator({ className, ...rest }: ComponentProps<'hr'>) {\n return <hr className={cn('my-1 border-0 border-t border-(--rule)', className)} {...rest} />\n}\n\nexport interface SidebarTriggerProps extends ComponentProps<'button'> {\n /** What the button announces. Both states, because it says both things. */\n labels?: { open: string; close: string }\n}\n\n/**\n * The control that opens and closes the rail.\n *\n * Its accessible name changes with what it will DO, and `aria-expanded` reports\n * what is true now — a button permanently called \"Toggle sidebar\" tells a\n * screen reader user nothing about which way it will go.\n */\nexport function SidebarTrigger({\n labels = { open: 'Open the sidebar', close: 'Close the sidebar' },\n className,\n onClick,\n ...rest\n}: SidebarTriggerProps) {\n const { open, toggle, side } = useSidebarState()\n // The glyph points at the edge the rail is on, so the button is a picture of\n // what it will do rather than a picture of somebody else's layout.\n const Icon =\n side === 'end' ? (open ? PanelRightClose : PanelRightOpen) : open ? PanelLeftClose : PanelLeftOpen\n\n return (\n <button\n type=\"button\"\n aria-label={open ? labels.close : labels.open}\n aria-expanded={open}\n onClick={(event) => {\n toggle()\n onClick?.(event)\n }}\n className={cn(\n 'grid size-8 shrink-0 place-items-center rounded-(--radius) text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)',\n className,\n )}\n {...rest}\n >\n <Icon size={16} strokeWidth={1.5} aria-hidden />\n </button>\n )\n}\n\nexport interface SidebarGroupProps {\n /** The heading over the rows. */\n label: string\n /** How many rows are inside, printed on the far side of the heading. */\n count?: number\n /**\n * A mark that belongs to the GROUP — \"Beta\", \"3 new\".\n *\n * Beside the label, not out at the end with the count: it qualifies the\n * words, and a qualifier that has drifted to the other side of the row reads\n * as a second, unrelated fact.\n */\n badge?: ReactNode\n /**\n * A control on the heading row — a menu, an \"add\" button.\n *\n * Sits between the label and the count, and is NOT rendered inside the\n * heading's own button: a control nested in a control is one the keyboard\n * reaches by pressing the thing it is inside.\n */\n action?: ReactNode\n /** Whether the group folds at all. A group of two rows usually should not. */\n collapsible?: boolean\n defaultOpen?: boolean\n children: ReactNode\n className?: string\n}\n\n/**\n * A labelled block of rows, optionally foldable.\n *\n * The heading is the same SIZE as the rows beneath it and outranks them by\n * weight and by ink. Both halves of that are corrections. Smaller, it inverted\n * the hierarchy it exists to express — a group read as a footnote over a list\n * rather than as a title over its own contents. In the MONO face, which is\n * where it went next, ten of them stacked in a column read as a terminal\n * listing: mono is this system's voice for code, metadata and figures, and a\n * navigation heading is none of those. Rank belongs to weight and to a step up\n * the ink ladder, which are the two signals that can outrank a row without\n * changing what kind of thing it is.\n *\n * An open group draws a hairline down its rows. Fifty rows under seven headings\n * have nothing in them saying which heading any given row belongs to — only the\n * distance to the last one, which is gone the moment the list is scrolled.\n *\n * The whole block hides when the rail is collapsed to icons: a heading with no\n * room for its own word is two or three letters and a number, and the rows are\n * still there underneath as icons.\n */\nexport function SidebarGroup({\n label,\n count,\n badge,\n action,\n collapsible = true,\n defaultOpen = true,\n children,\n className,\n}: SidebarGroupProps) {\n const { iconOnly } = useSidebarState()\n const [open, setOpen] = useState(defaultOpen)\n const labelId = useId()\n\n const rows = <div className=\"group/rows flex flex-col gap-0.5\">{children}</div>\n\n if (iconOnly) {\n // No heading, and no rule: both of them are words and an indent, and there\n // is room for neither. The rows keep their own group semantics from the\n // label, which is still announced even though it is not drawn.\n return (\n <div role=\"group\" aria-label={label} className={cn('group/rows flex flex-col gap-0.5', className)}>\n {children}\n </div>\n )\n }\n\n const heading = (\n <>\n <span className=\"truncate text-sm font-medium text-(--ink)\">{label}</span>\n {badge}\n {count !== undefined && (\n <span className=\"ms-auto text-xs tabular-nums text-(--ink-3-aa)\">{count}</span>\n )}\n </>\n )\n\n if (!collapsible) {\n return (\n <div role=\"group\" aria-labelledby={labelId} className={cn('flex flex-col gap-1', className)}>\n <div className=\"flex min-h-8 items-center gap-1.5 px-2 py-1\" id={labelId}>\n {heading}\n {action}\n </div>\n <div className=\"ms-[0.9rem] flex flex-col gap-0.5 border-s border-(--rule) ps-2\">{rows}</div>\n </div>\n )\n }\n\n return (\n // Radix rather than `{open && …}`: a conditional render cannot animate — the\n // rows are simply not there on the next frame — and Radix publishes the\n // measured height, so the panel opens to what it actually is rather than to\n // a guessed maximum.\n <Collapsible open={open} onOpenChange={setOpen} className={cn('flex flex-col gap-1', className)}>\n <div className=\"flex min-h-9 items-center gap-1.5\">\n <CollapsibleTrigger className=\"group/heading flex min-h-8 flex-1 items-center gap-1.5 rounded-(--radius-sm) px-2 py-1 text-start transition-colors duration-(--duration-fast) hover:bg-(--stone)\">\n <ChevronRight\n size={12}\n strokeWidth={2}\n aria-hidden\n className=\"shrink-0 text-(--ink-3-aa) transition-transform duration-(--duration-base) ease-(--ease-out-expo) group-data-[state=open]/heading:rotate-90 motion-reduce:transition-none\"\n />\n {heading}\n </CollapsibleTrigger>\n {action}\n </div>\n <CollapsibleContent>\n <div className=\"ms-[0.9rem] flex flex-col gap-0.5 border-s border-(--rule) ps-2\">{rows}</div>\n </CollapsibleContent>\n </Collapsible>\n )\n}\n\n/**\n * One filled row at a time, and it follows the pointer.\n *\n * A rail draws the current page filled and fills a row under the pointer, and\n * those are two different facts wearing the same paint — so a reader with the\n * mouse in the list sees two rows claiming to be where they are. The fill is\n * the POINTER'S while the pointer is in the list, and returns to the current\n * page when it leaves; `aria-current` never moves, so nothing about what is\n * true changes, only what is drawn.\n *\n * `not-hover` rather than an ordering trick: composed with `group-hover`, it\n * says \"the list is hovered and this row is not\" in one selector, which does\n * not depend on which of two equally specific rules Tailwind emitted last.\n */\nconst MOVING_HIGHLIGHT =\n 'group-hover/rows:not-hover:bg-transparent group-hover/rows:not-hover:font-normal group-hover/rows:not-hover:text-(--ink-3-aa)'\n\nexport interface SidebarBranchProps {\n /** The row's own words, and the name of the group it opens. */\n label: string\n /** Drawn at the start of the row, and the whole of the row when collapsed. */\n icon?: LucideIcon\n /** A count or a state at the end of the row. */\n trailing?: ReactNode\n defaultOpen?: boolean\n open?: boolean\n onOpenChange?: (open: boolean) => void\n children: ReactNode\n className?: string\n}\n\n/**\n * A row that opens onto more rows.\n *\n * The thing a rail is for and the thing a flat list of groups cannot do: a\n * workspace with projects in it, a folder with documents in it, a service with\n * its environments. `SidebarGroup` is a HEADING over a set — it is not itself a\n * place, and it has no icon and no state. This is a place that contains places,\n * so it is a row like any other and it carries the same icon, trailing slot and\n * hover as its children.\n *\n * The children sit behind the same hairline a group draws, one indent further\n * in, so nesting reads as depth rather than as two unrelated lists. Two levels\n * is what the indent has room for at this width; a third is a tree, and a tree\n * in a 16rem column is a horizontal scrollbar with an outline in it.\n *\n * Collapsed to icons the row becomes its icon and the children are not drawn —\n * there is nowhere for an indent to go, and a nested icon under an unnested one\n * is two glyphs with no visible relationship.\n *\n * @example\n * <SidebarBranch label=\"Acme HQ\" icon={Building} defaultOpen>\n * <SidebarItem href=\"/hq\" icon={Home}>Home</SidebarItem>\n * <SidebarItem href=\"/hq/tasks\" icon={CheckSquare}>My tasks</SidebarItem>\n * </SidebarBranch>\n */\nexport function SidebarBranch({\n label,\n icon: Icon,\n trailing,\n defaultOpen = false,\n open: controlled,\n onOpenChange,\n children,\n className,\n}: SidebarBranchProps) {\n const { iconOnly, side } = useSidebarState()\n const [uncontrolled, setUncontrolled] = useState(defaultOpen)\n const open = controlled ?? uncontrolled\n\n const setOpen = (next: boolean) => {\n if (controlled === undefined) setUncontrolled(next)\n onOpenChange?.(next)\n }\n\n if (iconOnly && Icon) {\n // The row, and nothing under it. A branch collapsed to an icon is a place\n // you can still see; its contents are a level of structure the width no\n // longer has.\n return (\n <Tooltip content={label} side={side === 'end' ? 'left' : 'right'}>\n <button\n type=\"button\"\n aria-label={label}\n className={cn(\n 'flex min-h-(--control-h-sm) items-center justify-center rounded-(--radius) text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)',\n className,\n )}\n >\n <Icon size={18} strokeWidth={1.5} aria-hidden />\n </button>\n </Tooltip>\n )\n }\n\n return (\n <Collapsible open={open} onOpenChange={setOpen} className={cn('flex flex-col gap-0.5', className)}>\n <CollapsibleTrigger className=\"group/branch flex min-h-(--control-h-sm) items-center gap-3 rounded-(--radius) px-3 py-2 text-start text-sm text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)\">\n {Icon && <Icon size={18} strokeWidth={1.5} aria-hidden className=\"shrink-0\" />}\n <span className=\"truncate\">{label}</span>\n {trailing !== undefined && <span className=\"ms-auto shrink-0\">{trailing}</span>}\n {/* The chevron goes at the END and only when nothing else is there, so\n a row with a count does not put two marks in the same corner. */}\n {trailing === undefined && (\n <ChevronRight\n size={12}\n strokeWidth={2}\n aria-hidden\n className=\"ms-auto shrink-0 transition-transform duration-(--duration-base) ease-(--ease-out-expo) group-data-[state=open]/branch:rotate-90 motion-reduce:transition-none\"\n />\n )}\n </CollapsibleTrigger>\n <CollapsibleContent>\n <div className=\"group/rows ms-[1.35rem] flex flex-col gap-0.5 border-s border-(--rule) ps-2\">\n {children}\n </div>\n </CollapsibleContent>\n </Collapsible>\n )\n}\n\nexport interface SidebarItemProps extends NavItemProps {\n /** A count or a state on the far side of the row. Hidden with the label. */\n trailing?: ReactNode\n}\n\n/**\n * One row.\n *\n * `NavItem` with the two things a RAIL adds: a trailing slot, and an answer for\n * the state where there is no room for words. Collapsed to icons the label is\n * removed from the layout rather than hidden with CSS — a `sr-only` label still\n * occupies the flex row's gap — and moves into a tooltip, because an icon on\n * its own is a guess for everyone and unusable for a screen reader.\n *\n * A row with no `icon` keeps its label collapsed, since hiding it would leave a\n * blank row: the icon is what makes the collapsed state legible, and a rail\n * that collapses needs one on every row.\n */\nexport function SidebarItem({ trailing, children, className, onClick, ...rest }: SidebarItemProps) {\n const { iconOnly, side, mobile, setOpen } = useSidebarState()\n\n // A drawer closes when a row in it is taken. On a phone the rail is over the\n // page, so following a link inside it and leaving it open is a reader landing\n // on a destination they cannot see — and every application that ships this\n // pattern without it has the same bug report.\n const take = (event: MouseEvent<HTMLAnchorElement>) => {\n if (mobile) setOpen(false)\n onClick?.(event)\n }\n\n if (iconOnly && rest.icon && !rest.asChild) {\n return (\n <Tooltip content={children} side={side === 'end' ? 'left' : 'right'}>\n <NavItem {...rest} onClick={take} className={cn('justify-center px-0', className)}>\n <span className=\"sr-only\">{children}</span>\n </NavItem>\n </Tooltip>\n )\n }\n\n // `asChild` hands the row to a router's own Link, and Radix's Slot takes\n // exactly ONE child — a second one, or even the `false` an unused conditional\n // leaves behind, throws rather than renders. So a slotted row passes its\n // children straight through and puts its own trailing content inside the\n // child it was handed. NavItem's `icon` has the same constraint for the same\n // reason, and says so.\n if (rest.asChild) {\n return (\n <NavItem {...rest} onClick={take} className={cn(MOVING_HIGHLIGHT, className)}>\n {children}\n </NavItem>\n )\n }\n\n return (\n <NavItem {...rest} onClick={take} className={cn(MOVING_HIGHLIGHT, className)}>\n {children}\n {trailing !== undefined && <span className=\"ms-auto shrink-0\">{trailing}</span>}\n </NavItem>\n )\n}\n"],"mappings":";AAoYM,SAmFF,UAnFE,KAmFF,YAnFE;AAlYN;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,UAAU;AACnB,SAAS,qBAAqB;AAC9B,SAAS,aAAa,oBAAoB,0BAA0B;AACpE,SAAS,eAAkC;AAC3C,SAAS,SAAS,uBAAuB;AA2CzC,MAAM,SAGF;AAAA,EACF,IAAI;AAAA,IACF,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,UAAU;AAAA,IACV,OAAO;AAAA,IACP,KAAK;AAAA,IACL,WAAW;AAAA,IACX,SAAS;AAAA,IACT,WAAW;AAAA,IACX,SAAS;AAAA,EACX;AAAA,EACA,IAAI;AAAA,IACF,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,UAAU;AAAA,IACV,OAAO;AAAA,IACP,KAAK;AAAA,IACL,WAAW;AAAA,IACX,SAAS;AAAA,IACT,WAAW;AAAA,IACX,SAAS;AAAA,EACX;AAAA,EACA,IAAI;AAAA,IACF,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,UAAU;AAAA,IACV,OAAO;AAAA,IACP,KAAK;AAAA,IACL,WAAW;AAAA,IACX,SAAS;AAAA,IACT,WAAW;AAAA,IACX,SAAS;AAAA,EACX;AAAA,EACA,IAAI;AAAA,IACF,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,UAAU;AAAA,IACV,OAAO;AAAA,IACP,KAAK;AAAA,IACL,WAAW;AAAA,IACX,SAAS;AAAA,IACT,WAAW;AAAA,IACX,SAAS;AAAA,EACX;AACF;AAuBA,MAAM,iBAAiB,cAAmC,IAAI;AAa9D,MAAM,YAA0B;AAAA,EAC9B,MAAM;AAAA,EACN,SAAS,MAAM;AAAA,EAAC;AAAA,EAChB,QAAQ,MAAM;AAAA,EAAC;AAAA,EACf,aAAa;AAAA,EACb,UAAU;AAAA,EACV,MAAM;AAAA,EACN,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,YAAY;AACd;AAGA,SAAS,kBAAgC;AACvC,SAAO,WAAW,cAAc,KAAK;AACvC;AAeO,SAAS,aAA2B;AACzC,QAAM,QAAQ,WAAW,cAAc;AACvC,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,oDAAoD;AAChF,SAAO;AACT;AAkFO,SAAS,gBAAgB;AAAA,EAC9B,MAAM;AAAA,EACN,cAAc;AAAA,EACd;AAAA,EACA,cAAc;AAAA,EACd,WAAW;AAAA,EACX,OAAO;AAAA,EACP,UAAU;AAAA,EACV,aAAa;AAAA,EACb,UAAU;AAAA,EACV,YAAY;AAAA,EACZ;AACF,GAAyB;AACvB,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,WAAW;AAK5D,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAK1C,YAAU,MAAM;AACd,QAAI,eAAe,KAAM;AACzB,UAAM,QAAQ,OAAO,WAAW,OAAO,UAAU,EAAE,KAAK;AACxD,UAAM,OAAO,MAAM,UAAU,MAAM,OAAO;AAC1C,SAAK;AACL,UAAM,iBAAiB,UAAU,IAAI;AACrC,WAAO,MAAM,MAAM,oBAAoB,UAAU,IAAI;AAAA,EACvD,GAAG,CAAC,UAAU,CAAC;AAKf,YAAU,MAAM;AACd,QAAI,OAAQ,WAAU,KAAK;AAAA,EAC7B,GAAG,CAAC,MAAM,CAAC;AAEX,YAAU,MAAM;AACd,QAAI,CAAC,QAAS;AACd,QAAI;AACF,YAAM,SAAS,OAAO,aAAa,QAAQ,OAAO;AAClD,UAAI,WAAW,UAAU,WAAW,SAAU,iBAAgB,WAAW,MAAM;AAAA,IACjF,QAAQ;AAAA,IAER;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AAEZ,YAAU,MAAM;AACd,QAAI,CAAC,QAAS;AACd,QAAI;AACF,aAAO,aAAa,QAAQ,SAAS,eAAe,SAAS,QAAQ;AAAA,IACvE,QAAQ;AAAA,IAER;AAAA,EACF,GAAG,CAAC,SAAS,YAAY,CAAC;AAE1B,QAAM,SAAS,cAAc;AAC7B,QAAM,OAAO,SAAS,SAAS;AAE/B,QAAM,UAAU;AAAA,IACd,CAAC,SAAkB;AACjB,UAAI,OAAQ,WAAU,IAAI;AAAA,eACjB,eAAe,OAAW,iBAAgB,IAAI;AACvD,qBAAe,IAAI;AAAA,IACrB;AAAA,IACA,CAAC,QAAQ,YAAY,YAAY;AAAA,EACnC;AAEA,QAAM,SAAS,YAAY,MAAM,QAAQ,CAAC,IAAI,GAAG,CAAC,MAAM,OAAO,CAAC;AAEhE,YAAU,MAAM;AACd,QAAI,CAAC,YAAY,gBAAgB,OAAQ;AACzC,UAAM,QAAQ,CAAC,UAAyB;AAItC,UAAI,MAAM,IAAI,YAAY,MAAM,SAAS,YAAY,EAAG;AACxD,UAAI,CAAC,MAAM,WAAW,CAAC,MAAM,QAAS;AACtC,YAAM,eAAe;AACrB,aAAO;AAAA,IACT;AACA,WAAO,iBAAiB,WAAW,KAAK;AACxC,WAAO,MAAM,OAAO,oBAAoB,WAAW,KAAK;AAAA,EAC1D,GAAG,CAAC,UAAU,aAAa,MAAM,CAAC;AAElC,QAAM,QAAQ;AAAA,IACZ,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA;AAAA,MAGA,UAAU,CAAC,UAAU,CAAC,QAAQ,gBAAgB;AAAA,MAC9C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,CAAC,MAAM,SAAS,QAAQ,aAAa,MAAM,SAAS,QAAQ,WAAW,UAAU;AAAA,EACnF;AAQA,SACE,oBAAC,eAAe,UAAf,EAAwB,OACvB,8BAAC,mBAAiB,UAAS,GAC7B;AAEJ;AAiEO,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAiB;AACf,QAAM,EAAE,MAAM,SAAS,aAAa,UAAU,MAAM,SAAS,QAAQ,WAAW,WAAW,IACzF,gBAAgB;AAClB,gBAAc,WAAW,SAAS,OAAO,gDAAgD;AAEzF,QAAM,OAAO,CAAC,QAAQ,gBAAgB;AACtC,QAAM,SAAS,eAAe,OAAO,OAAO,OAAO,UAAU;AAE7D,SACE,iCAIG;AAAA,cAAU,UAAU,QACnB;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAY;AAAA,QACZ,SAAS,MAAM,QAAQ,KAAK;AAAA,QAC5B,qBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA,YAAY,aAAa;AAAA,QAC3B;AAAA;AAAA,IACF;AAAA,IAEF;AAAA,MAAC;AAAA;AAAA,QACC,cAAY;AAAA,QACZ,cAAY,OAAO,SAAS;AAAA,QAC5B,oBAAkB;AAAA,QAClB,aAAW;AAAA,QACX,gBAAc;AAAA,QAKd,OAAQ,UAAU,UAAU,CAAC,QAAS;AAAA,QAGtC,WAAW;AAAA,UACT;AAAA,UACA,OAAO,QAAQ,WAAW,yBAAyB;AAAA;AAAA;AAAA;AAAA,UAInD,YAAY,cAAc;AAAA,UAC1B,YAAY,eAAe,SAAS,QAAQ,SAAS;AAAA,UACrD,YAAY,WACV,CAAC,SACA,SAAS,QAAQ,6BAA6B;AAAA;AAAA;AAAA;AAAA,UAIjD,UAAU;AAAA,YACR,OAAO;AAAA,YACP,YAAY,OAAO,WAAW,OAAO;AAAA,YACrC,SAAS,QAAQ,OAAO,MAAM,OAAO;AAAA,YACrC,SAAS,QACL,OACE,OAAO,UACP,OAAO,UACT,OACE,OAAO,YACP,OAAO;AAAA,UACf;AAAA,UACA;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QAMJ,8BAAC,SAAI,WAAW,GAAG,wBAAwB,OAAO,oBAAoB,QAAQ,GAC3E,UACH;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;AAkBO,SAAS,aAAa,EAAE,WAAW,UAAU,GAAG,KAAK,GAA0B;AACpF,QAAM,EAAE,QAAQ,IAAI,gBAAgB;AAEpC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,YAAY,WACV;AAAA,QACF;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AASO,SAAS,cAAc,EAAE,WAAW,UAAU,GAAG,KAAK,GAA0B;AACrF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAGO,SAAS,eAAe,EAAE,WAAW,UAAU,GAAG,KAAK,GAA0B;AACtF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,sEAAsE,SAAS;AAAA,MAC5F,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAQO,SAAS,cAAc,EAAE,WAAW,UAAU,GAAG,KAAK,GAA0B;AACrF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,6DAA6D,SAAS;AAAA,MACnF,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAGO,SAAS,iBAAiB,EAAE,WAAW,GAAG,KAAK,GAAyB;AAC7E,SAAO,oBAAC,QAAG,WAAW,GAAG,0CAA0C,SAAS,GAAI,GAAG,MAAM;AAC3F;AAcO,SAAS,eAAe;AAAA,EAC7B,SAAS,EAAE,MAAM,oBAAoB,OAAO,oBAAoB;AAAA,EAChE;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAwB;AACtB,QAAM,EAAE,MAAM,QAAQ,KAAK,IAAI,gBAAgB;AAG/C,QAAM,OACJ,SAAS,QAAS,OAAO,kBAAkB,iBAAkB,OAAO,iBAAiB;AAEvF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY,OAAO,OAAO,QAAQ,OAAO;AAAA,MACzC,iBAAe;AAAA,MACf,SAAS,CAAC,UAAU;AAClB,eAAO;AACP,kBAAU,KAAK;AAAA,MACjB;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ,8BAAC,QAAK,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC;AAAA;AAAA,EAChD;AAEJ;AAmDO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,cAAc;AAAA,EACd;AAAA,EACA;AACF,GAAsB;AACpB,QAAM,EAAE,SAAS,IAAI,gBAAgB;AACrC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,WAAW;AAC5C,QAAM,UAAU,MAAM;AAEtB,QAAM,OAAO,oBAAC,SAAI,WAAU,oCAAoC,UAAS;AAEzE,MAAI,UAAU;AAIZ,WACE,oBAAC,SAAI,MAAK,SAAQ,cAAY,OAAO,WAAW,GAAG,oCAAoC,SAAS,GAC7F,UACH;AAAA,EAEJ;AAEA,QAAM,UACJ,iCACE;AAAA,wBAAC,UAAK,WAAU,6CAA6C,iBAAM;AAAA,IAClE;AAAA,IACA,UAAU,UACT,oBAAC,UAAK,WAAU,kDAAkD,iBAAM;AAAA,KAE5E;AAGF,MAAI,CAAC,aAAa;AAChB,WACE,qBAAC,SAAI,MAAK,SAAQ,mBAAiB,SAAS,WAAW,GAAG,uBAAuB,SAAS,GACxF;AAAA,2BAAC,SAAI,WAAU,+CAA8C,IAAI,SAC9D;AAAA;AAAA,QACA;AAAA,SACH;AAAA,MACA,oBAAC,SAAI,WAAU,mEAAmE,gBAAK;AAAA,OACzF;AAAA,EAEJ;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKE,qBAAC,eAAY,MAAY,cAAc,SAAS,WAAW,GAAG,uBAAuB,SAAS,GAC5F;AAAA,2BAAC,SAAI,WAAU,qCACb;AAAA,6BAAC,sBAAmB,WAAU,qKAC5B;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAM;AAAA,cACN,aAAa;AAAA,cACb,eAAW;AAAA,cACX,WAAU;AAAA;AAAA,UACZ;AAAA,UACC;AAAA,WACH;AAAA,QACC;AAAA,SACH;AAAA,MACA,oBAAC,sBACC,8BAAC,SAAI,WAAU,mEAAmE,gBAAK,GACzF;AAAA,OACF;AAAA;AAEJ;AAgBA,MAAM,mBACJ;AAyCK,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA,cAAc;AAAA,EACd,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,EAAE,UAAU,KAAK,IAAI,gBAAgB;AAC3C,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,WAAW;AAC5D,QAAM,OAAO,cAAc;AAE3B,QAAM,UAAU,CAAC,SAAkB;AACjC,QAAI,eAAe,OAAW,iBAAgB,IAAI;AAClD,mBAAe,IAAI;AAAA,EACrB;AAEA,MAAI,YAAY,MAAM;AAIpB,WACE,oBAAC,WAAQ,SAAS,OAAO,MAAM,SAAS,QAAQ,SAAS,SACvD;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAY;AAAA,QACZ,WAAW;AAAA,UACT;AAAA,UACA;AAAA,QACF;AAAA,QAEA,8BAAC,QAAK,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC;AAAA;AAAA,IAChD,GACF;AAAA,EAEJ;AAEA,SACE,qBAAC,eAAY,MAAY,cAAc,SAAS,WAAW,GAAG,yBAAyB,SAAS,GAC9F;AAAA,yBAAC,sBAAmB,WAAU,oNAC3B;AAAA,cAAQ,oBAAC,QAAK,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC,WAAU,YAAW;AAAA,MAC5E,oBAAC,UAAK,WAAU,YAAY,iBAAM;AAAA,MACjC,aAAa,UAAa,oBAAC,UAAK,WAAU,oBAAoB,oBAAS;AAAA,MAGvE,aAAa,UACZ;AAAA,QAAC;AAAA;AAAA,UACC,MAAM;AAAA,UACN,aAAa;AAAA,UACb,eAAW;AAAA,UACX,WAAU;AAAA;AAAA,MACZ;AAAA,OAEJ;AAAA,IACA,oBAAC,sBACC,8BAAC,SAAI,WAAU,+EACZ,UACH,GACF;AAAA,KACF;AAEJ;AAoBO,SAAS,YAAY,EAAE,UAAU,UAAU,WAAW,SAAS,GAAG,KAAK,GAAqB;AACjG,QAAM,EAAE,UAAU,MAAM,QAAQ,QAAQ,IAAI,gBAAgB;AAM5D,QAAM,OAAO,CAAC,UAAyC;AACrD,QAAI,OAAQ,SAAQ,KAAK;AACzB,cAAU,KAAK;AAAA,EACjB;AAEA,MAAI,YAAY,KAAK,QAAQ,CAAC,KAAK,SAAS;AAC1C,WACE,oBAAC,WAAQ,SAAS,UAAU,MAAM,SAAS,QAAQ,SAAS,SAC1D,8BAAC,WAAS,GAAG,MAAM,SAAS,MAAM,WAAW,GAAG,uBAAuB,SAAS,GAC9E,8BAAC,UAAK,WAAU,WAAW,UAAS,GACtC,GACF;AAAA,EAEJ;AAQA,MAAI,KAAK,SAAS;AAChB,WACE,oBAAC,WAAS,GAAG,MAAM,SAAS,MAAM,WAAW,GAAG,kBAAkB,SAAS,GACxE,UACH;AAAA,EAEJ;AAEA,SACE,qBAAC,WAAS,GAAG,MAAM,SAAS,MAAM,WAAW,GAAG,kBAAkB,SAAS,GACxE;AAAA;AAAA,IACA,aAAa,UAAa,oBAAC,UAAK,WAAU,oBAAoB,oBAAS;AAAA,KAC1E;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":["'use client'\n\nimport {\n ChevronRight,\n PanelLeftClose,\n PanelLeftOpen,\n PanelRightClose,\n PanelRightOpen,\n type LucideIcon,\n} from 'lucide-react'\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useState,\n type ComponentProps,\n type MouseEvent,\n type ReactNode,\n} from 'react'\nimport { cn } from '../../lib/cn'\nimport { warnBlankName } from '../../lib/warn'\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../Collapsible/Collapsible'\nimport { NavItem, type NavItemProps } from '../NavItem/NavItem'\nimport { Tooltip, TooltipProvider } from '../Tooltip/Tooltip'\n\n/** What happens to the rail when it is closed. See {@link SidebarProviderProps.collapsible}. */\nexport type SidebarCollapsible = 'icon' | 'offcanvas' | 'none'\n\n/** Which edge of the layout the rail is on. Logical, so it follows `dir`. */\nexport type SidebarSide = 'start' | 'end'\n\n/** How the rail meets the page. See {@link SidebarProviderProps.variant}. */\nexport type SidebarVariant = 'flush' | 'inset' | 'floating'\n\n/** Where the rail stops being a column. See {@link SidebarProviderProps.breakpoint}. */\nexport type SidebarBreakpoint = 'sm' | 'md' | 'lg' | 'xl'\n\n/**\n * The drawer, written as CSS rather than decided in JavaScript.\n *\n * The first version asked `matchMedia` and rendered a different tree from the\n * answer. That answer does not exist until an effect runs, so the server sent —\n * and a phone painted — a 256px column beside a 390px screen, and the page\n * scrolled sideways until React caught up. A layout that is wrong for a frame\n * is wrong: it is the first frame, it is the one a reader sees on a slow phone,\n * and on this site's own suite it was a real failure on every route.\n *\n * So the element is the same in both states and the media query decides, which\n * is what a media query is. JavaScript is left with the two things CSS cannot\n * say — `inert`, and whether to render the scrim — and being a frame late on\n * either of those changes nothing anyone can see.\n *\n * The breakpoints are NAMED rather than numeric because these are literal class\n * strings: Tailwind emits what it can read in the source, and a class built\n * from a runtime number is a class that does not exist. Naming them also puts\n * the rail on the same four steps as everything else in the page around it,\n * which a number never quite lands on.\n *\n * It slides on `inset-inline-start`, not on `translate-x`. A transform is a\n * PHYSICAL axis, so the closed drawer needed an `rtl:` twin of every offset —\n * which is the physical-with-a-patch shape this system forbids everywhere else,\n * and which cost 3.6 kB of stylesheet on its own: each `rtl:` selector carries\n * the whole `:lang()` list, four breakpoints deep. A logical inset needs no\n * twin, is one property in each direction, and puts the rail back under the\n * same rule as every `ps-` and `me-` in the package.\n */\nconst DRAWER: Record<\n SidebarBreakpoint,\n { query: string; frame: string; fixed: string; absolute: string; start: string; end: string; openStart: string; openEnd: string; shutStart: string; shutEnd: string }\n> = {\n sm: {\n query: '(max-width: 39.99rem)',\n frame: 'max-sm:inset-y-0 max-sm:z-(--z-modal) max-sm:w-(--sidebar-w) max-sm:m-0 max-sm:rounded-none max-sm:border-0 max-sm:transition-[inset-inline-start,inset-inline-end]',\n fixed: 'max-sm:fixed',\n absolute: 'max-sm:absolute',\n start: 'max-sm:border-e max-sm:border-(--rule)',\n end: 'max-sm:border-s max-sm:border-(--rule)',\n openStart: 'max-sm:start-0',\n openEnd: 'max-sm:end-0',\n shutStart: 'max-sm:-start-(--sidebar-w)',\n shutEnd: 'max-sm:-end-(--sidebar-w)',\n },\n md: {\n query: '(max-width: 47.99rem)',\n frame: 'max-md:inset-y-0 max-md:z-(--z-modal) max-md:w-(--sidebar-w) max-md:m-0 max-md:rounded-none max-md:border-0 max-md:transition-[inset-inline-start,inset-inline-end]',\n fixed: 'max-md:fixed',\n absolute: 'max-md:absolute',\n start: 'max-md:border-e max-md:border-(--rule)',\n end: 'max-md:border-s max-md:border-(--rule)',\n openStart: 'max-md:start-0',\n openEnd: 'max-md:end-0',\n shutStart: 'max-md:-start-(--sidebar-w)',\n shutEnd: 'max-md:-end-(--sidebar-w)',\n },\n lg: {\n query: '(max-width: 63.99rem)',\n frame: 'max-lg:inset-y-0 max-lg:z-(--z-modal) max-lg:w-(--sidebar-w) max-lg:m-0 max-lg:rounded-none max-lg:border-0 max-lg:transition-[inset-inline-start,inset-inline-end]',\n fixed: 'max-lg:fixed',\n absolute: 'max-lg:absolute',\n start: 'max-lg:border-e max-lg:border-(--rule)',\n end: 'max-lg:border-s max-lg:border-(--rule)',\n openStart: 'max-lg:start-0',\n openEnd: 'max-lg:end-0',\n shutStart: 'max-lg:-start-(--sidebar-w)',\n shutEnd: 'max-lg:-end-(--sidebar-w)',\n },\n xl: {\n query: '(max-width: 79.99rem)',\n frame: 'max-xl:inset-y-0 max-xl:z-(--z-modal) max-xl:w-(--sidebar-w) max-xl:m-0 max-xl:rounded-none max-xl:border-0 max-xl:transition-[inset-inline-start,inset-inline-end]',\n fixed: 'max-xl:fixed',\n absolute: 'max-xl:absolute',\n start: 'max-xl:border-e max-xl:border-(--rule)',\n end: 'max-xl:border-s max-xl:border-(--rule)',\n openStart: 'max-xl:start-0',\n openEnd: 'max-xl:end-0',\n shutStart: 'max-xl:-start-(--sidebar-w)',\n shutEnd: 'max-xl:-end-(--sidebar-w)',\n },\n}\n\ninterface SidebarState {\n /** Whether the rail is showing — the docked column, or the open drawer. */\n open: boolean\n setOpen: (open: boolean) => void\n toggle: () => void\n collapsible: SidebarCollapsible\n /** True only in the state where labels are hidden and rows are icons. */\n iconOnly: boolean\n side: SidebarSide\n variant: SidebarVariant\n /**\n * True while the viewport is under the provider's breakpoint, where the rail\n * is a drawer over the page rather than a column beside it.\n */\n mobile: boolean\n /** The drawer covers its positioned ancestor rather than the window. */\n contained: boolean\n /** The step below which the rail is a drawer, or `null` for never. */\n breakpoint: SidebarBreakpoint | null\n}\n\nconst SidebarContext = createContext<SidebarState | null>(null)\n\n/**\n * What the parts read when nobody wrapped them: open, and not collapsible.\n *\n * A rail without a provider used to THROW, which is defensible for a hook a\n * consumer called by hand and wrong for the component itself — `<Sidebar>` on\n * its own is the first thing anybody writes, and the documentation site's own\n * props panel renders exactly that. A component that cannot draw without a\n * wrapper is not strict, it is unusable; so the parts fall back to the state a\n * rail with no controls would be in, and the button that would toggle it does\n * nothing because there is nothing holding the answer.\n */\nconst UNWRAPPED: SidebarState = {\n open: true,\n setOpen: () => {},\n toggle: () => {},\n collapsible: 'none',\n iconOnly: false,\n side: 'start',\n variant: 'flush',\n mobile: false,\n contained: false,\n breakpoint: null,\n}\n\n/** The state the parts read, wrapped or not. */\nfunction useSidebarState(): SidebarState {\n return useContext(SidebarContext) ?? UNWRAPPED\n}\n\n/**\n * The rail's own state, for anything that has to answer to it.\n *\n * A page beside the rail needs it to reserve the right width; a control inside\n * needs to know whether its label is being drawn.\n *\n * This one THROWS outside a provider, and the parts above do not, and the\n * difference is who made the mistake. A part rendered on its own is somebody\n * writing `<Sidebar>` to see what it looks like; a call to this hook is code\n * asking for state that nothing is keeping, and returning a plausible default\n * there is a layout that is wrong in one state and right in the other with\n * nothing to say which.\n */\nexport function useSidebar(): SidebarState {\n const state = useContext(SidebarContext)\n if (!state) throw new Error('useSidebar must be used inside a <SidebarProvider>')\n return state\n}\n\nexport interface SidebarProviderProps {\n /** Controlled open state. Leave off to let the provider own it. */\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n /** What closing does to the rail. See {@link SidebarProps.collapsible}. */\n collapsible?: SidebarCollapsible\n /**\n * The keyboard shortcut that toggles the rail, as a single letter.\n *\n * `b`, with the platform's own modifier, because that is what every editor\n * and every application shell already uses for the same thing. Pass `null`\n * to bind nothing — an app that already owns that chord should not have it\n * taken twice, and a rail that cannot be closed does not need a shortcut for\n * closing it.\n */\n shortcut?: string | null\n /** Which edge the rail is on. Logical: `end` is the right in LTR, the left in RTL. */\n side?: SidebarSide\n /**\n * How the rail meets the page.\n *\n * `flush` is a column with a hairline down its inner edge — the rail and the\n * page are one surface divided by a line. `floating` lifts the rail off that\n * surface as its own bordered panel with the page ground showing around it.\n * `inset` is the same gesture the other way up: the rail sits on the page\n * ground and `SidebarInset` draws the CONTENT as the panel.\n *\n * `floating` and `inset` both need a ground to sit on — put `bg-(--stone)` on\n * the element holding the provider, or they are a panel on the same colour as\n * the thing behind it.\n */\n variant?: SidebarVariant\n /**\n * The step below which the rail becomes a drawer over the page.\n *\n * A rail is a column when there is a column's worth of room and an overlay\n * when there is not; below this the rail is `fixed`, covers the page from its\n * own edge, and closes onto a scrim. `null` pins it as a column at every\n * width, for a layout that is never narrow — an embedded console, a preview\n * frame.\n *\n * Named, not a number: the switch is a media query in the stylesheet, and a\n * class built from a runtime number is a class the compiler never emitted. It\n * also puts the rail on the same four steps as the page around it.\n */\n breakpoint?: SidebarBreakpoint | null\n /**\n * A `localStorage` key under which the DOCKED state is remembered.\n *\n * A reader who put the rail away did not mean \"until the next page\". Only the\n * docked state is kept: an open drawer is a thing a reader did to this screen,\n * and restoring it on the next visit is a page that opens with its navigation\n * over the top of itself.\n *\n * Read after mount, never during render — a value from storage in the first\n * pass is a hydration mismatch, and the markup the server sent is the one the\n * client has to agree with.\n */\n persist?: string | null\n /**\n * The drawer covers its nearest positioned ancestor rather than the window.\n *\n * For a rail inside a bounded frame — a device preview, an embedded console,\n * an example on a documentation page. `fixed` resolves against the viewport\n * wherever the markup sits, so without this the drawer inside a 400px preview\n * opens across the whole page it is previewed on. Give the frame `relative`.\n */\n contained?: boolean\n children: ReactNode\n}\n\n/**\n * Holds whether the rail is open, and binds the shortcut that changes it.\n *\n * Separate from `Sidebar` itself because the answer is needed on BOTH sides of\n * the layout: the rail draws itself from it, and the content beside the rail\n * reserves width from it. A state that lived inside the rail could only ever\n * be read downwards.\n */\nexport function SidebarProvider({\n open: controlled,\n defaultOpen = true,\n onOpenChange,\n collapsible = 'icon',\n shortcut = 'b',\n side = 'start',\n variant = 'flush',\n breakpoint = 'md',\n persist = null,\n contained = false,\n children,\n}: SidebarProviderProps) {\n const [uncontrolled, setUncontrolled] = useState(defaultOpen)\n // The drawer is its own answer, not the docked column's. They are different\n // questions — \"keep the rail beside the page\" and \"show me the rail now\" —\n // and one state answering both is a phone that loads with its navigation\n // covering the page, because `defaultOpen` was written for the column.\n const [drawer, setDrawer] = useState(false)\n const [mobile, setMobile] = useState(false)\n\n // The same query the classes carry, asked in JavaScript for the two things a\n // class cannot answer: whether the closed rail is `inert`, and whether there\n // is a scrim. The LAYOUT is not waiting on this — see `DRAWER`.\n useEffect(() => {\n if (breakpoint === null) return\n const query = window.matchMedia(DRAWER[breakpoint].query)\n const sync = () => setMobile(query.matches)\n sync()\n query.addEventListener('change', sync)\n return () => query.removeEventListener('change', sync)\n }, [breakpoint])\n\n // Crossing INTO drawer width closes it. A rail that was docked a moment ago\n // would otherwise become an overlay sitting open over the page nobody asked\n // to have covered.\n useEffect(() => {\n if (mobile) setDrawer(false)\n }, [mobile])\n\n useEffect(() => {\n if (!persist) return\n try {\n const stored = window.localStorage.getItem(persist)\n if (stored === 'open' || stored === 'closed') setUncontrolled(stored === 'open')\n } catch {\n // A storage-blocked context just starts at `defaultOpen` every time.\n }\n }, [persist])\n\n useEffect(() => {\n if (!persist) return\n try {\n window.localStorage.setItem(persist, uncontrolled ? 'open' : 'closed')\n } catch {\n // Same.\n }\n }, [persist, uncontrolled])\n\n const docked = controlled ?? uncontrolled\n const open = mobile ? drawer : docked\n\n const setOpen = useCallback(\n (next: boolean) => {\n if (mobile) setDrawer(next)\n else if (controlled === undefined) setUncontrolled(next)\n onOpenChange?.(next)\n },\n [mobile, controlled, onOpenChange],\n )\n\n const toggle = useCallback(() => setOpen(!open), [open, setOpen])\n\n useEffect(() => {\n if (!shortcut || collapsible === 'none') return\n const onKey = (event: KeyboardEvent) => {\n // `metaKey` OR `ctrlKey`, not the platform's own: a Mac keyboard on a\n // Linux browser and a PC keyboard on a Mac both exist, and a shortcut\n // that works for one of them is a shortcut with a bug report attached.\n if (event.key.toLowerCase() !== shortcut.toLowerCase()) return\n if (!event.metaKey && !event.ctrlKey) return\n event.preventDefault()\n toggle()\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [shortcut, collapsible, toggle])\n\n const value = useMemo<SidebarState>(\n () => ({\n open,\n setOpen,\n toggle,\n collapsible,\n // Never at drawer width: closed there means the rail is off the page, and\n // a column of icons is the answer for a layout that still has a column.\n iconOnly: !mobile && !open && collapsible === 'icon',\n side,\n variant,\n mobile,\n contained,\n breakpoint,\n }),\n [open, setOpen, toggle, collapsible, side, variant, mobile, contained, breakpoint],\n )\n\n // The tooltip provider is supplied here rather than demanded of the app. A\n // collapsed rail is rows of icons, every one of which names itself through a\n // tooltip — so without it the component does not degrade, it throws, and only\n // in the state a reader reaches by pressing the button on it. Radix scopes\n // nested providers, so an app that already has one keeps its own settings for\n // everything outside this subtree.\n return (\n <SidebarContext.Provider value={value}>\n <TooltipProvider>{children}</TooltipProvider>\n </SidebarContext.Provider>\n )\n}\n\nexport interface SidebarProps extends Omit<ComponentProps<'nav'>, 'children'> {\n /**\n * Names the landmark. Required: a page with two navigations in it announces\n * two things called \"navigation\" unless each of them says which it is.\n */\n label: string\n /**\n * What the scrim behind the open drawer announces.\n *\n * It is a button — tapping beside a drawer closes it, and that has to be a\n * control a keyboard and a screen reader can reach, not a decorated `<div>`.\n */\n scrimLabel?: string\n children: ReactNode\n}\n\n/**\n * A navigation rail down the side of an application.\n *\n * A `<nav>`, not an `<aside>`. The element decides the landmark, and a rail of\n * links announced as \"complementary\" is not the one a screen reader user jumps\n * to when they go looking for the navigation.\n *\n * Composed rather than configured: a rail is a header, a scrolling middle and a\n * footer, and every product wants different things in all three. What this owns\n * is the part that is the same everywhere — the width, the edge, the scroll\n * behaviour, and what happens when it closes.\n *\n * **Closing has three shapes**, and `collapsible` on the provider picks one.\n * `icon` keeps the rail and drops the labels, which is right when the rows are\n * a fixed set a reader learns the shape of. `offcanvas` takes the whole rail\n * away, which is right when the rows are a long index nobody memorises.\n * `none` is a rail that does not close.\n *\n * The trigger belongs INSIDE the rail — `SidebarHeader` places it — rather than\n * out in an application's masthead. A control that hides a thing should live on\n * the thing: in the masthead it is one more anonymous icon in a row of them,\n * and nothing connects it to the column it operates.\n *\n * **Under the provider's `breakpoint` it is a drawer**, not a column: `fixed`\n * against its own edge, over a scrim, and `inert` while closed. Merely\n * translating it off-screen is not closed — it keeps focus and it is still read\n * aloud, so a shut drawer puts its whole index between the reader and the page\n * they were on. The scrim is a real button, because tapping beside a drawer is\n * how a drawer is closed and that gesture has to exist for a keyboard too.\n *\n * `fixed` resolves against the viewport unless an ancestor has a `transform`,\n * `filter` or `perspective`, which makes that ancestor the containing block\n * instead. Do not put one on an element wrapping the provider, or the drawer\n * opens inside it.\n *\n * @example\n * <SidebarProvider>\n * <Sidebar label=\"Documentation\">\n * <SidebarHeader>Acme</SidebarHeader>\n * <SidebarContent>\n * <SidebarGroup label=\"Guide\" count={2}>\n * <SidebarItem href=\"/start\" icon={Home}>Getting started</SidebarItem>\n * </SidebarGroup>\n * </SidebarContent>\n * </Sidebar>\n * </SidebarProvider>\n */\nexport function Sidebar({\n label,\n scrimLabel = 'Close the navigation',\n className,\n children,\n ...rest\n}: SidebarProps) {\n const { open, setOpen, collapsible, iconOnly, side, variant, mobile, contained, breakpoint } =\n useSidebarState()\n warnBlankName('Sidebar', 'label', label, 'the rail is announced as an unnamed navigation')\n\n const gone = !open && collapsible === 'offcanvas'\n const drawer = breakpoint === null ? null : DRAWER[breakpoint]\n\n return (\n <>\n {/* Only while it is actually an overlay. On a column there is nothing to\n close onto, and a full-page button over a docked rail is a page that\n cannot be clicked. */}\n {drawer && mobile && open && (\n <button\n type=\"button\"\n aria-label={scrimLabel}\n onClick={() => setOpen(false)}\n data-m22-animated\n className={cn(\n 'inset-0 z-(--z-scrim) bg-(--scrim) animate-[m22-fade-in_var(--duration-fast)_var(--ease)]',\n contained ? 'absolute' : 'fixed',\n )}\n />\n )}\n <nav\n aria-label={label}\n data-state={open ? 'open' : 'collapsed'}\n data-collapsible={collapsible}\n data-side={side}\n data-variant={variant}\n // Off-screen is not closed. Without this the drawer keeps focus and\n // keeps being read aloud while it sits beyond the edge of the page.\n // JavaScript decides it, and a frame's delay costs nothing: the\n // POSITION is CSS, so nothing has moved.\n inert={(drawer && mobile && !open) || undefined}\n // `group`, so every descendant can answer the rail's state without\n // being handed a prop through four levels of composition.\n className={cn(\n 'group/sidebar flex shrink-0 flex-col overflow-hidden bg-(--paper) transition-[width] duration-(--duration-base) ease-(--ease-out-expo) motion-reduce:transition-none',\n gone ? 'w-0' : iconOnly ? 'w-(--sidebar-w-icon)' : 'w-(--sidebar-w)',\n // `inset` gives the border to `SidebarInset`, which is the panel in\n // that arrangement; the rail is the ground it sits on and draws no\n // edge of its own, or the two lines run parallel a few pixels apart.\n variant === 'floating' && 'my-2 rounded-(--radius-frame) border border-(--rule)',\n variant === 'floating' && (side === 'end' ? 'me-2' : 'ms-2'),\n variant === 'flush' &&\n !gone &&\n (side === 'end' ? 'border-s border-(--rule)' : 'border-e border-(--rule)'),\n // The drawer, below the step. Everything above is the column; these\n // override it inside one media query, so the first paint is already\n // right and no effect has to run for the page to be the right width.\n drawer && [\n drawer.frame,\n contained ? drawer.absolute : drawer.fixed,\n side === 'end' ? drawer.end : drawer.start,\n side === 'end'\n ? open\n ? drawer.openEnd\n : drawer.shutEnd\n : open\n ? drawer.openStart\n : drawer.shutStart,\n ],\n className,\n )}\n {...rest}\n >\n {/* The inner column is the full width at every state, so the rows\n inside do not reflow while the rail animates — they are clipped by\n the rail instead, which is what makes the motion read as a panel\n sliding rather than as a column of text reflowing under a wipe. */}\n <div className={cn('flex h-full flex-col', gone ? 'w-(--sidebar-w)' : 'w-full')}>\n {children}\n </div>\n </nav>\n </>\n )\n}\n\n/**\n * The page beside the rail.\n *\n * Every layout built on this component was writing the same\n * `flex min-w-0 flex-1 flex-col` by hand, and `min-w-0` is the half everybody\n * forgets: a flex child's floor is its content, so one wide table inside pushes\n * the whole page past the viewport and takes the rail's width with it.\n *\n * It is also the other half of `variant=\"inset\"`. There the rail is the ground\n * and this is the panel — a bordered `--paper` surface with the ground showing\n * around it — which is why the variant lives on the provider rather than on\n * either piece: one setting, two components, and no way to set half of it.\n *\n * A `<div>`, not a `<main>`. What goes in here is usually a masthead AND the\n * page under it, and only one of those is the main landmark.\n */\nexport function SidebarInset({ className, children, ...rest }: ComponentProps<'div'>) {\n const { variant } = useSidebarState()\n\n return (\n <div\n className={cn(\n 'flex min-w-0 flex-1 flex-col',\n variant === 'inset' &&\n 'my-2 me-2 overflow-hidden rounded-(--radius-frame) border border-(--rule) bg-(--paper) ms-0',\n className,\n )}\n {...rest}\n >\n {children}\n </div>\n )\n}\n\n/**\n * The block at the top of the rail: a brand, a workspace, a switcher.\n *\n * It is also where `SidebarTrigger` belongs, and the layout assumes one:\n * anything passed as `children` takes the space and the trigger sits at the\n * inline end of the row.\n *\n * `--bar-h` is the floor, not a number of its own. This block and the masthead\n * beside it meet at a corner, and their two bottom rules are read as one line —\n * so a floor typed here is a floor that has to be retyped there, and the two\n * were 56px and 64px for exactly that reason. The token also survives a rail\n * that pins its own density: `--bar-h` is declared on the root, so its\n * `var(--control-h-md)` resolves there and inherits as one answer, which is\n * what keeps a compact rail's head level with a comfortable page's bar.\n */\nexport function SidebarHeader({ className, children, ...rest }: ComponentProps<'div'>) {\n return (\n <div\n className={cn(\n 'flex min-h-(--bar-h) shrink-0 items-center gap-2 border-b border-(--rule) px-3',\n className,\n )}\n {...rest}\n >\n {children}\n </div>\n )\n}\n\n/** The scrolling middle. Everything that is a list of places goes here. */\nexport function SidebarContent({ className, children, ...rest }: ComponentProps<'div'>) {\n return (\n <div\n className={cn('flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto p-2 scroll-slim', className)}\n {...rest}\n >\n {children}\n </div>\n )\n}\n\n/**\n * The block at the bottom: the utilities, the account, the thing a rail ends on.\n *\n * A separate landmark from the content above it because it is a separate kind\n * of thing — a reader scanning the index does not want Trash and Help in it.\n */\nexport function SidebarFooter({ className, children, ...rest }: ComponentProps<'div'>) {\n return (\n <div\n className={cn('flex shrink-0 flex-col gap-1 border-t border-(--rule) p-2', className)}\n {...rest}\n >\n {children}\n </div>\n )\n}\n\n/** A rule between blocks of rows, inset to the rail's own padding. */\nexport function SidebarSeparator({ className, ...rest }: ComponentProps<'hr'>) {\n return <hr className={cn('my-1 border-0 border-t border-(--rule)', className)} {...rest} />\n}\n\nexport interface SidebarTriggerProps extends ComponentProps<'button'> {\n /** What the button announces. Both states, because it says both things. */\n labels?: { open: string; close: string }\n}\n\n/**\n * The control that opens and closes the rail.\n *\n * Its accessible name changes with what it will DO, and `aria-expanded` reports\n * what is true now — a button permanently called \"Toggle sidebar\" tells a\n * screen reader user nothing about which way it will go.\n */\nexport function SidebarTrigger({\n labels = { open: 'Open the sidebar', close: 'Close the sidebar' },\n className,\n onClick,\n ...rest\n}: SidebarTriggerProps) {\n const { open, toggle, side } = useSidebarState()\n // The glyph points at the edge the rail is on, so the button is a picture of\n // what it will do rather than a picture of somebody else's layout.\n const Icon =\n side === 'end' ? (open ? PanelRightClose : PanelRightOpen) : open ? PanelLeftClose : PanelLeftOpen\n\n return (\n <button\n type=\"button\"\n aria-label={open ? labels.close : labels.open}\n aria-expanded={open}\n onClick={(event) => {\n toggle()\n onClick?.(event)\n }}\n className={cn(\n 'grid size-8 shrink-0 place-items-center rounded-(--radius) text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)',\n className,\n )}\n {...rest}\n >\n <Icon size={16} strokeWidth={1.5} aria-hidden />\n </button>\n )\n}\n\nexport interface SidebarGroupProps {\n /** The heading over the rows. */\n label: string\n /** How many rows are inside, printed on the far side of the heading. */\n count?: number\n /**\n * A mark that belongs to the GROUP — \"Beta\", \"3 new\".\n *\n * Beside the label, not out at the end with the count: it qualifies the\n * words, and a qualifier that has drifted to the other side of the row reads\n * as a second, unrelated fact.\n */\n badge?: ReactNode\n /**\n * A control on the heading row — a menu, an \"add\" button.\n *\n * Sits between the label and the count, and is NOT rendered inside the\n * heading's own button: a control nested in a control is one the keyboard\n * reaches by pressing the thing it is inside.\n */\n action?: ReactNode\n /** Whether the group folds at all. A group of two rows usually should not. */\n collapsible?: boolean\n defaultOpen?: boolean\n children: ReactNode\n className?: string\n}\n\n/**\n * A labelled block of rows, optionally foldable.\n *\n * The heading is the same SIZE as the rows beneath it and outranks them by\n * weight and by ink. Both halves of that are corrections. Smaller, it inverted\n * the hierarchy it exists to express — a group read as a footnote over a list\n * rather than as a title over its own contents. In the MONO face, which is\n * where it went next, ten of them stacked in a column read as a terminal\n * listing: mono is this system's voice for code, metadata and figures, and a\n * navigation heading is none of those. Rank belongs to weight and to a step up\n * the ink ladder, which are the two signals that can outrank a row without\n * changing what kind of thing it is.\n *\n * An open group draws a hairline down its rows. Fifty rows under seven headings\n * have nothing in them saying which heading any given row belongs to — only the\n * distance to the last one, which is gone the moment the list is scrolled.\n *\n * The whole block hides when the rail is collapsed to icons: a heading with no\n * room for its own word is two or three letters and a number, and the rows are\n * still there underneath as icons.\n */\nexport function SidebarGroup({\n label,\n count,\n badge,\n action,\n collapsible = true,\n defaultOpen = true,\n children,\n className,\n}: SidebarGroupProps) {\n const { iconOnly } = useSidebarState()\n const [open, setOpen] = useState(defaultOpen)\n const labelId = useId()\n\n const rows = <div className=\"group/rows flex flex-col gap-0.5\">{children}</div>\n\n if (iconOnly) {\n // No heading, and no rule: both of them are words and an indent, and there\n // is room for neither. The rows keep their own group semantics from the\n // label, which is still announced even though it is not drawn.\n return (\n <div role=\"group\" aria-label={label} className={cn('group/rows flex flex-col gap-0.5', className)}>\n {children}\n </div>\n )\n }\n\n const heading = (\n <>\n <span className=\"truncate text-sm font-medium text-(--ink)\">{label}</span>\n {badge}\n {count !== undefined && (\n <span className=\"ms-auto text-xs tabular-nums text-(--ink-3-aa)\">{count}</span>\n )}\n </>\n )\n\n if (!collapsible) {\n return (\n <div role=\"group\" aria-labelledby={labelId} className={cn('flex flex-col gap-1', className)}>\n <div className=\"flex min-h-8 items-center gap-1.5 px-2 py-1\" id={labelId}>\n {heading}\n {action}\n </div>\n <div className=\"ms-[0.9rem] flex flex-col gap-0.5 border-s border-(--rule) ps-2\">{rows}</div>\n </div>\n )\n }\n\n return (\n // Radix rather than `{open && …}`: a conditional render cannot animate — the\n // rows are simply not there on the next frame — and Radix publishes the\n // measured height, so the panel opens to what it actually is rather than to\n // a guessed maximum.\n <Collapsible open={open} onOpenChange={setOpen} className={cn('flex flex-col gap-1', className)}>\n <div className=\"flex min-h-9 items-center gap-1.5\">\n <CollapsibleTrigger className=\"group/heading flex min-h-8 flex-1 items-center gap-1.5 rounded-(--radius-sm) px-2 py-1 text-start transition-colors duration-(--duration-fast) hover:bg-(--stone)\">\n <ChevronRight\n size={12}\n strokeWidth={2}\n aria-hidden\n className=\"shrink-0 text-(--ink-3-aa) transition-transform duration-(--duration-base) ease-(--ease-out-expo) group-data-[state=open]/heading:rotate-90 motion-reduce:transition-none\"\n />\n {heading}\n </CollapsibleTrigger>\n {action}\n </div>\n <CollapsibleContent>\n <div className=\"ms-[0.9rem] flex flex-col gap-0.5 border-s border-(--rule) ps-2\">{rows}</div>\n </CollapsibleContent>\n </Collapsible>\n )\n}\n\n/**\n * One filled row at a time, and it follows the pointer.\n *\n * A rail draws the current page filled and fills a row under the pointer, and\n * those are two different facts wearing the same paint — so a reader with the\n * mouse in the list sees two rows claiming to be where they are. The fill is\n * the POINTER'S while the pointer is in the list, and returns to the current\n * page when it leaves; `aria-current` never moves, so nothing about what is\n * true changes, only what is drawn.\n *\n * `not-hover` rather than an ordering trick: composed with `group-hover`, it\n * says \"the list is hovered and this row is not\" in one selector, which does\n * not depend on which of two equally specific rules Tailwind emitted last.\n *\n * The WEIGHT is scoped to the current row, and the other two are not, because\n * only the current row has anything to give up: it alone carries `font-medium`,\n * `bg-(--stone)` and `--ink`, and on any other row those three are no-ops. Two\n * of them stayed no-ops. `font-normal` did not — it is an absolute weight, not\n * an undo, so in a rail whose base face is lighter than 400 it made every row\n * in the list HEAVIER the moment the pointer arrived. Selecting one row\n * appeared to embolden its whole section, which is the opposite of a highlight\n * that follows the pointer.\n */\nconst MOVING_HIGHLIGHT =\n 'group-hover/rows:not-hover:bg-transparent group-hover/rows:not-hover:text-(--ink-3-aa) group-hover/rows:not-hover:aria-[current=page]:font-normal'\n\nexport interface SidebarBranchProps {\n /** The row's own words, and the name of the group it opens. */\n label: string\n /** Drawn at the start of the row, and the whole of the row when collapsed. */\n icon?: LucideIcon\n /** A count or a state at the end of the row. */\n trailing?: ReactNode\n defaultOpen?: boolean\n open?: boolean\n onOpenChange?: (open: boolean) => void\n children: ReactNode\n className?: string\n}\n\n/**\n * A row that opens onto more rows.\n *\n * The thing a rail is for and the thing a flat list of groups cannot do: a\n * workspace with projects in it, a folder with documents in it, a service with\n * its environments. `SidebarGroup` is a HEADING over a set — it is not itself a\n * place, and it has no icon and no state. This is a place that contains places,\n * so it is a row like any other and it carries the same icon, trailing slot and\n * hover as its children.\n *\n * The children sit behind the same hairline a group draws, one indent further\n * in, so nesting reads as depth rather than as two unrelated lists. Two levels\n * is what the indent has room for at this width; a third is a tree, and a tree\n * in a 16rem column is a horizontal scrollbar with an outline in it.\n *\n * Collapsed to icons the row becomes its icon and the children are not drawn —\n * there is nowhere for an indent to go, and a nested icon under an unnested one\n * is two glyphs with no visible relationship.\n *\n * @example\n * <SidebarBranch label=\"Acme HQ\" icon={Building} defaultOpen>\n * <SidebarItem href=\"/hq\" icon={Home}>Home</SidebarItem>\n * <SidebarItem href=\"/hq/tasks\" icon={CheckSquare}>My tasks</SidebarItem>\n * </SidebarBranch>\n */\nexport function SidebarBranch({\n label,\n icon: Icon,\n trailing,\n defaultOpen = false,\n open: controlled,\n onOpenChange,\n children,\n className,\n}: SidebarBranchProps) {\n const { iconOnly, side } = useSidebarState()\n const [uncontrolled, setUncontrolled] = useState(defaultOpen)\n const open = controlled ?? uncontrolled\n\n const setOpen = (next: boolean) => {\n if (controlled === undefined) setUncontrolled(next)\n onOpenChange?.(next)\n }\n\n if (iconOnly && Icon) {\n // The row, and nothing under it. A branch collapsed to an icon is a place\n // you can still see; its contents are a level of structure the width no\n // longer has.\n return (\n <Tooltip content={label} side={side === 'end' ? 'left' : 'right'}>\n <button\n type=\"button\"\n aria-label={label}\n className={cn(\n 'flex min-h-(--control-h-sm) items-center justify-center rounded-(--radius) text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)',\n className,\n )}\n >\n <Icon size={18} strokeWidth={1.5} aria-hidden />\n </button>\n </Tooltip>\n )\n }\n\n return (\n <Collapsible open={open} onOpenChange={setOpen} className={cn('flex flex-col gap-0.5', className)}>\n <CollapsibleTrigger className=\"group/branch flex min-h-(--control-h-sm) items-center gap-3 rounded-(--radius) px-3 py-2 text-start text-sm text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)\">\n {Icon && <Icon size={18} strokeWidth={1.5} aria-hidden className=\"shrink-0\" />}\n <span className=\"truncate\">{label}</span>\n {trailing !== undefined && <span className=\"ms-auto shrink-0\">{trailing}</span>}\n {/* The chevron goes at the END and only when nothing else is there, so\n a row with a count does not put two marks in the same corner. */}\n {trailing === undefined && (\n <ChevronRight\n size={12}\n strokeWidth={2}\n aria-hidden\n className=\"ms-auto shrink-0 transition-transform duration-(--duration-base) ease-(--ease-out-expo) group-data-[state=open]/branch:rotate-90 motion-reduce:transition-none\"\n />\n )}\n </CollapsibleTrigger>\n <CollapsibleContent>\n <div className=\"group/rows ms-[1.35rem] flex flex-col gap-0.5 border-s border-(--rule) ps-2\">\n {children}\n </div>\n </CollapsibleContent>\n </Collapsible>\n )\n}\n\nexport interface SidebarItemProps extends NavItemProps {\n /** A count or a state on the far side of the row. Hidden with the label. */\n trailing?: ReactNode\n}\n\n/**\n * One row.\n *\n * `NavItem` with the two things a RAIL adds: a trailing slot, and an answer for\n * the state where there is no room for words. Collapsed to icons the label is\n * removed from the layout rather than hidden with CSS — a `sr-only` label still\n * occupies the flex row's gap — and moves into a tooltip, because an icon on\n * its own is a guess for everyone and unusable for a screen reader.\n *\n * A row with no `icon` keeps its label collapsed, since hiding it would leave a\n * blank row: the icon is what makes the collapsed state legible, and a rail\n * that collapses needs one on every row.\n */\nexport function SidebarItem({ trailing, children, className, onClick, ...rest }: SidebarItemProps) {\n const { iconOnly, side, mobile, setOpen } = useSidebarState()\n\n // A drawer closes when a row in it is taken. On a phone the rail is over the\n // page, so following a link inside it and leaving it open is a reader landing\n // on a destination they cannot see — and every application that ships this\n // pattern without it has the same bug report.\n const take = (event: MouseEvent<HTMLAnchorElement>) => {\n if (mobile) setOpen(false)\n onClick?.(event)\n }\n\n if (iconOnly && rest.icon && !rest.asChild) {\n return (\n <Tooltip content={children} side={side === 'end' ? 'left' : 'right'}>\n <NavItem {...rest} onClick={take} className={cn('justify-center px-0', className)}>\n <span className=\"sr-only\">{children}</span>\n </NavItem>\n </Tooltip>\n )\n }\n\n // `asChild` hands the row to a router's own Link, and Radix's Slot takes\n // exactly ONE child — a second one, or even the `false` an unused conditional\n // leaves behind, throws rather than renders. So a slotted row passes its\n // children straight through and puts its own trailing content inside the\n // child it was handed. NavItem's `icon` has the same constraint for the same\n // reason, and says so.\n if (rest.asChild) {\n return (\n <NavItem {...rest} onClick={take} className={cn(MOVING_HIGHLIGHT, className)}>\n {children}\n </NavItem>\n )\n }\n\n return (\n <NavItem {...rest} onClick={take} className={cn(MOVING_HIGHLIGHT, className)}>\n {children}\n {trailing !== undefined && <span className=\"ms-auto shrink-0\">{trailing}</span>}\n </NavItem>\n )\n}\n"],"mappings":"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","names":[]}
package/dist/semantic.css CHANGED
@@ -186,6 +186,8 @@
186
186
  ceiling, not a width: a narrower column still wins. */
187
187
  --measure-record: 66ch;
188
188
  /* How far below the viewport top an anchored heading comes to rest — a fixed
189
- masthead plus a line of air. */
190
- --scroll-offset: 88px;
189
+ masthead plus a line of air. Both halves are now named: the masthead is
190
+ `--bar-h`, so a bar that moves takes its anchors with it, where `88px` was
191
+ a number somebody derived once by hand and nothing kept in step. */
192
+ --scroll-offset: calc(var(--bar-h) + 1.5rem);
191
193
  }