@kerfjs/ui 5.0.0-beta.16 → 5.0.0-beta.18

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 (218) hide show
  1. package/README.md +111 -81
  2. package/ai/component-catalog-extension.schema.json +79 -0
  3. package/ai/component-catalog.json +499 -0
  4. package/ai/component-catalog.schema.json +15 -0
  5. package/ai/public-api-signatures-v1.md +86 -32
  6. package/ai/skill.md +60 -51
  7. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  8. package/dist/app-tab.d.ts +1 -1
  9. package/dist/app-tab.js +1 -1
  10. package/dist/catalog.d.ts +12 -3
  11. package/dist/catalog.js +283 -90
  12. package/dist/catalog.js.map +1 -1
  13. package/dist/chunk-43FUQH35.js +83 -0
  14. package/dist/chunk-43FUQH35.js.map +1 -0
  15. package/dist/{chunk-YGN7OMLT.js → chunk-4TRDV6RL.js} +2 -2
  16. package/dist/chunk-4TRDV6RL.js.map +1 -0
  17. package/dist/chunk-6ETZKKAE.js +38 -0
  18. package/dist/chunk-6ETZKKAE.js.map +1 -0
  19. package/dist/chunk-6WTETUZ6.js +21 -0
  20. package/dist/chunk-6WTETUZ6.js.map +1 -0
  21. package/dist/chunk-7L6EHUMV.js +31 -0
  22. package/dist/chunk-7L6EHUMV.js.map +1 -0
  23. package/dist/chunk-BBRXX6PF.js +108 -0
  24. package/dist/chunk-BBRXX6PF.js.map +1 -0
  25. package/dist/chunk-DPDBVKNA.js +113 -0
  26. package/dist/chunk-DPDBVKNA.js.map +1 -0
  27. package/dist/chunk-ELSJX4W7.js +68 -0
  28. package/dist/chunk-ELSJX4W7.js.map +1 -0
  29. package/dist/{chunk-LCOBDOCC.js → chunk-ENVTNLYC.js} +153 -67
  30. package/dist/chunk-ENVTNLYC.js.map +1 -0
  31. package/dist/{chunk-AHQI52QO.js → chunk-FEY65TBV.js} +6 -2
  32. package/dist/chunk-FEY65TBV.js.map +1 -0
  33. package/dist/chunk-JTHUXDN2.js +58 -0
  34. package/dist/chunk-JTHUXDN2.js.map +1 -0
  35. package/dist/chunk-LPOMRC4C.js +25 -0
  36. package/dist/chunk-LPOMRC4C.js.map +1 -0
  37. package/dist/chunk-NENBUYX5.js +79 -0
  38. package/dist/chunk-NENBUYX5.js.map +1 -0
  39. package/dist/{chunk-XXMOARTH.js → chunk-NVWXKU37.js} +57 -16
  40. package/dist/chunk-NVWXKU37.js.map +1 -0
  41. package/dist/chunk-PIDTEQ3U.js +105 -0
  42. package/dist/chunk-PIDTEQ3U.js.map +1 -0
  43. package/dist/chunk-PX3YSS5C.js +32 -0
  44. package/dist/chunk-PX3YSS5C.js.map +1 -0
  45. package/dist/chunk-PZQVSDLH.js +61 -0
  46. package/dist/chunk-PZQVSDLH.js.map +1 -0
  47. package/dist/chunk-QIRXYKCE.js +113 -0
  48. package/dist/chunk-QIRXYKCE.js.map +1 -0
  49. package/dist/chunk-QO55FN2Y.js +20 -0
  50. package/dist/chunk-QO55FN2Y.js.map +1 -0
  51. package/dist/chunk-QQFJMRIB.js +45 -0
  52. package/dist/chunk-QQFJMRIB.js.map +1 -0
  53. package/dist/chunk-S3HKPI7I.js +218 -0
  54. package/dist/chunk-S3HKPI7I.js.map +1 -0
  55. package/dist/chunk-S3PCZ7NV.js +38 -0
  56. package/dist/chunk-S3PCZ7NV.js.map +1 -0
  57. package/dist/{chunk-7BR3DO2A.js → chunk-SRSJO5QE.js} +13 -4
  58. package/dist/chunk-SRSJO5QE.js.map +1 -0
  59. package/dist/chunk-U5IFGQCD.js +117 -0
  60. package/dist/chunk-U5IFGQCD.js.map +1 -0
  61. package/dist/{chunk-56RJXPHG.js → chunk-Y6LYKFXY.js} +1 -1
  62. package/dist/{chunk-56RJXPHG.js.map → chunk-Y6LYKFXY.js.map} +1 -1
  63. package/dist/chunk-ZK2G3VR5.js +35 -0
  64. package/dist/chunk-ZK2G3VR5.js.map +1 -0
  65. package/dist/chunk-ZVI5YST2.js +41 -0
  66. package/dist/chunk-ZVI5YST2.js.map +1 -0
  67. package/dist/collapsible-panel.d.ts +2 -2
  68. package/dist/collapsible-panel.js +47 -6
  69. package/dist/collapsible-panel.js.map +1 -1
  70. package/dist/device-class.js +33 -5
  71. package/dist/device-class.js.map +1 -1
  72. package/dist/disclosure-arrow.js +1 -1
  73. package/dist/empty-state.d.ts +1 -1
  74. package/dist/empty-state.js +1 -1
  75. package/dist/floating-toolbar.d.ts +1 -1
  76. package/dist/floating-toolbar.js +1 -1
  77. package/dist/index.js +25 -25
  78. package/dist/list-action-row.d.ts +1 -1
  79. package/dist/list-action-row.js +1 -1
  80. package/dist/list-header.d.ts +1 -1
  81. package/dist/list-header.js +1 -1
  82. package/dist/list-inset-control.d.ts +1 -1
  83. package/dist/list-inset-control.js +1 -1
  84. package/dist/list-inset-text.d.ts +1 -1
  85. package/dist/list-inset-text.js +1 -1
  86. package/dist/list-item.d.ts +1 -1
  87. package/dist/list-item.js +1 -1
  88. package/dist/loading-spinner.js +1 -1
  89. package/dist/nav-stack.d.ts +1 -1
  90. package/dist/nav-stack.js +1 -1
  91. package/dist/panel-header.d.ts +1 -1
  92. package/dist/panel-header.js +1 -1
  93. package/dist/resizable-region.d.ts +1 -1
  94. package/dist/resizable-region.js +1 -1
  95. package/dist/segmented-control.js +1 -1
  96. package/dist/select.d.ts +1 -1
  97. package/dist/select.js +1 -1
  98. package/dist/skeleton.d.ts +1 -1
  99. package/dist/skeleton.js +1 -1
  100. package/dist/split-view.d.ts +1 -1
  101. package/dist/split-view.js +70 -10
  102. package/dist/split-view.js.map +1 -1
  103. package/dist/state-banner.d.ts +1 -1
  104. package/dist/state-banner.js +1 -1
  105. package/dist/styles/catalog.css +48 -1
  106. package/dist/styles/toolbar-text.css +54 -2
  107. package/dist/styles/webawesome.css +131 -0
  108. package/dist/tab-bar.d.ts +1 -1
  109. package/dist/tab-bar.js +1 -1
  110. package/dist/tab-scaffold.d.ts +1 -1
  111. package/dist/tab-scaffold.js +43 -8
  112. package/dist/tab-scaffold.js.map +1 -1
  113. package/dist/token-search-field.js +1 -1
  114. package/dist/toolbar-control-group.d.ts +1 -1
  115. package/dist/toolbar-control-group.js +1 -1
  116. package/dist/toolbar-text.d.ts +17 -1
  117. package/dist/toolbar-text.js +1 -1
  118. package/dist/toolbar.d.ts +1 -1
  119. package/dist/toolbar.js +1 -1
  120. package/dist/value-table.d.ts +2 -2
  121. package/dist/value-table.js +1 -1
  122. package/dist/wire-catalog.d.ts +31 -2
  123. package/dist/wire-catalog.js +203 -8
  124. package/dist/wire-catalog.js.map +1 -1
  125. package/dist/wire-nav-stack.js +22 -7
  126. package/dist/wire-nav-stack.js.map +1 -1
  127. package/dist/wire-resizable-regions.d.ts +1 -1
  128. package/dist/wire-resizable-regions.js +1 -1
  129. package/dist/wire-sidebar.js +52 -41
  130. package/dist/wire-sidebar.js.map +1 -1
  131. package/dist/wire-tab-bars.js +1 -1
  132. package/dist/wire-tab-scaffold.js +9 -4
  133. package/dist/wire-tab-scaffold.js.map +1 -1
  134. package/dist/wire-token-search-fields.d.ts +1 -1
  135. package/dist/wire-token-search-fields.js +1 -1
  136. package/dist/workbench.d.ts +1 -1
  137. package/dist/workbench.js +56 -9
  138. package/dist/workbench.js.map +1 -1
  139. package/docs/app-layouts.md +7 -7
  140. package/docs/catalog.md +110 -25
  141. package/docs/collapsible-panel.md +38 -11
  142. package/docs/component-contract.md +30 -2
  143. package/docs/component-selection.md +76 -65
  144. package/docs/design/templates.md +1 -1
  145. package/docs/design-philosophy.md +5 -5
  146. package/docs/device-class.md +8 -8
  147. package/docs/examples/command-palette-adapter.tsx +67 -17
  148. package/docs/examples/component-catalog-extension.json +45 -0
  149. package/docs/layout.md +19 -19
  150. package/docs/nav-stack.md +15 -6
  151. package/docs/recipes.md +4 -4
  152. package/docs/split-view.md +3 -3
  153. package/docs/tab-scaffold.md +16 -7
  154. package/docs/ux-demo.md +2 -2
  155. package/docs/webawesome-theme.md +73 -8
  156. package/docs/workbench.md +18 -5
  157. package/llms.txt +36 -15
  158. package/package.json +6 -4
  159. package/ux-demo/recipes/app-shell.tsx +206 -10
  160. package/ux-demo/recipes/collapsible-sidebar.tsx +167 -44
  161. package/ux-demo/recipes/compact-toolbar.tsx +114 -12
  162. package/ux-demo/recipes/composer-form.tsx +106 -18
  163. package/ux-demo/recipes/list-detail-dialog.tsx +144 -8
  164. package/ux-demo/recipes/list-workspace-states.tsx +165 -8
  165. package/ux-demo/recipes/loaders.ts +5 -2
  166. package/ux-demo/recipes/loading-inspector.tsx +124 -41
  167. package/ux-demo/recipes/mount-recipe.ts +27 -6
  168. package/ux-demo/recipes/navigation-sidebar.tsx +120 -9
  169. package/ux-demo/recipes/navigation-stack.tsx +61 -19
  170. package/ux-demo/recipes/types.ts +3 -1
  171. package/ux-demo/recipes/workspace-header.tsx +57 -2
  172. package/dist/chunk-2Z5TWHKQ.js +0 -34
  173. package/dist/chunk-2Z5TWHKQ.js.map +0 -1
  174. package/dist/chunk-37T3VNIZ.js +0 -19
  175. package/dist/chunk-37T3VNIZ.js.map +0 -1
  176. package/dist/chunk-55DATV3A.js +0 -10
  177. package/dist/chunk-55DATV3A.js.map +0 -1
  178. package/dist/chunk-5ATCDSQS.js +0 -42
  179. package/dist/chunk-5ATCDSQS.js.map +0 -1
  180. package/dist/chunk-5E2HVJ33.js +0 -10
  181. package/dist/chunk-5E2HVJ33.js.map +0 -1
  182. package/dist/chunk-7BR3DO2A.js.map +0 -1
  183. package/dist/chunk-7JB7VPRI.js +0 -34
  184. package/dist/chunk-7JB7VPRI.js.map +0 -1
  185. package/dist/chunk-7LDCTU7D.js +0 -134
  186. package/dist/chunk-7LDCTU7D.js.map +0 -1
  187. package/dist/chunk-AHQI52QO.js.map +0 -1
  188. package/dist/chunk-AQNKJRFH.js +0 -79
  189. package/dist/chunk-AQNKJRFH.js.map +0 -1
  190. package/dist/chunk-AVZHJN6C.js +0 -14
  191. package/dist/chunk-AVZHJN6C.js.map +0 -1
  192. package/dist/chunk-B24C4A6T.js +0 -22
  193. package/dist/chunk-B24C4A6T.js.map +0 -1
  194. package/dist/chunk-BLK3UEEI.js +0 -23
  195. package/dist/chunk-BLK3UEEI.js.map +0 -1
  196. package/dist/chunk-CH4SQ6H5.js +0 -39
  197. package/dist/chunk-CH4SQ6H5.js.map +0 -1
  198. package/dist/chunk-DAFFDO33.js +0 -14
  199. package/dist/chunk-DAFFDO33.js.map +0 -1
  200. package/dist/chunk-I7INRJO4.js +0 -17
  201. package/dist/chunk-I7INRJO4.js.map +0 -1
  202. package/dist/chunk-L4OKFDJY.js +0 -24
  203. package/dist/chunk-L4OKFDJY.js.map +0 -1
  204. package/dist/chunk-LCOBDOCC.js.map +0 -1
  205. package/dist/chunk-M5JRI6MN.js +0 -27
  206. package/dist/chunk-M5JRI6MN.js.map +0 -1
  207. package/dist/chunk-RNPGFQ33.js +0 -10
  208. package/dist/chunk-RNPGFQ33.js.map +0 -1
  209. package/dist/chunk-RUNWEPR4.js +0 -10
  210. package/dist/chunk-RUNWEPR4.js.map +0 -1
  211. package/dist/chunk-VPCTCUED.js +0 -10
  212. package/dist/chunk-VPCTCUED.js.map +0 -1
  213. package/dist/chunk-WVPYVDXY.js +0 -15
  214. package/dist/chunk-WVPYVDXY.js.map +0 -1
  215. package/dist/chunk-XE5ZLPCC.js +0 -20
  216. package/dist/chunk-XE5ZLPCC.js.map +0 -1
  217. package/dist/chunk-XXMOARTH.js.map +0 -1
  218. package/dist/chunk-YGN7OMLT.js.map +0 -1
