@kerfjs/ui 5.0.0-beta.54 → 5.0.0-beta.55

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 (109) hide show
  1. package/README.md +3 -2
  2. package/ai/component-catalog-v2.json +58 -1
  3. package/ai/component-catalog.json +105 -8
  4. package/ai/public-api-signatures-v1.md +265 -28
  5. package/ai/skill.md +6 -4
  6. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  7. package/dist/browser/popup-menu.js +3 -0
  8. package/dist/catalog-resources.d.ts +1 -1
  9. package/dist/catalog.d.ts +10 -4
  10. package/dist/catalog.js +243 -300
  11. package/dist/catalog.js.map +1 -1
  12. package/dist/{chunk-7IUSV4XG.js → chunk-24PHKUR3.js} +2 -2
  13. package/dist/chunk-24PHKUR3.js.map +1 -0
  14. package/dist/chunk-4BIMLITI.js +92 -0
  15. package/dist/chunk-4BIMLITI.js.map +1 -0
  16. package/dist/{chunk-ZK5EGVFP.js → chunk-5OLVUEND.js} +12 -2
  17. package/dist/chunk-5OLVUEND.js.map +1 -0
  18. package/dist/{chunk-CMUIXJZM.js → chunk-BSKEH3UC.js} +2 -2
  19. package/dist/{chunk-CMUIXJZM.js.map → chunk-BSKEH3UC.js.map} +1 -1
  20. package/dist/chunk-EAX2GSS4.js +384 -0
  21. package/dist/chunk-EAX2GSS4.js.map +1 -0
  22. package/dist/chunk-KH6VUGHU.js +399 -0
  23. package/dist/chunk-KH6VUGHU.js.map +1 -0
  24. package/dist/{chunk-BJJKFVWW.js → chunk-LZXQVUGN.js} +2 -2
  25. package/dist/chunk-LZXQVUGN.js.map +1 -0
  26. package/dist/chunk-N7FHFNT6.js +55 -0
  27. package/dist/chunk-N7FHFNT6.js.map +1 -0
  28. package/dist/{chunk-KPNTVB7A.js → chunk-R754MHXX.js} +3 -3
  29. package/dist/{chunk-KPNTVB7A.js.map → chunk-R754MHXX.js.map} +1 -1
  30. package/dist/{chunk-HJKRCKDR.js → chunk-RNRUVPJ3.js} +4 -2
  31. package/dist/chunk-RNRUVPJ3.js.map +1 -0
  32. package/dist/collapsible-panel.d.ts +40 -13
  33. package/dist/collapsible-panel.js +41 -10
  34. package/dist/collapsible-panel.js.map +1 -1
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +17 -16
  37. package/dist/list-header.js +1 -1
  38. package/dist/list-inset-text.d.ts +7 -1
  39. package/dist/list-inset-text.js +2 -1
  40. package/dist/panel-toolbar-BFQxFITu.d.ts +52 -0
  41. package/dist/popup-menu-register.d.ts +9 -0
  42. package/dist/popup-menu-register.js +30 -0
  43. package/dist/popup-menu-register.js.map +1 -0
  44. package/dist/popup-menu.d.ts +71 -0
  45. package/dist/popup-menu.js +3 -0
  46. package/dist/popup-menu.js.map +1 -0
  47. package/dist/state-banner.js +1 -1
  48. package/dist/styles/catalog/components/catalog-example.css +0 -16
  49. package/dist/styles/catalog/components/catalog-stage.css +8 -13
  50. package/dist/styles/catalog/components/catalog.css +8 -18
  51. package/dist/styles/catalog.css +0 -5
  52. package/dist/styles/popup-menu.css +12 -0
  53. package/dist/styles/state-banner.css +13 -4
  54. package/dist/styles/styles.css +1 -0
  55. package/dist/styles/tab-bar.css +31 -3
  56. package/dist/styles/toolbar-control-group.css +13 -0
  57. package/dist/styles/toolbar.css +43 -2
  58. package/dist/styles/workbench.css +31 -0
  59. package/dist/text.js +1 -1
  60. package/dist/token-search-field.d.ts +10 -1
  61. package/dist/token-search-field.js +1 -1
  62. package/dist/{types-CEsieBZi.d.ts → types-D6vzj-VT.d.ts} +15 -0
  63. package/dist/wire-catalog.d.ts +13 -1
  64. package/dist/wire-catalog.js +18 -3
  65. package/dist/wire-catalog.js.map +1 -1
  66. package/dist/wire-sidebar.js +6 -3
  67. package/dist/wire-sidebar.js.map +1 -1
  68. package/dist/wire-token-search-fields.js +1 -1
  69. package/dist/wire-workbench.js +1 -350
  70. package/dist/wire-workbench.js.map +1 -1
  71. package/dist/workbench.d.ts +107 -6
  72. package/dist/workbench.js +11 -208
  73. package/dist/workbench.js.map +1 -1
  74. package/docs/accessibility.md +12 -1
  75. package/docs/catalog.md +27 -9
  76. package/docs/collapsible-panel.md +55 -0
  77. package/docs/component-contract.md +2 -2
  78. package/docs/component-selection.md +25 -17
  79. package/docs/design/templates/state-banner/danger-dark.svg +1 -1
  80. package/docs/design/templates/state-banner/danger.svg +1 -1
  81. package/docs/design/templates/state-banner/info-dark.svg +1 -1
  82. package/docs/design/templates/state-banner/info.svg +1 -1
  83. package/docs/design/templates/state-banner/success-dark.svg +1 -1
  84. package/docs/design/templates/state-banner/success.svg +1 -1
  85. package/docs/design/templates/state-banner/warning-dark.svg +1 -1
  86. package/docs/design/templates/state-banner/warning.svg +1 -1
  87. package/docs/design/templates/state-banner-dark.svg +1 -1
  88. package/docs/design/templates/state-banner.svg +1 -1
  89. package/docs/layout.md +5 -0
  90. package/docs/recipes.md +44 -37
  91. package/docs/surface-scaffold.md +1 -1
  92. package/docs/ux-demo.md +6 -2
  93. package/docs/workbench.md +92 -7
  94. package/llms.txt +6 -5
  95. package/package.json +13 -2
  96. package/ux-demo/recipes/app-shell.tsx +141 -223
  97. package/ux-demo/recipes/collapsible-sidebar.tsx +44 -75
  98. package/ux-demo/recipes/list-detail-dialog.tsx +150 -54
  99. package/ux-demo/recipes/mount-recipe.ts +0 -5
  100. package/ux-demo/recipes/types.ts +0 -1
  101. package/dist/chunk-7IUSV4XG.js.map +0 -1
  102. package/dist/chunk-BJJKFVWW.js.map +0 -1
  103. package/dist/chunk-HJKRCKDR.js.map +0 -1
  104. package/dist/chunk-ZK5EGVFP.js.map +0 -1
  105. package/dist/styles/catalog/components/catalog-detail.css +0 -35
  106. package/dist/styles/catalog/components/catalog-resource-footer.css +0 -37
  107. package/dist/styles/catalog/components/catalog-secondary-sections.css +0 -38
  108. package/dist/styles/catalog/components/catalog-section-list.css +0 -22
  109. package/dist/styles/catalog/components/catalog-sidebar.css +0 -82
