@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
@@ -4,17 +4,30 @@ 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 { Spacer } from '@kerfjs/ui';
9
+ import { List } from '@kerfjs/ui/list';
9
10
  import { ListInsetText } from '@kerfjs/ui/list-inset-text';
11
+ import { Row } from '@kerfjs/ui/row';
10
12
  import { Select } from '@kerfjs/ui/select';
11
13
  import { StateBanner } from '@kerfjs/ui/state-banner';
14
+ import { Text } from '@kerfjs/ui/text';
12
15
  import { Toolbar } from '@kerfjs/ui/toolbar';
13
16
  import { ToolbarText } from '@kerfjs/ui/toolbar-text';
14
17
  import { signal } from 'kerfjs';
15
18
 
16
- import { RecipeOwnershipNote, RecipeRoot } from './recipe-root.js';
17
- import type { RecipeFactory } from './types.js';
19
+ import type { RecipeFactory, RecipePresentation } from './types.js';
20
+
21
+ export const presentation: RecipePresentation = {
22
+ viewport: {
23
+ width: 'medium',
24
+ frame: 'solid',
25
+ surface: 'default',
26
+ overflow: 'hidden',
27
+ shadow: true,
28
+ },
29
+ note: 'The recipe owns field, message, and action rhythm. The app owns validation rules, draft persistence, permissions, and transport.',
30
+ };
18
31
 
19
32
  type ValueField = HTMLElement & { value: string };
20
33
 
@@ -40,78 +53,77 @@ export const createRecipe: RecipeFactory = (announce) => {
40
53
  const audience = signal('team');
41
54
  const status = signal<'idle' | 'error' | 'saved'>('idle');
42
55
  const render = () => (
43
- <RecipeRoot
44
- element="form"
45
- recipe="recipe-composer-form"
46
- measure="form"
47
- contentLayout
48
- ariaLabelledBy="recipe-composer-title"
49
- ariaDescribedBy="recipe-composer-summary"
56
+ <form
57
+ data-recipe="recipe-composer-form"
58
+ aria-labelledby="recipe-composer-title"
59
+ aria-describedby="recipe-composer-summary"
50
60
  noValidate
51
61
  >
52
- <Toolbar
53
- label="Publish workspace update"
54
- dividerSides=""
55
- leading={
56
- <ToolbarText
57
- text="Publish workspace update"
58
- size="xlarge"
59
- id="recipe-composer-title"
62
+ <List gap="l" controlInsets="tb">
63
+ {/* The title and its supporting line are one heading unit. */}
64
+ <List>
65
+ <Toolbar
66
+ label="Publish workspace update"
67
+ dividerSides=""
68
+ leading={
69
+ <ToolbarText
70
+ text="Publish workspace update"
71
+ size="xlarge"
72
+ id="recipe-composer-title"
73
+ />
74
+ }
75
+ />
76
+ <ListInsetText sides="rl">
77
+ <Text variant="span" tone="quiet" id="recipe-composer-summary">
78
+ Share a concise, actionable update with collaborators.
79
+ </Text>
80
+ </ListInsetText>
81
+ </List>
82
+ {status.value === 'error' && (
83
+ <StateBanner
84
+ title="Add a title before publishing"
85
+ detail="The update body and audience are preserved."
86
+ tone="danger"
87
+ urgency="alert"
88
+ />
89
+ )}
90
+ {status.value === 'saved' && (
91
+ <StateBanner
92
+ title="Update published"
93
+ detail="The team audience can now read it."
94
+ tone="success"
95
+ />
96
+ )}
97
+ <List gap="xs" controlInsets="rl">
98
+ <wa-input
99
+ name="recipe-title"
100
+ label="Update title"
101
+ hint="Summarize the outcome in one line."
102
+ required
103
+ value={title.value}
104
+ ></wa-input>
105
+ <wa-textarea
106
+ name="recipe-body"
107
+ label="Details"
108
+ hint="Include decisions, owners, and the next checkpoint."
109
+ rows="5"
110
+ maxlength="400"
111
+ with-count
112
+ value={body.value}
113
+ ></wa-textarea>
114
+ <Select<string>
115
+ name="recipe-audience"
116
+ value={audience.value}
117
+ label="Audience"
118
+ choices={[
119
+ { value: 'team', label: 'Workspace team' },
120
+ { value: 'reviewers', label: 'Reviewers' },
121
+ { value: 'organization', label: 'Entire organization' },
122
+ ]}
60
123
  />
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">
124
+ </List>
125
+ <Row gap="xs" wrap controlInsets="rl">
126
+ <Spacer flex />
115
127
  <wa-button
116
128
  appearance="outlined"
117
129
  data-action="recipe-action"
@@ -127,13 +139,9 @@ export const createRecipe: RecipeFactory = (announce) => {
127
139
  >
128
140
  Publish update
129
141
  </wa-button>
130
- </div>
131
- <RecipeOwnershipNote contentItem={false}>
132
- The recipe owns field, message, and action rhythm. The app owns
133
- validation rules, draft persistence, permissions, and transport.
134
- </RecipeOwnershipNote>
135
- </footer>
136
- </RecipeRoot>
142
+ </Row>
143
+ </List>
144
+ </form>
137
145
  );
138
146
  return {
139
147
  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 { Pane } from '@kerfjs/ui/pane';
11
- import { Toolbar } from '@kerfjs/ui/toolbar';
12
- import { ToolbarControlGroup } from '@kerfjs/ui/toolbar-control-group';
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<keyof typeof records>('alpha');
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 render = () => (
54
- <RecipeRoot recipe="recipe-list-detail-dialog" surface={false}>
55
- <div class="recipe-list-detail-dialog kui-content">
56
- <wa-button
57
- variant="brand"
58
- appearance="accent"
59
- data-action="recipe-action"
60
- data-recipe-command="open"
61
- >
62
- Open project details
63
- </wa-button>
64
- <RecipeOwnershipNote>
65
- Web Awesome owns modal focus and dismissal. The recipe owns
66
- header/body/list-detail anatomy; the app owns open state, selection,
67
- and policy.
68
- </RecipeOwnershipNote>
69
- <wa-dialog
70
- class="recipe-dialog"
71
- label="Project details"
72
- without-header
73
- open={open.value}
74
- style="--spacing:0;--width:min(62rem, calc(100vw - 2rem))"
75
- >
76
- <Pane>
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 keyof typeof records;
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
  };