@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
@@ -0,0 +1,123 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Check, Copy } from "lucide-react";
4
+ import { useEffect, useState } from "react";
5
+ import { cn } from "../../lib/cn.js";
6
+ import { Button } from "../Button/Button.js";
7
+ const LANGUAGE_LABEL = {
8
+ bash: "Shell",
9
+ css: "CSS",
10
+ html: "HTML",
11
+ js: "JavaScript",
12
+ json: "JSON",
13
+ jsx: "JSX",
14
+ md: "Markdown",
15
+ sh: "Shell",
16
+ ts: "TypeScript",
17
+ tsx: "TSX"
18
+ };
19
+ const HIGHLIGHTED = "[&_pre]:m-0 [&_pre]:min-w-max [&_pre]:bg-transparent [&_pre]:p-0 [&_pre]:font-mono [&_pre]:text-[13px] [&_pre]:leading-[1.7] [&_code]:bg-transparent";
20
+ function CodeBlock({
21
+ code,
22
+ html,
23
+ title,
24
+ lang,
25
+ maxHeight,
26
+ copyable = true,
27
+ label,
28
+ className,
29
+ lineNumbers = false,
30
+ highlightLines
31
+ }) {
32
+ const [copied, setCopied] = useState(false);
33
+ useEffect(() => {
34
+ if (!copied) return;
35
+ const timer = setTimeout(() => setCopied(false), 1600);
36
+ return () => clearTimeout(timer);
37
+ }, [copied]);
38
+ const copy = async () => {
39
+ try {
40
+ await navigator.clipboard.writeText(code);
41
+ setCopied(true);
42
+ } catch {
43
+ }
44
+ };
45
+ const languageLabel = lang ? LANGUAGE_LABEL[lang] ?? lang.toUpperCase() : void 0;
46
+ const banded = new Set(highlightLines ?? []);
47
+ return /* @__PURE__ */ jsxs(
48
+ "div",
49
+ {
50
+ className: cn(
51
+ // `min-w-0` is what lets the body below actually scroll: a grid or flex
52
+ // item defaults to `min-width: auto`, so without it the block grows to
53
+ // the min-content width of its longest line — an unbroken install
54
+ // command — and pushes the whole page sideways on a phone.
55
+ "flex min-w-0 flex-col overflow-hidden rounded-(--radius-lg) border border-(--rule) bg-(--paper-2)",
56
+ className
57
+ ),
58
+ children: [
59
+ title || languageLabel || copyable ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 border-b border-(--rule) py-1.5 ps-4 pe-1.5", children: [
60
+ title ? /* @__PURE__ */ jsx("span", { className: "mono-meta truncate text-(--ink-2)", children: title }) : null,
61
+ languageLabel ? /* @__PURE__ */ jsx("span", { className: "mono-meta shrink-0 text-(--ink-3-aa)", children: languageLabel }) : null,
62
+ copyable ? /* @__PURE__ */ jsx(
63
+ Button,
64
+ {
65
+ iconOnly: true,
66
+ size: "sm",
67
+ variant: "ghost",
68
+ onClick: copy,
69
+ "aria-label": copied ? "Copied" : "Copy the snippet",
70
+ className: "ms-auto shrink-0 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
71
+ children: copied ? /* @__PURE__ */ jsx(Check, { size: 14, strokeWidth: 1.5, "aria-hidden": true }) : /* @__PURE__ */ jsx(Copy, { size: 14, strokeWidth: 1.5, "aria-hidden": true })
72
+ }
73
+ ) : null
74
+ ] }) : null,
75
+ /* @__PURE__ */ jsx(
76
+ "div",
77
+ {
78
+ tabIndex: 0,
79
+ role: "group",
80
+ "aria-label": label ?? title ?? "Code",
81
+ style: maxHeight === void 0 ? void 0 : { maxHeight },
82
+ className: "overflow-auto px-4 py-3.5 scroll-slim",
83
+ children: html === void 0 ? /* @__PURE__ */ jsx("pre", { className: "m-0 min-w-max bg-transparent p-0 font-mono text-[13px] leading-[1.7]", children: /* @__PURE__ */ jsx("code", { className: "block", children: code.split("\n").map((line, index) => /* @__PURE__ */ jsxs(
84
+ "span",
85
+ {
86
+ "data-line": index + 1,
87
+ "data-highlighted": banded.has(index + 1) ? "" : void 0,
88
+ className: cn(
89
+ "block min-h-[1.7em] border-s-2 border-transparent ps-2",
90
+ banded.has(index + 1) && "border-(--accent) bg-(--stone)"
91
+ ),
92
+ children: [
93
+ lineNumbers ? /* @__PURE__ */ jsx(
94
+ "span",
95
+ {
96
+ "aria-hidden": true,
97
+ className: "inline-block w-8 shrink-0 select-none pe-3 text-end tabular-nums text-(--ink-3-aa)",
98
+ children: index + 1
99
+ }
100
+ ) : null,
101
+ line
102
+ ]
103
+ },
104
+ index
105
+ )) }) }) : (
106
+ // Trusted markup only: the output of the caller's own highlighter, run
107
+ // over the caller's own `code`. It is not a place to put anything a
108
+ // reader wrote — hand that to `code` and let the block render it as
109
+ // text, which it cannot mis-execute.
110
+ /* @__PURE__ */ jsx("div", { className: HIGHLIGHTED, dangerouslySetInnerHTML: { __html: html } })
111
+ )
112
+ }
113
+ )
114
+ ]
115
+ }
116
+ );
117
+ }
118
+ var CodeBlock_default = CodeBlock;
119
+ export {
120
+ CodeBlock,
121
+ CodeBlock_default as default
122
+ };
123
+ //# sourceMappingURL=CodeBlock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/CodeBlock/CodeBlock.tsx"],"sourcesContent":["'use client'\n\nimport { Check, Copy } from 'lucide-react'\nimport { useEffect, useState } from 'react'\nimport { cn } from '../../lib/cn'\nimport { Button } from '../Button/Button'\n\n/**\n * What a language id is CALLED, for the handful where the id is not a label.\n *\n * Shiki's ids are identifiers, not words: `tsx` and `bash` read fine in upper\n * case, `md` and `ts` do not, and nobody reads `sh` as \"shell\". Anything absent\n * falls through to the id upper-cased, which is right far more often than it is\n * wrong — and a wrong label here is a label, not a crash.\n */\nconst LANGUAGE_LABEL: Record<string, string> = {\n bash: 'Shell',\n css: 'CSS',\n html: 'HTML',\n js: 'JavaScript',\n json: 'JSON',\n jsx: 'JSX',\n md: 'Markdown',\n sh: 'Shell',\n ts: 'TypeScript',\n tsx: 'TSX',\n}\n\ninterface CommonProps {\n /**\n * The snippet, verbatim.\n *\n * Required even when `html` is supplied, and that is the point: this is what\n * the copy button puts on the clipboard. A block that copies its own rendered\n * markup hands the reader a wall of `<span>`s, and one that copies\n * `textContent` scraped back out of the DOM is a single non-breaking space\n * away from pasting something that does not run.\n */\n code: string\n /** A filename or a caption, printed at the start of the strip. */\n title?: string\n /**\n * The language, printed at the end of the strip.\n *\n * Optional, and the omission is visible rather than silent: a block with no\n * language gets no label instead of claiming a wrong one. A code block that\n * does not say what it is written in makes the reader infer it from the\n * syntax, which is the one thing they came to the block to learn.\n */\n lang?: string\n /**\n * Caps the block's height and scrolls past it.\n *\n * A number is pixels; a string is any CSS length (`'24rem'`, `'50vh'`). The\n * body scrolls in both axes and is focusable, so what is past the fold stays\n * reachable with a keyboard rather than merely present in the DOM.\n */\n maxHeight?: number | string\n /** Drops the copy button, for a block nobody is meant to run. */\n copyable?: boolean\n /**\n * Names the scrollable body for a screen reader. Defaults to `title`, and to\n * \"Code\" when there is not one.\n */\n label?: string\n className?: string\n}\n\n/**\n * The two forms, and why they are two.\n *\n * `html` is pre-highlighted markup for the SAME snippet. The package does not\n * highlight, and will not: a highlighter is a few hundred kilobytes of grammar\n * and a build-time job. The documentation site runs Shiki inside its generator\n * and hands the result here, which is the shape that half exists for. Without\n * `html` the block renders `code` as text, which is what a template, a README\n * or an agent's answer has and all it needs.\n *\n * `lineNumbers` and `highlightLines` belong to the plain form only, and that is\n * enforced in the TYPE rather than left to a note. They are a per-LINE\n * structure, and `html` is one opaque string this component is not going to\n * start parsing. Typed apart, passing both is a compile error at the call site;\n * typed together it is a prop that renders nothing and says nothing.\n *\n * The plain form is written first because the emitted prop table reads the\n * first arm of a union: this way `lineNumbers` documents as `boolean` rather\n * than as the `never` the other arm gives it.\n */\ntype WithPlainSource = CommonProps & {\n /**\n * Absent on this form; `string` on the other.\n *\n * Pass pre-highlighted markup — Shiki's output, run at build time over this\n * same `code` — and the block renders that instead of the plain text. Doing\n * so takes `lineNumbers` and `highlightLines` away with it.\n */\n html?: undefined\n /** Numbers every line, counting from one. */\n lineNumbers?: boolean\n /**\n * Lines to band, counting from one. A number past the end of the snippet is\n * ignored rather than throwing — the usual cause is a snippet that got\n * shorter while the annotation did not.\n */\n highlightLines?: number[]\n}\n\ntype WithHighlightedHtml = CommonProps & {\n html: string\n /** Not available alongside `html`, which the component does not parse. */\n lineNumbers?: never\n /** Not available alongside `html`, which the component does not parse. */\n highlightLines?: never\n}\n\nexport type CodeBlockProps = WithPlainSource | WithHighlightedHtml\n\n/**\n * The caller's `<pre>`, normalised to ours.\n *\n * Shiki emits a `<pre>` of its own with a background and padding baked in, so\n * the highlighted path would otherwise draw a second plate a shade off the one\n * it is sitting on, at a size that does not match the plain path.\n */\nconst HIGHLIGHTED =\n '[&_pre]:m-0 [&_pre]:min-w-max [&_pre]:bg-transparent [&_pre]:p-0 [&_pre]:font-mono [&_pre]:text-[13px] [&_pre]:leading-[1.7] [&_code]:bg-transparent'\n\n/**\n * A multi-line snippet, on a plate, with a way to take it away.\n *\n * The strip along the top carries the title, the language and the copy button,\n * and it is there whenever there is anything to put in it. It is deliberately\n * not a hover affordance: a control that appears on hover does not exist on a\n * touch screen, which is where a reader is most likely to want the snippet and\n * least able to select it by hand.\n *\n * Line numbers live INSIDE their own line's row rather than in a parallel gutter\n * column. Two columns sharing a line-height align right up until one of them\n * wraps, ships a different font, or scrolls on its own; a number that is a child\n * of the line it numbers cannot come apart from it. The cost is that a number\n * scrolls away with its line: it is inside the scrolling box, not in a gutter\n * beside it. A gutter that stayed put would be a second column to keep in sync,\n * which is the failure this avoids.\n *\n * The body is a focusable, named `role=\"group\"`, and both halves of that are\n * load-bearing. Focusable, because a scrollable box whose contents are not\n * themselves focusable is unreachable by keyboard — there is nothing to Tab to\n * and therefore no way to press an arrow key at it, so the right-hand half of a\n * long line does not exist for anyone not using a mouse. Named, because a tab\n * stop that announces nothing lands the reader in an anonymous box and leaves\n * them to work out what they have arrived in.\n *\n * `group` rather than `region`, and that is the deliberate half. A region is a\n * LANDMARK — one of the handful of major sections a reader navigates a whole\n * page by — and a snippet is not one of those. Three fenced blocks in one\n * article would put three landmarks called \"Code\" into that map, which is\n * exactly the noise the `landmark-unique` rule exists to catch, and it would\n * push the page's real landmarks down a list nobody can now skim. `group`\n * carries the same accessible name to the same reader on the way in, and\n * carries it nowhere else.\n *\n * For a function name inside a sentence, reach for `Code`.\n *\n * @example\n * <CodeBlock code=\"pnpm add @misoto22/design\" lang=\"bash\" />\n * @example\n * <CodeBlock\n * title=\"cn.ts\"\n * lang=\"ts\"\n * lineNumbers\n * highlightLines={[2]}\n * maxHeight=\"24rem\"\n * code={source}\n * />\n * @example\n * // Highlighted at build time by the site's own Shiki pass.\n * <CodeBlock code={source} html={highlighted} lang=\"tsx\" />\n */\nexport function CodeBlock({\n code,\n html,\n title,\n lang,\n maxHeight,\n copyable = true,\n label,\n className,\n lineNumbers = false,\n highlightLines,\n}: CodeBlockProps) {\n const [copied, setCopied] = useState(false)\n\n useEffect(() => {\n if (!copied) return\n const timer = setTimeout(() => setCopied(false), 1600)\n return () => clearTimeout(timer)\n }, [copied])\n\n const copy = async () => {\n try {\n await navigator.clipboard.writeText(code)\n setCopied(true)\n } catch {\n // Clipboard access is denied in an insecure context and inside some\n // embeds. The snippet is still selectable, so there is nothing to\n // recover — and an unhandled rejection here would take the page with it.\n }\n }\n\n const languageLabel = lang ? (LANGUAGE_LABEL[lang] ?? lang.toUpperCase()) : undefined\n const banded = new Set(highlightLines ?? [])\n\n return (\n <div\n className={cn(\n // `min-w-0` is what lets the body below actually scroll: a grid or flex\n // item defaults to `min-width: auto`, so without it the block grows to\n // the min-content width of its longest line — an unbroken install\n // command — and pushes the whole page sideways on a phone.\n 'flex min-w-0 flex-col overflow-hidden rounded-(--radius-lg) border border-(--rule) bg-(--paper-2)',\n className,\n )}\n >\n {title || languageLabel || copyable ? (\n <div className=\"flex items-center gap-3 border-b border-(--rule) py-1.5 ps-4 pe-1.5\">\n {title ? <span className=\"mono-meta truncate text-(--ink-2)\">{title}</span> : null}\n {languageLabel ? (\n <span className=\"mono-meta shrink-0 text-(--ink-3-aa)\">{languageLabel}</span>\n ) : null}\n {copyable ? (\n <Button\n iconOnly\n size=\"sm\"\n variant=\"ghost\"\n onClick={copy}\n aria-label={copied ? 'Copied' : 'Copy the snippet'}\n // `sm` is 36px, under the 44px a finger needs. The bump is gated\n // on a coarse pointer so a mouse-driven page keeps the compact\n // strip (WCAG 2.5.5).\n className=\"ms-auto shrink-0 pointer-coarse:min-h-11 pointer-coarse:min-w-11\"\n >\n {copied ? (\n <Check size={14} strokeWidth={1.5} aria-hidden />\n ) : (\n <Copy size={14} strokeWidth={1.5} aria-hidden />\n )}\n </Button>\n ) : null}\n </div>\n ) : null}\n\n <div\n tabIndex={0}\n // A named group, not a landmark: see the note above the component.\n role=\"group\"\n aria-label={label ?? title ?? 'Code'}\n style={maxHeight === undefined ? undefined : { maxHeight }}\n className=\"overflow-auto px-4 py-3.5 scroll-slim\"\n >\n {html === undefined ? (\n <pre className=\"m-0 min-w-max bg-transparent p-0 font-mono text-[13px] leading-[1.7]\">\n <code className=\"block\">\n {code.split('\\n').map((line, index) => (\n <span\n key={index}\n data-line={index + 1}\n data-highlighted={banded.has(index + 1) ? '' : undefined}\n // The transparent border is carried by EVERY row, banded or\n // not. On the banded ones alone it would shift them two pixels\n // along the inline axis — which is the exact misalignment a\n // highlight is there to help the reader see past.\n //\n // `min-h` is what keeps a blank line a line: an empty block\n // has no line box and would otherwise collapse to nothing,\n // taking every number below it out of step with its code.\n className={cn(\n 'block min-h-[1.7em] border-s-2 border-transparent ps-2',\n banded.has(index + 1) && 'border-(--accent) bg-(--stone)',\n )}\n >\n {lineNumbers ? (\n <span\n aria-hidden\n className=\"inline-block w-8 shrink-0 select-none pe-3 text-end tabular-nums text-(--ink-3-aa)\"\n >\n {index + 1}\n </span>\n ) : null}\n {line}\n </span>\n ))}\n </code>\n </pre>\n ) : (\n // Trusted markup only: the output of the caller's own highlighter, run\n // over the caller's own `code`. It is not a place to put anything a\n // reader wrote — hand that to `code` and let the block render it as\n // text, which it cannot mis-execute.\n <div className={HIGHLIGHTED} dangerouslySetInnerHTML={{ __html: html }} />\n )}\n </div>\n </div>\n )\n}\n\nexport default CodeBlock\n"],"mappings":"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","names":[]}
@@ -12,8 +12,9 @@ function CollapsibleContent({
12
12
  return /* @__PURE__ */ jsx(
13
13
  CollapsiblePrimitive.Content,
14
14
  {
15
+ "data-m22-animated": true,
15
16
  className: cn(
16
- "overflow-hidden data-[state=closed]:animate-[m22-collapsible-up_var(--duration-base)_var(--ease)] data-[state=open]:animate-[m22-collapsible-down_var(--duration-base)_var(--ease)] motion-reduce:animate-none",
17
+ "overflow-hidden data-[state=closed]:animate-[m22-collapsible-up_var(--duration-base)_var(--ease)] data-[state=open]:animate-[m22-collapsible-down_var(--duration-base)_var(--ease)]",
17
18
  className
18
19
  ),
19
20
  ...props
@@ -39,7 +40,14 @@ function CollapsibleSection({
39
40
  }
40
41
  )
41
42
  ] }),
42
- /* @__PURE__ */ jsx(CollapsiblePrimitive.Content, { className: "overflow-hidden text-sm leading-relaxed text-(--ink-2) data-[state=closed]:animate-[m22-collapsible-up_var(--duration-base)_var(--ease)] data-[state=open]:animate-[m22-collapsible-down_var(--duration-base)_var(--ease)]", children: /* @__PURE__ */ jsx("div", { className: "pb-3", children }) })
43
+ /* @__PURE__ */ jsx(
44
+ CollapsiblePrimitive.Content,
45
+ {
46
+ "data-m22-animated": true,
47
+ className: "overflow-hidden text-sm leading-relaxed text-(--ink-2) data-[state=closed]:animate-[m22-collapsible-up_var(--duration-base)_var(--ease)] data-[state=open]:animate-[m22-collapsible-down_var(--duration-base)_var(--ease)]",
48
+ children: /* @__PURE__ */ jsx("div", { className: "pb-3", children })
49
+ }
50
+ )
43
51
  ] });
