@kerfjs/ui 5.0.0-beta.49 → 5.0.0-beta.51

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 (319) hide show
  1. package/README.md +69 -15
  2. package/ai/catalog-authoring.json +12 -1
  3. package/ai/catalog-authoring.schema.json +49 -1
  4. package/ai/compile-time-contracts-v1.json +19 -2
  5. package/ai/compile-time-contracts-v1.schema.json +1 -0
  6. package/ai/component-catalog-v2-overrides.json +7 -0
  7. package/ai/component-catalog-v2.d.ts +19 -1
  8. package/ai/component-catalog-v2.json +920 -149
  9. package/ai/component-catalog-v2.schema.json +33 -1
  10. package/ai/component-catalog.json +689 -153
  11. package/ai/component-catalog.schema.json +21 -1
  12. package/ai/public-api-signatures-v1.md +404 -184
  13. package/ai/skill.md +31 -14
  14. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  15. package/analyzer/index.mjs +105 -35
  16. package/dist/app-tab.d.ts +3 -1
  17. package/dist/app-tab.js +1 -1
  18. package/dist/badge.d.ts +24 -0
  19. package/dist/badge.js +3 -0
  20. package/dist/badge.js.map +1 -0
  21. package/dist/browser/badge.js +3 -0
  22. package/dist/browser/list-header.js +1 -0
  23. package/dist/browser/state-banner.js +1 -0
  24. package/dist/catalog-resources.d.ts +2 -2
  25. package/dist/catalog.d.ts +47 -125
  26. package/dist/catalog.js +429 -262
  27. package/dist/catalog.js.map +1 -1
  28. package/dist/{chunk-W2FK7ZNY.js → chunk-4R777DUZ.js} +10 -2
  29. package/dist/chunk-4R777DUZ.js.map +1 -0
  30. package/dist/{chunk-OMSRSL3B.js → chunk-5FZZMIMD.js} +6 -4
  31. package/dist/chunk-5FZZMIMD.js.map +1 -0
  32. package/dist/{chunk-AQFWBG23.js → chunk-5PMGQDG7.js} +5 -3
  33. package/dist/chunk-5PMGQDG7.js.map +1 -0
  34. package/dist/{chunk-SOK5EUF7.js → chunk-6ZXOQEAN.js} +4 -2
  35. package/dist/chunk-6ZXOQEAN.js.map +1 -0
  36. package/dist/{chunk-I4V3UDSA.js → chunk-7GUQYHKA.js} +25 -6
  37. package/dist/chunk-7GUQYHKA.js.map +1 -0
  38. package/dist/{chunk-XKFRMGMW.js → chunk-7IUSV4XG.js} +3 -1
  39. package/dist/chunk-7IUSV4XG.js.map +1 -0
  40. package/dist/{chunk-IU4QUY5I.js → chunk-AEPXGCNF.js} +20 -3
  41. package/dist/chunk-AEPXGCNF.js.map +1 -0
  42. package/dist/{chunk-VDUJZF5G.js → chunk-CMUIXJZM.js} +2 -2
  43. package/dist/{chunk-VDUJZF5G.js.map → chunk-CMUIXJZM.js.map} +1 -1
  44. package/dist/{chunk-DAO2VFTT.js → chunk-D547PH4L.js} +7 -5
  45. package/dist/chunk-D547PH4L.js.map +1 -0
  46. package/dist/{chunk-2TYUFZUC.js → chunk-D7GWXIAS.js} +4 -2
  47. package/dist/chunk-D7GWXIAS.js.map +1 -0
  48. package/dist/{chunk-E34TE2ZI.js → chunk-DVZCMMB3.js} +6 -4
  49. package/dist/chunk-DVZCMMB3.js.map +1 -0
  50. package/dist/{chunk-6NDBE2OK.js → chunk-E36EVCKG.js} +5 -3
  51. package/dist/chunk-E36EVCKG.js.map +1 -0
  52. package/dist/chunk-EKYZCRCX.js +256 -0
  53. package/dist/chunk-EKYZCRCX.js.map +1 -0
  54. package/dist/{chunk-TIGSG6BX.js → chunk-HJKRCKDR.js} +7 -3
  55. package/dist/chunk-HJKRCKDR.js.map +1 -0
  56. package/dist/{chunk-Y6LYKFXY.js → chunk-HMQ2ZDGG.js} +5 -3
  57. package/dist/chunk-HMQ2ZDGG.js.map +1 -0
  58. package/dist/{chunk-UDHHKZWO.js → chunk-JM53SMVF.js} +9 -5
  59. package/dist/chunk-JM53SMVF.js.map +1 -0
  60. package/dist/{chunk-OGTJNHRG.js → chunk-JMGKM7RK.js} +6 -4
  61. package/dist/chunk-JMGKM7RK.js.map +1 -0
  62. package/dist/{chunk-GYGM363P.js → chunk-KX7DP2GO.js} +17 -7
  63. package/dist/chunk-KX7DP2GO.js.map +1 -0
  64. package/dist/{chunk-OL4TC3U2.js → chunk-MO56ZZLG.js} +6 -2
  65. package/dist/chunk-MO56ZZLG.js.map +1 -0
  66. package/dist/{chunk-VXFGMDA7.js → chunk-MTM43SJO.js} +16 -2
  67. package/dist/chunk-MTM43SJO.js.map +1 -0
  68. package/dist/{chunk-ZPWZIVI6.js → chunk-N3GKMPDW.js} +7 -3
  69. package/dist/chunk-N3GKMPDW.js.map +1 -0
  70. package/dist/{chunk-DYBPI5O2.js → chunk-NDUGJ4TU.js} +4 -2
  71. package/dist/chunk-NDUGJ4TU.js.map +1 -0
  72. package/dist/{chunk-I45QO2IZ.js → chunk-NMGTHYW3.js} +7 -5
  73. package/dist/chunk-NMGTHYW3.js.map +1 -0
  74. package/dist/{chunk-WSXUPSID.js → chunk-NN6GYOA2.js} +7 -3
  75. package/dist/chunk-NN6GYOA2.js.map +1 -0
  76. package/dist/{chunk-OGNE3OFT.js → chunk-NZXWU4R5.js} +24 -3
  77. package/dist/chunk-NZXWU4R5.js.map +1 -0
  78. package/dist/{chunk-V6GQKOX2.js → chunk-O3RCCSQJ.js} +6 -4
  79. package/dist/chunk-O3RCCSQJ.js.map +1 -0
  80. package/dist/{chunk-WVLMZ5SG.js → chunk-S4QRSBFI.js} +25 -3
  81. package/dist/chunk-S4QRSBFI.js.map +1 -0
  82. package/dist/{chunk-CK6P5AUK.js → chunk-UBH743HV.js} +10 -7
  83. package/dist/chunk-UBH743HV.js.map +1 -0
  84. package/dist/{chunk-T7DOWS6T.js → chunk-ULCDZTWL.js} +7 -4
  85. package/dist/chunk-ULCDZTWL.js.map +1 -0
  86. package/dist/{chunk-V6HCTCVD.js → chunk-UPZ6EEJN.js} +9 -5
  87. package/dist/chunk-UPZ6EEJN.js.map +1 -0
  88. package/dist/{chunk-CAB6NQ2T.js → chunk-VOCPXGQN.js} +3 -1
  89. package/dist/chunk-VOCPXGQN.js.map +1 -0
  90. package/dist/chunk-XD7DWYXG.js +34 -0
  91. package/dist/chunk-XD7DWYXG.js.map +1 -0
  92. package/dist/{chunk-B6JYRX3P.js → chunk-ZIYOJFZD.js} +43 -4
  93. package/dist/chunk-ZIYOJFZD.js.map +1 -0
  94. package/dist/{chunk-EYALJQQD.js → chunk-ZK5EGVFP.js} +4 -2
  95. package/dist/chunk-ZK5EGVFP.js.map +1 -0
  96. package/dist/{chunk-6WTETUZ6.js → chunk-ZLWQZUGF.js} +8 -2
  97. package/dist/chunk-ZLWQZUGF.js.map +1 -0
  98. package/dist/collapsible-panel.d.ts +4 -2
  99. package/dist/collapsible-panel.js +4 -2
  100. package/dist/collapsible-panel.js.map +1 -1
  101. package/dist/css-values.d.ts +39 -3
  102. package/dist/css-values.js +1 -1
  103. package/dist/disclosure-arrow.d.ts +3 -1
  104. package/dist/disclosure-arrow.js +1 -1
  105. package/dist/empty-state.d.ts +3 -1
  106. package/dist/empty-state.js +1 -1
  107. package/dist/floating-toolbar.d.ts +3 -1
  108. package/dist/floating-toolbar.js +1 -1
  109. package/dist/grid.d.ts +17 -1
  110. package/dist/grid.js +2 -1
  111. package/dist/index.d.ts +4 -3
  112. package/dist/index.js +34 -33
  113. package/dist/list-action-row.d.ts +3 -1
  114. package/dist/list-action-row.js +1 -1
  115. package/dist/list-header.d.ts +6 -1
  116. package/dist/list-header.js +2 -1
  117. package/dist/list-inset-control.d.ts +3 -1
  118. package/dist/list-inset-control.js +1 -1
  119. package/dist/list-inset-text.d.ts +3 -1
  120. package/dist/list-inset-text.js +1 -1
  121. package/dist/list-item.d.ts +3 -1
  122. package/dist/list-item.js +1 -1
  123. package/dist/list.d.ts +23 -2
  124. package/dist/list.js +2 -1
  125. package/dist/loading-spinner.d.ts +3 -1
  126. package/dist/loading-spinner.js +1 -1
  127. package/dist/lucide-icon.d.ts +3 -1
  128. package/dist/lucide-icon.js +1 -1
  129. package/dist/nav-stack.d.ts +3 -1
  130. package/dist/nav-stack.js +1 -1
  131. package/dist/pane.d.ts +19 -2
  132. package/dist/pane.js +1 -1
  133. package/dist/resizable-region.d.ts +5 -2
  134. package/dist/resizable-region.js +1 -1
  135. package/dist/row.d.ts +21 -1
  136. package/dist/row.js +2 -1
  137. package/dist/segmented-control.d.ts +3 -1
  138. package/dist/segmented-control.js +1 -1
  139. package/dist/select.d.ts +16 -4
  140. package/dist/select.js +1 -1
  141. package/dist/skeleton.d.ts +3 -1
  142. package/dist/skeleton.js +1 -1
  143. package/dist/spacer.d.ts +3 -1
  144. package/dist/spacer.js +1 -1
  145. package/dist/split-view.d.ts +3 -1
  146. package/dist/split-view.js +24 -5
  147. package/dist/split-view.js.map +1 -1
  148. package/dist/state-banner.d.ts +3 -1
  149. package/dist/state-banner.js +2 -1
  150. package/dist/styles/badge.css +86 -0
  151. package/dist/styles/catalog/components/catalog-detail.css +35 -0
  152. package/dist/styles/catalog/components/catalog-example-stack.css +9 -0
  153. package/dist/styles/catalog/components/catalog-example.css +180 -0
  154. package/dist/styles/catalog/components/catalog-resource-footer.css +37 -0
  155. package/dist/styles/catalog/components/catalog-secondary-sections.css +38 -0
  156. package/dist/styles/catalog/components/catalog-section-list.css +22 -0
  157. package/dist/styles/catalog/components/catalog-sidebar.css +82 -0
  158. package/dist/styles/catalog/components/catalog-stage.css +60 -0
  159. package/dist/styles/catalog/components/catalog.css +22 -0
  160. package/dist/styles/catalog.css +10 -587
  161. package/dist/styles/collapsible-panel.css +132 -4
  162. package/dist/styles/document.css +23 -0
  163. package/dist/styles/foundation.css +21 -7
  164. package/dist/styles/grid.css +10 -0
  165. package/dist/styles/list-header.css +49 -60
  166. package/dist/styles/list-inset-control.css +8 -1
  167. package/dist/styles/list-item.css +6 -0
  168. package/dist/styles/list.css +11 -0
  169. package/dist/styles/nav-stack.css +89 -2
  170. package/dist/styles/pane.css +86 -1
  171. package/dist/styles/resizable-region.css +117 -5
  172. package/dist/styles/row.css +10 -0
  173. package/dist/styles/select.css +39 -7
  174. package/dist/styles/split-view.css +108 -2
  175. package/dist/styles/state-banner.css +0 -27
  176. package/dist/styles/styles.css +1 -0
  177. package/dist/styles/tab-bar.css +6 -1
  178. package/dist/styles/tab-scaffold.css +64 -1
  179. package/dist/styles/text.css +10 -0
  180. package/dist/styles/token-search-field.css +5 -40
  181. package/dist/styles/toolbar-control-group.css +198 -25
  182. package/dist/styles/toolbar-text.css +8 -1
  183. package/dist/styles/toolbar.css +169 -5
  184. package/dist/styles/webawesome.css +70 -9
  185. package/dist/styles/workbench.css +136 -4
  186. package/dist/sunken-panel.d.ts +3 -1
  187. package/dist/sunken-panel.js +1 -1
  188. package/dist/surface-scaffold.d.ts +6 -2
  189. package/dist/surface-scaffold.js +1 -1
  190. package/dist/tab-bar.d.ts +8 -1
  191. package/dist/tab-bar.js +1 -1
  192. package/dist/tab-scaffold.d.ts +3 -1
  193. package/dist/tab-scaffold.js +3 -1
  194. package/dist/tab-scaffold.js.map +1 -1
  195. package/dist/text.d.ts +9 -5
  196. package/dist/text.js +1 -1
  197. package/dist/token-search-field.d.ts +5 -1
  198. package/dist/token-search-field.js +1 -1
  199. package/dist/toolbar-control-group.d.ts +33 -3
  200. package/dist/toolbar-control-group.js +1 -1
  201. package/dist/toolbar-text.d.ts +3 -1
  202. package/dist/toolbar-text.js +1 -1
  203. package/dist/toolbar.d.ts +30 -1
  204. package/dist/toolbar.js +1 -1
  205. package/dist/types-CEsieBZi.d.ts +69 -0
  206. package/dist/value-table.d.ts +6 -2
  207. package/dist/value-table.js +1 -1
  208. package/dist/wire-resizable-regions.js +1 -1
  209. package/dist/wire-sidebar.d.ts +18 -3
  210. package/dist/wire-sidebar.js +95 -14
  211. package/dist/wire-sidebar.js.map +1 -1
  212. package/dist/wire-token-search-fields.js +1 -1
  213. package/dist/workbench.d.ts +3 -1
  214. package/dist/workbench.js +3 -1
  215. package/dist/workbench.js.map +1 -1
  216. package/docs/accessibility.md +56 -9
  217. package/docs/app-layouts.md +73 -0
  218. package/docs/catalog.md +48 -9
  219. package/docs/collapsible-panel.md +81 -8
  220. package/docs/component-contract.md +96 -28
  221. package/docs/component-integration.md +9 -4
  222. package/docs/component-selection.md +36 -15
  223. package/docs/design/templates/list/stack-dark.svg +1 -1
  224. package/docs/design/templates/list/stack.svg +1 -1
  225. package/docs/design/templates/list-dark.svg +1 -1
  226. package/docs/design/templates/list-header/action-dark.svg +1 -1
  227. package/docs/design/templates/list-header/action.svg +1 -1
  228. package/docs/design/templates/list-header/count-dark.svg +1 -1
  229. package/docs/design/templates/list-header/count.svg +1 -1
  230. package/docs/design/templates/list-header/inline-action-dark.svg +1 -1
  231. package/docs/design/templates/list-header/inline-action.svg +1 -1
  232. package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -1
  233. package/docs/design/templates/list-header/toggle-expanded.svg +1 -1
  234. package/docs/design/templates/list-header-dark.svg +1 -1
  235. package/docs/design/templates/list-header.svg +1 -1
  236. package/docs/design/templates/list.svg +1 -1
  237. package/docs/design/templates/state-banner/info-badge-dark.svg +1 -1
  238. package/docs/design/templates/state-banner/info-badge.svg +1 -1
  239. package/docs/design/templates/state-banner-dark.svg +1 -1
  240. package/docs/design/templates/state-banner.svg +1 -1
  241. package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -1
  242. package/docs/design/templates/toolbar/title-and-actions.svg +1 -1
  243. package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -1
  244. package/docs/design/templates/toolbar-control-group/with-text.svg +1 -1
  245. package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
  246. package/docs/design/templates/toolbar-control-group.svg +1 -1
  247. package/docs/design/templates/toolbar-dark.svg +1 -1
  248. package/docs/design/templates/toolbar-text/default-dark.svg +1 -1
  249. package/docs/design/templates/toolbar-text/default.svg +1 -1
  250. package/docs/design/templates/toolbar-text/large-dark.svg +1 -1
  251. package/docs/design/templates/toolbar-text/large.svg +1 -1
  252. package/docs/design/templates/toolbar-text/small-dark.svg +1 -1
  253. package/docs/design/templates/toolbar-text/small.svg +1 -1
  254. package/docs/design/templates/toolbar-text-dark.svg +1 -1
  255. package/docs/design/templates/toolbar-text.svg +1 -1
  256. package/docs/design/templates/toolbar.svg +1 -1
  257. package/docs/design-philosophy.md +6 -5
  258. package/docs/document-baseline.md +31 -0
  259. package/docs/layout.md +86 -21
  260. package/docs/nav-stack.md +8 -0
  261. package/docs/recipes.md +78 -43
  262. package/docs/split-view.md +8 -0
  263. package/docs/tab-scaffold.md +4 -2
  264. package/docs/type-contracts.md +21 -16
  265. package/docs/ui-analyzer.md +19 -0
  266. package/docs/ui-evaluator.md +17 -0
  267. package/docs/ux-demo.md +8 -8
  268. package/docs/webawesome-theme.md +25 -6
  269. package/docs/workbench.md +20 -1
  270. package/evaluator/index.mjs +73 -8
  271. package/llms.txt +61 -23
  272. package/package.json +12 -3
  273. package/ux-demo/recipes/app-shell.tsx +244 -184
  274. package/ux-demo/recipes/collapsible-sidebar.tsx +175 -116
  275. package/ux-demo/recipes/compact-toolbar.tsx +119 -102
  276. package/ux-demo/recipes/composer-form.tsx +79 -69
  277. package/ux-demo/recipes/list-detail-dialog.tsx +120 -136
  278. package/ux-demo/recipes/list-workspace-states.tsx +85 -79
  279. package/ux-demo/recipes/loaders.ts +5 -2
  280. package/ux-demo/recipes/loading-inspector.tsx +151 -134
  281. package/ux-demo/recipes/navigation-sidebar.tsx +93 -80
  282. package/ux-demo/recipes/navigation-stack.tsx +33 -22
  283. package/ux-demo/recipes/types.ts +14 -0
  284. package/ux-demo/recipes/workspace-header.tsx +73 -57
  285. package/dist/chunk-2TYUFZUC.js.map +0 -1
  286. package/dist/chunk-6NDBE2OK.js.map +0 -1
  287. package/dist/chunk-6WTETUZ6.js.map +0 -1
  288. package/dist/chunk-AQFWBG23.js.map +0 -1
  289. package/dist/chunk-B6JYRX3P.js.map +0 -1
  290. package/dist/chunk-CAB6NQ2T.js.map +0 -1
  291. package/dist/chunk-CK6P5AUK.js.map +0 -1
  292. package/dist/chunk-DAO2VFTT.js.map +0 -1
  293. package/dist/chunk-DYBPI5O2.js.map +0 -1
  294. package/dist/chunk-E34TE2ZI.js.map +0 -1
  295. package/dist/chunk-EYALJQQD.js.map +0 -1
  296. package/dist/chunk-GYGM363P.js.map +0 -1
  297. package/dist/chunk-I45QO2IZ.js.map +0 -1
  298. package/dist/chunk-I4V3UDSA.js.map +0 -1
  299. package/dist/chunk-IU4QUY5I.js.map +0 -1
  300. package/dist/chunk-OGNE3OFT.js.map +0 -1
  301. package/dist/chunk-OGTJNHRG.js.map +0 -1
  302. package/dist/chunk-OL4TC3U2.js.map +0 -1
  303. package/dist/chunk-OMSRSL3B.js.map +0 -1
  304. package/dist/chunk-SOK5EUF7.js.map +0 -1
  305. package/dist/chunk-T7DOWS6T.js.map +0 -1
  306. package/dist/chunk-TIGSG6BX.js.map +0 -1
  307. package/dist/chunk-UDHHKZWO.js.map +0 -1
  308. package/dist/chunk-V6GQKOX2.js.map +0 -1
  309. package/dist/chunk-V6HCTCVD.js.map +0 -1
  310. package/dist/chunk-VXFGMDA7.js.map +0 -1
  311. package/dist/chunk-W2FK7ZNY.js.map +0 -1
  312. package/dist/chunk-WN52USHV.js +0 -115
  313. package/dist/chunk-WN52USHV.js.map +0 -1
  314. package/dist/chunk-WSXUPSID.js.map +0 -1
  315. package/dist/chunk-WVLMZ5SG.js.map +0 -1
  316. package/dist/chunk-XKFRMGMW.js.map +0 -1
  317. package/dist/chunk-Y6LYKFXY.js.map +0 -1
  318. package/dist/chunk-ZPWZIVI6.js.map +0 -1
  319. package/ux-demo/recipes/recipes.css +0 -453
