@kerfjs/ui 4.4.1 → 5.0.0-beta.11

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 (386) hide show
  1. package/README.md +241 -10
  2. package/ai/component-catalog.json +4828 -0
  3. package/ai/component-catalog.schema.json +79 -0
  4. package/ai/public-api-signatures-v1.md +1371 -0
  5. package/ai/skill.md +73 -11
  6. package/ai/webawesome-jsx-signatures-v1.md +106 -0
  7. package/dist/app-tab.d.ts +14 -2
  8. package/dist/app-tab.js +3 -1
  9. package/dist/browser/app-tab.js +3 -2
  10. package/dist/browser/disclosure-arrow.js +4 -0
  11. package/dist/browser/empty-state.js +3 -3
  12. package/dist/browser/floating-toolbar.js +3 -0
  13. package/dist/browser/list-action-row.js +4 -0
  14. package/dist/browser/list-header.js +6 -0
  15. package/dist/browser/list-item.js +4 -0
  16. package/dist/browser/loading-spinner.js +2 -2
  17. package/dist/browser/lucide-icon.js +2 -2
  18. package/dist/browser/panel-header.js +7 -0
  19. package/dist/browser/resizable-region.js +2 -2
  20. package/dist/browser/segmented-control.js +3 -2
  21. package/dist/browser/select.js +4 -3
  22. package/dist/browser/skeleton.js +3 -0
  23. package/dist/browser/state-banner.js +3 -2
  24. package/dist/browser/tab-bar.js +2 -2
  25. package/dist/browser/token-search-field.js +4 -0
  26. package/dist/browser/toolbar-control-group.js +2 -2
  27. package/dist/browser/toolbar-text.js +3 -2
  28. package/dist/browser/toolbar.js +2 -2
  29. package/dist/browser/value-table.js +3 -2
  30. package/dist/catalog.d.ts +119 -0
  31. package/dist/catalog.js +146 -0
  32. package/dist/catalog.js.map +1 -0
  33. package/dist/chunk-2Z5TWHKQ.js +34 -0
  34. package/dist/chunk-2Z5TWHKQ.js.map +1 -0
  35. package/dist/chunk-37T3VNIZ.js +19 -0
  36. package/dist/chunk-37T3VNIZ.js.map +1 -0
  37. package/dist/chunk-56RJXPHG.js +43 -0
  38. package/dist/chunk-56RJXPHG.js.map +1 -0
  39. package/dist/chunk-5ATCDSQS.js +42 -0
  40. package/dist/chunk-5ATCDSQS.js.map +1 -0
  41. package/dist/chunk-5E2HVJ33.js +10 -0
  42. package/dist/chunk-5E2HVJ33.js.map +1 -0
  43. package/dist/chunk-7BR3DO2A.js +31 -0
  44. package/dist/chunk-7BR3DO2A.js.map +1 -0
  45. package/dist/chunk-7JB7VPRI.js +34 -0
  46. package/dist/chunk-7JB7VPRI.js.map +1 -0
  47. package/dist/chunk-7LDCTU7D.js +134 -0
  48. package/dist/chunk-7LDCTU7D.js.map +1 -0
  49. package/dist/{chunk-4HHCYSGT.js → chunk-AQNKJRFH.js} +2 -2
  50. package/dist/{chunk-4HHCYSGT.js.map → chunk-AQNKJRFH.js.map} +1 -1
  51. package/dist/{chunk-MBR3TYNL.js → chunk-AVZHJN6C.js} +3 -3
  52. package/dist/chunk-AVZHJN6C.js.map +1 -0
  53. package/dist/chunk-B24C4A6T.js +22 -0
  54. package/dist/chunk-B24C4A6T.js.map +1 -0
  55. package/dist/chunk-BLK3UEEI.js +23 -0
  56. package/dist/chunk-BLK3UEEI.js.map +1 -0
  57. package/dist/chunk-CH4SQ6H5.js +39 -0
  58. package/dist/chunk-CH4SQ6H5.js.map +1 -0
  59. package/dist/chunk-I7INRJO4.js +17 -0
  60. package/dist/chunk-I7INRJO4.js.map +1 -0
  61. package/dist/chunk-L4OKFDJY.js +24 -0
  62. package/dist/chunk-L4OKFDJY.js.map +1 -0
  63. package/dist/chunk-LCOBDOCC.js +265 -0
  64. package/dist/chunk-LCOBDOCC.js.map +1 -0
  65. package/dist/{chunk-3O7ZEUW2.js → chunk-M5JRI6MN.js} +3 -3
  66. package/dist/chunk-M5JRI6MN.js.map +1 -0
  67. package/dist/{chunk-MFKMHK5W.js → chunk-RNPGFQ33.js} +3 -3
  68. package/dist/chunk-RNPGFQ33.js.map +1 -0
  69. package/dist/chunk-RUNWEPR4.js +10 -0
  70. package/dist/chunk-RUNWEPR4.js.map +1 -0
  71. package/dist/chunk-XE5ZLPCC.js +20 -0
  72. package/dist/chunk-XE5ZLPCC.js.map +1 -0
  73. package/dist/{chunk-MQIZAD4A.js → chunk-XXMOARTH.js} +5 -3
  74. package/dist/chunk-XXMOARTH.js.map +1 -0
  75. package/dist/{chunk-TRNBHPNW.js → chunk-YGN7OMLT.js} +11 -8
  76. package/dist/chunk-YGN7OMLT.js.map +1 -0
  77. package/dist/collapsible-panel.d.ts +66 -0
  78. package/dist/collapsible-panel.js +22 -0
  79. package/dist/collapsible-panel.js.map +1 -0
  80. package/dist/device-class.d.ts +62 -0
  81. package/dist/device-class.js +75 -0
  82. package/dist/device-class.js.map +1 -0
  83. package/dist/disclosure-arrow.d.ts +14 -0
  84. package/dist/disclosure-arrow.js +4 -0
  85. package/dist/disclosure-arrow.js.map +1 -0
  86. package/dist/floating-toolbar.d.ts +28 -0
  87. package/dist/floating-toolbar.js +3 -0
  88. package/dist/floating-toolbar.js.map +1 -0
  89. package/dist/index.d.ts +11 -6
  90. package/dist/index.js +22 -16
  91. package/dist/list-action-row.d.ts +52 -0
  92. package/dist/list-action-row.js +5 -0
  93. package/dist/list-action-row.js.map +1 -0
  94. package/dist/list-header.d.ts +43 -0
  95. package/dist/list-header.js +7 -0
  96. package/dist/list-header.js.map +1 -0
  97. package/dist/list-item.d.ts +33 -0
  98. package/dist/list-item.js +5 -0
  99. package/dist/list-item.js.map +1 -0
  100. package/dist/nav-stack.d.ts +38 -0
  101. package/dist/nav-stack.js +5 -0
  102. package/dist/nav-stack.js.map +1 -0
  103. package/dist/panel-header.d.ts +36 -0
  104. package/dist/panel-header.js +7 -0
  105. package/dist/panel-header.js.map +1 -0
  106. package/dist/resizable-region.d.ts +3 -1
  107. package/dist/resizable-region.js +1 -1
  108. package/dist/segmented-control.d.ts +3 -1
  109. package/dist/segmented-control.js +2 -1
  110. package/dist/select-register.d.ts +5 -1
  111. package/dist/select-register.js +3 -0
  112. package/dist/select-register.js.map +1 -1
  113. package/dist/select.d.ts +5 -2
  114. package/dist/select.js +2 -1
  115. package/dist/skeleton.d.ts +24 -0
  116. package/dist/skeleton.js +3 -0
  117. package/dist/skeleton.js.map +1 -0
  118. package/dist/split-view.d.ts +42 -0
  119. package/dist/split-view.js +21 -0
  120. package/dist/split-view.js.map +1 -0
  121. package/dist/state-banner.d.ts +3 -1
  122. package/dist/state-banner.js +2 -1
  123. package/dist/styles/app-tab.css +152 -0
  124. package/dist/styles/catalog.css +522 -0
  125. package/dist/styles/collapsible-panel.css +146 -0
  126. package/dist/styles/disclosure-arrow.css +23 -0
  127. package/dist/styles/empty-state.css +47 -0
  128. package/dist/styles/floating-toolbar.css +38 -0
  129. package/dist/styles/foundation.css +347 -0
  130. package/dist/styles/layout.css +73 -0
  131. package/dist/styles/list-action-row.css +160 -0
  132. package/dist/styles/list-header.css +160 -0
  133. package/dist/styles/list-item.css +123 -0
  134. package/dist/styles/loading-spinner.css +21 -0
  135. package/dist/styles/lucide-icon.css +6 -0
  136. package/dist/styles/nav-stack.css +111 -0
  137. package/dist/styles/panel-header.css +64 -0
  138. package/dist/styles/resizable-region.css +111 -0
  139. package/{src → dist/styles}/segmented-control.css +62 -31
  140. package/dist/styles/select.css +138 -0
  141. package/dist/styles/skeleton.css +31 -0
  142. package/dist/styles/split-view.css +35 -0
  143. package/dist/styles/state-banner.css +173 -0
  144. package/dist/styles/styles.css +23 -0
  145. package/dist/styles/tab-bar.css +45 -0
  146. package/dist/styles/tab-scaffold.css +84 -0
  147. package/dist/styles/token-search-field.css +289 -0
  148. package/dist/styles/toolbar-control-group.css +225 -0
  149. package/dist/styles/toolbar-text.css +40 -0
  150. package/dist/styles/toolbar.css +61 -0
  151. package/dist/styles/value-table.css +75 -0
  152. package/{src → dist/styles}/webawesome.css +119 -29
  153. package/dist/styles/workbench.css +94 -0
  154. package/dist/tab-bar.d.ts +8 -1
  155. package/dist/tab-bar.js +1 -1
  156. package/dist/tab-scaffold.d.ts +30 -0
  157. package/dist/tab-scaffold.js +16 -0
  158. package/dist/tab-scaffold.js.map +1 -0
  159. package/dist/token-search-field.d.ts +43 -0
  160. package/dist/token-search-field.js +4 -0
  161. package/dist/token-search-field.js.map +1 -0
  162. package/dist/toolbar-control-group.d.ts +3 -1
  163. package/dist/toolbar-control-group.js +1 -1
  164. package/dist/toolbar-text.d.ts +15 -3
  165. package/dist/toolbar-text.js +2 -1
  166. package/dist/value-table.d.ts +12 -2
  167. package/dist/value-table.js +2 -1
  168. package/dist/webawesome.d.ts +100 -0
  169. package/dist/webawesome.js +3 -0
  170. package/dist/webawesome.js.map +1 -0
  171. package/dist/wire-catalog.d.ts +26 -0
  172. package/dist/wire-catalog.js +48 -0
  173. package/dist/wire-catalog.js.map +1 -0
  174. package/dist/wire-nav-stack.d.ts +15 -0
  175. package/dist/wire-nav-stack.js +88 -0
  176. package/dist/wire-nav-stack.js.map +1 -0
  177. package/dist/wire-resizable-regions.js +1 -1
  178. package/dist/wire-sidebar.d.ts +43 -0
  179. package/dist/wire-sidebar.js +117 -0
  180. package/dist/wire-sidebar.js.map +1 -0
  181. package/dist/wire-tab-bars.d.ts +15 -2
  182. package/dist/wire-tab-bars.js +1 -1
  183. package/dist/wire-tab-scaffold.d.ts +11 -0
  184. package/dist/wire-tab-scaffold.js +16 -0
  185. package/dist/wire-tab-scaffold.js.map +1 -0
  186. package/dist/wire-token-search-fields.d.ts +106 -0
  187. package/dist/wire-token-search-fields.js +5 -0
  188. package/dist/wire-token-search-fields.js.map +1 -0
  189. package/dist/workbench.d.ts +33 -0
  190. package/dist/workbench.js +17 -0
  191. package/dist/workbench.js.map +1 -0
  192. package/docs/accessibility.md +112 -3
  193. package/docs/app-layouts.md +61 -0
  194. package/docs/catalog.md +155 -0
  195. package/docs/collapsible-panel.md +102 -0
  196. package/docs/component-contract.md +169 -4
  197. package/docs/component-selection.md +186 -0
  198. package/docs/design/templates/empty-state/filtered-dark.svg +1 -0
  199. package/docs/design/templates/empty-state/filtered.svg +1 -0
  200. package/docs/design/templates/empty-state/with-action-dark.svg +1 -0
  201. package/docs/design/templates/empty-state/with-action.svg +1 -0
  202. package/docs/design/templates/empty-state-dark.svg +7 -0
  203. package/docs/design/templates/empty-state.svg +7 -0
  204. package/docs/design/templates/list-action-row/default-dark.svg +1 -0
  205. package/docs/design/templates/list-action-row/default.svg +1 -0
  206. package/docs/design/templates/list-action-row/selected-dark.svg +1 -0
  207. package/docs/design/templates/list-action-row/selected.svg +1 -0
  208. package/docs/design/templates/list-action-row-dark.svg +7 -0
  209. package/docs/design/templates/list-action-row.svg +7 -0
  210. package/docs/design/templates/list-header/action-dark.svg +1 -0
  211. package/docs/design/templates/list-header/action.svg +1 -0
  212. package/docs/design/templates/list-header/count-dark.svg +1 -0
  213. package/docs/design/templates/list-header/count.svg +1 -0
  214. package/docs/design/templates/list-header/toggle-collapsed-dark.svg +1 -0
  215. package/docs/design/templates/list-header/toggle-collapsed.svg +1 -0
  216. package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -0
  217. package/docs/design/templates/list-header/toggle-expanded.svg +1 -0
  218. package/docs/design/templates/list-header-dark.svg +11 -0
  219. package/docs/design/templates/list-header.svg +11 -0
  220. package/docs/design/templates/list-item/default-dark.svg +1 -0
  221. package/docs/design/templates/list-item/default.svg +1 -0
  222. package/docs/design/templates/list-item/multiline-dark.svg +1 -0
  223. package/docs/design/templates/list-item/multiline.svg +1 -0
  224. package/docs/design/templates/list-item/selected-dark.svg +1 -0
  225. package/docs/design/templates/list-item/selected.svg +1 -0
  226. package/docs/design/templates/list-item/trailing-dark.svg +1 -0
  227. package/docs/design/templates/list-item/trailing.svg +1 -0
  228. package/docs/design/templates/list-item-dark.svg +11 -0
  229. package/docs/design/templates/list-item.svg +11 -0
  230. package/docs/design/templates/panel-header/icon-actions-dark.svg +1 -0
  231. package/docs/design/templates/panel-header/icon-actions.svg +1 -0
  232. package/docs/design/templates/panel-header/icon-summary-actions-dark.svg +1 -0
  233. package/docs/design/templates/panel-header/icon-summary-actions.svg +1 -0
  234. package/docs/design/templates/panel-header/no-icon-dark.svg +1 -0
  235. package/docs/design/templates/panel-header/no-icon.svg +1 -0
  236. package/docs/design/templates/panel-header/page-heading-dark.svg +1 -0
  237. package/docs/design/templates/panel-header/page-heading.svg +1 -0
  238. package/docs/design/templates/panel-header/title-only-dark.svg +1 -0
  239. package/docs/design/templates/panel-header/title-only.svg +1 -0
  240. package/docs/design/templates/panel-header-dark.svg +13 -0
  241. package/docs/design/templates/panel-header.svg +13 -0
  242. package/docs/design/templates/segmented-control/equal-dark.svg +1 -0
  243. package/docs/design/templates/segmented-control/equal.svg +1 -0
  244. package/docs/design/templates/segmented-control/pill-small-dark.svg +1 -0
  245. package/docs/design/templates/segmented-control/pill-small.svg +1 -0
  246. package/docs/design/templates/segmented-control-dark.svg +7 -0
  247. package/docs/design/templates/segmented-control.svg +7 -0
  248. package/docs/design/templates/skeleton/avatar-dark.svg +1 -0
  249. package/docs/design/templates/skeleton/avatar.svg +1 -0
  250. package/docs/design/templates/skeleton/block-dark.svg +1 -0
  251. package/docs/design/templates/skeleton/block.svg +1 -0
  252. package/docs/design/templates/skeleton/lines-dark.svg +1 -0
  253. package/docs/design/templates/skeleton/lines.svg +1 -0
  254. package/docs/design/templates/skeleton-dark.svg +9 -0
  255. package/docs/design/templates/skeleton.svg +9 -0
  256. package/docs/design/templates/state-banner/danger-dark.svg +1 -0
  257. package/docs/design/templates/state-banner/danger.svg +1 -0
  258. package/docs/design/templates/state-banner/info-dark.svg +1 -0
  259. package/docs/design/templates/state-banner/info.svg +1 -0
  260. package/docs/design/templates/state-banner/success-dark.svg +1 -0
  261. package/docs/design/templates/state-banner/success.svg +1 -0
  262. package/docs/design/templates/state-banner/warning-dark.svg +1 -0
  263. package/docs/design/templates/state-banner/warning.svg +1 -0
  264. package/docs/design/templates/state-banner-dark.svg +11 -0
  265. package/docs/design/templates/state-banner.svg +11 -0
  266. package/docs/design/templates/tab-bar/workspace-tabs-dark.svg +1 -0
  267. package/docs/design/templates/tab-bar/workspace-tabs.svg +1 -0
  268. package/docs/design/templates/tab-bar-dark.svg +5 -0
  269. package/docs/design/templates/tab-bar.svg +5 -0
  270. package/docs/design/templates/token-search-field/empty-dark.svg +1 -0
  271. package/docs/design/templates/token-search-field/empty.svg +1 -0
  272. package/docs/design/templates/token-search-field/tokens-dark.svg +1 -0
  273. package/docs/design/templates/token-search-field/tokens.svg +1 -0
  274. package/docs/design/templates/token-search-field-dark.svg +7 -0
  275. package/docs/design/templates/token-search-field.svg +7 -0
  276. package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -0
  277. package/docs/design/templates/toolbar/title-and-actions.svg +1 -0
  278. package/docs/design/templates/toolbar-control-group/borderless-single-dark.svg +1 -0
  279. package/docs/design/templates/toolbar-control-group/borderless-single.svg +1 -0
  280. package/docs/design/templates/toolbar-control-group/icon-buttons-dark.svg +1 -0
  281. package/docs/design/templates/toolbar-control-group/icon-buttons.svg +1 -0
  282. package/docs/design/templates/toolbar-control-group/push-buttons-dark.svg +1 -0
  283. package/docs/design/templates/toolbar-control-group/push-buttons.svg +1 -0
  284. package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -0
  285. package/docs/design/templates/toolbar-control-group/with-text.svg +1 -0
  286. package/docs/design/templates/toolbar-control-group-dark.svg +11 -0
  287. package/docs/design/templates/toolbar-control-group.svg +11 -0
  288. package/docs/design/templates/toolbar-dark.svg +5 -0
  289. package/docs/design/templates/toolbar-text/default-dark.svg +1 -0
  290. package/docs/design/templates/toolbar-text/default.svg +1 -0
  291. package/docs/design/templates/toolbar-text/large-dark.svg +1 -0
  292. package/docs/design/templates/toolbar-text/large.svg +1 -0
  293. package/docs/design/templates/toolbar-text/small-dark.svg +1 -0
  294. package/docs/design/templates/toolbar-text/small.svg +1 -0
  295. package/docs/design/templates/toolbar-text-dark.svg +9 -0
  296. package/docs/design/templates/toolbar-text.svg +9 -0
  297. package/docs/design/templates/toolbar.svg +5 -0
  298. package/docs/design/templates/value-table/details-dark.svg +1 -0
  299. package/docs/design/templates/value-table/details.svg +1 -0
  300. package/docs/design/templates/value-table-dark.svg +5 -0
  301. package/docs/design/templates/value-table.svg +5 -0
  302. package/docs/design/templates.md +114 -0
  303. package/docs/design-philosophy.md +61 -1
  304. package/docs/device-class.md +54 -0
  305. package/docs/examples/command-palette-adapter.tsx +70 -0
  306. package/docs/layout.md +131 -0
  307. package/docs/nav-stack.md +47 -0
  308. package/docs/recipes.md +167 -0
  309. package/docs/split-view.md +49 -0
  310. package/docs/tab-scaffold.md +41 -0
  311. package/docs/ux-demo.md +23 -9
  312. package/docs/webawesome-theme.md +15 -0
  313. package/docs/workbench.md +47 -0
  314. package/llms.txt +123 -14
  315. package/package.json +147 -44
  316. package/ux-demo/recipes/app-shell.tsx +32 -0
  317. package/ux-demo/recipes/collapsible-sidebar.tsx +100 -0
  318. package/ux-demo/recipes/compact-toolbar.tsx +43 -0
  319. package/ux-demo/recipes/composer-form.tsx +63 -0
  320. package/ux-demo/recipes/list-detail-dialog.tsx +45 -0
  321. package/ux-demo/recipes/list-workspace-states.tsx +26 -0
  322. package/ux-demo/recipes/loaders.ts +33 -0
  323. package/ux-demo/recipes/loading-inspector.tsx +81 -0
  324. package/ux-demo/recipes/mount-recipe.ts +43 -0
  325. package/ux-demo/recipes/navigation-sidebar.tsx +26 -0
  326. package/ux-demo/recipes/navigation-stack.tsx +76 -0
  327. package/ux-demo/recipes/recipes.css +448 -0
  328. package/ux-demo/recipes/types.ts +17 -0
  329. package/ux-demo/recipes/workspace-header.tsx +12 -0
  330. package/dist/browser/dialog-header.js +0 -3
  331. package/dist/browser/menu-header.js +0 -3
  332. package/dist/browser/menu-item.js +0 -3
  333. package/dist/browser/page-header.js +0 -3
  334. package/dist/chunk-2PES33HS.js +0 -13
  335. package/dist/chunk-2PES33HS.js.map +0 -1
  336. package/dist/chunk-3O7ZEUW2.js.map +0 -1
  337. package/dist/chunk-723YNJBH.js +0 -17
  338. package/dist/chunk-723YNJBH.js.map +0 -1
  339. package/dist/chunk-GY5WH7TO.js +0 -10
  340. package/dist/chunk-GY5WH7TO.js.map +0 -1
  341. package/dist/chunk-MBR3TYNL.js.map +0 -1
  342. package/dist/chunk-MFKMHK5W.js.map +0 -1
  343. package/dist/chunk-MQIZAD4A.js.map +0 -1
  344. package/dist/chunk-NHXX4JI5.js +0 -28
  345. package/dist/chunk-NHXX4JI5.js.map +0 -1
  346. package/dist/chunk-OPCJRCYO.js +0 -19
  347. package/dist/chunk-OPCJRCYO.js.map +0 -1
  348. package/dist/chunk-T5XNMVOI.js +0 -24
  349. package/dist/chunk-T5XNMVOI.js.map +0 -1
  350. package/dist/chunk-TRNBHPNW.js.map +0 -1
  351. package/dist/chunk-VHW5YNIB.js +0 -17
  352. package/dist/chunk-VHW5YNIB.js.map +0 -1
  353. package/dist/chunk-VKH2JLDZ.js +0 -10
  354. package/dist/chunk-VKH2JLDZ.js.map +0 -1
  355. package/dist/chunk-WY4CAEPG.js +0 -14
  356. package/dist/chunk-WY4CAEPG.js.map +0 -1
  357. package/dist/dialog-header.d.ts +0 -14
  358. package/dist/dialog-header.js +0 -3
  359. package/dist/dialog-header.js.map +0 -1
  360. package/dist/menu-header.d.ts +0 -15
  361. package/dist/menu-header.js +0 -3
  362. package/dist/menu-header.js.map +0 -1
  363. package/dist/menu-item.d.ts +0 -22
  364. package/dist/menu-item.js +0 -3
  365. package/dist/menu-item.js.map +0 -1
  366. package/dist/page-header.d.ts +0 -9
  367. package/dist/page-header.js +0 -3
  368. package/dist/page-header.js.map +0 -1
  369. package/src/app-tab.css +0 -20
  370. package/src/dialog-header.css +0 -9
  371. package/src/empty-state.css +0 -6
  372. package/src/foundation.css +0 -106
  373. package/src/loading-spinner.css +0 -4
  374. package/src/lucide-icon.css +0 -1
  375. package/src/menu-header.css +0 -13
  376. package/src/menu-item.css +0 -12
  377. package/src/page-header.css +0 -3
  378. package/src/resizable-region.css +0 -19
  379. package/src/select.css +0 -18
  380. package/src/state-banner.css +0 -31
  381. package/src/styles.css +0 -18
  382. package/src/tab-bar.css +0 -4
  383. package/src/toolbar-control-group.css +0 -25
  384. package/src/toolbar-text.css +0 -4
  385. package/src/toolbar.css +0 -9
  386. package/src/value-table.css +0 -5
