@misoto22/design 0.7.0 → 0.8.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 (369) hide show
  1. package/AGENTS.md +87 -0
  2. package/CLAUDE.md +1 -0
  3. package/README.md +50 -5
  4. package/bin/misoto22-design.mjs +59 -13
  5. package/dist/agent/Accordion.md +24 -1
  6. package/dist/agent/Alert.md +23 -1
  7. package/dist/agent/AppShell.md +30 -2
  8. package/dist/agent/ArchitectureFigure.md +25 -2
  9. package/dist/agent/AreaChart.md +27 -2
  10. package/dist/agent/Article.md +26 -3
  11. package/dist/agent/AspectRatio.md +57 -0
  12. package/dist/agent/Avatar.md +25 -5
  13. package/dist/agent/Badge.md +22 -1
  14. package/dist/agent/BarChart.md +29 -2
  15. package/dist/agent/BarList.md +27 -1
  16. package/dist/agent/BigNumber.md +26 -2
  17. package/dist/agent/BoxPlot.md +24 -1
  18. package/dist/agent/Breadcrumb.md +24 -2
  19. package/dist/agent/BulletChart.md +23 -1
  20. package/dist/agent/Button.md +25 -1
  21. package/dist/agent/Calendar.md +37 -3
  22. package/dist/agent/Card.md +24 -2
  23. package/dist/agent/Checkbox.md +23 -2
  24. package/dist/agent/Code.md +50 -0
  25. package/dist/agent/CodeBlock.md +74 -0
  26. package/dist/agent/Collapsible.md +22 -1
  27. package/dist/agent/Combobox.md +29 -3
  28. package/dist/agent/Command.md +25 -2
  29. package/dist/agent/ComposedChart.md +26 -2
  30. package/dist/agent/ContextMenu.md +37 -4
  31. package/dist/agent/DataflowFigure.md +22 -1
  32. package/dist/agent/DatePicker.md +34 -6
  33. package/dist/agent/DescriptionList.md +68 -0
  34. package/dist/agent/Diagram.md +27 -3
  35. package/dist/agent/DiagramCanvas.md +24 -1
  36. package/dist/agent/DiagramExportMenu.md +29 -3
  37. package/dist/agent/DiagramInspector.md +23 -1
  38. package/dist/agent/DiagramLegend.md +22 -1
  39. package/dist/agent/DiagramMinimap.md +24 -4
  40. package/dist/agent/DiagramToolbar.md +23 -1
  41. package/dist/agent/Dialog.md +26 -3
  42. package/dist/agent/DropdownMenu.md +45 -6
  43. package/dist/agent/EmptyState.md +26 -1
  44. package/dist/agent/ErrorState.md +27 -2
  45. package/dist/agent/Facet.md +25 -1
  46. package/dist/agent/Field.md +45 -4
  47. package/dist/agent/FigureBand.md +24 -1
  48. package/dist/agent/FloatingIconButton.md +23 -1
  49. package/dist/agent/FunnelChart.md +23 -1
  50. package/dist/agent/Heading.md +64 -0
  51. package/dist/agent/Heatmap.md +25 -1
  52. package/dist/agent/Histogram.md +26 -2
  53. package/dist/agent/Input.md +22 -1
  54. package/dist/agent/Kbd.md +19 -1
  55. package/dist/agent/LifecycleFigure.md +22 -1
  56. package/dist/agent/LineChart.md +26 -2
  57. package/dist/agent/LinkArrow.md +20 -1
  58. package/dist/agent/Markdown.md +68 -0
  59. package/dist/agent/NativeSelect.md +23 -2
  60. package/dist/agent/NavItem.md +21 -1
  61. package/dist/agent/Pagination.md +28 -2
  62. package/dist/agent/PieChart.md +26 -2
  63. package/dist/agent/Popover.md +23 -1
  64. package/dist/agent/Progress.md +27 -3
  65. package/dist/agent/RadarChart.md +26 -2
  66. package/dist/agent/RadialChart.md +26 -3
  67. package/dist/agent/RadioGroup.md +23 -2
  68. package/dist/agent/SankeyChart.md +25 -2
  69. package/dist/agent/ScatterChart.md +27 -2
  70. package/dist/agent/ScrollArea.md +24 -3
  71. package/dist/agent/SearchableMenu.md +23 -1
  72. package/dist/agent/Select.md +29 -3
  73. package/dist/agent/Separator.md +28 -4
  74. package/dist/agent/SequenceFigure.md +23 -1
  75. package/dist/agent/Sheet.md +25 -2
  76. package/dist/agent/Skeleton.md +24 -2
  77. package/dist/agent/Slider.md +27 -4
  78. package/dist/agent/Sparkline.md +23 -1
  79. package/dist/agent/Spinner.md +24 -2
  80. package/dist/agent/StatusDot.md +20 -1
  81. package/dist/agent/StatusPill.md +28 -2
  82. package/dist/agent/Steps.md +24 -1
  83. package/dist/agent/Switch.md +21 -1
  84. package/dist/agent/Table.md +26 -3
  85. package/dist/agent/Tabs.md +24 -2
  86. package/dist/agent/Tag.md +40 -9
  87. package/dist/agent/Text.md +65 -0
  88. package/dist/agent/Textarea.md +20 -1
  89. package/dist/agent/Timestamp.md +64 -0
  90. package/dist/agent/Toast.md +26 -2
  91. package/dist/agent/ToggleGroup.md +24 -2
  92. package/dist/agent/Toolbar.md +71 -0
  93. package/dist/agent/Tooltip.md +23 -1
  94. package/dist/agent/TreemapChart.md +24 -1
  95. package/dist/agent/WaterfallChart.md +24 -1
  96. package/dist/agent/WorkflowFigure.md +24 -2
  97. package/dist/agent/catalog.json +5602 -745
  98. package/dist/agent/index.md +13 -4
  99. package/dist/article.css +16 -4
  100. package/dist/charts/AreaChart/AreaChart.d.ts +9 -2
  101. package/dist/charts/AreaChart/AreaChart.js +12 -13
  102. package/dist/charts/AreaChart/AreaChart.js.map +1 -1
  103. package/dist/charts/BarChart/BarChart.d.ts +14 -3
  104. package/dist/charts/BarChart/BarChart.js +20 -12
  105. package/dist/charts/BarChart/BarChart.js.map +1 -1
  106. package/dist/charts/BarList/BarList.d.ts +9 -1
  107. package/dist/charts/BarList/BarList.js +9 -1
  108. package/dist/charts/BarList/BarList.js.map +1 -1
  109. package/dist/charts/BigNumber/BigNumber.d.ts +19 -3
  110. package/dist/charts/BigNumber/BigNumber.js +25 -5
  111. package/dist/charts/BigNumber/BigNumber.js.map +1 -1
  112. package/dist/charts/BoxPlot/BoxPlot.js +4 -2
  113. package/dist/charts/BoxPlot/BoxPlot.js.map +1 -1
  114. package/dist/charts/BulletChart/BulletChart.js +28 -7
  115. package/dist/charts/BulletChart/BulletChart.js.map +1 -1
  116. package/dist/charts/ComposedChart/ComposedChart.d.ts +9 -2
  117. package/dist/charts/ComposedChart/ComposedChart.js +10 -10
  118. package/dist/charts/ComposedChart/ComposedChart.js.map +1 -1
  119. package/dist/charts/FunnelChart/FunnelChart.d.ts +7 -2
  120. package/dist/charts/FunnelChart/FunnelChart.js +6 -16
  121. package/dist/charts/FunnelChart/FunnelChart.js.map +1 -1
  122. package/dist/charts/Heatmap/Heatmap.d.ts +17 -3
  123. package/dist/charts/Heatmap/Heatmap.js +20 -11
  124. package/dist/charts/Heatmap/Heatmap.js.map +1 -1
  125. package/dist/charts/Histogram/Histogram.d.ts +24 -1
  126. package/dist/charts/Histogram/Histogram.js +43 -11
  127. package/dist/charts/Histogram/Histogram.js.map +1 -1
  128. package/dist/charts/LineChart/LineChart.d.ts +9 -2
  129. package/dist/charts/LineChart/LineChart.js +11 -12
  130. package/dist/charts/LineChart/LineChart.js.map +1 -1
  131. package/dist/charts/PieChart/PieChart.d.ts +15 -3
  132. package/dist/charts/PieChart/PieChart.js +14 -6
  133. package/dist/charts/PieChart/PieChart.js.map +1 -1
  134. package/dist/charts/RadarChart/RadarChart.d.ts +15 -3
  135. package/dist/charts/RadarChart/RadarChart.js +11 -9
  136. package/dist/charts/RadarChart/RadarChart.js.map +1 -1
  137. package/dist/charts/RadialChart/RadialChart.d.ts +22 -4
  138. package/dist/charts/RadialChart/RadialChart.js +28 -6
  139. package/dist/charts/RadialChart/RadialChart.js.map +1 -1
  140. package/dist/charts/SankeyChart/SankeyChart.d.ts +16 -4
  141. package/dist/charts/SankeyChart/SankeyChart.js +14 -6
  142. package/dist/charts/SankeyChart/SankeyChart.js.map +1 -1
  143. package/dist/charts/ScatterChart/ScatterChart.d.ts +24 -3
  144. package/dist/charts/ScatterChart/ScatterChart.js +21 -15
  145. package/dist/charts/ScatterChart/ScatterChart.js.map +1 -1
  146. package/dist/charts/Sparkline/Sparkline.js +5 -3
  147. package/dist/charts/Sparkline/Sparkline.js.map +1 -1
  148. package/dist/charts/TreemapChart/TreemapChart.d.ts +7 -2
  149. package/dist/charts/TreemapChart/TreemapChart.js +20 -11
  150. package/dist/charts/TreemapChart/TreemapChart.js.map +1 -1
  151. package/dist/charts/WaterfallChart/WaterfallChart.js +4 -2
  152. package/dist/charts/WaterfallChart/WaterfallChart.js.map +1 -1
  153. package/dist/charts/lib/export.d.ts +1 -0
  154. package/dist/charts/lib/figure.d.ts +21 -1
  155. package/dist/charts/lib/figure.js +5 -2
  156. package/dist/charts/lib/figure.js.map +1 -1
  157. package/dist/charts/lib/scale.d.ts +23 -0
  158. package/dist/charts/lib/scale.js +13 -0
  159. package/dist/charts/lib/scale.js.map +1 -0
  160. package/dist/charts/lib/selection.d.ts +20 -0
  161. package/dist/charts/lib/selection.js +18 -0
  162. package/dist/charts/lib/selection.js.map +1 -0
  163. package/dist/charts/lib/toolbar.d.ts +1 -0
  164. package/dist/components/Accordion/Accordion.js +8 -1
  165. package/dist/components/Accordion/Accordion.js.map +1 -1
  166. package/dist/components/AppShell/AppShell.d.ts +6 -0
  167. package/dist/components/AppShell/AppShell.js +34 -4
  168. package/dist/components/AppShell/AppShell.js.map +1 -1
  169. package/dist/components/Article/Article.d.ts +11 -4
  170. package/dist/components/Article/Article.js +13 -0
  171. package/dist/components/Article/Article.js.map +1 -1
  172. package/dist/components/AspectRatio/AspectRatio.d.ts +46 -0
  173. package/dist/components/AspectRatio/AspectRatio.js +33 -0
  174. package/dist/components/AspectRatio/AspectRatio.js.map +1 -0
  175. package/dist/components/Avatar/Avatar.d.ts +12 -3
  176. package/dist/components/Avatar/Avatar.js +6 -1
  177. package/dist/components/Avatar/Avatar.js.map +1 -1
  178. package/dist/components/Breadcrumb/Breadcrumb.d.ts +6 -0
  179. package/dist/components/Breadcrumb/Breadcrumb.js +15 -0
  180. package/dist/components/Breadcrumb/Breadcrumb.js.map +1 -1
  181. package/dist/components/Button/Button.js +14 -0
  182. package/dist/components/Button/Button.js.map +1 -1
  183. package/dist/components/Calendar/Calendar.d.ts +29 -3
  184. package/dist/components/Calendar/Calendar.js +60 -12
  185. package/dist/components/Calendar/Calendar.js.map +1 -1
  186. package/dist/components/Card/Card.d.ts +6 -0
  187. package/dist/components/Card/Card.js +1 -1
  188. package/dist/components/Card/Card.js.map +1 -1
  189. package/dist/components/Checkbox/Checkbox.d.ts +5 -2
  190. package/dist/components/Checkbox/Checkbox.js +12 -2
  191. package/dist/components/Checkbox/Checkbox.js.map +1 -1
  192. package/dist/components/Code/Code.d.ts +30 -0
  193. package/dist/components/Code/Code.js +21 -0
  194. package/dist/components/Code/Code.js.map +1 -0
  195. package/dist/components/CodeBlock/CodeBlock.d.ts +141 -0
  196. package/dist/components/CodeBlock/CodeBlock.js +123 -0
  197. package/dist/components/CodeBlock/CodeBlock.js.map +1 -0
  198. package/dist/components/Collapsible/Collapsible.js +10 -2
  199. package/dist/components/Collapsible/Collapsible.js.map +1 -1
  200. package/dist/components/Combobox/Combobox.d.ts +15 -1
  201. package/dist/components/Combobox/Combobox.js +25 -5
  202. package/dist/components/Combobox/Combobox.js.map +1 -1
  203. package/dist/components/Command/Command.d.ts +4 -2
  204. package/dist/components/Command/Command.js +13 -1
  205. package/dist/components/Command/Command.js.map +1 -1
  206. package/dist/components/ContextMenu/ContextMenu.d.ts +30 -5
  207. package/dist/components/ContextMenu/ContextMenu.js +32 -2
  208. package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
  209. package/dist/components/DatePicker/DatePicker.d.ts +36 -4
  210. package/dist/components/DatePicker/DatePicker.js +55 -12
  211. package/dist/components/DatePicker/DatePicker.js.map +1 -1
  212. package/dist/components/DescriptionList/DescriptionList.d.ts +73 -0
  213. package/dist/components/DescriptionList/DescriptionList.js +35 -0
  214. package/dist/components/DescriptionList/DescriptionList.js.map +1 -0
  215. package/dist/components/Diagram/Diagram.d.ts +8 -3
  216. package/dist/components/Diagram/Diagram.js +82 -10
  217. package/dist/components/Diagram/Diagram.js.map +1 -1
  218. package/dist/components/Dialog/Dialog.d.ts +18 -3
  219. package/dist/components/Dialog/Dialog.js +20 -2
  220. package/dist/components/Dialog/Dialog.js.map +1 -1
  221. package/dist/components/DropdownMenu/DropdownMenu.d.ts +49 -11
  222. package/dist/components/DropdownMenu/DropdownMenu.js +32 -2
  223. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  224. package/dist/components/EmptyState/EmptyState.d.ts +25 -1
  225. package/dist/components/EmptyState/EmptyState.js +3 -1
  226. package/dist/components/EmptyState/EmptyState.js.map +1 -1
  227. package/dist/components/ErrorState/ErrorState.d.ts +22 -1
  228. package/dist/components/ErrorState/ErrorState.js +11 -2
  229. package/dist/components/ErrorState/ErrorState.js.map +1 -1
  230. package/dist/components/Field/Field.d.ts +70 -3
  231. package/dist/components/Field/Field.js +53 -12
  232. package/dist/components/Field/Field.js.map +1 -1
  233. package/dist/components/Field/field-control.d.ts +31 -0
  234. package/dist/components/Field/field-control.js +12 -0
  235. package/dist/components/Field/field-control.js.map +1 -0
  236. package/dist/components/FloatingIconButton/FloatingIconButton.js +2 -0
  237. package/dist/components/FloatingIconButton/FloatingIconButton.js.map +1 -1
  238. package/dist/components/Heading/Heading.d.ts +62 -0
  239. package/dist/components/Heading/Heading.js +44 -0
  240. package/dist/components/Heading/Heading.js.map +1 -0
  241. package/dist/components/Markdown/Markdown.d.ts +139 -0
  242. package/dist/components/Markdown/Markdown.js +135 -0
  243. package/dist/components/Markdown/Markdown.js.map +1 -0
  244. package/dist/components/Markdown/inline.d.ts +29 -0
  245. package/dist/components/Markdown/inline.js +74 -0
  246. package/dist/components/Markdown/inline.js.map +1 -0
  247. package/dist/components/Markdown/nodes.d.ts +65 -0
  248. package/dist/components/Markdown/nodes.js +1 -0
  249. package/dist/components/Markdown/nodes.js.map +1 -0
  250. package/dist/components/Markdown/parse.d.ts +24 -0
  251. package/dist/components/Markdown/parse.js +110 -0
  252. package/dist/components/Markdown/parse.js.map +1 -0
  253. package/dist/components/Markdown/slug.d.ts +15 -0
  254. package/dist/components/Markdown/slug.js +7 -0
  255. package/dist/components/Markdown/slug.js.map +1 -0
  256. package/dist/components/NativeSelect/NativeSelect.d.ts +6 -0
  257. package/dist/components/NativeSelect/NativeSelect.js +2 -3
  258. package/dist/components/NativeSelect/NativeSelect.js.map +1 -1
  259. package/dist/components/Pagination/Pagination.d.ts +24 -1
  260. package/dist/components/Pagination/Pagination.js +6 -3
  261. package/dist/components/Pagination/Pagination.js.map +1 -1
  262. package/dist/components/Progress/Progress.d.ts +17 -5
  263. package/dist/components/Progress/Progress.js +18 -6
  264. package/dist/components/Progress/Progress.js.map +1 -1
  265. package/dist/components/RadioGroup/RadioGroup.d.ts +6 -0
  266. package/dist/components/RadioGroup/RadioGroup.js +11 -1
  267. package/dist/components/RadioGroup/RadioGroup.js.map +1 -1
  268. package/dist/components/ScrollArea/ScrollArea.d.ts +15 -0
  269. package/dist/components/ScrollArea/ScrollArea.js +2 -2
  270. package/dist/components/ScrollArea/ScrollArea.js.map +1 -1
  271. package/dist/components/SearchableMenu/SearchableMenu.d.ts +11 -2
  272. package/dist/components/SearchableMenu/SearchableMenu.js +24 -2
  273. package/dist/components/SearchableMenu/SearchableMenu.js.map +1 -1
  274. package/dist/components/Select/Select.d.ts +18 -2
  275. package/dist/components/Select/Select.js +20 -3
  276. package/dist/components/Select/Select.js.map +1 -1
  277. package/dist/components/Separator/Separator.d.ts +21 -3
  278. package/dist/components/Separator/Separator.js +9 -1
  279. package/dist/components/Separator/Separator.js.map +1 -1
  280. package/dist/components/Sheet/Sheet.d.ts +3 -0
  281. package/dist/components/Sheet/Sheet.js +11 -2
  282. package/dist/components/Sheet/Sheet.js.map +1 -1
  283. package/dist/components/Skeleton/Skeleton.d.ts +8 -1
  284. package/dist/components/Skeleton/Skeleton.js +1 -1
  285. package/dist/components/Skeleton/Skeleton.js.map +1 -1
  286. package/dist/components/Slider/Slider.d.ts +21 -3
  287. package/dist/components/Slider/Slider.js +54 -27
  288. package/dist/components/Slider/Slider.js.map +1 -1
  289. package/dist/components/Spinner/Spinner.d.ts +8 -0
  290. package/dist/components/Spinner/Spinner.js +3 -2
  291. package/dist/components/Spinner/Spinner.js.map +1 -1
  292. package/dist/components/StatusPill/StatusPill.d.ts +5 -0
  293. package/dist/components/StatusPill/StatusPill.js +6 -0
  294. package/dist/components/StatusPill/StatusPill.js.map +1 -1
  295. package/dist/components/Table/Table.d.ts +6 -0
  296. package/dist/components/Table/Table.js +3 -1
  297. package/dist/components/Table/Table.js.map +1 -1
  298. package/dist/components/Tag/Tag.d.ts +68 -9
  299. package/dist/components/Tag/Tag.js +50 -10
  300. package/dist/components/Tag/Tag.js.map +1 -1
  301. package/dist/components/Text/Text.d.ts +62 -0
  302. package/dist/components/Text/Text.js +30 -0
  303. package/dist/components/Text/Text.js.map +1 -0
  304. package/dist/components/Timestamp/Timestamp.d.ts +59 -0
  305. package/dist/components/Timestamp/Timestamp.js +56 -0
  306. package/dist/components/Timestamp/Timestamp.js.map +1 -0
  307. package/dist/components/Toast/Toast.d.ts +9 -1
  308. package/dist/components/Toast/Toast.js +27 -4
  309. package/dist/components/Toast/Toast.js.map +1 -1
  310. package/dist/components/ToggleGroup/ToggleGroup.d.ts +6 -1
  311. package/dist/components/ToggleGroup/ToggleGroup.js +11 -1
  312. package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
  313. package/dist/components/Toolbar/Toolbar.d.ts +55 -0
  314. package/dist/components/Toolbar/Toolbar.js +43 -0
  315. package/dist/components/Toolbar/Toolbar.js.map +1 -0
  316. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.d.ts +6 -0
  317. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js +48 -8
  318. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js.map +1 -1
  319. package/dist/diagrams/DataflowFigure/DataflowFigure.js +21 -2
  320. package/dist/diagrams/DataflowFigure/DataflowFigure.js.map +1 -1
  321. package/dist/diagrams/DiagramCanvas/DiagramCanvas.d.ts +14 -0
  322. package/dist/diagrams/DiagramCanvas/DiagramCanvas.js +14 -7
  323. package/dist/diagrams/DiagramCanvas/DiagramCanvas.js.map +1 -1
  324. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.d.ts +38 -4
  325. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js +14 -16
  326. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js.map +1 -1
  327. package/dist/diagrams/DiagramInspector/DiagramInspector.js +2 -2
  328. package/dist/diagrams/DiagramInspector/DiagramInspector.js.map +1 -1
  329. package/dist/diagrams/DiagramMinimap/DiagramMinimap.d.ts +23 -7
  330. package/dist/diagrams/DiagramMinimap/DiagramMinimap.js +45 -18
  331. package/dist/diagrams/DiagramMinimap/DiagramMinimap.js.map +1 -1
  332. package/dist/diagrams/LifecycleFigure/LifecycleFigure.js +44 -12
  333. package/dist/diagrams/LifecycleFigure/LifecycleFigure.js.map +1 -1
  334. package/dist/diagrams/SequenceFigure/SequenceFigure.js +35 -5
  335. package/dist/diagrams/SequenceFigure/SequenceFigure.js.map +1 -1
  336. package/dist/diagrams/WorkflowFigure/WorkflowFigure.d.ts +3 -1
  337. package/dist/diagrams/WorkflowFigure/WorkflowFigure.js +76 -28
  338. package/dist/diagrams/WorkflowFigure/WorkflowFigure.js.map +1 -1
  339. package/dist/diagrams/index.d.ts +1 -0
  340. package/dist/diagrams/index.js +10 -0
  341. package/dist/diagrams/index.js.map +1 -1
  342. package/dist/diagrams/lib/dev.d.ts +61 -0
  343. package/dist/diagrams/lib/dev.js +96 -0
  344. package/dist/diagrams/lib/dev.js.map +1 -0
  345. package/dist/diagrams/lib/frame.d.ts +48 -7
  346. package/dist/diagrams/lib/frame.js +17 -2
  347. package/dist/diagrams/lib/frame.js.map +1 -1
  348. package/dist/diagrams/lib/wires.js.map +1 -1
  349. package/dist/diagrams/spec.d.ts +64 -12
  350. package/dist/index.d.ts +16 -4
  351. package/dist/index.js +9 -0
  352. package/dist/index.js.map +1 -1
  353. package/dist/keyframes.css +57 -3
  354. package/dist/lib/overlay-container.d.ts +14 -4
  355. package/dist/lib/overlay-container.js.map +1 -1
  356. package/dist/lib/warn.d.ts +73 -0
  357. package/dist/lib/warn.js +38 -0
  358. package/dist/lib/warn.js.map +1 -0
  359. package/dist/semantic.css +17 -6
  360. package/dist/styles.css +1 -1
  361. package/dist/tokens.css +20 -2
  362. package/dist/tokens.d.ts +1 -1
  363. package/dist/tokens.js +39 -36
  364. package/dist/tokens.json +39 -36
  365. package/llms.txt +35 -0
  366. package/package.json +5 -2
  367. package/skills/misoto22-design/evals/evals.json +84 -0
  368. package/skills/misoto22-design/rules/a11y.md +55 -3
  369. package/skills/misoto22-design/rules/forms.md +5 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Select/Select.tsx"],"sourcesContent":["'use client'\n\nimport * as SelectPrimitive from '@radix-ui/react-select'\nimport { Check, ChevronDown, ChevronUp } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\nimport { CONTROL_BASE, CONTROL_BORDER, isInvalid } from '../../lib/control'\n\n/** Radix Select root, group and label, as typed passthroughs. */\nexport const SelectRoot = SelectPrimitive.Root\nexport const SelectGroup = SelectPrimitive.Group\n\nexport interface SelectProps extends ComponentProps<typeof SelectPrimitive.Root> {\n /**\n * Classes for the option PANEL, not the trigger.\n *\n * `className` styles the trigger, which is the common case. This exists for\n * the uncommon one: a select inside a bounded surface, where the default\n * 18rem of list would cover the thing the reader is choosing for — a year\n * picker over its own calendar, say.\n */\n contentClassName?: string\n /** Names the control. Required — the trigger shows a value, and a value is not a name. */\n label: string\n placeholder?: string\n /** Paints the resting border with `--danger` and reflects `aria-invalid`. */\n invalid?: boolean\n disabled?: boolean\n className?: string\n /** `SelectItem`s, optionally wrapped in `SelectGroup` with a `SelectLabel`. */\n children: ReactNode\n}\n\n/**\n * A choice from a list, styled the whole way down.\n *\n * The option list is ours — drawn from the same tokens as everything else, so\n * it does not change typeface, spacing and selection colour the moment it\n * opens. That is the whole reason this replaced the native control as the\n * default: a design system whose most common form control stops being part of\n * the system on click is not a design system, it is a stylesheet for closed\n * states.\n *\n * The keyboard contract is Radix's, which means it is the platform's: typeahead\n * works, the arrows move, Home and End reach the ends, and Escape closes\n * without choosing. That was the one genuine argument for staying native, and\n * it is answered.\n *\n * Past roughly a dozen options, reach for `Combobox` — a list nobody can filter\n * is worse than one they can type into. Where the platform picker is genuinely\n * better — a phone, or a form that must survive without JavaScript — reach for\n * `NativeSelect`.\n *\n * @example\n * <Field label=\"Region\">\n * <Select label=\"Region\" defaultValue=\"au\">\n * <SelectItem value=\"au\">Australia</SelectItem>\n * <SelectItem value=\"nz\">New Zealand</SelectItem>\n * </Select>\n * </Field>\n */\nexport function Select({\n label,\n placeholder = 'Select…',\n invalid,\n disabled,\n className,\n contentClassName,\n children,\n ...props\n}: SelectProps) {\n const bad = isInvalid(invalid)\n const container = useOverlayContainer()\n\n return (\n <SelectPrimitive.Root disabled={disabled} {...props}>\n <SelectPrimitive.Trigger\n aria-label={label}\n aria-invalid={bad || undefined}\n className={cn(\n CONTROL_BASE,\n 'group flex cursor-pointer items-center justify-between gap-2 text-start data-[placeholder]:text-(--ink-3-aa)',\n bad ? CONTROL_BORDER.invalid : CONTROL_BORDER.resting,\n className,\n )}\n >\n <SelectPrimitive.Value placeholder={placeholder} />\n <SelectPrimitive.Icon asChild>\n <ChevronDown\n size={16}\n strokeWidth={1.5}\n aria-hidden\n className=\"shrink-0 text-(--ink-3-aa) transition-transform duration-(--duration-fast) group-data-[state=open]:rotate-180\"\n />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n\n <SelectPrimitive.Portal container={container ?? undefined}>\n <SelectPrimitive.Content\n collisionBoundary={container ?? undefined}\n collisionPadding={8}\n position=\"popper\"\n sideOffset={6}\n data-m22-animated\n className={cn(\n 'z-(--z-dropdown) max-h-72 min-w-(--radix-select-trigger-width) overflow-hidden rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) panel-blur',\n contentClassName,\n 'data-[state=open]:animate-[m22-pop-in_var(--duration-fast)_var(--ease-out-expo)] data-[state=closed]:animate-[m22-pop-out_var(--duration-fast)_var(--ease)] origin-(--radix-popper-transform-origin)',\n )}\n >\n <SelectPrimitive.ScrollUpButton className=\"flex h-6 items-center justify-center text-(--ink-3-aa)\">\n <ChevronUp size={14} strokeWidth={1.5} aria-hidden />\n </SelectPrimitive.ScrollUpButton>\n <SelectPrimitive.Viewport className=\"p-1.5\">{children}</SelectPrimitive.Viewport>\n <SelectPrimitive.ScrollDownButton className=\"flex h-6 items-center justify-center text-(--ink-3-aa)\">\n <ChevronDown size={14} strokeWidth={1.5} aria-hidden />\n </SelectPrimitive.ScrollDownButton>\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n </SelectPrimitive.Root>\n )\n}\n\nexport type SelectItemProps = ComponentProps<typeof SelectPrimitive.Item>\n\n/** One option. The tick marks the chosen one; the fill marks the highlighted one. */\nexport function SelectItem({ className, children, ...props }: SelectItemProps) {\n return (\n <SelectPrimitive.Item\n className={cn(\n 'flex cursor-pointer select-none items-center gap-2.5 rounded-(--radius-row) px-2.5 py-2 text-sm text-(--ink-2) outline-none transition-colors duration-(--duration-fast) data-[highlighted]:bg-(--stone) data-[highlighted]:text-(--ink) data-[disabled]:pointer-events-none data-[disabled]:opacity-(--disabled-opacity)',\n className,\n )}\n {...props}\n >\n {/* The box is always there and only the tick inside it appears, so\n choosing an option does not shunt every label sideways. */}\n <span className=\"grid size-3.5 shrink-0 place-items-center\">\n <SelectPrimitive.ItemIndicator asChild>\n <Check size={14} strokeWidth={2} aria-hidden />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n )\n}\n\n/**\n * Mono eyebrow heading for a group of options.\n *\n * Must sit inside a `SelectGroup` — Radix throws otherwise, because a heading\n * with no group is a heading for nothing, and assistive tech would announce it\n * as an option.\n */\nexport function SelectLabel({\n className,\n ...props\n}: ComponentProps<typeof SelectPrimitive.Label>) {\n return (\n <SelectPrimitive.Label\n className={cn('px-2.5 py-1.5 eyebrow text-(--ink-3-aa)', className)}\n {...props}\n />\n )\n}\n\n/** Hairline divider between groups. */\nexport function SelectSeparator({\n className,\n ...props\n}: ComponentProps<typeof SelectPrimitive.Separator>) {\n return (\n <SelectPrimitive.Separator className={cn('my-1 h-px bg-(--rule)', className)} {...props} />\n )\n}\n\nexport default Select\n"],"mappings":";AA6EM,SAUE,KAVF;AA3EN,YAAY,qBAAqB;AACjC,SAAS,OAAO,aAAa,iBAAiB;AAE9C,SAAS,UAAU;AACnB,SAAS,2BAA2B;AACpC,SAAS,cAAc,gBAAgB,iBAAiB;AAGjD,MAAM,aAAa,gBAAgB;AACnC,MAAM,cAAc,gBAAgB;AAmDpC,SAAS,OAAO;AAAA,EACrB;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAgB;AACd,QAAM,MAAM,UAAU,OAAO;AAC7B,QAAM,YAAY,oBAAoB;AAEtC,SACE,qBAAC,gBAAgB,MAAhB,EAAqB,UAAqB,GAAG,OAC5C;AAAA;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,cAAY;AAAA,QACZ,gBAAc,OAAO;AAAA,QACrB,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA,MAAM,eAAe,UAAU,eAAe;AAAA,UAC9C;AAAA,QACF;AAAA,QAEA;AAAA,8BAAC,gBAAgB,OAAhB,EAAsB,aAA0B;AAAA,UACjD,oBAAC,gBAAgB,MAAhB,EAAqB,SAAO,MAC3B;AAAA,YAAC;AAAA;AAAA,cACC,MAAM;AAAA,cACN,aAAa;AAAA,cACb,eAAW;AAAA,cACX,WAAU;AAAA;AAAA,UACZ,GACF;AAAA;AAAA;AAAA,IACF;AAAA,IAEA,oBAAC,gBAAgB,QAAhB,EAAuB,WAAW,aAAa,QAC9C;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACD,mBAAmB,aAAa;AAAA,QAChC,kBAAkB;AAAA,QAChB,UAAS;AAAA,QACT,YAAY;AAAA,QACZ,qBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QAEA;AAAA,8BAAC,gBAAgB,gBAAhB,EAA+B,WAAU,0DACxC,8BAAC,aAAU,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC,GACrD;AAAA,UACA,oBAAC,gBAAgB,UAAhB,EAAyB,WAAU,SAAS,UAAS;AAAA,UACtD,oBAAC,gBAAgB,kBAAhB,EAAiC,WAAU,0DAC1C,8BAAC,eAAY,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC,GACvD;AAAA;AAAA;AAAA,IACF,GACF;AAAA,KACF;AAEJ;AAKO,SAAS,WAAW,EAAE,WAAW,UAAU,GAAG,MAAM,GAAoB;AAC7E,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAIJ;AAAA,4BAAC,UAAK,WAAU,6CACd,8BAAC,gBAAgB,eAAhB,EAA8B,SAAO,MACpC,8BAAC,SAAM,MAAM,IAAI,aAAa,GAAG,eAAW,MAAC,GAC/C,GACF;AAAA,QACA,oBAAC,gBAAgB,UAAhB,EAA0B,UAAS;AAAA;AAAA;AAAA,EACtC;AAEJ;AASO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,GAAG;AACL,GAAiD;AAC/C,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,WAAW,GAAG,2CAA2C,SAAS;AAAA,MACjE,GAAG;AAAA;AAAA,EACN;AAEJ;AAGO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,GAAG;AACL,GAAqD;AACnD,SACE,oBAAC,gBAAgB,WAAhB,EAA0B,WAAW,GAAG,yBAAyB,SAAS,GAAI,GAAG,OAAO;AAE7F;AAEA,IAAO,iBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Select/Select.tsx"],"sourcesContent":["'use client'\n\nimport * as SelectPrimitive from '@radix-ui/react-select'\nimport { Check, ChevronDown, ChevronUp } from 'lucide-react'\nimport { useId, type ComponentProps, type ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { warnBlankName } from '../../lib/warn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\nimport { useFieldControl } from '../Field/field-control'\nimport { CONTROL_BASE, CONTROL_BORDER, isInvalid } from '../../lib/control'\n\n/** Radix Select root, group and label, as typed passthroughs. */\nexport const SelectRoot = SelectPrimitive.Root\nexport const SelectGroup = SelectPrimitive.Group\n\nexport interface SelectProps extends ComponentProps<typeof SelectPrimitive.Root> {\n /**\n * Classes for the option PANEL, not the trigger.\n *\n * `className` styles the trigger, which is the common case. This exists for\n * the uncommon one: a select inside a bounded surface, where the default\n * 18rem of list would cover the thing the reader is choosing for — a year\n * picker over its own calendar, say.\n */\n contentClassName?: string\n /**\n * Names the control. Required — the trigger shows a value, and a value is not\n * a name.\n *\n * It is announced together with the value, not instead of it: the trigger is\n * named by the label and by its own text, so a reader hears \"Region,\n * Australia\". Inside a `Field` with a label, that label is used and this one\n * is not repeated.\n */\n label: string\n placeholder?: string\n /** Paints the resting border with `--danger` and reflects `aria-invalid`. */\n invalid?: boolean\n disabled?: boolean\n className?: string\n /** The TRIGGER's id — the element a label points at. A `Field` sets it. */\n id?: string\n /** Ids of the copy describing the control. A `Field` sets it from hint, error and description. */\n 'aria-describedby'?: string\n /** The spelling a form library sets; read together with `invalid`. */\n 'aria-invalid'?: boolean | 'true' | 'false'\n /** Announced on the trigger. A `Field` sets it from `required`. */\n 'aria-required'?: boolean\n /** `SelectItem`s, optionally wrapped in `SelectGroup` with a `SelectLabel`. */\n children: ReactNode\n}\n\n/**\n * A choice from a list, styled the whole way down.\n *\n * The option list is ours — drawn from the same tokens as everything else, so\n * it does not change typeface, spacing and selection colour the moment it\n * opens. That is the whole reason this replaced the native control as the\n * default: a design system whose most common form control stops being part of\n * the system on click is not a design system, it is a stylesheet for closed\n * states.\n *\n * The keyboard contract is Radix's, which means it is the platform's: typeahead\n * works, the arrows move, Home and End reach the ends, and Escape closes\n * without choosing. That was the one genuine argument for staying native, and\n * it is answered.\n *\n * Past roughly a dozen options, reach for `Combobox` — a list nobody can filter\n * is worse than one they can type into. Where the platform picker is genuinely\n * better — a phone, or a form that must survive without JavaScript — reach for\n * `NativeSelect`.\n *\n * @example\n * <Field label=\"Region\">\n * <Select label=\"Region\" defaultValue=\"au\">\n * <SelectItem value=\"au\">Australia</SelectItem>\n * <SelectItem value=\"nz\">New Zealand</SelectItem>\n * </Select>\n * </Field>\n */\nexport function Select({\n label,\n placeholder = 'Select…',\n invalid,\n disabled,\n className,\n contentClassName,\n children,\n id,\n 'aria-describedby': describedBy,\n 'aria-invalid': ariaInvalid,\n 'aria-required': ariaRequired,\n ...props\n}: SelectProps) {\n warnBlankName('Select', 'label', label, 'the trigger is announced with no name')\n const bad = isInvalid(invalid, ariaInvalid)\n\n const field = useFieldControl()\n const generated = useId()\n // The id belongs on the TRIGGER: Radix's root renders no DOM node at all, so\n // an id handed to it lands nowhere and the label above points at nothing.\n const triggerId = id ?? generated\n const valueId = `${triggerId}-value`\n const nameId = field?.labelId ?? `${triggerId}-name`\n\n const container = useOverlayContainer()\n\n return (\n <SelectPrimitive.Root disabled={disabled} {...props}>\n <SelectPrimitive.Trigger\n id={triggerId}\n // Named by the label AND by its own value, in that order. `aria-label`\n // outranks name-from-content, so naming it \"Region\" told a reader the\n // noun and never the answer — and the answer is the only thing on the\n // trigger.\n aria-labelledby={`${nameId} ${valueId}`}\n aria-describedby={describedBy}\n aria-required={ariaRequired}\n aria-invalid={bad || undefined}\n className={cn(\n CONTROL_BASE,\n 'group flex cursor-pointer items-center justify-between gap-2 text-start data-[placeholder]:text-(--ink-3-aa)',\n bad ? CONTROL_BORDER.invalid : CONTROL_BORDER.resting,\n className,\n )}\n >\n {field?.labelId == null && (\n <span id={nameId} className=\"sr-only\">\n {label}\n </span>\n )}\n {/* Wrapped rather than styled directly: Radix's Value drops className.\n The wrapper is also what `aria-labelledby` points at, so the name\n picks up the value and not the chevron. */}\n <span id={valueId} className=\"truncate\">\n <SelectPrimitive.Value placeholder={placeholder} />\n </span>\n <SelectPrimitive.Icon asChild>\n <ChevronDown\n size={16}\n strokeWidth={1.5}\n aria-hidden\n className=\"shrink-0 text-(--ink-3-aa) transition-transform duration-(--duration-fast) group-data-[state=open]:rotate-180\"\n />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n\n <SelectPrimitive.Portal container={container ?? undefined}>\n <SelectPrimitive.Content\n collisionBoundary={container ?? undefined}\n collisionPadding={8}\n position=\"popper\"\n sideOffset={6}\n data-m22-animated\n className={cn(\n 'z-(--z-dropdown) max-h-72 min-w-(--radix-select-trigger-width) overflow-hidden rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) panel-blur',\n contentClassName,\n 'data-[state=open]:animate-[m22-pop-in_var(--duration-fast)_var(--ease-out-expo)] data-[state=closed]:animate-[m22-pop-out_var(--duration-fast)_var(--ease)] origin-(--radix-popper-transform-origin)',\n )}\n >\n <SelectPrimitive.ScrollUpButton className=\"flex h-6 items-center justify-center text-(--ink-3-aa)\">\n <ChevronUp size={14} strokeWidth={1.5} aria-hidden />\n </SelectPrimitive.ScrollUpButton>\n <SelectPrimitive.Viewport className=\"p-1.5\">{children}</SelectPrimitive.Viewport>\n <SelectPrimitive.ScrollDownButton className=\"flex h-6 items-center justify-center text-(--ink-3-aa)\">\n <ChevronDown size={14} strokeWidth={1.5} aria-hidden />\n </SelectPrimitive.ScrollDownButton>\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n </SelectPrimitive.Root>\n )\n}\n\nexport type SelectItemProps = ComponentProps<typeof SelectPrimitive.Item>\n\n/** One option. The tick marks the chosen one; the fill marks the highlighted one. */\nexport function SelectItem({ className, children, ...props }: SelectItemProps) {\n return (\n <SelectPrimitive.Item\n className={cn(\n 'flex cursor-pointer select-none items-center gap-2.5 rounded-(--radius-row) px-2.5 py-2 text-sm text-(--ink-2) outline-none transition-colors duration-(--duration-fast) data-[highlighted]:bg-(--stone) data-[highlighted]:text-(--ink) data-[disabled]:pointer-events-none data-[disabled]:opacity-(--disabled-opacity)',\n className,\n )}\n {...props}\n >\n {/* The box is always there and only the tick inside it appears, so\n choosing an option does not shunt every label sideways. */}\n <span className=\"grid size-3.5 shrink-0 place-items-center\">\n <SelectPrimitive.ItemIndicator asChild>\n <Check size={14} strokeWidth={2} aria-hidden />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n )\n}\n\n/**\n * Mono eyebrow heading for a group of options.\n *\n * Must sit inside a `SelectGroup` — Radix throws otherwise, because a heading\n * with no group is a heading for nothing, and assistive tech would announce it\n * as an option.\n */\nexport function SelectLabel({\n className,\n ...props\n}: ComponentProps<typeof SelectPrimitive.Label>) {\n return (\n <SelectPrimitive.Label\n className={cn('px-2.5 py-1.5 eyebrow text-(--ink-3-aa)', className)}\n {...props}\n />\n )\n}\n\n/** Hairline divider between groups. */\nexport function SelectSeparator({\n className,\n ...props\n}: ComponentProps<typeof SelectPrimitive.Separator>) {\n return (\n <SelectPrimitive.Separator className={cn('my-1 h-px bg-(--rule)', className)} {...props} />\n )\n}\n\nexport default Select\n"],"mappings":";AA6GM,SAkBI,KAlBJ;AA3GN,YAAY,qBAAqB;AACjC,SAAS,OAAO,aAAa,iBAAiB;AAC9C,SAAS,aAAkD;AAC3D,SAAS,UAAU;AACnB,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,uBAAuB;AAChC,SAAS,cAAc,gBAAgB,iBAAiB;AAGjD,MAAM,aAAa,gBAAgB;AACnC,MAAM,cAAc,gBAAgB;AAmEpC,SAAS,OAAO;AAAA,EACrB;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,GAAG;AACL,GAAgB;AACd,gBAAc,UAAU,SAAS,OAAO,uCAAuC;AAC/E,QAAM,MAAM,UAAU,SAAS,WAAW;AAE1C,QAAM,QAAQ,gBAAgB;AAC9B,QAAM,YAAY,MAAM;AAGxB,QAAM,YAAY,MAAM;AACxB,QAAM,UAAU,GAAG,SAAS;AAC5B,QAAM,SAAS,OAAO,WAAW,GAAG,SAAS;AAE7C,QAAM,YAAY,oBAAoB;AAEtC,SACE,qBAAC,gBAAgB,MAAhB,EAAqB,UAAqB,GAAG,OAC5C;AAAA;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,IAAI;AAAA,QAKJ,mBAAiB,GAAG,MAAM,IAAI,OAAO;AAAA,QACrC,oBAAkB;AAAA,QAClB,iBAAe;AAAA,QACf,gBAAc,OAAO;AAAA,QACrB,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA,MAAM,eAAe,UAAU,eAAe;AAAA,UAC9C;AAAA,QACF;AAAA,QAEC;AAAA,iBAAO,WAAW,QACjB,oBAAC,UAAK,IAAI,QAAQ,WAAU,WACzB,iBACH;AAAA,UAKF,oBAAC,UAAK,IAAI,SAAS,WAAU,YAC3B,8BAAC,gBAAgB,OAAhB,EAAsB,aAA0B,GACnD;AAAA,UACA,oBAAC,gBAAgB,MAAhB,EAAqB,SAAO,MAC3B;AAAA,YAAC;AAAA;AAAA,cACC,MAAM;AAAA,cACN,aAAa;AAAA,cACb,eAAW;AAAA,cACX,WAAU;AAAA;AAAA,UACZ,GACF;AAAA;AAAA;AAAA,IACF;AAAA,IAEA,oBAAC,gBAAgB,QAAhB,EAAuB,WAAW,aAAa,QAC9C;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACD,mBAAmB,aAAa;AAAA,QAChC,kBAAkB;AAAA,QAChB,UAAS;AAAA,QACT,YAAY;AAAA,QACZ,qBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QAEA;AAAA,8BAAC,gBAAgB,gBAAhB,EAA+B,WAAU,0DACxC,8BAAC,aAAU,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC,GACrD;AAAA,UACA,oBAAC,gBAAgB,UAAhB,EAAyB,WAAU,SAAS,UAAS;AAAA,UACtD,oBAAC,gBAAgB,kBAAhB,EAAiC,WAAU,0DAC1C,8BAAC,eAAY,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC,GACvD;AAAA;AAAA;AAAA,IACF,GACF;AAAA,KACF;AAEJ;AAKO,SAAS,WAAW,EAAE,WAAW,UAAU,GAAG,MAAM,GAAoB;AAC7E,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAIJ;AAAA,4BAAC,UAAK,WAAU,6CACd,8BAAC,gBAAgB,eAAhB,EAA8B,SAAO,MACpC,8BAAC,SAAM,MAAM,IAAI,aAAa,GAAG,eAAW,MAAC,GAC/C,GACF;AAAA,QACA,oBAAC,gBAAgB,UAAhB,EAA0B,UAAS;AAAA;AAAA;AAAA,EACtC;AAEJ;AASO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,GAAG;AACL,GAAiD;AAC/C,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,WAAW,GAAG,2CAA2C,SAAS;AAAA,MACjE,GAAG;AAAA;AAAA,EACN;AAEJ;AAGO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,GAAG;AACL,GAAqD;AACnD,SACE,oBAAC,gBAAgB,WAAhB,EAA0B,WAAW,GAAG,yBAAyB,SAAS,GAAI,GAAG,OAAO;AAE7F;AAEA,IAAO,iBAAQ;","names":[]}
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { HTMLAttributes } from 'react';
2
+ import { HTMLAttributes, ReactNode } from 'react';
3
3
 
