@kerfjs/ui 5.0.0-beta.50 → 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 (205) hide show
  1. package/README.md +29 -5
  2. package/ai/compile-time-contracts-v1.json +5 -2
  3. package/ai/component-catalog-v2.d.ts +19 -1
  4. package/ai/component-catalog-v2.json +842 -127
  5. package/ai/component-catalog-v2.schema.json +33 -1
  6. package/ai/component-catalog.json +603 -126
  7. package/ai/component-catalog.schema.json +21 -1
  8. package/ai/public-api-signatures-v1.md +201 -29
  9. package/ai/skill.md +12 -9
  10. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  11. package/analyzer/index.mjs +105 -35
  12. package/dist/app-tab.js +1 -1
  13. package/dist/catalog-resources.d.ts +1 -1
  14. package/dist/catalog.d.ts +28 -5
  15. package/dist/catalog.js +66 -25
  16. package/dist/catalog.js.map +1 -1
  17. package/dist/{chunk-COUPLRZG.js → chunk-5FZZMIMD.js} +3 -3
  18. package/dist/{chunk-COUPLRZG.js.map → chunk-5FZZMIMD.js.map} +1 -1
  19. package/dist/{chunk-DARTMI5Z.js → chunk-5PMGQDG7.js} +2 -2
  20. package/dist/{chunk-DARTMI5Z.js.map → chunk-5PMGQDG7.js.map} +1 -1
  21. package/dist/{chunk-V6RZWDII.js → chunk-7GUQYHKA.js} +19 -2
  22. package/dist/chunk-7GUQYHKA.js.map +1 -0
  23. package/dist/{chunk-ZNZ2JDBW.js → chunk-7IUSV4XG.js} +1 -1
  24. package/dist/chunk-7IUSV4XG.js.map +1 -0
  25. package/dist/{chunk-ELRMBQEC.js → chunk-AEPXGCNF.js} +17 -2
  26. package/dist/chunk-AEPXGCNF.js.map +1 -0
  27. package/dist/{chunk-5CE64WGC.js → chunk-D547PH4L.js} +3 -3
  28. package/dist/{chunk-5CE64WGC.js.map → chunk-D547PH4L.js.map} +1 -1
  29. package/dist/{chunk-S6ZTTTV7.js → chunk-DVZCMMB3.js} +3 -3
  30. package/dist/{chunk-S6ZTTTV7.js.map → chunk-DVZCMMB3.js.map} +1 -1
  31. package/dist/chunk-EKYZCRCX.js +256 -0
  32. package/dist/chunk-EKYZCRCX.js.map +1 -0
  33. package/dist/{chunk-3V75EQIP.js → chunk-JM53SMVF.js} +3 -3
  34. package/dist/{chunk-3V75EQIP.js.map → chunk-JM53SMVF.js.map} +1 -1
  35. package/dist/{chunk-2WMSE4MK.js → chunk-JMGKM7RK.js} +2 -2
  36. package/dist/{chunk-2WMSE4MK.js.map → chunk-JMGKM7RK.js.map} +1 -1
  37. package/dist/{chunk-2YOUUEWR.js → chunk-KX7DP2GO.js} +4 -4
  38. package/dist/{chunk-2YOUUEWR.js.map → chunk-KX7DP2GO.js.map} +1 -1
  39. package/dist/{chunk-OL4TC3U2.js → chunk-MO56ZZLG.js} +6 -2
  40. package/dist/chunk-MO56ZZLG.js.map +1 -0
  41. package/dist/{chunk-OQI7RCRP.js → chunk-MTM43SJO.js} +7 -1
  42. package/dist/chunk-MTM43SJO.js.map +1 -0
  43. package/dist/{chunk-ZSYLBVWG.js → chunk-NMGTHYW3.js} +3 -3
  44. package/dist/{chunk-ZSYLBVWG.js.map → chunk-NMGTHYW3.js.map} +1 -1
  45. package/dist/{chunk-AQSKJ6U6.js → chunk-NZXWU4R5.js} +21 -2
  46. package/dist/chunk-NZXWU4R5.js.map +1 -0
  47. package/dist/{chunk-DJKWFYAA.js → chunk-O3RCCSQJ.js} +3 -3
  48. package/dist/{chunk-DJKWFYAA.js.map → chunk-O3RCCSQJ.js.map} +1 -1
  49. package/dist/{chunk-UMGY5JSP.js → chunk-S4QRSBFI.js} +22 -2
  50. package/dist/chunk-S4QRSBFI.js.map +1 -0
  51. package/dist/{chunk-KKNK3JUT.js → chunk-UBH743HV.js} +5 -5
  52. package/dist/chunk-UBH743HV.js.map +1 -0
  53. package/dist/{chunk-54HM36IQ.js → chunk-ULCDZTWL.js} +2 -2
  54. package/dist/{chunk-54HM36IQ.js.map → chunk-ULCDZTWL.js.map} +1 -1
  55. package/dist/{chunk-SIU2AC5W.js → chunk-UPZ6EEJN.js} +3 -3
  56. package/dist/{chunk-SIU2AC5W.js.map → chunk-UPZ6EEJN.js.map} +1 -1
  57. package/dist/collapsible-panel.d.ts +1 -1
  58. package/dist/collapsible-panel.js.map +1 -1
  59. package/dist/css-values.d.ts +39 -3
  60. package/dist/css-values.js +1 -1
  61. package/dist/grid.d.ts +15 -1
  62. package/dist/grid.js +2 -1
  63. package/dist/index.d.ts +1 -1
  64. package/dist/index.js +19 -19
  65. package/dist/list-action-row.js +1 -1
  66. package/dist/list-header.js +1 -1
  67. package/dist/list-item.js +1 -1
  68. package/dist/list.d.ts +21 -2
  69. package/dist/list.js +2 -1
  70. package/dist/nav-stack.js +1 -1
  71. package/dist/pane.d.ts +17 -2
  72. package/dist/pane.js +1 -1
  73. package/dist/row.d.ts +19 -1
  74. package/dist/row.js +2 -1
  75. package/dist/segmented-control.js +1 -1
  76. package/dist/select.d.ts +13 -3
  77. package/dist/select.js +1 -1
  78. package/dist/skeleton.js +1 -1
  79. package/dist/spacer.js +1 -1
  80. package/dist/split-view.js +1 -1
  81. package/dist/state-banner.js +1 -1
  82. package/dist/styles/catalog/components/catalog-example-stack.css +4 -1
  83. package/dist/styles/catalog/components/catalog-example.css +145 -0
  84. package/dist/styles/collapsible-panel.css +132 -4
  85. package/dist/styles/document.css +23 -0
  86. package/dist/styles/foundation.css +21 -7
  87. package/dist/styles/grid.css +10 -0
  88. package/dist/styles/list-header.css +45 -0
  89. package/dist/styles/list-inset-control.css +8 -1
  90. package/dist/styles/list-item.css +6 -0
  91. package/dist/styles/list.css +11 -0
  92. package/dist/styles/nav-stack.css +89 -2
  93. package/dist/styles/pane.css +86 -1
  94. package/dist/styles/resizable-region.css +93 -5
  95. package/dist/styles/row.css +10 -0
  96. package/dist/styles/select.css +39 -4
  97. package/dist/styles/split-view.css +107 -1
  98. package/dist/styles/tab-scaffold.css +64 -1
  99. package/dist/styles/text.css +6 -0
  100. package/dist/styles/toolbar-control-group.css +90 -11
  101. package/dist/styles/toolbar-text.css +4 -1
  102. package/dist/styles/toolbar.css +130 -38
  103. package/dist/styles/webawesome.css +48 -2
  104. package/dist/styles/workbench.css +136 -4
  105. package/dist/text.d.ts +4 -3
  106. package/dist/text.js +1 -1
  107. package/dist/toolbar-text.js +1 -1
  108. package/dist/toolbar.d.ts +24 -3
  109. package/dist/toolbar.js +1 -1
  110. package/dist/{types-CzgSrFXP.d.ts → types-CEsieBZi.d.ts} +6 -1
  111. package/dist/value-table.js +1 -1
  112. package/dist/wire-resizable-regions.js +1 -1
  113. package/dist/wire-sidebar.d.ts +18 -3
  114. package/dist/wire-sidebar.js +95 -14
  115. package/dist/wire-sidebar.js.map +1 -1
  116. package/docs/accessibility.md +45 -8
  117. package/docs/app-layouts.md +73 -0
  118. package/docs/catalog.md +23 -2
  119. package/docs/collapsible-panel.md +81 -8
  120. package/docs/component-contract.md +28 -9
  121. package/docs/component-integration.md +9 -4
  122. package/docs/component-selection.md +25 -6
  123. package/docs/design/templates/list/stack-dark.svg +1 -1
  124. package/docs/design/templates/list/stack.svg +1 -1
  125. package/docs/design/templates/list-dark.svg +1 -1
  126. package/docs/design/templates/list-header/action-dark.svg +1 -1
  127. package/docs/design/templates/list-header/action.svg +1 -1
  128. package/docs/design/templates/list-header/count-dark.svg +1 -1
  129. package/docs/design/templates/list-header/count.svg +1 -1
  130. package/docs/design/templates/list-header/inline-action-dark.svg +1 -1
  131. package/docs/design/templates/list-header/inline-action.svg +1 -1
  132. package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -1
  133. package/docs/design/templates/list-header/toggle-expanded.svg +1 -1
  134. package/docs/design/templates/list-header-dark.svg +1 -1
  135. package/docs/design/templates/list-header.svg +1 -1
  136. package/docs/design/templates/list.svg +1 -1
  137. package/docs/design/templates/state-banner/info-badge-dark.svg +1 -1
  138. package/docs/design/templates/state-banner/info-badge.svg +1 -1
  139. package/docs/design/templates/state-banner-dark.svg +1 -1
  140. package/docs/design/templates/state-banner.svg +1 -1
  141. package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -1
  142. package/docs/design/templates/toolbar/title-and-actions.svg +1 -1
  143. package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -1
  144. package/docs/design/templates/toolbar-control-group/with-text.svg +1 -1
  145. package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
  146. package/docs/design/templates/toolbar-control-group.svg +1 -1
  147. package/docs/design/templates/toolbar-dark.svg +1 -1
  148. package/docs/design/templates/toolbar-text/default-dark.svg +1 -1
  149. package/docs/design/templates/toolbar-text/default.svg +1 -1
  150. package/docs/design/templates/toolbar-text/large-dark.svg +1 -1
  151. package/docs/design/templates/toolbar-text/large.svg +1 -1
  152. package/docs/design/templates/toolbar-text/small-dark.svg +1 -1
  153. package/docs/design/templates/toolbar-text/small.svg +1 -1
  154. package/docs/design/templates/toolbar-text-dark.svg +1 -1
  155. package/docs/design/templates/toolbar-text.svg +1 -1
  156. package/docs/design/templates/toolbar.svg +1 -1
  157. package/docs/document-baseline.md +31 -0
  158. package/docs/layout.md +86 -21
  159. package/docs/nav-stack.md +8 -0
  160. package/docs/recipes.md +78 -43
  161. package/docs/split-view.md +8 -0
  162. package/docs/tab-scaffold.md +4 -2
  163. package/docs/type-contracts.md +21 -17
  164. package/docs/ui-analyzer.md +19 -0
  165. package/docs/ui-evaluator.md +17 -0
  166. package/docs/ux-demo.md +4 -4
  167. package/docs/webawesome-theme.md +20 -2
  168. package/docs/workbench.md +20 -1
  169. package/evaluator/index.mjs +73 -8
  170. package/llms.txt +32 -14
  171. package/package.json +4 -2
  172. package/ux-demo/recipes/app-shell.tsx +209 -178
  173. package/ux-demo/recipes/collapsible-sidebar.tsx +188 -159
  174. package/ux-demo/recipes/compact-toolbar.tsx +48 -35
  175. package/ux-demo/recipes/composer-form.tsx +85 -79
  176. package/ux-demo/recipes/list-detail-dialog.tsx +124 -142
  177. package/ux-demo/recipes/list-workspace-states.tsx +83 -83
  178. package/ux-demo/recipes/loaders.ts +5 -2
  179. package/ux-demo/recipes/loading-inspector.tsx +157 -160
  180. package/ux-demo/recipes/navigation-sidebar.tsx +111 -109
  181. package/ux-demo/recipes/navigation-stack.tsx +50 -49
  182. package/ux-demo/recipes/types.ts +14 -0
  183. package/ux-demo/recipes/workspace-header.tsx +73 -56
  184. package/dist/chunk-AQSKJ6U6.js.map +0 -1
  185. package/dist/chunk-ELRMBQEC.js.map +0 -1
  186. package/dist/chunk-J3EE6XWD.js +0 -115
  187. package/dist/chunk-J3EE6XWD.js.map +0 -1
  188. package/dist/chunk-KKNK3JUT.js.map +0 -1
  189. package/dist/chunk-OL4TC3U2.js.map +0 -1
  190. package/dist/chunk-OQI7RCRP.js.map +0 -1
  191. package/dist/chunk-UMGY5JSP.js.map +0 -1
  192. package/dist/chunk-V6RZWDII.js.map +0 -1
  193. package/dist/chunk-ZNZ2JDBW.js.map +0 -1
  194. package/ux-demo/recipes/app-shell.css +0 -87
  195. package/ux-demo/recipes/collapsible-sidebar.css +0 -83
  196. package/ux-demo/recipes/compact-toolbar.css +0 -35
  197. package/ux-demo/recipes/composer-form.css +0 -19
  198. package/ux-demo/recipes/list-detail-dialog.css +0 -42
  199. package/ux-demo/recipes/list-workspace-states.css +0 -30
  200. package/ux-demo/recipes/loading-inspector.css +0 -7
  201. package/ux-demo/recipes/navigation-sidebar.css +0 -12
  202. package/ux-demo/recipes/navigation-stack.css +0 -12
  203. package/ux-demo/recipes/recipe-root.css +0 -67
  204. package/ux-demo/recipes/recipe-root.tsx +0 -87
  205. package/ux-demo/recipes/workspace-header.css +0 -7
