@misoto22/design 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) 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/components/AppShell/AppShell.js +2 -2
  101. package/dist/components/AppShell/AppShell.js.map +1 -1
  102. package/dist/components/Button/Button.js +3 -3
  103. package/dist/components/Button/Button.js.map +1 -1
  104. package/dist/components/Kbd/Kbd.d.ts +7 -0
  105. package/dist/components/Kbd/Kbd.js +1 -1
  106. package/dist/components/Kbd/Kbd.js.map +1 -1
  107. package/dist/components/Sidebar/Sidebar.d.ts +8 -0
  108. package/dist/components/Sidebar/Sidebar.js +2 -2
  109. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  110. package/dist/semantic.css +4 -2
  111. package/dist/styles.css +1 -1
  112. package/dist/themes.css +91 -0
  113. package/dist/tokens.css +67 -1
  114. package/dist/tokens.d.ts +2 -0
  115. package/dist/tokens.js +14 -3
  116. package/dist/tokens.json +14 -3
  117. package/package.json +1 -1
@@ -4,7 +4,7 @@ A bar that fills, or sweeps when the end is unknown.
4
4
 
5
5
  - Group: Feedback
6
6
  - Import: `import { Progress } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/progress/
9
9
  - Related: spinner
10
10
 
@@ -4,7 +4,7 @@ A profile across several named dimensions.
4
4
 
5
5
  - Group: Charts
6
6
  - Import: `import { RadarChart } from '@misoto22/design/charts'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/radar-chart/
9
9
  - Related: line-chart, pie-chart
10
10
 
@@ -4,7 +4,7 @@ Values on an arc — a gauge, or a few totals against one scale.
4
4
 
5
5
  - Group: Charts
6
6
  - Import: `import { RadialChart } from '@misoto22/design/charts'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/radial-chart/
9
9
  - Related: pie-chart, bar-chart
10
10
 
@@ -4,7 +4,7 @@ A set of mutually exclusive choices.
4
4
 
5
5
  - Group: Forms
6
6
  - Import: `import { RadioGroup } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/radio-group/
9
9
  - Related: checkbox, select
10
10
 
@@ -4,7 +4,7 @@ Where a quantity goes as it moves through stages.
4
4
 
5
5
  - Group: Charts
6
6
  - Import: `import { SankeyChart } from '@misoto22/design/charts'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/sankey-chart/
9
9
  - Related: bar-chart
10
10
 
@@ -4,7 +4,7 @@ Two measures against each other, one mark per observation.
4
4
 
5
5
  - Group: Charts
6
6
  - Import: `import { ScatterChart } from '@misoto22/design/charts'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/scatter-chart/
9
9
  - Related: line-chart, heatmap
10
10
 
@@ -4,7 +4,7 @@ A box that scrolls, with a scrollbar that looks the same everywhere.
4
4
 
5
5
  - Group: Surfaces
6
6
  - Import: `import { ScrollArea } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/scroll-area/
9
9
  - Related: table
10
10
 
@@ -4,7 +4,7 @@ A menu of actions you can type into.
4
4
 
5
5
  - Group: Overlays
6
6
  - Import: `import { SearchableMenu } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/searchable-menu/
9
9
  - Related: dropdown-menu, command, combobox
10
10
 
@@ -4,7 +4,7 @@ A choice from a list, styled the whole way down.
4
4
 
5
5
  - Group: Forms
6
6
  - Import: `import { Select } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/select/
9
9
  - Related: combobox, native-select, field
10
10
 
@@ -4,7 +4,7 @@ A rule, in the three weights a monochrome page needs — with words in it when t
4
4
 
5
5
  - Group: Display
6
6
  - Import: `import { Separator } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/separator/
9
9
 
10
10
  ## When to reach for it
@@ -4,7 +4,7 @@ A call chain over time: who asks whom, in what order, and what comes back.
4
4
 
5
5
  - Group: Diagrams
6
6
  - Import: `import { SequenceFigure } from '@misoto22/design/diagrams'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/sequence-figure/
9
9
  - Related: workflow-figure, architecture-figure
10
10
 
@@ -4,7 +4,7 @@ A panel docked to an edge of the viewport.
4
4
 
5
5
  - Group: Overlays
6
6
  - Import: `import { Sheet } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/sheet/
9
9
  - Related: dialog, popover
10
10
 
@@ -4,7 +4,7 @@ A navigation rail down the side of an application.
4
4
 
5
5
  - Group: Navigation
6
6
  - Import: `import { Sidebar } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/sidebar/
9
9
  - Related: nav-item, collapsible, app-shell
10
10
 
@@ -102,7 +102,7 @@ The page beside the rail. Every layout built on this component was writing the s
102
102
 
103
103
  ## SidebarHeader
104
104
 
105
- The block at the top of the rail: a brand, a workspace, a switcher. It is also where `SidebarTrigger` belongs, and the layout assumes one: anything passed as `children` takes the space and the trigger sits at the inline end of the row.
105
+ The block at the top of the rail: a brand, a workspace, a switcher. It is also where `SidebarTrigger` belongs, and the layout assumes one: anything passed as `children` takes the space and the trigger sits at the inline end of the row. `--bar-h` is the floor, not a number of its own. This block and the masthead beside it meet at a corner, and their two bottom rules are read as one line — so a floor typed here is a floor that has to be retyped there, and the two were 56px and 64px for exactly that reason. The token also survives a rail that pins its own density: `--bar-h` is declared on the root, so its `var(--control-h-md)` resolves there and inherits as one answer, which is what keeps a compact rail's head level with a comfortable page's bar.
106
106
 
107
107
  ## SidebarContent
108
108
 
@@ -4,7 +4,7 @@ The shape of the page, before the page.
4
4
 
5
5
  - Group: Feedback
6
6
  - Import: `import { Skeleton } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/skeleton/
9
9
  - Related: spinner, empty-state
10
10
 
@@ -4,7 +4,7 @@ A value chosen along a range.
4
4
 
5
5
  - Group: Forms
6
6
  - Import: `import { Slider } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/slider/
9
9
  - Related: progress
10
10
 
@@ -4,7 +4,7 @@ A run of numbers at the size of a word.
4
4
 
5
5
  - Group: Data
6
6
  - Import: `import { Sparkline } from '@misoto22/design/charts'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/sparkline/
9
9
  - Related: line-chart, table
10
10
 
@@ -4,7 +4,7 @@ The one “working” indicator — a ring, never a shimmer.
4
4
 
5
5
  - Group: Feedback
6
6
  - Import: `import { Spinner } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/spinner/
9
9
  - Related: skeleton, progress
10
10
 
@@ -4,7 +4,7 @@ The dot beside a status word.
4
4
 
5
5
  - Group: Display
6
6
  - Import: `import { StatusDot } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/status-dot/
9
9
  - Related: status-pill
10
10
 
@@ -4,7 +4,7 @@ A live state, named: a dot plus an uppercase mono label.
4
4
 
