@kerfjs/ui 5.0.0-beta.4 → 5.0.0-beta.41

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 (498) hide show
  1. package/README.md +342 -108
  2. package/ai/application-ui-diagnostic-ids-v1.json +100 -0
  3. package/ai/application-ui-profile-sync.cjs +529 -0
  4. package/ai/application-ui-profile.d.mts +1 -0
  5. package/ai/application-ui-profile.d.ts +102 -0
  6. package/ai/application-ui-profile.defaults.json +41 -0
  7. package/ai/application-ui-profile.mjs +626 -0
  8. package/ai/application-ui-profile.schema.json +130 -0
  9. package/ai/catalog-authoring.json +25 -0
  10. package/ai/catalog-authoring.schema.json +61 -0
  11. package/ai/compile-time-contracts-v1.json +132 -0
  12. package/ai/compile-time-contracts-v1.schema.json +51 -0
  13. package/ai/component-catalog-extension-v2.d.ts +7 -0
  14. package/ai/component-catalog-extension-v2.schema.json +7 -0
  15. package/ai/component-catalog-extension.schema.json +79 -0
  16. package/ai/component-catalog-v2-overrides.json +496 -0
  17. package/ai/component-catalog-v2.d.ts +90 -0
  18. package/ai/component-catalog-v2.json +5678 -0
  19. package/ai/component-catalog-v2.schema.json +298 -0
  20. package/ai/component-catalog.json +2694 -417
  21. package/ai/component-catalog.schema.json +39 -0
  22. package/ai/public-api-signatures-v1.md +1591 -126
  23. package/ai/skill.md +140 -54
  24. package/ai/webawesome-jsx-signatures-v1.md +2 -1
  25. package/analyzer/cli.mjs +49 -0
  26. package/analyzer/index.d.mts +46 -0
  27. package/analyzer/index.mjs +1086 -0
  28. package/analyzer/report.schema.json +66 -0
  29. package/dist/app-tab.d.ts +15 -4
  30. package/dist/app-tab.js +3 -1
  31. package/dist/browser/app-tab.js +1 -0
  32. package/dist/browser/floating-toolbar.js +3 -0
  33. package/dist/browser/list-action-row.js +5 -0
  34. package/dist/browser/list-header.js +7 -0
  35. package/dist/browser/list-inset-control.js +3 -0
  36. package/dist/browser/list-inset-text.js +3 -0
  37. package/dist/browser/list-item.js +5 -0
  38. package/dist/browser/list.js +3 -0
  39. package/dist/browser/pane.js +3 -0
  40. package/dist/browser/row.js +3 -0
  41. package/dist/browser/segmented-control.js +1 -0
  42. package/dist/browser/select.js +1 -0
  43. package/dist/browser/skeleton.js +3 -0
  44. package/dist/browser/spacer.js +3 -0
  45. package/dist/browser/state-banner.js +1 -0
  46. package/dist/browser/sunken-panel.js +3 -0
  47. package/dist/browser/surface-scaffold.js +3 -0
  48. package/dist/browser/text.js +3 -0
  49. package/dist/browser/toolbar-text.js +1 -0
  50. package/dist/browser/value-table.js +1 -0
  51. package/dist/catalog-resources.d.ts +35 -0
  52. package/dist/catalog-resources.js +22 -0
  53. package/dist/catalog-resources.js.map +1 -0
  54. package/dist/catalog.d.ts +151 -0
  55. package/dist/catalog.js +396 -0
  56. package/dist/catalog.js.map +1 -0
  57. package/dist/chunk-2TYUFZUC.js +22 -0
  58. package/dist/chunk-2TYUFZUC.js.map +1 -0
  59. package/dist/chunk-6NDBE2OK.js +32 -0
  60. package/dist/chunk-6NDBE2OK.js.map +1 -0
  61. package/dist/chunk-6WTETUZ6.js +21 -0
  62. package/dist/chunk-6WTETUZ6.js.map +1 -0
  63. package/dist/chunk-AQFWBG23.js +83 -0
  64. package/dist/chunk-AQFWBG23.js.map +1 -0
  65. package/dist/chunk-B6JYRX3P.js +51 -0
  66. package/dist/chunk-B6JYRX3P.js.map +1 -0
  67. package/dist/chunk-C34NZOLJ.js +53 -0
  68. package/dist/chunk-C34NZOLJ.js.map +1 -0
  69. package/dist/chunk-CAB6NQ2T.js +102 -0
  70. package/dist/chunk-CAB6NQ2T.js.map +1 -0
  71. package/dist/chunk-CK6P5AUK.js +136 -0
  72. package/dist/chunk-CK6P5AUK.js.map +1 -0
  73. package/dist/chunk-DAO2VFTT.js +129 -0
  74. package/dist/chunk-DAO2VFTT.js.map +1 -0
  75. package/dist/chunk-DEYOGDE7.js +141 -0
  76. package/dist/chunk-DEYOGDE7.js.map +1 -0
  77. package/dist/chunk-DYBPI5O2.js +25 -0
  78. package/dist/chunk-DYBPI5O2.js.map +1 -0
  79. package/dist/{chunk-TRNBHPNW.js → chunk-E34TE2ZI.js} +12 -8
  80. package/dist/chunk-E34TE2ZI.js.map +1 -0
  81. package/dist/chunk-EYALJQQD.js +25 -0
  82. package/dist/chunk-EYALJQQD.js.map +1 -0
  83. package/dist/chunk-I45QO2IZ.js +88 -0
  84. package/dist/chunk-I45QO2IZ.js.map +1 -0
  85. package/dist/chunk-I4V3UDSA.js +79 -0
  86. package/dist/chunk-I4V3UDSA.js.map +1 -0
  87. package/dist/chunk-OGTJNHRG.js +42 -0
  88. package/dist/chunk-OGTJNHRG.js.map +1 -0
  89. package/dist/chunk-OL4TC3U2.js +156 -0
  90. package/dist/chunk-OL4TC3U2.js.map +1 -0
  91. package/dist/chunk-OMSRSL3B.js +120 -0
  92. package/dist/chunk-OMSRSL3B.js.map +1 -0
  93. package/dist/chunk-R3EBGEAW.js +46 -0
  94. package/dist/chunk-R3EBGEAW.js.map +1 -0
  95. package/dist/chunk-SOK5EUF7.js +25 -0
  96. package/dist/chunk-SOK5EUF7.js.map +1 -0
  97. package/dist/{chunk-7BR3DO2A.js → chunk-SRSJO5QE.js} +13 -4
  98. package/dist/chunk-SRSJO5QE.js.map +1 -0
  99. package/dist/chunk-T7DOWS6T.js +61 -0
  100. package/dist/chunk-T7DOWS6T.js.map +1 -0
  101. package/dist/chunk-TIGSG6BX.js +218 -0
  102. package/dist/chunk-TIGSG6BX.js.map +1 -0
  103. package/dist/chunk-UDHHKZWO.js +41 -0
  104. package/dist/chunk-UDHHKZWO.js.map +1 -0
  105. package/dist/{chunk-MQIZAD4A.js → chunk-UKKSMZYT.js} +76 -22
  106. package/dist/chunk-UKKSMZYT.js.map +1 -0
  107. package/dist/chunk-V6GQKOX2.js +39 -0
  108. package/dist/chunk-V6GQKOX2.js.map +1 -0
  109. package/dist/chunk-V6HCTCVD.js +46 -0
  110. package/dist/chunk-V6HCTCVD.js.map +1 -0
  111. package/dist/chunk-VDUJZF5G.js +515 -0
  112. package/dist/chunk-VDUJZF5G.js.map +1 -0
  113. package/dist/chunk-VXFGMDA7.js +31 -0
  114. package/dist/chunk-VXFGMDA7.js.map +1 -0
  115. package/dist/chunk-WN52USHV.js +115 -0
  116. package/dist/chunk-WN52USHV.js.map +1 -0
  117. package/dist/chunk-WSXUPSID.js +43 -0
  118. package/dist/chunk-WSXUPSID.js.map +1 -0
  119. package/dist/chunk-X5GRDQ7V.js +50 -0
  120. package/dist/chunk-X5GRDQ7V.js.map +1 -0
  121. package/dist/{chunk-56RJXPHG.js → chunk-Y6LYKFXY.js} +1 -1
  122. package/dist/{chunk-56RJXPHG.js.map → chunk-Y6LYKFXY.js.map} +1 -1
  123. package/dist/chunk-ZASJJMGV.js +17 -0
  124. package/dist/chunk-ZASJJMGV.js.map +1 -0
  125. package/dist/chunk-ZPWZIVI6.js +47 -0
  126. package/dist/chunk-ZPWZIVI6.js.map +1 -0
  127. package/dist/collapsible-panel.d.ts +77 -0
  128. package/dist/collapsible-panel.js +84 -0
  129. package/dist/collapsible-panel.js.map +1 -0
  130. package/dist/css-values.d.ts +75 -0
  131. package/dist/css-values.js +3 -0
  132. package/dist/css-values.js.map +1 -0
  133. package/dist/device-class.d.ts +62 -0
  134. package/dist/device-class.js +103 -0
  135. package/dist/device-class.js.map +1 -0
  136. package/dist/disclosure-arrow.js +1 -1
  137. package/dist/divider-sides-BzB6rphT.d.ts +6 -0
  138. package/dist/empty-state.d.ts +3 -2
  139. package/dist/empty-state.js +1 -1
  140. package/dist/flex-alignment-CF8NLj7i.d.ts +4 -0
  141. package/dist/floating-toolbar.d.ts +29 -0
  142. package/dist/floating-toolbar.js +3 -0
  143. package/dist/floating-toolbar.js.map +1 -0
  144. package/dist/index.d.ts +28 -14
  145. package/dist/index.js +34 -23
  146. package/dist/{menu-action-row.d.ts → list-action-row.d.ts} +19 -8
  147. package/dist/list-action-row.js +7 -0
  148. package/dist/list-action-row.js.map +1 -0
  149. package/dist/list-header.d.ts +80 -0
  150. package/dist/list-header.js +9 -0
  151. package/dist/list-header.js.map +1 -0
  152. package/dist/list-inset-control.d.ts +20 -0
  153. package/dist/list-inset-control.js +3 -0
  154. package/dist/list-inset-control.js.map +1 -0
  155. package/dist/list-inset-text.d.ts +29 -0
  156. package/dist/list-inset-text.js +3 -0
  157. package/dist/list-inset-text.js.map +1 -0
  158. package/dist/list-item.d.ts +45 -0
  159. package/dist/list-item.js +7 -0
  160. package/dist/list-item.js.map +1 -0
  161. package/dist/list.d.ts +30 -0
  162. package/dist/list.js +5 -0
  163. package/dist/list.js.map +1 -0
  164. package/dist/loading-spinner.js +1 -1
  165. package/dist/nav-stack.d.ts +41 -0
  166. package/dist/nav-stack.js +6 -0
  167. package/dist/nav-stack.js.map +1 -0
  168. package/dist/pane.d.ts +50 -0
  169. package/dist/pane.js +4 -0
  170. package/dist/pane.js.map +1 -0
  171. package/dist/resizable-region.d.ts +21 -3
  172. package/dist/resizable-region.js +1 -1
  173. package/dist/row.d.ts +28 -0
  174. package/dist/row.js +5 -0
  175. package/dist/row.js.map +1 -0
  176. package/dist/segmented-control.d.ts +11 -8
  177. package/dist/segmented-control.js +3 -1
  178. package/dist/select-register.d.ts +5 -1
  179. package/dist/select-register.js +129 -1
  180. package/dist/select-register.js.map +1 -1
  181. package/dist/select.d.ts +34 -8
  182. package/dist/select.js +3 -1
  183. package/dist/semantic-content-BbzjvSu9.d.ts +14 -0
  184. package/dist/skeleton.d.ts +25 -0
  185. package/dist/skeleton.js +4 -0
  186. package/dist/skeleton.js.map +1 -0
  187. package/dist/spacer.d.ts +16 -0
  188. package/dist/spacer.js +4 -0
  189. package/dist/spacer.js.map +1 -0
  190. package/dist/split-view.d.ts +43 -0
  191. package/dist/split-view.js +82 -0
  192. package/dist/split-view.js.map +1 -0
  193. package/dist/state-banner.d.ts +11 -5
  194. package/dist/state-banner.js +3 -1
  195. package/dist/styles/app-tab.css +26 -0
  196. package/dist/styles/catalog.css +582 -0
  197. package/dist/styles/collapsible-panel.css +212 -0
  198. package/dist/styles/floating-toolbar.css +38 -0
  199. package/dist/styles/foundation.css +80 -0
  200. package/dist/styles/layout.css +2 -20
  201. package/dist/styles/list-action-row.css +224 -0
  202. package/dist/styles/list-header.css +224 -0
  203. package/dist/styles/list-inset-control.css +23 -0
  204. package/dist/styles/list-inset-text.css +30 -0
  205. package/dist/styles/list-item.css +171 -0
  206. package/dist/styles/list.css +118 -0
  207. package/dist/styles/nav-stack.css +141 -0
  208. package/dist/styles/pane.css +62 -0
  209. package/dist/styles/resizable-region.css +158 -0
  210. package/dist/styles/row.css +92 -0
  211. package/dist/styles/segmented-control.css +10 -7
  212. package/dist/styles/select.css +115 -2
  213. package/dist/styles/skeleton.css +31 -0
  214. package/dist/styles/spacer.css +11 -0
  215. package/dist/styles/split-view.css +35 -0
  216. package/dist/styles/state-banner.css +49 -10
  217. package/dist/styles/styles.css +10 -5
  218. package/dist/styles/sunken-panel.css +22 -0
  219. package/dist/styles/surface-scaffold.css +51 -0
  220. package/dist/styles/tab-bar.css +24 -0
  221. package/dist/styles/tab-scaffold.css +85 -0
  222. package/dist/styles/text.css +6 -0
  223. package/dist/styles/token-search-field.css +14 -2
  224. package/dist/styles/toolbar-control-group.css +235 -8
  225. package/dist/styles/toolbar-text.css +65 -2
  226. package/dist/styles/toolbar.css +47 -2
  227. package/dist/styles/webawesome.css +171 -0
  228. package/dist/styles/workbench.css +187 -0
  229. package/dist/sunken-panel.d.ts +20 -0
  230. package/dist/sunken-panel.js +3 -0
  231. package/dist/sunken-panel.js.map +1 -0
  232. package/dist/surface-scaffold.d.ts +26 -0
  233. package/dist/surface-scaffold.js +3 -0
  234. package/dist/surface-scaffold.js.map +1 -0
  235. package/dist/tab-bar.d.ts +24 -6
  236. package/dist/tab-bar.js +1 -1
  237. package/dist/tab-scaffold.d.ts +31 -0
  238. package/dist/tab-scaffold.js +51 -0
  239. package/dist/tab-scaffold.js.map +1 -0
  240. package/dist/text.d.ts +20 -0
  241. package/dist/text.js +3 -0
  242. package/dist/text.js.map +1 -0
  243. package/dist/token-search-field.d.ts +30 -13
  244. package/dist/token-search-field.js +1 -1
  245. package/dist/toolbar-control-group.d.ts +33 -7
  246. package/dist/toolbar-control-group.js +1 -1
  247. package/dist/toolbar-text.d.ts +31 -4
  248. package/dist/toolbar-text.js +3 -1
  249. package/dist/toolbar.d.ts +10 -7
  250. package/dist/toolbar.js +1 -1
  251. package/dist/value-table.d.ts +7 -3
  252. package/dist/value-table.js +3 -1
  253. package/dist/webawesome.d.ts +1 -0
  254. package/dist/wire-catalog.d.ts +57 -0
  255. package/dist/wire-catalog.js +302 -0
  256. package/dist/wire-catalog.js.map +1 -0
  257. package/dist/wire-nav-stack.d.ts +18 -0
  258. package/dist/wire-nav-stack.js +228 -0
  259. package/dist/wire-nav-stack.js.map +1 -0
  260. package/dist/wire-resizable-regions.d.ts +1 -1
  261. package/dist/wire-resizable-regions.js +1 -1
  262. package/dist/wire-sidebar.d.ts +48 -0
  263. package/dist/wire-sidebar.js +147 -0
  264. package/dist/wire-sidebar.js.map +1 -0
  265. package/dist/wire-tab-bars.d.ts +19 -2
  266. package/dist/wire-tab-bars.js +1 -1
  267. package/dist/wire-tab-scaffold.d.ts +11 -0
  268. package/dist/wire-tab-scaffold.js +21 -0
  269. package/dist/wire-tab-scaffold.js.map +1 -0
  270. package/dist/wire-token-search-fields.d.ts +104 -4
  271. package/dist/wire-token-search-fields.js +1 -1
  272. package/dist/workbench.d.ts +44 -0
  273. package/dist/workbench.js +106 -0
  274. package/dist/workbench.js.map +1 -0
  275. package/docs/accessibility.md +155 -20
  276. package/docs/app-layouts.md +61 -0
  277. package/docs/catalog.md +364 -0
  278. package/docs/collapsible-panel.md +142 -0
  279. package/docs/component-contract.md +258 -28
  280. package/docs/component-integration.md +63 -0
  281. package/docs/component-selection.md +219 -57
  282. package/docs/design/templates/empty-state/filtered-dark.svg +1 -0
  283. package/docs/design/templates/empty-state/filtered.svg +1 -0
  284. package/docs/design/templates/empty-state/with-action-dark.svg +1 -0
  285. package/docs/design/templates/empty-state/with-action.svg +1 -0
  286. package/docs/design/templates/empty-state-dark.svg +1 -0
  287. package/docs/design/templates/empty-state.svg +1 -0
  288. package/docs/design/templates/list/compact-dark.svg +1 -0
  289. package/docs/design/templates/list/compact.svg +1 -0
  290. package/docs/design/templates/list/stack-dark.svg +1 -0
  291. package/docs/design/templates/list/stack.svg +1 -0
  292. package/docs/design/templates/list-action-row/default-dark.svg +1 -0
  293. package/docs/design/templates/list-action-row/default.svg +1 -0
  294. package/docs/design/templates/list-action-row/selected-dark.svg +1 -0
  295. package/docs/design/templates/list-action-row/selected.svg +1 -0
  296. package/docs/design/templates/list-action-row-dark.svg +1 -0
  297. package/docs/design/templates/list-action-row.svg +1 -0
  298. package/docs/design/templates/list-dark.svg +1 -0
  299. package/docs/design/templates/list-header/action-dark.svg +1 -0
  300. package/docs/design/templates/list-header/action.svg +1 -0
  301. package/docs/design/templates/list-header/count-dark.svg +1 -0
  302. package/docs/design/templates/list-header/count.svg +1 -0
  303. package/docs/design/templates/list-header/inline-action-dark.svg +1 -0
  304. package/docs/design/templates/list-header/inline-action.svg +1 -0
  305. package/docs/design/templates/list-header/toggle-collapsed-dark.svg +1 -0
  306. package/docs/design/templates/list-header/toggle-collapsed.svg +1 -0
  307. package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -0
  308. package/docs/design/templates/list-header/toggle-expanded.svg +1 -0
  309. package/docs/design/templates/list-header-dark.svg +1 -0
  310. package/docs/design/templates/list-header.svg +1 -0
  311. package/docs/design/templates/list-item/default-dark.svg +1 -0
  312. package/docs/design/templates/list-item/default.svg +1 -0
  313. package/docs/design/templates/list-item/multiline-dark.svg +1 -0
  314. package/docs/design/templates/list-item/multiline.svg +1 -0
  315. package/docs/design/templates/list-item/selected-dark.svg +1 -0
  316. package/docs/design/templates/list-item/selected.svg +1 -0
  317. package/docs/design/templates/list-item/trailing-dark.svg +1 -0
  318. package/docs/design/templates/list-item/trailing.svg +1 -0
  319. package/docs/design/templates/list-item-dark.svg +1 -0
  320. package/docs/design/templates/list-item.svg +1 -0
  321. package/docs/design/templates/list.svg +1 -0
  322. package/docs/design/templates/segmented-control/equal-dark.svg +1 -0
  323. package/docs/design/templates/segmented-control/equal.svg +1 -0
  324. package/docs/design/templates/segmented-control/pill-small-dark.svg +1 -0
  325. package/docs/design/templates/segmented-control/pill-small.svg +1 -0
  326. package/docs/design/templates/segmented-control-dark.svg +1 -0
  327. package/docs/design/templates/segmented-control.svg +1 -0
  328. package/docs/design/templates/skeleton/avatar-dark.svg +1 -0
  329. package/docs/design/templates/skeleton/avatar.svg +1 -0
  330. package/docs/design/templates/skeleton/block-dark.svg +1 -0
  331. package/docs/design/templates/skeleton/block.svg +1 -0
  332. package/docs/design/templates/skeleton/lines-dark.svg +1 -0
  333. package/docs/design/templates/skeleton/lines.svg +1 -0
  334. package/docs/design/templates/skeleton-dark.svg +1 -0
  335. package/docs/design/templates/skeleton.svg +1 -0
  336. package/docs/design/templates/state-banner/danger-dark.svg +1 -0
  337. package/docs/design/templates/state-banner/danger.svg +1 -0
  338. package/docs/design/templates/state-banner/info-badge-dark.svg +1 -0
  339. package/docs/design/templates/state-banner/info-badge.svg +1 -0
  340. package/docs/design/templates/state-banner/info-dark.svg +1 -0
  341. package/docs/design/templates/state-banner/info.svg +1 -0
  342. package/docs/design/templates/state-banner/success-dark.svg +1 -0
  343. package/docs/design/templates/state-banner/success.svg +1 -0
  344. package/docs/design/templates/state-banner/warning-dark.svg +1 -0
  345. package/docs/design/templates/state-banner/warning.svg +1 -0
  346. package/docs/design/templates/state-banner-dark.svg +1 -0
  347. package/docs/design/templates/state-banner.svg +1 -0
  348. package/docs/design/templates/tab-bar/workspace-tabs-dark.svg +1 -0
  349. package/docs/design/templates/tab-bar/workspace-tabs.svg +1 -0
  350. package/docs/design/templates/tab-bar-dark.svg +1 -0
  351. package/docs/design/templates/tab-bar.svg +1 -0
  352. package/docs/design/templates/token-search-field/empty-dark.svg +1 -0
  353. package/docs/design/templates/token-search-field/empty.svg +1 -0
  354. package/docs/design/templates/token-search-field/tokens-dark.svg +1 -0
  355. package/docs/design/templates/token-search-field/tokens.svg +1 -0
  356. package/docs/design/templates/token-search-field-dark.svg +1 -0
  357. package/docs/design/templates/token-search-field.svg +1 -0
  358. package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -0
  359. package/docs/design/templates/toolbar/title-and-actions.svg +1 -0
  360. package/docs/design/templates/toolbar-control-group/borderless-single-dark.svg +1 -0
  361. package/docs/design/templates/toolbar-control-group/borderless-single.svg +1 -0
  362. package/docs/design/templates/toolbar-control-group/icon-buttons-dark.svg +1 -0
  363. package/docs/design/templates/toolbar-control-group/icon-buttons.svg +1 -0
  364. package/docs/design/templates/toolbar-control-group/push-buttons-dark.svg +1 -0
  365. package/docs/design/templates/toolbar-control-group/push-buttons.svg +1 -0
  366. package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -0
  367. package/docs/design/templates/toolbar-control-group/with-text.svg +1 -0
  368. package/docs/design/templates/toolbar-control-group-dark.svg +1 -0
  369. package/docs/design/templates/toolbar-control-group.svg +1 -0
  370. package/docs/design/templates/toolbar-dark.svg +1 -0
  371. package/docs/design/templates/toolbar-text/default-dark.svg +1 -0
  372. package/docs/design/templates/toolbar-text/default.svg +1 -0
  373. package/docs/design/templates/toolbar-text/large-dark.svg +1 -0
  374. package/docs/design/templates/toolbar-text/large.svg +1 -0
  375. package/docs/design/templates/toolbar-text/small-dark.svg +1 -0
  376. package/docs/design/templates/toolbar-text/small.svg +1 -0
  377. package/docs/design/templates/toolbar-text-dark.svg +1 -0
  378. package/docs/design/templates/toolbar-text.svg +1 -0
  379. package/docs/design/templates/toolbar.svg +1 -0
  380. package/docs/design/templates/value-table/details-dark.svg +1 -0
  381. package/docs/design/templates/value-table/details.svg +1 -0
  382. package/docs/design/templates/value-table-dark.svg +1 -0
  383. package/docs/design/templates/value-table.svg +1 -0
  384. package/docs/design/templates.md +128 -0
  385. package/docs/design-philosophy.md +33 -11
  386. package/docs/device-class.md +54 -0
  387. package/docs/examples/application-ui-profile.json +60 -0
  388. package/docs/examples/command-palette-adapter.tsx +68 -17
  389. package/docs/examples/component-catalog-extension-v2.json +80 -0
  390. package/docs/examples/component-catalog-extension.json +45 -0
  391. package/docs/layout.md +169 -38
  392. package/docs/nav-stack.md +77 -0
  393. package/docs/recipes.md +64 -16
  394. package/docs/split-view.md +55 -0
  395. package/docs/sunken-panel.md +53 -0
  396. package/docs/surface-scaffold.md +25 -0
  397. package/docs/tab-scaffold.md +52 -0
  398. package/docs/type-contracts.md +127 -0
  399. package/docs/ui-analyzer.md +118 -0
  400. package/docs/ui-doctor.md +91 -0
  401. package/docs/ui-evaluator.md +122 -0
  402. package/docs/ux-demo.md +29 -10
  403. package/docs/webawesome-theme.md +82 -8
  404. package/docs/workbench.md +123 -0
  405. package/doctor/cli.mjs +115 -0
  406. package/doctor/config.schema.json +68 -0
  407. package/doctor/eslint-diagnostics.d.mts +24 -0
  408. package/doctor/eslint-diagnostics.mjs +24 -0
  409. package/doctor/index.d.mts +116 -0
  410. package/doctor/index.mjs +1272 -0
  411. package/doctor/report.schema.json +118 -0
  412. package/evaluator/cli.mjs +89 -0
  413. package/evaluator/index.d.mts +90 -0
  414. package/evaluator/index.mjs +1138 -0
  415. package/evaluator/report.schema.json +152 -0
  416. package/llms.txt +260 -32
  417. package/package.json +205 -38
  418. package/traversal-exclusions.mjs +27 -0
  419. package/ux-demo/recipes/app-shell.tsx +223 -13
  420. package/ux-demo/recipes/collapsible-sidebar.tsx +230 -0
  421. package/ux-demo/recipes/compact-toolbar.tsx +116 -12
  422. package/ux-demo/recipes/composer-form.tsx +120 -19
  423. package/ux-demo/recipes/list-detail-dialog.tsx +205 -0
  424. package/ux-demo/recipes/list-workspace-states.tsx +176 -9
  425. package/ux-demo/recipes/loaders.ts +13 -4
  426. package/ux-demo/recipes/loading-inspector.tsx +191 -0
  427. package/ux-demo/recipes/mount-recipe.ts +31 -5
  428. package/ux-demo/recipes/navigation-sidebar.tsx +123 -11
  429. package/ux-demo/recipes/navigation-stack.tsx +121 -0
  430. package/ux-demo/recipes/recipes.css +125 -16
  431. package/ux-demo/recipes/types.ts +9 -1
  432. package/ux-demo/recipes/workspace-header.tsx +72 -3
  433. package/dist/browser/dialog-header.js +0 -5
  434. package/dist/browser/menu-action-row.js +0 -3
  435. package/dist/browser/menu-header.js +0 -5
  436. package/dist/browser/menu-item.js +0 -3
  437. package/dist/browser/page-header.js +0 -3
  438. package/dist/chunk-2PES33HS.js +0 -13
  439. package/dist/chunk-2PES33HS.js.map +0 -1
  440. package/dist/chunk-55DATV3A.js +0 -10
  441. package/dist/chunk-55DATV3A.js.map +0 -1
  442. package/dist/chunk-7BR3DO2A.js.map +0 -1
  443. package/dist/chunk-7LDCTU7D.js +0 -134
  444. package/dist/chunk-7LDCTU7D.js.map +0 -1
  445. package/dist/chunk-AQNKJRFH.js +0 -79
  446. package/dist/chunk-AQNKJRFH.js.map +0 -1
  447. package/dist/chunk-CTQKI2XL.js +0 -28
  448. package/dist/chunk-CTQKI2XL.js.map +0 -1
  449. package/dist/chunk-DAFFDO33.js +0 -14
  450. package/dist/chunk-DAFFDO33.js.map +0 -1
  451. package/dist/chunk-EGHL5S4P.js +0 -33
  452. package/dist/chunk-EGHL5S4P.js.map +0 -1
  453. package/dist/chunk-GY5WH7TO.js +0 -10
  454. package/dist/chunk-GY5WH7TO.js.map +0 -1
  455. package/dist/chunk-H5AGGVU5.js +0 -75
  456. package/dist/chunk-H5AGGVU5.js.map +0 -1
  457. package/dist/chunk-K3G72I6D.js +0 -24
  458. package/dist/chunk-K3G72I6D.js.map +0 -1
  459. package/dist/chunk-M5JRI6MN.js +0 -27
  460. package/dist/chunk-M5JRI6MN.js.map +0 -1
  461. package/dist/chunk-MBR3TYNL.js +0 -14
  462. package/dist/chunk-MBR3TYNL.js.map +0 -1
  463. package/dist/chunk-MFKMHK5W.js +0 -10
  464. package/dist/chunk-MFKMHK5W.js.map +0 -1
  465. package/dist/chunk-MQIZAD4A.js.map +0 -1
  466. package/dist/chunk-NPPD2A6O.js +0 -40
  467. package/dist/chunk-NPPD2A6O.js.map +0 -1
  468. package/dist/chunk-R6JHHBKQ.js +0 -19
  469. package/dist/chunk-R6JHHBKQ.js.map +0 -1
  470. package/dist/chunk-S3TPM2LP.js +0 -23
  471. package/dist/chunk-S3TPM2LP.js.map +0 -1
  472. package/dist/chunk-TRNBHPNW.js.map +0 -1
  473. package/dist/chunk-VHW5YNIB.js +0 -17
  474. package/dist/chunk-VHW5YNIB.js.map +0 -1
  475. package/dist/chunk-WVPYVDXY.js +0 -15
  476. package/dist/chunk-WVPYVDXY.js.map +0 -1
  477. package/dist/chunk-XZXRLY5Q.js +0 -33
  478. package/dist/chunk-XZXRLY5Q.js.map +0 -1
  479. package/dist/dialog-header.d.ts +0 -15
  480. package/dist/dialog-header.js +0 -5
  481. package/dist/dialog-header.js.map +0 -1
  482. package/dist/menu-action-row.js +0 -4
  483. package/dist/menu-action-row.js.map +0 -1
  484. package/dist/menu-header.d.ts +0 -41
  485. package/dist/menu-header.js +0 -6
  486. package/dist/menu-header.js.map +0 -1
  487. package/dist/menu-item.d.ts +0 -31
  488. package/dist/menu-item.js +0 -4
  489. package/dist/menu-item.js.map +0 -1
  490. package/dist/page-header.d.ts +0 -9
  491. package/dist/page-header.js +0 -3
  492. package/dist/page-header.js.map +0 -1
  493. package/dist/styles/dialog-header.css +0 -87
  494. package/dist/styles/menu-action-row.css +0 -160
  495. package/dist/styles/menu-header.css +0 -143
  496. package/dist/styles/menu-item.css +0 -123
  497. package/dist/styles/page-header.css +0 -33
  498. package/ux-demo/recipes/master-detail-dialog.tsx +0 -45