@@ -4,9 +4,10 @@ import '@kerfjs/ui/webawesome.css';
4
4
  import '@awesome.me/webawesome/dist/components/button/button.js';
5
5
  import '@awesome.me/webawesome/dist/components/input/input.js';
6
6
  import '@awesome.me/webawesome/dist/components/textarea/textarea.js';
7
- import './recipes.css';
8
7
 
8
+ import { List } from '@kerfjs/ui/list';
9
9
  import { ListInsetText } from '@kerfjs/ui/list-inset-text';
10
+ import { Row } from '@kerfjs/ui/row';
10
11
  import { Select } from '@kerfjs/ui/select';
11
12
  import { StateBanner } from '@kerfjs/ui/state-banner';
12
13
  import { Text } from '@kerfjs/ui/text';
@@ -14,7 +15,18 @@ import { Toolbar } from '@kerfjs/ui/toolbar';
14
15
  import { ToolbarText } from '@kerfjs/ui/toolbar-text';
15
16
  import { signal } from 'kerfjs';
16
17
 
17
- import type { RecipeFactory } from './types.js';
18
+ import type { RecipeFactory, RecipePresentation } from './types.js';
19
+
20
+ export const presentation: RecipePresentation = {
21
+ viewport: {
22
+ width: 'medium',
23
+ frame: 'solid',
24
+ surface: 'default',
25
+ overflow: 'hidden',
26
+ shadow: true,
27
+ },
28
+ note: 'The recipe owns field, message, and action rhythm. The app owns validation rules, draft persistence, permissions, and transport.',
29
+ };
18
30
 