5
5
  - Group: Display
6
6
  - Import: `import { StatusPill } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/status-pill/
9
9
  - Related: status-dot, badge
10
10
 
@@ -4,7 +4,7 @@ A numbered sequence, as a rail — one thing after another, with a rule through
4
4
 
5
5
  - Group: Display
6
6
  - Import: `import { Steps } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/steps/
9
9
  - Related: diagram, article
10
10
 
@@ -4,7 +4,7 @@ A setting that takes effect immediately.
4
4
 
5
5
  - Group: Forms
6
6
  - Import: `import { Switch } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/switch/
9
9
  - Related: checkbox
10
10
 
@@ -4,7 +4,7 @@ A ruled data table — alignment, sorting and rules all per column.
4
4
 
5
5
  - Group: Data
6
6
  - Import: `import { Table } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/table/
9
9
  - Related: card, figure-band
10
10
 
@@ -4,7 +4,7 @@ One strip, several panels.
4
4
 
5
5
  - Group: Navigation
6
6
  - Import: `import { Tabs } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/tabs/
9
9
  - Related: accordion
10
10
 
package/dist/agent/Tag.md CHANGED
@@ -4,7 +4,7 @@ A subject label — a topic, a technology, a filter facet — that filters with
4
4
 
5
5
  - Group: Display
6
6
  - Import: `import { Tag } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/tag/
9
9
  - Related: badge, status-pill
10
10
 
@@ -4,7 +4,7 @@ The system’s paragraph, on the second rung of the ink ladder.
4
4
 
5
5
  - Group: Display
6
6
  - Import: `import { Text } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/text/
9
9
  - Related: heading, article, markdown
10
10
 
@@ -4,7 +4,7 @@ Multi-line text entry, resizable vertically only.
4
4
 
5
5
  - Group: Forms
6
6
  - Import: `import { Textarea } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/textarea/
9
9
  - Related: input, field
10
10
 
@@ -4,7 +4,7 @@ A date or a time, rendered the one way the system renders them.
4
4
 
5
5
  - Group: Display
6
6
  - Import: `import { Timestamp } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/timestamp/
9
9
  - Related: text, badge, description-list
10
10
 
@@ -4,7 +4,7 @@ A transient confirmation, mounted once near the app root.
4
4
 
5
5
  - Group: Feedback
6
6
  - Import: `import { Toast } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/toast/
9
9
  - Related: alert
10
10
 
@@ -4,7 +4,7 @@ A segmented control: several options, one strip.
4
4
 
5
5
  - Group: Forms
6
6
  - Import: `import { ToggleGroup } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/toggle-group/
9
9
  - Related: tabs, radio-group
10
10
 
@@ -4,7 +4,7 @@ The bar of actions at the edge of a working surface.
4
4
 
5
5
  - Group: Surfaces
6
6
  - Import: `import { Toolbar } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/toolbar/
9
9
  - Related: button, app-shell, card
10
10
 
@@ -4,7 +4,7 @@ A short label on hover and on focus.
4
4
 
5
5
  - Group: Overlays
6
6
  - Import: `import { Tooltip } from '@misoto22/design'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/tooltip/
9
9
  - Related: dialog
10
10
 
@@ -4,7 +4,7 @@ Part of a whole, when the whole has too many parts for a pie — and the parts n
4
4
 
5
5
  - Group: Charts
6
6
  - Import: `import { TreemapChart } from '@misoto22/design/charts'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/treemap-chart/
9
9
  - Related: pie-chart, bar-chart
10
10
 
@@ -4,7 +4,7 @@ How a total got from one figure to another.
4
4
 
5
5
  - Group: Charts
