@kerfjs/ui 5.0.0-beta.50 → 5.0.0-beta.52

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