@@ -18,12 +18,65 @@ A reference image defines hierarchy, relationships, and intended feel. It does n
18
18
 
19
19
  ### Continuity is correctness
20
20
 
21
- Unrelated updates must not close controls, move focus, reset a draft, replace a selection, or jump scroll. Give every durable and transient state an explicit owner. Preserve DOM identity when meaning has not changed. Distinguish blocking foreground work from background synchronization.
21
+ Unrelated updates must not close controls, move focus, reset a draft, replace a selection, or jump scroll. Give every durable and transient state an explicit owner. The default owner is the application: domain data and persisted layout preferences — a navigation stack, a selection, a tab order, a pane size, a `collapsed`/`expanded` disclosure flag — are the app's signals, which it reads to render. A `wire…` helper owns only the *ephemeral mechanics* around that state (a push/pop animation, overflow autoscroll, a live resize preview) and reports committed changes through callbacks. A helper takes over a piece of transient state itself only when hand-rolling that behavior is substantial and error-prone enough to cause real, inconsistent variation across apps — `wireTokenSearchFields`'s collapsible reveal/focus/Escape/blur-collapse is the bar; a one-line boolean toggle is not. Preserve DOM identity when meaning has not changed. Distinguish blocking foreground work from background synchronization.
22
22
 
23
23
  ### Hierarchy precedes decoration