@@ -1,32 +1,22 @@
1
1
  import '@kerfjs/ui/layout.css';
2
+ import '@kerfjs/ui/workbench.css';
2
3
 
3
- import { deviceClass } from '@kerfjs/ui/device-class';
4
- import { Grid } from '@kerfjs/ui/grid';
5
4
  import { List } from '@kerfjs/ui/list';
6
- import { ListHeader } from '@kerfjs/ui/list-header';
7
5
  import { ListItem } from '@kerfjs/ui/list-item';
8
6
  import { LucideIcon } from '@kerfjs/ui/lucide-icon';
9
- import { Pane, type PaneSeparatorSide } from '@kerfjs/ui/pane';
10
- import { ResizableRegion } from '@kerfjs/ui/resizable-region';
11
- import { Row } from '@kerfjs/ui/row';
12
7
  import { Toolbar } from '@kerfjs/ui/toolbar';
13
8
  import { ToolbarControlGroup } from '@kerfjs/ui/toolbar-control-group';
14
9
  import { ToolbarText } from '@kerfjs/ui/toolbar-text';
15
10
  import { ValueTable, ValueTableRow } from '@kerfjs/ui/value-table';
16
- import { signal } from 'kerfjs';
11
+ import { wireWorkbench } from '@kerfjs/ui/wire-workbench';
12
+ import { Workbench } from '@kerfjs/ui/workbench';
13
+ import { delegate, signal } from 'kerfjs';
17
14
  import { Bell, Folder, Inbox, Settings } from 'lucide';