@@ -19,14 +19,210 @@ export const createRecipe: RecipeFactory = (announce) => {
19
19
  const selected = signal('inbox');
20
20
  const navigationSize = signal(224);
21
21
  const inspectorSize = signal(240);
22
- const responsivePane = signal<'content' | 'navigation' | 'inspector'>('content');
23
- const render = () => <section class="kui-recipe recipe-shell kui-recipe__surface kui-layout" data-recipe="recipe-app-shell" data-responsive-pane={responsivePane.value}>
24
- <Toolbar label="Atlas workspace" leading={<ToolbarControlGroup appearance="borderless" single><ToolbarText text="Atlas" /></ToolbarControlGroup>} trailing={<div class="recipe-shell__toolbar-actions"><ToolbarControlGroup className="recipe-shell__responsive-controls" appearance="borderless" label="Workspace panes"><button type="button" aria-label="Show navigation" aria-controls="recipe-shell-navigation" aria-pressed={String(responsivePane.value === 'navigation')} data-action="recipe-action" data-recipe-command="show-navigation"><LucideIcon icon={Folder} name="folder" /></button><button type="button" aria-label="Show content" aria-controls="recipe-shell-content" aria-pressed={String(responsivePane.value === 'content')} data-action="recipe-action" data-recipe-command="show-content"><LucideIcon icon={Inbox} name="inbox" /></button><button type="button" aria-label="Show inspector" aria-controls="recipe-shell-inspector" aria-pressed={String(responsivePane.value === 'inspector')} data-action="recipe-action" data-recipe-command="show-inspector"><LucideIcon icon={Settings} name="settings" /></button></ToolbarControlGroup><ToolbarControlGroup appearance="borderless" label="Workspace controls"><button type="button" aria-label="Notifications" data-action="recipe-action" data-recipe-command="notify"><LucideIcon icon={Bell} name="bell" /></button><button type="button" aria-label="Settings" data-action="recipe-action" data-recipe-command="settings"><LucideIcon icon={Settings} name="settings" /></button></ToolbarControlGroup></div>} />
25
- <div class="recipe-shell__body">
26
- <div id="recipe-shell-navigation" class="recipe-shell__nav-region"><ResizableRegion id="recipe-navigation" label="Navigation" size={navigationSize.value} min={180} max={320}><aside class="recipe-shell__nav kui-pane"><ListHeader label="Workspace" /><nav class="recipe-shell__nav-list kui-pane__content kui-content" aria-label="Workspace"><section><ListItem action="recipe-action" itemId="inbox" label="Inbox" icon={<LucideIcon icon={Inbox} name="inbox" />} selected={selected.value === 'inbox'} /><ListItem action="recipe-action" itemId="projects" label="Projects with a deliberately wrapping title" icon={<LucideIcon icon={Folder} name="folder" />} selected={selected.value === 'projects'} multiline /></section></nav></aside></ResizableRegion></div>
27
- <main id="recipe-shell-content" class="recipe-shell__main kui-pane"><PanelHeader title={selected.value === 'inbox' ? 'Inbox triage' : 'Active projects'} titleId="recipe-shell-main-title" actions={<button class="kui-recipe__button" data-primary="true" type="button" data-action="recipe-action" data-recipe-command="new">New task</button>} /><div class="recipe-shell__main-body kui-pane__content kui-content"><p class="kui-recipe__ownership kui-content-item">Recipe owns pane geometry and one scroll owner per pane. The app owns routing, data, pane visibility, sizes, and persistence.</p><div class="recipe-shell__cards">{['Release accessibility audit', 'Prepare tablet navigation', 'Review stale-data states', 'Confirm package boundaries'].map((title) => <article class="recipe-shell__card kui-content-item"><strong>{title}</strong><p class="kui-recipe__muted">Assigned to the interface systems team · due this week</p></article>)}</div></div></main>
28
- <div id="recipe-shell-inspector" class="recipe-shell__inspector-region"><ResizableRegion id="recipe-inspector" label="Inspector" size={inspectorSize.value} min={200} max={360} edge="start"><aside class="recipe-shell__inspector kui-pane"><PanelHeader title="Inspector" titleId="recipe-shell-inspector-title" /><div class="recipe-shell__inspector-body kui-pane__content kui-content"><ValueTable label="Selected task"><ValueTableRow label="Status" value="In review" /><ValueTableRow label="Owner" value="Mara Chen" /><ValueTableRow label="Priority" value="High" /></ValueTable></div></aside></ResizableRegion></div>
29
- </div>
30
- </section>;
31
- return { render, action(command, element) { const pane = command.match(/^show-(navigation|content|inspector)$/)?.[1] as typeof responsivePane.value | undefined; if (pane) responsivePane.value = pane; const id = element.dataset.itemId; if (id) selected.value = id; announce(id ? `Opened ${id}` : pane ? `Showing ${pane}` : command === 'new' ? 'New task requested' : `${command} requested`); }, resize(id, size) { if (id === 'recipe-navigation') navigationSize.value = size; if (id === 'recipe-inspector') inspectorSize.value = size; announce(`${id} resized to ${size}px`); } };
22
+ const responsivePane = signal<'content' | 'navigation' | 'inspector'>(
23
+ 'content',
24
+ );
25
+ const render = () => (
26
+ <section
27
+ class="kui-recipe recipe-shell kui-recipe__surface kui-layout"
28
+ data-recipe="recipe-app-shell"
29
+ data-responsive-pane={responsivePane.value}
30
+ >
31
+ <Toolbar
32
+ label="Atlas workspace"
33
+ leading={
34
+ <ToolbarControlGroup appearance="borderless" single>
35
+ <ToolbarText text="Atlas" />
36
+ </ToolbarControlGroup>
37
+ }
38
+ trailing={
39
+ <div class="recipe-shell__toolbar-actions">
40
+ <ToolbarControlGroup
41
+ className="recipe-shell__responsive-controls"
42
+ appearance="borderless"
43
+ label="Workspace panes"
44
+ >
45
+ <button
46
+ type="button"
47
+ aria-label="Show navigation"
48
+ aria-controls="recipe-shell-navigation"
49
+ aria-pressed={String(responsivePane.value === 'navigation')}
50
+ data-action="recipe-action"
51
+ data-recipe-command="show-navigation"
52
+ >
53
+ <LucideIcon icon={Folder} name="folder" />
54
+ </button>
55
+ <button
56
+ type="button"
57
+ aria-label="Show content"
58
+ aria-controls="recipe-shell-content"
59
+ aria-pressed={String(responsivePane.value === 'content')}
60
+ data-action="recipe-action"
61
+ data-recipe-command="show-content"
62
+ >
63
+ <LucideIcon icon={Inbox} name="inbox" />
64
+ </button>
65
+ <button
66
+ type="button"
67
+ aria-label="Show inspector"
68
+ aria-controls="recipe-shell-inspector"
69
+ aria-pressed={String(responsivePane.value === 'inspector')}
70
+ data-action="recipe-action"
71
+ data-recipe-command="show-inspector"
72
+ >
73
+ <LucideIcon icon={Settings} name="settings" />
74
+ </button>
75
+ </ToolbarControlGroup>
76
+ <ToolbarControlGroup
77
+ appearance="borderless"
78
+ label="Workspace controls"
79
+ >
80
+ <button
81
+ type="button"
82
+ aria-label="Notifications"
83
+ data-action="recipe-action"
84
+ data-recipe-command="notify"
85
+ >
86
+ <LucideIcon icon={Bell} name="bell" />
87
+ </button>
88
+ <button
89
+ type="button"
90
+ aria-label="Settings"
91
+ data-action="recipe-action"
92
+ data-recipe-command="settings"
93
+ >
94
+ <LucideIcon icon={Settings} name="settings" />
95
+ </button>
96
+ </ToolbarControlGroup>
97
+ </div>
98
+ }
99
+ />
100
+ <div class="recipe-shell__body">
101
+ <div id="recipe-shell-navigation" class="recipe-shell__nav-region">
102
+ <ResizableRegion
103
+ id="recipe-navigation"
104
+ label="Navigation"
105
+ size={navigationSize.value}
106
+ min={180}
107
+ max={320}
108
+ >
109
+ <aside class="recipe-shell__nav kui-pane">
110
+ <ListHeader label="Workspace" />
111
+ <nav
112
+ class="recipe-shell__nav-list kui-pane__content kui-content"
113
+ aria-label="Workspace"
114
+ >
115
+ <section>
116
+ <ListItem
117
+ action="recipe-action"
118
+ itemId="inbox"
119
+ label="Inbox"
120
+ icon={<LucideIcon icon={Inbox} name="inbox" />}
121
+ selected={selected.value === 'inbox'}
122
+ />
123
+ <ListItem
124
+ action="recipe-action"
125
+ itemId="projects"
126
+ label="Projects with a deliberately wrapping title"
127
+ icon={<LucideIcon icon={Folder} name="folder" />}
128
+ selected={selected.value === 'projects'}
129
+ multiline
130
+ />
131
+ </section>
132
+ </nav>
133
+ </aside>
134
+ </ResizableRegion>
135
+ </div>
136
+ <main id="recipe-shell-content" class="recipe-shell__main kui-pane">
137
+ <PanelHeader
138
+ title={
139
+ selected.value === 'inbox' ? 'Inbox triage' : 'Active projects'
140
+ }
141
+ titleId="recipe-shell-main-title"
142
+ actions={
143
+ <button
144
+ class="kui-recipe__button"
145
+ data-primary="true"
146
+ type="button"
147
+ data-action="recipe-action"
148
+ data-recipe-command="new"
149
+ >
150
+ New task
151
+ </button>
152
+ }
153
+ />
154
+ <div class="recipe-shell__main-body kui-pane__content kui-content">
155
+ <p class="kui-recipe__ownership kui-content-item">
156
+ Recipe owns pane geometry and one scroll owner per pane. The app
157
+ owns routing, data, pane visibility, sizes, and persistence.
158
+ </p>
159
+ <div class="recipe-shell__cards">
160
+ {[
161
+ 'Release accessibility audit',
162
+ 'Prepare tablet navigation',
163
+ 'Review stale-data states',
164
+ 'Confirm package boundaries',
165
+ ].map((title) => (
166
+ <article class="recipe-shell__card kui-content-item">
167
+ <strong>{title}</strong>
168
+ <p class="kui-recipe__muted">
169
+ Assigned to the interface systems team · due this week
170
+ </p>
171
+ </article>
172
+ ))}
173
+ </div>
174
+ </div>
175
+ </main>
176
+ <div id="recipe-shell-inspector" class="recipe-shell__inspector-region">
177
+ <ResizableRegion
178
+ id="recipe-inspector"
179
+ label="Inspector"
180
+ size={inspectorSize.value}
181
+ min={200}
182
+ max={360}
183
+ edge="start"
184
+ >
185
+ <aside class="recipe-shell__inspector kui-pane">
186
+ <PanelHeader
187
+ title="Inspector"
188
+ titleId="recipe-shell-inspector-title"
189
+ />
190
+ <div class="recipe-shell__inspector-body kui-pane__content kui-content">
191
+ <ValueTable label="Selected task">
192
+ <ValueTableRow label="Status" value="In review" />
193
+ <ValueTableRow label="Owner" value="Mara Chen" />
194
+ <ValueTableRow label="Priority" value="High" />
195
+ </ValueTable>
196
+ </div>
197
+ </aside>
198
+ </ResizableRegion>
199
+ </div>
200
+ </div>
201
+ </section>
202
+ );
203
+ return {
204
+ render,
205
+ action(command, element) {
206
+ const pane = command.match(
207
+ /^show-(navigation|content|inspector)$/,
208
+ )?.[1] as typeof responsivePane.value | undefined;
209
+ if (pane) responsivePane.value = pane;
210
+ const id = element.dataset.itemId;
211
+ if (id) selected.value = id;
212
+ announce(
213
+ id
214
+ ? `Opened ${id}`
215
+ : pane
216
+ ? `Showing ${pane}`
217
+ : command === 'new'
218
+ ? 'New task requested'
219
+ : `${command} requested`,
220
+ );
221
+ },
222
+ resize(id, size) {
223
+ if (id === 'recipe-navigation') navigationSize.value = size;
224
+ if (id === 'recipe-inspector') inspectorSize.value = size;
225
+ announce(`${id} resized to ${size}px`);
226
+ },
227
+ };
32
228
  };