19
31
  type ValueField = HTMLElement & { value: string };
20
32
 
@@ -41,73 +53,75 @@ export const createRecipe: RecipeFactory = (announce) => {
41
53
  const status = signal<'idle' | 'error' | 'saved'>('idle');
42
54
  const render = () => (
43
55
  <form
44
- class="kui-recipe recipe-form kui-recipe__surface kui-content"
45
56
  data-recipe="recipe-composer-form"
46
57
  aria-labelledby="recipe-composer-title"
47
58
  aria-describedby="recipe-composer-summary"
48
59
  noValidate
49
60
  >
50
- <Toolbar
51
- label="Publish workspace update"
52
- dividerSides=""
53
- leading={
54
- <ToolbarText
55
- text="Publish workspace update"
56
- size="xlarge"
57
- id="recipe-composer-title"
61
+ <List gap="l" controlInsets="tb">
62
+ {/* The title and its supporting line are one heading unit. */}
63
+ <List>
64
+ <Toolbar
65
+ label="Publish workspace update"
66
+ dividerSides=""
67
+ leading={
68
+ <ToolbarText
69
+ text="Publish workspace update"
70
+ size="xlarge"
71
+ id="recipe-composer-title"
72
+ />
73
+ }
74
+ />
75
+ <ListInsetText sides="rl">
76
+ <Text variant="span" tone="quiet" id="recipe-composer-summary">
77
+ Share a concise, actionable update with collaborators.
78
+ </Text>
79
+ </ListInsetText>
80
+ </List>
81
+ {status.value === 'error' && (
82
+ <StateBanner
83
+ title="Add a title before publishing"
84
+ detail="The update body and audience are preserved."
85
+ tone="danger"
86
+ urgency="alert"
87
+ />
88
+ )}
89
+ {status.value === 'saved' && (
90
+ <StateBanner
91
+ title="Update published"
92
+ detail="The team audience can now read it."
93
+ tone="success"
94
+ />
95
+ )}
96
+ <List gap="xs" controlInsets="rl">
97
+ <wa-input
98
+ name="recipe-title"
99
+ label="Update title"
100
+ hint="Summarize the outcome in one line."
101
+ required
102
+ value={title.value}
103
+ ></wa-input>
104
+ <wa-textarea
105
+ name="recipe-body"
106
+ label="Details"
107
+ hint="Include decisions, owners, and the next checkpoint."
108
+ rows="5"
109
+ maxlength="400"
110
+ with-count
111
+ value={body.value}
112
+ ></wa-textarea>
113
+ <Select<string>
114
+ name="recipe-audience"
115
+ value={audience.value}
116
+ label="Audience"
117
+ choices={[
118
+ { value: 'team', label: 'Workspace team' },
119
+ { value: 'reviewers', label: 'Reviewers' },
120
+ { value: 'organization', label: 'Entire organization' },
121
+ ]}
58
122
  />
59
- }
60
- />
61
- <ListInsetText className="kui-recipe__heading-summary">
62
- <span id="recipe-composer-summary">
63
- Share a concise, actionable update with collaborators.
64
- </span>
65
- </ListInsetText>
66
- {status.value === 'error' && (
67
- <StateBanner
68
- title="Add a title before publishing"
69
- detail="The update body and audience are preserved."
70
- tone="danger"
71
- urgency="alert"
72
- />
73
- )}
74
- {status.value === 'saved' && (
75
- <StateBanner
76
- title="Update published"
77
- detail="The team audience can now read it."
78
- tone="success"
79
- />
80
- )}
81
- <div class="recipe-form__section recipe-form__fields">
82
- <wa-input
83
- name="recipe-title"
84
- label="Update title"
85
- hint="Summarize the outcome in one line."
86
- required
87
- value={title.value}
88
- ></wa-input>
89
- <wa-textarea
90
- name="recipe-body"
91
- label="Details"
92
- hint="Include decisions, owners, and the next checkpoint."
93
- rows="5"
94
- maxlength="400"
95
- with-count
96
- value={body.value}
97
- ></wa-textarea>
98
- <Select<string>
99
- name="recipe-audience"
100
- value={audience.value}
101
- label="Audience"
102
- choices={[
103
- { value: 'team', label: 'Workspace team' },
104
- { value: 'reviewers', label: 'Reviewers' },
105
- { value: 'organization', label: 'Entire organization' },
106
- ]}
107
- />
108
- </div>
109
- <footer class="recipe-form__section recipe-form__footer">
110
- <div class="recipe-form__actions kui-control-cluster">
123
+ </List>
124
+ <Row gap="xs" wrap controlInsets="rl">
111
125
  <wa-button
112
126
  appearance="outlined"
113
127
  data-action="recipe-action"
@@ -123,12 +137,8 @@ export const createRecipe: RecipeFactory = (announce) => {
123
137
  >
124
138
  Publish update
125
139
  </wa-button>
126
- </div>
127
- <Text class="kui-recipe__ownership">
128
- The recipe owns field, message, and action rhythm. The app owns
129
- validation rules, draft persistence, permissions, and transport.
130
- </Text>
131
- </footer>
140
+ </Row>
141
+ </List>
132
142
  </form>
133
143
  );
134
144
  return {
@@ -1,21 +1,23 @@
1
1
  import '@kerfjs/ui/layout.css';
2
+ import '@kerfjs/ui/nav-stack.css';
3
+ import '@kerfjs/ui/split-view.css';
2
4
  import '@kerfjs/ui/webawesome.css';
3
5
  import '@awesome.me/webawesome/dist/components/button/button.js';
4
6
  import '@awesome.me/webawesome/dist/components/dialog/dialog.js';
5
- import './recipes.css';
6
7
 
8
+ import { deviceClass } from '@kerfjs/ui/device-class';
9
+ import { List } from '@kerfjs/ui/list';
7
10
  import { ListHeader } from '@kerfjs/ui/list-header';
8
11
  import { ListItem } from '@kerfjs/ui/list-item';
9
12
  import { LucideIcon } from '@kerfjs/ui/lucide-icon';
10
- import { Text } from '@kerfjs/ui/text';
11
- import { Toolbar } from '@kerfjs/ui/toolbar';
12
- import { ToolbarControlGroup } from '@kerfjs/ui/toolbar-control-group';
13
- import { ToolbarText } from '@kerfjs/ui/toolbar-text';
13
+ import { Row } from '@kerfjs/ui/row';
14
+ import { SplitView } from '@kerfjs/ui/split-view';
15
+ import { DialogSurface } from '@kerfjs/ui/surface-scaffold';
14
16
  import { ValueTable, ValueTableRow } from '@kerfjs/ui/value-table';
15
17
  import { signal } from 'kerfjs';
16
- import { FileText } from 'lucide';
18
+ import { ChevronRight, FileText } from 'lucide';
17
19
 
18
- import type { RecipeFactory } from './types.js';
20
+ import type { RecipeFactory, RecipePresentation } from './types.js';
19
21
 
20
22
  const records = {
21
23
  alpha: {
@@ -38,147 +40,132 @@ const records = {
38
40
  },
39
41
  } as const;
40
42
 
43
+ type RecordId = keyof typeof records;
44
+
41
45
  interface RecipeDialogElement extends HTMLElement {
42
46
  open: boolean;
43
47
  }
44
48
 
49
+ export const presentation: RecipePresentation = {
50
+ viewport: { layout: 'grid', width: 'full' },
51
+ note: 'Web Awesome owns modal focus, Escape, and the close control; DialogSurface owns dialog geometry; SplitView owns the list-detail panes and their compact drill-down. The app owns open state, selection, and record actions.',
52
+ };
53
+
45
54
  export const createRecipe: RecipeFactory = (announce) => {
46
- const selected = signal<keyof typeof records>('alpha');
55
+ const selected = signal<RecordId>('alpha');
56
+ // Compact devices drill from the list into the detail; this flag is the
57
+ // pushed detail view. Roomy devices show both panes side by side.
58
+ const detailOpen = signal(false);
47
59
  const open = { value: false };
48
- const render = () => (
49
- <section
50
- class="kui-recipe kui-content"
51
- data-recipe="recipe-list-detail-dialog"
52
- >
53
- <button
54
- class="kui-recipe__button kui-content-item"
55
- data-primary="true"
56
- type="button"
57
- data-action="recipe-action"
58
- data-recipe-command="open"
59
- >
60
- Open project details
61
- </button>
62
- <Text class="kui-recipe__ownership kui-content-item">
63
- Web Awesome owns modal focus and dismissal. The recipe owns
64
- header/body/list-detail anatomy; the app owns open state, selection, and
65
- policy.
66
- </Text>
67
- <wa-dialog
68
- class="recipe-dialog"
69
- label="Project details"
70
- without-header
71
- open={open.value}
60
+ const device = deviceClass();
61
+
62
+ const projectList = (compact: boolean) => (
63
+ <List>
64
+ {compact ? null : <ListHeader label="Recent projects" />}
65
+ <section aria-label="Projects">
66
+ {Object.entries(records).map(([id, record]) => (
67
+ <ListItem
68
+ action="recipe-action"
69
+ itemId={id}
70
+ label={record.name}
71
+ description={`${record.owner} · ${record.state}`}
72
+ icon={<LucideIcon icon={FileText} name="file-text" />}
73
+ trailing={
74
+ compact ? (
75
+ <LucideIcon icon={ChevronRight} name="chevron-right" />
76
+ ) : undefined
77
+ }
78
+ selected={!compact && selected.value === id}
79
+ multiline
80
+ />
81
+ ))}
82
+ </section>
83
+ </List>
84
+ );
85
+
86
+ const projectDetail = (compact: boolean) => {
87
+ const record = records[selected.value];
88
+ return (
89
+ <List>
90
+ {compact ? null : <ListHeader label={record.name} />}
91
+ <ValueTable label="Project details">
92
+ <ValueTableRow label="Owner" value={record.owner} />
93
+ <ValueTableRow label="Status" value={record.state} />
94
+ <ValueTableRow label="Updated" value={record.updated} />
95
+ </ValueTable>
96
+ </List>
97
+ );
98
+ };
99
+
100
+ const render = () => {
101
+ const compact = device.value.compact;
102
+ return (
103
+ <section
104
+ data-recipe="recipe-list-detail-dialog"
105
+ aria-label="Project details dialog"
72
106
  >
73
- <div class="recipe-dialog__pane kui-pane">
74
- <div class="recipe-list-detail kui-pane__content">
75
- <nav
76
- class="recipe-list-detail__list kui-pane"
77
- aria-label="Projects"
107
+ <Row hAlign="center">
108
+ <DialogSurface
109
+ size="large"
110
+ presentation={compact ? 'fullscreen' : 'modal'}
111
+ bodyInset="none"
112
+ footerInset="comfortable"
113
+ >
114
+ <wa-button
115
+ variant="brand"
116
+ appearance="accent"
117
+ data-action="recipe-action"
118
+ data-recipe-command="open"
78
119
  >
79
- <ListHeader label="Recent projects" />
80
- <div class="kui-pane__content kui-content">
81
- <section>
82
- {Object.entries(records).map(([id, record]) => (
83
- <ListItem
84
- action="recipe-action"
85
- itemId={id}
86
- label={record.name}
87
- selected={selected.value === id}
88
- multiline
89
- />
90
- ))}
91
- </section>
92
- </div>
93
- </nav>
94
- <section class="recipe-list-detail__detail kui-pane">
95
- <Toolbar
96
- label="Project details"
97
- dividerSides=""
98
- leading={
99
- <>
100
- <ToolbarControlGroup appearance="borderless" single>
101
- <LucideIcon icon={FileText} name="file-text" />
102
- </ToolbarControlGroup>
103
- <ToolbarText
104
- text="Project details"
105
- size="xlarge"
106
- id="recipe-dialog-title"
107
- />
108
- </>
109
- }
110
- trailing={
111
- <ToolbarControlGroup appearance="borderless" single>
112
- <wa-button
113
- appearance="plain"
114
- data-action="recipe-action"
115
- data-recipe-command="close"
116
- >
117
- Close
118
- </wa-button>
119
- </ToolbarControlGroup>
120
- }
120
+ Open project details
121
+ </wa-button>
122
+ <wa-dialog label="Project details" open={open.value} with-footer>
123
+ <SplitView
124
+ id="recipe-projects"
125
+ label="Projects"
126
+ compact={compact}
127
+ detailActive={compact && detailOpen.value}
128
+ listTitle="Recent projects"
129
+ detailTitle={records[selected.value].name}
130
+ backLabel="Back to projects"
131
+ list={projectList(compact)}
132
+ detail={projectDetail(compact)}
121
133
  />
122
- <Text
123
- class="kui-recipe__heading-summary"
124
- id="recipe-dialog-summary"
134
+ <wa-button
135
+ slot="footer"
136
+ appearance="outlined"
137
+ data-action="recipe-action"
138
+ data-recipe-command="archive"
125
139
  >
126
- Compare delivery state without leaving the workspace.
127
- </Text>
128
- <div
129
- class="recipe-list-detail__body kui-pane__content kui-content"
130
- aria-live="polite"
140
+ Archive
141
+ </wa-button>
142
+ <wa-button
143
+ slot="footer"
144
+ variant="brand"
145
+ appearance="accent"
146
+ data-action="recipe-action"
147
+ data-recipe-command="open-record"
131
148
  >
132
- <Text
133
- variant="h3"
134
- class="recipe-list-detail__title kui-recipe__pane-title kui-content-item"
135
- >
136
- {records[selected.value].name}
137
- </Text>
138
- <ValueTable label="Project details">
139
- <ValueTableRow
140
- label="Owner"
141
- value={records[selected.value].owner}
142
- />
143
- <ValueTableRow
144
- label="Status"
145
- value={records[selected.value].state}
146
- />
147
- <ValueTableRow
148
- label="Updated"
149
- value={records[selected.value].updated}
150
- />
151
- </ValueTable>
152
- <div class="recipe-list-detail__actions kui-control-cluster">
153
- <wa-button
154
- appearance="outlined"
155
- data-action="recipe-action"
156
- data-recipe-command="archive"
157
- >
158
- Archive
159
- </wa-button>
160
- <wa-button
161
- variant="brand"
162
- appearance="accent"
163
- data-action="recipe-action"
164
- data-recipe-command="open-record"
165
- >
166
- Open project
167
- </wa-button>
168
- </div>
169
- </div>
170
- </section>
171
- </div>
172
- </div>
173
- </wa-dialog>
174
- </section>
175
- );
149
+ Open project
150
+ </wa-button>
151
+ </wa-dialog>
152
+ </DialogSurface>
153
+ </Row>
154
+ </section>
155
+ );
156
+ };
176
157
  return {
177
158
  render,
178
159
  action(command, element) {
160
+ if (command === 'nav-back') {
161
+ detailOpen.value = false;
162
+ announce('Back to projects');
163
+ return;
164
+ }
179
165
  const id = element.dataset.itemId;
180
166
  if (id && id in records) {
181
- selected.value = id as keyof typeof records;
167
+ selected.value = id as RecordId;
168
+ detailOpen.value = true;
182
169
  announce(`Selected ${records[selected.value].name}`);
183
170
  return;
184
171
  }
@@ -191,14 +178,11 @@ export const createRecipe: RecipeFactory = (announce) => {
191
178
  dialog.open = true;
192
179
  return;
193
180
  }
194
- if (command === 'close' && dialog) {
195
- dialog.open = false;
196
- return;
197
- }
198
181
  announce(`${command} requested`);
199
182
  },
200
183
  afterHide() {
201
184
  open.value = false;
185
+ detailOpen.value = false;
202
186
  },
203
187
  };
204
188
  };