@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.
- package/README.md +29 -5
- package/ai/compile-time-contracts-v1.json +5 -2
- package/ai/component-catalog-v2.d.ts +19 -1
- package/ai/component-catalog-v2.json +842 -127
- package/ai/component-catalog-v2.schema.json +33 -1
- package/ai/component-catalog.json +603 -126
- package/ai/component-catalog.schema.json +21 -1
- package/ai/public-api-signatures-v1.md +201 -29
- package/ai/skill.md +12 -9
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/analyzer/index.mjs +105 -35
- package/dist/app-tab.js +1 -1
- package/dist/catalog-resources.d.ts +1 -1
- package/dist/catalog.d.ts +28 -5
- package/dist/catalog.js +66 -25
- package/dist/catalog.js.map +1 -1
- package/dist/{chunk-COUPLRZG.js → chunk-5FZZMIMD.js} +3 -3
- package/dist/{chunk-COUPLRZG.js.map → chunk-5FZZMIMD.js.map} +1 -1
- package/dist/{chunk-DARTMI5Z.js → chunk-5PMGQDG7.js} +2 -2
- package/dist/{chunk-DARTMI5Z.js.map → chunk-5PMGQDG7.js.map} +1 -1
- package/dist/{chunk-V6RZWDII.js → chunk-7GUQYHKA.js} +19 -2
- package/dist/chunk-7GUQYHKA.js.map +1 -0
- package/dist/{chunk-ZNZ2JDBW.js → chunk-7IUSV4XG.js} +1 -1
- package/dist/chunk-7IUSV4XG.js.map +1 -0
- package/dist/{chunk-ELRMBQEC.js → chunk-AEPXGCNF.js} +17 -2
- package/dist/chunk-AEPXGCNF.js.map +1 -0
- package/dist/{chunk-5CE64WGC.js → chunk-D547PH4L.js} +3 -3
- package/dist/{chunk-5CE64WGC.js.map → chunk-D547PH4L.js.map} +1 -1
- package/dist/{chunk-S6ZTTTV7.js → chunk-DVZCMMB3.js} +3 -3
- package/dist/{chunk-S6ZTTTV7.js.map → chunk-DVZCMMB3.js.map} +1 -1
- package/dist/chunk-EKYZCRCX.js +256 -0
- package/dist/chunk-EKYZCRCX.js.map +1 -0
- package/dist/{chunk-3V75EQIP.js → chunk-JM53SMVF.js} +3 -3
- package/dist/{chunk-3V75EQIP.js.map → chunk-JM53SMVF.js.map} +1 -1
- package/dist/{chunk-2WMSE4MK.js → chunk-JMGKM7RK.js} +2 -2
- package/dist/{chunk-2WMSE4MK.js.map → chunk-JMGKM7RK.js.map} +1 -1
- package/dist/{chunk-2YOUUEWR.js → chunk-KX7DP2GO.js} +4 -4
- package/dist/{chunk-2YOUUEWR.js.map → chunk-KX7DP2GO.js.map} +1 -1
- package/dist/{chunk-OL4TC3U2.js → chunk-MO56ZZLG.js} +6 -2
- package/dist/chunk-MO56ZZLG.js.map +1 -0
- package/dist/{chunk-OQI7RCRP.js → chunk-MTM43SJO.js} +7 -1
- package/dist/chunk-MTM43SJO.js.map +1 -0
- package/dist/{chunk-ZSYLBVWG.js → chunk-NMGTHYW3.js} +3 -3
- package/dist/{chunk-ZSYLBVWG.js.map → chunk-NMGTHYW3.js.map} +1 -1
- package/dist/{chunk-AQSKJ6U6.js → chunk-NZXWU4R5.js} +21 -2
- package/dist/chunk-NZXWU4R5.js.map +1 -0
- package/dist/{chunk-DJKWFYAA.js → chunk-O3RCCSQJ.js} +3 -3
- package/dist/{chunk-DJKWFYAA.js.map → chunk-O3RCCSQJ.js.map} +1 -1
- package/dist/{chunk-UMGY5JSP.js → chunk-S4QRSBFI.js} +22 -2
- package/dist/chunk-S4QRSBFI.js.map +1 -0
- package/dist/{chunk-KKNK3JUT.js → chunk-UBH743HV.js} +5 -5
- package/dist/chunk-UBH743HV.js.map +1 -0
- package/dist/{chunk-54HM36IQ.js → chunk-ULCDZTWL.js} +2 -2
- package/dist/{chunk-54HM36IQ.js.map → chunk-ULCDZTWL.js.map} +1 -1
- package/dist/{chunk-SIU2AC5W.js → chunk-UPZ6EEJN.js} +3 -3
- package/dist/{chunk-SIU2AC5W.js.map → chunk-UPZ6EEJN.js.map} +1 -1
- package/dist/collapsible-panel.d.ts +1 -1
- package/dist/collapsible-panel.js.map +1 -1
- package/dist/css-values.d.ts +39 -3
- package/dist/css-values.js +1 -1
- package/dist/grid.d.ts +15 -1
- package/dist/grid.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +19 -19
- package/dist/list-action-row.js +1 -1
- package/dist/list-header.js +1 -1
- package/dist/list-item.js +1 -1
- package/dist/list.d.ts +21 -2
- package/dist/list.js +2 -1
- package/dist/nav-stack.js +1 -1
- package/dist/pane.d.ts +17 -2
- package/dist/pane.js +1 -1
- package/dist/row.d.ts +19 -1
- package/dist/row.js +2 -1
- package/dist/segmented-control.js +1 -1
- package/dist/select.d.ts +13 -3
- package/dist/select.js +1 -1
- package/dist/skeleton.js +1 -1
- package/dist/spacer.js +1 -1
- package/dist/split-view.js +1 -1
- package/dist/state-banner.js +1 -1
- package/dist/styles/catalog/components/catalog-example-stack.css +4 -1
- package/dist/styles/catalog/components/catalog-example.css +145 -0
- package/dist/styles/collapsible-panel.css +132 -4
- package/dist/styles/document.css +23 -0
- package/dist/styles/foundation.css +21 -7
- package/dist/styles/grid.css +10 -0
- package/dist/styles/list-header.css +45 -0
- package/dist/styles/list-inset-control.css +8 -1
- package/dist/styles/list-item.css +6 -0
- package/dist/styles/list.css +11 -0
- package/dist/styles/nav-stack.css +89 -2
- package/dist/styles/pane.css +86 -1
- package/dist/styles/resizable-region.css +93 -5
- package/dist/styles/row.css +10 -0
- package/dist/styles/select.css +39 -4
- package/dist/styles/split-view.css +107 -1
- package/dist/styles/tab-scaffold.css +64 -1
- package/dist/styles/text.css +6 -0
- package/dist/styles/toolbar-control-group.css +90 -11
- package/dist/styles/toolbar-text.css +4 -1
- package/dist/styles/toolbar.css +130 -38
- package/dist/styles/webawesome.css +48 -2
- package/dist/styles/workbench.css +136 -4
- package/dist/text.d.ts +4 -3
- package/dist/text.js +1 -1
- package/dist/toolbar-text.js +1 -1
- package/dist/toolbar.d.ts +24 -3
- package/dist/toolbar.js +1 -1
- package/dist/{types-CzgSrFXP.d.ts → types-CEsieBZi.d.ts} +6 -1
- package/dist/value-table.js +1 -1
- package/dist/wire-resizable-regions.js +1 -1
- package/dist/wire-sidebar.d.ts +18 -3
- package/dist/wire-sidebar.js +95 -14
- package/dist/wire-sidebar.js.map +1 -1
- package/docs/accessibility.md +45 -8
- package/docs/app-layouts.md +73 -0
- package/docs/catalog.md +23 -2
- package/docs/collapsible-panel.md +81 -8
- package/docs/component-contract.md +28 -9
- package/docs/component-integration.md +9 -4
- package/docs/component-selection.md +25 -6
- package/docs/design/templates/list/stack-dark.svg +1 -1
- package/docs/design/templates/list/stack.svg +1 -1
- package/docs/design/templates/list-dark.svg +1 -1
- package/docs/design/templates/list-header/action-dark.svg +1 -1
- package/docs/design/templates/list-header/action.svg +1 -1
- package/docs/design/templates/list-header/count-dark.svg +1 -1
- package/docs/design/templates/list-header/count.svg +1 -1
- package/docs/design/templates/list-header/inline-action-dark.svg +1 -1
- package/docs/design/templates/list-header/inline-action.svg +1 -1
- package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -1
- package/docs/design/templates/list-header/toggle-expanded.svg +1 -1
- package/docs/design/templates/list-header-dark.svg +1 -1
- package/docs/design/templates/list-header.svg +1 -1
- package/docs/design/templates/list.svg +1 -1
- package/docs/design/templates/state-banner/info-badge-dark.svg +1 -1
- package/docs/design/templates/state-banner/info-badge.svg +1 -1
- package/docs/design/templates/state-banner-dark.svg +1 -1
- package/docs/design/templates/state-banner.svg +1 -1
- package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -1
- package/docs/design/templates/toolbar/title-and-actions.svg +1 -1
- package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -1
- package/docs/design/templates/toolbar-control-group/with-text.svg +1 -1
- package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
- package/docs/design/templates/toolbar-control-group.svg +1 -1
- package/docs/design/templates/toolbar-dark.svg +1 -1
- package/docs/design/templates/toolbar-text/default-dark.svg +1 -1
- package/docs/design/templates/toolbar-text/default.svg +1 -1
- package/docs/design/templates/toolbar-text/large-dark.svg +1 -1
- package/docs/design/templates/toolbar-text/large.svg +1 -1
- package/docs/design/templates/toolbar-text/small-dark.svg +1 -1
- package/docs/design/templates/toolbar-text/small.svg +1 -1
- package/docs/design/templates/toolbar-text-dark.svg +1 -1
- package/docs/design/templates/toolbar-text.svg +1 -1
- package/docs/design/templates/toolbar.svg +1 -1
- package/docs/document-baseline.md +31 -0
- package/docs/layout.md +86 -21
- package/docs/nav-stack.md +8 -0
- package/docs/recipes.md +78 -43
- package/docs/split-view.md +8 -0
- package/docs/tab-scaffold.md +4 -2
- package/docs/type-contracts.md +21 -17
- package/docs/ui-analyzer.md +19 -0
- package/docs/ui-evaluator.md +17 -0
- package/docs/ux-demo.md +4 -4
- package/docs/webawesome-theme.md +20 -2
- package/docs/workbench.md +20 -1
- package/evaluator/index.mjs +73 -8
- package/llms.txt +32 -14
- package/package.json +4 -2
- package/ux-demo/recipes/app-shell.tsx +209 -178
- package/ux-demo/recipes/collapsible-sidebar.tsx +188 -159
- package/ux-demo/recipes/compact-toolbar.tsx +48 -35
- package/ux-demo/recipes/composer-form.tsx +85 -79
- package/ux-demo/recipes/list-detail-dialog.tsx +124 -142
- package/ux-demo/recipes/list-workspace-states.tsx +83 -83
- package/ux-demo/recipes/loaders.ts +5 -2
- package/ux-demo/recipes/loading-inspector.tsx +157 -160
- package/ux-demo/recipes/navigation-sidebar.tsx +111 -109
- package/ux-demo/recipes/navigation-stack.tsx +50 -49
- package/ux-demo/recipes/types.ts +14 -0
- package/ux-demo/recipes/workspace-header.tsx +73 -56
- package/dist/chunk-AQSKJ6U6.js.map +0 -1
- package/dist/chunk-ELRMBQEC.js.map +0 -1
- package/dist/chunk-J3EE6XWD.js +0 -115
- package/dist/chunk-J3EE6XWD.js.map +0 -1
- package/dist/chunk-KKNK3JUT.js.map +0 -1
- package/dist/chunk-OL4TC3U2.js.map +0 -1
- package/dist/chunk-OQI7RCRP.js.map +0 -1
- package/dist/chunk-UMGY5JSP.js.map +0 -1
- package/dist/chunk-V6RZWDII.js.map +0 -1
- package/dist/chunk-ZNZ2JDBW.js.map +0 -1
- package/ux-demo/recipes/app-shell.css +0 -87
- package/ux-demo/recipes/collapsible-sidebar.css +0 -83
- package/ux-demo/recipes/compact-toolbar.css +0 -35
- package/ux-demo/recipes/composer-form.css +0 -19
- package/ux-demo/recipes/list-detail-dialog.css +0 -42
- package/ux-demo/recipes/list-workspace-states.css +0 -30
- package/ux-demo/recipes/loading-inspector.css +0 -7
- package/ux-demo/recipes/navigation-sidebar.css +0 -12
- package/ux-demo/recipes/navigation-stack.css +0 -12
- package/ux-demo/recipes/recipe-root.css +0 -67
- package/ux-demo/recipes/recipe-root.tsx +0 -87
- package/ux-demo/recipes/workspace-header.css +0 -7
|
@@ -4,17 +4,29 @@ import '@kerfjs/ui/webawesome.css';
|
|
|
4
4
|
import '@awesome.me/webawesome/dist/components/button/button.js';
|
|
5
5
|
import '@awesome.me/webawesome/dist/components/input/input.js';
|
|
6
6
|
import '@awesome.me/webawesome/dist/components/textarea/textarea.js';
|
|
7
|
-
import './composer-form.css';
|
|
8
7
|
|
|
8
|
+
import { List } from '@kerfjs/ui/list';
|
|
9
9
|
import { ListInsetText } from '@kerfjs/ui/list-inset-text';
|
|
10
|
+
import { Row } from '@kerfjs/ui/row';
|
|
10
11
|
import { Select } from '@kerfjs/ui/select';
|
|
11
12
|
import { StateBanner } from '@kerfjs/ui/state-banner';
|
|
13
|
+
import { Text } from '@kerfjs/ui/text';
|
|
12
14
|
import { Toolbar } from '@kerfjs/ui/toolbar';
|
|
13
15
|
import { ToolbarText } from '@kerfjs/ui/toolbar-text';
|
|
14
16
|
import { signal } from 'kerfjs';
|
|
15
17
|
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
+
import type { RecipeFactory, RecipePresentation } from './types.js';
|
|
19
|
+
|
|
20
|
+
export const presentation: RecipePresentation = {
|
|
21
|
+
viewport: {
|
|
22
|
+
width: 'medium',
|
|
23
|
+
frame: 'solid',
|
|
24
|
+
surface: 'default',
|
|
25
|
+
overflow: 'hidden',
|
|
26
|
+
shadow: true,
|
|
27
|
+
},
|
|
28
|
+
note: 'The recipe owns field, message, and action rhythm. The app owns validation rules, draft persistence, permissions, and transport.',
|
|
29
|
+
};
|
|
18
30
|
|
|
19
31
|
type ValueField = HTMLElement & { value: string };
|
|
20
32
|
|
|
@@ -40,78 +52,76 @@ export const createRecipe: RecipeFactory = (announce) => {
|
|
|
40
52
|
const audience = signal('team');
|
|
41
53
|
const status = signal<'idle' | 'error' | 'saved'>('idle');
|
|
42
54
|
const render = () => (
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
contentLayout
|
|
48
|
-
ariaLabelledBy="recipe-composer-title"
|
|
49
|
-
ariaDescribedBy="recipe-composer-summary"
|
|
55
|
+
<form
|
|
56
|
+
data-recipe="recipe-composer-form"
|
|
57
|
+
aria-labelledby="recipe-composer-title"
|
|
58
|
+
aria-describedby="recipe-composer-summary"
|
|
50
59
|
noValidate
|
|
51
60
|
>
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
<List gap="l" controlInsets="tb">
|
|
62
|
+
{/* The title and its supporting line are one heading unit. */}
|
|
63
|
+
<List>
|
|
64
|
+
<Toolbar
|
|
65
|
+
label="Publish workspace update"
|
|
66
|
+
dividerSides=""
|
|
67
|
+
leading={
|
|
68
|
+
<ToolbarText
|
|
69
|
+
text="Publish workspace update"
|
|
70
|
+
size="xlarge"
|
|
71
|
+
id="recipe-composer-title"
|
|
72
|
+
/>
|
|
73
|
+
}
|
|
74
|
+
/>
|
|
75
|
+
<ListInsetText sides="rl">
|
|
76
|
+
<Text variant="span" tone="quiet" id="recipe-composer-summary">
|
|
77
|
+
Share a concise, actionable update with collaborators.
|
|
78
|
+
</Text>
|
|
79
|
+
</ListInsetText>
|
|
80
|
+
</List>
|
|
81
|
+
{status.value === 'error' && (
|
|
82
|
+
<StateBanner
|
|
83
|
+
title="Add a title before publishing"
|
|
84
|
+
detail="The update body and audience are preserved."
|
|
85
|
+
tone="danger"
|
|
86
|
+
urgency="alert"
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
89
|
+
{status.value === 'saved' && (
|
|
90
|
+
<StateBanner
|
|
91
|
+
title="Update published"
|
|
92
|
+
detail="The team audience can now read it."
|
|
93
|
+
tone="success"
|
|
94
|
+
/>
|
|
95
|
+
)}
|
|
96
|
+
<List gap="xs" controlInsets="rl">
|
|
97
|
+
<wa-input
|
|
98
|
+
name="recipe-title"
|
|
99
|
+
label="Update title"
|
|
100
|
+
hint="Summarize the outcome in one line."
|
|
101
|
+
required
|
|
102
|
+
value={title.value}
|
|
103
|
+
></wa-input>
|
|
104
|
+
<wa-textarea
|
|
105
|
+
name="recipe-body"
|
|
106
|
+
label="Details"
|
|
107
|
+
hint="Include decisions, owners, and the next checkpoint."
|
|
108
|
+
rows="5"
|
|
109
|
+
maxlength="400"
|
|
110
|
+
with-count
|
|
111
|
+
value={body.value}
|
|
112
|
+
></wa-textarea>
|
|
113
|
+
<Select<string>
|
|
114
|
+
name="recipe-audience"
|
|
115
|
+
value={audience.value}
|
|
116
|
+
label="Audience"
|
|
117
|
+
choices={[
|
|
118
|
+
{ value: 'team', label: 'Workspace team' },
|
|
119
|
+
{ value: 'reviewers', label: 'Reviewers' },
|
|
120
|
+
{ value: 'organization', label: 'Entire organization' },
|
|
121
|
+
]}
|
|
60
122
|
/>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<div class="recipe-form__heading-summary">
|
|
64
|
-
<ListInsetText sides="rl">
|
|
65
|
-
<span id="recipe-composer-summary">
|
|
66
|
-
Share a concise, actionable update with collaborators.
|
|
67
|
-
</span>
|
|
68
|
-
</ListInsetText>
|
|
69
|
-
</div>
|
|
70
|
-
{status.value === 'error' && (
|
|
71
|
-
<StateBanner
|
|
72
|
-
title="Add a title before publishing"
|
|
73
|
-
detail="The update body and audience are preserved."
|
|
74
|
-
tone="danger"
|
|
75
|
-
urgency="alert"
|
|
76
|
-
/>
|
|
77
|
-
)}
|
|
78
|
-
{status.value === 'saved' && (
|
|
79
|
-
<StateBanner
|
|
80
|
-
title="Update published"
|
|
81
|
-
detail="The team audience can now read it."
|
|
82
|
-
tone="success"
|
|
83
|
-
/>
|
|
84
|
-
)}
|
|
85
|
-
<div class="recipe-form__section recipe-form__fields">
|
|
86
|
-
<wa-input
|
|
87
|
-
name="recipe-title"
|
|
88
|
-
label="Update title"
|
|
89
|
-
hint="Summarize the outcome in one line."
|
|
90
|
-
required
|
|
91
|
-
value={title.value}
|
|
92
|
-
></wa-input>
|
|
93
|
-
<wa-textarea
|
|
94
|
-
name="recipe-body"
|
|
95
|
-
label="Details"
|
|
96
|
-
hint="Include decisions, owners, and the next checkpoint."
|
|
97
|
-
rows="5"
|
|
98
|
-
maxlength="400"
|
|
99
|
-
with-count
|
|
100
|
-
value={body.value}
|
|
101
|
-
></wa-textarea>
|
|
102
|
-
<Select<string>
|
|
103
|
-
name="recipe-audience"
|
|
104
|
-
value={audience.value}
|
|
105
|
-
label="Audience"
|
|
106
|
-
choices={[
|
|
107
|
-
{ value: 'team', label: 'Workspace team' },
|
|
108
|
-
{ value: 'reviewers', label: 'Reviewers' },
|
|
109
|
-
{ value: 'organization', label: 'Entire organization' },
|
|
110
|
-
]}
|
|
111
|
-
/>
|
|
112
|
-
</div>
|
|
113
|
-
<footer class="recipe-form__section recipe-form__footer">
|
|
114
|
-
<div class="recipe-form__actions kui-control-cluster">
|
|
123
|
+
</List>
|
|
124
|
+
<Row gap="xs" wrap controlInsets="rl">
|
|
115
125
|
<wa-button
|
|
116
126
|
appearance="outlined"
|
|
117
127
|
data-action="recipe-action"
|
|
@@ -127,13 +137,9 @@ export const createRecipe: RecipeFactory = (announce) => {
|
|
|
127
137
|
>
|
|
128
138
|
Publish update
|
|
129
139
|
</wa-button>
|
|
130
|
-
</
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
validation rules, draft persistence, permissions, and transport.
|
|
134
|
-
</RecipeOwnershipNote>
|
|
135
|
-
</footer>
|
|
136
|
-
</RecipeRoot>
|
|
140
|
+
</Row>
|
|
141
|
+
</List>
|
|
142
|
+
</form>
|
|
137
143
|
);
|
|
138
144
|
return {
|
|
139
145
|
render,
|
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
import '@kerfjs/ui/layout.css';
|
|
2
|
+
import '@kerfjs/ui/nav-stack.css';
|
|
3
|
+
import '@kerfjs/ui/split-view.css';
|
|
2
4
|
import '@kerfjs/ui/webawesome.css';
|
|
3
5
|
import '@awesome.me/webawesome/dist/components/button/button.js';
|
|
4
6
|
import '@awesome.me/webawesome/dist/components/dialog/dialog.js';
|
|
5
|
-
import './list-detail-dialog.css';
|
|
6
7
|
|
|
8
|
+
import { deviceClass } from '@kerfjs/ui/device-class';
|
|
9
|
+
import { List } from '@kerfjs/ui/list';
|
|
7
10
|
import { ListHeader } from '@kerfjs/ui/list-header';
|
|
8
11
|
import { ListItem } from '@kerfjs/ui/list-item';
|
|
9
12
|
import { LucideIcon } from '@kerfjs/ui/lucide-icon';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { ToolbarText } from '@kerfjs/ui/toolbar-text';
|
|
13
|
+
import { Row } from '@kerfjs/ui/row';
|
|
14
|
+
import { SplitView } from '@kerfjs/ui/split-view';
|
|
15
|
+
import { DialogSurface } from '@kerfjs/ui/surface-scaffold';
|
|
14
16
|
import { ValueTable, ValueTableRow } from '@kerfjs/ui/value-table';
|
|
15
17
|
import { signal } from 'kerfjs';
|
|
16
|
-
import { FileText } from 'lucide';
|
|
18
|
+
import { ChevronRight, FileText } from 'lucide';
|
|
17
19
|
|
|
18
|
-
import {
|
|
19
|
-
RecipeHeadingSummary,
|
|
20
|
-
RecipeOwnershipNote,
|
|
21
|
-
RecipeRoot,
|
|
22
|
-
} from './recipe-root.js';
|
|
23
|
-
import type { RecipeFactory } from './types.js';
|
|
20
|
+
import type { RecipeFactory, RecipePresentation } from './types.js';
|
|
24
21
|
|
|
25
22
|
const records = {
|
|
26
23
|
alpha: {
|
|
@@ -43,144 +40,132 @@ const records = {
|
|
|
43
40
|
},
|
|
44
41
|
} as const;
|
|
45
42
|
|
|
43
|
+
type RecordId = keyof typeof records;
|
|
44
|
+
|
|
46
45
|
interface RecipeDialogElement extends HTMLElement {
|
|
47
46
|
open: boolean;
|
|
48
47
|
}
|
|
49
48
|
|
|
49
|
+
export const presentation: RecipePresentation = {
|
|
50
|
+
viewport: { layout: 'grid', width: 'full' },
|
|
51
|
+
note: 'Web Awesome owns modal focus, Escape, and the close control; DialogSurface owns dialog geometry; SplitView owns the list-detail panes and their compact drill-down. The app owns open state, selection, and record actions.',
|
|
52
|
+
};
|
|
53
|
+
|
|
50
54
|
export const createRecipe: RecipeFactory = (announce) => {
|
|
51
|
-
const selected = signal<
|
|
55
|
+
const selected = signal<RecordId>('alpha');
|
|
56
|
+
// Compact devices drill from the list into the detail; this flag is the
|
|
57
|
+
// pushed detail view. Roomy devices show both panes side by side.
|
|
58
|
+
const detailOpen = signal(false);
|
|
52
59
|
const open = { value: false };
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
<div class="recipe-list-detail">
|
|
78
|
-
<div class="recipe-list-detail__list">
|
|
79
|
-
<Pane contentElement="nav" contentLabel="Projects">
|
|
80
|
-
<ListHeader label="Recent projects" />
|
|
81
|
-
<section>
|
|
82
|
-
{Object.entries(records).map(([id, record]) => (
|
|
83
|
-
<ListItem
|
|
84
|
-
action="recipe-action"
|
|
85
|
-
itemId={id}
|
|
86
|
-
label={record.name}
|
|
87
|
-
selected={selected.value === id}
|
|
88
|
-
multiline
|
|
89
|
-
/>
|
|
90
|
-
))}
|
|
91
|
-
</section>
|
|
92
|
-
</Pane>
|
|
93
|
-
</div>
|
|
94
|
-
<div class="recipe-list-detail__detail">
|
|
95
|
-
<Pane
|
|
96
|
-
element="section"
|
|
97
|
-
header={
|
|
98
|
-
<>
|
|
99
|
-
<Toolbar
|
|
100
|
-
label="Project details"
|
|
101
|
-
dividerSides=""
|
|
102
|
-
leading={
|
|
103
|
-
<>
|
|
104
|
-
<ToolbarControlGroup appearance="borderless" single>
|
|
105
|
-
<LucideIcon icon={FileText} name="file-text" />
|
|
106
|
-
</ToolbarControlGroup>
|
|
107
|
-
<ToolbarText
|
|
108
|
-
text="Project details"
|
|
109
|
-
size="xlarge"
|
|
110
|
-
id="recipe-dialog-title"
|
|
111
|
-
/>
|
|
112
|
-
</>
|
|
113
|
-
}
|
|
114
|
-
trailing={
|
|
115
|
-
<ToolbarControlGroup appearance="borderless" single>
|
|
116
|
-
<wa-button
|
|
117
|
-
appearance="plain"
|
|
118
|
-
data-action="recipe-action"
|
|
119
|
-
data-recipe-command="close"
|
|
120
|
-
>
|
|
121
|
-
Close
|
|
122
|
-
</wa-button>
|
|
123
|
-
</ToolbarControlGroup>
|
|
124
|
-
}
|
|
125
|
-
/>
|
|
126
|
-
<RecipeHeadingSummary id="recipe-dialog-summary">
|
|
127
|
-
Compare delivery state without leaving the workspace.
|
|
128
|
-
</RecipeHeadingSummary>
|
|
129
|
-
</>
|
|
130
|
-
}
|
|
131
|
-
>
|
|
132
|
-
<div class="recipe-list-detail__body">
|
|
133
|
-
<div aria-live="polite">
|
|
134
|
-
<h3 class="recipe-list-detail__title kui-content-item">
|
|
135
|
-
{records[selected.value].name}
|
|
136
|
-
</h3>
|
|
137
|
-
<ValueTable label="Project details">
|
|
138
|
-
<ValueTableRow
|
|
139
|
-
label="Owner"
|
|
140
|
-
value={records[selected.value].owner}
|
|
141
|
-
/>
|
|
142
|
-
<ValueTableRow
|
|
143
|
-
label="Status"
|
|
144
|
-
value={records[selected.value].state}
|
|
145
|
-
/>
|
|
146
|
-
<ValueTableRow
|
|
147
|
-
label="Updated"
|
|
148
|
-
value={records[selected.value].updated}
|
|
149
|
-
/>
|
|
150
|
-
</ValueTable>
|
|
151
|
-
<div class="recipe-list-detail__actions kui-control-cluster">
|
|
152
|
-
<wa-button
|
|
153
|
-
appearance="outlined"
|
|
154
|
-
data-action="recipe-action"
|
|
155
|
-
data-recipe-command="archive"
|
|
156
|
-
>
|
|
157
|
-
Archive
|
|
158
|
-
</wa-button>
|
|
159
|
-
<wa-button
|
|
160
|
-
variant="brand"
|
|
161
|
-
appearance="accent"
|
|
162
|
-
data-action="recipe-action"
|
|
163
|
-
data-recipe-command="open-record"
|
|
164
|
-
>
|
|
165
|
-
Open project
|
|
166
|
-
</wa-button>
|
|
167
|
-
</div>
|
|
168
|
-
</div>
|
|
169
|
-
</div>
|
|
170
|
-
</Pane>
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
</Pane>
|
|
174
|
-
</wa-dialog>
|
|
175
|
-
</div>
|
|
176
|
-
</RecipeRoot>
|
|
60
|
+
const device = deviceClass();
|
|
61
|
+
|
|
62
|
+
const projectList = (compact: boolean) => (
|
|
63
|
+
<List>
|
|
64
|
+
{compact ? null : <ListHeader label="Recent projects" />}
|
|
65
|
+
<section aria-label="Projects">
|
|
66
|
+
{Object.entries(records).map(([id, record]) => (
|
|
67
|
+
<ListItem
|
|
68
|
+
action="recipe-action"
|
|
69
|
+
itemId={id}
|
|
70
|
+
label={record.name}
|
|
71
|
+
description={`${record.owner} · ${record.state}`}
|
|
72
|
+
icon={<LucideIcon icon={FileText} name="file-text" />}
|
|
73
|
+
trailing={
|
|
74
|
+
compact ? (
|
|
75
|
+
<LucideIcon icon={ChevronRight} name="chevron-right" />
|
|
76
|
+
) : undefined
|
|
77
|
+
}
|
|
78
|
+
selected={!compact && selected.value === id}
|
|
79
|
+
multiline
|
|
80
|
+
/>
|
|
81
|
+
))}
|
|
82
|
+
</section>
|
|
83
|
+
</List>
|
|
177
84
|
);
|
|
85
|
+
|
|
86
|
+
const projectDetail = (compact: boolean) => {
|
|
87
|
+
const record = records[selected.value];
|
|
88
|
+
return (
|
|
89
|
+
<List>
|
|
90
|
+
{compact ? null : <ListHeader label={record.name} />}
|
|
91
|
+
<ValueTable label="Project details">
|
|
92
|
+
<ValueTableRow label="Owner" value={record.owner} />
|
|
93
|
+
<ValueTableRow label="Status" value={record.state} />
|
|
94
|
+
<ValueTableRow label="Updated" value={record.updated} />
|
|
95
|
+
</ValueTable>
|
|
96
|
+
</List>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const render = () => {
|
|
101
|
+
const compact = device.value.compact;
|
|
102
|
+
return (
|
|
103
|
+
<section
|
|
104
|
+
data-recipe="recipe-list-detail-dialog"
|
|
105
|
+
aria-label="Project details dialog"
|
|
106
|
+
>
|
|
107
|
+
<Row hAlign="center">
|
|
108
|
+
<DialogSurface
|
|
109
|
+
size="large"
|
|
110
|
+
presentation={compact ? 'fullscreen' : 'modal'}
|
|
111
|
+
bodyInset="none"
|
|
112
|
+
footerInset="comfortable"
|
|
113
|
+
>
|
|
114
|
+
<wa-button
|
|
115
|
+
variant="brand"
|
|
116
|
+
appearance="accent"
|
|
117
|
+
data-action="recipe-action"
|
|
118
|
+
data-recipe-command="open"
|
|
119
|
+
>
|
|
120
|
+
Open project details
|
|
121
|
+
</wa-button>
|
|
122
|
+
<wa-dialog label="Project details" open={open.value} with-footer>
|
|
123
|
+
<SplitView
|
|
124
|
+
id="recipe-projects"
|
|
125
|
+
label="Projects"
|
|
126
|
+
compact={compact}
|
|
127
|
+
detailActive={compact && detailOpen.value}
|
|
128
|
+
listTitle="Recent projects"
|
|
129
|
+
detailTitle={records[selected.value].name}
|
|
130
|
+
backLabel="Back to projects"
|
|
131
|
+
list={projectList(compact)}
|
|
132
|
+
detail={projectDetail(compact)}
|
|
133
|
+
/>
|
|
134
|
+
<wa-button
|
|
135
|
+
slot="footer"
|
|
136
|
+
appearance="outlined"
|
|
137
|
+
data-action="recipe-action"
|
|
138
|
+
data-recipe-command="archive"
|
|
139
|
+
>
|
|
140
|
+
Archive
|
|
141
|
+
</wa-button>
|
|
142
|
+
<wa-button
|
|
143
|
+
slot="footer"
|
|
144
|
+
variant="brand"
|
|
145
|
+
appearance="accent"
|
|
146
|
+
data-action="recipe-action"
|
|
147
|
+
data-recipe-command="open-record"
|
|
148
|
+
>
|
|
149
|
+
Open project
|
|
150
|
+
</wa-button>
|
|
151
|
+
</wa-dialog>
|
|
152
|
+
</DialogSurface>
|
|
153
|
+
</Row>
|
|
154
|
+
</section>
|
|
155
|
+
);
|
|
156
|
+
};
|
|
178
157
|
return {
|
|
179
158
|
render,
|
|
180
159
|
action(command, element) {
|
|
160
|
+
if (command === 'nav-back') {
|
|
161
|
+
detailOpen.value = false;
|
|
162
|
+
announce('Back to projects');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
181
165
|
const id = element.dataset.itemId;
|
|
182
166
|
if (id && id in records) {
|
|
183
|
-
selected.value = id as
|
|
167
|
+
selected.value = id as RecordId;
|
|
168
|
+
detailOpen.value = true;
|
|
184
169
|
announce(`Selected ${records[selected.value].name}`);
|
|
185
170
|
return;
|
|
186
171
|
}
|
|
@@ -193,14 +178,11 @@ export const createRecipe: RecipeFactory = (announce) => {
|
|
|
193
178
|
dialog.open = true;
|
|
194
179
|
return;
|
|
195
180
|
}
|
|
196
|
-
if (command === 'close' && dialog) {
|
|
197
|
-
dialog.open = false;
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
181
|
announce(`${command} requested`);
|
|
201
182
|
},
|
|
202
183
|
afterHide() {
|
|
203
184
|
open.value = false;
|
|
185
|
+
detailOpen.value = false;
|
|
204
186
|
},
|
|
205
187
|
};
|
|
206
188
|
};
|