@kerfjs/ui 5.0.0-beta.17 → 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.
- package/README.md +108 -79
- package/ai/component-catalog-extension.schema.json +79 -0
- package/ai/component-catalog.json +499 -0
- package/ai/component-catalog.schema.json +15 -0
- package/ai/public-api-signatures-v1.md +67 -31
- package/ai/skill.md +58 -49
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/dist/app-tab.d.ts +1 -1
- package/dist/app-tab.js +1 -1
- package/dist/catalog.d.ts +10 -3
- package/dist/catalog.js +279 -90
- package/dist/catalog.js.map +1 -1
- package/dist/chunk-43FUQH35.js +83 -0
- package/dist/chunk-43FUQH35.js.map +1 -0
- package/dist/{chunk-YGN7OMLT.js → chunk-4TRDV6RL.js} +2 -2
- package/dist/chunk-4TRDV6RL.js.map +1 -0
- package/dist/chunk-6ETZKKAE.js +38 -0
- package/dist/chunk-6ETZKKAE.js.map +1 -0
- package/dist/chunk-6WTETUZ6.js +21 -0
- package/dist/chunk-6WTETUZ6.js.map +1 -0
- package/dist/chunk-7L6EHUMV.js +31 -0
- package/dist/chunk-7L6EHUMV.js.map +1 -0
- package/dist/chunk-BBRXX6PF.js +108 -0
- package/dist/chunk-BBRXX6PF.js.map +1 -0
- package/dist/chunk-DPDBVKNA.js +113 -0
- package/dist/chunk-DPDBVKNA.js.map +1 -0
- package/dist/chunk-ELSJX4W7.js +68 -0
- package/dist/chunk-ELSJX4W7.js.map +1 -0
- package/dist/{chunk-6NLRBSVX.js → chunk-ENVTNLYC.js} +125 -68
- package/dist/chunk-ENVTNLYC.js.map +1 -0
- package/dist/{chunk-AHQI52QO.js → chunk-FEY65TBV.js} +6 -2
- package/dist/chunk-FEY65TBV.js.map +1 -0
- package/dist/chunk-JTHUXDN2.js +58 -0
- package/dist/chunk-JTHUXDN2.js.map +1 -0
- package/dist/chunk-LPOMRC4C.js +25 -0
- package/dist/chunk-LPOMRC4C.js.map +1 -0
- package/dist/chunk-NENBUYX5.js +79 -0
- package/dist/chunk-NENBUYX5.js.map +1 -0
- package/dist/{chunk-XXMOARTH.js → chunk-NVWXKU37.js} +57 -16
- package/dist/chunk-NVWXKU37.js.map +1 -0
- package/dist/chunk-PIDTEQ3U.js +105 -0
- package/dist/chunk-PIDTEQ3U.js.map +1 -0
- package/dist/chunk-PX3YSS5C.js +32 -0
- package/dist/chunk-PX3YSS5C.js.map +1 -0
- package/dist/chunk-PZQVSDLH.js +61 -0
- package/dist/chunk-PZQVSDLH.js.map +1 -0
- package/dist/chunk-QIRXYKCE.js +113 -0
- package/dist/chunk-QIRXYKCE.js.map +1 -0
- package/dist/chunk-QO55FN2Y.js +20 -0
- package/dist/chunk-QO55FN2Y.js.map +1 -0
- package/dist/chunk-QQFJMRIB.js +45 -0
- package/dist/chunk-QQFJMRIB.js.map +1 -0
- package/dist/chunk-S3HKPI7I.js +218 -0
- package/dist/chunk-S3HKPI7I.js.map +1 -0
- package/dist/{chunk-WXMGNX2Y.js → chunk-S3PCZ7NV.js} +2 -2
- package/dist/chunk-S3PCZ7NV.js.map +1 -0
- package/dist/{chunk-7BR3DO2A.js → chunk-SRSJO5QE.js} +13 -4
- package/dist/chunk-SRSJO5QE.js.map +1 -0
- package/dist/chunk-U5IFGQCD.js +117 -0
- package/dist/chunk-U5IFGQCD.js.map +1 -0
- package/dist/{chunk-56RJXPHG.js → chunk-Y6LYKFXY.js} +1 -1
- package/dist/{chunk-56RJXPHG.js.map → chunk-Y6LYKFXY.js.map} +1 -1
- package/dist/chunk-ZK2G3VR5.js +35 -0
- package/dist/chunk-ZK2G3VR5.js.map +1 -0
- package/dist/chunk-ZVI5YST2.js +41 -0
- package/dist/chunk-ZVI5YST2.js.map +1 -0
- package/dist/collapsible-panel.d.ts +2 -2
- package/dist/collapsible-panel.js +47 -6
- package/dist/collapsible-panel.js.map +1 -1
- package/dist/device-class.js +33 -5
- package/dist/device-class.js.map +1 -1
- package/dist/disclosure-arrow.js +1 -1
- package/dist/empty-state.d.ts +1 -1
- package/dist/empty-state.js +1 -1
- package/dist/floating-toolbar.d.ts +1 -1
- package/dist/floating-toolbar.js +1 -1
- package/dist/index.js +25 -25
- package/dist/list-action-row.d.ts +1 -1
- package/dist/list-action-row.js +1 -1
- package/dist/list-header.d.ts +1 -1
- package/dist/list-header.js +1 -1
- package/dist/list-inset-control.d.ts +1 -1
- package/dist/list-inset-control.js +1 -1
- package/dist/list-inset-text.d.ts +1 -1
- package/dist/list-inset-text.js +1 -1
- package/dist/list-item.d.ts +1 -1
- package/dist/list-item.js +1 -1
- package/dist/loading-spinner.js +1 -1
- package/dist/nav-stack.d.ts +1 -1
- package/dist/nav-stack.js +1 -1
- package/dist/panel-header.d.ts +1 -1
- package/dist/panel-header.js +1 -1
- package/dist/resizable-region.d.ts +1 -1
- package/dist/resizable-region.js +1 -1
- package/dist/segmented-control.js +1 -1
- package/dist/select.d.ts +1 -1
- package/dist/select.js +1 -1
- package/dist/skeleton.d.ts +1 -1
- package/dist/skeleton.js +1 -1
- package/dist/split-view.d.ts +1 -1
- package/dist/split-view.js +70 -10
- package/dist/split-view.js.map +1 -1
- package/dist/state-banner.d.ts +1 -1
- package/dist/state-banner.js +1 -1
- package/dist/styles/catalog.css +26 -0
- package/dist/tab-bar.d.ts +1 -1
- package/dist/tab-bar.js +1 -1
- package/dist/tab-scaffold.d.ts +1 -1
- package/dist/tab-scaffold.js +43 -8
- package/dist/tab-scaffold.js.map +1 -1
- package/dist/token-search-field.js +1 -1
- package/dist/toolbar-control-group.d.ts +1 -1
- package/dist/toolbar-control-group.js +1 -1
- package/dist/toolbar-text.js +1 -1
- package/dist/toolbar.d.ts +1 -1
- package/dist/toolbar.js +1 -1
- package/dist/value-table.d.ts +2 -2
- package/dist/value-table.js +1 -1
- package/dist/wire-catalog.d.ts +31 -2
- package/dist/wire-catalog.js +203 -8
- package/dist/wire-catalog.js.map +1 -1
- package/dist/wire-nav-stack.js +22 -7
- package/dist/wire-nav-stack.js.map +1 -1
- package/dist/wire-resizable-regions.d.ts +1 -1
- package/dist/wire-resizable-regions.js +1 -1
- package/dist/wire-sidebar.js +52 -41
- package/dist/wire-sidebar.js.map +1 -1
- package/dist/wire-tab-bars.js +1 -1
- package/dist/wire-tab-scaffold.js +9 -4
- package/dist/wire-tab-scaffold.js.map +1 -1
- package/dist/wire-token-search-fields.d.ts +1 -1
- package/dist/wire-token-search-fields.js +1 -1
- package/dist/workbench.d.ts +1 -1
- package/dist/workbench.js +56 -9
- package/dist/workbench.js.map +1 -1
- package/docs/app-layouts.md +7 -7
- package/docs/catalog.md +108 -24
- package/docs/collapsible-panel.md +38 -11
- package/docs/component-contract.md +30 -2
- package/docs/component-selection.md +71 -63
- package/docs/design/templates.md +1 -1
- package/docs/design-philosophy.md +5 -5
- package/docs/device-class.md +8 -8
- package/docs/examples/command-palette-adapter.tsx +67 -17
- package/docs/examples/component-catalog-extension.json +45 -0
- package/docs/layout.md +19 -19
- package/docs/nav-stack.md +15 -6
- package/docs/recipes.md +4 -4
- package/docs/split-view.md +3 -3
- package/docs/tab-scaffold.md +16 -7
- package/docs/webawesome-theme.md +8 -8
- package/docs/workbench.md +18 -5
- package/llms.txt +20 -14
- package/package.json +6 -4
- package/ux-demo/recipes/app-shell.tsx +206 -10
- package/ux-demo/recipes/collapsible-sidebar.tsx +167 -44
- package/ux-demo/recipes/compact-toolbar.tsx +114 -12
- package/ux-demo/recipes/composer-form.tsx +106 -18
- package/ux-demo/recipes/list-detail-dialog.tsx +144 -8
- package/ux-demo/recipes/list-workspace-states.tsx +165 -8
- package/ux-demo/recipes/loaders.ts +5 -2
- package/ux-demo/recipes/loading-inspector.tsx +124 -41
- package/ux-demo/recipes/mount-recipe.ts +27 -6
- package/ux-demo/recipes/navigation-sidebar.tsx +120 -9
- package/ux-demo/recipes/navigation-stack.tsx +61 -19
- package/ux-demo/recipes/types.ts +3 -1
- package/ux-demo/recipes/workspace-header.tsx +57 -2
- package/dist/chunk-2Z5TWHKQ.js +0 -34
- package/dist/chunk-2Z5TWHKQ.js.map +0 -1
- package/dist/chunk-37T3VNIZ.js +0 -19
- package/dist/chunk-37T3VNIZ.js.map +0 -1
- package/dist/chunk-55DATV3A.js +0 -10
- package/dist/chunk-55DATV3A.js.map +0 -1
- package/dist/chunk-5ATCDSQS.js +0 -42
- package/dist/chunk-5ATCDSQS.js.map +0 -1
- package/dist/chunk-5E2HVJ33.js +0 -10
- package/dist/chunk-5E2HVJ33.js.map +0 -1
- package/dist/chunk-6NLRBSVX.js.map +0 -1
- package/dist/chunk-7BR3DO2A.js.map +0 -1
- package/dist/chunk-7JB7VPRI.js +0 -34
- package/dist/chunk-7JB7VPRI.js.map +0 -1
- package/dist/chunk-7LDCTU7D.js +0 -134
- package/dist/chunk-7LDCTU7D.js.map +0 -1
- package/dist/chunk-AHQI52QO.js.map +0 -1
- package/dist/chunk-AQNKJRFH.js +0 -79
- package/dist/chunk-AQNKJRFH.js.map +0 -1
- package/dist/chunk-AVZHJN6C.js +0 -14
- package/dist/chunk-AVZHJN6C.js.map +0 -1
- package/dist/chunk-B24C4A6T.js +0 -22
- package/dist/chunk-B24C4A6T.js.map +0 -1
- package/dist/chunk-CH4SQ6H5.js +0 -39
- package/dist/chunk-CH4SQ6H5.js.map +0 -1
- package/dist/chunk-DAFFDO33.js +0 -14
- package/dist/chunk-DAFFDO33.js.map +0 -1
- package/dist/chunk-I7INRJO4.js +0 -17
- package/dist/chunk-I7INRJO4.js.map +0 -1
- package/dist/chunk-L4OKFDJY.js +0 -24
- package/dist/chunk-L4OKFDJY.js.map +0 -1
- package/dist/chunk-M5JRI6MN.js +0 -27
- package/dist/chunk-M5JRI6MN.js.map +0 -1
- package/dist/chunk-RNPGFQ33.js +0 -10
- package/dist/chunk-RNPGFQ33.js.map +0 -1
- package/dist/chunk-VPCTCUED.js +0 -10
- package/dist/chunk-VPCTCUED.js.map +0 -1
- package/dist/chunk-WRTAQ7BZ.js +0 -23
- package/dist/chunk-WRTAQ7BZ.js.map +0 -1
- package/dist/chunk-WVPYVDXY.js +0 -15
- package/dist/chunk-WVPYVDXY.js.map +0 -1
- package/dist/chunk-WXMGNX2Y.js.map +0 -1
- package/dist/chunk-XXMOARTH.js.map +0 -1
- package/dist/chunk-YGN7OMLT.js.map +0 -1
- package/dist/chunk-ZQQ6P4SV.js +0 -20
- package/dist/chunk-ZQQ6P4SV.js.map +0 -1
|
@@ -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 {
|
|
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 {
|
|
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) =>
|
|
42
|
-
|
|
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 = () =>
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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) {
|
|
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
|
-
{
|
|
95
|
-
|
|
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 = () =>
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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(
|
|
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) {
|
|
139
|
+
if (element.getAttribute('name') === 'recipe-sort' && value) {
|
|
140
|
+
sort.value = value;
|
|
141
|
+
announce(`Sort: ${value}`);
|
|
142
|
+
}
|
|
41
143
|
},
|
|
42
144
|
};
|
|
43
145
|
};
|
|
@@ -15,8 +15,13 @@ import type { RecipeFactory } from './types.js';
|
|
|
15
15
|
|
|
16
16
|
type ValueField = HTMLElement & { value: string };
|
|
17
17
|
|
|
18
|
-
const syncControlledFieldValues = (
|
|
19
|
-
|
|
18
|
+
const syncControlledFieldValues = (
|
|
19
|
+
action: HTMLElement,
|
|
20
|
+
values: Record<string, string>,
|
|
21
|
+
) => {
|
|
22
|
+
const form = action.closest<HTMLElement>(
|
|
23
|
+
'[data-recipe="recipe-composer-form"]',
|
|
24
|
+
);
|
|
20
25
|
if (!form) return;
|
|
21
26
|
for (const [name, value] of Object.entries(values)) {
|
|
22
27
|
const field = form.querySelector<ValueField>(`[name="${name}"]`);
|
|
@@ -26,20 +31,93 @@ const syncControlledFieldValues = (action: HTMLElement, values: Record<string, s
|
|
|
26
31
|
|
|
27
32
|
export const createRecipe: RecipeFactory = (announce) => {
|
|
28
33
|
const title = signal('');
|
|
29
|
-
const body = signal(
|
|
34
|
+
const body = signal(
|
|
35
|
+
'The tablet layout now keeps navigation, content, and inspector focus order aligned.',
|
|
36
|
+
);
|
|
30
37
|
const audience = signal('team');
|
|
31
38
|
const status = signal<'idle' | 'error' | 'saved'>('idle');
|
|
32
|
-
const render = () =>
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
const render = () => (
|
|
40
|
+
<form
|
|
41
|
+
class="kui-recipe recipe-form kui-recipe__surface kui-content"
|
|
42
|
+
data-recipe="recipe-composer-form"
|
|
43
|
+
aria-labelledby="recipe-composer-title"
|
|
44
|
+
aria-describedby="recipe-composer-summary"
|
|
45
|
+
noValidate
|
|
46
|
+
>
|
|
47
|
+
<PanelHeader
|
|
48
|
+
title="Publish workspace update"
|
|
49
|
+
titleId="recipe-composer-title"
|
|
50
|
+
summary="Share a concise, actionable update with collaborators."
|
|
51
|
+
summaryId="recipe-composer-summary"
|
|
52
|
+
/>
|
|
53
|
+
{status.value === 'error' && (
|
|
54
|
+
<StateBanner
|
|
55
|
+
title="Add a title before publishing"
|
|
56
|
+
detail="The update body and audience are preserved."
|
|
57
|
+
tone="danger"
|
|
58
|
+
urgency="alert"
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
{status.value === 'saved' && (
|
|
62
|
+
<StateBanner
|
|
63
|
+
title="Update published"
|
|
64
|
+
detail="The team audience can now read it."
|
|
65
|
+
tone="success"
|
|
66
|
+
/>
|
|
67
|
+
)}
|
|
68
|
+
<div class="recipe-form__section recipe-form__fields">
|
|
69
|
+
<wa-input
|
|
70
|
+
name="recipe-title"
|
|
71
|
+
label="Update title"
|
|
72
|
+
hint="Summarize the outcome in one line."
|
|
73
|
+
required
|
|
74
|
+
value={title.value}
|
|
75
|
+
></wa-input>
|
|
76
|
+
<wa-textarea
|
|
77
|
+
name="recipe-body"
|
|
78
|
+
label="Details"
|
|
79
|
+
hint="Include decisions, owners, and the next checkpoint."
|
|
80
|
+
rows="5"
|
|
81
|
+
maxlength="400"
|
|
82
|
+
with-count
|
|
83
|
+
value={body.value}
|
|
84
|
+
></wa-textarea>
|
|
85
|
+
<Select
|
|
86
|
+
name="recipe-audience"
|
|
87
|
+
value={audience.value}
|
|
88
|
+
label="Audience"
|
|
89
|
+
choices={[
|
|
90
|
+
{ value: 'team', label: 'Workspace team' },
|
|
91
|
+
{ value: 'reviewers', label: 'Reviewers' },
|
|
92
|
+
{ value: 'organization', label: 'Entire organization' },
|
|
93
|
+
]}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
<footer class="recipe-form__section recipe-form__footer">
|
|
97
|
+
<div class="recipe-form__actions kui-control-cluster">
|
|
98
|
+
<wa-button
|
|
99
|
+
appearance="outlined"
|
|
100
|
+
data-action="recipe-action"
|
|
101
|
+
data-recipe-command="reset"
|
|
102
|
+
>
|
|
103
|
+
Reset
|
|
104
|
+
</wa-button>
|
|
105
|
+
<wa-button
|
|
106
|
+
variant="brand"
|
|
107
|
+
appearance="accent"
|
|
108
|
+
data-action="recipe-action"
|
|
109
|
+
data-recipe-command="submit"
|
|
110
|
+
>
|
|
111
|
+
Publish update
|
|
112
|
+
</wa-button>
|
|
113
|
+
</div>
|
|
114
|
+
<p class="kui-recipe__ownership">
|
|
115
|
+
The recipe owns field, message, and action rhythm. The app owns
|
|
116
|
+
validation rules, draft persistence, permissions, and transport.
|
|
117
|
+
</p>
|
|
118
|
+
</footer>
|
|
119
|
+
</form>
|
|
120
|
+
);
|
|
43
121
|
return {
|
|
44
122
|
render,
|
|
45
123
|
action(command, element) {
|
|
@@ -47,17 +125,27 @@ export const createRecipe: RecipeFactory = (announce) => {
|
|
|
47
125
|
title.value = '';
|
|
48
126
|
body.value = '';
|
|
49
127
|
status.value = 'idle';
|
|
50
|
-
syncControlledFieldValues(element, {
|
|
128
|
+
syncControlledFieldValues(element, {
|
|
129
|
+
'recipe-title': title.value,
|
|
130
|
+
'recipe-body': body.value,
|
|
131
|
+
});
|
|
51
132
|
announce('Draft reset');
|
|
52
133
|
return;
|
|
53
134
|
}
|
|
54
|
-
if (command === 'submit') {
|
|
135
|
+
if (command === 'submit') {
|
|
136
|
+
status.value = title.value.trim() ? 'saved' : 'error';
|
|
137
|
+
announce(
|
|
138
|
+
status.value === 'saved' ? 'Update published' : 'Title required',
|
|
139
|
+
);
|
|
140
|
+
}
|
|
55
141
|
},
|
|
56
142
|
change(element) {
|
|
57
|
-
const value =
|
|
143
|
+
const value =
|
|
144
|
+
(element as HTMLElement & { value?: string | null }).value ?? '';
|
|
58
145
|
if (element.getAttribute('name') === 'recipe-title') title.value = value;
|
|
59
146
|
if (element.getAttribute('name') === 'recipe-body') body.value = value;
|
|
60
|
-
if (element.getAttribute('name') === 'recipe-audience')
|
|
147
|
+
if (element.getAttribute('name') === 'recipe-audience')
|
|
148
|
+
audience.value = value;
|
|
61
149
|
},
|
|
62
150
|
};
|
|
63
151
|
};
|