@workbench-kit/jdw-editor 0.0.1-prototype.0 → 0.0.2-prototype.0.2.10
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/package.json +4 -4
- package/src/index.ts +27 -14
- package/src/screen-spec/JdwSampleScreenExplorer.tsx +81 -0
- package/src/screen-spec/ScreenNodeInspector.tsx +369 -0
- package/src/screen-spec/ScreenSpecEditor.tsx +431 -0
- package/src/screen-spec/ScreenSpecEditorStoryHost.tsx +103 -0
- package/src/screen-spec/ScreenSpecPalette.tsx +119 -0
- package/src/screen-spec/ScreenSpecWorkbench.tsx +177 -0
- package/src/screen-spec/filterScreenSpecOutline.ts +40 -0
- package/src/{useScreenSpecPipeline.ts → screen-spec/useScreenSpecPipeline.ts} +82 -88
- package/src/JdwSampleScreenExplorer.tsx +0 -181
- package/src/ScreenNodeInspector.tsx +0 -203
- package/src/ScreenSpecEditor.tsx +0 -196
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workbench-kit/jdw-editor",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2-prototype.0.2.10",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
"!src/**/*.stories.tsx"
|
|
15
15
|
],
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@workbench-kit/jdw": "0.0.
|
|
18
|
-
"@workbench-kit/react": "0.0.
|
|
17
|
+
"@workbench-kit/jdw": "0.0.2-prototype.0.2.10",
|
|
18
|
+
"@workbench-kit/react": "0.0.2-prototype.0.2.10"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"react": "^19.0.0"
|
|
22
22
|
},
|
|
23
|
-
"description": "
|
|
23
|
+
"description": "JDW template-to-WidgetTreeLab sample flow with deprecated Screen Spec compatibility editors.",
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public",
|
|
26
26
|
"tag": "prototype",
|
package/src/index.ts
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
|
-
export { JdwSampleScreenExplorer } from './JdwSampleScreenExplorer.js';
|
|
2
|
-
export type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from './
|
|
6
|
-
export {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
export { JdwSampleScreenExplorer } from './screen-spec/JdwSampleScreenExplorer.js';
|
|
2
|
+
export type { JdwSampleScreenExplorerProps } from './screen-spec/JdwSampleScreenExplorer.js';
|
|
3
|
+
export { ScreenNodeInspector } from './screen-spec/ScreenNodeInspector.js';
|
|
4
|
+
export type { ScreenNodeInspectorProps } from './screen-spec/ScreenNodeInspector.js';
|
|
5
|
+
export { ScreenSpecEditor } from './screen-spec/ScreenSpecEditor.js';
|
|
6
|
+
export type {
|
|
7
|
+
ScreenSpecDetailTab,
|
|
8
|
+
ScreenSpecEditorPane,
|
|
9
|
+
ScreenSpecEditorProps,
|
|
10
|
+
ScreenSpecLeftRailView,
|
|
11
|
+
} from './screen-spec/ScreenSpecEditor.js';
|
|
12
|
+
export {
|
|
13
|
+
SCREEN_PALETTE_ITEMS,
|
|
14
|
+
SCREEN_PALETTE_MIME,
|
|
15
|
+
ScreenSpecPalette,
|
|
16
|
+
readScreenPaletteDragData,
|
|
17
|
+
writeScreenPaletteDragData,
|
|
18
|
+
} from './screen-spec/ScreenSpecPalette.js';
|
|
19
|
+
export type { ScreenPaletteItem, ScreenSpecPaletteProps } from './screen-spec/ScreenSpecPalette.js';
|
|
20
|
+
export { filterScreenSpecOutline } from './screen-spec/filterScreenSpecOutline.js';
|
|
21
|
+
export { ScreenSpecWorkbench } from './screen-spec/ScreenSpecWorkbench.js';
|
|
22
|
+
export type { ScreenSpecWorkbenchProps } from './screen-spec/ScreenSpecWorkbench.js';
|
|
23
|
+
export { useScreenSpecPipeline } from './screen-spec/useScreenSpecPipeline.js';
|
|
24
|
+
export type {
|
|
25
|
+
ScreenSpecPipelineState,
|
|
26
|
+
UseScreenSpecPipelineResult,
|
|
27
|
+
} from './screen-spec/useScreenSpecPipeline.js';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
|
+
import { createScreenSpecPaletteAssetCatalog } from '@workbench-kit/jdw';
|
|
3
|
+
import { BUILTIN_JDW_REGISTRY } from '@workbench-kit/react/jdw';
|
|
4
|
+
import { WidgetTreeLab } from '@workbench-kit/react/widget-tree';
|
|
5
|
+
import { Field, Select, WorkbenchAuthoringShell } from '@workbench-kit/react/primitives';
|
|
6
|
+
import {
|
|
7
|
+
JDW_SAMPLE_SCREENS,
|
|
8
|
+
formatJdwSampleScreenJson,
|
|
9
|
+
type JdwSampleScreenDefinition,
|
|
10
|
+
} from '@workbench-kit/react/jdw/samples';
|
|
11
|
+
|
|
12
|
+
export interface JdwSampleScreenExplorerProps {
|
|
13
|
+
readonly samples?: readonly JdwSampleScreenDefinition[] | undefined;
|
|
14
|
+
readonly initialSampleId?: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function resolveSample(
|
|
18
|
+
samples: readonly JdwSampleScreenDefinition[],
|
|
19
|
+
sampleId: string,
|
|
20
|
+
): JdwSampleScreenDefinition {
|
|
21
|
+
return samples.find((entry) => entry.id === sampleId) ?? samples[0]!;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Compiles a selected Screen Spec template once, then opens the resulting JDW
|
|
26
|
+
* document in the canonical WidgetTreeLab design/code authoring surface.
|
|
27
|
+
*/
|
|
28
|
+
export function JdwSampleScreenExplorer({
|
|
29
|
+
samples = JDW_SAMPLE_SCREENS,
|
|
30
|
+
initialSampleId,
|
|
31
|
+
}: JdwSampleScreenExplorerProps) {
|
|
32
|
+
const [sampleId, setSampleId] = useState(initialSampleId ?? samples[0]?.id ?? '');
|
|
33
|
+
const activeSample = resolveSample(samples, sampleId);
|
|
34
|
+
const [documentValue, setDocumentValue] = useState(() => formatJdwSampleScreenJson(activeSample));
|
|
35
|
+
const assetCatalog = useMemo(() => createScreenSpecPaletteAssetCatalog(), []);
|
|
36
|
+
|
|
37
|
+
const sampleOptions = useMemo(
|
|
38
|
+
() =>
|
|
39
|
+
samples.map((sample) => (
|
|
40
|
+
<option key={sample.id} value={sample.id}>
|
|
41
|
+
{sample.title}
|
|
42
|
+
</option>
|
|
43
|
+
)),
|
|
44
|
+
[samples],
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (samples.length === 0) {
|
|
48
|
+
return <div data-testid="jdw-sample-explorer-empty">No sample screens configured.</div>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<WorkbenchAuthoringShell
|
|
53
|
+
data-testid="jdw-sample-explorer"
|
|
54
|
+
toolbar={
|
|
55
|
+
<Field label="Load sample" htmlFor="jdw-sample-screen-select" inline>
|
|
56
|
+
<Select
|
|
57
|
+
id="jdw-sample-screen-select"
|
|
58
|
+
aria-label="Load sample"
|
|
59
|
+
data-testid="jdw-sample-screen-select"
|
|
60
|
+
controlWidth="wide"
|
|
61
|
+
value={activeSample.id}
|
|
62
|
+
onValueChange={(nextId) => {
|
|
63
|
+
const next = resolveSample(samples, nextId);
|
|
64
|
+
setSampleId(next.id);
|
|
65
|
+
setDocumentValue(formatJdwSampleScreenJson(next));
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
{sampleOptions}
|
|
69
|
+
</Select>
|
|
70
|
+
</Field>
|
|
71
|
+
}
|
|
72
|
+
>
|
|
73
|
+
<WidgetTreeLab
|
|
74
|
+
assetCatalog={assetCatalog}
|
|
75
|
+
registry={BUILTIN_JDW_REGISTRY}
|
|
76
|
+
value={documentValue}
|
|
77
|
+
onChange={setDocumentValue}
|
|
78
|
+
/>
|
|
79
|
+
</WorkbenchAuthoringShell>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { useMemo, useState, type ReactNode } from 'react';
|
|
2
|
+
import type { ScreenNode, ScreenTextStyle } from '@workbench-kit/jdw';
|
|
3
|
+
import {
|
|
4
|
+
NumberInput,
|
|
5
|
+
TextInput,
|
|
6
|
+
WorkbenchPropertyHint,
|
|
7
|
+
WorkbenchPropertyNumberRow,
|
|
8
|
+
WorkbenchPropertyPanel,
|
|
9
|
+
WorkbenchPropertyRow,
|
|
10
|
+
WorkbenchPropertySearch,
|
|
11
|
+
WorkbenchPropertySection,
|
|
12
|
+
WorkbenchPropertyStack,
|
|
13
|
+
WorkbenchPropertyTextRow,
|
|
14
|
+
filterWorkbenchPropertyFields,
|
|
15
|
+
isWorkbenchPropertySearchActive,
|
|
16
|
+
type WorkbenchPropertyFieldManifestEntry,
|
|
17
|
+
} from '@workbench-kit/react/primitives';
|
|
18
|
+
|
|
19
|
+
export interface ScreenNodeInspectorProps {
|
|
20
|
+
readonly node: ScreenNode;
|
|
21
|
+
readonly parentKind?: ScreenNode['kind'] | undefined;
|
|
22
|
+
readonly onChange: (node: ScreenNode) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readStyle(node: Extract<ScreenNode, { kind: 'text' | 'panel' }>): ScreenTextStyle {
|
|
26
|
+
return node.style ?? {};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function patchStyle(
|
|
30
|
+
node: Extract<ScreenNode, { kind: 'text' | 'panel' }>,
|
|
31
|
+
patch: ScreenTextStyle,
|
|
32
|
+
): ScreenNode {
|
|
33
|
+
return { ...node, style: { ...node.style, ...patch } };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function OptionalNumberRow({
|
|
37
|
+
label,
|
|
38
|
+
testId,
|
|
39
|
+
value,
|
|
40
|
+
onChange,
|
|
41
|
+
}: {
|
|
42
|
+
readonly label: string;
|
|
43
|
+
readonly testId: string;
|
|
44
|
+
readonly value: number | undefined;
|
|
45
|
+
readonly onChange: (value: number | undefined) => void;
|
|
46
|
+
}) {
|
|
47
|
+
return (
|
|
48
|
+
<WorkbenchPropertyRow label={label} htmlFor={testId}>
|
|
49
|
+
<NumberInput
|
|
50
|
+
id={testId}
|
|
51
|
+
data-testid={testId}
|
|
52
|
+
controlWidth="full"
|
|
53
|
+
nullable
|
|
54
|
+
value={value}
|
|
55
|
+
onEmptyValue={() => onChange(undefined)}
|
|
56
|
+
onValueChange={(next) => onChange(next)}
|
|
57
|
+
/>
|
|
58
|
+
</WorkbenchPropertyRow>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function usePropertyVisibility(
|
|
63
|
+
fields: readonly WorkbenchPropertyFieldManifestEntry[],
|
|
64
|
+
query: string,
|
|
65
|
+
) {
|
|
66
|
+
const filtered = useMemo(() => filterWorkbenchPropertyFields({ fields, query }), [fields, query]);
|
|
67
|
+
const visible = useMemo(() => new Set(filtered.fieldIds), [filtered.fieldIds]);
|
|
68
|
+
const sections = useMemo(() => new Set(filtered.sectionIds), [filtered.sectionIds]);
|
|
69
|
+
const searching = isWorkbenchPropertySearchActive(query);
|
|
70
|
+
return {
|
|
71
|
+
searching,
|
|
72
|
+
showField: (id: string) => !searching || visible.has(id),
|
|
73
|
+
showSection: (sectionId: string) => !searching || sections.has(sectionId),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function InspectorShell({
|
|
78
|
+
query,
|
|
79
|
+
onQueryChange,
|
|
80
|
+
children,
|
|
81
|
+
}: {
|
|
82
|
+
readonly query: string;
|
|
83
|
+
readonly onQueryChange: (value: string) => void;
|
|
84
|
+
readonly children: ReactNode;
|
|
85
|
+
}) {
|
|
86
|
+
return (
|
|
87
|
+
<WorkbenchPropertyPanel
|
|
88
|
+
className="jdw-screen-node-inspector"
|
|
89
|
+
data-testid="screen-spec-node-inspector"
|
|
90
|
+
>
|
|
91
|
+
<WorkbenchPropertySearch
|
|
92
|
+
data-testid="screen-spec-props-search"
|
|
93
|
+
value={query}
|
|
94
|
+
onValueChange={onQueryChange}
|
|
95
|
+
/>
|
|
96
|
+
<WorkbenchPropertyStack>{children}</WorkbenchPropertyStack>
|
|
97
|
+
</WorkbenchPropertyPanel>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function ContentNodeInspector({
|
|
102
|
+
node,
|
|
103
|
+
parentKind,
|
|
104
|
+
onChange,
|
|
105
|
+
}: {
|
|
106
|
+
readonly node: Extract<ScreenNode, { kind: 'text' | 'panel' }>;
|
|
107
|
+
readonly parentKind?: ScreenNode['kind'] | undefined;
|
|
108
|
+
readonly onChange: (node: ScreenNode) => void;
|
|
109
|
+
}) {
|
|
110
|
+
const [query, setQuery] = useState('');
|
|
111
|
+
const style = readStyle(node);
|
|
112
|
+
const fields = useMemo((): WorkbenchPropertyFieldManifestEntry[] => {
|
|
113
|
+
const next: WorkbenchPropertyFieldManifestEntry[] = [
|
|
114
|
+
{ id: 'content', label: 'Content', sectionId: 'content', keywords: ['text'] },
|
|
115
|
+
];
|
|
116
|
+
if (node.kind === 'panel') {
|
|
117
|
+
next.push({
|
|
118
|
+
id: 'panel-background',
|
|
119
|
+
label: 'Panel background',
|
|
120
|
+
sectionId: 'content',
|
|
121
|
+
keywords: ['fill'],
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
next.push(
|
|
125
|
+
{ id: 'color', label: 'Text color', sectionId: 'style', keywords: ['colour'] },
|
|
126
|
+
{ id: 'font-size', label: 'Font size', sectionId: 'style', keywords: ['typography'] },
|
|
127
|
+
{ id: 'background', label: 'Background', sectionId: 'style' },
|
|
128
|
+
);
|
|
129
|
+
if (parentKind === 'grid') {
|
|
130
|
+
next.push(
|
|
131
|
+
{ id: 'col', label: 'Column', sectionId: 'placement', keywords: ['grid'] },
|
|
132
|
+
{ id: 'row', label: 'Row', sectionId: 'placement', keywords: ['grid'] },
|
|
133
|
+
{ id: 'col-span', label: 'Column span', sectionId: 'placement', keywords: ['grid'] },
|
|
134
|
+
{ id: 'row-span', label: 'Row span', sectionId: 'placement', keywords: ['grid'] },
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return next;
|
|
138
|
+
}, [node.kind, parentKind]);
|
|
139
|
+
const { showField, showSection, searching } = usePropertyVisibility(fields, query);
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<InspectorShell query={query} onQueryChange={setQuery}>
|
|
143
|
+
{showSection('content') ? (
|
|
144
|
+
<WorkbenchPropertySection collapsible title="Content">
|
|
145
|
+
{showField('content') ? (
|
|
146
|
+
<WorkbenchPropertyRow label="Content" htmlFor="screen-spec-field-content">
|
|
147
|
+
<TextInput
|
|
148
|
+
id="screen-spec-field-content"
|
|
149
|
+
data-testid="screen-spec-field-content"
|
|
150
|
+
controlWidth="full"
|
|
151
|
+
value={node.content}
|
|
152
|
+
onValueChange={(content) => onChange({ ...node, content })}
|
|
153
|
+
/>
|
|
154
|
+
</WorkbenchPropertyRow>
|
|
155
|
+
) : null}
|
|
156
|
+
{node.kind === 'panel' && showField('panel-background') ? (
|
|
157
|
+
<WorkbenchPropertyTextRow
|
|
158
|
+
htmlFor="screen-spec-field-panel-background"
|
|
159
|
+
label="Panel background"
|
|
160
|
+
value={node.background ?? ''}
|
|
161
|
+
onValueChange={(background) => onChange({ ...node, background })}
|
|
162
|
+
/>
|
|
163
|
+
) : null}
|
|
164
|
+
</WorkbenchPropertySection>
|
|
165
|
+
) : null}
|
|
166
|
+
|
|
167
|
+
{showSection('style') ? (
|
|
168
|
+
<WorkbenchPropertySection collapsible title="Style">
|
|
169
|
+
{showField('color') ? (
|
|
170
|
+
<WorkbenchPropertyTextRow
|
|
171
|
+
htmlFor="screen-spec-field-color"
|
|
172
|
+
label="Text color"
|
|
173
|
+
value={style.color ?? ''}
|
|
174
|
+
onValueChange={(color) => onChange(patchStyle(node, { color }))}
|
|
175
|
+
/>
|
|
176
|
+
) : null}
|
|
177
|
+
{showField('font-size') ? (
|
|
178
|
+
<OptionalNumberRow
|
|
179
|
+
label="Font size"
|
|
180
|
+
testId="screen-spec-field-font-size"
|
|
181
|
+
value={style.fontSize}
|
|
182
|
+
onChange={(fontSize) => onChange(patchStyle(node, { fontSize }))}
|
|
183
|
+
/>
|
|
184
|
+
) : null}
|
|
185
|
+
{showField('background') ? (
|
|
186
|
+
<WorkbenchPropertyTextRow
|
|
187
|
+
htmlFor="screen-spec-field-background"
|
|
188
|
+
label="Background"
|
|
189
|
+
value={style.background ?? ''}
|
|
190
|
+
onValueChange={(background) => onChange(patchStyle(node, { background }))}
|
|
191
|
+
/>
|
|
192
|
+
) : null}
|
|
193
|
+
</WorkbenchPropertySection>
|
|
194
|
+
) : null}
|
|
195
|
+
|
|
196
|
+
{parentKind === 'grid' && showSection('placement') ? (
|
|
197
|
+
<WorkbenchPropertySection collapsible title="Placement">
|
|
198
|
+
{showField('col') ? (
|
|
199
|
+
<OptionalNumberRow
|
|
200
|
+
label="Column"
|
|
201
|
+
testId="screen-spec-field-col"
|
|
202
|
+
value={node.col}
|
|
203
|
+
onChange={(col) => onChange({ ...node, col })}
|
|
204
|
+
/>
|
|
205
|
+
) : null}
|
|
206
|
+
{showField('row') ? (
|
|
207
|
+
<OptionalNumberRow
|
|
208
|
+
label="Row"
|
|
209
|
+
testId="screen-spec-field-row"
|
|
210
|
+
value={node.row}
|
|
211
|
+
onChange={(row) => onChange({ ...node, row })}
|
|
212
|
+
/>
|
|
213
|
+
) : null}
|
|
214
|
+
{showField('col-span') ? (
|
|
215
|
+
<OptionalNumberRow
|
|
216
|
+
label="Column span"
|
|
217
|
+
testId="screen-spec-field-col-span"
|
|
218
|
+
value={node.colSpan}
|
|
219
|
+
onChange={(colSpan) => onChange({ ...node, colSpan })}
|
|
220
|
+
/>
|
|
221
|
+
) : null}
|
|
222
|
+
{showField('row-span') ? (
|
|
223
|
+
<OptionalNumberRow
|
|
224
|
+
label="Row span"
|
|
225
|
+
testId="screen-spec-field-row-span"
|
|
226
|
+
value={node.rowSpan}
|
|
227
|
+
onChange={(rowSpan) => onChange({ ...node, rowSpan })}
|
|
228
|
+
/>
|
|
229
|
+
) : null}
|
|
230
|
+
</WorkbenchPropertySection>
|
|
231
|
+
) : null}
|
|
232
|
+
|
|
233
|
+
{searching &&
|
|
234
|
+
!showSection('content') &&
|
|
235
|
+
!showSection('style') &&
|
|
236
|
+
!showSection('placement') ? (
|
|
237
|
+
<WorkbenchPropertyHint>No properties match.</WorkbenchPropertyHint>
|
|
238
|
+
) : null}
|
|
239
|
+
</InspectorShell>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function ExpandedNodeInspector({
|
|
244
|
+
node,
|
|
245
|
+
onChange,
|
|
246
|
+
}: {
|
|
247
|
+
readonly node: Extract<ScreenNode, { kind: 'expanded' }>;
|
|
248
|
+
readonly onChange: (node: ScreenNode) => void;
|
|
249
|
+
}) {
|
|
250
|
+
const [query, setQuery] = useState('');
|
|
251
|
+
const fields = useMemo(
|
|
252
|
+
(): WorkbenchPropertyFieldManifestEntry[] => [
|
|
253
|
+
{ id: 'flex', label: 'Flex', sectionId: 'layout', keywords: ['grow'] },
|
|
254
|
+
],
|
|
255
|
+
[],
|
|
256
|
+
);
|
|
257
|
+
const { showField, showSection, searching } = usePropertyVisibility(fields, query);
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<InspectorShell query={query} onQueryChange={setQuery}>
|
|
261
|
+
{showSection('layout') ? (
|
|
262
|
+
<WorkbenchPropertySection collapsible title="Layout">
|
|
263
|
+
{showField('flex') ? (
|
|
264
|
+
<OptionalNumberRow
|
|
265
|
+
label="Flex"
|
|
266
|
+
testId="screen-spec-field-flex"
|
|
267
|
+
value={node.flex}
|
|
268
|
+
onChange={(flex) => onChange({ ...node, flex })}
|
|
269
|
+
/>
|
|
270
|
+
) : null}
|
|
271
|
+
<WorkbenchPropertyHint>Edit the wrapped child from the outline.</WorkbenchPropertyHint>
|
|
272
|
+
</WorkbenchPropertySection>
|
|
273
|
+
) : null}
|
|
274
|
+
{searching && !showSection('layout') ? (
|
|
275
|
+
<WorkbenchPropertyHint>No properties match.</WorkbenchPropertyHint>
|
|
276
|
+
) : null}
|
|
277
|
+
</InspectorShell>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function ContainerNodeInspector({
|
|
282
|
+
node,
|
|
283
|
+
onChange,
|
|
284
|
+
}: {
|
|
285
|
+
readonly node: Extract<ScreenNode, { kind: 'row' | 'column' | 'grid' | 'stack' }>;
|
|
286
|
+
readonly onChange: (node: ScreenNode) => void;
|
|
287
|
+
}) {
|
|
288
|
+
const [query, setQuery] = useState('');
|
|
289
|
+
const fields = useMemo((): WorkbenchPropertyFieldManifestEntry[] => {
|
|
290
|
+
const next: WorkbenchPropertyFieldManifestEntry[] = [];
|
|
291
|
+
if (node.kind === 'grid') {
|
|
292
|
+
next.push({ id: 'columns', label: 'Columns', sectionId: 'layout', keywords: ['grid'] });
|
|
293
|
+
}
|
|
294
|
+
next.push(
|
|
295
|
+
{ id: 'gap', label: 'Gap', sectionId: 'layout', keywords: ['spacing'] },
|
|
296
|
+
{ id: 'padding', label: 'Padding', sectionId: 'layout', keywords: ['spacing'] },
|
|
297
|
+
{ id: 'background', label: 'Background', sectionId: 'style' },
|
|
298
|
+
);
|
|
299
|
+
return next;
|
|
300
|
+
}, [node.kind]);
|
|
301
|
+
const { showField, showSection, searching } = usePropertyVisibility(fields, query);
|
|
302
|
+
|
|
303
|
+
return (
|
|
304
|
+
<InspectorShell query={query} onQueryChange={setQuery}>
|
|
305
|
+
{showSection('layout') ? (
|
|
306
|
+
<WorkbenchPropertySection collapsible title="Layout">
|
|
307
|
+
{node.kind === 'grid' && showField('columns') ? (
|
|
308
|
+
<WorkbenchPropertyNumberRow
|
|
309
|
+
htmlFor="screen-spec-field-columns"
|
|
310
|
+
label="Columns"
|
|
311
|
+
min={1}
|
|
312
|
+
value={node.columns}
|
|
313
|
+
onValueChange={(columns) => onChange({ ...node, columns: Math.max(1, columns) })}
|
|
314
|
+
/>
|
|
315
|
+
) : null}
|
|
316
|
+
{showField('gap') ? (
|
|
317
|
+
<OptionalNumberRow
|
|
318
|
+
label="Gap"
|
|
319
|
+
testId="screen-spec-field-gap"
|
|
320
|
+
value={node.gap}
|
|
321
|
+
onChange={(gap) => onChange({ ...node, gap })}
|
|
322
|
+
/>
|
|
323
|
+
) : null}
|
|
324
|
+
{showField('padding') ? (
|
|
325
|
+
<OptionalNumberRow
|
|
326
|
+
label="Padding"
|
|
327
|
+
testId="screen-spec-field-padding"
|
|
328
|
+
value={node.padding}
|
|
329
|
+
onChange={(padding) => onChange({ ...node, padding })}
|
|
330
|
+
/>
|
|
331
|
+
) : null}
|
|
332
|
+
</WorkbenchPropertySection>
|
|
333
|
+
) : null}
|
|
334
|
+
{showSection('style') ? (
|
|
335
|
+
<WorkbenchPropertySection collapsible title="Style">
|
|
336
|
+
{showField('background') ? (
|
|
337
|
+
<WorkbenchPropertyTextRow
|
|
338
|
+
htmlFor="screen-spec-field-container-background"
|
|
339
|
+
label="Background"
|
|
340
|
+
value={node.background ?? ''}
|
|
341
|
+
onValueChange={(background) => onChange({ ...node, background })}
|
|
342
|
+
/>
|
|
343
|
+
) : null}
|
|
344
|
+
</WorkbenchPropertySection>
|
|
345
|
+
) : null}
|
|
346
|
+
{searching && !showSection('layout') && !showSection('style') ? (
|
|
347
|
+
<WorkbenchPropertyHint>No properties match.</WorkbenchPropertyHint>
|
|
348
|
+
) : null}
|
|
349
|
+
</InspectorShell>
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function ScreenNodeInspector({ node, parentKind, onChange }: ScreenNodeInspectorProps) {
|
|
354
|
+
if (node.kind === 'text' || node.kind === 'panel') {
|
|
355
|
+
return <ContentNodeInspector node={node} parentKind={parentKind} onChange={onChange} />;
|
|
356
|
+
}
|
|
357
|
+
if (node.kind === 'expanded') {
|
|
358
|
+
return <ExpandedNodeInspector node={node} onChange={onChange} />;
|
|
359
|
+
}
|
|
360
|
+
if (
|
|
361
|
+
node.kind === 'row' ||
|
|
362
|
+
node.kind === 'column' ||
|
|
363
|
+
node.kind === 'grid' ||
|
|
364
|
+
node.kind === 'stack'
|
|
365
|
+
) {
|
|
366
|
+
return <ContainerNodeInspector node={node} onChange={onChange} />;
|
|
367
|
+
}
|
|
368
|
+
return null;
|
|
369
|
+
}
|