shelving 1.236.2 → 1.238.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 (323) hide show
  1. package/extract/IndexExtractor.js +9 -2
  2. package/extract/MergingExtractor.d.ts +3 -1
  3. package/extract/MergingExtractor.js +41 -3
  4. package/extract/TypescriptExtractor.d.ts +1 -0
  5. package/extract/TypescriptExtractor.js +12 -2
  6. package/package.json +1 -1
  7. package/ui/README.md +35 -267
  8. package/ui/app/App.d.ts +14 -2
  9. package/ui/app/App.js +16 -2
  10. package/ui/app/App.md +58 -0
  11. package/ui/app/App.tsx +16 -2
  12. package/ui/block/Address.module.css +2 -1
  13. package/ui/block/Block.module.css +1 -1
  14. package/ui/block/Blockquote.module.css +5 -1
  15. package/ui/block/Caption.module.css +4 -1
  16. package/ui/block/Card.d.ts +1 -0
  17. package/ui/block/Card.js +1 -0
  18. package/ui/block/Card.md +85 -0
  19. package/ui/block/Card.module.css +7 -1
  20. package/ui/block/Card.tsx +1 -0
  21. package/ui/block/Definitions.module.css +6 -1
  22. package/ui/block/Divider.module.css +4 -1
  23. package/ui/block/Heading.d.ts +1 -0
  24. package/ui/block/Heading.js +1 -0
  25. package/ui/block/Heading.md +70 -0
  26. package/ui/block/Heading.module.css +5 -1
  27. package/ui/block/Heading.tsx +1 -0
  28. package/ui/block/Image.module.css +2 -1
  29. package/ui/block/Label.module.css +6 -1
  30. package/ui/block/List.d.ts +1 -0
  31. package/ui/block/List.js +1 -0
  32. package/ui/block/List.md +51 -0
  33. package/ui/block/List.module.css +2 -1
  34. package/ui/block/List.tsx +1 -0
  35. package/ui/block/Panel.d.ts +1 -0
  36. package/ui/block/Panel.js +1 -0
  37. package/ui/block/Panel.md +50 -0
  38. package/ui/block/Panel.module.css +3 -1
  39. package/ui/block/Panel.tsx +1 -0
  40. package/ui/block/Paragraph.d.ts +1 -0
  41. package/ui/block/Paragraph.js +1 -0
  42. package/ui/block/Paragraph.md +48 -0
  43. package/ui/block/Paragraph.module.css +2 -1
  44. package/ui/block/Paragraph.tsx +1 -0
  45. package/ui/block/Preformatted.module.css +10 -1
  46. package/ui/block/Prose.d.ts +1 -0
  47. package/ui/block/Prose.js +1 -0
  48. package/ui/block/Prose.md +49 -0
  49. package/ui/block/Prose.module.css +2 -1
  50. package/ui/block/Prose.tsx +1 -0
  51. package/ui/block/Section.d.ts +6 -0
  52. package/ui/block/Section.js +6 -0
  53. package/ui/block/Section.md +56 -0
  54. package/ui/block/Section.module.css +2 -1
  55. package/ui/block/Section.tsx +6 -0
  56. package/ui/block/Subheading.d.ts +1 -0
  57. package/ui/block/Subheading.js +1 -0
  58. package/ui/block/Subheading.md +58 -0
  59. package/ui/block/Subheading.module.css +5 -1
  60. package/ui/block/Subheading.tsx +1 -0
  61. package/ui/block/Table.d.ts +1 -0
  62. package/ui/block/Table.js +1 -0
  63. package/ui/block/Table.md +54 -0
  64. package/ui/block/Table.module.css +7 -1
  65. package/ui/block/Table.tsx +1 -0
  66. package/ui/block/Title.d.ts +1 -0
  67. package/ui/block/Title.js +1 -0
  68. package/ui/block/Title.md +57 -0
  69. package/ui/block/Title.module.css +5 -1
  70. package/ui/block/Title.tsx +1 -0
  71. package/ui/block/Video.module.css +7 -1
  72. package/ui/dialog/Dialog.d.ts +1 -0
  73. package/ui/dialog/Dialog.js +1 -0
  74. package/ui/dialog/Dialog.md +73 -0
  75. package/ui/dialog/Dialog.module.css +4 -1
  76. package/ui/dialog/Dialog.tsx +1 -0
  77. package/ui/dialog/Modal.d.ts +1 -0
  78. package/ui/dialog/Modal.js +1 -0
  79. package/ui/dialog/Modal.md +40 -0
  80. package/ui/dialog/Modal.module.css +8 -1
  81. package/ui/dialog/Modal.tsx +1 -0
  82. package/ui/docs/DocumentationButtons.d.ts +2 -0
  83. package/ui/docs/DocumentationButtons.js +2 -0
  84. package/ui/docs/DocumentationButtons.md +38 -0
  85. package/ui/docs/DocumentationButtons.tsx +2 -0
  86. package/ui/docs/DocumentationCard.d.ts +1 -0
  87. package/ui/docs/DocumentationCard.js +1 -0
  88. package/ui/docs/DocumentationCard.md +35 -0
  89. package/ui/docs/DocumentationCard.tsx +1 -0
  90. package/ui/docs/DocumentationKind.d.ts +1 -1
  91. package/ui/docs/DocumentationKind.js +9 -4
  92. package/ui/docs/DocumentationKind.tsx +10 -5
  93. package/ui/docs/DocumentationPage.d.ts +1 -0
  94. package/ui/docs/DocumentationPage.js +2 -0
  95. package/ui/docs/DocumentationPage.md +46 -0
  96. package/ui/docs/DocumentationPage.tsx +2 -0
  97. package/ui/form/Button.d.ts +1 -0
  98. package/ui/form/Button.js +1 -0
  99. package/ui/form/Button.md +88 -0
  100. package/ui/form/Button.module.css +10 -1
  101. package/ui/form/Button.tsx +1 -0
  102. package/ui/form/Field.d.ts +6 -1
  103. package/ui/form/Field.js +6 -1
  104. package/ui/form/Field.md +59 -0
  105. package/ui/form/Field.module.css +6 -1
  106. package/ui/form/Field.tsx +6 -1
  107. package/ui/form/Form.d.ts +1 -0
  108. package/ui/form/Form.md +118 -0
  109. package/ui/form/Form.module.css +2 -1
  110. package/ui/form/Form.tsx +1 -0
  111. package/ui/form/FormStore.md +47 -0
  112. package/ui/form/Input.module.css +10 -2
  113. package/ui/form/Popover.module.css +6 -1
  114. package/ui/form/Progress.module.css +4 -1
  115. package/ui/form/SchemaInput.d.ts +1 -0
  116. package/ui/form/SchemaInput.md +64 -0
  117. package/ui/form/SchemaInput.tsx +1 -0
  118. package/ui/inline/Code.d.ts +1 -0
  119. package/ui/inline/Code.js +1 -0
  120. package/ui/inline/Code.md +58 -0
  121. package/ui/inline/Code.module.css +7 -1
  122. package/ui/inline/Code.tsx +1 -0
  123. package/ui/inline/Deleted.module.css +4 -1
  124. package/ui/inline/Emphasis.module.css +1 -1
  125. package/ui/inline/Inserted.module.css +4 -1
  126. package/ui/inline/Link.d.ts +1 -0
  127. package/ui/inline/Link.js +1 -0
  128. package/ui/inline/Link.md +47 -0
  129. package/ui/inline/Link.module.css +4 -2
  130. package/ui/inline/Link.tsx +1 -0
  131. package/ui/inline/Mark.d.ts +1 -0
  132. package/ui/inline/Mark.js +1 -0
  133. package/ui/inline/Mark.md +40 -0
  134. package/ui/inline/Mark.module.css +3 -1
  135. package/ui/inline/Mark.tsx +1 -0
  136. package/ui/inline/Small.module.css +2 -1
  137. package/ui/inline/Strong.d.ts +1 -0
  138. package/ui/inline/Strong.js +1 -0
  139. package/ui/inline/Strong.md +34 -0
  140. package/ui/inline/Strong.module.css +2 -1
  141. package/ui/inline/Strong.tsx +1 -0
  142. package/ui/inline/Subscript.module.css +1 -1
  143. package/ui/inline/Superscript.module.css +1 -1
  144. package/ui/layout/CenteredLayout.d.ts +1 -0
  145. package/ui/layout/CenteredLayout.js +1 -0
  146. package/ui/layout/CenteredLayout.md +38 -0
  147. package/ui/layout/CenteredLayout.module.css +2 -1
  148. package/ui/layout/CenteredLayout.tsx +1 -0
  149. package/ui/layout/Layout.module.css +4 -2
  150. package/ui/layout/SidebarLayout.d.ts +1 -0
  151. package/ui/layout/SidebarLayout.js +1 -0
  152. package/ui/layout/SidebarLayout.md +65 -0
  153. package/ui/layout/SidebarLayout.module.css +5 -1
  154. package/ui/layout/SidebarLayout.tsx +1 -0
  155. package/ui/menu/Menu.d.ts +2 -0
  156. package/ui/menu/Menu.js +2 -0
  157. package/ui/menu/Menu.md +51 -0
  158. package/ui/menu/Menu.module.css +9 -1
  159. package/ui/menu/Menu.tsx +2 -0
  160. package/ui/menu/MenuItem.md +54 -0
  161. package/ui/misc/Catcher.d.ts +1 -0
  162. package/ui/misc/Catcher.js +1 -0
  163. package/ui/misc/Catcher.md +41 -0
  164. package/ui/misc/Catcher.tsx +1 -0
  165. package/ui/misc/Loading.d.ts +1 -0
  166. package/ui/misc/Loading.js +1 -0
  167. package/ui/misc/Loading.md +28 -0
  168. package/ui/misc/Loading.module.css +3 -1
  169. package/ui/misc/Loading.tsx +1 -0
  170. package/ui/misc/Mapper.md +40 -0
  171. package/ui/misc/Markup.d.ts +1 -0
  172. package/ui/misc/Markup.js +1 -0
  173. package/ui/misc/Markup.md +34 -0
  174. package/ui/misc/Markup.tsx +1 -0
  175. package/ui/misc/StatusIcon.d.ts +1 -0
  176. package/ui/misc/StatusIcon.js +1 -0
  177. package/ui/misc/StatusIcon.md +25 -0
  178. package/ui/misc/StatusIcon.module.css +2 -1
  179. package/ui/misc/StatusIcon.tsx +1 -0
  180. package/ui/misc/Tag.d.ts +1 -0
  181. package/ui/misc/Tag.js +1 -0
  182. package/ui/misc/Tag.md +47 -0
  183. package/ui/misc/Tag.module.css +9 -1
  184. package/ui/misc/Tag.tsx +1 -0
  185. package/ui/notice/Message.module.css +3 -1
  186. package/ui/notice/Notice.d.ts +1 -0
  187. package/ui/notice/Notice.js +1 -0
  188. package/ui/notice/Notice.md +53 -0
  189. package/ui/notice/Notice.module.css +7 -1
  190. package/ui/notice/Notice.tsx +1 -0
  191. package/ui/notice/Notices.d.ts +1 -0
  192. package/ui/notice/Notices.js +1 -0
  193. package/ui/notice/Notices.md +59 -0
  194. package/ui/notice/Notices.module.css +3 -1
  195. package/ui/notice/Notices.tsx +1 -0
  196. package/ui/page/HTML.d.ts +1 -0
  197. package/ui/page/HTML.js +1 -0
  198. package/ui/page/HTML.md +36 -0
  199. package/ui/page/HTML.tsx +1 -0
  200. package/ui/page/Head.d.ts +1 -0
  201. package/ui/page/Head.js +1 -0
  202. package/ui/page/Head.md +26 -0
  203. package/ui/page/Head.tsx +1 -0
  204. package/ui/page/Page.d.ts +1 -0
  205. package/ui/page/Page.js +1 -0
  206. package/ui/page/Page.md +42 -0
  207. package/ui/page/Page.tsx +1 -0
  208. package/ui/router/Navigation.d.ts +1 -0
  209. package/ui/router/Navigation.js +1 -0
  210. package/ui/router/Navigation.md +41 -0
  211. package/ui/router/Navigation.tsx +1 -0
  212. package/ui/router/NavigationStore.md +34 -0
  213. package/ui/router/Router.d.ts +1 -0
  214. package/ui/router/Router.js +1 -0
  215. package/ui/router/Router.md +143 -0
  216. package/ui/router/Router.tsx +1 -0
  217. package/ui/style/Color.module.css +28 -1
  218. package/ui/style/Duration.d.ts +24 -0
  219. package/ui/style/Duration.js +13 -0
  220. package/ui/style/Duration.module.css +23 -0
  221. package/ui/style/Duration.tsx +31 -0
  222. package/ui/style/Flex.module.css +3 -1
  223. package/ui/style/Font.d.ts +24 -0
  224. package/ui/style/Font.js +13 -0
  225. package/ui/style/Font.module.css +50 -0
  226. package/ui/style/Font.tsx +31 -0
  227. package/ui/style/Gap.module.css +2 -1
  228. package/ui/style/Padding.module.css +2 -1
  229. package/ui/style/Radius.d.ts +24 -0
  230. package/ui/style/Radius.js +13 -0
  231. package/ui/style/Radius.module.css +43 -0
  232. package/ui/style/Radius.tsx +31 -0
  233. package/ui/style/Scroll.module.css +4 -0
  234. package/ui/style/Shadow.d.ts +24 -0
  235. package/ui/style/Shadow.js +13 -0
  236. package/ui/style/Shadow.module.css +42 -0
  237. package/ui/style/Shadow.tsx +31 -0
  238. package/ui/style/Size.d.ts +24 -0
  239. package/ui/style/Size.js +13 -0
  240. package/ui/style/Size.module.css +69 -0
  241. package/ui/style/Size.tsx +31 -0
  242. package/ui/style/Space.module.css +19 -1
  243. package/ui/style/Status.module.css +2 -1
  244. package/ui/style/Stroke.d.ts +24 -0
  245. package/ui/style/Stroke.js +13 -0
  246. package/ui/style/Stroke.module.css +26 -0
  247. package/ui/style/Stroke.tsx +31 -0
  248. package/ui/style/TINT_CLASS.md +55 -0
  249. package/ui/style/Tint.module.css +4 -1
  250. package/ui/style/Typography.d.ts +10 -26
  251. package/ui/style/Typography.js +8 -3
  252. package/ui/style/Typography.module.css +2 -66
  253. package/ui/style/Typography.tsx +17 -31
  254. package/ui/style/Weight.d.ts +24 -0
  255. package/ui/style/Weight.js +13 -0
  256. package/ui/style/Weight.module.css +42 -0
  257. package/ui/style/Weight.tsx +31 -0
  258. package/ui/style/Width.module.css +7 -1
  259. package/ui/style/getColorClass.md +47 -0
  260. package/ui/style/getDurationClass.md +18 -0
  261. package/ui/style/getFontClass.md +39 -0
  262. package/ui/style/getRadiusClass.md +24 -0
  263. package/ui/style/getShadowClass.md +23 -0
  264. package/ui/style/getSizeClass.md +42 -0
  265. package/ui/style/getSpaceClass.md +35 -0
  266. package/ui/style/getStrokeClass.md +20 -0
  267. package/ui/style/getWeightClass.md +25 -0
  268. package/ui/style/getWidthClass.md +18 -0
  269. package/ui/style/index.d.ts +7 -0
  270. package/ui/style/index.js +7 -0
  271. package/ui/style/index.tsx +7 -0
  272. package/ui/style/layers.css +26 -0
  273. package/ui/transition/CollapseTransition.d.ts +1 -0
  274. package/ui/transition/CollapseTransition.js +1 -0
  275. package/ui/transition/CollapseTransition.md +34 -0
  276. package/ui/transition/CollapseTransition.tsx +1 -0
  277. package/ui/transition/FadeTransition.d.ts +1 -0
  278. package/ui/transition/FadeTransition.js +1 -0
  279. package/ui/transition/FadeTransition.md +36 -0
  280. package/ui/transition/FadeTransition.tsx +1 -0
  281. package/ui/transition/HorizontalTransition.d.ts +1 -0
  282. package/ui/transition/HorizontalTransition.js +1 -0
  283. package/ui/transition/HorizontalTransition.md +44 -0
  284. package/ui/transition/HorizontalTransition.tsx +1 -0
  285. package/ui/transition/Transition.d.ts +1 -0
  286. package/ui/transition/Transition.js +1 -0
  287. package/ui/transition/Transition.md +70 -0
  288. package/ui/transition/Transition.tsx +1 -0
  289. package/ui/transition/VerticalTransition.d.ts +1 -0
  290. package/ui/transition/VerticalTransition.js +1 -0
  291. package/ui/transition/VerticalTransition.md +34 -0
  292. package/ui/transition/VerticalTransition.tsx +1 -0
  293. package/ui/tree/TreeApp.d.ts +1 -0
  294. package/ui/tree/TreeApp.js +1 -0
  295. package/ui/tree/TreeApp.md +59 -0
  296. package/ui/tree/TreeApp.tsx +1 -0
  297. package/ui/tree/TreeMenu.d.ts +1 -0
  298. package/ui/tree/TreeMenu.js +1 -0
  299. package/ui/tree/TreeMenu.md +35 -0
  300. package/ui/tree/TreeMenu.tsx +1 -0
  301. package/ui/tree/TreeSidebar.d.ts +1 -0
  302. package/ui/tree/TreeSidebar.js +1 -0
  303. package/ui/tree/TreeSidebar.md +24 -0
  304. package/ui/tree/TreeSidebar.tsx +1 -0
  305. package/ui/util/getClass.md +55 -0
  306. package/ui/util/notify.md +50 -0
  307. package/ui/util/requireContext.md +24 -0
  308. package/ui/app/README.md +0 -32
  309. package/ui/block/README.md +0 -144
  310. package/ui/dialog/README.md +0 -80
  311. package/ui/docs/README.md +0 -71
  312. package/ui/form/README.md +0 -165
  313. package/ui/inline/README.md +0 -86
  314. package/ui/layout/README.md +0 -71
  315. package/ui/menu/README.md +0 -33
  316. package/ui/misc/README.md +0 -121
  317. package/ui/notice/README.md +0 -94
  318. package/ui/page/README.md +0 -56
  319. package/ui/router/README.md +0 -186
  320. package/ui/style/base.css +0 -161
  321. package/ui/transition/README.md +0 -80
  322. package/ui/tree/README.md +0 -78
  323. package/ui/util/README.md +0 -153