44
52
  }
45
53
  var Collapsible_default = Collapsible;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Collapsible/Collapsible.tsx"],"sourcesContent":["'use client'\n\nimport * as CollapsiblePrimitive from '@radix-ui/react-collapsible'\nimport { ChevronDown } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/** Radix Collapsible root, as a typed passthrough. */\nexport const Collapsible = CollapsiblePrimitive.Root\n\n/**\n * The trigger and the panel, unstyled, for a disclosure that needs its own\n * layout — a sidebar group whose header carries a count and a chevron on\n * opposite sides, say. `CollapsibleSection` is the composed version and is what\n * most call sites want; these two exist so the ones that do not have to reach\n * for Radix directly and re-derive the keyboard and `aria-expanded` wiring.\n *\n * The panel animates on `--radix-collapsible-content-height`, which Radix\n * measures — so it opens to its real height rather than to a guessed\n * `max-height`, which is what makes a long group and a short one take the same\n * time instead of the long one appearing to stall.\n */\nexport const CollapsibleTrigger = CollapsiblePrimitive.Trigger\n\nexport function CollapsibleContent({\n className,\n ...props\n}: ComponentProps<typeof CollapsiblePrimitive.Content>) {\n return (\n <CollapsiblePrimitive.Content\n className={cn(\n 'overflow-hidden data-[state=closed]:animate-[m22-collapsible-up_var(--duration-base)_var(--ease)] data-[state=open]:animate-[m22-collapsible-down_var(--duration-base)_var(--ease)] motion-reduce:animate-none',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport interface CollapsibleSectionProps\n extends Omit<ComponentProps<typeof CollapsiblePrimitive.Root>, 'children' | 'title'> {\n /** What the trigger says. */\n title: ReactNode\n children: ReactNode\n}\n\n/**\n * One thing that opens, on its own.\n *\n * The difference from `Accordion` is arithmetic: an accordion is a SET, and a\n * set can coordinate — opening one closes another. A collapsible is one\n * disclosure with nothing to coordinate with. Reaching for an accordion of one\n * gets you a component managing a value you never read.\n *\n * The marker here is a chevron rather than the accordion's plus, and\n * deliberately: this reveals more of the same thing, where an accordion row\n * opens a distinct answer.\n *\n * @example\n * <CollapsibleSection title=\"Advanced settings\">\n * <Field label=\"Retries\"><Input type=\"number\" /></Field>\n * </CollapsibleSection>\n */\nexport function CollapsibleSection({\n title,\n children,\n className,\n ...props\n}: CollapsibleSectionProps) {\n return (\n <CollapsiblePrimitive.Root className={cn('w-full', className)} {...props}>\n <CollapsiblePrimitive.Trigger className=\"group flex w-full items-center justify-between gap-4 py-3 text-start text-sm text-(--ink) transition-colors duration-(--duration-fast) hover:text-(--ink-2)\">\n {title}\n <ChevronDown\n size={16}\n strokeWidth={1.5}\n aria-hidden\n className=\"shrink-0 text-(--ink-3-aa) transition-transform duration-(--duration-base) ease-(--ease-out-expo) group-data-[state=open]:rotate-180\"\n />\n </CollapsiblePrimitive.Trigger>\n <CollapsiblePrimitive.Content className=\"overflow-hidden text-sm leading-relaxed text-(--ink-2) data-[state=closed]:animate-[m22-collapsible-up_var(--duration-base)_var(--ease)] data-[state=open]:animate-[m22-collapsible-down_var(--duration-base)_var(--ease)]\">\n <div className=\"pb-3\">{children}</div>\n </CollapsiblePrimitive.Content>\n </CollapsiblePrimitive.Root>\n )\n}\n\nexport default Collapsible\n"],"mappings":";AA6BI,cA0CE,YA1CF;AA3BJ,YAAY,0BAA0B;AACtC,SAAS,mBAAmB;AAE5B,SAAS,UAAU;AAGZ,MAAM,cAAc,qBAAqB;AAczC,MAAM,qBAAqB,qBAAqB;AAEhD,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA,GAAG;AACL,GAAwD;AACtD,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AA0BO,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA4B;AAC1B,SACE,qBAAC,qBAAqB,MAArB,EAA0B,WAAW,GAAG,UAAU,SAAS,GAAI,GAAG,OACjE;AAAA,yBAAC,qBAAqB,SAArB,EAA6B,WAAU,+JACrC;AAAA;AAAA,MACD;AAAA,QAAC;AAAA;AAAA,UACC,MAAM;AAAA,UACN,aAAa;AAAA,UACb,eAAW;AAAA,UACX,WAAU;AAAA;AAAA,MACZ;AAAA,OACF;AAAA,IACA,oBAAC,qBAAqB,SAArB,EAA6B,WAAU,8NACtC,8BAAC,SAAI,WAAU,QAAQ,UAAS,GAClC;AAAA,KACF;AAEJ;AAEA,IAAO,sBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Collapsible/Collapsible.tsx"],"sourcesContent":["'use client'\n\nimport * as CollapsiblePrimitive from '@radix-ui/react-collapsible'\nimport { ChevronDown } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/** Radix Collapsible root, as a typed passthrough. */\nexport const Collapsible = CollapsiblePrimitive.Root\n\n/**\n * The trigger and the panel, unstyled, for a disclosure that needs its own\n * layout — a sidebar group whose header carries a count and a chevron on\n * opposite sides, say. `CollapsibleSection` is the composed version and is what\n * most call sites want; these two exist so the ones that do not have to reach\n * for Radix directly and re-derive the keyboard and `aria-expanded` wiring.\n *\n * The panel animates on `--radix-collapsible-content-height`, which Radix\n * measures — so it opens to its real height rather than to a guessed\n * `max-height`, which is what makes a long group and a short one take the same\n * time instead of the long one appearing to stall.\n */\nexport const CollapsibleTrigger = CollapsiblePrimitive.Trigger\n\nexport function CollapsibleContent({\n className,\n ...props\n}: ComponentProps<typeof CollapsiblePrimitive.Content>) {\n return (\n <CollapsiblePrimitive.Content\n data-m22-animated\n className={cn(\n 'overflow-hidden data-[state=closed]:animate-[m22-collapsible-up_var(--duration-base)_var(--ease)] data-[state=open]:animate-[m22-collapsible-down_var(--duration-base)_var(--ease)]',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport interface CollapsibleSectionProps\n extends Omit<ComponentProps<typeof CollapsiblePrimitive.Root>, 'children' | 'title'> {\n /** What the trigger says. */\n title: ReactNode\n children: ReactNode\n}\n\n/**\n * One thing that opens, on its own.\n *\n * The difference from `Accordion` is arithmetic: an accordion is a SET, and a\n * set can coordinate — opening one closes another. A collapsible is one\n * disclosure with nothing to coordinate with. Reaching for an accordion of one\n * gets you a component managing a value you never read.\n *\n * The marker here is a chevron rather than the accordion's plus, and\n * deliberately: this reveals more of the same thing, where an accordion row\n * opens a distinct answer.\n *\n * @example\n * <CollapsibleSection title=\"Advanced settings\">\n * <Field label=\"Retries\"><Input type=\"number\" /></Field>\n * </CollapsibleSection>\n */\nexport function CollapsibleSection({\n title,\n children,\n className,\n ...props\n}: CollapsibleSectionProps) {\n return (\n <CollapsiblePrimitive.Root className={cn('w-full', className)} {...props}>\n <CollapsiblePrimitive.Trigger className=\"group flex w-full items-center justify-between gap-4 py-3 text-start text-sm text-(--ink) transition-colors duration-(--duration-fast) hover:text-(--ink-2)\">\n {title}\n <ChevronDown\n size={16}\n strokeWidth={1.5}\n aria-hidden\n className=\"shrink-0 text-(--ink-3-aa) transition-transform duration-(--duration-base) ease-(--ease-out-expo) group-data-[state=open]:rotate-180\"\n />\n </CollapsiblePrimitive.Trigger>\n <CollapsiblePrimitive.Content\n data-m22-animated\n className=\"overflow-hidden text-sm leading-relaxed text-(--ink-2) data-[state=closed]:animate-[m22-collapsible-up_var(--duration-base)_var(--ease)] data-[state=open]:animate-[m22-collapsible-down_var(--duration-base)_var(--ease)]\"\n >\n <div className=\"pb-3\">{children}</div>\n </CollapsiblePrimitive.Content>\n </CollapsiblePrimitive.Root>\n )\n}\n\nexport default Collapsible\n"],"mappings":";AA6BI,cA2CE,YA3CF;AA3BJ,YAAY,0BAA0B;AACtC,SAAS,mBAAmB;AAE5B,SAAS,UAAU;AAGZ,MAAM,cAAc,qBAAqB;AAczC,MAAM,qBAAqB,qBAAqB;AAEhD,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA,GAAG;AACL,GAAwD;AACtD,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,qBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AA0BO,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA4B;AAC1B,SACE,qBAAC,qBAAqB,MAArB,EAA0B,WAAW,GAAG,UAAU,SAAS,GAAI,GAAG,OACjE;AAAA,yBAAC,qBAAqB,SAArB,EAA6B,WAAU,+JACrC;AAAA;AAAA,MACD;AAAA,QAAC;AAAA;AAAA,UACC,MAAM;AAAA,UACN,aAAa;AAAA,UACb,eAAW;AAAA,UACX,WAAU;AAAA;AAAA,MACZ;AAAA,OACF;AAAA,IACA;AAAA,MAAC,qBAAqB;AAAA,MAArB;AAAA,QACC,qBAAiB;AAAA,QACjB,WAAU;AAAA,QAEV,8BAAC,SAAI,WAAU,QAAQ,UAAS;AAAA;AAAA,IAClC;AAAA,KACF;AAEJ;AAEA,IAAO,sBAAQ;","names":[]}
@@ -9,7 +9,13 @@ interface ComboboxOption {
9
9
  }
10
10
  interface CommonProps {
11
11
  options: ComboboxOption[];
12
- /** Names the control. Required — the trigger's text is a value, not a label. */
12
+ /**
13
+ * Names the control. Required — the trigger's text is a value, not a label.
14
+ *
15
+ * Announced together with the summary rather than instead of it, so a reader
16
+ * hears "Tags, 3 selected". Inside a `Field` with a label, that label names
17
+ * the trigger and this one is not repeated.
18
+ */
13
19
  label: string;
14
20
  /** Shown on the trigger when nothing is chosen. */
15
21
  placeholder?: string;
@@ -19,6 +25,14 @@ interface CommonProps {
19
25
  emptyMessage?: string;
20
26
  disabled?: boolean;
21
27
  className?: string;
28
+ /** The TRIGGER's id — the element a label points at. A `Field` sets it. */
29
+ id?: string;
30
+ /** Ids of the copy describing the control. A `Field` sets it from hint, error and description. */
31
+ 'aria-describedby'?: string;
32
+ /** Paints the resting border with `--danger` and is announced. A `Field` sets it from `error`. */
33
+ 'aria-invalid'?: boolean | 'true' | 'false';
34
+ /** Announced on the trigger. A `Field` sets it from `required`. */
35
+ 'aria-required'?: boolean;
22
36
  }
23
37
  interface SingleProps extends CommonProps {
24
38
  multiple?: false;
@@ -1,8 +1,11 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Check, ChevronsUpDown, X } from "lucide-react";
4
- import { useState } from "react";
4
+ import { useId, useState } from "react";
5
5
  import { cn } from "../../lib/cn.js";
6
+ import { CONTROL_BORDER, isInvalid } from "../../lib/control.js";
7
+ import { useFieldControl } from "../Field/field-control.js";
8
+ import { warnBlankName } from "../../lib/warn.js";
6
9
  import {
7
10
  Command,
8
11
  CommandEmpty,
@@ -21,9 +24,20 @@ function Combobox(props) {
21
24
  searchPlaceholder = "Search\u2026",
22
25
  emptyMessage = "Nothing matches.",
23
26
  disabled = false,
24
- className
27
+ className,
28
+ id,
29
+ "aria-describedby": describedBy,
30
+ "aria-invalid": ariaInvalid,
31
+ "aria-required": ariaRequired
25
32
  } = props;
26
33
  const multiple = props.multiple === true;
34
+ warnBlankName("Combobox", "label", label, "the trigger is announced with no name");
35
+ const field = useFieldControl();
36
+ const generated = useId();
37
+ const triggerId = id ?? generated;
38
+ const summaryId = `${triggerId}-value`;
39
+ const nameId = field?.labelId ?? `${triggerId}-name`;
40
+ const bad = isInvalid(void 0, ariaInvalid);
27
41
  const [open, setOpen] = useState(false);
28
42
  const [uncontrolled, setUncontrolled] = useState(() => {
29
43
  if (props.multiple === true) return props.defaultValue ?? [];
@@ -51,16 +65,22 @@ function Combobox(props) {
51
65
  PopoverTrigger,
52
66
  {
53
67
  role: "combobox",
68
+ id: triggerId,
54
69
  "aria-expanded": open,
55
- "aria-label": label,
70
+ "aria-labelledby": `${nameId} ${summaryId}`,
71
+ "aria-describedby": describedBy,
72
+ "aria-required": ariaRequired,
73
+ "aria-invalid": bad || void 0,
56
74
  disabled,
57
75
  className: cn(
58
- "group flex w-full items-center justify-between gap-2 rounded-(--radius) border border-(--rule-2) bg-(--paper) px-(--field-px) py-(--field-py) text-start text-sm transition-colors duration-(--duration-fast) hover:border-(--rule-hard) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none",
76
+ "group flex w-full items-center justify-between gap-2 rounded-(--radius) border bg-(--paper) px-(--field-px) py-(--field-py) text-start text-sm transition-colors duration-(--duration-fast) hover:border-(--rule-hard) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none",
77
+ bad ? CONTROL_BORDER.invalid : CONTROL_BORDER.resting,
59
78
  chosen.length > 0 ? "text-(--ink)" : "text-(--ink-3-aa)",
60
79
  className
61
80
  ),
62
81
  children: [
63
- /* @__PURE__ */ jsx("span", { className: "truncate", children: summary }),
82
+ field?.labelId == null && /* @__PURE__ */ jsx("span", { id: nameId, className: "sr-only", children: label }),
83
+ /* @__PURE__ */ jsx("span", { id: summaryId, className: "truncate", children: summary }),
64
84
  /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1", children: [
65
85
  multiple && chosen.length > 0 && // A `<span role="button">` rather than a nested `<button>`, which
66
86
  // is invalid inside the trigger and which browsers reparent — the
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Combobox/Combobox.tsx"],"sourcesContent":["'use client'\n\nimport { Check, ChevronsUpDown, X } from 'lucide-react'\nimport { useState } from 'react'\nimport { cn } from '../../lib/cn'\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '../Command/Command'\nimport { Popover, PopoverContent, PopoverTrigger } from '../Popover/Popover'\n\nexport interface ComboboxOption {\n value: string\n label: string\n /** Extra text matched by the filter but not shown as the label. */\n keywords?: string[]\n disabled?: boolean\n}\n\ninterface CommonProps {\n options: ComboboxOption[]\n /** Names the control. Required — the trigger's text is a value, not a label. */\n label: string\n /** Shown on the trigger when nothing is chosen. */\n placeholder?: string\n /** Placeholder inside the filter field. */\n searchPlaceholder?: string\n /** Shown when the filter matches nothing. Say what WOULD match. */\n emptyMessage?: string\n disabled?: boolean\n className?: string\n}\n\ninterface SingleProps extends CommonProps {\n multiple?: false\n value?: string\n defaultValue?: string\n onValueChange?: (value: string) => void\n}\n\ninterface MultipleProps extends CommonProps {\n /**\n * Several at once. The trigger then summarises the choice rather than naming\n * it — \"3 selected\" past a threshold, because a trigger that grows with its\n * value reflows the form every time someone picks another one.\n */\n multiple: true\n value?: string[]\n defaultValue?: string[]\n onValueChange?: (value: string[]) => void\n}\n\nexport type ComboboxProps = SingleProps | MultipleProps\n\n/** How many labels the trigger prints before it starts counting instead. */\nconst SUMMARISE_AFTER = 2\n\n/**\n * A select you can type into, choosing one or several.\n *\n * The line against `Select` is length, and it is not a matter of taste: a\n * styled select is better up to roughly a dozen options, because a list nobody\n * can filter is faster to scan than one they have to think about. Past that,\n * this is the right answer.\n *\n * Filtering, the highlighted row and the arrow keys come from cmdk, which\n * implements the ARIA combobox pattern properly: the highlight moves through\n * `aria-activedescendant` while focus stays in the input. Hand-rolled comboboxes\n * move focus into the list instead, and the typed text stops being editable.\n *\n * Controlled or uncontrolled, like every other form control here.\n *\n * @example\n * <Combobox label=\"Framework\" options={FRAMEWORKS} placeholder=\"Pick one\" />\n * @example\n * <Combobox multiple label=\"Tags\" options={TAGS} />\n */\nexport function Combobox(props: ComboboxProps) {\n const {\n options,\n label,\n placeholder = 'Select…',\n searchPlaceholder = 'Search…',\n emptyMessage = 'Nothing matches.',\n disabled = false,\n className,\n } = props\n const multiple = props.multiple === true\n\n const [open, setOpen] = useState(false)\n const [uncontrolled, setUncontrolled] = useState<string[]>(() => {\n if (props.multiple === true) return props.defaultValue ?? []\n return props.defaultValue ? [props.defaultValue] : []\n })\n\n const controlled =\n props.multiple === true\n ? props.value\n : props.value === undefined\n ? undefined\n : props.value === ''\n ? []\n : [props.value]\n const current = controlled ?? uncontrolled\n\n const commit = (next: string[]) => {\n if (controlled === undefined) setUncontrolled(next)\n if (props.multiple === true) props.onValueChange?.(next)\n else props.onValueChange?.(next[0] ?? '')\n }\n\n const choose = (option: string) => {\n if (multiple) {\n // Toggling, and the panel stays open — picking three things should not\n // cost three round trips through the trigger.\n commit(current.includes(option) ? current.filter((v) => v !== option) : [...current, option])\n return\n }\n // Choosing the selected option again clears it, which is what a reader\n // expects from a control whose value is optional.\n commit(current.includes(option) ? [] : [option])\n setOpen(false)\n }\n\n const chosen = options.filter((option) => current.includes(option.value))\n const summary =\n chosen.length === 0\n ? placeholder\n : chosen.length <= SUMMARISE_AFTER\n ? chosen.map((option) => option.label).join(', ')\n : `${chosen.length} selected`\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger\n // A combobox trigger is a button that OPENS a listbox; the roles for\n // the list itself belong to cmdk inside the panel.\n role=\"combobox\"\n aria-expanded={open}\n aria-label={label}\n disabled={disabled}\n className={cn(\n 'group flex w-full items-center justify-between gap-2 rounded-(--radius) border border-(--rule-2) bg-(--paper) px-(--field-px) py-(--field-py) text-start text-sm transition-colors duration-(--duration-fast) hover:border-(--rule-hard) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none',\n chosen.length > 0 ? 'text-(--ink)' : 'text-(--ink-3-aa)',\n className,\n )}\n >\n <span className=\"truncate\">{summary}</span>\n <span className=\"flex shrink-0 items-center gap-1\">\n {multiple && chosen.length > 0 && (\n // A `<span role=\"button\">` rather than a nested `<button>`, which\n // is invalid inside the trigger and which browsers reparent — the\n // clear control then falls outside the field entirely.\n <span\n role=\"button\"\n tabIndex={0}\n aria-label={`Clear ${label}`}\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n commit([])\n }}\n onKeyDown={(event) => {\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n event.stopPropagation()\n commit([])\n }}\n className=\"grid size-5 cursor-pointer 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={12} strokeWidth={2} aria-hidden />\n </span>\n )}\n <ChevronsUpDown size={14} strokeWidth={1.5} aria-hidden className=\"text-(--ink-3-aa)\" />\n </span>\n </PopoverTrigger>\n <PopoverContent\n label={label}\n // `overflow-hidden` so the panel's own corners clip the square-cornered\n // command list inside it; without it the list's edges poked through the\n // radius and the two bottom corners looked chipped.\n className=\"w-(--radix-popover-trigger-width) overflow-hidden p-0\"\n align=\"start\"\n >\n <Command label={`${label}: ${searchPlaceholder}`} className=\"rounded-none border-0\">\n {/* The panel's own name is \"<label>: <placeholder>\", set on Command\n above. cmdk points the input's aria-labelledby at it, and\n aria-labelledby beats aria-label — so naming the input directly\n did nothing, and a screen reader met two comboboxes called \"Tags\",\n one inside the other. */}\n <CommandInput placeholder={searchPlaceholder} />\n <CommandList>\n <CommandEmpty>{emptyMessage}</CommandEmpty>\n <CommandGroup>\n {options.map((option) => {\n const selected = current.includes(option.value)\n return (\n <CommandItem\n key={option.value}\n value={option.value}\n keywords={[option.label, ...(option.keywords ?? [])]}\n disabled={option.disabled}\n onSelect={choose}\n aria-selected={selected}\n >\n <span\n className={cn(\n 'grid size-4 shrink-0 place-items-center transition-opacity duration-(--duration-fast)',\n multiple &&\n 'rounded-(--radius-sm) border border-(--rule-2) text-(--accent-foreground) data-[on=true]:border-(--accent) data-[on=true]:bg-(--accent)',\n )}\n data-on={selected}\n >\n <Check\n size={12}\n strokeWidth={2.5}\n aria-hidden\n className={selected ? 'opacity-100' : 'opacity-0'}\n />\n </span>\n {option.label}\n </CommandItem>\n )\n })}\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n )\n}\n\nexport default Combobox\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Combobox/Combobox.tsx"],"sourcesContent":["'use client'\n\nimport { Check, ChevronsUpDown, X } from 'lucide-react'\nimport { useId, useState } from 'react'\nimport { cn } from '../../lib/cn'\nimport { CONTROL_BORDER, isInvalid } from '../../lib/control'\nimport { useFieldControl } from '../Field/field-control'\nimport { warnBlankName } from '../../lib/warn'\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '../Command/Command'\nimport { Popover, PopoverContent, PopoverTrigger } from '../Popover/Popover'\n\nexport interface ComboboxOption {\n value: string\n label: string\n /** Extra text matched by the filter but not shown as the label. */\n keywords?: string[]\n disabled?: boolean\n}\n\ninterface CommonProps {\n options: ComboboxOption[]\n /**\n * Names the control. Required — the trigger's text is a value, not a label.\n *\n * Announced together with the summary rather than instead of it, so a reader\n * hears \"Tags, 3 selected\". Inside a `Field` with a label, that label names\n * the trigger and this one is not repeated.\n */\n label: string\n /** Shown on the trigger when nothing is chosen. */\n placeholder?: string\n /** Placeholder inside the filter field. */\n searchPlaceholder?: string\n /** Shown when the filter matches nothing. Say what WOULD match. */\n emptyMessage?: string\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 /** Paints the resting border with `--danger` and is announced. A `Field` sets it from `error`. */\n 'aria-invalid'?: boolean | 'true' | 'false'\n /** Announced on the trigger. A `Field` sets it from `required`. */\n 'aria-required'?: boolean\n}\n\ninterface SingleProps extends CommonProps {\n multiple?: false\n value?: string\n defaultValue?: string\n onValueChange?: (value: string) => void\n}\n\ninterface MultipleProps extends CommonProps {\n /**\n * Several at once. The trigger then summarises the choice rather than naming\n * it — \"3 selected\" past a threshold, because a trigger that grows with its\n * value reflows the form every time someone picks another one.\n */\n multiple: true\n value?: string[]\n defaultValue?: string[]\n onValueChange?: (value: string[]) => void\n}\n\nexport type ComboboxProps = SingleProps | MultipleProps\n\n/** How many labels the trigger prints before it starts counting instead. */\nconst SUMMARISE_AFTER = 2\n\n/**\n * A select you can type into, choosing one or several.\n *\n * The line against `Select` is length, and it is not a matter of taste: a\n * styled select is better up to roughly a dozen options, because a list nobody\n * can filter is faster to scan than one they have to think about. Past that,\n * this is the right answer.\n *\n * Filtering, the highlighted row and the arrow keys come from cmdk, which\n * implements the ARIA combobox pattern properly: the highlight moves through\n * `aria-activedescendant` while focus stays in the input. Hand-rolled comboboxes\n * move focus into the list instead, and the typed text stops being editable.\n *\n * Controlled or uncontrolled, like every other form control here.\n *\n * @example\n * <Combobox label=\"Framework\" options={FRAMEWORKS} placeholder=\"Pick one\" />\n * @example\n * <Combobox multiple label=\"Tags\" options={TAGS} />\n */\nexport function Combobox(props: ComboboxProps) {\n const {\n options,\n label,\n placeholder = 'Select…',\n searchPlaceholder = 'Search…',\n emptyMessage = 'Nothing matches.',\n disabled = false,\n className,\n id,\n 'aria-describedby': describedBy,\n 'aria-invalid': ariaInvalid,\n 'aria-required': ariaRequired,\n } = props\n const multiple = props.multiple === true\n warnBlankName('Combobox', 'label', label, 'the trigger is announced with no name')\n\n const field = useFieldControl()\n const generated = useId()\n const triggerId = id ?? generated\n const summaryId = `${triggerId}-value`\n const nameId = field?.labelId ?? `${triggerId}-name`\n const bad = isInvalid(undefined, ariaInvalid)\n\n const [open, setOpen] = useState(false)\n const [uncontrolled, setUncontrolled] = useState<string[]>(() => {\n if (props.multiple === true) return props.defaultValue ?? []\n return props.defaultValue ? [props.defaultValue] : []\n })\n\n const controlled =\n props.multiple === true\n ? props.value\n : props.value === undefined\n ? undefined\n : props.value === ''\n ? []\n : [props.value]\n const current = controlled ?? uncontrolled\n\n const commit = (next: string[]) => {\n if (controlled === undefined) setUncontrolled(next)\n if (props.multiple === true) props.onValueChange?.(next)\n else props.onValueChange?.(next[0] ?? '')\n }\n\n const choose = (option: string) => {\n if (multiple) {\n // Toggling, and the panel stays open — picking three things should not\n // cost three round trips through the trigger.\n commit(current.includes(option) ? current.filter((v) => v !== option) : [...current, option])\n return\n }\n // Choosing the selected option again clears it, which is what a reader\n // expects from a control whose value is optional.\n commit(current.includes(option) ? [] : [option])\n setOpen(false)\n }\n\n const chosen = options.filter((option) => current.includes(option.value))\n const summary =\n chosen.length === 0\n ? placeholder\n : chosen.length <= SUMMARISE_AFTER\n ? chosen.map((option) => option.label).join(', ')\n : `${chosen.length} selected`\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger\n // A combobox trigger is a button that OPENS a listbox; the roles for\n // the list itself belong to cmdk inside the panel.\n role=\"combobox\"\n id={triggerId}\n aria-expanded={open}\n // The label and the summary, in that order — never the label alone.\n // `aria-label` outranks the trigger's own text, so a reader was told\n // \"Tags\" and never \"3 selected\". The two ids are named explicitly\n // rather than pointing at the whole trigger, which would drag the clear\n // control's own name into the value.\n aria-labelledby={`${nameId} ${summaryId}`}\n aria-describedby={describedBy}\n aria-required={ariaRequired}\n aria-invalid={bad || undefined}\n disabled={disabled}\n className={cn(\n 'group flex w-full items-center justify-between gap-2 rounded-(--radius) border bg-(--paper) px-(--field-px) py-(--field-py) text-start text-sm transition-colors duration-(--duration-fast) hover:border-(--rule-hard) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none',\n bad ? CONTROL_BORDER.invalid : CONTROL_BORDER.resting,\n chosen.length > 0 ? 'text-(--ink)' : 'text-(--ink-3-aa)',\n className,\n )}\n >\n {field?.labelId == null && (\n <span id={nameId} className=\"sr-only\">\n {label}\n </span>\n )}\n <span id={summaryId} className=\"truncate\">\n {summary}\n </span>\n <span className=\"flex shrink-0 items-center gap-1\">\n {multiple && chosen.length > 0 && (\n // A `<span role=\"button\">` rather than a nested `<button>`, which\n // is invalid inside the trigger and which browsers reparent — the\n // clear control then falls outside the field entirely.\n <span\n role=\"button\"\n tabIndex={0}\n aria-label={`Clear ${label}`}\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n commit([])\n }}\n onKeyDown={(event) => {\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n event.stopPropagation()\n commit([])\n }}\n className=\"grid size-5 cursor-pointer 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={12} strokeWidth={2} aria-hidden />\n </span>\n )}\n <ChevronsUpDown size={14} strokeWidth={1.5} aria-hidden className=\"text-(--ink-3-aa)\" />\n </span>\n </PopoverTrigger>\n <PopoverContent\n label={label}\n // `overflow-hidden` so the panel's own corners clip the square-cornered\n // command list inside it; without it the list's edges poked through the\n // radius and the two bottom corners looked chipped.\n className=\"w-(--radix-popover-trigger-width) overflow-hidden p-0\"\n align=\"start\"\n >\n <Command label={`${label}: ${searchPlaceholder}`} className=\"rounded-none border-0\">\n {/* The panel's own name is \"<label>: <placeholder>\", set on Command\n above. cmdk points the input's aria-labelledby at it, and\n aria-labelledby beats aria-label — so naming the input directly\n did nothing, and a screen reader met two comboboxes called \"Tags\",\n one inside the other. */}\n <CommandInput placeholder={searchPlaceholder} />\n <CommandList>\n <CommandEmpty>{emptyMessage}</CommandEmpty>\n <CommandGroup>\n {options.map((option) => {\n const selected = current.includes(option.value)\n return (\n <CommandItem\n key={option.value}\n value={option.value}\n keywords={[option.label, ...(option.keywords ?? [])]}\n disabled={option.disabled}\n onSelect={choose}\n aria-selected={selected}\n >\n <span\n className={cn(\n 'grid size-4 shrink-0 place-items-center transition-opacity duration-(--duration-fast)',\n multiple &&\n 'rounded-(--radius-sm) border border-(--rule-2) text-(--accent-foreground) data-[on=true]:border-(--accent) data-[on=true]:bg-(--accent)',\n )}\n data-on={selected}\n >\n <Check\n size={12}\n strokeWidth={2.5}\n aria-hidden\n className={selected ? 'opacity-100' : 'opacity-0'}\n />\n </span>\n {option.label}\n </CommandItem>\n )\n })}\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n )\n}\n\nexport default Combobox\n"],"mappings":"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","names":[]}
@@ -1,6 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ComponentProps, ReactNode } from 'react';
3
3
  import { Command as Command$1 } from 'cmdk';
4
+ import { LucideIcon } from 'lucide-react';
4
5
 
5
6
  /**
6
7
  * A filterable list of actions — the ⌘K surface.
@@ -66,12 +67,13 @@ interface CommandItemProps extends ComponentProps<typeof Command$1.Item> {
66
67
  /** A shortcut printed at the end of the row. */
67
68
  shortcut?: string;
68
69
  /**
69
- * A leading glyph. Pass the icon element, sized 16.
70
+ * A leading glyph. Either spelling — `icon={<Settings size={16} />}` passes
71
+ * the element, `icon={Settings}` passes the component and this sizes it.
70
72
  *
71
73
  * It is what makes a long list scannable — the eye sorts by shape before it
72
74
  * reads, and forty identical rows of text defeat that.
73
75
  */
74
- icon?: ReactNode;
76
+ icon?: LucideIcon | ReactNode;
75
77
  /**
76
78
  * A quiet note at the end of the row — what kind of thing this is, or its
77
79
  * current state. Not a description: a palette that prints a sentence per row
@@ -2,7 +2,19 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Command as CommandPrimitive } from "cmdk";
4
4
  import { Search } from "lucide-react";
5
+ import { isValidElement } from "react";
5
6
  import { cn } from "../../lib/cn.js";
7
+ function isIconComponent(icon) {
8
+ if (isValidElement(icon)) return false;
9
+ if (typeof icon === "function") return true;
10
+ if (typeof icon !== "object" || icon === null) return false;
11
+ return !Array.isArray(icon) && !(Symbol.iterator in icon) && !("then" in icon);
12
+ }
13
+ function iconNode(icon) {
14
+ if (!isIconComponent(icon)) return icon;
15
+ const Icon = icon;
16
+ return /* @__PURE__ */ jsx(Icon, { size: 16, strokeWidth: 1.5, "aria-hidden": true });
17
+ }
6
18
  import { Dialog, DialogContent } from "../Dialog/Dialog.js";
7
19
  import { Kbd } from "../Kbd/Kbd.js";
8
20
  function Command({
@@ -122,7 +134,7 @@ function CommandItem({ shortcut, icon, meta, className, children, ...props }) {
122
134
  ),
123
135
  ...props,
124
136
  children: [
125
- icon && /* @__PURE__ */ jsx("span", { className: "grid size-4 shrink-0 place-items-center text-(--ink-3-aa) [&_svg]:size-4", children: icon }),
137
+ icon && /* @__PURE__ */ jsx("span", { className: "grid size-4 shrink-0 place-items-center text-(--ink-3-aa) [&_svg]:size-4", children: iconNode(icon) }),
126
138
  children,
127
139
  meta && /* @__PURE__ */ jsx("span", { className: "ms-auto ps-3 mono-meta text-(--ink-3-aa)", children: meta }),
128
140
  shortcut && /* @__PURE__ */ jsx(Kbd, { className: cn(meta ? "" : "ms-auto"), children: shortcut })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Command/Command.tsx"],"sourcesContent":["'use client'\n\nimport { Command as CommandPrimitive } from 'cmdk'\nimport { Search } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { Dialog, DialogContent } from '../Dialog/Dialog'\nimport { Kbd } from '../Kbd/Kbd'\n\n/**\n * A filterable list of actions — the ⌘K surface.\n *\n * Built on cmdk rather than on a `Select` or a menu, because the interaction is\n * neither: the list is filtered as you type, the highlighted row moves with the\n * arrow keys while focus STAYS in the input, and Enter runs the highlighted\n * row. That is the ARIA combobox pattern, and it is the part nobody should\n * hand-roll — `aria-activedescendant` moving without focus moving is precisely\n * where a home-made palette stops working with a screen reader.\n *\n * @example\n * <Command label=\"Command palette\">\n * <CommandInput placeholder=\"Type a command…\" />\n * <CommandList>\n * <CommandEmpty>Nothing matches.</CommandEmpty>\n * <CommandGroup heading=\"Navigate\">\n * <CommandItem onSelect={go}>Components</CommandItem>\n * </CommandGroup>\n * </CommandList>\n * </Command>\n */\nexport function Command({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive>) {\n return (\n <CommandPrimitive\n className={cn(\n 'flex w-full flex-col overflow-hidden rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) panel-blur',\n className,\n )}\n {...props}\n />\n )\n}\n\n/** The filter field. Carries the search icon and the combobox semantics. */\nexport function CommandInput({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Input>) {\n return (\n <div className=\"flex items-center gap-3 border-b border-(--rule) px-4\">\n <Search size={18} strokeWidth={1.5} aria-hidden className=\"shrink-0 text-(--ink-3-aa)\" />\n <CommandPrimitive.Input\n className={cn(\n 'h-13 w-full bg-transparent text-[15px] text-(--ink) outline-none placeholder:text-(--ink-3-aa)',\n className,\n )}\n {...props}\n />\n </div>\n )\n}\n\nexport function CommandList({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.List>) {\n return (\n <CommandPrimitive.List\n className={cn('max-h-72 overflow-y-auto overflow-x-hidden p-2 scroll-slim', className)}\n {...props}\n />\n )\n}\n\n/** Shown when the filter matches nothing. Say what would match, not \"no results\". */\nexport function CommandEmpty(props: ComponentProps<typeof CommandPrimitive.Empty>) {\n return (\n <CommandPrimitive.Empty\n className=\"px-3 py-10 text-center text-sm text-(--ink-3-aa)\"\n {...props}\n />\n )\n}\n\n/**\n * The key-hint strip along the bottom.\n *\n * A palette is a keyboard surface whose keys are invisible: nothing on screen\n * says the arrows move the row or that Enter runs it, and a reader who reaches\n * for the mouse has been failed by the design rather than by themselves.\n *\n * @example\n * <CommandFooter>\n * <CommandHint keys={['↑', '↓']}>navigate</CommandHint>\n * <CommandHint keys={['↵']}>open</CommandHint>\n * </CommandFooter>\n */\nexport function CommandFooter({ className, ...props }: ComponentProps<'div'>) {\n return (\n <div\n className={cn(\n 'flex items-center gap-4 border-t border-(--rule) bg-(--paper-2) px-3.5 py-2',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport interface CommandHintProps extends Omit<ComponentProps<'span'>, 'children'> {\n /** The keys this hint describes, printed as `Kbd` chips. */\n keys: string[]\n /** What they do — a verb, lowercase, no sentence. */\n children: ReactNode\n}\n\n/** One key-and-verb pair inside a `CommandFooter`. */\nexport function CommandHint({ keys, children, className, ...props }: CommandHintProps) {\n return (\n <span className={cn('flex items-center gap-1.5 mono-meta text-(--ink-3-aa)', className)} {...props}>\n {keys.map((key) => (\n <Kbd key={key}>{key}</Kbd>\n ))}\n {children}\n </span>\n )\n}\n\nexport function CommandGroup({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Group>) {\n return (\n <CommandPrimitive.Group\n className={cn(\n 'overflow-hidden [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:eyebrow [&_[cmdk-group-heading]]:text-(--ink-3-aa)',\n className,\n )}\n {...props}\n />\n )\n}\n\n/**\n * A divider between groups.\n *\n * Marked presentational, because ARIA permits a `listbox` to contain only\n * `option` and `group` — and the library renders this as `role=\"separator\"`,\n * which puts a critical `aria-required-children` violation inside every palette\n * that uses one. The grouping is already announced by the groups themselves, so\n * removing the divider from the accessibility tree loses nothing.\n */\nexport function CommandSeparator({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Separator>) {\n return (\n <CommandPrimitive.Separator\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn('my-1 h-px bg-(--rule)', className)}\n {...props}\n />\n )\n}\n\nexport interface CommandItemProps extends ComponentProps<typeof CommandPrimitive.Item> {\n /** A shortcut printed at the end of the row. */\n shortcut?: string\n /**\n * A leading glyph. Pass the icon element, sized 16.\n *\n * It is what makes a long list scannable — the eye sorts by shape before it\n * reads, and forty identical rows of text defeat that.\n */\n icon?: ReactNode\n /**\n * A quiet note at the end of the row — what kind of thing this is, or its\n * current state. Not a description: a palette that prints a sentence per row\n * stops being scannable at about six of them.\n */\n meta?: ReactNode\n}\n\nexport function CommandItem({ shortcut, icon, meta, className, children, ...props }: CommandItemProps) {\n return (\n <CommandPrimitive.Item\n className={cn(\n 'flex cursor-pointer items-center gap-3 rounded-(--radius-row) px-3 py-2.5 text-[15px] text-(--ink-2) outline-none transition-colors duration-(--duration-fast)',\n // The highlighted row is a chosen state, and law 7 says a chosen state\n // reads --accent. The leading rule gives it an edge the eye catches\n // while scrolling, which a fill alone does not.\n 'relative data-[selected=true]:bg-(--accent-muted) data-[selected=true]:text-(--ink)',\n 'before:absolute before:inset-y-1 before:start-0 before:w-0.5 before:rounded-(--radius-pill) before:bg-transparent data-[selected=true]:before:bg-(--accent)',\n 'data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-(--disabled-opacity)',\n className,\n )}\n {...props}\n >\n {icon && (\n <span className=\"grid size-4 shrink-0 place-items-center text-(--ink-3-aa) [&_svg]:size-4\">\n {icon}\n </span>\n )}\n {children}\n {meta && <span className=\"ms-auto ps-3 mono-meta text-(--ink-3-aa)\">{meta}</span>}\n {shortcut && <Kbd className={cn(meta ? '' : 'ms-auto')}>{shortcut}</Kbd>}\n </CommandPrimitive.Item>\n )\n}\n\nexport interface CommandDialogProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n /** Names the palette for assistive tech. */\n label: string\n children: ReactNode\n}\n\n/**\n * The palette in a modal, which is how it is nearly always used.\n *\n * The dialog's own padding is removed: a palette is edge-to-edge, and its input\n * is the first thing focus lands on.\n */\nexport function CommandDialog({ open, onOpenChange, label, children }: CommandDialogProps) {\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent\n title={label}\n hideTitle\n showClose={false}\n // Above centre, not at it. A palette is read as a layer over the page\n // rather than as a message about it, and centring it puts the list\n // under the reader's own hands on a laptop.\n // `scrollbar-gutter: auto` undoes the `stable` that `Dialog`'s own\n // `scroll-slim` sets. The dialog never scrolls — the list inside it\n // does — but the reserved gutter was still there, so the list stopped\n // thirteen pixels short of the panel and drew its own hairline inside\n // an empty channel. Two bars, one of them permanently blank.\n className=\"top-[10vh] w-[min(94vw,44rem)] translate-y-0 overflow-hidden p-0 [scrollbar-gutter:auto]\"\n >\n {/* The modal has the whole screen, so its list may be taller than the\n default an inline menu should keep.\n\n `scroll-hairline` rather than `scroll-slim`: eleven pixels of grey\n down the side of a palette is the widest thing in it. */}\n <Command\n label={label}\n className=\"rounded-none border-0 [&_[cmdk-list]]:max-h-[26rem] [&_[cmdk-list]]:scroll-hairline\"\n >\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n )\n}\n\nexport default Command\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Command/Command.tsx"],"sourcesContent":["'use client'\n\nimport { Command as CommandPrimitive } from 'cmdk'\nimport { Search, type LucideIcon } from 'lucide-react'\nimport { isValidElement, type ComponentProps, type ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/**\n * True for a component type — `icon={Settings}` — rather than a rendered element.\n *\n * Everything a `ReactNode` can be is an element, a primitive, an array, another\n * iterable or a promise; a Lucide icon is none of those, it is a `forwardRef`\n * object. So both spellings are tellable apart at runtime, which is what lets\n * one prop take either — and it has to, because this prop meant the opposite\n * thing one import away and the wrong spelling failed at render, not at the\n * type.\n */\nfunction isIconComponent(icon: LucideIcon | ReactNode): icon is LucideIcon {\n if (isValidElement(icon)) return false\n if (typeof icon === 'function') return true\n if (typeof icon !== 'object' || icon === null) return false\n return !Array.isArray(icon) && !(Symbol.iterator in icon) && !('then' in icon)\n}\n\n/** The leading glyph, sized by this component when it was handed a component. */\nfunction iconNode(icon: LucideIcon | ReactNode): ReactNode {\n if (!isIconComponent(icon)) return icon\n const Icon = icon\n return <Icon size={16} strokeWidth={1.5} aria-hidden />\n}\nimport { Dialog, DialogContent } from '../Dialog/Dialog'\nimport { Kbd } from '../Kbd/Kbd'\n\n/**\n * A filterable list of actions — the ⌘K surface.\n *\n * Built on cmdk rather than on a `Select` or a menu, because the interaction is\n * neither: the list is filtered as you type, the highlighted row moves with the\n * arrow keys while focus STAYS in the input, and Enter runs the highlighted\n * row. That is the ARIA combobox pattern, and it is the part nobody should\n * hand-roll — `aria-activedescendant` moving without focus moving is precisely\n * where a home-made palette stops working with a screen reader.\n *\n * @example\n * <Command label=\"Command palette\">\n * <CommandInput placeholder=\"Type a command…\" />\n * <CommandList>\n * <CommandEmpty>Nothing matches.</CommandEmpty>\n * <CommandGroup heading=\"Navigate\">\n * <CommandItem onSelect={go}>Components</CommandItem>\n * </CommandGroup>\n * </CommandList>\n * </Command>\n */\nexport function Command({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive>) {\n return (\n <CommandPrimitive\n className={cn(\n 'flex w-full flex-col overflow-hidden rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) panel-blur',\n className,\n )}\n {...props}\n />\n )\n}\n\n/** The filter field. Carries the search icon and the combobox semantics. */\nexport function CommandInput({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Input>) {\n return (\n <div className=\"flex items-center gap-3 border-b border-(--rule) px-4\">\n <Search size={18} strokeWidth={1.5} aria-hidden className=\"shrink-0 text-(--ink-3-aa)\" />\n <CommandPrimitive.Input\n className={cn(\n 'h-13 w-full bg-transparent text-[15px] text-(--ink) outline-none placeholder:text-(--ink-3-aa)',\n className,\n )}\n {...props}\n />\n </div>\n )\n}\n\nexport function CommandList({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.List>) {\n return (\n <CommandPrimitive.List\n className={cn('max-h-72 overflow-y-auto overflow-x-hidden p-2 scroll-slim', className)}\n {...props}\n />\n )\n}\n\n/** Shown when the filter matches nothing. Say what would match, not \"no results\". */\nexport function CommandEmpty(props: ComponentProps<typeof CommandPrimitive.Empty>) {\n return (\n <CommandPrimitive.Empty\n className=\"px-3 py-10 text-center text-sm text-(--ink-3-aa)\"\n {...props}\n />\n )\n}\n\n/**\n * The key-hint strip along the bottom.\n *\n * A palette is a keyboard surface whose keys are invisible: nothing on screen\n * says the arrows move the row or that Enter runs it, and a reader who reaches\n * for the mouse has been failed by the design rather than by themselves.\n *\n * @example\n * <CommandFooter>\n * <CommandHint keys={['↑', '↓']}>navigate</CommandHint>\n * <CommandHint keys={['↵']}>open</CommandHint>\n * </CommandFooter>\n */\nexport function CommandFooter({ className, ...props }: ComponentProps<'div'>) {\n return (\n <div\n className={cn(\n 'flex items-center gap-4 border-t border-(--rule) bg-(--paper-2) px-3.5 py-2',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport interface CommandHintProps extends Omit<ComponentProps<'span'>, 'children'> {\n /** The keys this hint describes, printed as `Kbd` chips. */\n keys: string[]\n /** What they do — a verb, lowercase, no sentence. */\n children: ReactNode\n}\n\n/** One key-and-verb pair inside a `CommandFooter`. */\nexport function CommandHint({ keys, children, className, ...props }: CommandHintProps) {\n return (\n <span className={cn('flex items-center gap-1.5 mono-meta text-(--ink-3-aa)', className)} {...props}>\n {keys.map((key) => (\n <Kbd key={key}>{key}</Kbd>\n ))}\n {children}\n </span>\n )\n}\n\nexport function CommandGroup({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Group>) {\n return (\n <CommandPrimitive.Group\n className={cn(\n 'overflow-hidden [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:eyebrow [&_[cmdk-group-heading]]:text-(--ink-3-aa)',\n className,\n )}\n {...props}\n />\n )\n}\n\n/**\n * A divider between groups.\n *\n * Marked presentational, because ARIA permits a `listbox` to contain only\n * `option` and `group` — and the library renders this as `role=\"separator\"`,\n * which puts a critical `aria-required-children` violation inside every palette\n * that uses one. The grouping is already announced by the groups themselves, so\n * removing the divider from the accessibility tree loses nothing.\n */\nexport function CommandSeparator({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Separator>) {\n return (\n <CommandPrimitive.Separator\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn('my-1 h-px bg-(--rule)', className)}\n {...props}\n />\n )\n}\n\nexport interface CommandItemProps extends ComponentProps<typeof CommandPrimitive.Item> {\n /** A shortcut printed at the end of the row. */\n shortcut?: string\n /**\n * A leading glyph. Either spelling — `icon={<Settings size={16} />}` passes\n * the element, `icon={Settings}` passes the component and this sizes it.\n *\n * It is what makes a long list scannable — the eye sorts by shape before it\n * reads, and forty identical rows of text defeat that.\n */\n icon?: LucideIcon | ReactNode\n /**\n * A quiet note at the end of the row — what kind of thing this is, or its\n * current state. Not a description: a palette that prints a sentence per row\n * stops being scannable at about six of them.\n */\n meta?: ReactNode\n}\n\nexport function CommandItem({ shortcut, icon, meta, className, children, ...props }: CommandItemProps) {\n return (\n <CommandPrimitive.Item\n className={cn(\n 'flex cursor-pointer items-center gap-3 rounded-(--radius-row) px-3 py-2.5 text-[15px] text-(--ink-2) outline-none transition-colors duration-(--duration-fast)',\n // The highlighted row is a chosen state, and law 7 says a chosen state\n // reads --accent. The leading rule gives it an edge the eye catches\n // while scrolling, which a fill alone does not.\n 'relative data-[selected=true]:bg-(--accent-muted) data-[selected=true]:text-(--ink)',\n 'before:absolute before:inset-y-1 before:start-0 before:w-0.5 before:rounded-(--radius-pill) before:bg-transparent data-[selected=true]:before:bg-(--accent)',\n 'data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-(--disabled-opacity)',\n className,\n )}\n {...props}\n >\n {icon && (\n <span className=\"grid size-4 shrink-0 place-items-center text-(--ink-3-aa) [&_svg]:size-4\">\n {iconNode(icon)}\n </span>\n )}\n {children}\n {meta && <span className=\"ms-auto ps-3 mono-meta text-(--ink-3-aa)\">{meta}</span>}\n {shortcut && <Kbd className={cn(meta ? '' : 'ms-auto')}>{shortcut}</Kbd>}\n </CommandPrimitive.Item>\n )\n}\n\nexport interface CommandDialogProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n /** Names the palette for assistive tech. */\n label: string\n children: ReactNode\n}\n\n/**\n * The palette in a modal, which is how it is nearly always used.\n *\n * The dialog's own padding is removed: a palette is edge-to-edge, and its input\n * is the first thing focus lands on.\n */\nexport function CommandDialog({ open, onOpenChange, label, children }: CommandDialogProps) {\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent\n title={label}\n hideTitle\n showClose={false}\n // Above centre, not at it. A palette is read as a layer over the page\n // rather than as a message about it, and centring it puts the list\n // under the reader's own hands on a laptop.\n // `scrollbar-gutter: auto` undoes the `stable` that `Dialog`'s own\n // `scroll-slim` sets. The dialog never scrolls — the list inside it\n // does — but the reserved gutter was still there, so the list stopped\n // thirteen pixels short of the panel and drew its own hairline inside\n // an empty channel. Two bars, one of them permanently blank.\n className=\"top-[10vh] w-[min(94vw,44rem)] translate-y-0 overflow-hidden p-0 [scrollbar-gutter:auto]\"\n >\n {/* The modal has the whole screen, so its list may be taller than the\n default an inline menu should keep.\n\n `scroll-hairline` rather than `scroll-slim`: eleven pixels of grey\n down the side of a palette is the widest thing in it. */}\n <Command\n label={label}\n className=\"rounded-none border-0 [&_[cmdk-list]]:max-h-[26rem] [&_[cmdk-list]]:scroll-hairline\"\n >\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n )\n}\n\nexport default Command\n"],"mappings":"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","names":[]}
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { ComponentProps } from 'react';
2
+ import { ComponentProps, ReactNode } from 'react';
3
3
  import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
4
4
  import { LucideIcon } from 'lucide-react';
5
5
 
@@ -25,14 +25,39 @@ declare const ContextMenuTrigger: react.ForwardRefExoticComponent<ContextMenuPri
25
25
  */
26
26
  declare function ContextMenuContent({ className, ...rest }: ComponentProps<typeof ContextMenuPrimitive.Content>): react.JSX.Element;
27
27
  interface ContextMenuItemProps extends ComponentProps<typeof ContextMenuPrimitive.Item> {
28
- icon?: LucideIcon;
28
+ /**
29
+ * Optional leading icon. Either spelling — `icon={Copy}` passes the component
30
+ * and this sizes it, `icon={<Copy size={16} />}` passes the element and this
31
+ * places it.
32
+ */
33
+ icon?: LucideIcon | ReactNode;
34
+ /** Paints the row as destructive. Use for delete, revoke, disconnect. */
29
35
  destructive?: boolean;
30
36
  }
31
37
  /** A row. Highlight follows `data-highlighted`, which covers hover and keyboard. */
32
- declare function ContextMenuItem({ icon: Icon, destructive, className, children, ...rest }: ContextMenuItemProps): react.JSX.Element;
38
+ declare function ContextMenuItem({ icon, destructive, className, children, ...rest }: ContextMenuItemProps): react.JSX.Element;
33
39
  /** Hairline divider between groups. */
34
40
  declare function ContextMenuSeparator({ className, ...rest }: ComponentProps<typeof ContextMenuPrimitive.Separator>): react.JSX.Element;
35
- /** Mono eyebrow heading for a group. */
41
+ /**
42
+ * Mono eyebrow heading, on its own.
43
+ *
44
+ * Visual only: Radix renders it as a bare `<div>` with no role. Reach for
45
+ * `ContextMenuGroup` when the eyebrow is a HEADING over rows; this is right for
46
+ * a line that heads nothing.
47
+ */
36
48
  declare function ContextMenuLabel({ className, ...rest }: ComponentProps<typeof ContextMenuPrimitive.Label>): react.JSX.Element;
49
+ interface ContextMenuGroupProps extends ComponentProps<typeof ContextMenuPrimitive.Group> {
50
+ /** The eyebrow over the rows, and the group's accessible name. */
51
+ label?: ReactNode;
52
+ }
53
+ /**
54
+ * A named section of a menu.
55
+ *
56
+ * The eyebrow alone was a picture of a heading: Radix's `MenuLabel` carries no
57
+ * role and no `aria-labelledby` wiring, so a sighted reader saw sections and a
58
+ * screen-reader user got one undifferentiated list. This renders the group,
59
+ * renders the label inside it, and points the one at the other.
60
+ */
61
+ declare function ContextMenuGroup({ label, children, ...rest }: ContextMenuGroupProps): react.JSX.Element;
37
62
 
38
- export { ContextMenu, ContextMenuContent, ContextMenuItem, type ContextMenuItemProps, ContextMenuLabel, ContextMenuSeparator, ContextMenuTrigger, ContextMenu as default };
63
+ export { ContextMenu, ContextMenuContent, ContextMenuGroup, type ContextMenuGroupProps, ContextMenuItem, type ContextMenuItemProps, ContextMenuLabel, ContextMenuSeparator, ContextMenuTrigger, ContextMenu as default };
@@ -1,8 +1,23 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
4
+ import { isValidElement, useId } from "react";
4
5
  import { cn } from "../../lib/cn.js";
5
6
  import { useOverlayContainer } from "../../lib/overlay-container.js";
7
+ function isIconComponent(icon) {
8
+ if (isValidElement(icon)) return false;
9
+ if (typeof icon === "function") return true;
10
+ if (typeof icon !== "object" || icon === null) return false;
11
+ return !Array.isArray(icon) && !(Symbol.iterator in icon) && !("then" in icon);
12
+ }
13
+ function iconNode(icon) {
14
+ if (icon === void 0 || icon === null || icon === false) return null;
15
+ if (!isIconComponent(icon)) {
16
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "grid size-4 shrink-0 place-items-center [&_svg]:size-4", children: icon });
17
+ }
18
+ const Icon = icon;
19
+ return /* @__PURE__ */ jsx(Icon, { size: 16, strokeWidth: 1.5, className: "shrink-0", "aria-hidden": true });
20
+ }
6
21
  const ContextMenu = ContextMenuPrimitive.Root;
7
22
  const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
8
23
  function ContextMenuContent({
@@ -26,7 +41,7 @@ function ContextMenuContent({
26
41
  ) });
27
42
  }
28
43
  function ContextMenuItem({
29
- icon: Icon,
44
+ icon,
30
45
  destructive = false,
31
46
  className,
32
47
  children,
@@ -42,7 +57,7 @@ function ContextMenuItem({
42
57
  ),
43
58
  ...rest,
44
59
  children: [
45
- Icon && /* @__PURE__ */ jsx(Icon, { size: 16, strokeWidth: 1.5, className: "shrink-0", "aria-hidden": true }),
60
+ iconNode(icon),
46
61
  children
47
62
  ]
48
63
  }
@@ -66,10 +81,25 @@ function ContextMenuLabel({
66
81
  }
67
82
  );
68
83
  }
84
+ function ContextMenuGroup({ label, children, ...rest }) {
85
+ const id = useId();
86
+ return /* @__PURE__ */ jsxs(
87
+ ContextMenuPrimitive.Group,
88
+ {
89
+ "aria-labelledby": label === void 0 ? void 0 : id,
90
+ ...rest,
91
+ children: [
92
+ label !== void 0 && /* @__PURE__ */ jsx(ContextMenuLabel, { id, children: label }),
93
+ children
94
+ ]
95
+ }
96
+ );
97
+ }
69
98
  var ContextMenu_default = ContextMenu;
70
99
  export {
71
100
  ContextMenu,
72
101
  ContextMenuContent,
102
+ ContextMenuGroup,
73
103
  ContextMenuItem,
74
104
  ContextMenuLabel,
75
105
  ContextMenuSeparator,