@kerfjs/ui 4.4.1 → 4.5.0-beta.1

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 (499) hide show
  1. package/README.md +425 -42
  2. package/ai/application-ui-diagnostic-ids-v1.json +92 -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 +102 -0
  12. package/ai/compile-time-contracts-v1.schema.json +49 -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 +319 -0
  17. package/ai/component-catalog-v2.d.ts +67 -0
  18. package/ai/component-catalog-v2.json +4675 -0
  19. package/ai/component-catalog-v2.schema.json +246 -0
  20. package/ai/component-catalog.json +5826 -0
  21. package/ai/component-catalog.schema.json +95 -0
  22. package/ai/public-api-signatures-v1.md +1719 -0
  23. package/ai/skill.md +129 -21
  24. package/ai/webawesome-jsx-signatures-v1.md +106 -0
  25. package/analyzer/cli.mjs +46 -0
  26. package/analyzer/index.d.mts +45 -0
  27. package/analyzer/index.mjs +737 -0
  28. package/analyzer/report.schema.json +66 -0
  29. package/dist/app-tab.d.ts +14 -2
  30. package/dist/app-tab.js +3 -1
  31. package/dist/browser/app-tab.js +3 -2
  32. package/dist/browser/disclosure-arrow.js +4 -0
  33. package/dist/browser/empty-state.js +3 -3
  34. package/dist/browser/floating-toolbar.js +3 -0
  35. package/dist/browser/list-action-row.js +4 -0
  36. package/dist/browser/list-header.js +6 -0
  37. package/dist/browser/list-inset-control.js +3 -0
  38. package/dist/browser/list-inset-text.js +3 -0
  39. package/dist/browser/list-item.js +4 -0
  40. package/dist/browser/list.js +3 -0
  41. package/dist/browser/loading-spinner.js +2 -2
  42. package/dist/browser/lucide-icon.js +2 -2
  43. package/dist/browser/pane.js +3 -0
  44. package/dist/browser/panel-header.js +7 -0
  45. package/dist/browser/resizable-region.js +2 -2
  46. package/dist/browser/segmented-control.js +3 -2
  47. package/dist/browser/select.js +4 -3
  48. package/dist/browser/skeleton.js +3 -0
  49. package/dist/browser/state-banner.js +3 -2
  50. package/dist/browser/sunken-panel.js +3 -0
  51. package/dist/browser/tab-bar.js +2 -2
  52. package/dist/browser/token-search-field.js +4 -0
  53. package/dist/browser/toolbar-control-group.js +2 -2
  54. package/dist/browser/toolbar-text.js +3 -2
  55. package/dist/browser/toolbar.js +2 -2
  56. package/dist/browser/value-table.js +3 -2
  57. package/dist/catalog-resources.d.ts +34 -0
  58. package/dist/catalog-resources.js +22 -0
  59. package/dist/catalog-resources.js.map +1 -0
  60. package/dist/catalog.d.ts +150 -0
  61. package/dist/catalog.js +377 -0
  62. package/dist/catalog.js.map +1 -0
  63. package/dist/chunk-5VTB2BKP.js +35 -0
  64. package/dist/chunk-5VTB2BKP.js.map +1 -0
  65. package/dist/chunk-6WTETUZ6.js +21 -0
  66. package/dist/chunk-6WTETUZ6.js.map +1 -0
  67. package/dist/chunk-7KEUJIIC.js +218 -0
  68. package/dist/chunk-7KEUJIIC.js.map +1 -0
  69. package/dist/chunk-7XRDUG4Y.js +58 -0
  70. package/dist/chunk-7XRDUG4Y.js.map +1 -0
  71. package/dist/chunk-B3A3IVO5.js +83 -0
  72. package/dist/chunk-B3A3IVO5.js.map +1 -0
  73. package/dist/chunk-DPDBVKNA.js +113 -0
  74. package/dist/chunk-DPDBVKNA.js.map +1 -0
  75. package/dist/chunk-FEAMR7S4.js +68 -0
  76. package/dist/chunk-FEAMR7S4.js.map +1 -0
  77. package/dist/chunk-FEY65TBV.js +15 -0
  78. package/dist/chunk-FEY65TBV.js.map +1 -0
  79. package/dist/{chunk-TRNBHPNW.js → chunk-IJBSZ4NX.js} +11 -8
  80. package/dist/chunk-IJBSZ4NX.js.map +1 -0
  81. package/dist/chunk-J5BFYY7Q.js +35 -0
  82. package/dist/chunk-J5BFYY7Q.js.map +1 -0
  83. package/dist/chunk-JRKSK2HX.js +79 -0
  84. package/dist/chunk-JRKSK2HX.js.map +1 -0
  85. package/dist/chunk-KWWR5VMS.js +45 -0
  86. package/dist/chunk-KWWR5VMS.js.map +1 -0
  87. package/dist/chunk-LPOMRC4C.js +25 -0
  88. package/dist/chunk-LPOMRC4C.js.map +1 -0
  89. package/dist/chunk-M7JBER5U.js +41 -0
  90. package/dist/chunk-M7JBER5U.js.map +1 -0
  91. package/dist/chunk-NENBUYX5.js +79 -0
  92. package/dist/chunk-NENBUYX5.js.map +1 -0
  93. package/dist/chunk-NWSEP5RT.js +117 -0
  94. package/dist/chunk-NWSEP5RT.js.map +1 -0
  95. package/dist/chunk-OJWWHFH3.js +426 -0
  96. package/dist/chunk-OJWWHFH3.js.map +1 -0
  97. package/dist/chunk-PSKN344N.js +114 -0
  98. package/dist/chunk-PSKN344N.js.map +1 -0
  99. package/dist/chunk-PX3YSS5C.js +32 -0
  100. package/dist/chunk-PX3YSS5C.js.map +1 -0
  101. package/dist/chunk-QO55FN2Y.js +20 -0
  102. package/dist/chunk-QO55FN2Y.js.map +1 -0
  103. package/dist/chunk-R7ZWUN64.js +38 -0
  104. package/dist/chunk-R7ZWUN64.js.map +1 -0
  105. package/dist/chunk-RKVQEH4J.js +61 -0
  106. package/dist/chunk-RKVQEH4J.js.map +1 -0
  107. package/dist/chunk-SRSJO5QE.js +40 -0
  108. package/dist/chunk-SRSJO5QE.js.map +1 -0
  109. package/dist/chunk-SYDMYBPG.js +31 -0
  110. package/dist/chunk-SYDMYBPG.js.map +1 -0
  111. package/dist/chunk-TB6DY7H5.js +25 -0
  112. package/dist/chunk-TB6DY7H5.js.map +1 -0
  113. package/dist/chunk-UFZKLBCF.js +108 -0
  114. package/dist/chunk-UFZKLBCF.js.map +1 -0
  115. package/dist/{chunk-MQIZAD4A.js → chunk-UKKSMZYT.js} +76 -22
  116. package/dist/chunk-UKKSMZYT.js.map +1 -0
  117. package/dist/chunk-W5L2JSBY.js +40 -0
  118. package/dist/chunk-W5L2JSBY.js.map +1 -0
  119. package/dist/chunk-Y45PPBDV.js +105 -0
  120. package/dist/chunk-Y45PPBDV.js.map +1 -0
  121. package/dist/chunk-Y6LYKFXY.js +43 -0
  122. package/dist/chunk-Y6LYKFXY.js.map +1 -0
  123. package/dist/collapsible-panel.d.ts +66 -0
  124. package/dist/collapsible-panel.js +63 -0
  125. package/dist/collapsible-panel.js.map +1 -0
  126. package/dist/device-class.d.ts +62 -0
  127. package/dist/device-class.js +103 -0
  128. package/dist/device-class.js.map +1 -0
  129. package/dist/disclosure-arrow.d.ts +14 -0
  130. package/dist/disclosure-arrow.js +4 -0
  131. package/dist/disclosure-arrow.js.map +1 -0
  132. package/dist/divider-sides-267FA7sY.d.ts +4 -0
  133. package/dist/empty-state.d.ts +1 -1
  134. package/dist/empty-state.js +1 -1
  135. package/dist/floating-toolbar.d.ts +28 -0
  136. package/dist/floating-toolbar.js +3 -0
  137. package/dist/floating-toolbar.js.map +1 -0
  138. package/dist/index.d.ts +20 -9
  139. package/dist/index.js +29 -18
  140. package/dist/list-action-row.d.ts +52 -0
  141. package/dist/list-action-row.js +5 -0
  142. package/dist/list-action-row.js.map +1 -0
  143. package/dist/list-header.d.ts +64 -0
  144. package/dist/list-header.js +7 -0
  145. package/dist/list-header.js.map +1 -0
  146. package/dist/list-inset-control.d.ts +16 -0
  147. package/dist/list-inset-control.js +3 -0
  148. package/dist/list-inset-control.js.map +1 -0
  149. package/dist/list-inset-text.d.ts +25 -0
  150. package/dist/list-inset-text.js +3 -0
  151. package/dist/list-inset-text.js.map +1 -0
  152. package/dist/list-item.d.ts +33 -0
  153. package/dist/list-item.js +5 -0
  154. package/dist/list-item.js.map +1 -0
  155. package/dist/list.d.ts +19 -0
  156. package/dist/list.js +3 -0
  157. package/dist/list.js.map +1 -0
  158. package/dist/loading-spinner.js +1 -1
  159. package/dist/nav-stack.d.ts +38 -0
  160. package/dist/nav-stack.js +5 -0
  161. package/dist/nav-stack.js.map +1 -0
  162. package/dist/pane.d.ts +49 -0
  163. package/dist/pane.js +4 -0
  164. package/dist/pane.js.map +1 -0
  165. package/dist/panel-header.d.ts +42 -0
  166. package/dist/panel-header.js +7 -0
  167. package/dist/panel-header.js.map +1 -0
  168. package/dist/resizable-region.d.ts +3 -1
  169. package/dist/resizable-region.js +1 -1
  170. package/dist/segmented-control.d.ts +8 -6
  171. package/dist/segmented-control.js +2 -1
  172. package/dist/select-register.d.ts +5 -1
  173. package/dist/select-register.js +3 -0
  174. package/dist/select-register.js.map +1 -1
  175. package/dist/select.d.ts +15 -6
  176. package/dist/select.js +2 -1
  177. package/dist/skeleton.d.ts +24 -0
  178. package/dist/skeleton.js +3 -0
  179. package/dist/skeleton.js.map +1 -0
  180. package/dist/split-view.d.ts +42 -0
  181. package/dist/split-view.js +81 -0
  182. package/dist/split-view.js.map +1 -0
  183. package/dist/state-banner.d.ts +8 -3
  184. package/dist/state-banner.js +2 -1
  185. package/dist/styles/app-tab.css +152 -0
  186. package/dist/styles/catalog.css +575 -0
  187. package/dist/styles/collapsible-panel.css +146 -0
  188. package/dist/styles/disclosure-arrow.css +23 -0
  189. package/dist/styles/empty-state.css +47 -0
  190. package/dist/styles/floating-toolbar.css +38 -0
  191. package/dist/styles/foundation.css +362 -0
  192. package/dist/styles/layout.css +55 -0
  193. package/dist/styles/list-action-row.css +160 -0
  194. package/dist/styles/list-header.css +160 -0
  195. package/dist/styles/list-inset-control.css +7 -0
  196. package/dist/styles/list-inset-text.css +14 -0
  197. package/dist/styles/list-item.css +123 -0
  198. package/dist/styles/list.css +43 -0
  199. package/dist/styles/loading-spinner.css +21 -0
  200. package/dist/styles/lucide-icon.css +6 -0
  201. package/dist/styles/nav-stack.css +111 -0
  202. package/dist/styles/pane.css +62 -0
  203. package/dist/styles/panel-header.css +64 -0
  204. package/dist/styles/resizable-region.css +111 -0
  205. package/{src → dist/styles}/segmented-control.css +58 -31
  206. package/dist/styles/select.css +140 -0
  207. package/dist/styles/skeleton.css +31 -0
  208. package/dist/styles/split-view.css +35 -0
  209. package/dist/styles/state-banner.css +198 -0
  210. package/dist/styles/styles.css +24 -0
  211. package/dist/styles/sunken-panel.css +22 -0
  212. package/dist/styles/tab-bar.css +45 -0
  213. package/dist/styles/tab-scaffold.css +84 -0
  214. package/dist/styles/token-search-field.css +289 -0
  215. package/dist/styles/toolbar-control-group.css +225 -0
  216. package/dist/styles/toolbar-text.css +92 -0
  217. package/dist/styles/toolbar.css +97 -0
  218. package/dist/styles/value-table.css +75 -0
  219. package/dist/styles/webawesome.css +399 -0
  220. package/dist/styles/workbench.css +94 -0
  221. package/dist/sunken-panel.d.ts +19 -0
  222. package/dist/sunken-panel.js +3 -0
  223. package/dist/sunken-panel.js.map +1 -0
  224. package/dist/tab-bar.d.ts +10 -2
  225. package/dist/tab-bar.js +1 -1
  226. package/dist/tab-scaffold.d.ts +30 -0
  227. package/dist/tab-scaffold.js +51 -0
  228. package/dist/tab-scaffold.js.map +1 -0
  229. package/dist/token-search-field.d.ts +59 -0
  230. package/dist/token-search-field.js +4 -0
  231. package/dist/token-search-field.js.map +1 -0
  232. package/dist/toolbar-control-group.d.ts +11 -5
  233. package/dist/toolbar-control-group.js +1 -1
  234. package/dist/toolbar-text.d.ts +31 -4
  235. package/dist/toolbar-text.js +2 -1
  236. package/dist/toolbar.d.ts +5 -3
  237. package/dist/toolbar.js +1 -1
  238. package/dist/value-table.d.ts +13 -3
  239. package/dist/value-table.js +2 -1
  240. package/dist/webawesome.d.ts +100 -0
  241. package/dist/webawesome.js +3 -0
  242. package/dist/webawesome.js.map +1 -0
  243. package/dist/wire-catalog.d.ts +57 -0
  244. package/dist/wire-catalog.js +302 -0
  245. package/dist/wire-catalog.js.map +1 -0
  246. package/dist/wire-nav-stack.d.ts +15 -0
  247. package/dist/wire-nav-stack.js +103 -0
  248. package/dist/wire-nav-stack.js.map +1 -0
  249. package/dist/wire-resizable-regions.d.ts +1 -1
  250. package/dist/wire-resizable-regions.js +1 -1
  251. package/dist/wire-sidebar.d.ts +43 -0
  252. package/dist/wire-sidebar.js +128 -0
  253. package/dist/wire-sidebar.js.map +1 -0
  254. package/dist/wire-tab-bars.d.ts +18 -2
  255. package/dist/wire-tab-bars.js +1 -1
  256. package/dist/wire-tab-scaffold.d.ts +11 -0
  257. package/dist/wire-tab-scaffold.js +21 -0
  258. package/dist/wire-tab-scaffold.js.map +1 -0
  259. package/dist/wire-token-search-fields.d.ts +111 -0
  260. package/dist/wire-token-search-fields.js +5 -0
  261. package/dist/wire-token-search-fields.js.map +1 -0
  262. package/dist/workbench.d.ts +33 -0
  263. package/dist/workbench.js +64 -0
  264. package/dist/workbench.js.map +1 -0
  265. package/docs/accessibility.md +124 -4
  266. package/docs/app-layouts.md +61 -0
  267. package/docs/catalog.md +364 -0
  268. package/docs/collapsible-panel.md +129 -0
  269. package/docs/component-contract.md +312 -5
  270. package/docs/component-selection.md +219 -0
  271. package/docs/design/templates/empty-state/filtered-dark.svg +1 -0
  272. package/docs/design/templates/empty-state/filtered.svg +1 -0
  273. package/docs/design/templates/empty-state/with-action-dark.svg +1 -0
  274. package/docs/design/templates/empty-state/with-action.svg +1 -0
  275. package/docs/design/templates/empty-state-dark.svg +1 -0
  276. package/docs/design/templates/empty-state.svg +1 -0
  277. package/docs/design/templates/list/compact-dark.svg +1 -0
  278. package/docs/design/templates/list/compact.svg +1 -0
  279. package/docs/design/templates/list/stack-dark.svg +1 -0
  280. package/docs/design/templates/list/stack.svg +1 -0
  281. package/docs/design/templates/list-action-row/default-dark.svg +1 -0
  282. package/docs/design/templates/list-action-row/default.svg +1 -0
  283. package/docs/design/templates/list-action-row/selected-dark.svg +1 -0
  284. package/docs/design/templates/list-action-row/selected.svg +1 -0
  285. package/docs/design/templates/list-action-row-dark.svg +1 -0
  286. package/docs/design/templates/list-action-row.svg +1 -0
  287. package/docs/design/templates/list-dark.svg +1 -0
  288. package/docs/design/templates/list-header/action-dark.svg +1 -0
  289. package/docs/design/templates/list-header/action.svg +1 -0
  290. package/docs/design/templates/list-header/count-dark.svg +1 -0
  291. package/docs/design/templates/list-header/count.svg +1 -0
  292. package/docs/design/templates/list-header/toggle-collapsed-dark.svg +1 -0
  293. package/docs/design/templates/list-header/toggle-collapsed.svg +1 -0
  294. package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -0
  295. package/docs/design/templates/list-header/toggle-expanded.svg +1 -0
  296. package/docs/design/templates/list-header-dark.svg +1 -0
  297. package/docs/design/templates/list-header.svg +1 -0
  298. package/docs/design/templates/list-item/default-dark.svg +1 -0
  299. package/docs/design/templates/list-item/default.svg +1 -0
  300. package/docs/design/templates/list-item/multiline-dark.svg +1 -0
  301. package/docs/design/templates/list-item/multiline.svg +1 -0
  302. package/docs/design/templates/list-item/selected-dark.svg +1 -0
  303. package/docs/design/templates/list-item/selected.svg +1 -0
  304. package/docs/design/templates/list-item/trailing-dark.svg +1 -0
  305. package/docs/design/templates/list-item/trailing.svg +1 -0
  306. package/docs/design/templates/list-item-dark.svg +1 -0
  307. package/docs/design/templates/list-item.svg +1 -0
  308. package/docs/design/templates/list.svg +1 -0
  309. package/docs/design/templates/panel-header/icon-actions-dark.svg +1 -0
  310. package/docs/design/templates/panel-header/icon-actions.svg +1 -0
  311. package/docs/design/templates/panel-header/icon-summary-actions-dark.svg +1 -0
  312. package/docs/design/templates/panel-header/icon-summary-actions.svg +1 -0
  313. package/docs/design/templates/panel-header/no-icon-dark.svg +1 -0
  314. package/docs/design/templates/panel-header/no-icon.svg +1 -0
  315. package/docs/design/templates/panel-header/page-heading-dark.svg +1 -0
  316. package/docs/design/templates/panel-header/page-heading.svg +1 -0
  317. package/docs/design/templates/panel-header/title-only-dark.svg +1 -0
  318. package/docs/design/templates/panel-header/title-only.svg +1 -0
  319. package/docs/design/templates/panel-header-dark.svg +1 -0
  320. package/docs/design/templates/panel-header.svg +1 -0
  321. package/docs/design/templates/segmented-control/equal-dark.svg +1 -0
  322. package/docs/design/templates/segmented-control/equal.svg +1 -0
  323. package/docs/design/templates/segmented-control/pill-small-dark.svg +1 -0
  324. package/docs/design/templates/segmented-control/pill-small.svg +1 -0
  325. package/docs/design/templates/segmented-control-dark.svg +1 -0
  326. package/docs/design/templates/segmented-control.svg +1 -0
  327. package/docs/design/templates/skeleton/avatar-dark.svg +1 -0
  328. package/docs/design/templates/skeleton/avatar.svg +1 -0
  329. package/docs/design/templates/skeleton/block-dark.svg +1 -0
  330. package/docs/design/templates/skeleton/block.svg +1 -0
  331. package/docs/design/templates/skeleton/lines-dark.svg +1 -0
  332. package/docs/design/templates/skeleton/lines.svg +1 -0
  333. package/docs/design/templates/skeleton-dark.svg +1 -0
  334. package/docs/design/templates/skeleton.svg +1 -0
  335. package/docs/design/templates/state-banner/danger-dark.svg +1 -0
  336. package/docs/design/templates/state-banner/danger.svg +1 -0
  337. package/docs/design/templates/state-banner/info-badge-dark.svg +1 -0
  338. package/docs/design/templates/state-banner/info-badge.svg +1 -0
  339. package/docs/design/templates/state-banner/info-dark.svg +1 -0
  340. package/docs/design/templates/state-banner/info.svg +1 -0
  341. package/docs/design/templates/state-banner/success-dark.svg +1 -0
  342. package/docs/design/templates/state-banner/success.svg +1 -0
  343. package/docs/design/templates/state-banner/warning-dark.svg +1 -0
  344. package/docs/design/templates/state-banner/warning.svg +1 -0
  345. package/docs/design/templates/state-banner-dark.svg +1 -0
  346. package/docs/design/templates/state-banner.svg +1 -0
  347. package/docs/design/templates/tab-bar/workspace-tabs-dark.svg +1 -0
  348. package/docs/design/templates/tab-bar/workspace-tabs.svg +1 -0
  349. package/docs/design/templates/tab-bar-dark.svg +1 -0
  350. package/docs/design/templates/tab-bar.svg +1 -0
  351. package/docs/design/templates/token-search-field/empty-dark.svg +1 -0
  352. package/docs/design/templates/token-search-field/empty.svg +1 -0
  353. package/docs/design/templates/token-search-field/tokens-dark.svg +1 -0
  354. package/docs/design/templates/token-search-field/tokens.svg +1 -0
  355. package/docs/design/templates/token-search-field-dark.svg +1 -0
  356. package/docs/design/templates/token-search-field.svg +1 -0
  357. package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -0
  358. package/docs/design/templates/toolbar/title-and-actions.svg +1 -0
  359. package/docs/design/templates/toolbar-control-group/borderless-single-dark.svg +1 -0
  360. package/docs/design/templates/toolbar-control-group/borderless-single.svg +1 -0
  361. package/docs/design/templates/toolbar-control-group/icon-buttons-dark.svg +1 -0
  362. package/docs/design/templates/toolbar-control-group/icon-buttons.svg +1 -0
  363. package/docs/design/templates/toolbar-control-group/push-buttons-dark.svg +1 -0
  364. package/docs/design/templates/toolbar-control-group/push-buttons.svg +1 -0
  365. package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -0
  366. package/docs/design/templates/toolbar-control-group/with-text.svg +1 -0
  367. package/docs/design/templates/toolbar-control-group-dark.svg +1 -0
  368. package/docs/design/templates/toolbar-control-group.svg +1 -0
  369. package/docs/design/templates/toolbar-dark.svg +1 -0
  370. package/docs/design/templates/toolbar-text/default-dark.svg +1 -0
  371. package/docs/design/templates/toolbar-text/default.svg +1 -0
  372. package/docs/design/templates/toolbar-text/large-dark.svg +1 -0
  373. package/docs/design/templates/toolbar-text/large.svg +1 -0
  374. package/docs/design/templates/toolbar-text/small-dark.svg +1 -0
  375. package/docs/design/templates/toolbar-text/small.svg +1 -0
  376. package/docs/design/templates/toolbar-text-dark.svg +1 -0
  377. package/docs/design/templates/toolbar-text.svg +1 -0
  378. package/docs/design/templates/toolbar.svg +1 -0
  379. package/docs/design/templates/value-table/details-dark.svg +1 -0
  380. package/docs/design/templates/value-table/details.svg +1 -0
  381. package/docs/design/templates/value-table-dark.svg +1 -0
  382. package/docs/design/templates/value-table.svg +1 -0
  383. package/docs/design/templates.md +130 -0
  384. package/docs/design-philosophy.md +59 -1
  385. package/docs/device-class.md +54 -0
  386. package/docs/examples/application-ui-profile.json +60 -0
  387. package/docs/examples/command-palette-adapter.tsx +120 -0
  388. package/docs/examples/component-catalog-extension-v2.json +81 -0
  389. package/docs/examples/component-catalog-extension.json +45 -0
  390. package/docs/layout.md +150 -0
  391. package/docs/nav-stack.md +56 -0
  392. package/docs/recipes.md +167 -0
  393. package/docs/split-view.md +49 -0
  394. package/docs/sunken-panel.md +53 -0
  395. package/docs/tab-scaffold.md +50 -0
  396. package/docs/type-contracts.md +102 -0
  397. package/docs/ui-analyzer.md +86 -0
  398. package/docs/ui-doctor.md +84 -0
  399. package/docs/ui-evaluator.md +122 -0
  400. package/docs/ux-demo.md +23 -9
  401. package/docs/webawesome-theme.md +87 -7
  402. package/docs/workbench.md +73 -0
  403. package/doctor/cli.mjs +115 -0
  404. package/doctor/config.schema.json +68 -0
  405. package/doctor/eslint-diagnostics.d.mts +8 -0
  406. package/doctor/eslint-diagnostics.mjs +7 -0
  407. package/doctor/index.d.mts +116 -0
  408. package/doctor/index.mjs +1257 -0
  409. package/doctor/report.schema.json +118 -0
  410. package/evaluator/cli.mjs +89 -0
  411. package/evaluator/index.d.mts +90 -0
  412. package/evaluator/index.mjs +1138 -0
  413. package/evaluator/report.schema.json +152 -0
  414. package/llms.txt +197 -14
  415. package/package.json +228 -46
  416. package/traversal-exclusions.mjs +27 -0
  417. package/ux-demo/recipes/app-shell.tsx +228 -0
  418. package/ux-demo/recipes/collapsible-sidebar.tsx +223 -0
  419. package/ux-demo/recipes/compact-toolbar.tsx +145 -0
  420. package/ux-demo/recipes/composer-form.tsx +151 -0
  421. package/ux-demo/recipes/list-detail-dialog.tsx +181 -0
  422. package/ux-demo/recipes/list-workspace-states.tsx +183 -0
  423. package/ux-demo/recipes/loaders.ts +36 -0
  424. package/ux-demo/recipes/loading-inspector.tsx +164 -0
  425. package/ux-demo/recipes/mount-recipe.ts +64 -0
  426. package/ux-demo/recipes/navigation-sidebar.tsx +137 -0
  427. package/ux-demo/recipes/navigation-stack.tsx +118 -0
  428. package/ux-demo/recipes/recipes.css +448 -0
  429. package/ux-demo/recipes/types.ts +19 -0
  430. package/ux-demo/recipes/workspace-header.tsx +67 -0
  431. package/dist/browser/dialog-header.js +0 -3
  432. package/dist/browser/menu-header.js +0 -3
  433. package/dist/browser/menu-item.js +0 -3
  434. package/dist/browser/page-header.js +0 -3
  435. package/dist/chunk-2PES33HS.js +0 -13
  436. package/dist/chunk-2PES33HS.js.map +0 -1
  437. package/dist/chunk-3O7ZEUW2.js +0 -27
  438. package/dist/chunk-3O7ZEUW2.js.map +0 -1
  439. package/dist/chunk-4HHCYSGT.js +0 -79
  440. package/dist/chunk-4HHCYSGT.js.map +0 -1
  441. package/dist/chunk-55DATV3A.js +0 -10
  442. package/dist/chunk-55DATV3A.js.map +0 -1
  443. package/dist/chunk-723YNJBH.js +0 -17
  444. package/dist/chunk-723YNJBH.js.map +0 -1
  445. package/dist/chunk-DAFFDO33.js +0 -14
  446. package/dist/chunk-DAFFDO33.js.map +0 -1
  447. package/dist/chunk-GY5WH7TO.js +0 -10
  448. package/dist/chunk-GY5WH7TO.js.map +0 -1
  449. package/dist/chunk-MBR3TYNL.js +0 -14
  450. package/dist/chunk-MBR3TYNL.js.map +0 -1
  451. package/dist/chunk-MFKMHK5W.js +0 -10
  452. package/dist/chunk-MFKMHK5W.js.map +0 -1
  453. package/dist/chunk-MQIZAD4A.js.map +0 -1
  454. package/dist/chunk-NHXX4JI5.js +0 -28
  455. package/dist/chunk-NHXX4JI5.js.map +0 -1
  456. package/dist/chunk-OPCJRCYO.js +0 -19
  457. package/dist/chunk-OPCJRCYO.js.map +0 -1
  458. package/dist/chunk-T5XNMVOI.js +0 -24
  459. package/dist/chunk-T5XNMVOI.js.map +0 -1
  460. package/dist/chunk-TRNBHPNW.js.map +0 -1
  461. package/dist/chunk-VHW5YNIB.js +0 -17
  462. package/dist/chunk-VHW5YNIB.js.map +0 -1
  463. package/dist/chunk-VKH2JLDZ.js +0 -10
  464. package/dist/chunk-VKH2JLDZ.js.map +0 -1
  465. package/dist/chunk-WVPYVDXY.js +0 -15
  466. package/dist/chunk-WVPYVDXY.js.map +0 -1
  467. package/dist/chunk-WY4CAEPG.js +0 -14
  468. package/dist/chunk-WY4CAEPG.js.map +0 -1
  469. package/dist/dialog-header.d.ts +0 -14
  470. package/dist/dialog-header.js +0 -3
  471. package/dist/dialog-header.js.map +0 -1
  472. package/dist/menu-header.d.ts +0 -15
  473. package/dist/menu-header.js +0 -3
  474. package/dist/menu-header.js.map +0 -1
  475. package/dist/menu-item.d.ts +0 -22
  476. package/dist/menu-item.js +0 -3
  477. package/dist/menu-item.js.map +0 -1
  478. package/dist/page-header.d.ts +0 -9
  479. package/dist/page-header.js +0 -3
  480. package/dist/page-header.js.map +0 -1
  481. package/src/app-tab.css +0 -20
  482. package/src/dialog-header.css +0 -9
  483. package/src/empty-state.css +0 -6
  484. package/src/foundation.css +0 -106
  485. package/src/loading-spinner.css +0 -4
  486. package/src/lucide-icon.css +0 -1
  487. package/src/menu-header.css +0 -13
  488. package/src/menu-item.css +0 -12
  489. package/src/page-header.css +0 -3
  490. package/src/resizable-region.css +0 -19
  491. package/src/select.css +0 -18
  492. package/src/state-banner.css +0 -31
  493. package/src/styles.css +0 -18
  494. package/src/tab-bar.css +0 -4
  495. package/src/toolbar-control-group.css +0 -25
  496. package/src/toolbar-text.css +0 -4
  497. package/src/toolbar.css +0 -9
  498. package/src/value-table.css +0 -5
  499. package/src/webawesome.css +0 -178