6
6
  - Import: `import { WaterfallChart } from '@misoto22/design/charts'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/waterfall-chart/
9
9
  - Related: bar-chart, funnel-chart
10
10
 
@@ -4,7 +4,7 @@ A process in lanes and phases, with the main path drawn heavier than its branche
4
4
 
5
5
  - Group: Diagrams
6
6
  - Import: `import { WorkflowFigure } from '@misoto22/design/diagrams'`
7
- - Version: 0.9.0
7
+ - Version: 0.10.0
8
8
  - Docs: https://ui.misoto22.com/components/workflow-figure/
9
9
  - Related: lifecycle-figure, sequence-figure, steps
10
10
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.9.0",
2
+ "version": "0.10.0",
3
3
  "groups": [
4
4
  "Actions",
5
5
  "Forms",
@@ -7205,6 +7205,7 @@
7205
7205
  {
7206
7206
  "axis": "data-chart-palette",
7207
7207
  "values": [
7208
+ "accent",
7208
7209
  "chroma"
7209
7210
  ],
7210
7211
  "unset": "the neutral series ramp"
@@ -1,4 +1,4 @@
1
- # @misoto22/design 0.9.0
1
+ # @misoto22/design 0.10.0
2
2
 
3
3
  > Monochrome design system: portable CSS tokens and accessible React 19
4
4
  > primitives. Components ship compiled — import them, do not copy them.
@@ -16,7 +16,7 @@ npx misoto22-design docs <Component>
16
16
  Attributes, independent, and they work on any element — not just the root.
17
17
  An unset axis is the default.
18
18
 
19
- - `data-chart-palette`: `chroma` (unset = the neutral series ramp)
19
+ - `data-chart-palette`: `accent` `chroma` (unset = the neutral series ramp)
20
20
  - `data-density`: `comfortable` `compact` (unset = comfortable)
21
21
  - `data-mode`: `dark` `light` (unset = follows the app)
22
22
  - `data-motion`: `snappy` `still` (unset = calm)
@@ -85,6 +85,11 @@ function ChartContainer({
85
85
  // 1200px it is a 675px-tall plot with six points in it. The cap is
86
86
  // what stops the aspect ratio running away with a wide container,
87
87
  // and `min-h` keeps it from collapsing in a narrow one.
88
+ //
89
+ // With a footer the shape moves DOWN one element rather than being
90
+ // dropped — see the container below. Sizing this box 16:9 would hand
91
+ // the footer's height to the footer out of the plot's share, which is
92
+ // a plot that shrinks as its brush grows.
88
93
  !footer && "aspect-video max-h-[26rem] min-h-[13rem]",
89
94
  // Recharts' own defaults, re-pointed at the token layer.
90
95
  "[&_.recharts-cartesian-axis-tick_text]:fill-(--chart-axis)",
@@ -104,7 +109,17 @@ function ChartContainer({
104
109
  ...rest,
105
110
  children: [
106
111
  /* @__PURE__ */ jsx(ChartStyle, { id: chartId, config }),
107
- /* @__PURE__ */ jsx(ResponsiveContainer, { className: "min-h-0 w-full flex-1", initialDimension, children }),
112
+ /* @__PURE__ */ jsx(
113
+ ResponsiveContainer,
114
+ {
115
+ className: cn(
116
+ "w-full flex-1",
117
+ footer ? "aspect-video max-h-[26rem] min-h-[13rem]" : "min-h-0"
118
+ ),
119
+ initialDimension,
120
+ children
121
+ }
122
+ ),
108
123
  footer
109
124
  ]
110
125
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/charts/lib/chart.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n useContext,\n useId,\n useMemo,\n type ComponentProps,\n type ComponentType,\n type ReactNode,\n} from 'react'\nimport { ResponsiveContainer } from 'recharts'\nimport { cn } from '../../lib/cn'\n\n/**\n * How many series the system will paint before it refuses.\n *\n * Eight is not a rendering limit — it is the width of the validated ramp in\n * `tokens.css`. A ninth series has no slot, and generating one would mean\n * inventing a colour nobody checked against the eighth. The catalogued answer\n * to \"I have twelve categories\" is to fold the tail into an \"Other\" series or\n * to facet, and this constant is where that decision is enforced rather than\n * left to whoever is looking at the chart.\n */\nexport const SERIES_SLOTS = 8\n\n/** Theme name → the selector its block is written under. */\nconst THEMES = { light: '', dark: \"[data-mode='dark']\" } as const\n\ntype ThemeKey = keyof typeof THEMES\n\nconst THEME_KEYS = Object.keys(THEMES) as ThemeKey[]\n\n/** Colours for one series, per theme. At least one theme must be given. */\ntype ThemeColors = {\n [K in ThemeKey]: Required<Pick<Record<ThemeKey, string[]>, K>> &\n Partial<Omit<Record<ThemeKey, string[]>, K>>\n}[ThemeKey]\n\nexport interface ChartSeries {\n /**\n * What the legend, the tooltip and the hidden data table call this series.\n *\n * Required, unlike the shape this was ported from. Colour is never the only\n * carrier of identity in this system, and a legend entry with no label is a\n * swatch that means nothing — least of all in the monochrome default, where\n * two series differ by a step of grey and a texture.\n */\n label: ReactNode\n /** Drawn in place of the legend/tooltip swatch. Decorative; label still wins. */\n icon?: ComponentType\n /**\n * Explicit colours for this series, overriding its slot on the series ramp.\n *\n * Reach for this only when the data itself carries a colour the reader\n * already knows — a brand, a traffic light, a map key. For \"I want my chart\n * to have colours\", set `data-chart-palette=\"chroma\"` on any ancestor: that\n * swaps the whole ramp for a palette validated against both grounds, which a\n * hand-picked array is not.\n *\n * More than one colour paints the series as a horizontal gradient across\n * those stops. Values are plain CSS colours; anything that could break out of\n * a declaration is rejected at the boundary rather than injected.\n */\n colors?: ThemeColors\n}\n\n/** Series keys → how each is labelled and painted. Declaration order is slot order. */\nexport type ChartConfig = Record<string, ChartSeries>\n\ninterface ChartContextValue {\n config: ChartConfig\n}\n\nconst ChartContext = createContext<ChartContextValue | null>(null)\n\n/** The chart config of the nearest chart root. Throws outside one. */\nexport function useChart(): ChartContextValue {\n const context = useContext(ChartContext)\n if (!context) throw new Error('A chart part must be rendered inside a chart root')\n return context\n}\n\n/**\n * A CSS colour, or something that would escape the declaration it is written\n * into.\n *\n * `ChartStyle` writes consumer-supplied values into a `<style>` element, which\n * makes this a system boundary in the security sense: without the check, a\n * `colors` array carrying `red; } html { display: none } .x {` is not a colour,\n * it is a stylesheet. The allowed set is deliberately shaped around what a\n * colour is made of — identifiers, digits, `#`, brackets, commas, percent — so\n * `var(--x)`, `oklch(…)` and `color-mix(in srgb, …)` all pass and no `;`, `{`,\n * `}`, `<`, `\\` or comment sequence does.\n */\nconst CSS_COLOR = /^[A-Za-z0-9#(),.%\\-\\s_]{1,120}$/\n\nfunction isSafeColor(value: unknown): value is string {\n return typeof value === 'string' && CSS_COLOR.test(value) && !value.includes('/*')\n}\n\n/**\n * Distributes fewer colours than slots, giving the surplus to the last stops.\n * Two colours over four slots read as `[a, a, b, b]`; three over four as\n * `[a, b, c, c]`.\n */\nfunction distribute(colors: string[], slots: number): string[] {\n if (colors.length >= slots) return colors.slice(0, slots)\n\n const base = Math.floor(slots / colors.length)\n const extra = slots % colors.length\n const out: string[] = []\n colors.forEach((color, index) => {\n const count = base + (index >= colors.length - extra ? 1 : 0)\n for (let n = 0; n < count; n += 1) out.push(color)\n })\n return out\n}\n\n/** How many gradient stops a series paints with — one means a flat fill. */\nexport function colorStops(series: ChartSeries | undefined): number {\n if (!series?.colors) return 1\n return Math.max(1, ...THEME_KEYS.map((theme) => series.colors?.[theme]?.length ?? 0))\n}\n\n/**\n * The custom properties one chart instance paints from.\n *\n * Every series resolves to `--color-<key>-<n>`, and every mark, swatch and\n * gradient stop in the package reads only those names. That indirection is what\n * lets the same chart be repainted by an ancestor attribute — a theme, a\n * palette, dark mode — without a component knowing anything about colour.\n *\n * A series with no `colors` takes its slot on the ramp, in declaration order.\n * That is the \"assign in fixed order, never cycle\" rule expressed as code: the\n * fourth series is `--series-4` whether or not the first three are on screen,\n * so hiding a series never repaints the survivors.\n */\nfunction ChartStyle({ id, config }: { id: string; config: ChartConfig }) {\n const css = useMemo(() => {\n const entries = Object.entries(config)\n\n const rules = (theme: ThemeKey) =>\n entries\n .flatMap(([key, series], slot) => {\n const stops = colorStops(series)\n const slotColor = `var(--series-${(slot % SERIES_SLOTS) + 1})`\n const given = series.colors?.[theme] ?? series.colors?.light ?? series.colors?.dark\n const safe = given?.filter(isSafeColor) ?? []\n // A rejected colour falls back to the ramp rather than to nothing: an\n // unpainted series is invisible, which is a worse failure than an\n // ignored value and a harder one to notice.\n const colors = safe.length > 0 ? safe : [slotColor]\n return distribute(colors, stops).map(\n (color, index) => ` --color-${cssName(key)}-${index}: ${color};`,\n )\n })\n .join('\\n')\n\n return THEME_KEYS.map((theme) => {\n const body = rules(theme)\n if (!body) return ''\n return `${THEMES[theme]} [data-chart='${id}'] {\\n${body}\\n}`\n })\n .filter(Boolean)\n .join('\\n')\n }, [id, config])\n\n if (!css) return null\n\n return <style>{css}</style>\n}\n\n/**\n * A series key, reduced to what may appear in a custom property name. Keys come\n * from consumer data, so the same boundary argument as `isSafeColor` applies —\n * here it is the property NAME rather than its value.\n */\nexport function cssName(key: string): string {\n return key.replace(/[^A-Za-z0-9_-]/g, '-')\n}\n\nexport interface ChartContainerProps\n extends Omit<ComponentProps<'div'>, 'children'>,\n Pick<\n ComponentProps<typeof ResponsiveContainer>,\n 'initialDimension' | 'minHeight' | 'minWidth' | 'maxHeight' | 'height' | 'width' | 'children'\n > {\n config: ChartConfig\n /** Rendered under the plot, inside the same measured box — the brush lives here. */\n footer?: ReactNode\n}\n\n/**\n * The measured box every chart is drawn into, and the only place Recharts'\n * hard-coded greys are re-pointed at system tokens.\n *\n * Recharts paints its axes, grid, cursor and radial track with literal `#ccc`\n * and `#fff`. The selectors below catch each of those and hand it a token, so a\n * chart follows light/dark and a surface theme without a consumer configuring\n * anything — and so nothing in the package has to pass a colour down by prop.\n */\nexport function ChartContainer({\n id,\n config,\n initialDimension = { width: 320, height: 200 },\n className,\n children,\n footer,\n ...rest\n}: ChartContainerProps) {\n const generated = useId().replace(/:/g, '')\n const chartId = `chart-${id ?? generated}`\n\n return (\n <ChartContext.Provider value={{ config }}>\n <div\n data-slot=\"chart\"\n data-chart={chartId}\n className={cn(\n 'relative flex min-h-0 w-full flex-1 flex-col justify-center text-xs',\n // 16:9 is right for a card and wrong for a full-width panel: at\n // 1200px it is a 675px-tall plot with six points in it. The cap is\n // what stops the aspect ratio running away with a wide container,\n // and `min-h` keeps it from collapsing in a narrow one.\n !footer && 'aspect-video max-h-[26rem] min-h-[13rem]',\n // Recharts' own defaults, re-pointed at the token layer.\n \"[&_.recharts-cartesian-axis-tick_text]:fill-(--chart-axis)\",\n \"[&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-(--chart-grid)\",\n '[&_.recharts-curve.recharts-tooltip-cursor]:stroke-(--chart-cursor)',\n \"[&_.recharts-polar-grid_[stroke='#ccc']]:stroke-(--chart-grid)\",\n '[&_.recharts-radial-bar-background-sector]:fill-(--chart-track)',\n '[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-(--chart-track)',\n \"[&_.recharts-reference-line_[stroke='#ccc']]:stroke-(--chart-grid)\",\n \"[&_.recharts-dot[stroke='#fff']]:stroke-transparent\",\n \"[&_.recharts-sector[stroke='#fff']]:stroke-transparent\",\n // Recharts draws its own focus ring; the system draws one focus ring,\n // in one place, and this is how the duplicate is removed.\n '[&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden',\n className,\n )}\n {...rest}\n >\n <ChartStyle id={chartId} config={config} />\n <ResponsiveContainer className=\"min-h-0 w-full flex-1\" initialDimension={initialDimension}>\n {children}\n </ResponsiveContainer>\n {footer}\n </div>\n </ChartContext.Provider>\n )\n}\n\n/**\n * Reads the config entry behind a Recharts payload item.\n *\n * Recharts hands the same series different names in different chart types — a\n * pie sector's key is on `payload.payload[nameKey]`, a bar's is `dataKey` — so\n * this is the one place that disagreement is resolved.\n */\nexport function seriesFromPayload(\n config: ChartConfig,\n payload: unknown,\n key: string,\n): ChartSeries | undefined {\n if (typeof payload !== 'object' || payload === null) return undefined\n\n const nested =\n 'payload' in payload && typeof payload.payload === 'object' && payload.payload !== null\n ? (payload.payload as Record<string, unknown>)\n : undefined\n\n let name = key\n const direct = (payload as Record<string, unknown>)[key]\n if (typeof direct === 'string') name = direct\n else if (nested && typeof nested[key] === 'string') name = nested[key] as string\n\n return name in config ? config[name] : config[key]\n}\n\n/** Formats an expanded stack's axis, where 0–1 means 0–100%. */\nexport function percentTick(value: number): string {\n return `${Math.round(value * 100)}%`\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/charts/lib/chart.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n useContext,\n useId,\n useMemo,\n type ComponentProps,\n type ComponentType,\n type ReactNode,\n} from 'react'\nimport { ResponsiveContainer } from 'recharts'\nimport { cn } from '../../lib/cn'\n\n/**\n * How many series the system will paint before it refuses.\n *\n * Eight is not a rendering limit — it is the width of the validated ramp in\n * `tokens.css`. A ninth series has no slot, and generating one would mean\n * inventing a colour nobody checked against the eighth. The catalogued answer\n * to \"I have twelve categories\" is to fold the tail into an \"Other\" series or\n * to facet, and this constant is where that decision is enforced rather than\n * left to whoever is looking at the chart.\n */\nexport const SERIES_SLOTS = 8\n\n/** Theme name → the selector its block is written under. */\nconst THEMES = { light: '', dark: \"[data-mode='dark']\" } as const\n\ntype ThemeKey = keyof typeof THEMES\n\nconst THEME_KEYS = Object.keys(THEMES) as ThemeKey[]\n\n/** Colours for one series, per theme. At least one theme must be given. */\ntype ThemeColors = {\n [K in ThemeKey]: Required<Pick<Record<ThemeKey, string[]>, K>> &\n Partial<Omit<Record<ThemeKey, string[]>, K>>\n}[ThemeKey]\n\nexport interface ChartSeries {\n /**\n * What the legend, the tooltip and the hidden data table call this series.\n *\n * Required, unlike the shape this was ported from. Colour is never the only\n * carrier of identity in this system, and a legend entry with no label is a\n * swatch that means nothing — least of all in the monochrome default, where\n * two series differ by a step of grey and a texture.\n */\n label: ReactNode\n /** Drawn in place of the legend/tooltip swatch. Decorative; label still wins. */\n icon?: ComponentType\n /**\n * Explicit colours for this series, overriding its slot on the series ramp.\n *\n * Reach for this only when the data itself carries a colour the reader\n * already knows — a brand, a traffic light, a map key. For \"I want my chart\n * to have colours\", set `data-chart-palette=\"chroma\"` on any ancestor: that\n * swaps the whole ramp for a palette validated against both grounds, which a\n * hand-picked array is not.\n *\n * More than one colour paints the series as a horizontal gradient across\n * those stops. Values are plain CSS colours; anything that could break out of\n * a declaration is rejected at the boundary rather than injected.\n */\n colors?: ThemeColors\n}\n\n/** Series keys → how each is labelled and painted. Declaration order is slot order. */\nexport type ChartConfig = Record<string, ChartSeries>\n\ninterface ChartContextValue {\n config: ChartConfig\n}\n\nconst ChartContext = createContext<ChartContextValue | null>(null)\n\n/** The chart config of the nearest chart root. Throws outside one. */\nexport function useChart(): ChartContextValue {\n const context = useContext(ChartContext)\n if (!context) throw new Error('A chart part must be rendered inside a chart root')\n return context\n}\n\n/**\n * A CSS colour, or something that would escape the declaration it is written\n * into.\n *\n * `ChartStyle` writes consumer-supplied values into a `<style>` element, which\n * makes this a system boundary in the security sense: without the check, a\n * `colors` array carrying `red; } html { display: none } .x {` is not a colour,\n * it is a stylesheet. The allowed set is deliberately shaped around what a\n * colour is made of — identifiers, digits, `#`, brackets, commas, percent — so\n * `var(--x)`, `oklch(…)` and `color-mix(in srgb, …)` all pass and no `;`, `{`,\n * `}`, `<`, `\\` or comment sequence does.\n */\nconst CSS_COLOR = /^[A-Za-z0-9#(),.%\\-\\s_]{1,120}$/\n\nfunction isSafeColor(value: unknown): value is string {\n return typeof value === 'string' && CSS_COLOR.test(value) && !value.includes('/*')\n}\n\n/**\n * Distributes fewer colours than slots, giving the surplus to the last stops.\n * Two colours over four slots read as `[a, a, b, b]`; three over four as\n * `[a, b, c, c]`.\n */\nfunction distribute(colors: string[], slots: number): string[] {\n if (colors.length >= slots) return colors.slice(0, slots)\n\n const base = Math.floor(slots / colors.length)\n const extra = slots % colors.length\n const out: string[] = []\n colors.forEach((color, index) => {\n const count = base + (index >= colors.length - extra ? 1 : 0)\n for (let n = 0; n < count; n += 1) out.push(color)\n })\n return out\n}\n\n/** How many gradient stops a series paints with — one means a flat fill. */\nexport function colorStops(series: ChartSeries | undefined): number {\n if (!series?.colors) return 1\n return Math.max(1, ...THEME_KEYS.map((theme) => series.colors?.[theme]?.length ?? 0))\n}\n\n/**\n * The custom properties one chart instance paints from.\n *\n * Every series resolves to `--color-<key>-<n>`, and every mark, swatch and\n * gradient stop in the package reads only those names. That indirection is what\n * lets the same chart be repainted by an ancestor attribute — a theme, a\n * palette, dark mode — without a component knowing anything about colour.\n *\n * A series with no `colors` takes its slot on the ramp, in declaration order.\n * That is the \"assign in fixed order, never cycle\" rule expressed as code: the\n * fourth series is `--series-4` whether or not the first three are on screen,\n * so hiding a series never repaints the survivors.\n */\nfunction ChartStyle({ id, config }: { id: string; config: ChartConfig }) {\n const css = useMemo(() => {\n const entries = Object.entries(config)\n\n const rules = (theme: ThemeKey) =>\n entries\n .flatMap(([key, series], slot) => {\n const stops = colorStops(series)\n const slotColor = `var(--series-${(slot % SERIES_SLOTS) + 1})`\n const given = series.colors?.[theme] ?? series.colors?.light ?? series.colors?.dark\n const safe = given?.filter(isSafeColor) ?? []\n // A rejected colour falls back to the ramp rather than to nothing: an\n // unpainted series is invisible, which is a worse failure than an\n // ignored value and a harder one to notice.\n const colors = safe.length > 0 ? safe : [slotColor]\n return distribute(colors, stops).map(\n (color, index) => ` --color-${cssName(key)}-${index}: ${color};`,\n )\n })\n .join('\\n')\n\n return THEME_KEYS.map((theme) => {\n const body = rules(theme)\n if (!body) return ''\n return `${THEMES[theme]} [data-chart='${id}'] {\\n${body}\\n}`\n })\n .filter(Boolean)\n .join('\\n')\n }, [id, config])\n\n if (!css) return null\n\n return <style>{css}</style>\n}\n\n/**\n * A series key, reduced to what may appear in a custom property name. Keys come\n * from consumer data, so the same boundary argument as `isSafeColor` applies —\n * here it is the property NAME rather than its value.\n */\nexport function cssName(key: string): string {\n return key.replace(/[^A-Za-z0-9_-]/g, '-')\n}\n\nexport interface ChartContainerProps\n extends Omit<ComponentProps<'div'>, 'children'>,\n Pick<\n ComponentProps<typeof ResponsiveContainer>,\n 'initialDimension' | 'minHeight' | 'minWidth' | 'maxHeight' | 'height' | 'width' | 'children'\n > {\n config: ChartConfig\n /** Rendered under the plot, inside the same measured box — the brush lives here. */\n footer?: ReactNode\n}\n\n/**\n * The measured box every chart is drawn into, and the only place Recharts'\n * hard-coded greys are re-pointed at system tokens.\n *\n * Recharts paints its axes, grid, cursor and radial track with literal `#ccc`\n * and `#fff`. The selectors below catch each of those and hand it a token, so a\n * chart follows light/dark and a surface theme without a consumer configuring\n * anything — and so nothing in the package has to pass a colour down by prop.\n */\nexport function ChartContainer({\n id,\n config,\n initialDimension = { width: 320, height: 200 },\n className,\n children,\n footer,\n ...rest\n}: ChartContainerProps) {\n const generated = useId().replace(/:/g, '')\n const chartId = `chart-${id ?? generated}`\n\n return (\n <ChartContext.Provider value={{ config }}>\n <div\n data-slot=\"chart\"\n data-chart={chartId}\n className={cn(\n 'relative flex min-h-0 w-full flex-1 flex-col justify-center text-xs',\n // 16:9 is right for a card and wrong for a full-width panel: at\n // 1200px it is a 675px-tall plot with six points in it. The cap is\n // what stops the aspect ratio running away with a wide container,\n // and `min-h` keeps it from collapsing in a narrow one.\n //\n // With a footer the shape moves DOWN one element rather than being\n // dropped — see the container below. Sizing this box 16:9 would hand\n // the footer's height to the footer out of the plot's share, which is\n // a plot that shrinks as its brush grows.\n !footer && 'aspect-video max-h-[26rem] min-h-[13rem]',\n // Recharts' own defaults, re-pointed at the token layer.\n \"[&_.recharts-cartesian-axis-tick_text]:fill-(--chart-axis)\",\n \"[&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-(--chart-grid)\",\n '[&_.recharts-curve.recharts-tooltip-cursor]:stroke-(--chart-cursor)',\n \"[&_.recharts-polar-grid_[stroke='#ccc']]:stroke-(--chart-grid)\",\n '[&_.recharts-radial-bar-background-sector]:fill-(--chart-track)',\n '[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-(--chart-track)',\n \"[&_.recharts-reference-line_[stroke='#ccc']]:stroke-(--chart-grid)\",\n \"[&_.recharts-dot[stroke='#fff']]:stroke-transparent\",\n \"[&_.recharts-sector[stroke='#fff']]:stroke-transparent\",\n // Recharts draws its own focus ring; the system draws one focus ring,\n // in one place, and this is how the duplicate is removed.\n '[&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden',\n className,\n )}\n {...rest}\n >\n <ChartStyle id={chartId} config={config} />\n {/* The plot carries the shape when there is a footer under it.\n It used to carry nothing: the outer box dropped `aspect-video` AND\n `min-h` the moment a footer existed, on the reasoning that a fixed\n aspect plus a footer overflows — which is true of the aspect and\n not of the floor. So a chart with a brush had a plot with no\n intrinsic height at all, and in any container that sizes to its\n content the plot measured zero and only the brush's own 56px\n survived. Recharts says so out loud — \"width(-1) and height(-1) of\n chart should be greater than 0\" — into a console nobody reads. */}\n <ResponsiveContainer\n className={cn(\n 'w-full flex-1',\n footer ? 'aspect-video max-h-[26rem] min-h-[13rem]' : 'min-h-0',\n )}\n initialDimension={initialDimension}\n >\n {children}\n </ResponsiveContainer>\n {footer}\n </div>\n </ChartContext.Provider>\n )\n}\n\n/**\n * Reads the config entry behind a Recharts payload item.\n *\n * Recharts hands the same series different names in different chart types — a\n * pie sector's key is on `payload.payload[nameKey]`, a bar's is `dataKey` — so\n * this is the one place that disagreement is resolved.\n */\nexport function seriesFromPayload(\n config: ChartConfig,\n payload: unknown,\n key: string,\n): ChartSeries | undefined {\n if (typeof payload !== 'object' || payload === null) return undefined\n\n const nested =\n 'payload' in payload && typeof payload.payload === 'object' && payload.payload !== null\n ? (payload.payload as Record<string, unknown>)\n : undefined\n\n let name = key\n const direct = (payload as Record<string, unknown>)[key]\n if (typeof direct === 'string') name = direct\n else if (nested && typeof nested[key] === 'string') name = nested[key] as string\n\n return name in config ? config[name] : config[key]\n}\n\n/** Formats an expanded stack's axis, where 0–1 means 0–100%. */\nexport function percentTick(value: number): string {\n return `${Math.round(value * 100)}%`\n}\n"],"mappings":"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","names":[]}
@@ -83,7 +83,7 @@ function AppShell({
83
83
  "md:static md:z-auto md:translate-x-0"
84
84
  ),
85
85
  children: [
86
- brand && /* @__PURE__ */ jsx("div", { className: "flex h-14 items-center border-b border-(--rule) px-5", children: brand }),
86
+ brand && /* @__PURE__ */ jsx("div", { className: "flex h-(--bar-h) items-center border-b border-(--rule) px-5", children: brand }),
87
87
  /* @__PURE__ */ jsx(
88
88
  "nav",
89
89
  {
@@ -96,7 +96,7 @@ function AppShell({
96
96
  }
97
97
  ),
98
98
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col", children: [
99
- /* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-(--z-sticky) flex h-14 items-center gap-3 border-b border-(--rule) bg-(--paper)/85 px-(--page-pad) backdrop-blur", children: [
99
+ /* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-(--z-sticky) flex h-(--bar-h) items-center gap-3 border-b border-(--rule) bg-(--paper)/85 px-(--page-pad) backdrop-blur", children: [
100
100
  /* @__PURE__ */ jsx(
101
101
  "button",
102
102
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/AppShell/AppShell.tsx"],"sourcesContent":["'use client'\n\nimport { Menu, X } from 'lucide-react'\nimport { useCallback, useEffect, useId, useRef, useState, useSyncExternalStore } from 'react'\nimport type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/**\n * The width the sidebar stops being a drawer at — `--breakpoint-md`, the same\n * 768px every `md:` variant below is compiled against. Written out rather than\n * read from the token, because a media query string is not a custom property\n * and `matchMedia` cannot resolve one.\n */\nconst DESKTOP = '(min-width: 768px)'\n\n/** True where nothing can answer the question: a server, or a DOM without it. */\nfunction cannotMeasure(): boolean {\n return typeof window === 'undefined' || typeof window.matchMedia !== 'function'\n}\n\nfunction subscribeToWidth(onChange: () => void): () => void {\n if (cannotMeasure()) return () => {}\n const list = window.matchMedia(DESKTOP)\n list.addEventListener('change', onChange)\n return () => list.removeEventListener('change', onChange)\n}\n\n/**\n * Falls back to \"not a drawer\", which is the safe answer rather than the likely\n * one: an environment that cannot measure must not take an entire application's\n * navigation out of the accessibility tree on a guess.\n */\nfunction readIsDrawer(): boolean {\n if (cannotMeasure()) return false\n return !window.matchMedia(DESKTOP).matches\n}\n\nfunction readIsDrawerOnServer(): boolean {\n return false\n}\n\n/**\n * True while the sidebar is a drawer rather than a column.\n *\n * `open` alone cannot answer this. It is false on a desktop too, where the\n * sidebar is a visible grid column — so anything keyed on `open` by itself\n * would hide the navigation of every desktop app in the package.\n *\n * `useSyncExternalStore` rather than an effect: the server has no viewport, so\n * it renders the desktop answer and React reconciles the real one on hydration\n * instead of the two silently disagreeing.\n */\nfunction useIsDrawer(): boolean {\n return useSyncExternalStore(subscribeToWidth, readIsDrawer, readIsDrawerOnServer)\n}\n\nexport interface AppShellProps extends HTMLAttributes<HTMLDivElement> {\n /** Navigation content for the sidebar (e.g. a stack of `NavItem`s). */\n sidebar: ReactNode\n /** Optional content for the sticky topbar, laid out after the mobile toggle. */\n topbar?: ReactNode\n /** Optional brand lockup pinned to the top of the sidebar. */\n brand?: ReactNode\n /**\n * Which element the content well renders as.\n *\n * `main` is right for the shell of an application, and is the default. A\n * document may contain exactly one `main` landmark, so an AppShell rendered\n * INSIDE another page — a preview on a documentation site, a screenshot\n * harness — must pass `div`, or the page has two and assistive tech can no\n * longer answer \"where is the content\".\n */\n contentAs?: 'main' | 'div'\n /**\n * Names the sidebar landmark.\n *\n * Two `complementary` landmarks with the same name cannot be told apart, and\n * a shell rendered inside another page — a preview, a screenshot harness —\n * makes exactly that pair. It is also the only way a non-English app gets a\n * landmark name its readers can read.\n */\n sidebarLabel?: string\n /** Names the navigation landmark inside the sidebar. */\n navLabel?: string\n /** The drawer toggle, closed and open. */\n openLabel?: string\n closeLabel?: string\n children: ReactNode\n}\n\n/**\n * Two columns on a desktop, one column and a drawer on a phone.\n *\n * The drawer closes on Escape as well as on the scrim, because a drawer that\n * only closes by tapping outside it strands a keyboard user inside a menu they\n * cannot leave. The scrim is a `<button>` for the same reason — a `<div>` with\n * an `onClick` is not reachable by keyboard and not announced as anything.\n *\n * Below `md` the closed drawer is `inert`. Sliding it off-screen is a visual\n * state and nothing more: without that attribute every link in it stays\n * focusable and stays in the accessibility tree, so Tab from the toggle walks\n * into a menu nobody can see. Closing it also returns focus to the toggle,\n * because the element focus was on is the element that just left.\n *\n * @example\n * <AppShell brand={<Wordmark />} sidebar={<NavItem href=\"/\">Home</NavItem>}>\n * <Card>…</Card>\n * </AppShell>\n */\nexport function AppShell({\n sidebar,\n topbar,\n brand,\n contentAs: Content = 'main',\n sidebarLabel = 'Sidebar',\n navLabel = 'Primary',\n openLabel = 'Open navigation',\n closeLabel = 'Close navigation',\n children,\n className,\n ...rest\n}: AppShellProps) {\n const [open, setOpen] = useState(false)\n const sidebarId = useId()\n const toggle = useRef<HTMLButtonElement>(null)\n const isDrawer = useIsDrawer()\n\n /**\n * Both ways out, so both return focus.\n *\n * Escape used to leave the reader standing on a link that had just slid off\n * screen; the scrim is worse, because the scrim itself is the focused element\n * and it unmounts, dropping focus to `<body>`. Either way the drawer is about\n * to become inert, and focus inside an inert subtree is focus the browser\n * throws away.\n */\n const close = useCallback(() => {\n setOpen(false)\n toggle.current?.focus()\n }, [])\n\n useEffect(() => {\n if (!open) return\n const onKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') close()\n }\n document.addEventListener('keydown', onKey)\n return () => document.removeEventListener('keydown', onKey)\n }, [open, close])\n\n return (\n <div\n className={cn('min-h-svh bg-(--paper) md:grid md:grid-cols-[15rem_1fr]', className)}\n {...rest}\n >\n {open && (\n <button\n type=\"button\"\n aria-label={closeLabel}\n onClick={close}\n className=\"fixed inset-0 z-(--z-scrim) bg-(--scrim) md:hidden\"\n />\n )}\n\n <aside\n id={sidebarId}\n aria-label={sidebarLabel}\n // Only below `md`, and only while it is shut. Above the breakpoint this\n // element is the page's navigation column, and inert would delete it.\n inert={isDrawer && !open}\n // The transition is motion like any other, and the reduced-motion rule\n // in keyframes.css matches on this attribute.\n data-m22-animated\n className={cn(\n 'fixed inset-y-0 start-0 z-(--z-modal) flex w-60 flex-col border-e border-(--rule) bg-(--paper) transition-transform duration-(--duration-slow) ease-(--ease-out-expo)',\n // The drawer slides in from the edge reading STARTS at, so in an\n // right-to-left document it comes from the right.\n open ? 'translate-x-0' : '-translate-x-full rtl:translate-x-full',\n 'md:static md:z-auto md:translate-x-0',\n )}\n >\n {brand && (\n <div className=\"flex h-14 items-center border-b border-(--rule) px-5\">{brand}</div>\n )}\n <nav\n aria-label={navLabel}\n className=\"flex flex-1 flex-col gap-1 overflow-y-auto p-3 scroll-slim\"\n >\n {sidebar}\n </nav>\n </aside>\n\n <div className=\"flex min-w-0 flex-col\">\n <header className=\"sticky top-0 z-(--z-sticky) flex h-14 items-center gap-3 border-b border-(--rule) bg-(--paper)/85 px-(--page-pad) backdrop-blur\">\n <button\n type=\"button\"\n aria-label={open ? closeLabel : openLabel}\n aria-expanded={open}\n aria-controls={sidebarId}\n ref={toggle}\n onClick={() => setOpen((previous) => !previous)}\n className=\"-ms-2.5 grid size-11 place-items-center rounded-(--radius) text-(--ink-2) transition-colors duration-(--duration-fast) hover:text-(--ink) md:hidden\"\n >\n {open ? <X size={20} strokeWidth={1.5} aria-hidden /> : <Menu size={20} strokeWidth={1.5} aria-hidden />}\n </button>\n {topbar}\n </header>\n\n <Content className=\"mx-auto w-full max-w-(--w-page) px-(--page-pad) py-8\">{children}</Content>\n </div>\n </div>\n )\n}\n\nexport default AppShell\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/AppShell/AppShell.tsx"],"sourcesContent":["'use client'\n\nimport { Menu, X } from 'lucide-react'\nimport { useCallback, useEffect, useId, useRef, useState, useSyncExternalStore } from 'react'\nimport type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/**\n * The width the sidebar stops being a drawer at — `--breakpoint-md`, the same\n * 768px every `md:` variant below is compiled against. Written out rather than\n * read from the token, because a media query string is not a custom property\n * and `matchMedia` cannot resolve one.\n */\nconst DESKTOP = '(min-width: 768px)'\n\n/** True where nothing can answer the question: a server, or a DOM without it. */\nfunction cannotMeasure(): boolean {\n return typeof window === 'undefined' || typeof window.matchMedia !== 'function'\n}\n\nfunction subscribeToWidth(onChange: () => void): () => void {\n if (cannotMeasure()) return () => {}\n const list = window.matchMedia(DESKTOP)\n list.addEventListener('change', onChange)\n return () => list.removeEventListener('change', onChange)\n}\n\n/**\n * Falls back to \"not a drawer\", which is the safe answer rather than the likely\n * one: an environment that cannot measure must not take an entire application's\n * navigation out of the accessibility tree on a guess.\n */\nfunction readIsDrawer(): boolean {\n if (cannotMeasure()) return false\n return !window.matchMedia(DESKTOP).matches\n}\n\nfunction readIsDrawerOnServer(): boolean {\n return false\n}\n\n/**\n * True while the sidebar is a drawer rather than a column.\n *\n * `open` alone cannot answer this. It is false on a desktop too, where the\n * sidebar is a visible grid column — so anything keyed on `open` by itself\n * would hide the navigation of every desktop app in the package.\n *\n * `useSyncExternalStore` rather than an effect: the server has no viewport, so\n * it renders the desktop answer and React reconciles the real one on hydration\n * instead of the two silently disagreeing.\n */\nfunction useIsDrawer(): boolean {\n return useSyncExternalStore(subscribeToWidth, readIsDrawer, readIsDrawerOnServer)\n}\n\nexport interface AppShellProps extends HTMLAttributes<HTMLDivElement> {\n /** Navigation content for the sidebar (e.g. a stack of `NavItem`s). */\n sidebar: ReactNode\n /** Optional content for the sticky topbar, laid out after the mobile toggle. */\n topbar?: ReactNode\n /** Optional brand lockup pinned to the top of the sidebar. */\n brand?: ReactNode\n /**\n * Which element the content well renders as.\n *\n * `main` is right for the shell of an application, and is the default. A\n * document may contain exactly one `main` landmark, so an AppShell rendered\n * INSIDE another page — a preview on a documentation site, a screenshot\n * harness — must pass `div`, or the page has two and assistive tech can no\n * longer answer \"where is the content\".\n */\n contentAs?: 'main' | 'div'\n /**\n * Names the sidebar landmark.\n *\n * Two `complementary` landmarks with the same name cannot be told apart, and\n * a shell rendered inside another page — a preview, a screenshot harness —\n * makes exactly that pair. It is also the only way a non-English app gets a\n * landmark name its readers can read.\n */\n sidebarLabel?: string\n /** Names the navigation landmark inside the sidebar. */\n navLabel?: string\n /** The drawer toggle, closed and open. */\n openLabel?: string\n closeLabel?: string\n children: ReactNode\n}\n\n/**\n * Two columns on a desktop, one column and a drawer on a phone.\n *\n * The drawer closes on Escape as well as on the scrim, because a drawer that\n * only closes by tapping outside it strands a keyboard user inside a menu they\n * cannot leave. The scrim is a `<button>` for the same reason — a `<div>` with\n * an `onClick` is not reachable by keyboard and not announced as anything.\n *\n * Below `md` the closed drawer is `inert`. Sliding it off-screen is a visual\n * state and nothing more: without that attribute every link in it stays\n * focusable and stays in the accessibility tree, so Tab from the toggle walks\n * into a menu nobody can see. Closing it also returns focus to the toggle,\n * because the element focus was on is the element that just left.\n *\n * @example\n * <AppShell brand={<Wordmark />} sidebar={<NavItem href=\"/\">Home</NavItem>}>\n * <Card>…</Card>\n * </AppShell>\n */\nexport function AppShell({\n sidebar,\n topbar,\n brand,\n contentAs: Content = 'main',\n sidebarLabel = 'Sidebar',\n navLabel = 'Primary',\n openLabel = 'Open navigation',\n closeLabel = 'Close navigation',\n children,\n className,\n ...rest\n}: AppShellProps) {\n const [open, setOpen] = useState(false)\n const sidebarId = useId()\n const toggle = useRef<HTMLButtonElement>(null)\n const isDrawer = useIsDrawer()\n\n /**\n * Both ways out, so both return focus.\n *\n * Escape used to leave the reader standing on a link that had just slid off\n * screen; the scrim is worse, because the scrim itself is the focused element\n * and it unmounts, dropping focus to `<body>`. Either way the drawer is about\n * to become inert, and focus inside an inert subtree is focus the browser\n * throws away.\n */\n const close = useCallback(() => {\n setOpen(false)\n toggle.current?.focus()\n }, [])\n\n useEffect(() => {\n if (!open) return\n const onKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') close()\n }\n document.addEventListener('keydown', onKey)\n return () => document.removeEventListener('keydown', onKey)\n }, [open, close])\n\n return (\n <div\n className={cn('min-h-svh bg-(--paper) md:grid md:grid-cols-[15rem_1fr]', className)}\n {...rest}\n >\n {open && (\n <button\n type=\"button\"\n aria-label={closeLabel}\n onClick={close}\n className=\"fixed inset-0 z-(--z-scrim) bg-(--scrim) md:hidden\"\n />\n )}\n\n <aside\n id={sidebarId}\n aria-label={sidebarLabel}\n // Only below `md`, and only while it is shut. Above the breakpoint this\n // element is the page's navigation column, and inert would delete it.\n inert={isDrawer && !open}\n // The transition is motion like any other, and the reduced-motion rule\n // in keyframes.css matches on this attribute.\n data-m22-animated\n className={cn(\n 'fixed inset-y-0 start-0 z-(--z-modal) flex w-60 flex-col border-e border-(--rule) bg-(--paper) transition-transform duration-(--duration-slow) ease-(--ease-out-expo)',\n // The drawer slides in from the edge reading STARTS at, so in an\n // right-to-left document it comes from the right.\n open ? 'translate-x-0' : '-translate-x-full rtl:translate-x-full',\n 'md:static md:z-auto md:translate-x-0',\n )}\n >\n {brand && (\n <div className=\"flex h-(--bar-h) items-center border-b border-(--rule) px-5\">{brand}</div>\n )}\n <nav\n aria-label={navLabel}\n className=\"flex flex-1 flex-col gap-1 overflow-y-auto p-3 scroll-slim\"\n >\n {sidebar}\n </nav>\n </aside>\n\n <div className=\"flex min-w-0 flex-col\">\n <header className=\"sticky top-0 z-(--z-sticky) flex h-(--bar-h) items-center gap-3 border-b border-(--rule) bg-(--paper)/85 px-(--page-pad) backdrop-blur\">\n <button\n type=\"button\"\n aria-label={open ? closeLabel : openLabel}\n aria-expanded={open}\n aria-controls={sidebarId}\n ref={toggle}\n onClick={() => setOpen((previous) => !previous)}\n className=\"-ms-2.5 grid size-11 place-items-center rounded-(--radius) text-(--ink-2) transition-colors duration-(--duration-fast) hover:text-(--ink) md:hidden\"\n >\n {open ? <X size={20} strokeWidth={1.5} aria-hidden /> : <Menu size={20} strokeWidth={1.5} aria-hidden />}\n </button>\n {topbar}\n </header>\n\n <Content className=\"mx-auto w-full max-w-(--w-page) px-(--page-pad) py-8\">{children}</Content>\n </div>\n </div>\n )\n}\n\nexport default AppShell\n"],"mappings":"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","names":[]}
@@ -13,9 +13,9 @@ const VARIANT = {
13
13
  danger: "bg-(--danger) text-(--paper) border border-(--danger) hover:opacity-85"
14
14
  };
15
15
  const SIZE = {
16
- sm: "min-h-(--control-h-sm) px-(--control-px-sm) py-(--control-py-sm) text-[13px]",
17
- md: "min-h-(--control-h-md) px-(--control-px-md) py-(--control-py-md) text-sm",
18
- lg: "min-h-(--control-h-lg) px-(--control-px-lg) py-(--control-py-lg) text-[15px]"
16
+ sm: "min-h-(--control-h-sm) px-(--control-px-sm) py-(--control-py-sm) text-[13px] leading-(--control-lh)",
17
+ md: "min-h-(--control-h-md) px-(--control-px-md) py-(--control-py-md) text-sm leading-(--control-lh)",
18
+ lg: "min-h-(--control-h-lg) px-(--control-px-lg) py-(--control-py-lg) text-[15px] leading-(--control-lh)"
19
19
  };
20
20
  const ICON_SIZE = {
21
21
  sm: "size-(--control-h-sm) p-0",