@@ -62,8 +62,15 @@ function _absorbIndex(element, index) {
62
62
  // Recurse first so nested levels absorb their own indexes before we look at this one.
63
63
  // Only descend into elements that actually have children — leaving childless leaves (e.g. files) untouched, including their `undefined` children.
64
64
  const recursed = Array.from(walkElements(element.props.children)).map(child => notNullish(child.props.children) ? _absorbIndex(child, index) : child);
65
- // Find the index child by key.
66
- const indexChild = recursed.find(child => anyMatch(child.key, ...index));
65
+ // Find the index child by pattern priority: the earliest `index` pattern with a matching child wins, so a
66
+ // README.md is preferred over an index.ts barrel even when the barrel is listed first on disk (a barrel carries
67
+ // no prose, so absorbing it instead of the README would silently drop the directory's documentation).
68
+ let indexChild;
69
+ for (const matcher of index) {
70
+ indexChild = recursed.find(child => anyMatch(child.key, matcher));
71
+ if (indexChild)
72
+ break;
73
+ }
67
74
  if (!indexChild)
68
75
  return { ...element, props: { ...element.props, children: recursed } };
69
76
  // Fold the index child into the parent, and drop it from children.
@@ -20,9 +20,11 @@ export interface MergingExtractorOptions {
20
20
  /**
21
21
  * Through extractor that walks a tree of `tree-element` nodes and merges sibling tree elements whose keys match a `merges` template pair.
22
22
  * - Purely key-based: it doesn't care whether siblings are directories or files — any element with children is processed, at every level.
23
+ * - **Token-first:** a secondary (e.g. `Card.md`) is preferentially folded into the same-named documentation token declared by a sibling file (the `Card` symbol inside `Card.tsx`), not the file element. Exported names are unique across the package, so this is unambiguous — and it lands the prose on the published symbol page, which survives the module flatten (file-element content does not).
24
+ * - **File fallback:** when no same-named token exists, the secondary folds into a sibling *file* whose key matches a `merges` candidate (whole-file prose like `template.md` → `template.ts`).
23
25
  * - The primary (winning) element keeps its `key`, `source`, and `type`; the secondary's `title`, `description`,
24
26
  * `content`, and `children` are folded in via `mergeTreeElements()`.
25
- * - A secondary with no matching primary is left in place — pure prose files (e.g. `concepts.md` with no `concepts.ts`) stand alone.
27
+ * - A secondary with no matching token or file is left in place — pure prose files (e.g. `concepts.md` with no `concepts.ts`) stand alone.
26
28
  *
27
29
  * @example
28
30
  * ```ts
@@ -15,9 +15,11 @@ const DEFAULT_MERGES = {
15
15
  /**
16
16
  * Through extractor that walks a tree of `tree-element` nodes and merges sibling tree elements whose keys match a `merges` template pair.
17
17
  * - Purely key-based: it doesn't care whether siblings are directories or files — any element with children is processed, at every level.
18
+ * - **Token-first:** a secondary (e.g. `Card.md`) is preferentially folded into the same-named documentation token declared by a sibling file (the `Card` symbol inside `Card.tsx`), not the file element. Exported names are unique across the package, so this is unambiguous — and it lands the prose on the published symbol page, which survives the module flatten (file-element content does not).
19
+ * - **File fallback:** when no same-named token exists, the secondary folds into a sibling *file* whose key matches a `merges` candidate (whole-file prose like `template.md` → `template.ts`).
18
20
  * - The primary (winning) element keeps its `key`, `source`, and `type`; the secondary's `title`, `description`,
19
21
  * `content`, and `children` are folded in via `mergeTreeElements()`.
20
- * - A secondary with no matching primary is left in place — pure prose files (e.g. `concepts.md` with no `concepts.ts`) stand alone.
22
+ * - A secondary with no matching token or file is left in place — pure prose files (e.g. `concepts.md` with no `concepts.ts`) stand alone.
21
23
  *
22
24
  * @example
23
25
  * ```ts
@@ -70,10 +72,24 @@ function _mergeElement(element, merges) {
70
72
  }
71
73
  /** Merge same-template siblings at one directory level. */
72
74
  function _mergeChildren(children, merges) {
73
- // Index children by key so we can look up primary candidates quickly.
75
+ // Index children by key so we can look up primary file candidates quickly.
74
76
  const byKey = new Map();
75
77
  for (const child of children)
76
78
  byKey.set(child.key, child);
79
+ // Index exported documentation tokens by name, recording the key of the file element that declares each.
80
+ // Exported names are unique across the package, so a `Foo.md` can target the symbol `Foo` directly.
81
+ const tokensByName = new Map();
82
+ for (const child of children) {
83
+ if (child.type !== "tree-element")
84
+ continue;
85
+ for (const token of walkElements(child.props.children)) {
86
+ const t = token;
87
+ if (t.type === "tree-documentation")
88
+ tokensByName.set(t.props.name, { fileKey: child.key, token: t });
89
+ }
90
+ }
91
+ // Accumulate token replacements per declaring file: fileKey → (tokenKey → merged token).
92
+ const tokenUpdates = new Map();
77
93
  // Walk in original order, deciding for each whether it's a secondary that should fold into a primary.
78
94
  const skip = new Set();
79
95
  for (const secondary of children) {
@@ -81,6 +97,18 @@ function _mergeChildren(children, merges) {
81
97
  const matches = matchTemplate(lhs, secondary.key);
82
98
  if (!matches)
83
99
  continue;
100
+ // Prefer folding a `.md` into the same-named documentation token (e.g. `Card.md` → the `Card` component) so the
101
+ // prose lands on the published symbol page — the module flatten keeps tokens but discards file-element content.
102
+ const base = Object.values(matches)[0];
103
+ const hit = base ? tokensByName.get(base) : undefined;
104
+ if (hit && hit.token !== secondary) {
105
+ const fileMap = tokenUpdates.get(hit.fileKey) ?? new Map();
106
+ fileMap.set(hit.token.key, mergeTreeElements(fileMap.get(hit.token.key) ?? hit.token, secondary));
107
+ tokenUpdates.set(hit.fileKey, fileMap);
108
+ skip.add(secondary);
109
+ break;
110
+ }
111
+ // Otherwise fall back to merging into a sibling file (whole-file prose like `template.md` → `template.ts`).
84
112
  for (const rhs of candidates) {
85
113
  const primaryKey = renderTemplate(rhs, matches);
86
114
  const primary = byKey.get(primaryKey);
@@ -94,5 +122,15 @@ function _mergeChildren(children, merges) {
94
122
  break;
95
123
  }
96
124
  }
97
- return children.filter(c => !skip.has(c)).map(c => byKey.get(c.key) ?? c);
125
+ // Rebuild: drop merged `.md` secondaries, apply file-level merges, and fold token updates into their file elements.
126
+ return children
127
+ .filter(c => !skip.has(c))
128
+ .map(c => {
129
+ const updated = byKey.get(c.key) ?? c;
130
+ const fileMap = tokenUpdates.get(c.key);
131
+ if (!fileMap)
132
+ return updated;
133
+ const merged = Array.from(walkElements(updated.props.children), child => fileMap.get(child.key) ?? child);
134
+ return { ...updated, props: { ...updated.props, children: merged } };
135
+ });
98
136
  }
@@ -6,6 +6,7 @@ import { FileExtractor } from "./FileExtractor.js";
6
6
  * - Extracts exported, public, non-`_`-prefixed declarations as `tree-documentation` children.
7
7
  * - Overloaded declarations sharing a name are merged into a single `tree-documentation` with multiple `signatures`.
8
8
  * - Class declarations synthesise their `signatures`, `params`, and `returns` from the constructor — `new ClassName<…>(…)` including generics, one signature per constructor overload, with `returns` set to the class type. Param descriptions come from the constructor's `@param` first, then the class's `@param`.
9
+ * - A `@kind` tag in a symbol's JSDoc overrides the inferred kind — e.g. `@kind component` relabels a React component (otherwise a `function`) so the docs site groups and colours it as a component. The override also drops the trailing `()` from the title, since a non-function kind reads as a bare name.
9
10
  * - Top-of-file JSDoc comment becomes the file's `content`.
10
11
  * - Sets `description` (a plain-text summary from the first JSDoc paragraph) on the file and every `tree-documentation` child.
11
12
  * - Sets `title` on every `tree-documentation` child — `name()` for functions and methods, bare `name` for other kinds. Parent class context comes from the `class` prop ("member of …" affordance), never the title.
@@ -7,6 +7,7 @@ import { extractMarkdownProps } from "./MarkupExtractor.js";
7
7
  * - Extracts exported, public, non-`_`-prefixed declarations as `tree-documentation` children.
8
8
  * - Overloaded declarations sharing a name are merged into a single `tree-documentation` with multiple `signatures`.
9
9
  * - Class declarations synthesise their `signatures`, `params`, and `returns` from the constructor — `new ClassName<…>(…)` including generics, one signature per constructor overload, with `returns` set to the class type. Param descriptions come from the constructor's `@param` first, then the class's `@param`.
10
+ * - A `@kind` tag in a symbol's JSDoc overrides the inferred kind — e.g. `@kind component` relabels a React component (otherwise a `function`) so the docs site groups and colours it as a component. The override also drops the trailing `()` from the title, since a non-function kind reads as a bare name.
10
11
  * - Top-of-file JSDoc comment becomes the file's `content`.
11
12
  * - Sets `description` (a plain-text summary from the first JSDoc paragraph) on the file and every `tree-documentation` child.
12
13
  * - Sets `title` on every `tree-documentation` child — `name()` for functions and methods, bare `name` for other kinds. Parent class context comes from the `class` prop ("member of …" affordance), never the title.
@@ -127,7 +128,8 @@ function _extractStatement(statement, source) {
127
128
  if (name.startsWith("_"))
128
129
  return;
129
130
  const jsDoc = _getJSDoc(statement, source);
130
- const kind = _getKind(statement);
131
+ // A `@kind` tag overrides the AST-inferred kind (e.g. `@kind component` for a React component declared as a function).
132
+ const kind = jsDoc?.kind ?? _getKind(statement);
131
133
  if (!kind)
132
134
  return;
133
135
  const signatures = _getSignatures(statement, source, name);
@@ -442,10 +444,11 @@ function _getClassMembers(statement, source, className) {
442
444
  }
443
445
  /**
444
446
  * `@rule` names handled (parsed or deliberately discarded) — everything else is appended to `unhandled` as raw markup.
447
+ * - `@kind` is parsed by `_parseJSDocKind` to override the AST-inferred kind, so it must not also leak into `unhandled`.
445
448
  * - `@see` is recognised here purely to strip it: it's a VS Code hover affordance (a link back to the docs site) and must
446
449
  * never leak into the rendered page content. It has no dedicated parser; it's simply discarded from the `unhandled` bucket.
447
450
  */
448
- const _HANDLED_RULES = new Set(["param", "params", "return", "returns", "throw", "throws", "example", "examples", "see"]);
451
+ const _HANDLED_RULES = new Set(["kind", "param", "params", "return", "returns", "throw", "throws", "example", "examples", "see"]);
449
452
  /** Extract JSDoc from a node. */
450
453
  function _getJSDoc(node, source) {
451
454
  const ranges = ts.getLeadingCommentRanges(source.text, node.pos);
@@ -460,6 +463,7 @@ function _getJSDoc(node, source) {
460
463
  if (!text.startsWith("/**"))
461
464
  continue;
462
465
  const description = _parseJSDocComment(text);
466
+ const kind = _parseJSDocKind(text);
463
467
  const params = _parseJSDocParams(text);
464
468
  const returns = _parseJSDocReturns(text);
465
469
  const throws = _parseJSDocThrows(text);
@@ -467,6 +471,7 @@ function _getJSDoc(node, source) {
467
471
  const unhandled = _parseJSDocUnhandled(text);
468
472
  return {
469
473
  description: description || undefined,
474
+ kind,
470
475
  params: params.length ? params : undefined,
471
476
  returns: returns.length ? returns : undefined,
472
477
  throws: throws.length ? throws : undefined,
@@ -529,6 +534,11 @@ function _parseJSDocComment(text) {
529
534
  const result = description.join("\n").trim();
530
535
  return result || undefined;
531
536
  }
537
+ /** Parse a single `@kind` override from a JSDoc comment (e.g. `@kind component`), or `undefined` when absent. */
538
+ function _parseJSDocKind(text) {
539
+ // `@kind name` — a single identifier-ish token (letters, digits, hyphens).
540
+ return text.match(/@kind\s+([\w-]+)/)?.[1];
541
+ }
532
542
  /** Parse `@param` tags from a JSDoc comment. Duplicates are kept (overloads). */
533
543
  function _parseJSDocParams(text) {
534
544
  const results = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.236.2",
3
+ "version": "1.238.0",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
package/ui/README.md CHANGED
@@ -8,299 +8,67 @@ The `ui` module exists so an app never hand-rolls the same form field, card, or
8
8
 
9
9
  ## How components work
10
10
 
11
- A few conventions run through every component (see also the React Components section of `AGENTS.md`):
11
+ A few conventions run through every component:
12
12
 
13
- - **Styling props, not CSS.** Visual options are props on the component — enumerated props for the scales (`color="red"`, `size="large"`, `space="none"`) and boolean props for on/off variants (`<Button strong>`, `<Section narrow>`, `<Flex wrap>`). Each maps to a class in a CSS Module. You never pass `style` or raw `className`.
14
- - **Composition.** Higher-level components — a `*Page`, a `*Card` — take their identity from library components like `Card`, `Section`, `Button`, and `Tag` rather than shipping their own styling.
13
+ - **Styling props are for one-off overrides.** Visual options are props on the component — enumerated props for the scales (`color="red"`, `size="large"`, `space="none"`) and boolean props for on/off variants (`<Button strong>`, `<Section narrow>`, `<Flex wrap>`). Each maps to a class in a CSS Module. Reach for them when a component needs to look different in *one place* — the way the docs site tints its accents purple — not as the way to dress a whole app. You never pass `style` or raw `className`.
14
+ - **Composition.** Higher-level components — a `*Page`, a `*Card` — take their identity from library components like [`Card`](/ui/Card), [`Section`](/ui/Section), [`Button`](/ui/Button), and [`Tag`](/ui/Tag) rather than shipping their own styling.
15
15
  - **Sentence case.** Titles, headings, and button labels capitalise only the first word.
16
- - **Theming via CSS variables.** Colour and spacing come from CSS custom properties with fallback chains, so a theme is a small set of variable overrides.
16
+ - **Theme with CSS.** An app-wide custom look is a CSS file, not a wall of props. Write a `theme.css` that overrides the base design-token variables (and, where needed, per-component hooks) at `:root`, and import it after the library styles. The recommended workflow is to spend time tuning those variables to match your design — see [Theming](#theming) below.
17
17
 
18
- ## Styling system
18
+ ## The styling system
19
19
 
20
- The styling system has four moving parts, all defined in [style/](./style/): design tokens, the tint scale, cascade layers, and the styling props. Components compose them in a predictable shape; consumers theme by overriding CSS custom properties at `:root`.
20
+ The styling system lives in `style/` and has four moving parts: design tokens, the tint scale, cascade layers, and the styling props. Components compose them in a predictable shape; consumers theme by overriding CSS custom properties at `:root`.
21
21
 
22
- ### Design tokens
22
+ **Design tokens.** Every design-token constant is defined at `:root`, split across the themed token modules in `style/` — each module owns one domain, documents the variables it defines, and is the page a theme author overrides. `style/layers.css` is the cascade-layer anchor; every `*.module.css` `@import`s it plus the specific token modules it references, so the tokens and the layer order reach every component regardless of bundle order. The domains are: colours ([`getColorClass`](/ui/getColorClass)), font sizes ([`getSizeClass`](/ui/getSizeClass)), font weights ([`getWeightClass`](/ui/getWeightClass)), font faces ([`getFontClass`](/ui/getFontClass)), spacing ([`getSpaceClass`](/ui/getSpaceClass)), widths ([`getWidthClass`](/ui/getWidthClass)), radii ([`getRadiusClass`](/ui/getRadiusClass)), strokes ([`getStrokeClass`](/ui/getStrokeClass)), shadows ([`getShadowClass`](/ui/getShadowClass)), and durations ([`getDurationClass`](/ui/getDurationClass)). Each also defines the semantic aliases a theme usually targets (`--color-primary`, `--color-link`, `--space-paragraph`, …). Components read tokens via `var(--token)`.
23
23
 
24
- [`style/base.css`](./style/base.css) defines every design-token constant at `:root` colours (`--color-*`), font sizes (`--size-*`), spacing (`--space-*`), radii (`--radius-*`), strokes (`--stroke-*`), shadows (`--shadow-*`), durations (`--duration-*`), font weights (`--weight-*`), and font faces (`--font-*`). Components read these via `var(--token)`; themes override them at `:root` in their own CSS file. No class selectors needed.
24
+ **The tint scale.** All colour flows from one anchor variable, `--tint-50`, from which a 21-step ladder is computed and *recomputed* under [`TINT_CLASS`](/ui/TINT_CLASS) the heart of how `color=` and `status=` retint a whole subtree. The ladder, the recompute trick, the painting conventions, and the theming guide all live on the [`TINT_CLASS`](/ui/TINT_CLASS) page.
25
25
 
26
- `base.css` is `@import`ed at the top of every `*.module.css` in the codebase that's how the design tokens (and the cascade layer order) reach every component automatically, regardless of bundle order.
26
+ **Cascade layers.** Styles are ordered by `@layer`, lowest to highest priority: `defaults` (`:root` tokens, the tint ladder, body baseline) → `components` (the bulk of the CSS: `.card`, `.button`, …) → `variants` (cross-cutting opt-in modifiers, which always beat components) `overrides` (top-priority structural fixes like `:first-child` / `:last-child` margin collapses). Unlayered rules beat all layered rules, so a theme should set tokens at `:root` or wrap its rules in `@layer`.
27
27
 
28
- Alongside the raw scales sit semantic aliases that themes usually target instead: `--color-primary` / `--color-secondary` / `--color-tertiary` (brand), `--color-link` / `--color-focus` (interaction), `--color-success` / `--color-warning` / `--color-failure` (status), `--space-paragraph` / `--space-section` (rhythm).
28
+ **Styling props.** The cross-cutting visual options are props, each backed by a helper in `style/` that maps the prop to a class. Colour and status move the tint anchor — [`getColorClass`](/ui/getColorClass) and [`getStatusClass`](/ui/getStatusClass); font size, weight, and family come from [`getSizeClass`](/ui/getSizeClass), [`getWeightClass`](/ui/getWeightClass), and [`getFontClass`](/ui/getFontClass), which [`getTypographyClass`](/ui/getTypographyClass) combines with text alignment and tint; spacing, padding, and gap from [`getSpaceClass`](/ui/getSpaceClass), [`getPaddingClass`](/ui/getPaddingClass), and [`getGapClass`](/ui/getGapClass); width constraints from [`getWidthClass`](/ui/getWidthClass); flex layout from [`getFlexClass`](/ui/getFlexClass); and opt-in scrolling from [`getScrollClass`](/ui/getScrollClass). Each helper's page lists its exact prop values and what they set. A component opts into the props it wants by extending the matching `*Props` interfaces and composing the `getXxxClass(props)` calls.
29
29
 
30
- ### The tint scale
30
+ Each painting component also exposes its own theme hooks — a single tint hook (`--card-tint`) to recolour the whole component, plus per-property hooks (`--card-background`, `--card-radius`, …) for surgical overrides. Those are documented in each component's own **Styling** section (see [`Card`](/ui/Card) for the precedent).
31
31
 
32
- All colour in the library flows from a single anchor variable, **`--tint-50`**, defined in [`style/Tint.module.css`](./style/Tint.module.css). From that one hue, a 21-step ladder — `--tint-00`, `--tint-05`, … `--tint-95`, `--tint-100` — is computed with `color-mix()` in OKLCH: `--tint-00` is black, `--tint-50` is the anchor hue itself, `--tint-100` is white, and every step in between mixes the anchor toward one extreme or the other.
32
+ ## Theming
33
33
 
34
- The anchor defaults to `--color-gray`, so the default ladder is a neutral grey ramp grey is just the colour you get when nothing moves the anchor. The page baseline paints from the extremes: `body { color: var(--tint-00); background: var(--tint-100); }`.
35
-
36
- The ladder is computed at `:root` and *recomputed* under the `.tint` class (`TINT_CLASS` in [`style/Tint.tsx`](./style/Tint.tsx)). That recomputation is the whole trick: move the anchor at any scope, apply `.tint`, and all 21 shades rebuild from the new hue at that scope. Colour and status classes are exactly that —
34
+ The recommended way to give an app its own look is a **theme stylesheet**, not styling props. Create a `theme.css`, override the base design-token variables at `:root`, and import it after the library styles:
37
35
 
38
36
  ```css
39
- /* Color.module.css — a colour variant just moves the anchor. */
40
- .red {
41
- --tint-50: var(--color-red);
42
- }
43
-
44
- /* Status.module.css a status maps a semantic name onto a palette colour. */
45
- .success {
46
- --tint-50: var(--color-success);
37
+ /* theme.css — imported after shelving/ui styles */
38
+ :root {
39
+ --color-primary: oklch(58% 0.25 300); /* purple brand */
40
+ --font-body: "Inter", system-ui;
41
+ --radius: 0.5rem; /* tighter corners everywhere */
42
+ --space: 1.125rem; /* roomier spacing scale */
47
43
  }
48
44
  ```
49
45
 
50
- `getColorClass()` and `getStatusClass()` compose `TINT_CLASS` automatically, so `<Card color="red">` is: move the anchor to red, rebuild the ladder, and let the card paint from the same steps it always paints from. Descendants inherit the rebuilt ladder, which is why a `<Tag>` or `<Preformatted>` nested in a red card tints to match it.
51
-
52
- Components paint from the ladder by convention:
53
-
54
- | Step | Used for |
55
- |---|---|
56
- | `--tint-00` | Body text, headings — maximum contrast |
57
- | `--tint-50` | The hue itself — accents, labels, `Tag` backgrounds, `strong` button backgrounds |
58
- | `--tint-80` | Borders |
59
- | `--tint-90` | Surfaces — `Card`, `Preformatted`, `Button` backgrounds |
60
- | `--tint-95` | Hover state of those surfaces |
61
- | `--tint-100` | The page background; text on `--tint-50` backgrounds |
62
-
63
- Pairings follow contrast: long text reads at `00`-on-`90` or `00`-on-`100`; short labels read at `100`-on-`50`.
64
-
65
- ### Cascade layers
66
-
67
- Order, lowest to highest priority:
68
-
69
- | Layer | What's in it |
70
- |---|---|
71
- | `defaults` | `:root` design tokens, the tint ladder, body baseline typography, low-priority opt-in defaults |
72
- | `components` | Component-defining CSS — the bulk of the codebase: `.card`, `.button`, `.notice`, `.heading`, etc. |
73
- | `variants` | Cross-cutting opt-in modifiers (Color, Status, Spacing, Padding, Gap, Width, Typography, Flex, Scroll). Always beat components. |
74
- | `overrides` | Top-priority structural overrides — `:first-child` / `:last-child` margin collapses, which need to beat variant-set margins |
46
+ Each base token lives in a themed module that documents the variables it defines and which ones a theme usually overrides. Work from broadest (a palette colour or scale root) to narrowest (a single semantic alias):
75
47
 
76
- **Unlayered rules beat all layered rules.** A consumer theme that wraps its overrides in `@layer theme { … }` or just sets tokens at `:root` is fine; one that writes raw class selectors without participating in the layer system will silently dominate variants.
48
+ - [weight](/ui/getWeightClass) · [size](/ui/getSizeClass) · [font](/ui/getFontClass) typography (`--weight-*`, `--size-*`, `--font-*`, `--case-label`).
49
+ - [color](/ui/getColorClass) — palette, semantic, and brand colours (`--color-*`).
50
+ - [space](/ui/getSpaceClass) · [width](/ui/getWidthClass) — layout spacing and widths (`--space-*`, `--width-*`).
51
+ - [radius](/ui/getRadiusClass) · [stroke](/ui/getStrokeClass) · [shadow](/ui/getShadowClass) · [duration](/ui/getDurationClass) — surface tokens (`--radius-*`, `--stroke-*`, `--shadow-*`, `--duration-*`).
77
52
 
78
- ### Styling props
53
+ The **tint ladder** is the one exception that doesn't follow the override-a-variable pattern: its 21 steps are *recomputed* from a single anchor inside every tinted scope, so you move the anchor rather than overriding individual steps. See [`TINT_CLASS`](/ui/TINT_CLASS) for the full theming guide, and each component's **Styling** section for its per-component hooks.
79
54
 
80
- [`style/`](./style/) exports the cross-cutting styling props. Each module has the same shape: a `.module.css` with classes inside `@layer variants`, and a `.tsx` exporting a `getXxxClass(props)` helper plus a props interface that components extend.
55
+ ## Finding your way around
81
56
 
82
- Scales anything with mutually-exclusive options are enumerated props:
57
+ The components below are listed in the index following this page; this is the short version of where to start reading.
83
58
 
84
- | Prop | Module | Values | Sets |
85
- |---|---|---|---|
86
- | `color=` | [`Color`](./style/Color.tsx) | `"primary"`, `"secondary"`, `"tertiary"`, `"red"`, `"orange"`, `"yellow"`, `"green"`, `"aqua"`, `"blue"`, `"purple"`, `"pink"`, `"gray"` | The tint anchor — recolours the whole scope |
87
- | `status=` | [`Status`](./style/Status.tsx) | `"info"`, `"success"`, `"warning"`, `"danger"`, `"error"`, `"loading"` | The tint anchor, via a semantic name |
88
- | `size=` | [`Typography`](./style/Typography.tsx) | `"xxsmall"` … `"xxlarge"` | `font-size` |
89
- | `tint=` | [`Typography`](./style/Typography.tsx) | `"00"`, `"05"`, … `"100"` | Text `color`, as a step of the current ladder |
90
- | `space=` | [`Spacing`](./style/Spacing.tsx) | `"none"`, `"xxsmall"` … `"xxlarge"` | `margin-block` (top + bottom) |
91
- | `padding=` | [`Padding`](./style/Padding.tsx) | `"none"`, `"xxsmall"` … `"xxlarge"` | `padding-block` (top + bottom) |
92
- | `gap=` | [`Gap`](./style/Gap.tsx) | `"none"`, `"xxsmall"` … `"xxlarge"` | `gap` between children |
59
+ **Content.** Block-level structure starts with [`Card`](/ui/Card), [`Section`](/ui/Section), and the [`Heading`](/ui/Heading) / [`Title`](/ui/Title) family, with [`Table`](/ui/Table), [`List`](/ui/List), and [`Figure`](/ui/Figure) for specific shapes; wrap longform copy in [`Prose`](/ui/Prose). Inline pieces — [`Link`](/ui/Link), [`Code`](/ui/Code), [`Strong`](/ui/Strong), [`Mark`](/ui/Mark) — live inside that block content. To render a Markdown string as components, use [`Markup`](/ui/Markup).
93
60
 
94
- On/off options stay boolean props:
95
-
96
- | Props | Module | Purpose |
97
- |---|---|---|
98
- | `narrow`, `wide`, `full` | [`Width`](./style/Width.tsx) | Constrain (or unconstrain) `max-width` |
99
- | `body`, `code`, `monospace`, `sans`, `serif` | [`Typography`](./style/Typography.tsx) | Font family |
100
- | `left`, `center`, `right` | [`Typography`](./style/Typography.tsx) | Text alignment |
101
- | `wrap`, `column`, `reverse`, justify/align (`left`, `middle`, `between`, …) | [`Flex`](./style/Flex.tsx) | Flex layout (composes `gap=`) |
102
- | `horizontal`, `vertical` | [`Scroll`](./style/Scroll.tsx) | Opt-in scrolling (combinable) |
103
-
104
- Status also keeps boolean aliases (`<Notice error>` ≡ `<Notice status="error">`) because they read naturally at call sites that hard-code one status.
105
-
106
- A component using styling props looks like:
107
-
108
- ```tsx
109
- export interface CardProps extends ColorProps, StatusProps, PaddingProps, SpacingProps, WidthVariants /* … */ {}
110
-
111
- export function Card({ children, ...props }: CardProps): ReactElement {
112
- return (
113
- <article
114
- className={getClass(
115
- getModuleClass(CARD_CSS, "card"),
116
- getStatusClass(props),
117
- getColorClass(props),
118
- getPaddingClass(props),
119
- getSpacingClass(props),
120
- getWidthClass(props),
121
- )}
122
- >
123
- {children}
124
- </article>
125
- );
126
- }
127
- ```
128
-
129
- ```tsx
130
- // At a call site:
131
- <Card color="purple" padding="large" space="none">…</Card>
132
- ```
61
+ **Structure.** Mount a client app with [`App`](/ui/App), or render a full server document with [`HTML`](/ui/HTML) and [`Page`](/ui/Page). Arrange the screen with [`CenteredLayout`](/ui/CenteredLayout) or [`SidebarLayout`](/ui/SidebarLayout), and drive URLs with [`Navigation`](/ui/Navigation) and [`Router`](/ui/Router).
133
62
 
134
- ### Component theme hooks
135
-
136
- Each component exposes per-component CSS custom properties for its overridable values, read with a `var(--component-hook, default)` fallback chain. A consumer overrides the hook at `:root` to retheme that one component without touching the rest of the design system.
137
-
138
- Every painting component exposes two kinds of hook:
139
-
140
- - **A tint hook** — the component rebinds the scale anchor once, at the top of its rule: `--tint-50: var(--card-tint, inherit);`. Setting `--card-tint: var(--color-purple)` recolours every card (and everything nested inside cards) while leaving the rest of the page alone. The `inherit` fallback is what lets `color=` / `status=` variants and parent scopes flow through when the hook is unset.
141
- - **Per-property hooks** — `--card-background`, `--card-border`, `--card-padding`, `--card-radius`, `--card-shadow`, and so on, for surgical overrides of a single painted property: `background: var(--card-background, var(--tint-90))`.
142
-
143
- Naming follows the file-prefix rule from [AGENTS.md](/AGENTS.md): hooks owned by a module file start with that file's kebab-case name — `Card.module.css` owns `--card-*`, `Button.module.css` owns `--button-*`. Design tokens declared at `:root` in `base.css` and the tint ladder itself are exempt — they're the global palette, not file-owned.
144
-
145
- ### Theming
146
-
147
- A theme is a CSS file of custom-property overrides at `:root`, imported after the base styles. Work from broadest to narrowest:
148
-
149
- 1. **Move a palette colour.** Overriding `--color-gray` moves the default anchor, retinting every neutral ladder in the app — the broadest possible change. Overriding `--color-red`, `--color-primary`, etc. re-aims every variant and status that maps to it.
150
- 2. **Retint one component family.** Set its tint hook: `--card-tint: var(--color-purple)` makes all cards (and their nested content) purple-tinted, with text, border, surface, and hover shades all derived for free.
151
- 3. **Override one property.** Per-property hooks are the scalpel: `--button-radius: 999px`, `--card-border: none`, `--tag-case: none`.
152
-
153
- **Don't override individual ladder steps (`--tint-90`, etc.) at `:root`.** The ladder is *recomputed* from the anchor inside every `.tint` scope — which includes every component that accepts `color=` or `status=` — so a step override at `:root` only reaches untinted regions and produces inconsistent surfaces. Move the anchor (option 1 or 2) instead, and the steps follow.
154
-
155
- ### How `:first-child` / `:last-child` margin overrides work
156
-
157
- Every paragraph-level component zeros its outer margins when it's the first or last child of its container — otherwise a Heading at the top of a Card would leave a strip of unwanted space. These rules live in `@layer overrides`, which beats every other layer including `variants`, so a `<Paragraph space="large">` still collapses its abutting edges correctly.
158
-
159
- Pattern:
160
-
161
- ```css
162
- @layer components {
163
- .paragraph { margin-block: var(--paragraph-space, var(--space-paragraph)); }
164
- }
165
-
166
- @layer overrides {
167
- .paragraph {
168
- &:first-child { margin-block-start: 0; }
169
- &:last-child { margin-block-end: 0; }
170
- }
171
- }
172
- ```
63
+ **Interaction.** Forms start at [`Form`](/ui/Form), which wires [`Field`](/ui/Field) and the typed inputs to a [`FormStore`](/ui/FormStore); [`Button`](/ui/Button) is the standalone action. Overlays are [`Dialog`](/ui/Dialog) and [`Modal`](/ui/Modal); navigation menus are [`Menu`](/ui/Menu) and [`MenuItem`](/ui/MenuItem); transient feedback is [`Notice`](/ui/Notice) (and the global [`Notices`](/ui/Notices) list); animate enter/leave with [`Transition`](/ui/Transition).
173
64
 
174
- ### Writing a new component
175
-
176
- A typical new block-level component looks like:
177
-
178
- ```tsx
179
- // Address.tsx
180
- import { type ColorProps, getColorClass } from "../style/Color.js";
181
- import { getSpacingClass, type SpacingProps } from "../style/Spacing.js";
182
- import { getTypographyClass, type TypographyProps } from "../style/Typography.js";
183
-
184
- export interface AddressProps extends ColorProps, SpacingProps, TypographyProps, ChildProps {}
185
-
186
- export function Address({ children, ...props }: AddressProps) {
187
- return (
188
- <address
189
- className={getClass(
190
- getModuleClass(styles, "address"),
191
- getColorClass(props),
192
- getSpacingClass(props),
193
- getTypographyClass(props),
194
- )}
195
- >
196
- {children}
197
- </address>
198
- );
199
- }
200
- ```
201
-
202
- ```css
203
- /* Address.module.css */
204
- @import "../style/base.css";
205
-
206
- @layer components {
207
- .address {
208
- /* Theme — rebind the tint anchor so `--address-tint` (and parent scopes) flow through. */
209
- --tint-50: var(--address-tint, inherit);
210
-
211
- /* Box */
212
- display: block;
213
- margin-inline: 0;
214
- margin-block: var(--address-space, var(--space-paragraph));
215
-
216
- /* Text — paint from the ladder, with a per-property hook in front. */
217
- color: var(--address-color, var(--tint-00));
218
- font-family: var(--address-font, inherit);
219
- font-size: var(--address-size, inherit);
220
- }
221
- }
222
-
223
- @layer overrides {
224
- .address {
225
- &:first-child { margin-block-start: 0; }
226
- &:last-child { margin-block-end: 0; }
227
- }
228
- }
229
- ```
230
-
231
- Checklist:
232
-
233
- - [ ] `@import "../style/base.css";` at the top.
234
- - [ ] All rules inside `@layer components { … }`.
235
- - [ ] All custom properties owned by this file start with the file name (`--address-*`, etc.), per [AGENTS.md](/AGENTS.md).
236
- - [ ] If the component paints colour, rebind the tint anchor at the top of the rule (`--tint-50: var(--address-tint, inherit);`) and paint from ladder steps with per-property hooks in front.
237
- - [ ] `:first-child` / `:last-child` overrides in a separate `@layer overrides { … }` block.
238
- - [ ] TSX extends the styling-prop interfaces (`ColorProps`, `SpacingProps`, `TypographyProps`, etc.) you want to expose; composes the matching `getXxxClass(props)` calls.
239
-
240
- ## Module map
241
-
242
- ### Content
243
-
244
- | Folder | What's inside |
245
- |---|---|
246
- | [block](/ui/block) | Block-level content — `Card`, `Section`, `Title`, `Heading`, `Table`, `List`, `Prose`, `Figure` |
247
- | [inline](/ui/inline) | Inline content — `Code`, `Strong`, `Emphasis`, `Link`, `Mark`, `Small` |
248
- | [misc](/ui/misc) | Cross-cutting pieces — `Markup`, `Tag`, `StatusIcon`, `Loading`, `Catcher`, `Mapper` |
249
- | [style](./style/) | The styling system — design tokens, the tint scale, styling props, `Flex`, `Scroll` |
250
-
251
- ### Structure
252
-
253
- | Folder | What's inside |
254
- |---|---|
255
- | [app](/ui/app) | The `<App>` root component |
256
- | [page](/ui/page) | Document-level components — `<HTML>`, `<Head>`, `<Page>` |
257
- | [layout](/ui/layout) | Page layouts — `SidebarLayout`, `CenteredLayout` |
258
- | [router](/ui/router) | Client-side routing — `<Navigation>`, `<Router>` |
259
-
260
- ### Interaction
261
-
262
- | Folder | What's inside |
263
- |---|---|
264
- | [form](/ui/form) | Forms and inputs — `<Form>`, `<Field>`, typed inputs, `<Button>`, `FormStore` |
265
- | [dialog](/ui/dialog) | `<Dialog>` and `<Modal>` overlays |
266
- | [menu](/ui/menu) | `<Menu>` and `<MenuItem>` |
267
- | [notice](/ui/notice) | Inline and global notices |
268
- | [transition](/ui/transition) | CSS enter / leave transitions |
269
-
270
- ### Documentation site
271
-
272
- | Folder | What's inside |
273
- |---|---|
274
- | [tree](/ui/tree) | `<TreeApp>` and the components that turn a tree into a site |
275
- | [docs](/ui/docs) | Page and card renderers for directories, files, and code symbols |
276
- | [util](/ui/util) | UI helper functions — context, meta, CSS class composition |
277
-
278
- ## Quick start
279
-
280
- A minimal single-screen app:
281
-
282
- ```tsx
283
- import { App, CenteredLayout, Section, Title, Paragraph } from "shelving/ui";
284
-
285
- function HelloApp() {
286
- return (
287
- <App app="My app">
288
- <CenteredLayout>
289
- <Section narrow>
290
- <Title>Hello</Title>
291
- <Paragraph>Welcome to the app.</Paragraph>
292
- </Section>
293
- </CenteredLayout>
294
- </App>
295
- );
296
- }
297
- ```
298
-
299
- For a routed, multi-page app, wrap the tree in [`<Navigation>` and `<Router>`](/ui/router). For a documentation site, hand an extracted tree to [`<TreeApp>`](/ui/tree) — see the [extract](/extract) guide.
65
+ **Documentation site.** Hand an extracted tree to [`TreeApp`](/ui/TreeApp) and you get a complete site — sidebar, routing, and a rendered page per element — using the renderers in `docs/`.
300
66
 
301
67
  ## See also
302
68
 
303
69
  - [extract](/extract) — builds the tree that the documentation components render
304
- - [markup](/markup) — Markdown rendering used by `<Markup>` and `<Prose>`
305
- - [store](/store) — reactive state behind `FormStore`, `NavigationStore`, and notices
70
+ - [markup](/markup) — Markdown rendering used by [`Markup`](/ui/Markup) and [`Prose`](/ui/Prose)
71
+ - [store](/store) — reactive state behind [`FormStore`](/ui/FormStore), `NavigationStore`, and notices
306
72
  - [react](/react) — store and provider hooks used alongside these components
73
+
74
+ > Building or extending a component? The contributor walkthrough (file layout, the tint-anchor + per-property-hook pattern, `:first-child` / `:last-child` overrides, and the checklist) lives in the **React Components** section of `AGENTS.md`.
package/ui/app/App.d.ts CHANGED
@@ -1,5 +1,16 @@
1
1
  import type { ReactElement } from "react";
2
- import "../style/base.css";
2
+ import "../style/layers.css";
3
+ import "../style/Color.module.css";
4
+ import "../style/Duration.module.css";
5
+ import "../style/Font.module.css";
6
+ import "../style/Radius.module.css";
7
+ import "../style/Shadow.module.css";
8
+ import "../style/Size.module.css";
9
+ import "../style/Space.module.css";
10
+ import "../style/Stroke.module.css";
11
+ import "../style/Tint.module.css";
12
+ import "../style/Weight.module.css";
13
+ import "../style/Width.module.css";
3
14
  import type { PossibleMeta } from "../util/index.js";
4
15
  import type { ChildProps } from "../util/props.js";
5
16
  /**
@@ -12,11 +23,12 @@ export interface AppProps extends PossibleMeta, ChildProps {
12
23
  /**
13
24
  * Root component for an application, providing the top-level `Meta` context and global styles.
14
25
  * - Descendants can read or update metadata via the provided `<Meta>` context.
15
- * - Design tokens and body baseline typography are set globally via `style/base.css`.
26
+ * - Design tokens and body baseline typography are set globally via the `style/` token modules (`Color`, `Size`, `Font`, …).
16
27
  *
17
28
  * @param children The application content.
18
29
  * @param meta The root meta (app name, root URL, language, etc.).
19
30
  * @returns The app root element wrapping `children`.
31
+ * @kind component
20
32
  * @example <App app="My App" root="https://example.com/"><Navigation>…</Navigation></App>
21
33
  * @see https://dhoulb.github.io/shelving/ui/app/App/App
22
34
  */
package/ui/app/App.js CHANGED
@@ -1,14 +1,28 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { MetaContext, requireMeta } from "../misc/MetaContext.js";
3
- import "../style/base.css";
3
+ // Load the global design tokens, cascade-layer order, and body baseline. Each module owns its own
4
+ // `:root` tokens (and body rules where relevant); see `modules/ui/README.md` for the styling system.
5
+ import "../style/layers.css";
6
+ import "../style/Color.module.css";
7
+ import "../style/Duration.module.css";
8
+ import "../style/Font.module.css";
9
+ import "../style/Radius.module.css";
10
+ import "../style/Shadow.module.css";
11
+ import "../style/Size.module.css";
12
+ import "../style/Space.module.css";
13
+ import "../style/Stroke.module.css";
14
+ import "../style/Tint.module.css";
15
+ import "../style/Weight.module.css";
16
+ import "../style/Width.module.css";
4
17
  /**
5
18
  * Root component for an application, providing the top-level `Meta` context and global styles.
6
19
  * - Descendants can read or update metadata via the provided `<Meta>` context.
7
- * - Design tokens and body baseline typography are set globally via `style/base.css`.
20
+ * - Design tokens and body baseline typography are set globally via the `style/` token modules (`Color`, `Size`, `Font`, …).
8
21
  *
9
22
  * @param children The application content.
10
23
  * @param meta The root meta (app name, root URL, language, etc.).
11
24
  * @returns The app root element wrapping `children`.
25
+ * @kind component
12
26
  * @example <App app="My App" root="https://example.com/"><Navigation>…</Navigation></App>
13
27
  * @see https://dhoulb.github.io/shelving/ui/app/App/App
14
28
  */