4
4
  interface SeparatorProps extends HTMLAttributes<HTMLDivElement> {
5
5
  orientation?: 'horizontal' | 'vertical';
@@ -11,20 +11,38 @@ interface SeparatorProps extends HTMLAttributes<HTMLDivElement> {
11
11
  * screen reader should hear as distinct.
12
12
  */
13
13
  decorative?: boolean;
14
+ /**
15
+ * Words in the break — "or continue with", "Older".
16
+ *
17
+ * Horizontal only, and it changes the construction rather than the styling:
18
+ * see the note on the component. `decorative` no longer applies, because the
19
+ * label is content and the two rules beside it are decoration either way.
20
+ */
21
+ label?: ReactNode;
14
22
  }
15
23
  /**
16
- * A rule.
24
+ * A rule, with or without words in it.
17
25
  *
18
26
  * In a monochrome system the rule does the work colour would otherwise do, so
19
27
  * it has three weights rather than one: hairline between rows, edge between
20
28
  * blocks, hard under a masthead. Picking by name keeps a page from drifting
21
29
  * into five hand-tuned greys.
22
30
  *
31
+ * **On the label.** "or continue with" was two Separators and a span at every
32
+ * call site, and the usual one-element version — text laid over a single rule
33
+ * with a background to punch a hole in it — needs to know the ground it is
34
+ * sitting on. Get that wrong and the notch is `--paper` on a card that is
35
+ * `--stone`, which reads as a rendering bug. So there is no ground: the rule is
36
+ * drawn twice, one piece either side of the label, and the gap is a gap. It is
37
+ * correct on any surface without being told which one it is on.
38
+ *
23
39
  * @example
24
40
  * <Separator weight="hard" />
25
41
  * @example
26
42
  * <Separator orientation="vertical" className="h-4" />
43
+ * @example
44
+ * <Separator label="or continue with" />
27
45
  */
28
- declare function Separator({ orientation, weight, decorative, className, ...rest }: SeparatorProps): react.JSX.Element;
46
+ declare function Separator({ orientation, weight, decorative, label, className, ...rest }: SeparatorProps): react.JSX.Element;
29
47
 
30
48
  export { Separator, type SeparatorProps, Separator as default };
@@ -1,4 +1,4 @@
1
- import { jsx } from "react/jsx-runtime";
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../../lib/cn.js";
3
3
  const WEIGHT = {
4
4
  hairline: "bg-(--rule)",
@@ -9,9 +9,17 @@ function Separator({
9
9
  orientation = "horizontal",
10
10
  weight = "hairline",
11
11
  decorative = true,
12
+ label,
12
13
  className,
13
14
  ...rest
14
15
  }) {
16
+ if (label != null && orientation === "horizontal") {
17
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex w-full items-center gap-3", className), ...rest, children: [
18
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("h-px flex-1", WEIGHT[weight]) }),
19
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 mono-meta text-(--ink-3-aa)", children: label }),
20
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("h-px flex-1", WEIGHT[weight]) })
21
+ ] });
22
+ }
15
23
  return /* @__PURE__ */ jsx(
16
24
  "div",
17
25
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Separator/Separator.tsx"],"sourcesContent":["import type { HTMLAttributes } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface SeparatorProps extends HTMLAttributes<HTMLDivElement> {\n orientation?: 'horizontal' | 'vertical'\n /** `hairline` divides rows inside one block; `edge` divides one block from the next. */\n weight?: 'hairline' | 'edge' | 'hard'\n /**\n * A rule that only groups things visually is decoration and must not be\n * announced. Set `false` when the rule genuinely separates two sections a\n * screen reader should hear as distinct.\n */\n decorative?: boolean\n}\n\nconst WEIGHT = {\n hairline: 'bg-(--rule)',\n edge: 'bg-(--rule-2)',\n hard: 'bg-(--rule-hard)',\n} as const\n\n/**\n * A rule.\n *\n * In a monochrome system the rule does the work colour would otherwise do, so\n * it has three weights rather than one: hairline between rows, edge between\n * blocks, hard under a masthead. Picking by name keeps a page from drifting\n * into five hand-tuned greys.\n *\n * @example\n * <Separator weight=\"hard\" />\n * @example\n * <Separator orientation=\"vertical\" className=\"h-4\" />\n */\nexport function Separator({\n orientation = 'horizontal',\n weight = 'hairline',\n decorative = true,\n className,\n ...rest\n}: SeparatorProps) {\n return (\n <div\n role={decorative ? 'none' : 'separator'}\n aria-orientation={decorative ? undefined : orientation}\n className={cn(\n 'shrink-0',\n orientation === 'horizontal' ? 'h-px w-full' : 'h-full w-px',\n WEIGHT[weight],\n className,\n )}\n {...rest}\n />\n )\n}\n\nexport default Separator\n"],"mappings":"AA0CI;AAzCJ,SAAS,UAAU;AAcnB,MAAM,SAAS;AAAA,EACb,UAAU;AAAA,EACV,MAAM;AAAA,EACN,MAAM;AACR;AAeO,SAAS,UAAU;AAAA,EACxB,cAAc;AAAA,EACd,SAAS;AAAA,EACT,aAAa;AAAA,EACb;AAAA,EACA,GAAG;AACL,GAAmB;AACjB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,aAAa,SAAS;AAAA,MAC5B,oBAAkB,aAAa,SAAY;AAAA,MAC3C,WAAW;AAAA,QACT;AAAA,QACA,gBAAgB,eAAe,gBAAgB;AAAA,QAC/C,OAAO,MAAM;AAAA,QACb;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,IAAO,oBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Separator/Separator.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface SeparatorProps extends HTMLAttributes<HTMLDivElement> {\n orientation?: 'horizontal' | 'vertical'\n /** `hairline` divides rows inside one block; `edge` divides one block from the next. */\n weight?: 'hairline' | 'edge' | 'hard'\n /**\n * A rule that only groups things visually is decoration and must not be\n * announced. Set `false` when the rule genuinely separates two sections a\n * screen reader should hear as distinct.\n */\n decorative?: boolean\n /**\n * Words in the break — \"or continue with\", \"Older\".\n *\n * Horizontal only, and it changes the construction rather than the styling:\n * see the note on the component. `decorative` no longer applies, because the\n * label is content and the two rules beside it are decoration either way.\n */\n label?: ReactNode\n}\n\nconst WEIGHT = {\n hairline: 'bg-(--rule)',\n edge: 'bg-(--rule-2)',\n hard: 'bg-(--rule-hard)',\n} as const\n\n/**\n * A rule, with or without words in it.\n *\n * In a monochrome system the rule does the work colour would otherwise do, so\n * it has three weights rather than one: hairline between rows, edge between\n * blocks, hard under a masthead. Picking by name keeps a page from drifting\n * into five hand-tuned greys.\n *\n * **On the label.** \"or continue with\" was two Separators and a span at every\n * call site, and the usual one-element version — text laid over a single rule\n * with a background to punch a hole in it — needs to know the ground it is\n * sitting on. Get that wrong and the notch is `--paper` on a card that is\n * `--stone`, which reads as a rendering bug. So there is no ground: the rule is\n * drawn twice, one piece either side of the label, and the gap is a gap. It is\n * correct on any surface without being told which one it is on.\n *\n * @example\n * <Separator weight=\"hard\" />\n * @example\n * <Separator orientation=\"vertical\" className=\"h-4\" />\n * @example\n * <Separator label=\"or continue with\" />\n */\nexport function Separator({\n orientation = 'horizontal',\n weight = 'hairline',\n decorative = true,\n label,\n className,\n ...rest\n}: SeparatorProps) {\n if (label != null && orientation === 'horizontal') {\n return (\n <div className={cn('flex w-full items-center gap-3', className)} {...rest}>\n <span aria-hidden=\"true\" className={cn('h-px flex-1', WEIGHT[weight])} />\n <span className=\"shrink-0 mono-meta text-(--ink-3-aa)\">{label}</span>\n <span aria-hidden=\"true\" className={cn('h-px flex-1', WEIGHT[weight])} />\n </div>\n )\n }\n\n return (\n <div\n role={decorative ? 'none' : 'separator'}\n aria-orientation={decorative ? undefined : orientation}\n className={cn(\n 'shrink-0',\n orientation === 'horizontal' ? 'h-px w-full' : 'h-full w-px',\n WEIGHT[weight],\n className,\n )}\n {...rest}\n />\n )\n}\n\nexport default Separator\n"],"mappings":"AA8DM,SACE,KADF;AA7DN,SAAS,UAAU;AAsBnB,MAAM,SAAS;AAAA,EACb,UAAU;AAAA,EACV,MAAM;AAAA,EACN,MAAM;AACR;AAyBO,SAAS,UAAU;AAAA,EACxB,cAAc;AAAA,EACd,SAAS;AAAA,EACT,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAmB;AACjB,MAAI,SAAS,QAAQ,gBAAgB,cAAc;AACjD,WACE,qBAAC,SAAI,WAAW,GAAG,kCAAkC,SAAS,GAAI,GAAG,MACnE;AAAA,0BAAC,UAAK,eAAY,QAAO,WAAW,GAAG,eAAe,OAAO,MAAM,CAAC,GAAG;AAAA,MACvE,oBAAC,UAAK,WAAU,wCAAwC,iBAAM;AAAA,MAC9D,oBAAC,UAAK,eAAY,QAAO,WAAW,GAAG,eAAe,OAAO,MAAM,CAAC,GAAG;AAAA,OACzE;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,aAAa,SAAS;AAAA,MAC5B,oBAAkB,aAAa,SAAY;AAAA,MAC3C,WAAW;AAAA,QACT;AAAA,QACA,gBAAgB,eAAe,gBAAgB;AAAA,QAC/C,OAAO,MAAM;AAAA,QACb;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,IAAO,oBAAQ;","names":[]}
@@ -27,6 +27,9 @@ interface SheetContentProps extends Omit<ComponentProps<typeof DialogPrimitive.C
27
27
  * The title is required, visible or not. A modal with no accessible name drops
28
28
  * a screen reader into an unnamed region with no way back out.
29
29
  *
30
+ * Portals into the element an enclosing `OverlayContainer` names, docking to
31
+ * that element's edge rather than the viewport's when there is one.
32
+ *
30
33
  * @example
31
34
  * <Sheet>
32
35
  * <SheetTrigger asChild><Button variant="secondary">Filters</Button></SheetTrigger>
@@ -3,6 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as DialogPrimitive from "@radix-ui/react-dialog";
4
4
  import { X } from "lucide-react";
5
5
  import { cn } from "../../lib/cn.js";
6
+ import { useOverlayContainer } from "../../lib/overlay-container.js";
6
7
  const Sheet = DialogPrimitive.Root;
7
8
  const SheetTrigger = DialogPrimitive.Trigger;
8
9
  const SheetClose = DialogPrimitive.Close;
@@ -21,20 +22,28 @@ function SheetContent({
21
22
  children,
22
23
  ...rest
23
24
  }) {
24
- return /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { children: [
25
+ const container = useOverlayContainer();
26
+ return /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { container: container ?? void 0, children: [
25
27
  /* @__PURE__ */ jsx(
26
28
  DialogPrimitive.Overlay,
27
29
  {
28
30
  "data-m22-animated": true,
29
- className: "fixed inset-0 z-(--z-overlay) bg-(--scrim) data-[state=open]:animate-[m22-fade-in_var(--duration-fast)_var(--ease)]"
31
+ className: cn(
32
+ "inset-0 z-(--z-overlay) bg-(--scrim) data-[state=open]:animate-[m22-fade-in_var(--duration-fast)_var(--ease)]",
33
+ container ? "absolute" : "fixed"
34
+ )
30
35
  }
31
36
  ),
32
37
  /* @__PURE__ */ jsxs(
33
38
  DialogPrimitive.Content,
34
39
  {
40
+ "data-m22-animated": true,
35
41
  className: cn(
36
42
  "fixed z-(--z-modal) flex flex-col overflow-y-auto border-(--panel-border) bg-(--panel-bg) p-6 panel-blur transition-transform duration-(--duration-slow) ease-(--ease-out-expo) scroll-slim",
37
43
  SIDE[side],
44
+ // Docked to the container's edge, and capped by its box — the widths
45
+ // above are read against the viewport and would overflow a frame.
46
+ container && "absolute max-h-full max-w-full",
38
47
  className
39
48
  ),
40
49
  ...rest,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Sheet/Sheet.tsx"],"sourcesContent":["'use client'\n\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { X } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/** Radix Dialog root, trigger and close — a sheet IS a dialog, docked. */\nexport const Sheet = DialogPrimitive.Root\nexport const SheetTrigger = DialogPrimitive.Trigger\nexport const SheetClose = DialogPrimitive.Close\n\nexport type SheetSide = 'start' | 'end' | 'top' | 'bottom'\n\n/**\n * Full literal class strings per edge, because Tailwind only generates what it\n * can see verbatim. The inline sides are named in READING order, so a sheet\n * docked to `end` comes from the right in English and the left in Arabic.\n */\nconst SIDE: Record<SheetSide, string> = {\n start:\n 'inset-y-0 start-0 h-full w-[min(24rem,92vw)] border-e data-[state=closed]:-translate-x-full rtl:data-[state=closed]:translate-x-full',\n end: 'inset-y-0 end-0 h-full w-[min(24rem,92vw)] border-s data-[state=closed]:translate-x-full rtl:data-[state=closed]:-translate-x-full',\n top: 'inset-x-0 top-0 w-full max-h-[85vh] border-b data-[state=closed]:-translate-y-full',\n bottom:\n 'inset-x-0 bottom-0 w-full max-h-[85vh] border-t data-[state=closed]:translate-y-full',\n}\n\nexport interface SheetContentProps\n extends Omit<ComponentProps<typeof DialogPrimitive.Content>, 'title'> {\n /** Which edge it is docked to. `end` by default. */\n side?: SheetSide\n title: ReactNode\n description?: ReactNode\n /** Hide the title visually while keeping it for assistive tech. */\n hideTitle?: boolean\n}\n\n/**\n * A panel docked to an edge of the viewport.\n *\n * It is a modal dialog — Radix's, so the focus trap, the escape key, the scroll\n * lock and the `aria-modal` wiring are the same ones `Dialog` gets. The only\n * differences are where it sits and which way it arrives, which is why this\n * shares that implementation rather than reproducing it: a second focus trap is\n * a second focus trap to get wrong.\n *\n * The title is required, visible or not. A modal with no accessible name drops\n * a screen reader into an unnamed region with no way back out.\n *\n * @example\n * <Sheet>\n * <SheetTrigger asChild><Button variant=\"secondary\">Filters</Button></SheetTrigger>\n * <SheetContent title=\"Filters\" description=\"Narrow the list.\">…</SheetContent>\n * </Sheet>\n */\nexport function SheetContent({\n side = 'end',\n title,\n description,\n hideTitle = false,\n className,\n children,\n ...rest\n}: SheetContentProps) {\n return (\n <DialogPrimitive.Portal>\n <DialogPrimitive.Overlay\n data-m22-animated\n className=\"fixed inset-0 z-(--z-overlay) bg-(--scrim) data-[state=open]:animate-[m22-fade-in_var(--duration-fast)_var(--ease)]\"\n />\n <DialogPrimitive.Content\n className={cn(\n 'fixed z-(--z-modal) flex flex-col overflow-y-auto border-(--panel-border) bg-(--panel-bg) p-6 panel-blur transition-transform duration-(--duration-slow) ease-(--ease-out-expo) scroll-slim',\n SIDE[side],\n className,\n )}\n {...rest}\n >\n <div className={cn('mb-4 flex flex-col gap-1 pe-8', hideTitle && 'sr-only')}>\n <DialogPrimitive.Title className=\"m-0 font-heading text-[length:var(--fs-item)] font-normal text-(--ink)\">\n {title}\n </DialogPrimitive.Title>\n {description !== undefined && (\n <DialogPrimitive.Description className=\"m-0 text-sm leading-relaxed text-(--ink-3-aa)\">\n {description}\n </DialogPrimitive.Description>\n )}\n </div>\n\n <DialogPrimitive.Close\n aria-label=\"Close\"\n className=\"absolute end-3 top-3 grid size-9 place-items-center rounded-(--radius-pill) text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)\"\n >\n <X size={16} strokeWidth={1.5} aria-hidden />\n </DialogPrimitive.Close>\n\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n )\n}\n\nexport default Sheet\n"],"mappings":";AAmEM,cAYE,YAZF;AAjEN,YAAY,qBAAqB;AACjC,SAAS,SAAS;AAElB,SAAS,UAAU;AAGZ,MAAM,QAAQ,gBAAgB;AAC9B,MAAM,eAAe,gBAAgB;AACrC,MAAM,aAAa,gBAAgB;AAS1C,MAAM,OAAkC;AAAA,EACtC,OACE;AAAA,EACF,KAAK;AAAA,EACL,KAAK;AAAA,EACL,QACE;AACJ;AA8BO,SAAS,aAAa;AAAA,EAC3B,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,SACE,qBAAC,gBAAgB,QAAhB,EACC;AAAA;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,qBAAiB;AAAA,QACjB,WAAU;AAAA;AAAA,IACZ;AAAA,IACA;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,KAAK,IAAI;AAAA,UACT;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QAEJ;AAAA,+BAAC,SAAI,WAAW,GAAG,iCAAiC,aAAa,SAAS,GACxE;AAAA,gCAAC,gBAAgB,OAAhB,EAAsB,WAAU,0EAC9B,iBACH;AAAA,YACC,gBAAgB,UACf,oBAAC,gBAAgB,aAAhB,EAA4B,WAAU,iDACpC,uBACH;AAAA,aAEJ;AAAA,UAEA;AAAA,YAAC,gBAAgB;AAAA,YAAhB;AAAA,cACC,cAAW;AAAA,cACX,WAAU;AAAA,cAEV,8BAAC,KAAE,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC;AAAA;AAAA,UAC7C;AAAA,UAEC;AAAA;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;AAEA,IAAO,gBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Sheet/Sheet.tsx"],"sourcesContent":["'use client'\n\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { X } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\n\n/** Radix Dialog root, trigger and close — a sheet IS a dialog, docked. */\nexport const Sheet = DialogPrimitive.Root\nexport const SheetTrigger = DialogPrimitive.Trigger\nexport const SheetClose = DialogPrimitive.Close\n\nexport type SheetSide = 'start' | 'end' | 'top' | 'bottom'\n\n/**\n * Full literal class strings per edge, because Tailwind only generates what it\n * can see verbatim. The inline sides are named in READING order, so a sheet\n * docked to `end` comes from the right in English and the left in Arabic.\n */\nconst SIDE: Record<SheetSide, string> = {\n start:\n 'inset-y-0 start-0 h-full w-[min(24rem,92vw)] border-e data-[state=closed]:-translate-x-full rtl:data-[state=closed]:translate-x-full',\n end: 'inset-y-0 end-0 h-full w-[min(24rem,92vw)] border-s data-[state=closed]:translate-x-full rtl:data-[state=closed]:-translate-x-full',\n top: 'inset-x-0 top-0 w-full max-h-[85vh] border-b data-[state=closed]:-translate-y-full',\n bottom:\n 'inset-x-0 bottom-0 w-full max-h-[85vh] border-t data-[state=closed]:translate-y-full',\n}\n\nexport interface SheetContentProps\n extends Omit<ComponentProps<typeof DialogPrimitive.Content>, 'title'> {\n /** Which edge it is docked to. `end` by default. */\n side?: SheetSide\n title: ReactNode\n description?: ReactNode\n /** Hide the title visually while keeping it for assistive tech. */\n hideTitle?: boolean\n}\n\n/**\n * A panel docked to an edge of the viewport.\n *\n * It is a modal dialog — Radix's, so the focus trap, the escape key, the scroll\n * lock and the `aria-modal` wiring are the same ones `Dialog` gets. The only\n * differences are where it sits and which way it arrives, which is why this\n * shares that implementation rather than reproducing it: a second focus trap is\n * a second focus trap to get wrong.\n *\n * The title is required, visible or not. A modal with no accessible name drops\n * a screen reader into an unnamed region with no way back out.\n *\n * Portals into the element an enclosing `OverlayContainer` names, docking to\n * that element's edge rather than the viewport's when there is one.\n *\n * @example\n * <Sheet>\n * <SheetTrigger asChild><Button variant=\"secondary\">Filters</Button></SheetTrigger>\n * <SheetContent title=\"Filters\" description=\"Narrow the list.\">…</SheetContent>\n * </Sheet>\n */\nexport function SheetContent({\n side = 'end',\n title,\n description,\n hideTitle = false,\n className,\n children,\n ...rest\n}: SheetContentProps) {\n const container = useOverlayContainer()\n\n return (\n <DialogPrimitive.Portal container={container ?? undefined}>\n <DialogPrimitive.Overlay\n data-m22-animated\n className={cn(\n 'inset-0 z-(--z-overlay) bg-(--scrim) data-[state=open]:animate-[m22-fade-in_var(--duration-fast)_var(--ease)]',\n container ? 'absolute' : 'fixed',\n )}\n />\n <DialogPrimitive.Content\n // The panel's own assertion that its travel is decorative — the scrim\n // beside it always made one and this half never did, so under reduced\n // motion the fade was cancelled and the panel still slid the whole\n // width of itself, which reads worse than leaving both alone. See the\n // reduced-motion block in keyframes.css for what the marker now means:\n // documentation with teeth, over a universal floor that no longer\n // depends on anyone remembering it.\n data-m22-animated\n className={cn(\n 'fixed z-(--z-modal) flex flex-col overflow-y-auto border-(--panel-border) bg-(--panel-bg) p-6 panel-blur transition-transform duration-(--duration-slow) ease-(--ease-out-expo) scroll-slim',\n SIDE[side],\n // Docked to the container's edge, and capped by its box — the widths\n // above are read against the viewport and would overflow a frame.\n container && 'absolute max-h-full max-w-full',\n className,\n )}\n {...rest}\n >\n <div className={cn('mb-4 flex flex-col gap-1 pe-8', hideTitle && 'sr-only')}>\n <DialogPrimitive.Title className=\"m-0 font-heading text-[length:var(--fs-item)] font-normal text-(--ink)\">\n {title}\n </DialogPrimitive.Title>\n {description !== undefined && (\n <DialogPrimitive.Description className=\"m-0 text-sm leading-relaxed text-(--ink-3-aa)\">\n {description}\n </DialogPrimitive.Description>\n )}\n </div>\n\n <DialogPrimitive.Close\n aria-label=\"Close\"\n className=\"absolute end-3 top-3 grid size-9 place-items-center rounded-(--radius-pill) text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)\"\n >\n <X size={16} strokeWidth={1.5} aria-hidden />\n </DialogPrimitive.Close>\n\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n )\n}\n\nexport default Sheet\n"],"mappings":";AAyEM,cA0BE,YA1BF;AAvEN,YAAY,qBAAqB;AACjC,SAAS,SAAS;AAElB,SAAS,UAAU;AACnB,SAAS,2BAA2B;AAG7B,MAAM,QAAQ,gBAAgB;AAC9B,MAAM,eAAe,gBAAgB;AACrC,MAAM,aAAa,gBAAgB;AAS1C,MAAM,OAAkC;AAAA,EACtC,OACE;AAAA,EACF,KAAK;AAAA,EACL,KAAK;AAAA,EACL,QACE;AACJ;AAiCO,SAAS,aAAa;AAAA,EAC3B,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,YAAY,oBAAoB;AAEtC,SACE,qBAAC,gBAAgB,QAAhB,EAAuB,WAAW,aAAa,QAC9C;AAAA;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,qBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA,YAAY,aAAa;AAAA,QAC3B;AAAA;AAAA,IACF;AAAA,IACA;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QAQC,qBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA,KAAK,IAAI;AAAA;AAAA;AAAA,UAGT,aAAa;AAAA,UACb;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QAEJ;AAAA,+BAAC,SAAI,WAAW,GAAG,iCAAiC,aAAa,SAAS,GACxE;AAAA,gCAAC,gBAAgB,OAAhB,EAAsB,WAAU,0EAC9B,iBACH;AAAA,YACC,gBAAgB,UACf,oBAAC,gBAAgB,aAAhB,EAA4B,WAAU,iDACpC,uBACH;AAAA,aAEJ;AAAA,UAEA;AAAA,YAAC,gBAAgB;AAAA,YAAhB;AAAA,cACC,cAAW;AAAA,cACX,WAAU;AAAA,cAEV,8BAAC,KAAE,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC;AAAA;AAAA,UAC7C;AAAA,UAEC;AAAA;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;AAEA,IAAO,gBAAQ;","names":[]}
@@ -3,13 +3,20 @@ import { HTMLAttributes, ReactNode } from 'react';
3
3
 
4
4
  type SkeletonProps = HTMLAttributes<HTMLDivElement>;
5
5
  /**
6
- * The fill every skeleton part is drawn from: `--stone`, and nothing else.
6
+ * The fill every skeleton part is drawn from: `--stone`, at the height of one
7
+ * line until something says otherwise.
7
8
  *
8
9
  * Deliberately NOT a shimmer. A shimmer sweeps a highlight across the shape,
9
10
  * which implies a light source; the White Reset has none, so the sweep reads as
10
11
  * a bug rather than as loading. The whole page breathes together instead —
11
12
  * see `SkeletonPage`, which owns the single pulse.
12
13
  *
14
+ * `h-3` is a default rather than a design. A `<div>` is already full width, so
15
+ * height was the one dimension a caller could omit and get nothing for — a
16
+ * bare `<Skeleton />` was a zero-height box, which is not a small skeleton but
17
+ * an absent one. Twelve pixels is the height `SkeletonLine` already chose, and
18
+ * any class the caller passes replaces it.
19
+ *
13
20
  * @example
14
21
  * <Skeleton className="h-3 w-40 rounded-(--radius-pill)" />
15
22
  */
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../../lib/cn.js";
3
3
  function Skeleton({ className, ...rest }) {
4
- return /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: cn("bg-(--stone)", className), ...rest });
4
+ return /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: cn("h-3 bg-(--stone)", className), ...rest });
5
5
  }
6
6
  function SkeletonLine({ className, ...rest }) {
7
7
  return /* @__PURE__ */ jsx(Skeleton, { className: cn("h-3 rounded-(--radius-pill)", className), ...rest });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Skeleton/Skeleton.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport type SkeletonProps = HTMLAttributes<HTMLDivElement>\n\n/**\n * The fill every skeleton part is drawn from: `--stone`, and nothing else.\n *\n * Deliberately NOT a shimmer. A shimmer sweeps a highlight across the shape,\n * which implies a light source; the White Reset has none, so the sweep reads as\n * a bug rather than as loading. The whole page breathes together instead —\n * see `SkeletonPage`, which owns the single pulse.\n *\n * @example\n * <Skeleton className=\"h-3 w-40 rounded-(--radius-pill)\" />\n */\nexport function Skeleton({ className, ...rest }: SkeletonProps) {\n return <div aria-hidden=\"true\" className={cn('bg-(--stone)', className)} {...rest} />\n}\n\n/** A line of type. Height is the line's; width is how far it runs. */\nexport function SkeletonLine({ className, ...rest }: SkeletonProps) {\n return <Skeleton className={cn('h-3 rounded-(--radius-pill)', className)} {...rest} />\n}\n\n/** A block: an image mount, a plate, a chart. */\nexport function SkeletonBlock({ className, ...rest }: SkeletonProps) {\n return <Skeleton className={cn('rounded-(--radius-sm)', className)} {...rest} />\n}\n\n/** A circle: an avatar, a marker. */\nexport function SkeletonCircle({ className, ...rest }: SkeletonProps) {\n return <Skeleton className={cn('size-9 rounded-full', className)} {...rest} />\n}\n\nexport interface SkeletonTextProps extends SkeletonProps {\n /** How many lines of prose to stand in for. */\n lines?: number\n}\n\n/**\n * A paragraph.\n *\n * The last line is short, because real prose ends mid-measure. A block of equal\n * full-width bars reads as a table, and the reader then flinches when a\n * paragraph arrives instead.\n */\nexport function SkeletonText({ lines = 3, className, ...rest }: SkeletonTextProps) {\n return (\n <div className={cn('flex flex-col gap-2.5', className)} {...rest}>\n {Array.from({ length: lines }, (_, index) => (\n <SkeletonLine key={index} className={index === lines - 1 ? 'w-[62%]' : 'w-full'} />\n ))}\n </div>\n )\n}\n\nexport interface SkeletonPageProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * What is loading, in words. Announced once; the shapes themselves are\n * `aria-hidden`, so this sentence is the entire experience for a screen\n * reader and \"Loading\" alone tells it nothing.\n */\n label: string\n children: ReactNode\n}\n\n/**\n * The frame a skeleton sits in: the live region, and the single pulse.\n *\n * One `animate-pulse` on the wrapper rather than one per part, so the page\n * breathes together instead of shimmering out of phase — twenty independently\n * animated bars is a visual noise floor, not a loading state.\n *\n * A skeleton whose shape does not match what replaces it is worse than none: it\n * promises a layout and then the page jumps out from under the reader. Compose\n * the parts to match the real thing, and change both together.\n *\n * @example\n * <SkeletonPage label=\"Loading projects\">\n * <SkeletonLine className=\"h-2.5 w-28\" />\n * <SkeletonBlock className=\"mt-6 h-40\" />\n * </SkeletonPage>\n */\nexport function SkeletonPage({ label, children, className, ...rest }: SkeletonPageProps) {\n return (\n <div\n role=\"status\"\n aria-busy=\"true\"\n data-m22-animated\n className={cn('animate-pulse motion-reduce:animate-none', className)}\n {...rest}\n >\n <span className=\"sr-only\">{label}</span>\n {children}\n </div>\n )\n}\n\nexport default Skeleton\n"],"mappings":"AAiBS,cAqEL,YArEK;AAhBT,SAAS,UAAU;AAeZ,SAAS,SAAS,EAAE,WAAW,GAAG,KAAK,GAAkB;AAC9D,SAAO,oBAAC,SAAI,eAAY,QAAO,WAAW,GAAG,gBAAgB,SAAS,GAAI,GAAG,MAAM;AACrF;AAGO,SAAS,aAAa,EAAE,WAAW,GAAG,KAAK,GAAkB;AAClE,SAAO,oBAAC,YAAS,WAAW,GAAG,+BAA+B,SAAS,GAAI,GAAG,MAAM;AACtF;AAGO,SAAS,cAAc,EAAE,WAAW,GAAG,KAAK,GAAkB;AACnE,SAAO,oBAAC,YAAS,WAAW,GAAG,yBAAyB,SAAS,GAAI,GAAG,MAAM;AAChF;AAGO,SAAS,eAAe,EAAE,WAAW,GAAG,KAAK,GAAkB;AACpE,SAAO,oBAAC,YAAS,WAAW,GAAG,uBAAuB,SAAS,GAAI,GAAG,MAAM;AAC9E;AAcO,SAAS,aAAa,EAAE,QAAQ,GAAG,WAAW,GAAG,KAAK,GAAsB;AACjF,SACE,oBAAC,SAAI,WAAW,GAAG,yBAAyB,SAAS,GAAI,GAAG,MACzD,gBAAM,KAAK,EAAE,QAAQ,MAAM,GAAG,CAAC,GAAG,UACjC,oBAAC,gBAAyB,WAAW,UAAU,QAAQ,IAAI,YAAY,YAApD,KAA8D,CAClF,GACH;AAEJ;AA6BO,SAAS,aAAa,EAAE,OAAO,UAAU,WAAW,GAAG,KAAK,GAAsB;AACvF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,aAAU;AAAA,MACV,qBAAiB;AAAA,MACjB,WAAW,GAAG,4CAA4C,SAAS;AAAA,MAClE,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,WAAU,WAAW,iBAAM;AAAA,QAChC;AAAA;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,mBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Skeleton/Skeleton.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport type SkeletonProps = HTMLAttributes<HTMLDivElement>\n\n/**\n * The fill every skeleton part is drawn from: `--stone`, at the height of one\n * line until something says otherwise.\n *\n * Deliberately NOT a shimmer. A shimmer sweeps a highlight across the shape,\n * which implies a light source; the White Reset has none, so the sweep reads as\n * a bug rather than as loading. The whole page breathes together instead —\n * see `SkeletonPage`, which owns the single pulse.\n *\n * `h-3` is a default rather than a design. A `<div>` is already full width, so\n * height was the one dimension a caller could omit and get nothing for — a\n * bare `<Skeleton />` was a zero-height box, which is not a small skeleton but\n * an absent one. Twelve pixels is the height `SkeletonLine` already chose, and\n * any class the caller passes replaces it.\n *\n * @example\n * <Skeleton className=\"h-3 w-40 rounded-(--radius-pill)\" />\n */\nexport function Skeleton({ className, ...rest }: SkeletonProps) {\n return <div aria-hidden=\"true\" className={cn('h-3 bg-(--stone)', className)} {...rest} />\n}\n\n/** A line of type. Height is the line's; width is how far it runs. */\nexport function SkeletonLine({ className, ...rest }: SkeletonProps) {\n return <Skeleton className={cn('h-3 rounded-(--radius-pill)', className)} {...rest} />\n}\n\n/** A block: an image mount, a plate, a chart. */\nexport function SkeletonBlock({ className, ...rest }: SkeletonProps) {\n return <Skeleton className={cn('rounded-(--radius-sm)', className)} {...rest} />\n}\n\n/** A circle: an avatar, a marker. */\nexport function SkeletonCircle({ className, ...rest }: SkeletonProps) {\n return <Skeleton className={cn('size-9 rounded-full', className)} {...rest} />\n}\n\nexport interface SkeletonTextProps extends SkeletonProps {\n /** How many lines of prose to stand in for. */\n lines?: number\n}\n\n/**\n * A paragraph.\n *\n * The last line is short, because real prose ends mid-measure. A block of equal\n * full-width bars reads as a table, and the reader then flinches when a\n * paragraph arrives instead.\n */\nexport function SkeletonText({ lines = 3, className, ...rest }: SkeletonTextProps) {\n return (\n <div className={cn('flex flex-col gap-2.5', className)} {...rest}>\n {Array.from({ length: lines }, (_, index) => (\n <SkeletonLine key={index} className={index === lines - 1 ? 'w-[62%]' : 'w-full'} />\n ))}\n </div>\n )\n}\n\nexport interface SkeletonPageProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * What is loading, in words. Announced once; the shapes themselves are\n * `aria-hidden`, so this sentence is the entire experience for a screen\n * reader and \"Loading\" alone tells it nothing.\n */\n label: string\n children: ReactNode\n}\n\n/**\n * The frame a skeleton sits in: the live region, and the single pulse.\n *\n * One `animate-pulse` on the wrapper rather than one per part, so the page\n * breathes together instead of shimmering out of phase — twenty independently\n * animated bars is a visual noise floor, not a loading state.\n *\n * A skeleton whose shape does not match what replaces it is worse than none: it\n * promises a layout and then the page jumps out from under the reader. Compose\n * the parts to match the real thing, and change both together.\n *\n * @example\n * <SkeletonPage label=\"Loading projects\">\n * <SkeletonLine className=\"h-2.5 w-28\" />\n * <SkeletonBlock className=\"mt-6 h-40\" />\n * </SkeletonPage>\n */\nexport function SkeletonPage({ label, children, className, ...rest }: SkeletonPageProps) {\n return (\n <div\n role=\"status\"\n aria-busy=\"true\"\n data-m22-animated\n className={cn('animate-pulse motion-reduce:animate-none', className)}\n {...rest}\n >\n <span className=\"sr-only\">{label}</span>\n {children}\n </div>\n )\n}\n\nexport default Skeleton\n"],"mappings":"AAwBS,cAqEL,YArEK;AAvBT,SAAS,UAAU;AAsBZ,SAAS,SAAS,EAAE,WAAW,GAAG,KAAK,GAAkB;AAC9D,SAAO,oBAAC,SAAI,eAAY,QAAO,WAAW,GAAG,oBAAoB,SAAS,GAAI,GAAG,MAAM;AACzF;AAGO,SAAS,aAAa,EAAE,WAAW,GAAG,KAAK,GAAkB;AAClE,SAAO,oBAAC,YAAS,WAAW,GAAG,+BAA+B,SAAS,GAAI,GAAG,MAAM;AACtF;AAGO,SAAS,cAAc,EAAE,WAAW,GAAG,KAAK,GAAkB;AACnE,SAAO,oBAAC,YAAS,WAAW,GAAG,yBAAyB,SAAS,GAAI,GAAG,MAAM;AAChF;AAGO,SAAS,eAAe,EAAE,WAAW,GAAG,KAAK,GAAkB;AACpE,SAAO,oBAAC,YAAS,WAAW,GAAG,uBAAuB,SAAS,GAAI,GAAG,MAAM;AAC9E;AAcO,SAAS,aAAa,EAAE,QAAQ,GAAG,WAAW,GAAG,KAAK,GAAsB;AACjF,SACE,oBAAC,SAAI,WAAW,GAAG,yBAAyB,SAAS,GAAI,GAAG,MACzD,gBAAM,KAAK,EAAE,QAAQ,MAAM,GAAG,CAAC,GAAG,UACjC,oBAAC,gBAAyB,WAAW,UAAU,QAAQ,IAAI,YAAY,YAApD,KAA8D,CAClF,GACH;AAEJ;AA6BO,SAAS,aAAa,EAAE,OAAO,UAAU,WAAW,GAAG,KAAK,GAAsB;AACvF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,aAAU;AAAA,MACV,qBAAiB;AAAA,MACjB,WAAW,GAAG,4CAA4C,SAAS;AAAA,MAClE,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,WAAU,WAAW,iBAAM;AAAA,QAChC;AAAA;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,mBAAQ;","names":[]}
@@ -10,9 +10,21 @@ interface SliderProps extends ComponentProps<typeof SliderPrimitive.Root> {
10
10
  * A range slider (two thumbs) needs one name per thumb — pass an array.
11
11
  */
12
12
  label: string | [string, string];
13
- /** Prints the current value beside the label. */
13
+ /**
14
+ * Prints the current value beside the label.
15
+ *
16
+ * A two-thumb range prints both names and both values, in the same order —
17
+ * the heading used to print the first name over a pair of numbers, which read
18
+ * as "Minimum" over "10 – 90".
19
+ */
14
20
  showValue?: boolean;
15
- /** Renders the value with a unit or a currency, e.g. `(n) => n + '%'`. */
21
+ /**
22
+ * Renders the value with a unit or a currency, e.g. `(n) => n + '%'`.
23
+ *
24
+ * Reaches assistive tech as well as the readout: it becomes each thumb's
25
+ * `aria-valuetext`, so a thumb showing "$1,200" announces that rather than the
26
+ * bare number. Left off, the platform announces the value itself.
27
+ */
16
28
  format?: (value: number) => string;
17
29
  }
18
30
  /**
@@ -27,9 +39,15 @@ interface SliderProps extends ComponentProps<typeof SliderPrimitive.Root> {
27
39
  * A 44px hit area sits invisibly around the 16px thumb, because a thumb sized
28
40
  * for the design is well under any pointer-target guideline.
29
41
  *
42
+ * Inside a `Field`, the hint, the error and the requirement land on the THUMB,
43
+ * which is the element carrying `role="slider"` — on the root they would sit on
44
+ * a `<span>` with no role and announce nothing. The NAME still comes from
45
+ * `label` here: a field's label above a slider points at that same roleless
46
+ * root, so it neither names the control nor clicks through to it.
47
+ *
30
48
  * @example
31
49
  * <Slider label="Quality" defaultValue={[80]} max={100} step={5} showValue format={(n) => `${n}%`} />
32
50
  */
33
- declare function Slider({ label, showValue, format, className, onValueChange, ...props }: SliderProps): react.JSX.Element;
51
+ declare function Slider({ label, showValue, format, className, onValueChange, 'aria-describedby': describedBy, 'aria-required': ariaRequired, 'aria-invalid': ariaInvalid, ...props }: SliderProps): react.JSX.Element;
34
52
 
35
53
  export { Slider, type SliderProps, Slider as default };
@@ -3,46 +3,73 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as SliderPrimitive from "@radix-ui/react-slider";
4
4
  import { useState } from "react";
5
5
  import { cn } from "../../lib/cn.js";
6
+ import { warnBlankName } from "../../lib/warn.js";
6
7
  function Slider({
7
8
  label,
8
9
  showValue = false,
9
- format = String,
10
+ format,
10
11
  className,
11
12
  onValueChange,
13
+ "aria-describedby": describedBy,
14
+ "aria-required": ariaRequired,
15
+ "aria-invalid": ariaInvalid,
12
16
  ...props
13
17
  }) {
14
18
  const names = Array.isArray(label) ? label : [label];
15
- const [uncontrolled, setUncontrolled] = useState(props.defaultValue ?? []);
19
+ warnBlankName("Slider", "label", names[0], "the thumb is announced with no name and its value describes nothing");
20
+ const [uncontrolled, setUncontrolled] = useState(
21
+ props.defaultValue ?? [props.min ?? 0]
22
+ );
16
23
  const current = props.value ?? uncontrolled;
17
24
  const handleChange = (next) => {
18
25
  if (props.value === void 0) setUncontrolled(next);
19
26
  onValueChange?.(next);
20
27
  };
21
- return /* @__PURE__ */ jsxs("div", { className: cn("flex w-full flex-col gap-3", className), children: [
22
- showValue && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between mono-meta text-(--ink-3-aa)", children: [
23
- /* @__PURE__ */ jsx("span", { children: names[0] }),
24
- /* @__PURE__ */ jsx("span", { className: "tabular-nums text-(--ink)", children: current.map((value) => format(value)).join(" \u2013 ") })
25
- ] }),
26
- /* @__PURE__ */ jsxs(
27
- SliderPrimitive.Root,
28
- {
29
- className: "group relative flex w-full touch-none select-none items-center py-3",
30
- onValueChange: handleChange,
31
- ...props,
32
- children: [
33
- /* @__PURE__ */ jsx(SliderPrimitive.Track, { className: "relative h-1 w-full grow overflow-hidden rounded-(--radius-pill) bg-(--stone) transition-[height] duration-(--duration-fast) group-hover:h-1.5", children: /* @__PURE__ */ jsx(SliderPrimitive.Range, { className: "absolute h-full bg-(--accent)" }) }),
34
- current.map((_, index) => /* @__PURE__ */ jsx(
35
- SliderPrimitive.Thumb,
36
- {
37
- "aria-label": names[index] ?? names[0],
38
- className: "relative block size-4 rounded-(--radius-pill) border border-(--accent) bg-(--paper) transition-[transform,background-color] duration-(--duration-fast) ease-(--ease-out-expo) before:absolute before:left-1/2 before:top-1/2 before:size-11 before:-translate-x-1/2 before:-translate-y-1/2 before:content-[''] hover:scale-110 hover:bg-(--stone) active:scale-125 data-[state=active]:scale-125 disabled:opacity-(--disabled-opacity) motion-reduce:transition-none"
39
- },
40
- index
41
- ))
42
- ]
43
- }
44
- )
45
- ] });
28
+ const print = format ?? String;
29
+ const heading = names.length > 1 ? current.map((_, index) => names[index] ?? names[0]).join(" \u2013 ") : names[0];
30
+ return /* @__PURE__ */ jsxs(
31
+ "div",
32
+ {
33
+ className: cn(
34
+ "flex w-full flex-col gap-3",
35
+ // On the wrapper, and driven by the prop: the thumb is a <span>, so a
36
+ // `disabled:` variant compiles to `&:disabled` and matches nothing —
37
+ // the control was drawn exactly like a live one while refusing to move.
38
+ // The wrapper is also what dims the readout above the track.
39
+ props.disabled && "opacity-(--disabled-opacity) pointer-events-none",
40
+ className
41
+ ),
42
+ children: [
43
+ showValue && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between mono-meta text-(--ink-3-aa)", children: [
44
+ /* @__PURE__ */ jsx("span", { children: heading }),
45
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums text-(--ink)", children: current.map((value) => print(value)).join(" \u2013 ") })
46
+ ] }),
47
+ /* @__PURE__ */ jsxs(
48
+ SliderPrimitive.Root,
49
+ {
50
+ className: "group relative flex w-full touch-none select-none items-center py-3",
51
+ onValueChange: handleChange,
52
+ ...props,
53
+ children: [
54
+ /* @__PURE__ */ jsx(SliderPrimitive.Track, { className: "relative h-1 w-full grow overflow-hidden rounded-(--radius-pill) bg-(--stone) transition-[height] duration-(--duration-fast) group-hover:h-1.5", children: /* @__PURE__ */ jsx(SliderPrimitive.Range, { className: "absolute h-full bg-(--accent)" }) }),
55
+ current.map((value, index) => /* @__PURE__ */ jsx(
56
+ SliderPrimitive.Thumb,
57
+ {
58
+ "aria-label": names[index] ?? names[0],
59
+ "aria-describedby": describedBy,
60
+ "aria-required": ariaRequired,
61
+ "aria-invalid": ariaInvalid,
62
+ "aria-valuetext": format ? format(value) : void 0,
63
+ className: "relative block size-4 rounded-(--radius-pill) border border-(--accent) bg-(--paper) transition-[transform,background-color] duration-(--duration-fast) ease-(--ease-out-expo) before:absolute before:left-1/2 before:top-1/2 before:size-11 before:-translate-x-1/2 before:-translate-y-1/2 before:content-[''] hover:scale-110 hover:bg-(--stone) active:scale-125 data-[state=active]:scale-125 motion-reduce:transition-none"
64
+ },
65
+ index
66
+ ))
67
+ ]
68
+ }
69
+ )
70
+ ]
71
+ }
72
+ );
46
73
  }