@@ -2,7 +2,10 @@ import '@kerfjs/ui/layout.css';
2
2
  import '@kerfjs/ui/collapsible-panel.css';
3
3
  import './recipes.css';
4
4
 
5
- import { CollapsiblePanel, CollapsiblePanelToggle } from '@kerfjs/ui/collapsible-panel';
5
+ import {
6
+ CollapsiblePanel,
7
+ CollapsiblePanelToggle,
8
+ } from '@kerfjs/ui/collapsible-panel';
6
9
  import { deviceClass } from '@kerfjs/ui/device-class';
7
10
  import { ListHeader } from '@kerfjs/ui/list-header';
8
11
  import { ListItem } from '@kerfjs/ui/list-item';
@@ -26,7 +29,12 @@ const DRAWER_ACTION = 'recipe-sidebar-drawer';
26
29
  */
27
30
  function memoryStorage(): SidebarStorage {
28
31
  const map = new Map<string, string>();
29
- return { getItem: (key) => map.get(key) ?? null, setItem: (key, value) => { map.set(key, value); } };
32
+ return {
33
+ getItem: (key) => map.get(key) ?? null,
34
+ setItem: (key, value) => {
35
+ map.set(key, value);
36
+ },
37
+ };
30
38
  }
31
39
 
32
40
  export const createRecipe: RecipeFactory = (announce) => {
@@ -38,52 +46,157 @@ export const createRecipe: RecipeFactory = (announce) => {
38
46
  // switches the rail/drawer to a dismissable overlay presentation.
39
47
  const device = deviceClass();
40
48
 
41
- const railToggle = (inPanel: boolean) => <CollapsiblePanelToggle side="left" collapsed={railCollapsed.value} action={RAIL_ACTION} panelId="sidebar-rail" label={railCollapsed.value ? 'Show navigation' : 'Hide navigation'} className={inPanel ? '' : 'recipe-collapsible-sidebar__reveal'} />;
42
- const drawerToggle = () => <CollapsiblePanelToggle side="bottom" collapsed={drawerCollapsed.value} action={DRAWER_ACTION} panelId="sidebar-console" label={drawerCollapsed.value ? 'Show activity' : 'Hide activity'} />;
49
+ const railToggle = (inPanel: boolean) => (
50
+ <CollapsiblePanelToggle
51
+ side="left"
52
+ collapsed={railCollapsed.value}
53
+ action={RAIL_ACTION}
54
+ panelId="sidebar-rail"
55
+ label={railCollapsed.value ? 'Show navigation' : 'Hide navigation'}
56
+ className={inPanel ? '' : 'recipe-collapsible-sidebar__reveal'}
57
+ />
58
+ );
59
+ const drawerToggle = () => (
60
+ <CollapsiblePanelToggle
61
+ side="bottom"
62
+ collapsed={drawerCollapsed.value}
63
+ action={DRAWER_ACTION}
64
+ panelId="sidebar-console"
65
+ label={drawerCollapsed.value ? 'Show activity' : 'Hide activity'}
66
+ />
67
+ );
43
68
 
44
- const render = () => <section class="kui-recipe recipe-collapsible-sidebar kui-recipe__surface" data-recipe="recipe-collapsible-sidebar" data-rail-collapsed={String(railCollapsed.value)} data-drawer-collapsed={String(drawerCollapsed.value)}>
45
- <CollapsiblePanel id="sidebar-rail" side="left" size={232} collapsed={railCollapsed.value} label="Workspace navigation" className="recipe-collapsible-sidebar__rail kui-pane">
46
- <div class="recipe-collapsible-sidebar__rail-head">
47
- <ToolbarText text="Atlas" />
48
- <ToolbarControlGroup appearance="borderless" single>{railToggle(true)}</ToolbarControlGroup>
49
- </div>
50
- <nav class="recipe-collapsible-sidebar__nav kui-pane__content kui-content" aria-label="Workspace">
51
- <section>
52
- <ListHeader label="Workspace" />
53
- <ListItem action="recipe-action" itemId="inbox" label="Inbox" icon={<LucideIcon icon={Inbox} name="inbox" />} trailing={<span>12</span>} selected={selected.value === 'inbox'} />
54
- <ListItem action="recipe-action" itemId="projects" label="Projects" icon={<LucideIcon icon={Folder} name="folder" />} selected={selected.value === 'projects'} />
55
- <ListItem action="recipe-action" itemId="shared" label="Shared with me" icon={<LucideIcon icon={Users} name="users" />} selected={selected.value === 'shared'} />
56
- </section>
57
- </nav>
58
- </CollapsiblePanel>
59
- <main class="recipe-collapsible-sidebar__main kui-pane">
60
- <PanelHeader
61
- title={selected.value === 'projects' ? 'Projects' : selected.value === 'shared' ? 'Shared with me' : 'Inbox'}
62
- titleId="recipe-collapsible-main-title"
63
- icon={railToggle(false)}
64
- actions={<ToolbarControlGroup appearance="borderless" single>{drawerToggle()}</ToolbarControlGroup>}
65
- />
66
- <div class="recipe-collapsible-sidebar__body kui-pane__content kui-content">
67
- <p class="kui-recipe__ownership kui-content-item">The app owns each panel's <code>collapsed</code> signal, sizes, and content; <code>wireSidebar</code> owns the toggle, focus move/restore, the compact overlay, and persistence.</p>
68
- <article class="recipe-collapsible-sidebar__card kui-content-item"><strong>Narrow the window to a compact width</strong><p class="kui-recipe__muted">The rail and drawer become a dismissable overlay: a backdrop, Escape, and a trapped Tab ring, all managed by the wire.</p></article>
69
- </div>
70
- </main>
71
- <CollapsiblePanel id="sidebar-console" side="bottom" size={168} collapsed={drawerCollapsed.value} label="Activity" className="recipe-collapsible-sidebar__drawer kui-pane">
72
- <Toolbar label="Activity" divider leading={<ToolbarControlGroup appearance="borderless" single><ToolbarText text="Activity" size="small" /></ToolbarControlGroup>} trailing={<ToolbarControlGroup appearance="borderless" single>{drawerToggle()}</ToolbarControlGroup>} />
73
- <div class="recipe-collapsible-sidebar__drawer-body kui-pane__content kui-content">
74
- <ul class="recipe-collapsible-sidebar__log">
75
- <li><LucideIcon icon={Bell} name="bell" />Deploy finished · 2m ago</li>
76
- <li><LucideIcon icon={Bell} name="bell" />Review requested · 9m ago</li>
77
- </ul>
78
- </div>
79
- </CollapsiblePanel>
80
- </section>;
69
+ const render = () => (
70
+ <section
71
+ class="kui-recipe recipe-collapsible-sidebar kui-recipe__surface"
72
+ data-recipe="recipe-collapsible-sidebar"
73
+ data-rail-collapsed={String(railCollapsed.value)}
74
+ data-drawer-collapsed={String(drawerCollapsed.value)}
75
+ >
76
+ <CollapsiblePanel
77
+ id="sidebar-rail"
78
+ side="left"
79
+ size={232}
80
+ collapsed={railCollapsed.value}
81
+ label="Workspace navigation"
82
+ className="recipe-collapsible-sidebar__rail kui-pane"
83
+ >
84
+ <div class="recipe-collapsible-sidebar__rail-head">
85
+ <ToolbarText text="Atlas" />
86
+ <ToolbarControlGroup appearance="borderless" single>
87
+ {railToggle(true)}
88
+ </ToolbarControlGroup>
89
+ </div>
90
+ <nav
91
+ class="recipe-collapsible-sidebar__nav kui-pane__content kui-content"
92
+ aria-label="Workspace"
93
+ >
94
+ <section>
95
+ <ListHeader label="Workspace" />
96
+ <ListItem
97
+ action="recipe-action"
98
+ itemId="inbox"
99
+ label="Inbox"
100
+ icon={<LucideIcon icon={Inbox} name="inbox" />}
101
+ trailing={<span>12</span>}
102
+ selected={selected.value === 'inbox'}
103
+ />
104
+ <ListItem
105
+ action="recipe-action"
106
+ itemId="projects"
107
+ label="Projects"
108
+ icon={<LucideIcon icon={Folder} name="folder" />}
109
+ selected={selected.value === 'projects'}
110
+ />
111
+ <ListItem
112
+ action="recipe-action"
113
+ itemId="shared"
114
+ label="Shared with me"
115
+ icon={<LucideIcon icon={Users} name="users" />}
116
+ selected={selected.value === 'shared'}
117
+ />
118
+ </section>
119
+ </nav>
120
+ </CollapsiblePanel>
121
+ <main class="recipe-collapsible-sidebar__main kui-pane">
122
+ <PanelHeader
123
+ title={
124
+ selected.value === 'projects'
125
+ ? 'Projects'
126
+ : selected.value === 'shared'
127
+ ? 'Shared with me'
128
+ : 'Inbox'
129
+ }
130
+ titleId="recipe-collapsible-main-title"
131
+ icon={railToggle(false)}
132
+ actions={
133
+ <ToolbarControlGroup appearance="borderless" single>
134
+ {drawerToggle()}
135
+ </ToolbarControlGroup>
136
+ }
137
+ />
138
+ <div class="recipe-collapsible-sidebar__body kui-pane__content kui-content">
139
+ <p class="kui-recipe__ownership kui-content-item">
140
+ The app owns each panel's <code>collapsed</code> signal, sizes, and
141
+ content; <code>wireSidebar</code> owns the toggle, focus
142
+ move/restore, the compact overlay, and persistence.
143
+ </p>
144
+ <article class="recipe-collapsible-sidebar__card kui-content-item">
145
+ <strong>Narrow the window to a compact width</strong>
146
+ <p class="kui-recipe__muted">
147
+ The rail and drawer become a dismissable overlay: a backdrop,
148
+ Escape, and a trapped Tab ring, all managed by the wire.
149
+ </p>
150
+ </article>
151
+ </div>
152
+ </main>
153
+ <CollapsiblePanel
154
+ id="sidebar-console"
155
+ side="bottom"
156
+ size={168}
157
+ collapsed={drawerCollapsed.value}
158
+ label="Activity"
159
+ className="recipe-collapsible-sidebar__drawer kui-pane"
160
+ >
161
+ <Toolbar
162
+ label="Activity"
163
+ divider
164
+ leading={
165
+ <ToolbarControlGroup appearance="borderless" single>
166
+ <ToolbarText text="Activity" size="small" />
167
+ </ToolbarControlGroup>
168
+ }
169
+ trailing={
170
+ <ToolbarControlGroup appearance="borderless" single>
171
+ {drawerToggle()}
172
+ </ToolbarControlGroup>
173
+ }
174
+ />
175
+ <div class="recipe-collapsible-sidebar__drawer-body kui-pane__content kui-content">
176
+ <ul class="recipe-collapsible-sidebar__log">
177
+ <li>
178
+ <LucideIcon icon={Bell} name="bell" />
179
+ Deploy finished · 2m ago
180
+ </li>
181
+ <li>
182
+ <LucideIcon icon={Bell} name="bell" />
183
+ Review requested · 9m ago
184
+ </li>
185
+ </ul>
186
+ </div>
187
+ </CollapsiblePanel>
188
+ </section>
189
+ );
81
190
 
82
191
  return {
83
192
  render,
84
193
  action(command, element) {
85
194
  const id = element.dataset.itemId;
86
- if (id) { selected.value = id; announce(`Opened ${id}`); return; }
195
+ if (id) {
196
+ selected.value = id;
197
+ announce(`Opened ${id}`);
198
+ return;
199
+ }
87
200
  announce(`${command} requested`);
88
201
  },
89
202
  wire(root) {
@@ -91,8 +204,18 @@ export const createRecipe: RecipeFactory = (announce) => {
91
204
  deviceClass: device,
92
205
  storage,
93
206
  panels: [
94
- { id: 'sidebar-rail', collapsed: railCollapsed, toggleAction: RAIL_ACTION, storageKey: 'recipe-sidebar-rail' },
95
- { id: 'sidebar-console', collapsed: drawerCollapsed, toggleAction: DRAWER_ACTION, storageKey: 'recipe-sidebar-console' },
207
+ {
208
+ id: 'sidebar-rail',
209
+ collapsed: railCollapsed,
210
+ toggleAction: RAIL_ACTION,
211
+ storageKey: 'recipe-sidebar-rail',
212
+ },
213
+ {
214
+ id: 'sidebar-console',
215
+ collapsed: drawerCollapsed,
216
+ toggleAction: DRAWER_ACTION,
217
+ storageKey: 'recipe-sidebar-console',
218
+ },
96
219
  ],
97
220
  });
98
221
  },
@@ -17,27 +17,129 @@ export const createRecipe: RecipeFactory = (announce) => {
17
17
  const view = signal('list');
18
18
  const sort = signal('updated');
19
19
  const filtered = signal(false);
20
- const render = () => <section class="kui-recipe recipe-compact-toolbar kui-recipe__surface kui-layout" data-recipe="recipe-compact-toolbar">
21
- <Toolbar
22
- label="Task workspace controls"
23
- leading={<ToolbarControlGroup appearance="borderless" single><ToolbarText text="Tasks" size="small" /></ToolbarControlGroup>}
24
- center={<ToolbarControlGroup><SegmentedControl id="recipe-view" label="View" value={view.value} action="recipe-action" appearance="toolbar" shape="pill" size="small" choices={[{ value: 'list', label: 'List' }, { value: 'board', label: 'Board' }, { value: 'timeline', label: 'Timeline' }]} /></ToolbarControlGroup>}
25
- trailing={<div class="kui-control-cluster"><Select name="recipe-sort" value={sort.value} ariaLabel="Sort tasks" fitMenu choices={[{ value: 'updated', label: 'Recently updated' }, { value: 'priority', label: 'Priority' }, { value: 'owner', label: 'Owner' }, { value: 'created', label: 'Created date' }]} /><ToolbarControlGroup label="Task actions" buttonAppearance="push"><button type="button" aria-label="Toggle filters" aria-pressed={String(filtered.value)} data-action="recipe-action" data-recipe-command="filter"><LucideIcon icon={Filter} name="filter" /></button><button type="button" aria-label="Refresh tasks" data-action="recipe-action" data-recipe-command="refresh"><LucideIcon icon={RefreshCw} name="refresh-cw" /></button></ToolbarControlGroup><button class="kui-recipe__button" type="button" aria-label="More task actions" data-action="recipe-action" data-recipe-command="more"><LucideIcon icon={MoreHorizontal} name="more-horizontal" /></button></div>}
26
- />
27
- <div class="recipe-compact-toolbar__guide kui-content-item"><div><strong>ToolbarControlGroup</strong><span class="kui-recipe__muted">Related commands or pressed tools.</span></div><div><strong>SegmentedControl</strong><span class="kui-recipe__muted">A few visible exclusive choices.</span></div><div><strong>Select</strong><span class="kui-recipe__muted">A longer controlled value list.</span></div><div><strong>Ordinary button</strong><span class="kui-recipe__muted">One independent command.</span></div></div>
28
- <p class="kui-recipe__ownership kui-content-item">The recipe owns control semantics and wrapping. The app owns values, actions, persistence, and responsive priority.</p>
29
- </section>;
20
+ const render = () => (
21
+ <section
22
+ class="kui-recipe recipe-compact-toolbar kui-recipe__surface kui-layout"
23
+ data-recipe="recipe-compact-toolbar"
24
+ >
25
+ <Toolbar
26
+ label="Task workspace controls"
27
+ leading={
28
+ <ToolbarControlGroup appearance="borderless" single>
29
+ <ToolbarText text="Tasks" size="small" />
30
+ </ToolbarControlGroup>
31
+ }
32
+ center={
33
+ <ToolbarControlGroup>
34
+ <SegmentedControl
35
+ id="recipe-view"
36
+ label="View"
37
+ value={view.value}
38
+ action="recipe-action"
39
+ appearance="toolbar"
40
+ shape="pill"
41
+ size="small"
42
+ choices={[
43
+ { value: 'list', label: 'List' },
44
+ { value: 'board', label: 'Board' },
45
+ { value: 'timeline', label: 'Timeline' },
46
+ ]}
47
+ />
48
+ </ToolbarControlGroup>
49
+ }
50
+ trailing={
51
+ <div class="kui-control-cluster">
52
+ <Select
53
+ name="recipe-sort"
54
+ value={sort.value}
55
+ ariaLabel="Sort tasks"
56
+ fitMenu
57
+ choices={[
58
+ { value: 'updated', label: 'Recently updated' },
59
+ { value: 'priority', label: 'Priority' },
60
+ { value: 'owner', label: 'Owner' },
61
+ { value: 'created', label: 'Created date' },
62
+ ]}
63
+ />
64
+ <ToolbarControlGroup label="Task actions" buttonAppearance="push">
65
+ <button
66
+ type="button"
67
+ aria-label="Toggle filters"
68
+ aria-pressed={String(filtered.value)}
69
+ data-action="recipe-action"
70
+ data-recipe-command="filter"
71
+ >
72
+ <LucideIcon icon={Filter} name="filter" />
73
+ </button>
74
+ <button
75
+ type="button"
76
+ aria-label="Refresh tasks"
77
+ data-action="recipe-action"
78
+ data-recipe-command="refresh"
79
+ >
80
+ <LucideIcon icon={RefreshCw} name="refresh-cw" />
81
+ </button>
82
+ </ToolbarControlGroup>
83
+ <button
84
+ class="kui-recipe__button"
85
+ type="button"
86
+ aria-label="More task actions"
87
+ data-action="recipe-action"
88
+ data-recipe-command="more"
89
+ >
90
+ <LucideIcon icon={MoreHorizontal} name="more-horizontal" />
91
+ </button>
92
+ </div>
93
+ }
94
+ />
95
+ <div class="recipe-compact-toolbar__guide kui-content-item">
96
+ <div>
97
+ <strong>ToolbarControlGroup</strong>
98
+ <span class="kui-recipe__muted">
99
+ Related commands or pressed tools.
100
+ </span>
101
+ </div>
102
+ <div>
103
+ <strong>SegmentedControl</strong>
104
+ <span class="kui-recipe__muted">
105
+ A few visible exclusive choices.
106
+ </span>
107
+ </div>
108
+ <div>
109
+ <strong>Select</strong>
110
+ <span class="kui-recipe__muted">A longer controlled value list.</span>
111
+ </div>
112
+ <div>
113
+ <strong>Ordinary button</strong>
114
+ <span class="kui-recipe__muted">One independent command.</span>
115
+ </div>
116
+ </div>
117
+ <p class="kui-recipe__ownership kui-content-item">
118
+ The recipe owns control semantics and wrapping. The app owns values,
119
+ actions, persistence, and responsive priority.
120
+ </p>
121
+ </section>
122
+ );
30
123
  return {
31
124
  render,
32
125
  action(command, element) {
33
126
  const value = element.dataset.segmentValue;
34
127
  if (value) view.value = value;
35
128
  if (command === 'filter') filtered.value = !filtered.value;
36
- announce(value ? `View: ${value}` : command === 'filter' ? `Filters ${filtered.value ? 'on' : 'off'}` : `${command} requested`);
129
+ announce(
130
+ value
131
+ ? `View: ${value}`
132
+ : command === 'filter'
133
+ ? `Filters ${filtered.value ? 'on' : 'off'}`
134
+ : `${command} requested`,
135
+ );
37
136
  },
38
137
  change(element) {
39
138
  const value = (element as HTMLElement & { value?: string }).value;
40
- if (element.getAttribute('name') === 'recipe-sort' && value) { sort.value = value; announce(`Sort: ${value}`); }
139
+ if (element.getAttribute('name') === 'recipe-sort' && value) {
140
+ sort.value = value;
141
+ announce(`Sort: ${value}`);
142
+ }
41
143
  },
42
144
  };
43
145
  };