18
15
 
19
16
  import type { RecipeFactory, RecipePresentation } from './types.js';
20
17
 
21
- type ShellPane = 'content' | 'navigation' | 'inspector';
22
- type ScreenEdges = readonly PaneSeparatorSide[];
23
-
24
- // Safe areas: the app bar claims the top edge and both sides, so it clears the
25
- // status area. Each pane below compensates only for the screen edges it
26
- // actually reaches: the bottom, plus its outer side in the three-pane layout or
27
- // both sides when it is the only pane shown.
28
- const appBarEdges: ScreenEdges = ['block-start', 'inline-start', 'inline-end'];
29
- const soleEdges: ScreenEdges = ['block-end', 'inline-start', 'inline-end'];
18
+ const NAVIGATION_ACTION = 'recipe-shell-toggle-navigation';
19
+ const INSPECTOR_ACTION = 'recipe-shell-toggle-inspector';
30
20
 
31
21
  const tasks = [
32
22
  ['Release accessibility audit', 'Interface systems · due this week'],
@@ -44,46 +34,19 @@ export const presentation: RecipePresentation = {
44
34
  overflow: 'hidden',
45
35
  shadow: true,
46
36
  },
47
- note: 'The recipe owns pane geometry and one scroll owner per pane. The app owns routing, data, pane visibility, sizes, and persistence.',
37
+ note: 'The recipe is a Workbench under an app bar: resizable navigation and inspector rails beside the task list, each rail toggled by its standard control, which moves into the work-area toolbar while the rail is hidden. On a small screen the rails overlay the task list one at a time. The app owns routing, data, rail visibility, sizes, and persistence.',
48
38
  };
49
39
 