24
24
 
25
25
  Establish one reading order and one dominant action per decision point. Group first with alignment, spacing, typography, and shared surfaces. A border, fill, badge, or nested card must communicate a real distinction.
26
26
 
27
+ ### Reach for the primitive, not for CSS
28
+
29
+ The package is designed to look right *unstyled*. A screen built from the
30
+ primitives, their props, and the semantic tokens should already read well, so
31
+ custom CSS is the exception. Before adding any `padding`, `margin`, `width`,
32
+ `height`, `border`, `background`, wrapper card, or decoration, check whether the
33
+ component, the pane, or the content-item already owns it — it almost always does,
34
+ and adding more usually **double-insets** or fights the component (the most common
35
+ mistake). Trust component defaults: render a control at its natural size and color
36
+ and fix the surrounding layout rather than overriding the control. Legitimate
37
+ custom CSS is limited to genuinely new structure, and even then may only join a
38
+ component's documented `publicClasses` or override documented `--kui-*` tokens at a
39
+ real composition boundary — never size, space, or re-skin a primitive by hand, and
40
+ never build a heading, toolbar, card, or pane geometry that a primitive provides.
41
+
42
+ ### Panes share one child-owned geometry
43
+
44
+ Sidebars, main areas, inspectors, and dialogs use the same unpadded pane. Their
45
+ children own margin, border, background, padding, and radius, so a transparent
46
+ surface occupies exactly the same geometry as a visible one. Major content
47
+ groups use 24px vertical separation; the inside of an item and the gap between
48
+ toolbar groups use 8px. Rows and actions keep 44px targets.
49
+
50
+ Use the package composition so the geometry has one owner:
51
+
52
+ ```tsx
53
+ <aside class="kui-pane">
54
+ <nav class="kui-pane__content kui-content">
55
+ <section>
56
+ <ListHeader label="Workspace" />
57
+ <ListItem action="open" label="Inbox" icon={inboxIcon} />
58
+ <ListItem action="open" label="Drafts" />
59
+ </section>
60
+ <div class="kui-content-item">Panel contents</div>
61
+ </nav>
62
+ </aside>
63
+ ```
64
+
65
+ Do not pad the pane and then pad every wrapper. That duplicates the geometry
66
+ and makes transparent borders behave differently from visible ones:
67
+
68
+ ```tsx
69
+ <aside class="pane padded">
70
+ <section class="padded">
71
+ <ListHeader label="Workspace" />
72
+ <div class="panel padded-again">Panel contents</div>
73
+ </section>
74
+ </aside>
75
+ ```
76
+
77
+ Change the shared item tokens at a real composition boundary when a product
78
+ needs different geometry; do not compensate with one-off negative margins.
79
+
27
80
  ### Prefer directness