@@ -1,33 +1,44 @@
1
1
  import '@kerfjs/ui/layout.css';
2
2
  import '@kerfjs/ui/collapsible-panel.css';
3
- import './collapsible-sidebar.css';
4
3
 
5
4
  import {
6
5
  CollapsiblePanel,
7
6
  CollapsiblePanelToggle,
8
7
  } from '@kerfjs/ui/collapsible-panel';
9
8
  import { deviceClass } from '@kerfjs/ui/device-class';
9
+ import { Grid } from '@kerfjs/ui/grid';
10
+ import { List } from '@kerfjs/ui/list';
10
11
  import { ListHeader } from '@kerfjs/ui/list-header';
11
12
  import { ListItem } from '@kerfjs/ui/list-item';
12
13
  import { LucideIcon } from '@kerfjs/ui/lucide-icon';
13
14
  import { Pane } from '@kerfjs/ui/pane';
15
+ import { Row } from '@kerfjs/ui/row';
16
+ import { Text } from '@kerfjs/ui/text';
14
17
  import { Toolbar } from '@kerfjs/ui/toolbar';
15
18
  import { ToolbarControlGroup } from '@kerfjs/ui/toolbar-control-group';
16
19
  import { ToolbarText } from '@kerfjs/ui/toolbar-text';