@@ -0,0 +1,364 @@
1
+ # Catalog — a reusable component-gallery shell
2
+
3
+ `@kerfjs/ui/catalog` is an opt-in, whole-screen shell for building a **component
4
+ catalog** — the collapsible category sidebar + titled preview stage + resources
5
+ footer that the kerf UI catalog itself uses. Point it at your own components and
6
+ you get the same shell without rebuilding it. Like the app layouts, it is a
7
+ subpath-only, tree-shakeable module that adds nothing to the main barrel.
8
+
9
+ ```bash
10
+ npm install @kerfjs/ui # kerfjs is a peer; @kerfjs/ui/select/register is needed only if entries use `related`
11
+ ```
12
+
13
+ - `Catalog(props)` returns the shell as `SafeHtml` (a `<main class="kui-catalog">`).
14
+ It is **controlled and stateless**: your app owns the `active`, `collapsed`, and
15
+ `theme` signals and computes the preview `content` from `active` in its own
16
+ `mount()` render.
17
+ - `wireCatalog(root, options)` wires the interactions (sidebar selection, the
18
+ related-entry popup menu, and the collapse/theme toggles) with one delegated
19
+ listener set and returns a disposer; it can also mirror the active id into the
20
+ URL and reveal the active sidebar row after a controlled render.
21
+
22
+ ## What you supply
23
+
24
+ - **`sections`** — category-grouped entries: `{ category, entries: [{ id, name,
25
+ description?, tags?, resources?, related? }] }`. Each entry becomes a sidebar
26
+ `ListItem` under a `ListHeader` for its category. Short `tags` render as quiet
27
+ trailing pills for decision metadata such as `Discouraged`.
28
+ - **`content`** — the rendered preview for the active entry. Keep a map of `id →
29
+ () => SafeHtml` in your app and call `renderers[active]()` in your render.
30
+ - **`brand`** — `{ title, subtitle?, logoUrl? }` for the sidebar header.
31
+ - **`secondarySections`** — an optional secondary "ecosystem" group shown below the
32
+ primary sections with a quieter treatment: `{ label, sections, collapsible?,
33
+ expanded? }`. When `collapsible`, the label is a disclosure toggle controlling
34
+ `expanded` (the app owns it; wire it with `wireCatalog`'s `onToggleSecondary`).
35
+ - Optional slots: `headerActions` (extra header controls), `sidebarFooter` (extra
36
+ sidebar content), and `status` (a footer status line).
37
+
38
+ Per-entry `resources` render as "open in new tab" links in the footer, and
39
+ `related` renders a "Related entries" popup menu — a `single` `ToolbarControlGroup`
40
+ around a `wa-dropdown` (grouped by each entry's `group`), so register its elements
41
+ with `@kerfjs/ui/select/register` when you use it.
42
+
43
+ ## Catalog demo authoring contract
44
+
45
+ This section is the single authoritative contract for tools and people that
46
+ author Catalog previews. The machine-readable discovery entry is
47
+ [`catalog-authoring.json`](../ai/catalog-authoring.json); exact props remain in
48
+ [`public-api-signatures-v1.md`](../ai/public-api-signatures-v1.md#kerfjsuicatalog).
49
+ The component catalog deliberately does not duplicate these rules: it describes
50
+ which component to choose, while this contract describes how to present the
51
+ chosen component.
52
+
53
+ ### Choose the demo mode
54
+
55
+ Classify every entry before rendering it:
56
+
57
+ | Entry kind | Preview purpose | Geometry overlay |
58
+ | --------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
59
+ | Focused component | Show one public component, its meaningful variants, and adverse states. | `true`; the overlay inspects each selected specimen. |
60
+ | Composition or recipe | Show several components cooperating as one product surface. | `false`; child geometry remains unmarked so the composition can be read as a whole. |
61
+
62
+ Keep `geometryOverlay` present and compute it from the active entry. Do not make
63
+ it a permanent catalog-wide `true`, and do not give individual specimens their
64
+ own overlay implementation.
65
+
66
+ ### Required structure
67
+
68
+ - `Catalog` is the one shell. The app owns active-entry state and passes one
69
+ active preview through `content`.
70
+ - `CatalogExampleStack` is the group for one preview's rows. Put route/test
71
+ metadata such as `data-demo` on its rendered root through `rootAttributes`.
72
+ Pass `label` when the stack needs an accessible name: the rendered `section`
73
+ is then exposed as a named region. An unlabeled stack remains an ordinary
74
+ grouping rather than adding an unnecessary landmark.
75
+ - `CatalogExample` is one row: optional generated `ListHeader` label, optional
76
+ generated note, then one specimen or one intentionally coupled specimen
77
+ cluster. Use one row per variant/state; do not hand-author the helper's private
78
+ classes.
79
+ - The specimen is an immediate child of `CatalogExample`. A focused component
80
+ row should place the component root there, without a decorative card or
81
+ spacing wrapper. A composition row may place the composition root there.
82
+
83
+ Use `align="glyph"` for a bare glyph/text specimen, `align="inline-control"`
84
+ for a control whose own inline padding contributes about 8px, and `align="none"`
85
+ (the default) for a content item or composition that owns its geometry.
86
+
87
+ ```tsx
88
+ import { CatalogExample, CatalogExampleStack } from "@kerfjs/ui/catalog";
89
+
90
+ const buttonPreview = (
91
+ <CatalogExampleStack
92
+ label="Button variants"
93
+ rootAttributes={{ "data-demo": "button" }}
94
+ >
95
+ <CatalogExample label="Icon" note="A bare glyph." align="glyph">
96
+ <LucideIcon icon={Plus} name="plus" />
97
+ </CatalogExample>
98
+ <CatalogExample label="Control" align="inline-control">
99
+ <SegmentedControl id="view" label="View" value="list" choices={choices} />
100
+ </CatalogExample>
101
+ <CatalogExample
102
+ label="Authoring note"
103
+ note="Explanatory chrome is not a specimen."
104
+ rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}
105
+ >
106
+ <p>Use the public helper contract.</p>
107
+ </CatalogExample>
108
+ </CatalogExampleStack>
109
+ );
110
+ ```
111
+
112
+ The overlay selects every immediate child of a `CatalogExample`; the helper
113
+ marks its generated label and note so they are excluded automatically. It does
114
+ not recursively promote a nested child to be the specimen. Outside an example
115
+ row, it selects only top-level
116
+ `[data-component]` roots in the canvas and ignores nested component descendants.
117
+ These rules keep a row's label/group scaffolding out of the measurement and make
118
+ the authored nesting determine exactly what is inspected.
119
+
120
+ ### Metadata ownership
121
+
122
+ Use `rootAttributes` on either helper for authoring metadata such as `data-demo`
123
+ or `data-catalog-geometry-overlay-skip`; the metadata lands on that helper's
124
+ rendered root. The slot accepts only `data-*` strings. Structural
125
+ `data-catalog-example`, `data-catalog-example-stack`, and `data-align` semantics
126
+ remain helper-owned and are rejected case-insensitively at runtime, including
127
+ from structurally widened or JavaScript objects. Do not copy the helpers'
128
+ private `kui-catalog-*` classes into preview markup.
129
+
130
+ The app owns entry ids, `kind`, routing, sources, relationships, and test hooks.
131
+ The helpers own their structural markers, label/note anatomy, alignment marker,
132
+ and private classes. Component metadata such as margin/border/padding ownership
133
+ lives in `component-catalog.json`; do not infer or overwrite it from overlay
134
+ pixels.
135
+
136
+ ### Geometry overlay and legend
137
+
138
+ Pass the conditional `geometryOverlay` boolean to `Catalog`, then call
139
+ `wireCatalogGeometryOverlay(root)` once after the first render and retain its
140
+ disposer alongside `wireCatalog`'s.
141
+
142
+ | Overlay mark | Meaning | It is not |
143
+ | ----------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
144
+ | Orange translucent band | A positive computed margin on the selected specimen, after subtracting the helper's alignment inset. | Padding, gap, or empty content. Zero and negative margins are not drawn. |
145
+ | Accent solid edge | The selected specimen's computed border widths and radius, except `none`/`hidden` styles. | A focus ring, outline, child border, or metadata ownership claim. |
146
+ | Quiet dashed outline | The border-box bound of a transparent selected specimen with no visible border. | A real CSS border, padding edge, or proof that the specimen owns size. |
147
+
148
+ The overlay recomputes from live rendered styles when specimens resize, preview
149
+ markup changes, theme/root attributes change, or loaded/inline stylesheets
150
+ change. Component CSS is therefore the source of truth; do not duplicate margin
151
+ or border values in demo data. The overlay does not visualize padding, gaps,
152
+ negative/zero margins, scroll overflow, hit targets, or nested descendants.
153
+ Inspect computed styles and the machine-readable `geometry` metadata for those
154
+ facts. Opaque borderless specimens do not receive the dashed bound.
155
+
156
+ ```tsx
157
+ <Catalog
158
+ {...props}
159
+ geometryOverlay={activeEntry.kind === "component"}
160
+ content={renderers[active.value]()}
161
+ />
162
+ ```
163
+
164
+ Put `rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}` on a
165
+ `CatalogExample` or `CatalogExampleStack` only when that whole subtree is
166
+ explanatory chrome rather than a specimen. The marker excludes the marked root
167
+ and every descendant from selection; it does not merely hide one band. It is
168
+ normally unnecessary in a composition because the active entry already sets the
169
+ global overlay to `false`.
170
+
171
+ Use the overlay together with machine-readable geometry ownership metadata; the
172
+ overlay verifies what is rendered, while metadata tells people and AI tools
173
+ whether the component, its parent, or its children are responsible for margin,
174
+ border, and padding. Catalogs for downstream components should conform to the
175
+ [`component-catalog-extension.schema.json`](../ai/component-catalog-extension.schema.json)
176
+ contract and can start from the checked
177
+ [`component-catalog-extension.json`](./examples/component-catalog-extension.json)
178
+ example; provide those entries beside Kerf's shipped catalog to AI tools.
179
+
180
+ ### Automated conformance and reviewed exceptions
181
+
182
+ Run `npm run check:demo-conformance` after changing a first-party demo, its
183
+ catalog kind, or the shell's overlay logic. The TypeScript-AST gate verifies
184
+ facts that source can prove without guessing at rendered intent:
185
+
186
+ - focused component routes import and use `CatalogExampleStack` and
187
+ `CatalogExample` from the public package;
188
+ - focused route metadata uses the helpers' `rootAttributes` slot, example rows
189
+ are not empty, and composition routes do not add redundant skip markers;
190
+ - every `@kerfjs/ui` import is a published package export and relative imports
191
+ do not reach into `ui/src`;
192
+ - demo JSX does not copy private `kui-catalog-*` structural classes; and
193
+ - the shell derives both geometry-overlay enablement and documented demo mode
194
+ from the active entry's source and kind.
195
+
196
+ The gate deliberately does not infer component ownership from arbitrary class
197
+ names, margins, borders, or nested descendants. Runtime selection and geometry
198
+ remain the browser suite's job.
199
+
200
+ A focused route may bypass the two public layout helpers only when the route's
201
+ stage geometry is itself the reviewed specimen. Add the narrow waiver to
202
+ [`catalog-conformance-exceptions.json`](../ux-demo/catalog-conformance-exceptions.json)
203
+ with the exact route, source file, stable diagnostic ids, a substantive reason,
204
+ and the reviewing `KF-*` ticket. Only helper/metadata rules are waivable;
205
+ private imports, private markup, empty examples, composition overlay drift, and
206
+ shell-mode drift always fail. The gate rejects duplicate, malformed, unused,
207
+ and stale exceptions, so delete a waiver when its route adopts the standard
208
+ helpers.
209
+
210
+ ## Selection reveal
211
+
212
+ Set `revealSelection: true` on `wireCatalog` for a long desktop sidebar. After
213
+ `onSelect` updates controlled state, the helper waits one animation frame, finds
214
+ the exact matching `data-item-id`, and scrolls it into view without changing
215
+ focus. A newer selection or disposal cancels the pending reveal. The default
216
+ media guard is the Catalog desktop layout (`min-width: 52.01rem`), so compact
217
+ layouts keep their existing scroll position.
218
+
219
+ Pass an options object instead of `true` to customize `block`, `inline`,
220
+ `behavior`, or `media`; `media: false` deliberately enables the behavior at all
221
+ sizes. For an initial deep link that did not come through `wireCatalog`, call
222
+ `revealCatalogEntry(app, initialId, { block: "center" })` after the first mount.
223
+
224
+ ## Complete example
225
+
226
+ ```tsx
227
+ import { mount, signal, type SafeHtml } from "kerfjs";
228
+ import {
229
+ Catalog,
230
+ CatalogExample,
231
+ CatalogExampleStack,
232
+ type CatalogSection,
233
+ } from "@kerfjs/ui/catalog";
234
+ import {
235
+ revealCatalogEntry,
236
+ wireCatalog,
237
+ wireCatalogGeometryOverlay,
238
+ } from "@kerfjs/ui/wire-catalog";
239
+ import "@kerfjs/ui/styles.css"; // or import each primitive's CSS + @kerfjs/ui/catalog.css
240
+
241
+ type DemoKind = "component" | "composition";
242
+ type DemoEntry = CatalogSection["entries"][number] & { kind: DemoKind };
243
+
244
+ // 1. Describe selection and overlay mode once.
245
+ const entries: DemoEntry[] = [
246
+ {
247
+ id: "button",
248
+ name: "Button",
249
+ kind: "component",
250
+ description: "A pressable control.",
251
+ resources: [
252
+ { label: "Source", href: "/src/button.tsx", detail: "src/button.tsx" },
253
+ ],
254
+ },
255
+ {
256
+ id: "profile-form",
257
+ name: "Profile form",
258
+ kind: "composition",
259
+ description: "A labeled field and save action working together.",
260
+ },
261
+ ];
262
+ const sections: CatalogSection[] = [
263
+ {
264
+ category: "Examples",
265
+ entries: entries.map(({ kind: _kind, ...entry }) => entry),
266
+ },
267
+ ];
268
+
269
+ // 2. Every preview uses one public group and public example rows.
270
+ const renderers: Record<string, () => SafeHtml> = {
271
+ button: () => (
272
+ <CatalogExampleStack
273
+ label="Button states"
274
+ rootAttributes={{ "data-demo": "button" }}
275
+ >
276
+ <CatalogExample label="Default" align="inline-control">
277
+ <Button label="Save" />
278
+ </CatalogExample>
279
+ <CatalogExample
280
+ label="Authoring note"
281
+ note="This explanatory row is deliberately excluded from inspection."
282
+ rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}
283
+ >
284
+ <p>The application owns product copy and actions.</p>
285
+ </CatalogExample>
286
+ </CatalogExampleStack>
287
+ ),
288
+ "profile-form": () => (
289
+ <CatalogExampleStack
290
+ label="Profile form composition"
291
+ rootAttributes={{ "data-demo": "profile-form" }}
292
+ >
293
+ <CatalogExample label="Complete composition">
294
+ <ProfileForm />
295
+ </CatalogExample>
296
+ </CatalogExampleStack>
297
+ ),
298
+ };
299
+
300
+ // 3. App-owned state (domain: which entry; transient: collapsed; global: theme).
301
+ const initial =
302
+ new URLSearchParams(location.search).get("c") ?? sections[0].entries[0].id;
303
+ const active = signal(initial);
304
+ const collapsed = signal(false);
305
+ const theme = signal<"light" | "dark">("light");
306
+ const activeEntry = () => entries.find(({ id }) => id === active.value) ?? entries[0];
307
+
308
+ const app = document.getElementById("app")!;
309
+ mount(app, () => (
310
+ <Catalog
311
+ brand={{ title: "Acme UI", subtitle: "Design system" }}
312
+ sections={sections}
313
+ active={active.value}
314
+ content={renderers[active.value]?.() ?? <></>}
315
+ collapsed={collapsed.value}
316
+ theme={theme.value}
317
+ geometryOverlay={activeEntry().kind === "component"}
318
+ />
319
+ ));
320
+
321
+ wireCatalog(app, {
322
+ onSelect: (id) => {
323
+ active.value = id;
324
+ },
325
+ onToggleSidebar: () => {
326
+ collapsed.value = !collapsed.value;
327
+ },
328
+ onToggleTheme: () => {
329
+ theme.value = theme.value === "dark" ? "light" : "dark";
330
+ document.documentElement.dataset.theme = theme.value; // apply your theme however you like
331
+ },
332
+ urlParam: "c", // mirror the active id into ?c=<id>
333
+ revealSelection: true, // reveal long desktop sidebars without moving focus
334
+ });
335
+ wireCatalogGeometryOverlay(app);
336
+
337
+ // Optional for an initial deep link whose row may start outside the viewport.
338
+ revealCatalogEntry(app, initial, { block: "center" });
339
+ ```
340
+
341
+ ## Ownership boundary
342
+
343
+ `Catalog` renders the shell; the app owns everything stateful:
344
+
345
+ - **`active`** is domain state (which entry is shown) — the app's signal, updated in
346
+ `onSelect`, read to compute `content`.
347
+ - **`collapsed`** is transient UI — the app's signal, flipped in `onToggleSidebar`.
348
+ - **`theme`** is a global preference — the app's signal; `wireCatalog` only reports
349
+ the toggle, the app applies the theme (the shell reads `theme` to show the toggle's
350
+ opposite-state label). Omit `theme` to hide the toggle entirely.
351
+
352
+ ## Custom action names
353
+
354
+ The shell emits `data-action="catalog-select"` (sidebar items),
355
+ `catalog-toggle-sidebar`, and `catalog-toggle-theme`. Override them with
356
+ `selectAction` / `toggleSidebarAction` / `toggleThemeAction` on `Catalog` (and the
357
+ matching options on `wireCatalog`) if they collide with your own action table.
358
+
359
+ ## CSS
360
+
361
+ `Catalog` composes public primitives (`Toolbar`, `ListHeader`, `ListItem`, `Select`,
362
+ …). Import `@kerfjs/ui/styles.css` for the whole layer, or `@kerfjs/ui/catalog.css`
363
+ plus each composed primitive's CSS. The shell is theme-aware and responsive: it
364
+ stacks the sidebar above the detail below ~832px and hides it when collapsed.
@@ -0,0 +1,142 @@
1
+ # CollapsiblePanel + wireSidebar — reusable sidebar / drawer support
2
+
3
+ `@kerfjs/ui/collapsible-panel` and `@kerfjs/ui/wire-sidebar` are the standalone,
4
+ reusable pieces for an app's own **side rails** and **bottom drawers** — the same
5
+ collapse semantics the `Workbench` shell and the catalog sidebar use, but outside
6
+ a full shell so you can dock one panel wherever your layout needs it. They are
7
+ subpath-only, tree-shakeable modules that add nothing to the main barrel.
8
+
9
+ ```bash
10
+ npm install @kerfjs/ui # kerfjs is a peer
11
+ ```
12
+
13
+ Import the panel CSS (`@kerfjs/ui/collapsible-panel.css`) alongside `foundation.css`.
14
+
15
+ ## The pieces
16
+
17
+ - **`CollapsiblePanel({ id, side, collapsed?, size?, label?, children })`** — the
18
+ docked panel: a `'left'` / `'right'` rail or a `'bottom'` drawer. It owns only
19
+ presentation. Collapsing snaps the panel's own size to zero in one reflow while
20
+ the fixed-size content slides out via `transform` (composited, clipped) — never
21
+ a per-frame width/height animation. A bottom drawer anchors that fixed-size
22
+ content to its stable bottom edge, so opening and closing move monotonically
23
+ through the transform alone instead of transitioning from a changing layout
24
+ origin and snapping at the end. The app owns the `collapsed` signal; `size`
25
+ overrides the CSS default width/height.
26
+ Reusable shell policies are typed props: `separator`, `collapseMotion`,
27
+ `contentOverflow`, and `presentation`. A collapsed panel may also receive a
28
+ `restoreControl`, which Kerf places at the safe-area-aware `restorePosition`.
29
+ Overlay presentation also clamps fixed-size animated content to the configured
30
+ responsive overlay maximum, so a remembered desktop size cannot escape a narrow
31
+ viewport.
32
+ - **`CollapsiblePanelToggle({ side, collapsed, action, panelId?, label? })`** and
33
+ **`collapsiblePanelToggleIcon(side, collapsed)`** — the standard toggle
34
+ affordance and its icon convention, so every sidebar reads the same: `PanelLeft*`
35
+ for a left rail, `PanelRight*` for a right rail, `PanelBottom*` for a bottom
36
+ drawer — the `Close` glyph while open, the `Open` glyph while collapsed. Placement
37
+ is the app's: put a collapse toggle in the panel's own header and an expand toggle
38
+ somewhere always-visible (a toolbar) so it is reachable while collapsed.
39
+ - **`wireSidebar(root, { panels, deviceClass?, storage? })`** — the interaction
40
+ semantics. Each `panels` entry is `{ id, collapsed, toggleAction, storageKey? }`.
41
+ It:
42
+ - **toggles** the panel's `collapsed` signal when any `[data-action=toggleAction]`
43
+ button is clicked, and remembers the trigger;
44
+ - **manages focus** — moves focus into the panel when it opens, and restores it
45
+ to the trigger when it closes;
46
+ - **presents a compact overlay** when `deviceClass.compact` is true (pass a
47
+ `deviceClass()` signal): the open panel floats over the content with a
48
+ dismissable backdrop, Escape and backdrop-click collapse it, and Tab is trapped
49
+ within the panel (the ARIA dialog pattern);
50
+ - accepts `compactPresentation: "hidden"` when a compact application replaces
51
+ the panel with different navigation instead of overlaying it;
52
+ - keeps compact overlays exclusive by default, collapsing another open panel
53
+ when a new one opens (`exclusiveCompact: false` opts out);
54
+ - **persists** the collapsed state to `storage` (default `localStorage`) under
55
+ `storageKey`, seeding the signal on wire-up.
56
+
57
+ Returns a disposer. Retain it and call it on teardown.
58
+
59
+ ## Example
60
+
61
+ ```tsx
62
+ import { signal, mount } from "kerfjs";
63
+ import { deviceClass } from "@kerfjs/ui/device-class";
64
+ import {
65
+ CollapsiblePanel,
66
+ CollapsiblePanelToggle,
67
+ } from "@kerfjs/ui/collapsible-panel";
68
+ import { wireSidebar } from "@kerfjs/ui/wire-sidebar";
69
+ import "@kerfjs/ui/collapsible-panel.css";
70
+
71
+ const navCollapsed = signal(false);
72
+ const device = deviceClass();
73
+
74
+ const app = document.querySelector("#app")!;
75
+ mount(app, () => (
76
+ <div class="layout">
77
+ <CollapsiblePanel
78
+ id="nav"
79
+ side="left"
80
+ collapsed={navCollapsed.value}
81
+ label="Navigator"
82
+ >
83
+ <header>
84
+ <CollapsiblePanelToggle
85
+ side="left"
86
+ collapsed={navCollapsed.value}
87
+ action="toggle-nav"
88
+ panelId="nav"
89
+ />
90
+ </header>
91
+ {/* nav items */}
92
+ </CollapsiblePanel>
93
+ <main>
94
+ {navCollapsed.value && (
95
+ <CollapsiblePanelToggle
96
+ side="left"
97
+ collapsed
98
+ action="toggle-nav"
99
+ label="Show navigator"
100
+ />
101
+ )}
102
+ {/* content */}
103
+ </main>
104
+ </div>
105
+ ));
106
+
107
+ const stop = wireSidebar(app, {
108
+ panels: [
109
+ {
110
+ id: "nav",
111
+ collapsed: navCollapsed,
112
+ toggleAction: "toggle-nav",
113
+ storageKey: "app.nav-collapsed",
114
+ },
115
+ ],
116
+ deviceClass: device,
117
+ });
118
+ ```
119
+
120
+ ## When to use which
121
+
122
+ - One or two independent rails / a drawer you place yourself → **`CollapsiblePanel` +
123
+ `wireSidebar`**.
124
+ - A whole Xcode-like workspace (left rail + right rail + bottom drawer + work area
125
+ in one shell) → **[`Workbench`](workbench.md)**, which owns the layout and the same
126
+ collapse animation.
127
+ - Drag-to-resize a panel → compose **[`ResizableRegion`](../src/resizable-region.tsx)**
128
+ / `wireResizableRegions`; the app owns the size signal.
129
+
130
+ The app still owns everything domain-specific — which panels exist, their order,
131
+ sizes, content, and any per-project persistence — exactly as with the other layouts.
132
+
133
+ ## Recipe and coverage
134
+
135
+ The catalog ships a runnable **Collapsible sidebar** recipe — a left rail and a
136
+ bottom drawer with the standard toggles, the compact overlay, and per-panel
137
+ persistence: [open it](../ux-demo/?component=recipe-collapsible-sidebar) or read
138
+ [`recipes.md`](recipes.md#collapsible-sidebar) · [TSX source](../ux-demo/recipes/collapsible-sidebar.tsx).
139
+ It is covered end-to-end across Chromium, Firefox, and WebKit by
140
+ `tests/browser/collapsible-sidebar-recipe.spec.ts` (collapse/expand, focus
141
+ move/restore, the compact overlay + Escape/backdrop dismiss, and the Tab trap),
142
+ alongside the component/wire unit tests.