28
81
 
29
82
  Use the shortest understandable, recoverable interaction. Avoid modes, dialogs, confirmations, and explicit saves when direct manipulation can safely express the same result. Low ceremony still requires a discoverable affordance, visible focus, and honest feedback.
@@ -42,6 +95,13 @@ foregrounds compatible with Hot Sheet 2 and Web Awesome. Consumers may replace
42
95
  the global palette, one semantic role, or one component instance through
43
96
  documented custom properties without rewriting component CSS.
44
97
 
98
+ Spacing is likewise a finite, semantic scale, not a free measurement. Five
99
+ canonical steps — 0, 4, 8, 16, and 24px, each a `--kui-space-*` token — map to a
100
+ single relationship apiece, chosen by how connected two elements are: no
101
+ separation, very minor air, standard within a group, between homogeneous groups,
102
+ and major between heterogeneous groups. Off-scale values (12px, 32px) are
103
+ deliberate exceptions. See [`layout.md`](layout.md) "Spacing scale".
104
+
45
105
  ### Responsive design reprioritizes
46
106
 
47
107
  Protect primary content, readable type, recognizable icons, and usable targets. Relocate secondary information before compressing it below a usable scale. Keep one clear scroll owner per region and test narrow, wide, zoomed, and intermediate layouts.