47
74
  var Slider_default = Slider;
48
75
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Slider/Slider.tsx"],"sourcesContent":["'use client'\n\nimport * as SliderPrimitive from '@radix-ui/react-slider'\nimport { useState, type ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface SliderProps extends ComponentProps<typeof SliderPrimitive.Root> {\n /**\n * Names the control. Required: a slider with no name announces only a number,\n * and a number with no noun is not information.\n *\n * A range slider (two thumbs) needs one name per thumb — pass an array.\n */\n label: string | [string, string]\n /** Prints the current value beside the label. */\n showValue?: boolean\n /** Renders the value with a unit or a currency, e.g. `(n) => n + '%'`. */\n format?: (value: number) => string\n}\n\n/**\n * A value chosen along a range.\n *\n * Radix owns the keyboard contract — arrows step, Page keys jump, Home and End\n * reach the ends — and the ARIA that reports the value. What is here is the\n * look, and the labelling, which is the part a slider most often gets wrong: a\n * thumb that announces \"42\" and nothing else leaves a screen reader user with a\n * number and no idea what it measures.\n *\n * A 44px hit area sits invisibly around the 16px thumb, because a thumb sized\n * for the design is well under any pointer-target guideline.\n *\n * @example\n * <Slider label=\"Quality\" defaultValue={[80]} max={100} step={5} showValue format={(n) => `${n}%`} />\n */\nexport function Slider({\n label,\n showValue = false,\n format = String,\n className,\n onValueChange,\n ...props\n}: SliderProps) {\n const names = Array.isArray(label) ? label : [label]\n\n // Tracked here, not read off the props. An uncontrolled slider's\n // `defaultValue` never changes, so the printed figure sat at its starting\n // number while the thumb moved — the one thing `showValue` exists to avoid.\n const [uncontrolled, setUncontrolled] = useState<number[]>(props.defaultValue ?? [])\n const current = props.value ?? uncontrolled\n\n const handleChange = (next: number[]) => {\n if (props.value === undefined) setUncontrolled(next)\n onValueChange?.(next)\n }\n\n return (\n <div className={cn('flex w-full flex-col gap-3', className)}>\n {showValue && (\n <div className=\"flex items-baseline justify-between mono-meta text-(--ink-3-aa)\">\n <span>{names[0]}</span>\n <span className=\"tabular-nums text-(--ink)\">\n {current.map((value) => format(value)).join(' – ')}\n </span>\n </div>\n )}\n <SliderPrimitive.Root\n className=\"group relative flex w-full touch-none select-none items-center py-3\"\n onValueChange={handleChange}\n {...props}\n >\n <SliderPrimitive.Track className=\"relative h-1 w-full grow overflow-hidden rounded-(--radius-pill) bg-(--stone) transition-[height] duration-(--duration-fast) group-hover:h-1.5\">\n <SliderPrimitive.Range className=\"absolute h-full bg-(--accent)\" />\n </SliderPrimitive.Track>\n {current.map((_, index) => (\n <SliderPrimitive.Thumb\n key={index}\n aria-label={names[index] ?? names[0]}\n // The visible thumb is 16px; the `before` pseudo-element widens the\n // hit area to 44px without changing what is drawn.\n // `transition-transform` and not `all`: the thumb's own position is\n // set by Radix as a `left` percentage, and transitioning that would\n // make it lag the pointer. Only the grow-on-grab is animated.\n className=\"relative block size-4 rounded-(--radius-pill) border border-(--accent) bg-(--paper) transition-[transform,background-color] duration-(--duration-fast) ease-(--ease-out-expo) before:absolute before:left-1/2 before:top-1/2 before:size-11 before:-translate-x-1/2 before:-translate-y-1/2 before:content-[''] hover:scale-110 hover:bg-(--stone) active:scale-125 data-[state=active]:scale-125 disabled:opacity-(--disabled-opacity) motion-reduce:transition-none\"\n />\n ))}\n </SliderPrimitive.Root>\n </div>\n )\n}\n\nexport default Slider\n"],"mappings":";AA2DQ,SACE,KADF;AAzDR,YAAY,qBAAqB;AACjC,SAAS,gBAAqC;AAC9C,SAAS,UAAU;AA+BZ,SAAS,OAAO;AAAA,EACrB;AAAA,EACA,YAAY;AAAA,EACZ,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAgB;AACd,QAAM,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AAKnD,QAAM,CAAC,cAAc,eAAe,IAAI,SAAmB,MAAM,gBAAgB,CAAC,CAAC;AACnF,QAAM,UAAU,MAAM,SAAS;AAE/B,QAAM,eAAe,CAAC,SAAmB;AACvC,QAAI,MAAM,UAAU,OAAW,iBAAgB,IAAI;AACnD,oBAAgB,IAAI;AAAA,EACtB;AAEA,SACE,qBAAC,SAAI,WAAW,GAAG,8BAA8B,SAAS,GACvD;AAAA,iBACC,qBAAC,SAAI,WAAU,mEACb;AAAA,0BAAC,UAAM,gBAAM,CAAC,GAAE;AAAA,MAChB,oBAAC,UAAK,WAAU,6BACb,kBAAQ,IAAI,CAAC,UAAU,OAAO,KAAK,CAAC,EAAE,KAAK,UAAK,GACnD;AAAA,OACF;AAAA,IAEF;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,WAAU;AAAA,QACV,eAAe;AAAA,QACd,GAAG;AAAA,QAEJ;AAAA,8BAAC,gBAAgB,OAAhB,EAAsB,WAAU,kJAC/B,8BAAC,gBAAgB,OAAhB,EAAsB,WAAU,iCAAgC,GACnE;AAAA,UACC,QAAQ,IAAI,CAAC,GAAG,UACf;AAAA,YAAC,gBAAgB;AAAA,YAAhB;AAAA,cAEC,cAAY,MAAM,KAAK,KAAK,MAAM,CAAC;AAAA,cAMnC,WAAU;AAAA;AAAA,YAPL;AAAA,UAQP,CACD;AAAA;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;AAEA,IAAO,iBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Slider/Slider.tsx"],"sourcesContent":["'use client'\n\nimport * as SliderPrimitive from '@radix-ui/react-slider'\nimport { useState, type ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\nimport { warnBlankName } from '../../lib/warn'\n\nexport interface SliderProps extends ComponentProps<typeof SliderPrimitive.Root> {\n /**\n * Names the control. Required: a slider with no name announces only a number,\n * and a number with no noun is not information.\n *\n * A range slider (two thumbs) needs one name per thumb — pass an array.\n */\n label: string | [string, string]\n /**\n * Prints the current value beside the label.\n *\n * A two-thumb range prints both names and both values, in the same order —\n * the heading used to print the first name over a pair of numbers, which read\n * as \"Minimum\" over \"10 – 90\".\n */\n showValue?: boolean\n /**\n * Renders the value with a unit or a currency, e.g. `(n) => n + '%'`.\n *\n * Reaches assistive tech as well as the readout: it becomes each thumb's\n * `aria-valuetext`, so a thumb showing \"$1,200\" announces that rather than the\n * bare number. Left off, the platform announces the value itself.\n */\n format?: (value: number) => string\n}\n\n/**\n * A value chosen along a range.\n *\n * Radix owns the keyboard contract — arrows step, Page keys jump, Home and End\n * reach the ends — and the ARIA that reports the value. What is here is the\n * look, and the labelling, which is the part a slider most often gets wrong: a\n * thumb that announces \"42\" and nothing else leaves a screen reader user with a\n * number and no idea what it measures.\n *\n * A 44px hit area sits invisibly around the 16px thumb, because a thumb sized\n * for the design is well under any pointer-target guideline.\n *\n * Inside a `Field`, the hint, the error and the requirement land on the THUMB,\n * which is the element carrying `role=\"slider\"` — on the root they would sit on\n * a `<span>` with no role and announce nothing. The NAME still comes from\n * `label` here: a field's label above a slider points at that same roleless\n * root, so it neither names the control nor clicks through to it.\n *\n * @example\n * <Slider label=\"Quality\" defaultValue={[80]} max={100} step={5} showValue format={(n) => `${n}%`} />\n */\nexport function Slider({\n label,\n showValue = false,\n format,\n className,\n onValueChange,\n 'aria-describedby': describedBy,\n 'aria-required': ariaRequired,\n 'aria-invalid': ariaInvalid,\n ...props\n}: SliderProps) {\n const names = Array.isArray(label) ? label : [label]\n\n // The other five guarded naming props are plain strings; this one is a\n // string or a pair, so the first name is what is checked — a range whose\n // lower thumb has no name is the same silence as a single one with none.\n // It catches a blank, not an absent: `label` is required, so a missing one\n // is a type error everywhere except a caller that has already left the\n // types behind.\n warnBlankName('Slider', 'label', names[0], 'the thumb is announced with no name and its value describes nothing')\n\n // Tracked here, not read off the props. An uncontrolled slider's\n // `defaultValue` never changes, so the printed figure sat at its starting\n // number while the thumb moved — the one thing `showValue` exists to avoid.\n //\n // The fallback is the primitive's own — one thumb at the minimum. The thumbs\n // are rendered from THIS array rather than from Radix's, so an empty one is a\n // track with nothing on it to drag, which is what `<Slider label=\"Volume\" />`\n // used to render.\n const [uncontrolled, setUncontrolled] = useState<number[]>(\n props.defaultValue ?? [props.min ?? 0],\n )\n const current = props.value ?? uncontrolled\n\n const handleChange = (next: number[]) => {\n if (props.value === undefined) setUncontrolled(next)\n onValueChange?.(next)\n }\n\n const print = format ?? String\n // One name per thumb, in the thumbs' own order, so the heading lines up with\n // the numbers under it. A single name stays a single name however many thumbs\n // there are — a price filter called \"Price\" is not \"Price – Price\".\n const heading =\n names.length > 1 ? current.map((_, index) => names[index] ?? names[0]).join(' – ') : names[0]\n\n return (\n <div\n className={cn(\n 'flex w-full flex-col gap-3',\n // On the wrapper, and driven by the prop: the thumb is a <span>, so a\n // `disabled:` variant compiles to `&:disabled` and matches nothing —\n // the control was drawn exactly like a live one while refusing to move.\n // The wrapper is also what dims the readout above the track.\n props.disabled && 'opacity-(--disabled-opacity) pointer-events-none',\n className,\n )}\n >\n {showValue && (\n <div className=\"flex items-baseline justify-between mono-meta text-(--ink-3-aa)\">\n <span>{heading}</span>\n <span className=\"tabular-nums text-(--ink)\">\n {current.map((value) => print(value)).join(' – ')}\n </span>\n </div>\n )}\n <SliderPrimitive.Root\n className=\"group relative flex w-full touch-none select-none items-center py-3\"\n onValueChange={handleChange}\n {...props}\n >\n <SliderPrimitive.Track className=\"relative h-1 w-full grow overflow-hidden rounded-(--radius-pill) bg-(--stone) transition-[height] duration-(--duration-fast) group-hover:h-1.5\">\n <SliderPrimitive.Range className=\"absolute h-full bg-(--accent)\" />\n </SliderPrimitive.Track>\n {current.map((value, index) => (\n <SliderPrimitive.Thumb\n key={index}\n aria-label={names[index] ?? names[0]}\n // The thumb is what carries role=\"slider\", so this is where a\n // Field's description and requirement have to land — on the root\n // they sit on a <span> with no role, which announces nothing.\n aria-describedby={describedBy}\n aria-required={ariaRequired}\n aria-invalid={ariaInvalid}\n // Only when a format was given: setting it otherwise would replace\n // the platform's own reading of the value with the same digits.\n aria-valuetext={format ? format(value) : undefined}\n // The visible thumb is 16px; the `before` pseudo-element widens the\n // hit area to 44px without changing what is drawn.\n // `transition-transform` and not `all`: the thumb's own position is\n // set by Radix as a `left` percentage, and transitioning that would\n // make it lag the pointer. Only the grow-on-grab is animated.\n className=\"relative block size-4 rounded-(--radius-pill) border border-(--accent) bg-(--paper) transition-[transform,background-color] duration-(--duration-fast) ease-(--ease-out-expo) before:absolute before:left-1/2 before:top-1/2 before:size-11 before:-translate-x-1/2 before:-translate-y-1/2 before:content-[''] hover:scale-110 hover:bg-(--stone) active:scale-125 data-[state=active]:scale-125 motion-reduce:transition-none\"\n />\n ))}\n </SliderPrimitive.Root>\n </div>\n )\n}\n\nexport default Slider\n"],"mappings":";AAiHQ,SACE,KADF;AA/GR,YAAY,qBAAqB;AACjC,SAAS,gBAAqC;AAC9C,SAAS,UAAU;AACnB,SAAS,qBAAqB;AAiDvB,SAAS,OAAO;AAAA,EACrB;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA,oBAAoB;AAAA,EACpB,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,GAAG;AACL,GAAgB;AACd,QAAM,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AAQnD,gBAAc,UAAU,SAAS,MAAM,CAAC,GAAG,qEAAqE;AAUhH,QAAM,CAAC,cAAc,eAAe,IAAI;AAAA,IACtC,MAAM,gBAAgB,CAAC,MAAM,OAAO,CAAC;AAAA,EACvC;AACA,QAAM,UAAU,MAAM,SAAS;AAE/B,QAAM,eAAe,CAAC,SAAmB;AACvC,QAAI,MAAM,UAAU,OAAW,iBAAgB,IAAI;AACnD,oBAAgB,IAAI;AAAA,EACtB;AAEA,QAAM,QAAQ,UAAU;AAIxB,QAAM,UACJ,MAAM,SAAS,IAAI,QAAQ,IAAI,CAAC,GAAG,UAAU,MAAM,KAAK,KAAK,MAAM,CAAC,CAAC,EAAE,KAAK,UAAK,IAAI,MAAM,CAAC;AAE9F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA;AAAA;AAAA;AAAA;AAAA,QAKA,MAAM,YAAY;AAAA,QAClB;AAAA,MACF;AAAA,MAEC;AAAA,qBACC,qBAAC,SAAI,WAAU,mEACb;AAAA,8BAAC,UAAM,mBAAQ;AAAA,UACf,oBAAC,UAAK,WAAU,6BACb,kBAAQ,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC,EAAE,KAAK,UAAK,GAClD;AAAA,WACF;AAAA,QAEF;AAAA,UAAC,gBAAgB;AAAA,UAAhB;AAAA,YACC,WAAU;AAAA,YACV,eAAe;AAAA,YACd,GAAG;AAAA,YAEJ;AAAA,kCAAC,gBAAgB,OAAhB,EAAsB,WAAU,kJAC/B,8BAAC,gBAAgB,OAAhB,EAAsB,WAAU,iCAAgC,GACnE;AAAA,cACC,QAAQ,IAAI,CAAC,OAAO,UACnB;AAAA,gBAAC,gBAAgB;AAAA,gBAAhB;AAAA,kBAEC,cAAY,MAAM,KAAK,KAAK,MAAM,CAAC;AAAA,kBAInC,oBAAkB;AAAA,kBAClB,iBAAe;AAAA,kBACf,gBAAc;AAAA,kBAGd,kBAAgB,SAAS,OAAO,KAAK,IAAI;AAAA,kBAMzC,WAAU;AAAA;AAAA,gBAhBL;AAAA,cAiBP,CACD;AAAA;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EACF;AAEJ;AAEA,IAAO,iBAAQ;","names":[]}
@@ -11,6 +11,8 @@ interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {
11
11
  * ink ground an ink ring is invisible.
12
12
  */
13
13
  tone?: 'default' | 'current';
14
+ /** Merged onto the RING, after `size` and `tone`, so it overrides both. */
15
+ className?: string;
14
16
  /**
15
17
  * Announced to assistive tech. Pass the specific thing being waited on
16
18
  * ("Loading projects"), not the generic word — a screen reader user hearing
@@ -39,6 +41,12 @@ interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {
39
41
  * page is broken, prefer `Skeleton` — a shape that describes what is coming
40
42
  * beats a dot that describes nothing.
41
43
  *
44
+ * `className` reaches the RING, alongside `size` and `tone`, and overrides
45
+ * them: every utility a caller has for a spinner is about the ring, and merged
46
+ * onto the wrapper instead `className="size-8"` grew an invisible box around an
47
+ * unchanged 18px circle. Layout still works from there — the wrapper is
48
+ * `inline-flex` and takes the ring's margin box as its own.
49
+ *
42
50
  * @example
43
51
  * <Spinner size="lg" label="Loading projects" />
44
52
  * @example
@@ -21,7 +21,7 @@ function Spinner({
21
21
  {
22
22
  role: label === null ? void 0 : "status",
23
23
  "aria-hidden": label === null ? true : void 0,
24
- className: cn("inline-flex shrink-0 items-center justify-center", className),
24
+ className: "inline-flex shrink-0 items-center justify-center",
25
25
  ...rest,
26
26
  children: [
27
27
  /* @__PURE__ */ jsx(
@@ -31,7 +31,8 @@ function Spinner({
31
31
  className: cn(
32
32
  "inline-block rounded-full border-solid motion-safe:animate-[m22-spin_0.7s_linear_infinite]",
33
33
  SIZE[size],
34
- TONE[tone]
34
+ TONE[tone],
35
+ className
35
36
  )
36
37
  }
37
38
  ),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Spinner/Spinner.tsx"],"sourcesContent":["import type { HTMLAttributes } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {\n /** 14px / 18px / 26px. Match the size of the type it sits beside. */\n size?: 'sm' | 'md' | 'lg'\n /**\n * `default` draws the ring against the page — a hairline track in the rule\n * colour with the leading quarter in ink. `current` draws it in the inherited\n * text colour, which is what a spinner inside a filled button needs: on an\n * ink ground an ink ring is invisible.\n */\n tone?: 'default' | 'current'\n /**\n * Announced to assistive tech. Pass the specific thing being waited on\n * (\"Loading projects\"), not the generic word — a screen reader user hearing\n * \"Loading\" three times cannot tell which three things.\n *\n * Pass `null` for a spinner that sits inside a control which already names\n * the operation (a button whose own label changes to \"Saving…\"), so the two\n * are not read out twice.\n */\n label?: string | null\n}\n\nconst SIZE = {\n sm: 'size-3.5 border-[1.5px]',\n md: 'size-[18px] border-2',\n lg: 'size-6.5 border-2',\n} as const\n\nconst TONE = {\n default: 'border-(--rule-2) border-t-(--ink)',\n current: 'border-current/25 border-t-current',\n} as const\n\n/**\n * The system's one \"working\" indicator.\n *\n * A ring, not a blurred glow and not a shimmer: the White Reset has no light\n * source, so depth and softness are not available to it. The leading quarter is\n * the only thing that distinguishes the ring from a plain circle, which is why\n * the track stays a hairline — a spinner is a hint that time is passing, not a\n * feature of the page.\n *\n * It spins under `motion-safe` only. A reader who has asked for less motion\n * gets a static ring, which still reads as \"not finished\" because the leading\n * quarter is darker than the track.\n *\n * For a wait long enough that the reader would otherwise wonder whether the\n * page is broken, prefer `Skeleton` — a shape that describes what is coming\n * beats a dot that describes nothing.\n *\n * @example\n * <Spinner size=\"lg\" label=\"Loading projects\" />\n * @example\n * // Inside a filled button, where the ground is ink:\n * <Spinner size=\"sm\" tone=\"current\" label={null} />\n */\nexport function Spinner({\n size = 'md',\n tone = 'default',\n className,\n label = 'Loading',\n ...rest\n}: SpinnerProps) {\n return (\n <span\n role={label === null ? undefined : 'status'}\n aria-hidden={label === null ? true : undefined}\n className={cn('inline-flex shrink-0 items-center justify-center', className)}\n {...rest}\n >\n <span\n data-m22-animated\n className={cn(\n 'inline-block rounded-full border-solid motion-safe:animate-[m22-spin_0.7s_linear_infinite]',\n SIZE[size],\n TONE[tone],\n )}\n />\n {label !== null && <span className=\"sr-only\">{label}</span>}\n </span>\n )\n}\n\nexport default Spinner\n"],"mappings":"AAmEI,SAME,KANF;AAlEJ,SAAS,UAAU;AAwBnB,MAAM,OAAO;AAAA,EACX,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,OAAO;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AACX;AAyBO,SAAS,QAAQ;AAAA,EACtB,OAAO;AAAA,EACP,OAAO;AAAA,EACP;AAAA,EACA,QAAQ;AAAA,EACR,GAAG;AACL,GAAiB;AACf,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,UAAU,OAAO,SAAY;AAAA,MACnC,eAAa,UAAU,OAAO,OAAO;AAAA,MACrC,WAAW,GAAG,oDAAoD,SAAS;AAAA,MAC1E,GAAG;AAAA,MAEJ;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,qBAAiB;AAAA,YACjB,WAAW;AAAA,cACT;AAAA,cACA,KAAK,IAAI;AAAA,cACT,KAAK,IAAI;AAAA,YACX;AAAA;AAAA,QACF;AAAA,QACC,UAAU,QAAQ,oBAAC,UAAK,WAAU,WAAW,iBAAM;AAAA;AAAA;AAAA,EACtD;AAEJ;AAEA,IAAO,kBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Spinner/Spinner.tsx"],"sourcesContent":["import type { HTMLAttributes } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {\n /** 14px / 18px / 26px. Match the size of the type it sits beside. */\n size?: 'sm' | 'md' | 'lg'\n /**\n * `default` draws the ring against the page — a hairline track in the rule\n * colour with the leading quarter in ink. `current` draws it in the inherited\n * text colour, which is what a spinner inside a filled button needs: on an\n * ink ground an ink ring is invisible.\n */\n tone?: 'default' | 'current'\n /** Merged onto the RING, after `size` and `tone`, so it overrides both. */\n className?: string\n /**\n * Announced to assistive tech. Pass the specific thing being waited on\n * (\"Loading projects\"), not the generic word — a screen reader user hearing\n * \"Loading\" three times cannot tell which three things.\n *\n * Pass `null` for a spinner that sits inside a control which already names\n * the operation (a button whose own label changes to \"Saving…\"), so the two\n * are not read out twice.\n */\n label?: string | null\n}\n\nconst SIZE = {\n sm: 'size-3.5 border-[1.5px]',\n md: 'size-[18px] border-2',\n lg: 'size-6.5 border-2',\n} as const\n\nconst TONE = {\n default: 'border-(--rule-2) border-t-(--ink)',\n current: 'border-current/25 border-t-current',\n} as const\n\n/**\n * The system's one \"working\" indicator.\n *\n * A ring, not a blurred glow and not a shimmer: the White Reset has no light\n * source, so depth and softness are not available to it. The leading quarter is\n * the only thing that distinguishes the ring from a plain circle, which is why\n * the track stays a hairline — a spinner is a hint that time is passing, not a\n * feature of the page.\n *\n * It spins under `motion-safe` only. A reader who has asked for less motion\n * gets a static ring, which still reads as \"not finished\" because the leading\n * quarter is darker than the track.\n *\n * For a wait long enough that the reader would otherwise wonder whether the\n * page is broken, prefer `Skeleton` — a shape that describes what is coming\n * beats a dot that describes nothing.\n *\n * `className` reaches the RING, alongside `size` and `tone`, and overrides\n * them: every utility a caller has for a spinner is about the ring, and merged\n * onto the wrapper instead `className=\"size-8\"` grew an invisible box around an\n * unchanged 18px circle. Layout still works from there — the wrapper is\n * `inline-flex` and takes the ring's margin box as its own.\n *\n * @example\n * <Spinner size=\"lg\" label=\"Loading projects\" />\n * @example\n * // Inside a filled button, where the ground is ink:\n * <Spinner size=\"sm\" tone=\"current\" label={null} />\n */\nexport function Spinner({\n size = 'md',\n tone = 'default',\n className,\n label = 'Loading',\n ...rest\n}: SpinnerProps) {\n return (\n <span\n role={label === null ? undefined : 'status'}\n aria-hidden={label === null ? true : undefined}\n className=\"inline-flex shrink-0 items-center justify-center\"\n {...rest}\n >\n <span\n data-m22-animated\n className={cn(\n 'inline-block rounded-full border-solid motion-safe:animate-[m22-spin_0.7s_linear_infinite]',\n SIZE[size],\n TONE[tone],\n className,\n )}\n />\n {label !== null && <span className=\"sr-only\">{label}</span>}\n </span>\n )\n}\n\nexport default Spinner\n"],"mappings":"AA2EI,SAME,KANF;AA1EJ,SAAS,UAAU;AA0BnB,MAAM,OAAO;AAAA,EACX,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,OAAO;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AACX;AA+BO,SAAS,QAAQ;AAAA,EACtB,OAAO;AAAA,EACP,OAAO;AAAA,EACP;AAAA,EACA,QAAQ;AAAA,EACR,GAAG;AACL,GAAiB;AACf,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,UAAU,OAAO,SAAY;AAAA,MACnC,eAAa,UAAU,OAAO,OAAO;AAAA,MACrC,WAAU;AAAA,MACT,GAAG;AAAA,MAEJ;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,qBAAiB;AAAA,YACjB,WAAW;AAAA,cACT;AAAA,cACA,KAAK,IAAI;AAAA,cACT,KAAK,IAAI;AAAA,cACT;AAAA,YACF;AAAA;AAAA,QACF;AAAA,QACC,UAAU,QAAQ,oBAAC,UAAK,WAAU,WAAW,iBAAM;AAAA;AAAA;AAAA,EACtD;AAEJ;AAEA,IAAO,kBAAQ;","names":[]}
@@ -15,6 +15,11 @@ interface StatusPillProps extends HTMLAttributes<HTMLSpanElement> {
15
15
  * how the same "available for work" chip ended up with three different dot
16
16
  * sizes and two different pulse timings on one site.
17
17
  *
18
+ * A warning or danger tone is doubled by a visually-hidden severity word, so
19
+ * the tone survives the dot being hidden. It does not survive monochrome: the
20
+ * pill's own text is `--ink-2` at every tone, and the state itself still
21
+ * belongs in the words the call site writes.
22
+ *
18
23
  * @example
19
24
  * <StatusPill>Available for work</StatusPill>
20
25
  * @example
@@ -1,6 +1,10 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../../lib/cn.js";
3
3
  import { StatusDot } from "../StatusDot/StatusDot.js";
4
+ const SEVERITY = {
5
+ warning: "Warning",
6
+ danger: "Error"
7
+ };
4
8
  function StatusPill({
5
9
  children,
6
10
  tone = "success",
@@ -8,6 +12,7 @@ function StatusPill({
8
12
  className,
9
13
  ...rest
10
14
  }) {
15
+ const severity = SEVERITY[tone];
11
16
  return /* @__PURE__ */ jsxs(
12
17
  "span",
13
18
  {
@@ -18,6 +23,7 @@ function StatusPill({
18
23
  ...rest,
19
24
  children: [
20
25
  /* @__PURE__ */ jsx(StatusDot, { tone, pulse }),
26
+ severity && /* @__PURE__ */ jsx("span", { className: "sr-only", children: severity }),
21
27
  children
22
28
  ]
23
29
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/StatusPill/StatusPill.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { StatusDot, type StatusTone } from '../StatusDot/StatusDot'\n\nexport interface StatusPillProps extends HTMLAttributes<HTMLSpanElement> {\n children: ReactNode\n tone?: StatusTone\n /** Pulsing live dot (default) vs a settled one. */\n pulse?: boolean\n}\n\n/**\n * A live state, named: a dot plus an uppercase mono label in an outlined pill.\n *\n * One component rather than a dot and a pill assembled per call site, which is\n * how the same \"available for work\" chip ended up with three different dot\n * sizes and two different pulse timings on one site.\n *\n * @example\n * <StatusPill>Available for work</StatusPill>\n * @example\n * <StatusPill tone=\"warning\" pulse={false}>Degraded</StatusPill>\n */\nexport function StatusPill({\n children,\n tone = 'success',\n pulse = true,\n className,\n ...rest\n}: StatusPillProps) {\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-2.5 rounded-(--radius-pill) border border-(--rule-2) bg-(--paper) py-1.5 ps-2.5 pe-3 eyebrow tracking-[0.12em] text-(--ink-2)',\n className,\n )}\n {...rest}\n >\n <StatusDot tone={tone} pulse={pulse} />\n {children}\n </span>\n )\n}\n\nexport default StatusPill\n"],"mappings":"AA+BI,SAOE,KAPF;AA9BJ,SAAS,UAAU;AACnB,SAAS,iBAAkC;AAqBpC,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,OAAO;AAAA,EACP,QAAQ;AAAA,EACR;AAAA,EACA,GAAG;AACL,GAAoB;AAClB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,4BAAC,aAAU,MAAY,OAAc;AAAA,QACpC;AAAA;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,qBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/StatusPill/StatusPill.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { StatusDot, type StatusTone } from '../StatusDot/StatusDot'\n\nexport interface StatusPillProps extends HTMLAttributes<HTMLSpanElement> {\n children: ReactNode\n tone?: StatusTone\n /** Pulsing live dot (default) vs a settled one. */\n pulse?: boolean\n}\n\n/**\n * The two tones a reader must not miss, in the words the dot cannot say.\n *\n * `tone` reaches only the StatusDot, and the dot is `aria-hidden` by law — so\n * before this, \"Degraded\" in a warning pill and \"Degraded\" in a neutral one\n * were the same sentence to anyone who could not see the colour.\n *\n * `success` and `neutral` add nothing on purpose. They are the absence of\n * alarm, which is what a reader assumes; announcing it before every settled\n * pill is noise charged to the two tones that are worth interrupting for.\n */\nconst SEVERITY: Partial<Record<StatusTone, string>> = {\n warning: 'Warning',\n danger: 'Error',\n}\n\n/**\n * A live state, named: a dot plus an uppercase mono label in an outlined pill.\n *\n * One component rather than a dot and a pill assembled per call site, which is\n * how the same \"available for work\" chip ended up with three different dot\n * sizes and two different pulse timings on one site.\n *\n * A warning or danger tone is doubled by a visually-hidden severity word, so\n * the tone survives the dot being hidden. It does not survive monochrome: the\n * pill's own text is `--ink-2` at every tone, and the state itself still\n * belongs in the words the call site writes.\n *\n * @example\n * <StatusPill>Available for work</StatusPill>\n * @example\n * <StatusPill tone=\"warning\" pulse={false}>Degraded</StatusPill>\n */\nexport function StatusPill({\n children,\n tone = 'success',\n pulse = true,\n className,\n ...rest\n}: StatusPillProps) {\n const severity = SEVERITY[tone]\n\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-2.5 rounded-(--radius-pill) border border-(--rule-2) bg-(--paper) py-1.5 ps-2.5 pe-3 eyebrow tracking-[0.12em] text-(--ink-2)',\n className,\n )}\n {...rest}\n >\n <StatusDot tone={tone} pulse={pulse} />\n {/* Absolutely positioned by `sr-only`, so it is out of flow and costs the\n pill neither a gap nor a pixel. */}\n {severity && <span className=\"sr-only\">{severity}</span>}\n {children}\n </span>\n )\n}\n\nexport default StatusPill\n"],"mappings":"AAsDI,SAOE,KAPF;AArDJ,SAAS,UAAU;AACnB,SAAS,iBAAkC;AAoB3C,MAAM,WAAgD;AAAA,EACpD,SAAS;AAAA,EACT,QAAQ;AACV;AAmBO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,OAAO;AAAA,EACP,QAAQ;AAAA,EACR;AAAA,EACA,GAAG;AACL,GAAoB;AAClB,QAAM,WAAW,SAAS,IAAI;AAE9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,4BAAC,aAAU,MAAY,OAAc;AAAA,QAGpC,YAAY,oBAAC,UAAK,WAAU,WAAW,oBAAS;AAAA,QAChD;AAAA;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,qBAAQ;","names":[]}