50
40
  export const createRecipe: RecipeFactory = (announce) => {
51
41
  const selected = signal('inbox');
52
42
  const navigationSize = signal(224);
53
43
  const inspectorSize = signal(240);
54
- const responsivePane = signal<ShellPane>('content');
55
- // Desktop-class viewports show all three panes; smaller ones show one pane
56
- // at a time and let the toolbar switch between them.
57
- const device = deviceClass();
58
-
59
- const paneButton = (
60
- pane: ShellPane,
61
- label: string,
62
- icon: typeof Folder,
63
- iconName: string,
64
- ) => (
65
- <button
66
- type="button"
67
- aria-label={label}
68
- aria-controls={`recipe-shell-${pane}`}
69
- aria-pressed={String(responsivePane.value === pane)}
70
- data-action="recipe-action"
71
- data-recipe-command={`show-${pane}`}
72
- >
73
- <LucideIcon icon={icon} name={iconName} />
74
- </button>
75
- );
44
+ const navigationCollapsed = signal(false);
45
+ const inspectorCollapsed = signal(false);
76
46
 
77
- const navigation = (edges: ScreenEdges) => (
78
- <Pane
79
- element="aside"
80
- safeAreaEdges={edges}
81
- id="recipe-shell-navigation"
82
- contentElement="nav"
83
- contentLabel="Workspace"
84
- >
85
- <section>
86
- <ListHeader label="Workspace" />
47
+ const navigation = () => (
48
+ <nav aria-label="Workspace">
49
+ <List gap="2xs">
87
50
  <ListItem
88
51
  action="recipe-action"
89
52
  itemId="inbox"
@@ -99,204 +62,159 @@ export const createRecipe: RecipeFactory = (announce) => {
99
62
  selected={selected.value === 'projects'}
100
63
  multiline
101
64
  />
102
- </section>
103
- </Pane>
104
- );
105
-
106
- const content = (edges: ScreenEdges) => (
107
- <Pane
108
- element="main"
109
- safeAreaEdges={edges}
110
- id="recipe-shell-content"
111
- header={
112
- <Toolbar
113
- label="Current workspace view"
114
- dividerSides=""
115
- leading={
116
- <ToolbarText
117
- text={
118
- selected.value === 'inbox' ? 'Inbox triage' : 'Active projects'
119
- }
120
- size="xlarge"
121
- id="recipe-shell-main-title"
122
- />
123
- }
124
- trailing={
125
- <ToolbarControlGroup appearance="borderless" content="text" single>
126
- <button
127
- type="button"
128
- data-action="recipe-action"
129
- data-recipe-command="new"
130
- >
131
- New task
132
- </button>
133
- </ToolbarControlGroup>
134
- }
135
- />
136
- }
137
- >
138
- <section aria-label="Tasks">
139
- {tasks.map(([title, detail]) => (
140
- <ListItem
141
- action="recipe-action"
142
- label={title}
143
- description={detail}
144
- multiline
145
- rootAttributes={{ 'data-recipe-command': 'open-task' }}
146
- />
147
- ))}
148
- </section>
149
- </Pane>
150
- );
151
-
152
- const inspector = (edges: ScreenEdges) => (
153
- <Pane
154
- element="aside"
155
- safeAreaEdges={edges}
156
- id="recipe-shell-inspector"
157
- header={
158
- <Toolbar
159
- label="Inspector"
160
- dividerSides=""
161
- leading={
162
- <ToolbarText
163
- text="Inspector"
164
- size="xlarge"
165
- id="recipe-shell-inspector-title"
166
- />
167
- }
168
- />
169
- }
170
- >
171
- <ValueTable label="Selected task">
172
- <ValueTableRow label="Status" value="In review" />
173
- <ValueTableRow label="Owner" value="Mara Chen" />
174
- <ValueTableRow label="Priority" value="High" />
175
- </ValueTable>
176
- </Pane>
65
+ </List>
66
+ </nav>
177
67
  );
178
68
 
179
- const render = () => {
180
- const onePane = !device.value.atLeast('desktop');
181
- return (
182
- <List
183
- fill
184
- rootAttributes={{
185
- 'data-recipe': 'recipe-app-shell',
186
- 'data-responsive-pane': onePane ? responsivePane.value : 'all',
187
- }}
188
- >
189
- <Toolbar
190
- label="Atlas workspace"
191
- safeAreaEdges={appBarEdges}
192
- leading={<ToolbarText text="Atlas" />}
193
- trailing={
194
- <>
195
- {onePane ? (
196
- <ToolbarControlGroup
197
- appearance="borderless"
198
- buttonAppearance="push"
199
- label="Workspace panes"
200
- >
201
- {paneButton(
202
- 'navigation',
203
- 'Show navigation',
204
- Folder,
205
- 'folder',
206
- )}
207
- {paneButton('content', 'Show content', Inbox, 'inbox')}
208
- {paneButton(
209
- 'inspector',
210
- 'Show inspector',
211
- Settings,
212
- 'settings',
213
- )}
214
- </ToolbarControlGroup>
215
- ) : null}
69
+ const render = () => (
70
+ <List fill rootAttributes={{ 'data-recipe': 'recipe-app-shell' }}>
71
+ <Toolbar
72
+ label="Atlas workspace"
73
+ safeAreaEdges={['block-start', 'inline-start', 'inline-end']}
74
+ dividerSides="b"
75
+ leading={<ToolbarText text="Atlas" />}
76
+ trailing={
77
+ <ToolbarControlGroup
78
+ appearance="borderless"
79
+ label="Workspace controls"
80
+ >
81
+ <button
82
+ type="button"
83
+ aria-label="Notifications"
84
+ data-action="recipe-action"
85
+ data-recipe-command="notify"
86
+ >
87
+ <LucideIcon icon={Bell} name="bell" />
88
+ </button>
89
+ <button
90
+ type="button"
91
+ aria-label="Settings"
92
+ data-action="recipe-action"
93
+ data-recipe-command="settings"
94
+ >
95
+ <LucideIcon icon={Settings} name="settings" />
96
+ </button>
97
+ </ToolbarControlGroup>
98
+ }
99
+ />
100
+ <List flex>
101
+ <Workbench
102
+ id="recipe-shell"
103
+ label="Atlas workspace panes"
104
+ leftRail={{
105
+ label: 'Navigation',
106
+ size: navigationSize.value,
107
+ resizable: { min: 180, max: 320 },
108
+ collapsed: navigationCollapsed.value,
109
+ toolbar: {
110
+ label: 'Navigation',
111
+ title: <ToolbarText text="Workspace" />,
112
+ toggle: { action: NAVIGATION_ACTION, name: 'navigation' },
113
+ },
114
+ content: navigation(),
115
+ }}
116
+ mainToolbar={{
117
+ label: 'Current workspace view',
118
+ title: (
119
+ <ToolbarText
120
+ text={
121
+ selected.value === 'inbox'
122
+ ? 'Inbox triage'
123
+ : 'Active projects'
124
+ }
125
+ size="xlarge"
126
+ id="recipe-shell-main-title"
127
+ />
128
+ ),
129
+ responsive: 'wrap',
130
+ trailing: (
216
131
  <ToolbarControlGroup
217
132
  appearance="borderless"
218
- label="Workspace controls"
133
+ content="text"
134
+ single
219
135
  >
220
136
  <button
221
137
  type="button"
222
- aria-label="Notifications"
223
138
  data-action="recipe-action"
224
- data-recipe-command="notify"
139
+ data-recipe-command="new"
225
140
  >
226
- <LucideIcon icon={Bell} name="bell" />
227
- </button>
228
- <button
229
- type="button"
230
- aria-label="Settings"
231
- data-action="recipe-action"
232
- data-recipe-command="settings"
233
- >
234
- <LucideIcon icon={Settings} name="settings" />
141
+ New task
235
142
  </button>
236
143
  </ToolbarControlGroup>
237
- </>
144
+ ),
145
+ }}
146
+ main={
147
+ <section aria-label="Tasks">
148
+ {tasks.map(([title, detail]) => (
149
+ <ListItem
150
+ action="recipe-action"
151
+ label={title}
152
+ description={detail}
153
+ multiline
154
+ rootAttributes={{ 'data-recipe-command': 'open-task' }}
155
+ />
156
+ ))}
157
+ </section>
238
158
  }
159
+ rightRail={{
160
+ label: 'Inspector',
161
+ size: inspectorSize.value,
162
+ resizable: { min: 200, max: 360 },
163
+ collapsed: inspectorCollapsed.value,
164
+ toolbar: {
165
+ label: 'Inspector',
166
+ title: <ToolbarText text="Inspector" />,
167
+ toggle: { action: INSPECTOR_ACTION, name: 'inspector' },
168
+ },
169
+ content: (
170
+ <ValueTable label="Selected task">
171
+ <ValueTableRow label="Status" value="In review" />
172
+ <ValueTableRow label="Owner" value="Mara Chen" />
173
+ <ValueTableRow label="Priority" value="High" />
174
+ </ValueTable>
175
+ ),
176
+ }}
239
177
  />
240
- {onePane ? (
241
- <Grid columns={1} gap="none" flex>
242
- {responsivePane.value === 'navigation'
243
- ? navigation(soleEdges)
244
- : responsivePane.value === 'inspector'
245
- ? inspector(soleEdges)
246
- : content(soleEdges)}
247
- </Grid>
248
- ) : (
249
- <Row gap="none" flex>
250
- <ResizableRegion
251
- id="recipe-navigation"
252
- label="Navigation"
253
- size={navigationSize.value}
254
- min={180}
255
- max={320}
256
- >
257
- {navigation(['block-end', 'inline-start'])}
258
- </ResizableRegion>
259
- <Grid columns={1} gap="none" flex>
260
- {content(['block-end'])}
261
- </Grid>
262
- <ResizableRegion
263
- id="recipe-inspector"
264
- label="Inspector"
265
- size={inspectorSize.value}
266
- min={200}
267
- max={360}
268
- edge="start"
269
- >
270
- {inspector(['block-end', 'inline-end'])}
271
- </ResizableRegion>
272
- </Row>
273
- )}
274
178
  </List>
275
- );
276
- };
179
+ </List>
180
+ );
181
+
277
182
  return {
278
183
  render,
279
184
  action(command, element) {
280
- const pane = command.match(
281
- /^show-(navigation|content|inspector)$/,
282
- )?.[1] as ShellPane | undefined;
283
- if (pane) responsivePane.value = pane;
284
185
  const id = element.dataset.itemId;
285
186
  if (id) selected.value = id;
286
187
  announce(
287
188
  id
288
189
  ? `Opened ${id}`
289
- : pane
290
- ? `Showing ${pane}`
291
- : command === 'new'
292
- ? 'New task requested'
293
- : `${command} requested`,
190
+ : command === 'new'
191
+ ? 'New task requested'
192
+ : `${command} requested`,
294
193
  );
295
194
  },
296
- resize(id, size) {
297
- if (id === 'recipe-navigation') navigationSize.value = size;
298
- if (id === 'recipe-inspector') inspectorSize.value = size;
299
- announce(`${id} resized to ${size}px`);
195
+ wire(root) {
196
+ const toggle = (action: string, collapsed: typeof navigationCollapsed) =>
197
+ delegate(root, 'click', `[data-action="${action}"]`, () => {
198
+ collapsed.value = !collapsed.value;
199
+ });
200
+ const stops = [
201
+ toggle(NAVIGATION_ACTION, navigationCollapsed),
202
+ toggle(INSPECTOR_ACTION, inspectorCollapsed),
203
+ wireWorkbench(root, {
204
+ id: 'recipe-shell',
205
+ panels: {
206
+ leftRail: { size: navigationSize, collapsed: navigationCollapsed },
207
+ rightRail: { size: inspectorSize, collapsed: inspectorCollapsed },
208
+ },
209
+ onResize: ({ panel, size }) =>
210
+ announce(
211
+ `${panel === 'leftRail' ? 'Navigation' : 'Inspector'} resized to ${size}px`,
212
+ ),
213
+ }),
214
+ ];
215
+ return () => {
216
+ for (const stop of stops) stop();
217
+ };
300
218
  },
301
219
  };
302
220
  };
@@ -4,7 +4,8 @@ import '@kerfjs/ui/floating-toolbar.css';
4
4
 
5
5
  import {
6
6
  CollapsiblePanel,
7
- CollapsiblePanelToggle,
7
+ CollapsiblePanelRelocated,
8
+ type CollapsiblePanelToolbar,
8
9
  } from '@kerfjs/ui/collapsible-panel';
9
10
  import { deviceClass } from '@kerfjs/ui/device-class';
10
11
  import { FloatingToolbar } from '@kerfjs/ui/floating-toolbar';
@@ -17,7 +18,6 @@ import { Pane } from '@kerfjs/ui/pane';
17
18
  import { Row } from '@kerfjs/ui/row';
18
19
  import { Text } from '@kerfjs/ui/text';
19
20
  import { Toolbar } from '@kerfjs/ui/toolbar';
20
- import { ToolbarControlGroup } from '@kerfjs/ui/toolbar-control-group';
21
21
  import { ToolbarText } from '@kerfjs/ui/toolbar-text';
22
22
  import { ValueTable, ValueTableRow } from '@kerfjs/ui/value-table';
23
23
  import { type SidebarStorage, wireSidebar } from '@kerfjs/ui/wire-sidebar';
@@ -73,24 +73,20 @@ export const createRecipe: RecipeFactory = (announce) => {
73
73
  const drawerCollapsed = signal(true);
74
74
  const storage = memoryStorage();
75
75
 
76
- const railToggle = () => (
77
- <CollapsiblePanelToggle
78
- side="left"
79
- collapsed={railCollapsed.value}
80
- action={RAIL_ACTION}
81
- panelId="sidebar-rail"
82
- label={railCollapsed.value ? 'Show navigation' : 'Hide navigation'}
83
- />
84
- );
85
- const drawerToggle = () => (
86
- <CollapsiblePanelToggle
87
- side="bottom"
88
- collapsed={drawerCollapsed.value}
89
- action={DRAWER_ACTION}
90
- panelId="sidebar-console"
91
- label={drawerCollapsed.value ? 'Show activity' : 'Hide activity'}
92
- />
93
- );
76
+ // Each panel composes its own toolbar with the standard toggle last. While a
77
+ // panel is collapsed, CollapsiblePanelRelocated carries that toggle: the
78
+ // rail's leads the main toolbar, the drawer's floats in its container's
79
+ // bottom-end corner. wireSidebar hands focus between the two positions.
80
+ const railToolbar: CollapsiblePanelToolbar = {
81
+ label: 'Workspace navigation',
82
+ title: <ToolbarText text="Atlas" />,
83
+ toggle: { action: RAIL_ACTION, name: 'navigation' },
84
+ };
85
+ const drawerToolbar: CollapsiblePanelToolbar = {
86
+ label: 'Activity',
87
+ title: <ToolbarText text="Activity" size="small" />,
88
+ toggle: { action: DRAWER_ACTION, name: 'activity' },
89
+ };
94
90
 
95
91
  const rail = () => (
96
92
  <CollapsiblePanel
@@ -99,23 +95,9 @@ export const createRecipe: RecipeFactory = (announce) => {
99
95
  size={232}
100
96
  collapsed={railCollapsed.value}
101
97
  label="Workspace navigation"
98
+ toolbar={railToolbar}
102
99
  >
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
- >
100
+ <nav aria-label="Workspace">
119
101
  <section>
120
102
  <ListHeader label="Workspace" />
121
103
  <ListItem
@@ -141,15 +123,10 @@ export const createRecipe: RecipeFactory = (announce) => {
141
123
  selected={selected.value === 'shared'}
142
124
  />
143
125
  </section>
144
- </Pane>
126
+ </nav>
145
127
  </CollapsiblePanel>
146
128
  );
147
129
 
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 is the drawer's own
151
- // `restoreControl`, which the panel floats in its container's bottom-end
152
- // corner. wireSidebar hands focus between them.
153
130
  const main = () => (
154
131
  <Pane
155
132
  element="main"
@@ -159,11 +136,12 @@ export const createRecipe: RecipeFactory = (announce) => {
159
136
  dividerSides=""
160
137
  leading={
161
138
  <>
162
- {railCollapsed.value ? (
163
- <ToolbarControlGroup appearance="borderless" single>
164
- {railToggle()}
165
- </ToolbarControlGroup>
166
- ) : null}
139
+ <CollapsiblePanelRelocated
140
+ panelId="sidebar-rail"
141
+ side="left"
142
+ collapsed={railCollapsed.value}
143
+ toolbar={railToolbar}
144
+ />
167
145
  <ToolbarText
168
146
  text={labels[selected.value] ?? 'Inbox'}
169
147
  size="xlarge"
@@ -195,39 +173,30 @@ export const createRecipe: RecipeFactory = (announce) => {
195
173
  size={168}
196
174
  collapsed={drawerCollapsed.value}
197
175
  label="Activity"
176
+ toolbar={drawerToolbar}
198
177
  restoreControl={
199
178
  <FloatingToolbar label="Activity drawer">
200
- <ToolbarControlGroup single>{drawerToggle()}</ToolbarControlGroup>
179
+ <CollapsiblePanelRelocated
180
+ panelId="sidebar-console"
181
+ side="bottom"
182
+ collapsed={drawerCollapsed.value}
183
+ toolbar={drawerToolbar}
184
+ />
201
185
  </FloatingToolbar>
202
186
  }
203
187
  >
204
- <Pane
205
- header={
206
- <Toolbar
207
- label="Activity"
208
- dividerSides=""
209
- leading={<ToolbarText text="Activity" size="small" />}
210
- trailing={
211
- <ToolbarControlGroup appearance="borderless" single>
212
- {drawerToggle()}
213
- </ToolbarControlGroup>
214
- }
215
- />
216
- }
217
- >
218
- <ValueTable label="Recent activity">
219
- <ValueTableRow
220
- label="Deploy finished"
221
- value="2m ago"
222
- icon={<LucideIcon icon={Bell} name="bell" />}
223
- />
224
- <ValueTableRow
225
- label="Review requested"
226
- value="9m ago"
227
- icon={<LucideIcon icon={Bell} name="bell" />}
228
- />
229
- </ValueTable>
230
- </Pane>
188
+ <ValueTable label="Recent activity">
189
+ <ValueTableRow
190
+ label="Deploy finished"
191
+ value="2m ago"
192
+ icon={<LucideIcon icon={Bell} name="bell" />}
193
+ />
194
+ <ValueTableRow
195
+ label="Review requested"
196
+ value="9m ago"
197
+ icon={<LucideIcon icon={Bell} name="bell" />}
198
+ />
199
+ </ValueTable>
231
200
  </CollapsiblePanel>
232
201
  );
233
202