@@ -0,0 +1,54 @@
1
+ # Device class
2
+
3
+ `@kerfjs/ui/device-class` reports the current viewport as a reactive **device
4
+ class** — a size bucket × orientation × viewport-segment count — so a layout can
5
+ pick its presentation from one signal instead of hand-wiring `matchMedia`. It is
6
+ the foundation the app/dialog layouts build on (see
7
+ [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)).
8
+
9
+ ```ts
10
+ import { deviceClass } from '@kerfjs/ui/device-class';
11
+
12
+ const device = deviceClass(); // ReadonlySignal<DeviceClass>
13
+
14
+ effect(() => {
15
+ if (device.value.compact) showStack();
16
+ else showSplit();
17
+ });
18
+ ```
19
+
20
+ ## `DeviceClass`
21
+
22
+ | Field | Meaning |
23
+ | --- | --- |
24
+ | `size` | `xs-mobile` \| `mobile` \| `tablet` \| `desktop` \| `xl-desktop` |
25
+ | `orientation` | `portrait` \| `landscape` |
26
+ | `segments` / `verticalSegments` | viewport segment counts (foldables / dual-screen); `1` on ordinary devices |
27
+ | `handset` | `xs-mobile` or `mobile` |
28
+ | `compact` | "one pane at a time" — a handset or a portrait tablet |
29
+ | `atLeast(size)` | true when the current size is `size` or larger |
30
+
31
+ Reading `device.value` inside an `effect`/`computed` re-runs when the viewport
32
+ crosses a breakpoint, rotates, or changes its segment count. One shared viewport
33
+ source backs every reader.
34
+
35
+ ## Breakpoints
36
+
37
+ Default minimum widths (px): `mobile` 360, `tablet` 720, `desktop` 1024,
38
+ `xl-desktop` 1440. Override per reader:
39
+
40
+ ```ts
41
+ const device = deviceClass({ breakpoints: { tablet: 900 } });
42
+ ```
43
+
44
+ The same numbers are mirrored as CSS custom properties in the foundation
45
+ (`--kui-bp-mobile`, `--kui-bp-tablet`, `--kui-bp-desktop`, `--kui-bp-xl-desktop`)
46
+ so CSS media queries and the JS signal read one source. `classifyViewport(width,
47
+ orientation, segments?, verticalSegments?, breakpoints?)` is exported as the
48
+ pure, DOM-free bucketing core.
49
+
50
+ ## Server rendering
51
+
52
+ Without a DOM, `deviceClass()` resolves to an SSR default (1024×768, landscape,
53
+ one segment) and hydrates to the real class on the client. Override the assumed
54
+ viewport with `deviceClass({ ssr: { width, height } })`.
@@ -0,0 +1,70 @@
1
+ import '@kerfjs/ui/layout.css';
2
+
3
+ import { delegate, mount, signal } from 'kerfjs';
4
+ import { delegateActions } from 'kerfjs/actions';
5
+
6
+ export interface AppCommand {
7
+ id: string;
8
+ label: string;
9
+ keywords?: string[];
10
+ }
11
+
12
+ export interface CommandPaletteCallbacks {
13
+ onClose(): void;
14
+ onRun(command: AppCommand): void;
15
+ }
16
+
17
+ /**
18
+ * Application-local example, not an @kerfjs/ui export. The application owns
19
+ * ranking, history, shortcuts, focus policy, command availability, and copy.
20
+ */
21
+ export function mountCommandPaletteAdapter(
22
+ root: HTMLElement,
23
+ commands: AppCommand[],
24
+ { onClose, onRun }: CommandPaletteCallbacks,
25
+ ): () => void {
26
+ const query = signal('');
27
+ const render = () => {
28
+ const needle = query.value.trim().toLocaleLowerCase();
29
+ const matches = commands.filter((command) => !needle ||
30
+ [command.label, ...(command.keywords ?? [])].some((value) => value.toLocaleLowerCase().includes(needle)));
31
+ return <section class="app-command-palette kui-content" aria-label="Command palette">
32
+ <div class="kui-content-item"><label for="app-command-query">Search commands</label><input id="app-command-query" type="search" value={query.value} autocomplete="off" data-command-query /></div>
33
+ <p class="kui-content-item" aria-live="polite">{matches.length} matching commands</p>
34
+ <ul class="kui-content-item" aria-label="Matching commands">{matches.map((command) => <li><button type="button" data-action="run-command" data-command-id={command.id}>{command.label}</button></li>)}</ul>
35
+ <footer class="kui-control-cluster kui-content-item" aria-label="Command palette actions">
36
+ <button type="button" data-action="clear-query">Clear search</button>
37
+ <button type="button" data-action="close-palette">Close</button>
38
+ </footer>
39
+ </section>;
40
+ };
41
+
42
+ const stopMount = mount(root, render);
43
+ const stopActions = delegateActions(root, 'click', {
44
+ 'run-command': (_event, element) => {
45
+ const id = (element as HTMLElement).dataset.commandId;
46
+ const command = commands.find((candidate) => candidate.id === id);
47
+ if (command) onRun(command);
48
+ },
49
+ 'clear-query': () => {
50
+ query.value = '';
51
+ root.querySelector<HTMLInputElement>('[data-command-query]')?.focus();
52
+ },
53
+ 'close-palette': onClose,
54
+ });
55
+ const stopInput = delegate<HTMLInputElement>(root, 'input', '[data-command-query]', (_event, input) => {
56
+ query.value = input.value;
57
+ });
58
+ const stopEscape = delegate(root, 'keydown', '[data-command-query]', (event) => {
59
+ if ((event as KeyboardEvent).key === 'Escape') onClose();
60
+ });
61
+ let disposed = false;
62
+ return () => {
63
+ if (disposed) return;
64
+ disposed = true;
65
+ stopEscape();
66
+ stopInput();
67
+ stopActions();
68
+ stopMount();
69
+ };
70
+ }
package/docs/layout.md ADDED
@@ -0,0 +1,131 @@
1
+ # Pane and content layout
2
+
3
+ Import `@kerfjs/ui/layout.css` for the shared geometry used by sidebars, main
4
+ areas, inspectors, and dialogs. The vocabulary deliberately describes structure
5
+ rather than location: the same pane can be placed anywhere.
6
+
7
+ ## Anatomy
8
+
9
+ ```tsx
10
+ <aside class="kui-pane">
11
+ <div class="kui-pane__toolbar"><Toolbar label="Workspace" ... /></div>
12
+ <div class="kui-pane__content kui-content">
13
+ <section>...</section>
14
+ <section>...</section>
15
+ </div>
16
+ <footer class="kui-pane__footer"><Toolbar label="Actions" ... /></footer>
17
+ </aside>
18
+ ```
19
+
20
+ `.kui-pane` has no padding. It reserves rows for an optional toolbar, one
21
+ scrolling content area, and an optional footer. A main area or dialog often
22
+ omits the footer; a navigation pane commonly uses all three. Fixed chrome stays
23
+ outside `.kui-pane__content`, which is the pane's scroll owner.
24
+
25
+ `.kui-content` is a vertical stack with a 24px gap between major children.
26
+ Sections may contain adjacent `ListItem` rows without adding another major gap.
27
+ Ordinary surface-like children use `.kui-content-item` and own their complete
28
+ geometry:
29
+
30
+ - 8px inline margin from the pane edge
31
+ - 1px border, transparent by default
32
+ - 8px internal padding and 8px internal gap
33
+ - `calc(1px + remify(11px))`, or 12px, rounded corners
34
+
35
+ Use `.kui-content-item--pill` for the 22px pill radius expressed as
36
+ `calc(1px + remify(21px))`. Consumers can make a content item visible without
37
+ changing its geometry by setting `--kui-content-item-border` and
38
+ `--kui-content-item-background`.
39
+
40
+ A visible parent surface does not make every child another visible card. The
41
+ composer recipe keeps its form as the single surface, uses `PanelHeader` for
42
+ its task hierarchy, and places field and action control edges on the shared 8px
43
+ inline gutter instead of nesting them inside another padded content item. A
44
+ conditional `StateBanner` remains visibly distinct because it communicates
45
+ semantic status.
46
+
47
+ ## Public roles and tokens
48
+
49
+ | Need | Class | Token / default |
50
+ | --- | --- | --- |
51
+ | Unpadded toolbar/content/footer structure | `.kui-pane` | — |
52
+ | Scrolling pane content | `.kui-pane__content` | — |
53
+ | Major vertical rhythm | `.kui-content` | `--kui-layout-content-gap: 24px` |
54
+ | Self-contained child geometry | `.kui-content-item` | 8px margin + 1px border + 8px padding |
55
+ | Pill child | `.kui-content-item--pill` | `--kui-layout-pill-radius: 22px` |
56
+ | Related controls | `.kui-control-cluster` | `--kui-layout-control-gap: 8px` |
57
+ | Inline metadata | `.kui-inline-metadata` | `--kui-layout-metadata-gap: 4px` |
58
+ | Explicit scroll owner outside a pane | `.kui-scroll-owner` | `overflow: auto` |
59
+
60
+ The component layer applies the same contract to `Toolbar`, `ListHeader`,
61
+ `ListItem`, `PanelHeader`, `StateBanner`, `ValueTable`,
62
+ `ValueTableRow`, tabs, and form controls. A value-table row separator starts at
63
+ the row's 8px content inset, or at 40px when the row contains its 24px leading
64
+ icon and 8px gap, and always ends 8px from the right edge. Each value-table row
65
+ also keeps 8px of root-scaled block padding independently of its semantic inline
66
+ inset. Most interactive rows and toolbar groups are 44px tall.
67
+ Toolbar groups reserve a real 1px outer border around a 42px inner area, even
68
+ when their border and background are transparent.
69
+ `PanelHeader` is a plain top `Toolbar` used as a panel, dialog, or page heading
70
+ and overrides no Toolbar styles. The leading zone holds the optional icon (a
71
+ normal bordered `ToolbarControlGroup` given a brand fill with a matching border,
72
+ a 22px glyph) and the title as extra-large `ToolbarText`; the app's trailing
73
+ controls go straight into the trailing zone, and the icon group is omitted when
74
+ no icon is passed. The optional subtitle is a separate row below the toolbar, its
75
+ text left-aligned with the title text.
76
+
77
+ ## Spacing scale
78
+
79
+ Spacing is not a free choice. The official scale is five canonical steps, each
80
+ expressing exactly one relationship — pick the step by **how connected two
81
+ elements are**, not by eye. Every scalable value is `remify`-authored against the
82
+ fixed 16px baseline, so it delivers as `rem`.
83
+
84
+ | Value | Token | Relationship — when to use |
85
+ | --- | --- | --- |
86
+ | 0px | `--kui-space-none` | No separation. The elements read as a single unit (a control and its own affordance, adjacent `ListItem` rows). |
87
+ | 4px | `--kui-space-2xs` | Very minor. Still one connected cluster, but readability or aesthetics demand a hair of air (inline metadata, an icon beside its label). |
88
+ | 8px | `--kui-space-xs` | Standard. Between elements **within a group** — the content-item padding/gutter, gaps between toolbar controls in a group. |
89
+ | 16px | `--kui-space-m` | Minor. Between **homogeneous groups** — two lists, two sibling sections of the same kind. |
90
+ | 24px | `--kui-space-l` | Major. Between **heterogeneous groups** — the `.kui-content` rhythm between major, differing regions. |
91
+
92
+ The two relationships that get confused most are 8px vs 24px: 8px is *inside* a
93
+ group, 24px is *between* major, differing regions. 16px sits between them for
94
+ same-kind groups.
95
+
96
+ `--kui-space-s` (12px) and `--kui-space-xl` (32px) exist but are **off the
97
+ canonical rhythm** — reach for them only as a deliberate exception, never as a
98
+ default step. Prefer the five canonical tokens so spacing stays legible and
99
+ consistent across every surface.
100
+
101
+ ## Ownership rules
102
+
103
+ 1. Do not pad a sidebar, main area, dialog, or `.kui-pane` shell. Children own
104
+ their own margin, border, background, padding, and radius.
105
+ 2. Use 24px gaps for major vertical separation and 8px gaps inside an item or
106
+ between toolbar groups. Do not confuse the two relationships.
107
+ 3. Wrap toolbar content in `ToolbarControlGroup`, including dormant text. A
108
+ transparent group still reserves the same 44px geometry as a visible group.
109
+ 4. Keep one scrolling content owner per pane. Toolbar and footer siblings stay
110
+ fixed while the content scrolls.
111
+ 5. A split item keeps dormant and interactive regions separate. For example,
112
+ `ListHeader` renders its title/count-or-badge cluster separately from its optional
113
+ logical-end 44px action. The header fills the available inline width and its
114
+ action glyph defaults to 18px; disclosure mode makes the title cluster itself
115
+ the button and supplies the production `DisclosureArrow` unless `actionIcon`
116
+ replaces it.
117
+ `ListActionRow` uses a noninteractive row root around sibling 44px primary
118
+ and trailing buttons. `ListItem.trailing` remains dormant content.
119
+ In multiline `ListItem` and `ListActionRow` rows, the leading icon stays
120
+ centered on the label's first line rather than the full wrapped label.
121
+ 6. Reading width, column placement, and responsive relocation remain application
122
+ decisions. The shared classes define local geometry, not the whole shell.
123
+ 7. A visible collapsible pane owns its collapse control in that pane's toolbar.
124
+ When the pane is hidden, move the restore control into the adjacent main
125
+ toolbar on the same logical edge: an inline-start pane restores from the
126
+ main toolbar's leading group, and an inline-end pane restores from its
127
+ trailing group. Do not leave an otherwise empty icon-only rail behind.
128
+
129
+ At narrow widths or 200% zoom, relocate or stack panes before shrinking targets.
130
+ The 8/1/8 item contract and 44px controls remain stable, so screenshots and
131
+ focus-order tests exercise the same model at every viewport.
@@ -0,0 +1,47 @@
1
+ # Navigation stack
2
+
3
+ `@kerfjs/ui/nav-stack` is an iOS-style push/pop navigation stack: views slide in
4
+ and out over one another while the top chrome settles. A **single-pane layout is
5
+ a `NavStack` with one entry**. It is one of the opt-in app layouts (see
6
+ [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)).
7
+
8
+ Import the component and its companion stylesheet:
9
+
10
+ ```ts
11
+ import { NavStack, type NavStackView } from '@kerfjs/ui/nav-stack';
12
+ import { wireNavStack } from '@kerfjs/ui/wire-nav-stack';
13
+ import '@kerfjs/ui/nav-stack.css';
14
+ ```
15
+
16
+ ## State lives in the app
17
+
18
+ Like every `@kerfjs/ui` component, `NavStack` is declarative: the app owns the
19
+ stack as a `signal<NavStackView[]>`, `NavStack({ views })` renders it, and
20
+ `wireNavStack` animates the transitions.
21
+
22
+ ```tsx
23
+ const views = signal<NavStackView[]>([{ key: 'inbox', title: 'Inbox', content: <InboxView /> }]);
24
+
25
+ // render inside mount():
26
+ <NavStack id="mail" label="Mail" views={views.value} />;
27
+
28
+ // once, after first render:
29
+ const dispose = wireNavStack(root, { onBack: () => { views.value = views.value.slice(0, -1); } });
30
+
31
+ // push / pop by editing the signal:
32
+ views.value = [...views.value, { key: id, title: 'Message', content: <MessageView id={id} /> }];
33
+ ```
34
+
35
+ `NavStack` renders every entry stacked, the last one active and the rest kept
36
+ mounted (so their DOM state and focus survive) but hidden. Each entry carries a
37
+ `key` (stable identity), `content`, an optional `title`, and optional per-view
38
+ `toolbar` actions. The back control appears automatically once the stack has more
39
+ than one entry; `wireNavStack`'s `onBack` is where the app pops its own signal.
40
+
41
+ ## Transitions
42
+
43
+ `wireNavStack(root, { onBack, duration? })` observes the rendered stack and
44
+ animates each change: a pushed view slides in from the trailing edge; a popped
45
+ view slides back off it over the revealed view. It returns a disposer. The
46
+ animation honors `prefers-reduced-motion` (transitions collapse to instant) and
47
+ `duration: 0` disables it. Applicable at every device size and inside dialogs.
@@ -0,0 +1,167 @@
1
+ # Production composition recipes
2
+
3
+ These seven reference compositions sit between individual primitives and product
4
+ code. Open each stable UX-catalog route to run it, then copy the linked TSX and
5
+ CSS. The examples import public package subpaths, use semantic layout owners,
6
+ and keep state in a per-instance application adapter. They are not new
7
+ monolithic components.
8
+
9
+ Copy the recipe source together with the catalog-independent
10
+ [`mount-recipe.ts`](../ux-demo/recipes/mount-recipe.ts) adapter. It mounts the
11
+ controller at one stable application root, uses `delegateActions()` for recipe
12
+ commands, forwards form and dialog lifecycle events, wires resize commits with
13
+ the public `onCommit` callback, retains every disposer, and returns one
14
+ idempotent disposer:
15
+
16
+ ```ts
17
+ import { createRecipe } from './navigation-sidebar.js';
18
+ import { mountRecipe } from './mount-recipe.js';
19
+
20
+ const root = document.querySelector<HTMLElement>('#navigation')!;
21
+ const stopRecipe = mountRecipe(root, createRecipe(announce));
22
+ window.addEventListener('pagehide', stopRecipe, { once: true });
23
+ ```
24
+
25
+ The adapter is delivered as reference source, not a new package runtime export.
26
+ `delegate()` remains a valid alternative when an application needs selector-
27
+ specific dispatch; either way, wire once at a stable root and retain disposal.
28
+
29
+ ## Desktop application shell
30
+
31
+ [Open the recipe](../ux-demo/?component=recipe-app-shell) · [TSX source](../ux-demo/recipes/app-shell.tsx) · [shared CSS](../ux-demo/recipes/recipes.css)
32
+
33
+ Use `Toolbar`, controlled `ResizableRegion` panes, and one
34
+ `.kui-pane__content` scroll owner per pane. The recipe owns the shell topology;
35
+ the app owns routing, responsive pane visibility, sizes, persistence, and data.
36
+ Keep each visible pane's collapse action in its own toolbar. Once hidden, put
37
+ its restore action in the adjacent main toolbar on the same logical edge:
38
+ leading for an inline-start sidebar and trailing for an inline-end inspector.
39
+ Collapse the pane completely rather than preserving an empty icon rail.
40
+ Adapt only public `--kui-layout-*` and component variables.
41
+
42
+ ## Navigation sidebar
43
+
44
+ [Open the recipe](../ux-demo/?component=recipe-navigation-sidebar) · [TSX source](../ux-demo/recipes/navigation-sidebar.tsx)
45
+
46
+ One unpadded `.kui-pane` owns toolbar/content/footer structure. Its
47
+ `.kui-content` uses 24px major gaps; `ListHeader`, `ListItem`, and other
48
+ `.kui-content-item` children own their 8px margin, 1px border, and 8px padding.
49
+ Rows and footer toolbar groups remain 44px tall. The app owns routes,
50
+ permissions, labels, selection, valid section counts and their localized
51
+ `countLabel` phrases, non-count badge content, disclosure state, and revealed
52
+ content. A toggled `ListHeader` supplies the production `DisclosureArrow` when
53
+ no custom `actionIcon` is needed; ordinary navigation rows stay chevron-free.
54
+
55
+ ## Workspace header
56
+
57
+ [Open the recipe](../ux-demo/?component=recipe-workspace-header) · [TSX source](../ux-demo/recipes/workspace-header.tsx)
58
+
59
+ `PanelHeader` owns the page heading while one control cluster holds secondary,
60
+ overflow, and primary actions. The app owns authorization and command policy;
61
+ controls relocate without changing focus order.
62
+
63
+ ## List-detail dialog
64
+
65
+ [Open the recipe](../ux-demo/?component=recipe-list-detail-dialog) · [TSX source](../ux-demo/recipes/list-detail-dialog.tsx)
66
+
67
+ The production Web Awesome dialog owns modal focus and Escape; the thin recipe
68
+ adapter restores the invoking control consistently after the hide event.
69
+ `PanelHeader`, `ListHeader`, `ListItem`, and `ValueTable` own their included
70
+ anatomy. The application owns open state, selection, dismissal policy, and
71
+ record actions. `PanelHeader` places the trailing controls the app passes
72
+ directly into its top toolbar's trailing zone.
73
+ The header sits on the dialog edge while retaining its internal control inset;
74
+ the selected title receives the full content gutter, the metadata table fills
75
+ the available detail width between the usual outer margins, and the action
76
+ cluster uses one outer gutter without a second content-item inset.
77
+ Do not rebuild the dialog or reach into private shadow parts.
78
+
79
+ ## Composer form
80
+
81
+ [Open the recipe](../ux-demo/?component=recipe-composer-form) · [TSX source](../ux-demo/recipes/composer-form.tsx)
82
+
83
+ `PanelHeader` supplies the task title and summary, with their ids referenced
84
+ by the form. Production fields own labels, help, and native focus. The field
85
+ and footer control edges sit directly on the shared 8px inline gutter rather
86
+ than acquiring a second content-item padding inset; major children remain 24px
87
+ apart and related controls use 8px gaps. The app owns
88
+ validation, drafts, permissions, and transport. Persistent error or success
89
+ feedback is the only nested semantic surface and uses `StateBanner`, not a toast.
90
+ Because upgraded Web Awesome fields retain live value properties, controlled
91
+ resets synchronize both those properties and the rendered value attributes;
92
+ the Reset action also announces `Draft reset` through the catalog live region.
93
+
94
+ ## List workspace states
95
+
96
+ [Open the recipe](../ux-demo/?component=recipe-list-workspace-states) · [TSX source](../ux-demo/recipes/list-workspace-states.tsx)
97
+
98
+ The same content region moves deterministically through loading, empty,
99
+ populated, stale/background refresh, and error/retry states. The recipe owns
100
+ feedback placement; the app owns fetching, cache age, retry policy, and domain
101
+ rows.
102
+
103
+ ## Compact toolbar choices and actions
104
+
105
+ [Open the recipe](../ux-demo/?component=recipe-compact-toolbar) · [TSX source](../ux-demo/recipes/compact-toolbar.tsx)
106
+
107
+ Use `ToolbarControlGroup` for related commands, `SegmentedControl` for a few
108
+ visible exclusive choices, `Select` for a longer value list, and an ordinary
109
+ button for an independent command. The app owns values, actions, persistence,
110
+ and responsive priority.
111
+
112
+ ## Navigation stack
113
+
114
+ [Open the recipe](../ux-demo/?component=recipe-navigation-stack) · [TSX source](../ux-demo/recipes/navigation-stack.tsx)
115
+
116
+ Drill from a library list into a detail and back with `NavStack`
117
+ (`@kerfjs/ui/nav-stack`): the app owns the stack as a signal of views and
118
+ pushes/pops it, `NavStack` renders it, and `wireNavStack` slides the content and
119
+ settles the chrome (reduced motion collapses the slide to instant). A live
120
+ `deviceClass()` badge shows the current size/orientation. See the layout guide
121
+ [`app-layouts.md`](app-layouts.md) for choosing among `NavStack`, `SplitView`,
122
+ `Workbench`, and `TabScaffold`.
123
+
124
+ ## Loading inspector
125
+
126
+ [Open the recipe](../ux-demo/?component=recipe-loading-inspector) · [TSX source](../ux-demo/recipes/loading-inspector.tsx)
127
+
128
+ A record inspector whose per-record values load asynchronously. Every
129
+ value-bearing component (`PanelHeader`, `ValueTable`/`ValueTableRow`, `Select`,
130
+ `SegmentedControl`, `ListItem`, `StateBanner`) takes its `placeholder` from one
131
+ loading flag, so the same real chrome renders a faithful loading state and then
132
+ the populated record — no separate skeleton markup. The composition is the point;
133
+ `Skeleton` is the primitive it builds on. The app owns the loading lifecycle and
134
+ which values are still unknown.
135
+
136
+ ## Collapsible sidebar
137
+
138
+ [Open the recipe](../ux-demo/?component=recipe-collapsible-sidebar) · [TSX source](../ux-demo/recipes/collapsible-sidebar.tsx)
139
+
140
+ A mini app frame whose left navigation rail and bottom activity drawer are
141
+ standalone `CollapsiblePanel`s (`@kerfjs/ui/collapsible-panel`) driven by
142
+ `wireSidebar` (`@kerfjs/ui/wire-sidebar`). `CollapsiblePanelToggle` supplies the
143
+ standard per-side glyph: a collapse toggle lives inside each panel and an expand
144
+ toggle lives in the always-visible main header, so a collapsed panel is still
145
+ reachable. `wireSidebar` owns the toggle delegation, moves focus into a panel on
146
+ open and restores it to the trigger on close, and — when a `deviceClass()` reports
147
+ `compact` — switches the open panel to a dismissable **overlay** (backdrop, Escape
148
+ and backdrop-click collapse, and a trapped Tab ring, the ARIA dialog pattern). It
149
+ also persists each panel's collapsed state through a supplied storage hook. The app
150
+ owns each `collapsed` signal, the panel sizes, and the content; the wire owns the
151
+ ephemeral interaction. For a full three-pane shell use `Workbench` instead — see
152
+ [`app-layouts.md`](app-layouts.md). This recipe is covered end-to-end across
153
+ Chromium, Firefox, and WebKit by `tests/browser/collapsible-sidebar-recipe.spec.ts`.
154
+
155
+ ## Rules shared by every recipe
156
+
157
+ - Import `@kerfjs/ui/layout.css`; keep every pane unpadded and use exactly one
158
+ `.kui-pane__content` scroll owner for each real boundary.
159
+ - For direct `wa-*` JSX, import types from `@kerfjs/ui/webawesome`. Import only
160
+ individual Web Awesome registration modules and theme them with
161
+ `@kerfjs/ui/webawesome.css`.
162
+ - Prefer public props and variables at the composition boundary. When a recipe
163
+ needs responsive topology, join only classes listed in the catalog's
164
+ `publicClasses`; do not copy component markup or select descendants by tag,
165
+ id, attribute alone, or an unlisted implementation class.
166
+ - Start from the copyable mount adapter, or reproduce its complete boundary:
167
+ wire stable `data-action` hooks once and retain every disposer.
@@ -0,0 +1,49 @@
1
+ # Split view (list-detail)
2
+
3
+ `@kerfjs/ui/split-view` is a list-detail layout: a list and a
4
+ detail side by side on roomy screens, collapsing to a `NavStack` (list → detail)
5
+ on compact ones. One of the opt-in app layouts (see
6
+ [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)).
7
+
8
+ ```ts
9
+ import { SplitView } from '@kerfjs/ui/split-view';
10
+ import { deviceClass } from '@kerfjs/ui/device-class';
11
+ import '@kerfjs/ui/split-view.css';
12
+ // plus nav-stack.css when the compact path is reachable, and
13
+ // wireResizableRegions / wireNavStack for the interactive behavior.
14
+ ```
15
+
16
+ ## Responsive by device class
17
+
18
+ `SplitView` is declarative; the app derives `compact` from the device class and
19
+ tracks its own selection:
20
+
21
+ ```tsx
22
+ const device = deviceClass();
23
+ const selected = signal<string | null>(null);
24
+
25
+ <SplitView
26
+ id="mail"
27
+ label="Mail"
28
+ compact={device.value.compact}
29
+ detailActive={selected.value !== null}
30
+ list={<ThreadList />}
31
+ detail={<Message id={selected.value} />}
32
+ listTitle="Threads"
33
+ detailTitle="Message"
34
+ resizable={{ size: 320, min: 220, max: 480 }}
35
+ />;
36
+ ```
37
+
38
+ - **Roomy** (`compact: false`): both panes show. With `resizable`, the list sits
39
+ in a `ResizableRegion` (wire it with `wireResizableRegions`); without it the
40
+ list takes a fixed `--kui-split-view-list-width` (default 320px) and the detail
41
+ fills the rest.
42
+ - **Compact** (`compact: true`): the split collapses to a `NavStack`. The list is
43
+ the root; when `detailActive` is true the detail is pushed over it with an
44
+ automatic back control (wire it with `wireNavStack`, whose `onBack` clears the
45
+ app's selection). This is the portrait-tablet / handset presentation; as a
46
+ dialog the compact form is a full-screen or large partial-cover modal.
47
+
48
+ Compose the interactive wiring from the existing helpers — `SplitView` adds no
49
+ wire of its own.
@@ -0,0 +1,41 @@
1
+ # Tab scaffold
2
+
3
+ `@kerfjs/ui/tab-scaffold` is a mobile-first, iOS-like bottom tab bar that switches
4
+ between major app sections, where **each tab keeps its own content mounted** —
5
+ usually a `NavStack`, so each tab's stack and scroll survive a switch. One of the
6
+ opt-in app layouts (see [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)).
7
+ It is distinct from `TabBar` (document-oriented, reorderable strips).
8
+
9
+ ```ts
10
+ import { TabScaffold } from '@kerfjs/ui/tab-scaffold';
11
+ import { wireTabScaffold } from '@kerfjs/ui/wire-tab-scaffold';
12
+ import '@kerfjs/ui/tab-scaffold.css';
13
+ ```
14
+
15
+ ## Controlled selection
16
+
17
+ The app owns the active tab (a signal); `TabScaffold` renders every tab's scene
18
+ (only the active one visible) plus the bottom bar, and `wireTabScaffold` reports
19
+ clicks.
20
+
21
+ ```tsx
22
+ const active = signal('home');
23
+
24
+ <TabScaffold
25
+ id="app"
26
+ label="Sections"
27
+ active={active.value}
28
+ tabs={[
29
+ { id: 'home', label: 'Home', icon: <HomeIcon />, content: <HomeStack /> },
30
+ { id: 'search', label: 'Search', icon: <SearchIcon />, content: <SearchStack /> },
31
+ ]}
32
+ />;
33
+
34
+ // once, after first render:
35
+ const dispose = wireTabScaffold(root, { onSelect: (id) => { active.value = id; } });
36
+ ```
37
+
38
+ Each `TabScaffoldTab` has an `id`, `label`, optional `icon`, and `content`. The
39
+ bottom bar respects the home-indicator safe area (`env(safe-area-inset-bottom)`)
40
+ and keeps 44px targets. On larger device classes, promote the tab set to a
41
+ `Workbench` rail or a persistent sidebar instead of a bottom bar.