@@ -0,0 +1,61 @@
1
+ # Choosing an app layout
2
+
3
+ `@kerfjs/ui` ships four opt-in, tree-shakeable whole-screen layouts plus the
4
+ [`device-class`](device-class.md) signal that drives their responsive behavior.
5
+ This guide maps a **data + interaction + device** situation to the layout to
6
+ reach for, and states the device-class threshold at which the presentation
7
+ changes. The layouts:
8
+
9
+ - [`NavStack`](nav-stack.md) — push/pop navigation (a single pane is a one-entry stack).
10
+ - [`SplitView`](split-view.md) — list-detail (two panes, collapsing to a stack).
11
+ - [`Workbench`](workbench.md) — the Xcode-like collapsible rails + drawer.
12
+ - [`TabScaffold`](tab-scaffold.md) — the iOS bottom tab bar (each tab a stack).
13
+ - [`CollapsiblePanel` + `wireSidebar`](collapsible-panel.md) — a standalone
14
+ collapsible rail or bottom drawer (with `CollapsiblePanelToggle`), outside a full
15
+ shell: the standard collapse animation, icon convention, and `wireSidebar`
16
+ semantics (focus, compact overlay, persistence).
17
+
18
+ Derive responsiveness from `deviceClass()`: `compact` (a handset or portrait
19
+ tablet) means "one pane at a time"; `atLeast('tablet')` / `atLeast('desktop')`
20
+ gate the roomier presentations.
21
+
22
+ ## Decision matrix
23
+
24
+ | Situation | Layout | Device threshold |
25
+ | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
26
+ | Simple app, a few flat sections | `NavStack` with one entry (single pane); add `TabScaffold` for 2–5 co-equal sections on handset | `TabScaffold` on `compact`; promote its tabs to a `Workbench` rail / sidebar `atLeast('desktop')` |
27
+ | Drill-down browsing (list → item → sub-item) | `NavStack`; upgrade to `SplitView` once list + detail fit together | `SplitView` two-pane `atLeast('tablet')` landscape / non-`compact`; `NavStack` form on `compact` |
28
+ | Two related panes, selecting on the left updates the right | `SplitView` | two panes when not `compact`; collapses to `NavStack` (list → detail) on `compact` |
29
+ | Complex tool / editor with peripheral panels (navigator, inspector, console) | `Workbench` | full three-panel `atLeast('desktop')`; on smaller classes present the rails via `NavStack` / overlay drawers, not a shrunken shell |
30
+ | Mobile app with 2–5 top-level destinations, each its own drill-down | `TabScaffold`, each tab a `NavStack` | bottom bar on `compact`; promote to a rail / sidebar `atLeast('desktop')` |
31
+
32
+ ### Worked examples
33
+
34
+ - **Settings screen (simple):** one `NavStack` entry per screen; push a subpage
35
+ on tap. No `SplitView`/`Workbench` — it is a single flow.
36
+ - **Mail (drill-down + two-pane):** `SplitView` with `list={<ThreadList/>}` and
37
+ `detail={<Message/>}`, `compact={device.value.compact}`,
38
+ `detailActive={selected != null}`. On desktop both panes show with a resizable
39
+ separator; on a phone it is a `NavStack` (threads → message, back clears the
40
+ selection).
41
+ - **IDE (complex tool):** `Workbench` with a left navigator rail, a right
42
+ inspector rail, and a bottom console drawer, each `collapsed` bound to a
43
+ signal. Only offer this `atLeast('desktop')`.
44
+ - **Social app (tabbed):** `TabScaffold` with Home / Search / Profile tabs, each
45
+ `content` a `NavStack`. On a tablet/desktop, render the same sections as a
46
+ `Workbench` left rail instead of a bottom bar.
47
+
48
+ ## Dialogs
49
+
50
+ Pick the dialog's inner layout by the same complexity axis, then apply the device
51
+ class to how it is presented (compose with [`overlay`](../../docs/19-native-overlay-backing.md)):
52
+
53
+ - **desktop:** an inline dialog — a `SplitView` two-pane body, or a `NavStack`
54
+ for a wizard.
55
+ - **portrait tablet / handset:** present a `SplitView`/complex dialog as a
56
+ full-screen modal (its `compact` `NavStack` form).
57
+ - **landscape tablet:** a large partial-cover modal (does not need to go full
58
+ screen).
59
+
60
+ A `NavStack` works as a dialog body at every size — a wizard pushes and pops its
61
+ steps with cross-faded chrome.
@@ -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,129 @@
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. The app owns the `collapsed` signal; `size`
22
+ overrides the CSS default width/height.
23
+ - **`CollapsiblePanelToggle({ side, collapsed, action, panelId?, label? })`** and
24
+ **`collapsiblePanelToggleIcon(side, collapsed)`** — the standard toggle
25
+ affordance and its icon convention, so every sidebar reads the same: `PanelLeft*`
26
+ for a left rail, `PanelRight*` for a right rail, `PanelBottom*` for a bottom
27
+ drawer — the `Close` glyph while open, the `Open` glyph while collapsed. Placement
28
+ is the app's: put a collapse toggle in the panel's own header and an expand toggle
29
+ somewhere always-visible (a toolbar) so it is reachable while collapsed.
30
+ - **`wireSidebar(root, { panels, deviceClass?, storage? })`** — the interaction
31
+ semantics. Each `panels` entry is `{ id, collapsed, toggleAction, storageKey? }`.
32
+ It:
33
+ - **toggles** the panel's `collapsed` signal when any `[data-action=toggleAction]`
34
+ button is clicked, and remembers the trigger;
35
+ - **manages focus** — moves focus into the panel when it opens, and restores it
36
+ to the trigger when it closes;
37
+ - **presents a compact overlay** when `deviceClass.compact` is true (pass a
38
+ `deviceClass()` signal): the open panel floats over the content with a
39
+ dismissable backdrop, Escape and backdrop-click collapse it, and Tab is trapped
40
+ within the panel (the ARIA dialog pattern);
41
+ - **persists** the collapsed state to `storage` (default `localStorage`) under
42
+ `storageKey`, seeding the signal on wire-up.
43
+
44
+ Returns a disposer. Retain it and call it on teardown.
45
+
46
+ ## Example
47
+
48
+ ```tsx
49
+ import { signal, mount } from "kerfjs";
50
+ import { deviceClass } from "@kerfjs/ui/device-class";
51
+ import {
52
+ CollapsiblePanel,
53
+ CollapsiblePanelToggle,
54
+ } from "@kerfjs/ui/collapsible-panel";
55
+ import { wireSidebar } from "@kerfjs/ui/wire-sidebar";
56
+ import "@kerfjs/ui/collapsible-panel.css";
57
+
58
+ const navCollapsed = signal(false);
59
+ const device = deviceClass();
60
+
61
+ const app = document.querySelector("#app")!;
62
+ mount(app, () => (
63
+ <div class="layout">
64
+ <CollapsiblePanel
65
+ id="nav"
66
+ side="left"
67
+ collapsed={navCollapsed.value}
68
+ label="Navigator"
69
+ >
70
+ <header>
71
+ <CollapsiblePanelToggle
72
+ side="left"
73
+ collapsed={navCollapsed.value}
74
+ action="toggle-nav"
75
+ panelId="nav"
76
+ />
77
+ </header>
78
+ {/* nav items */}
79
+ </CollapsiblePanel>
80
+ <main>
81
+ {navCollapsed.value && (
82
+ <CollapsiblePanelToggle
83
+ side="left"
84
+ collapsed
85
+ action="toggle-nav"
86
+ label="Show navigator"
87
+ />
88
+ )}
89
+ {/* content */}
90
+ </main>
91
+ </div>
92
+ ));
93
+
94
+ const stop = wireSidebar(app, {
95
+ panels: [
96
+ {
97
+ id: "nav",
98
+ collapsed: navCollapsed,
99
+ toggleAction: "toggle-nav",
100
+ storageKey: "app.nav-collapsed",
101
+ },
102
+ ],
103
+ deviceClass: device,
104
+ });
105
+ ```
106
+
107
+ ## When to use which
108
+
109
+ - One or two independent rails / a drawer you place yourself → **`CollapsiblePanel` +
110
+ `wireSidebar`**.
111
+ - A whole Xcode-like workspace (left rail + right rail + bottom drawer + work area
112
+ in one shell) → **[`Workbench`](workbench.md)**, which owns the layout and the same
113
+ collapse animation.
114
+ - Drag-to-resize a panel → compose **[`ResizableRegion`](../src/resizable-region.tsx)**
115
+ / `wireResizableRegions`; the app owns the size signal.
116
+
117
+ The app still owns everything domain-specific — which panels exist, their order,
118
+ sizes, content, and any per-project persistence — exactly as with the other layouts.
119
+
120
+ ## Recipe and coverage
121
+
122
+ The catalog ships a runnable **Collapsible sidebar** recipe — a left rail and a
123
+ bottom drawer with the standard toggles, the compact overlay, and per-panel
124
+ persistence: [open it](../ux-demo/?component=recipe-collapsible-sidebar) or read
125
+ [`recipes.md`](recipes.md#collapsible-sidebar) · [TSX source](../ux-demo/recipes/collapsible-sidebar.tsx).
126
+ It is covered end-to-end across Chromium, Firefox, and WebKit by
127
+ `tests/browser/collapsible-sidebar-recipe.spec.ts` (collapse/expand, focus
128
+ move/restore, the compact overlay + Escape/backdrop dismiss, and the Tab trap),
129
+ alongside the component/wire unit tests.