20
+ import { ValueTable, ValueTableRow } from '@kerfjs/ui/value-table';
17
21
  import { type SidebarStorage, wireSidebar } from '@kerfjs/ui/wire-sidebar';
18
22
  import { signal } from 'kerfjs';
19
23
  import { Bell, Folder, Inbox, Users } from 'lucide';
20
24
 
21
- import {
22
- RecipeMutedText,
23
- RecipeOwnershipNote,
24
- RecipeRoot,
25
- } from './recipe-root.js';
26
- import type { RecipeFactory } from './types.js';
25
+ import type { RecipeFactory, RecipePresentation } from './types.js';
27
26
 
28
27
  const RAIL_ACTION = 'recipe-sidebar-rail';
29
28
  const DRAWER_ACTION = 'recipe-sidebar-drawer';
30
29
 
30
+ export const presentation: RecipePresentation = {
31
+ viewport: {
32
+ width: 'full',
33
+ height: 'app',
34
+ frame: 'solid',
35
+ surface: 'default',
36
+ overflow: 'hidden',
37
+ shadow: true,
38
+ },
39
+ note: "The app owns each panel's collapsed signal, sizes, and content; wireSidebar owns the toggle, focus move/restore, the compact overlay, and persistence.",
40
+ };
41
+
31
42
  /**
32
43
  * A memory-backed store so the recipe demonstrates the persistence hook without
33
44
  * writing to the shared `localStorage` the catalog page runs in.
@@ -42,23 +53,31 @@ function memoryStorage(): SidebarStorage {
42
53
  };
43
54
  }
44
55
 
56
+ const labels: Record<string, string> = {
57
+ inbox: 'Inbox',
58
+ projects: 'Projects',
59
+ shared: 'Shared with me',
60
+ };
61
+
45
62
  export const createRecipe: RecipeFactory = (announce) => {
46
63
  const selected = signal('inbox');
47
- const railCollapsed = signal(false);
48
- const drawerCollapsed = signal(true);
49
- const storage = memoryStorage();
50
64
  // A shared window-backed device class: when the viewport is compact the wire
51
65
  // switches the rail/drawer to a dismissable overlay presentation.
52
66
  const device = deviceClass();
67
+ // A compact overlay only opens on a user action, so the first render already
68
+ // matches: collapsed on a compact device, open inline otherwise. The wire's
69
+ // `inlineCollapsed` keeps the open inline default for a later wide crossing.
70
+ const railCollapsed = signal(device.value.compact);
71
+ const drawerCollapsed = signal(true);
72
+ const storage = memoryStorage();
53
73
 
54
- const railToggle = (inPanel: boolean) => (
74
+ const railToggle = () => (
55
75
  <CollapsiblePanelToggle
56
76
  side="left"
57
77
  collapsed={railCollapsed.value}
58
78
  action={RAIL_ACTION}
59
79
  panelId="sidebar-rail"
60
80
  label={railCollapsed.value ? 'Show navigation' : 'Hide navigation'}
61
- className={inPanel ? '' : 'recipe-collapsible-sidebar__reveal'}
62
81
  />
63
82
  );
64
83
  const drawerToggle = () => (
@@ -71,158 +90,167 @@ export const createRecipe: RecipeFactory = (announce) => {
71
90
  />
72
91
  );
73
92
 
93
+ const rail = () => (
94
+ <CollapsiblePanel
95
+ id="sidebar-rail"
96
+ side="left"
97
+ size={232}
98
+ collapsed={railCollapsed.value}
99
+ label="Workspace navigation"
100
+ >
101
+ <Pane
102
+ contentElement="nav"
103
+ contentLabel="Workspace"
104
+ header={
105
+ <Toolbar
106
+ label="Workspace navigation"
107
+ dividerSides=""
108
+ leading={<ToolbarText text="Atlas" />}
109
+ trailing={
110
+ <ToolbarControlGroup appearance="borderless" single>
111
+ {railToggle()}
112
+ </ToolbarControlGroup>
113
+ }
114
+ />
115
+ }
116
+ >
117
+ <section>
118
+ <ListHeader label="Workspace" />
119
+ <ListItem
120
+ action="recipe-action"
121
+ itemId="inbox"
122
+ label="Inbox"
123
+ icon={<LucideIcon icon={Inbox} name="inbox" />}
124
+ trailing={<span>12</span>}
125
+ selected={selected.value === 'inbox'}
126
+ />
127
+ <ListItem
128
+ action="recipe-action"
129
+ itemId="projects"
130
+ label="Projects"
131
+ icon={<LucideIcon icon={Folder} name="folder" />}
132
+ selected={selected.value === 'projects'}
133
+ />
134
+ <ListItem
135
+ action="recipe-action"
136
+ itemId="shared"
137
+ label="Shared with me"
138
+ icon={<LucideIcon icon={Users} name="users" />}
139
+ selected={selected.value === 'shared'}
140
+ />
141
+ </section>
142
+ </Pane>
143
+ </CollapsiblePanel>
144
+ );
145
+
146
+ // Each panel's own header holds its collapse toggle; the always-visible main
147
+ // header holds its expand toggle only while it is collapsed, so exactly one
148
+ // control owns each action. wireSidebar hands focus between them.
149
+ const main = () => (
150
+ <Pane
151
+ element="main"
152
+ header={
153
+ <Toolbar
154
+ label="Current navigation view"
155
+ dividerSides=""
156
+ leading={
157
+ <>
158
+ {railCollapsed.value ? (
159
+ <ToolbarControlGroup appearance="borderless" single>
160
+ {railToggle()}
161
+ </ToolbarControlGroup>
162
+ ) : null}
163
+ <ToolbarText
164
+ text={labels[selected.value] ?? 'Inbox'}
165
+ size="xlarge"
166
+ id="recipe-collapsible-main-title"
167
+ />
168
+ </>
169
+ }
170
+ trailing={
171
+ drawerCollapsed.value ? (
172
+ <ToolbarControlGroup appearance="borderless" single>
173
+ {drawerToggle()}
174
+ </ToolbarControlGroup>
175
+ ) : undefined
176
+ }
177
+ />
178
+ }
179
+ >
180
+ <div class="kui-content-item">
181
+ <List gap="2xs">
182
+ <Text variant="span">
183
+ <strong>Narrow the window to a compact width</strong>
184
+ </Text>
185
+ <Text variant="span" tone="quiet" size="compact">
186
+ The rail and drawer start closed and open as a dismissable overlay:
187
+ a backdrop, Escape, and a trapped Tab ring, all managed by the wire.
188
+ </Text>
189
+ </List>
190
+ </div>
191
+ </Pane>
192
+ );
193
+
194
+ const drawer = () => (
195
+ <CollapsiblePanel
196
+ id="sidebar-console"
197
+ side="bottom"
198
+ size={168}
199
+ collapsed={drawerCollapsed.value}
200
+ label="Activity"
201
+ >
202
+ <Pane
203
+ header={
204
+ <Toolbar
205
+ label="Activity"
206
+ dividerSides="b"
207
+ leading={<ToolbarText text="Activity" size="small" />}
208
+ trailing={
209
+ <ToolbarControlGroup appearance="borderless" single>
210
+ {drawerToggle()}
211
+ </ToolbarControlGroup>
212
+ }
213
+ />
214
+ }
215
+ >
216
+ <ValueTable label="Recent activity">
217
+ <ValueTableRow
218
+ label="Deploy finished"
219
+ value="2m ago"
220
+ icon={<LucideIcon icon={Bell} name="bell" />}
221
+ />
222
+ <ValueTableRow
223
+ label="Review requested"
224
+ value="9m ago"
225
+ icon={<LucideIcon icon={Bell} name="bell" />}
226
+ />
227
+ </ValueTable>
228
+ </Pane>
229
+ </CollapsiblePanel>
230
+ );
231
+
232
+ // The root Row fills the frame's definite height with the rail beside a
233
+ // column whose main pane grows above the bottom drawer. Each CollapsiblePanel
234
+ // docks to a real frame edge, so it paints through that edge's safe area and
235
+ // hands the edge back to its siblings when it collapses.
74
236
  const render = () => (
75
- <RecipeRoot
76
- recipe="recipe-collapsible-sidebar"
77
- dataAttributes={{
237
+ <Row
238
+ gap="none"
239
+ fill
240
+ rootAttributes={{
241
+ 'data-recipe': 'recipe-collapsible-sidebar',
78
242
  'data-rail-collapsed': String(railCollapsed.value),
79
243
  'data-drawer-collapsed': String(drawerCollapsed.value),
80
244
  }}
81
245
  >
82
- <div class="recipe-collapsible-sidebar">
83
- <div class="recipe-collapsible-sidebar__rail">
84
- <CollapsiblePanel
85
- id="sidebar-rail"
86
- side="left"
87
- size={232}
88
- collapsed={railCollapsed.value}
89
- label="Workspace navigation"
90
- >
91
- <Pane
92
- contentElement="nav"
93
- contentClassName="recipe-collapsible-sidebar__nav"
94
- contentLabel="Workspace"
95
- header={
96
- <Toolbar
97
- label="Workspace navigation"
98
- dividerSides=""
99
- leading={<ToolbarText text="Atlas" />}
100
- trailing={
101
- <ToolbarControlGroup appearance="borderless" single>
102
- {railToggle(true)}
103
- </ToolbarControlGroup>
104
- }
105
- />
106
- }
107
- >
108
- <section>
109
- <ListHeader label="Workspace" />
110
- <ListItem
111
- action="recipe-action"
112
- itemId="inbox"
113
- label="Inbox"
114
- icon={<LucideIcon icon={Inbox} name="inbox" />}
115
- trailing={<span>12</span>}
116
- selected={selected.value === 'inbox'}
117
- />
118
- <ListItem
119
- action="recipe-action"
120
- itemId="projects"
121
- label="Projects"
122
- icon={<LucideIcon icon={Folder} name="folder" />}
123
- selected={selected.value === 'projects'}
124
- />
125
- <ListItem
126
- action="recipe-action"
127
- itemId="shared"
128
- label="Shared with me"
129
- icon={<LucideIcon icon={Users} name="users" />}
130
- selected={selected.value === 'shared'}
131
- />
132
- </section>
133
- </Pane>
134
- </CollapsiblePanel>
135
- </div>
136
- <div class="recipe-collapsible-sidebar__main">
137
- <Pane
138
- element="main"
139
- contentClassName="recipe-collapsible-sidebar__body"
140
- header={
141
- <Toolbar
142
- label="Current navigation view"
143
- dividerSides=""
144
- leading={
145
- <>
146
- <ToolbarControlGroup appearance="borderless" single>
147
- {railToggle(false)}
148
- </ToolbarControlGroup>
149
- <ToolbarText
150
- text={
151
- selected.value === 'projects'
152
- ? 'Projects'
153
- : selected.value === 'shared'
154
- ? 'Shared with me'
155
- : 'Inbox'
156
- }
157
- size="xlarge"
158
- id="recipe-collapsible-main-title"
159
- />
160
- </>
161
- }
162
- trailing={
163
- <ToolbarControlGroup appearance="borderless" single>
164
- {drawerToggle()}
165
- </ToolbarControlGroup>
166
- }
167
- />
168
- }
169
- >
170
- <RecipeOwnershipNote>
171
- The app owns each panel's <code>collapsed</code> signal, sizes,
172
- and content; <code>wireSidebar</code> owns the toggle, focus
173
- move/restore, the compact overlay, and persistence.
174
- </RecipeOwnershipNote>
175
- <article class="recipe-collapsible-sidebar__card kui-content-item">
176
- <strong>Narrow the window to a compact width</strong>
177
- <RecipeMutedText>
178
- The rail and drawer become a dismissable overlay: a backdrop,
179
- Escape, and a trapped Tab ring, all managed by the wire.
180
- </RecipeMutedText>
181
- </article>
182
- </Pane>
183
- </div>
184
- <div class="recipe-collapsible-sidebar__drawer">
185
- <CollapsiblePanel
186
- id="sidebar-console"
187
- side="bottom"
188
- size={168}
189
- collapsed={drawerCollapsed.value}
190
- label="Activity"
191
- >
192
- <Pane
193
- contentClassName="recipe-collapsible-sidebar__drawer-body"
194
- header={
195
- <Toolbar
196
- label="Activity"
197
- dividerSides="b"
198
- leading={<ToolbarText text="Activity" size="small" />}
199
- trailing={
200
- <ToolbarControlGroup appearance="borderless" single>
201
- {drawerToggle()}
202
- </ToolbarControlGroup>
203
- }
204
- />
205
- }
206
- >
207
- <ul class="recipe-collapsible-sidebar__log">
208
- <li>
209
- <span class="recipe-collapsible-sidebar__log-icon">
210
- <LucideIcon icon={Bell} name="bell" />
211
- </span>
212
- Deploy finished · 2m ago
213
- </li>
214
- <li>
215
- <span class="recipe-collapsible-sidebar__log-icon">
216
- <LucideIcon icon={Bell} name="bell" />
217
- </span>
218
- Review requested · 9m ago
219
- </li>
220
- </ul>
221
- </Pane>
222
- </CollapsiblePanel>
223
- </div>
224
- </div>
225
- </RecipeRoot>
246
+ {rail()}
247
+ <List flex>
248
+ <Grid columns={1} gap="none" flex>
249
+ {main()}
250
+ </Grid>
251
+ {drawer()}
252
+ </List>
253
+ </Row>
226
254
  );
227
255
 
228
256
  return {
@@ -246,6 +274,7 @@ export const createRecipe: RecipeFactory = (announce) => {
246
274
  collapsed: railCollapsed,
247
275
  toggleAction: RAIL_ACTION,
248
276
  storageKey: 'recipe-sidebar-rail',
277
+ inlineCollapsed: false,
249
278
  },
250
279
  {
251
280
  id: 'sidebar-console',
@@ -1,30 +1,48 @@
1
1
  import '@kerfjs/ui/layout.css';
2
2
  import '@kerfjs/ui/select/register';
3
- import './compact-toolbar.css';
4
3
 
4
+ import { deviceClass } from '@kerfjs/ui/device-class';
5
+ import { Grid } from '@kerfjs/ui/grid';
6
+ import { List } from '@kerfjs/ui/list';
7
+ import { ListInsetText } from '@kerfjs/ui/list-inset-text';
5
8
  import { LucideIcon } from '@kerfjs/ui/lucide-icon';
6
9
  import { SegmentedControl } from '@kerfjs/ui/segmented-control';
7
10
  import { Select } from '@kerfjs/ui/select';
11
+ import { Text } from '@kerfjs/ui/text';
8
12
  import { Toolbar } from '@kerfjs/ui/toolbar';
9
13
  import { ToolbarControlGroup } from '@kerfjs/ui/toolbar-control-group';
10
14
  import { ToolbarText } from '@kerfjs/ui/toolbar-text';
11
15
  import { signal } from 'kerfjs';
12
16
  import { Filter, MoreHorizontal, RefreshCw } from 'lucide';
13
17
 
14
- import {
15
- RecipeMutedText,
16
- RecipeOwnershipNote,
17
- RecipeRoot,
18
- } from './recipe-root.js';
19
- import type { RecipeFactory } from './types.js';
18
+ import type { RecipeFactory, RecipePresentation } from './types.js';
19
+
20
+ const guide = [
21
+ ['ToolbarControlGroup', 'Related commands or pressed tools.'],
22
+ ['SegmentedControl', 'A few visible exclusive choices.'],
23
+ ['Select', 'A longer controlled value list.'],
24
+ ['Ordinary button', 'One independent command.'],
25
+ ] as const;
26
+
27
+ export const presentation: RecipePresentation = {
28
+ viewport: {
29
+ width: 'full',
30
+ frame: 'solid',
31
+ surface: 'default',
32
+ overflow: 'hidden',
33
+ shadow: true,
34
+ },
35
+ note: 'The recipe owns control semantics and wrapping. The app owns values, actions, persistence, and responsive priority.',
36
+ };
20
37
 
21
38
  export const createRecipe: RecipeFactory = (announce) => {
22
39
  const view = signal('list');
23
40
  const sort = signal('updated');
24
41
  const filtered = signal(false);
42
+ const device = deviceClass();
25
43
  const render = () => (
26
- <RecipeRoot recipe="recipe-compact-toolbar">
27
- <div class="recipe-compact-toolbar kui-layout">
44
+ <section data-recipe="recipe-compact-toolbar" aria-label="Task workspace">
45
+ <List gap="l" controlInsets="b">
28
46
  <Toolbar
29
47
  label="Task workspace controls"
30
48
  responsive="stack"
@@ -99,32 +117,27 @@ export const createRecipe: RecipeFactory = (announce) => {
99
117
  </>
100
118
  }
101
119
  />
102
- <div class="recipe-compact-toolbar__guide kui-content-item">
103
- <div>
104
- <strong>ToolbarControlGroup</strong>
105
- <RecipeMutedText>
106
- Related commands or pressed tools.
107
- </RecipeMutedText>
108
- </div>
109
- <div>
110
- <strong>SegmentedControl</strong>
111
- <RecipeMutedText>A few visible exclusive choices.</RecipeMutedText>
112
- </div>
113
- <div>
114
- <strong>Select</strong>
115
- <RecipeMutedText>A longer controlled value list.</RecipeMutedText>
116
- </div>
117
- <div>
118
- <strong>Ordinary button</strong>
119
- <RecipeMutedText>One independent command.</RecipeMutedText>
120
- </div>
121
- </div>
122
- <RecipeOwnershipNote>
123
- The recipe owns control semantics and wrapping. The app owns values,
124
- actions, persistence, and responsive priority.
125
- </RecipeOwnershipNote>
126
- </div>
127
- </RecipeRoot>
120
+ <Grid
121
+ columns={
122
+ device.value.compact ? 1 : device.value.atLeast('desktop') ? 4 : 2
123
+ }
124
+ gap="none"
125
+ >
126
+ {guide.map(([name, use]) => (
127
+ <ListInsetText>
128
+ <List gap="2xs">
129
+ <Text variant="span">
130
+ <strong>{name}</strong>
131
+ </Text>
132
+ <Text variant="span" tone="quiet" size="compact">
133
+ {use}
134
+ </Text>
135
+ </List>
136
+ </ListInsetText>
137
+ ))}
138
+ </Grid>
139
+ </List>
140
+ </section>
128
141
  );
129
142
  return {
130
143
  render,