@workbench-kit/react 0.0.1-prototype.0 → 0.0.2-prototype.0.1.2

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 (155) hide show
  1. package/package.json +33 -9
  2. package/src/index.ts +545 -43
  3. package/src/jdw/JdwPreview.tsx +38 -0
  4. package/src/jdw/JsonCodeEditorPane.tsx +274 -0
  5. package/src/jdw/builtins/renderBuiltinWidgetLeaf.tsx +29 -0
  6. package/src/jdw/builtins/renderBuiltinWidgetNode.tsx +126 -0
  7. package/src/jdw/createBuiltinJdwRegistry.ts +111 -0
  8. package/src/jdw/cssRenderBackend.tsx +134 -0
  9. package/src/jdw/document.ts +38 -0
  10. package/src/jdw/fixtures/jdw-fixtures.ts +52 -0
  11. package/src/jdw/fixtures/jdw-sample-screens.ts +522 -0
  12. package/src/jdw/index.ts +45 -0
  13. package/src/jdw/parse.ts +1 -0
  14. package/src/jdw/renderJdw.tsx +32 -0
  15. package/src/json-config/JsonConfigWorkbench.tsx +316 -0
  16. package/src/json-config/index.ts +6 -0
  17. package/src/json-config/json-config-editor-state.ts +42 -0
  18. package/src/layout/Panel.tsx +146 -1
  19. package/src/layout/SideBarViewFrame.tsx +367 -328
  20. package/src/layout/WorkbenchCanvas.tsx +849 -0
  21. package/src/layout/WorkbenchFullscreen.tsx +368 -0
  22. package/src/layout/WorkbenchLayout.tsx +5 -0
  23. package/src/layout/WorkbenchLayoutBase.tsx +294 -0
  24. package/src/layout/WorkbenchPropertyPanel.tsx +497 -0
  25. package/src/layout/WorkbenchSidebarActions.tsx +274 -0
  26. package/src/layout/WorkbenchTree.tsx +351 -0
  27. package/src/layout/layoutHelpers.ts +25 -0
  28. package/src/modal/ConfirmDialog.tsx +61 -53
  29. package/src/modal/Modal.tsx +295 -35
  30. package/src/overlay/ContextMenu.tsx +146 -145
  31. package/src/primitives/AbsoluteBox.tsx +46 -0
  32. package/src/primitives/Badge.tsx +12 -12
  33. package/src/primitives/Button.tsx +44 -14
  34. package/src/primitives/Checkbox.tsx +11 -5
  35. package/src/primitives/EmptyState.tsx +27 -26
  36. package/src/primitives/Field.tsx +38 -38
  37. package/src/primitives/IconButton.tsx +5 -2
  38. package/src/primitives/List.tsx +111 -0
  39. package/src/primitives/NumberInput.tsx +38 -0
  40. package/src/primitives/ScrollArea.tsx +42 -0
  41. package/src/primitives/Select.tsx +1 -12
  42. package/src/primitives/StatusBar.tsx +46 -0
  43. package/src/primitives/TextArea.tsx +36 -0
  44. package/src/primitives/TextInput.tsx +11 -2
  45. package/src/primitives/WorkbenchChrome.tsx +424 -0
  46. package/src/primitives/WorkbenchEditor.tsx +394 -0
  47. package/src/primitives/index.ts +105 -0
  48. package/src/primitives/select/Select.tsx +266 -0
  49. package/src/primitives/select/index.ts +2 -0
  50. package/src/primitives/select/options.ts +38 -0
  51. package/src/primitives/select/overlay.ts +63 -0
  52. package/src/primitives/select/select.app.css +9 -0
  53. package/src/primitives/select/select.css +140 -0
  54. package/src/primitives/select/types.ts +18 -0
  55. package/src/primitives/styles.css +955 -0
  56. package/src/scrollbars.css +69 -0
  57. package/src/styles.css +7738 -1762
  58. package/src/utils/codicon.ts +17 -0
  59. package/src/widget-asset/WidgetAssetEditor.tsx +140 -0
  60. package/src/widget-asset/WidgetAssetMetadataForm.tsx +82 -0
  61. package/src/widget-asset/WidgetAssetModeControls.tsx +42 -0
  62. package/src/widget-asset/WidgetAssetSourceEditor.tsx +50 -0
  63. package/src/widget-asset/WidgetAssetWorkbench.tsx +112 -0
  64. package/src/widget-asset/index.ts +17 -0
  65. package/src/widget-asset/widget-asset-document.ts +37 -0
  66. package/src/widget-asset/widget-asset-mode.ts +3 -0
  67. package/src/widget-studio/builtin-widget-asset-catalog.ts +12 -0
  68. package/src/widget-studio/create-widget-studio-workspace-editor.tsx +79 -0
  69. package/src/widget-studio/index.ts +7 -0
  70. package/src/widget-studio/resolve-widget-studio-asset-catalog.ts +26 -0
  71. package/src/widget-tree/WidgetAssetPalette.tsx +77 -0
  72. package/src/widget-tree/WidgetInspectorPanel.tsx +266 -0
  73. package/src/widget-tree/WidgetSourceEditor.tsx +56 -0
  74. package/src/widget-tree/WidgetTreeLab.tsx +272 -0
  75. package/src/widget-tree/WidgetTreeModeControls.tsx +44 -0
  76. package/src/widget-tree/WidgetTreeSidePanel.tsx +49 -0
  77. package/src/widget-tree/WidgetTreeView.tsx +97 -0
  78. package/src/widget-tree/WidgetTreeWorkbench.tsx +111 -0
  79. package/src/widget-tree/WidgetTreeWorkspaceShell.tsx +154 -0
  80. package/src/widget-tree/demo-registry.ts +32 -0
  81. package/src/widget-tree/demo-widget-assets.ts +4 -0
  82. package/src/widget-tree/index.ts +27 -0
  83. package/src/widget-tree/widget-tree-document.ts +20 -0
  84. package/src/widget-tree/widget-tree-layout.ts +31 -0
  85. package/src/widget-tree/widget-tree-mode.ts +7 -0
  86. package/src/widget-tree/widget-tree-play-helpers.ts +74 -0
  87. package/src/workbench/ActivityBar.tsx +52 -52
  88. package/src/workbench/ArtifactShell.tsx +397 -0
  89. package/src/workbench/CommandPalette.tsx +819 -0
  90. package/src/workbench/ConfirmationFlow.tsx +245 -0
  91. package/src/workbench/MarkdownPreview.tsx +151 -0
  92. package/src/workbench/ShortcutCommandBridge.tsx +368 -0
  93. package/src/workbench/SplitView.tsx +136 -136
  94. package/src/workbench/StatusBar.tsx +178 -123
  95. package/src/workbench/StructuredArtifactEditor.tsx +205 -0
  96. package/src/workbench/Timeline.tsx +302 -0
  97. package/src/workbench/WorkbenchCanvasShell.tsx +941 -0
  98. package/src/workbench/WorkbenchShell.tsx +74 -70
  99. package/src/workbench/WorkbenchStandaloneShell.tsx +318 -291
  100. package/src/workbench/auth/WorkbenchAuthGate.tsx +61 -0
  101. package/src/workbench/auth/WorkbenchLoginView.tsx +635 -0
  102. package/src/workbench/auth/index.ts +19 -0
  103. package/src/workbench/chat/ChatComposer.tsx +165 -148
  104. package/src/workbench/chat/ChatMessageItem.tsx +10 -1
  105. package/src/workbench/chat/index.ts +9 -0
  106. package/src/workbench/chat/slashCommand.ts +28 -0
  107. package/src/workbench/chat/useChatRuntimeState.ts +76 -0
  108. package/src/workbench/chat/useSlashCommandSuggest.ts +101 -0
  109. package/src/workbench/commands.ts +520 -490
  110. package/src/workbench/index.ts +443 -98
  111. package/src/workbench/keyboard.ts +53 -0
  112. package/src/workbench/schema/index.ts +48 -0
  113. package/src/workbench/schema/workbenchDocument.ts +31 -0
  114. package/src/workbench/schema/workbenchDocumentActions.ts +6 -0
  115. package/src/workbench/schema/workbenchDocumentAdapter.ts +9 -0
  116. package/src/workbench/schema/workbenchDocumentPatch.ts +13 -0
  117. package/src/workbench/schema/workbenchDocumentRenderer.tsx +702 -0
  118. package/src/workbench/settings/NavigationPanel.tsx +47 -0
  119. package/src/workbench/settings/SchemaForm.tsx +420 -0
  120. package/src/workbench/settings/SectionedPanel.tsx +415 -0
  121. package/src/workbench/settings/StructuredDataForm.tsx +539 -0
  122. package/src/workbench/settings/StructuredDataSchemaPanel.tsx +559 -0
  123. package/src/workbench/settings/StructuredDataSchemaPanelEmbed.tsx +21 -0
  124. package/src/workbench/settings/StructuredDataSchemaPanelFrame.tsx +76 -0
  125. package/src/workbench/settings/StructuredDataTableView.tsx +127 -0
  126. package/src/workbench/settings/WorkbenchPanelRegion.tsx +46 -0
  127. package/src/workbench/settings/WorkbenchSettingsModal.tsx +21 -12
  128. package/src/workbench/settings/WorkbenchSettingsNav.tsx +38 -22
  129. package/src/workbench/settings/index.ts +135 -0
  130. package/src/workbench/settings/panel-region.css +21 -0
  131. package/src/workbench/settings/sectionedPanelScrollSpy.ts +143 -0
  132. package/src/workbench/settings/structured-data-schema-panel.css +330 -0
  133. package/src/workbench/settings/structured-data-table-view.css +85 -0
  134. package/src/workbench/settings/structuredDataSchema.ts +886 -0
  135. package/src/workbench/shell.ts +15 -0
  136. package/src/workbench/shellState.ts +203 -203
  137. package/src/workbench/standalone.ts +101 -94
  138. package/src/workbench/status.ts +129 -0
  139. package/src/workbench/theme.ts +44 -0
  140. package/src/workbench/workspace/MultiProviderExplorer.tsx +625 -0
  141. package/src/workbench/workspace/WorkspaceDraftsContext.tsx +111 -0
  142. package/src/workbench/workspace/WorkspaceEditor.tsx +47 -12
  143. package/src/workbench/workspace/WorkspaceEditorPanel.tsx +334 -353
  144. package/src/workbench/workspace/WorkspaceExplorer.tsx +532 -524
  145. package/src/workbench/workspace/WorkspaceFileIcon.tsx +32 -2
  146. package/src/workbench/workspace/WorkspaceSearchPanel.tsx +113 -113
  147. package/src/workbench/workspace/WorkspaceSearchResults.tsx +47 -47
  148. package/src/workbench/workspace/index.ts +156 -98
  149. package/src/workbench/workspace/mimeType.ts +45 -0
  150. package/src/workbench/workspace/path.ts +10 -10
  151. package/src/workbench/workspace/search.ts +6 -6
  152. package/src/workbench/workspace/tree.ts +1 -1
  153. package/src/workbench/workspace/types.ts +10 -10
  154. package/src/workbench/workspace/useVirtualWorkspace.ts +111 -98
  155. package/src/workbench/workspace/workspaceJsonDiagnostics.ts +83 -0
@@ -0,0 +1,38 @@
1
+ import type { WidgetRegistryContract } from '@workbench-kit/contracts';
2
+ import { parseJsonWidgetData, type LayoutConstraints } from '@workbench-kit/jdw';
3
+
4
+ import { WorkbenchParseError, WorkbenchRenderSurface } from '../layout/WorkbenchLayout.js';
5
+ import { useRenderJdw } from './renderJdw.js';
6
+
7
+ export interface JdwPreviewProps {
8
+ json: string;
9
+ registry?: WidgetRegistryContract<unknown> | undefined;
10
+ emptyLabel?: string | undefined;
11
+ className?: string | undefined;
12
+ layoutConstraints?: LayoutConstraints | undefined;
13
+ }
14
+
15
+ export function JdwPreview({
16
+ json,
17
+ registry,
18
+ emptyLabel = 'No render output.',
19
+ className,
20
+ layoutConstraints,
21
+ }: JdwPreviewProps) {
22
+ const parsed = parseJsonWidgetData(json);
23
+ const renderOutput = useRenderJdw(json, { registry, emptyLabel, layoutConstraints });
24
+
25
+ if (parsed.parseError !== null) {
26
+ return (
27
+ <WorkbenchParseError role="alert" data-testid="jdw-preview-error">
28
+ {parsed.parseError}
29
+ </WorkbenchParseError>
30
+ );
31
+ }
32
+
33
+ return (
34
+ <WorkbenchRenderSurface className={className} data-testid="jdw-preview-output">
35
+ {renderOutput ?? emptyLabel}
36
+ </WorkbenchRenderSurface>
37
+ );
38
+ }
@@ -0,0 +1,274 @@
1
+ import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
2
+ import type { WidgetJsonSchema } from '@workbench-kit/contracts';
3
+ import type { OnMount } from '@monaco-editor/react';
4
+ import type * as Monaco from 'monaco-editor';
5
+
6
+ import { IconButton } from '../primitives/IconButton';
7
+ import { ListEmptyState } from '../primitives/List';
8
+ import {
9
+ WorkbenchEditorBody,
10
+ WorkbenchEditorBottomPanel,
11
+ WorkbenchEditorBottomPanelBody,
12
+ WorkbenchEditorBottomPanelHeader,
13
+ WorkbenchEditorBottomPanelTitle,
14
+ WorkbenchEditorFrame,
15
+ WorkbenchEditorViewport,
16
+ WorkbenchProblemItem,
17
+ WorkbenchProblemList,
18
+ type WorkbenchProblemSeverity,
19
+ } from '../layout/WorkbenchLayoutBase';
20
+ import {
21
+ StatusBar as WorkbenchStatusBar,
22
+ StatusBarItem as WorkbenchStatusBarItem,
23
+ StatusBarSection as WorkbenchStatusBarSection,
24
+ } from '../workbench/StatusBar';
25
+ import { WorkspaceEditor, type WorkspaceEditorTheme } from '../workbench/workspace/WorkspaceEditor';
26
+ import type { WorkspaceFile } from '../workbench/workspace/types';
27
+
28
+ interface JsonEditorProblem {
29
+ endColumn: number;
30
+ endLineNumber: number;
31
+ message: string;
32
+ severity: number;
33
+ startColumn: number;
34
+ startLineNumber: number;
35
+ }
36
+
37
+ function toWorkbenchProblemSeverity(severity: number): WorkbenchProblemSeverity {
38
+ if (severity === 8) return 'error';
39
+ if (severity === 4) return 'warning';
40
+ return 'info';
41
+ }
42
+
43
+ export interface JsonCodeEditorPaneProps {
44
+ documentParseError?: string | null | undefined;
45
+ file: WorkspaceFile;
46
+ jsonSchema?: WidgetJsonSchema | null | undefined;
47
+ onChange: (value: string) => void;
48
+ onEditorMount?: OnMount | undefined;
49
+ onSave?: (() => void) | undefined;
50
+ readOnly?: boolean | undefined;
51
+ showProblemsPanel?: boolean | undefined;
52
+ theme?: WorkspaceEditorTheme | undefined;
53
+ value: string;
54
+ }
55
+
56
+ export function JsonCodeEditorPane({
57
+ documentParseError = null,
58
+ file,
59
+ jsonSchema = null,
60
+ onChange,
61
+ onEditorMount,
62
+ onSave,
63
+ readOnly = false,
64
+ showProblemsPanel = true,
65
+ theme = 'dark',
66
+ value,
67
+ }: JsonCodeEditorPaneProps) {
68
+ const editorRef = useRef<Monaco.editor.IStandaloneCodeEditor | null>(null);
69
+ const markerListenerRef = useRef<Monaco.IDisposable | null>(null);
70
+ const [monacoProblems, setMonacoProblems] = useState<JsonEditorProblem[]>([]);
71
+ const [showProblems, setShowProblems] = useState(false);
72
+
73
+ const problems = useMemo(() => {
74
+ if (!documentParseError) return monacoProblems;
75
+ return [
76
+ {
77
+ endColumn: 1,
78
+ endLineNumber: 1,
79
+ message: documentParseError,
80
+ severity: 8,
81
+ startColumn: 1,
82
+ startLineNumber: 1,
83
+ },
84
+ ...monacoProblems,
85
+ ];
86
+ }, [documentParseError, monacoProblems]);
87
+
88
+ const previousProblemCountRef = useRef(0);
89
+ useEffect(() => {
90
+ if (problems.length > previousProblemCountRef.current) {
91
+ setShowProblems(true);
92
+ }
93
+ previousProblemCountRef.current = problems.length;
94
+ }, [problems.length]);
95
+
96
+ useEffect(
97
+ () => () => {
98
+ markerListenerRef.current?.dispose();
99
+ markerListenerRef.current = null;
100
+ },
101
+ [],
102
+ );
103
+
104
+ const configureJsonSchema = (monaco: typeof Monaco, path: string) => {
105
+ if (!jsonSchema) return;
106
+
107
+ const jsonDefaults = (
108
+ monaco.languages.json as unknown as {
109
+ jsonDefaults?: { setDiagnosticsOptions: (options: unknown) => void };
110
+ }
111
+ ).jsonDefaults;
112
+ if (!jsonDefaults) return;
113
+
114
+ jsonDefaults.setDiagnosticsOptions({
115
+ validate: true,
116
+ enableSchemaRequest: false,
117
+ schemas: [
118
+ {
119
+ uri: 'https://workbench-kit.dev/schemas/widget-document.schema.json',
120
+ fileMatch: [path],
121
+ schema: jsonSchema,
122
+ },
123
+ ],
124
+ });
125
+ };
126
+
127
+ const handleMount: OnMount = (editor, monaco) => {
128
+ editorRef.current = editor;
129
+ configureJsonSchema(monaco, file.path);
130
+ onEditorMount?.(editor, monaco);
131
+
132
+ if (onSave) {
133
+ editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
134
+ onSave();
135
+ });
136
+ }
137
+
138
+ if (!showProblemsPanel) return;
139
+
140
+ markerListenerRef.current?.dispose();
141
+ markerListenerRef.current = null;
142
+
143
+ const model = editor.getModel();
144
+ if (!model) return;
145
+
146
+ const updateProblems = () => {
147
+ const markers = monaco.editor.getModelMarkers({ resource: model.uri });
148
+ setMonacoProblems(
149
+ markers.map((marker: Monaco.editor.IMarker) => ({
150
+ message: marker.message,
151
+ severity: marker.severity,
152
+ startLineNumber: marker.startLineNumber,
153
+ startColumn: marker.startColumn,
154
+ endLineNumber: marker.endLineNumber,
155
+ endColumn: marker.endColumn,
156
+ })),
157
+ );
158
+ };
159
+
160
+ updateProblems();
161
+ markerListenerRef.current = monaco.editor.onDidChangeMarkers(updateProblems);
162
+ };
163
+
164
+ const jumpToProblem = (problem: JsonEditorProblem) => {
165
+ const editor = editorRef.current;
166
+ if (!editor) return;
167
+ editor.setPosition({ lineNumber: problem.startLineNumber, column: problem.startColumn });
168
+ editor.revealPositionInCenter({
169
+ lineNumber: problem.startLineNumber,
170
+ column: problem.startColumn,
171
+ });
172
+ editor.focus();
173
+ };
174
+
175
+ return (
176
+ <WorkbenchEditorFrame className="ui-json-code-editor-pane">
177
+ <WorkbenchEditorBody>
178
+ <WorkbenchEditorViewport className="ui-json-code-editor-pane__viewport">
179
+ <WorkspaceEditor
180
+ compact
181
+ file={file}
182
+ readOnly={readOnly}
183
+ showHeader={false}
184
+ theme={theme}
185
+ value={value}
186
+ onChange={onChange}
187
+ onEditorMount={handleMount}
188
+ />
189
+ </WorkbenchEditorViewport>
190
+ </WorkbenchEditorBody>
191
+
192
+ {showProblemsPanel && showProblems ? (
193
+ <WorkbenchEditorBottomPanel aria-label="Problems" height={120}>
194
+ <WorkbenchEditorBottomPanelHeader>
195
+ <WorkbenchEditorBottomPanelTitle>Problems</WorkbenchEditorBottomPanelTitle>
196
+ <IconButton
197
+ compact
198
+ icon="codicon-close"
199
+ label="Close problems"
200
+ onClick={() => setShowProblems(false)}
201
+ />
202
+ </WorkbenchEditorBottomPanelHeader>
203
+ <WorkbenchEditorBottomPanelBody>
204
+ <WorkbenchProblemList aria-label="JSON problems" role="list">
205
+ {problems.length === 0 ? (
206
+ <ListEmptyState>No problems have been detected.</ListEmptyState>
207
+ ) : (
208
+ problems.map((problem, index) => (
209
+ <WorkbenchProblemItem
210
+ key={`${problem.startLineNumber}:${problem.startColumn}:${index}`}
211
+ location={`Line ${problem.startLineNumber}, Col ${problem.startColumn}:`}
212
+ message={problem.message}
213
+ severity={toWorkbenchProblemSeverity(problem.severity)}
214
+ onClick={() => jumpToProblem(problem)}
215
+ />
216
+ ))
217
+ )}
218
+ </WorkbenchProblemList>
219
+ </WorkbenchEditorBottomPanelBody>
220
+ </WorkbenchEditorBottomPanel>
221
+ ) : null}
222
+
223
+ {showProblemsPanel ? (
224
+ <WorkbenchStatusBar compact>
225
+ <WorkbenchStatusBarSection>
226
+ <WorkbenchStatusBarItem
227
+ active={showProblems}
228
+ icon={problems.length > 0 ? 'error' : 'check'}
229
+ status={problems.length > 0 ? 'failed' : 'completed'}
230
+ title="Toggle problems"
231
+ onClick={() => setShowProblems((current) => !current)}
232
+ >
233
+ {problems.length > 0 ? `${problems.length} Problems` : 'No Problems'}
234
+ </WorkbenchStatusBarItem>
235
+ </WorkbenchStatusBarSection>
236
+ </WorkbenchStatusBar>
237
+ ) : null}
238
+ </WorkbenchEditorFrame>
239
+ );
240
+ }
241
+
242
+ export interface JsonConfigValidationBannerProps {
243
+ canApply: boolean;
244
+ firstError?: ReactNode | undefined;
245
+ validationOk: boolean;
246
+ }
247
+
248
+ export function JsonConfigValidationBanner({
249
+ canApply,
250
+ firstError,
251
+ validationOk,
252
+ }: JsonConfigValidationBannerProps) {
253
+ const tone = !validationOk ? 'warning' : canApply ? 'default' : 'default';
254
+ const icon = !validationOk ? 'codicon-error' : canApply ? 'codicon-check' : 'codicon-info';
255
+ const message = !validationOk
256
+ ? (firstError ?? 'Invalid JSON')
257
+ : canApply
258
+ ? 'Valid — unsaved changes'
259
+ : 'JSON valid';
260
+
261
+ return (
262
+ <div
263
+ className="ui-json-config-validation-banner"
264
+ data-can-apply={canApply ? 'true' : 'false'}
265
+ data-testid="json-config-validation-banner"
266
+ data-validation={validationOk ? 'valid' : 'invalid'}
267
+ data-tone={tone}
268
+ role="status"
269
+ >
270
+ <i className={`codicon ${icon}`} aria-hidden />
271
+ <span>{message}</span>
272
+ </div>
273
+ );
274
+ }
@@ -0,0 +1,29 @@
1
+ import { createElement, type CSSProperties, type ReactNode } from 'react';
2
+ import type { GenericWidget } from '@workbench-kit/jdw';
3
+
4
+ function readNumber(value: unknown): number | undefined {
5
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
6
+ }
7
+
8
+ function textStyle(widget: GenericWidget): CSSProperties {
9
+ return {
10
+ color: typeof widget.color === 'string' ? widget.color : undefined,
11
+ background: typeof widget.background === 'string' ? widget.background : undefined,
12
+ fontSize: readNumber(widget.fontSize),
13
+ };
14
+ }
15
+
16
+ export function renderBuiltinWidgetLeaf(widget: GenericWidget): ReactNode {
17
+ if (widget.type === 'text') {
18
+ return createElement(
19
+ 'span',
20
+ {
21
+ 'data-widget-type': 'text',
22
+ style: textStyle(widget),
23
+ },
24
+ String(widget.text ?? ''),
25
+ );
26
+ }
27
+
28
+ return null;
29
+ }
@@ -0,0 +1,126 @@
1
+ import { createElement, type CSSProperties, type ReactNode } from 'react';
2
+ import { getWidgetChildren, type GenericWidget } from '@workbench-kit/jdw';
3
+
4
+ import { renderBuiltinWidgetLeaf } from './renderBuiltinWidgetLeaf.js';
5
+
6
+ const LAYOUT_TYPES = new Set(['row', 'column', 'grid']);
7
+
8
+ function readNumber(value: unknown): number | undefined {
9
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
10
+ }
11
+
12
+ function layoutContainerStyle(widget: GenericWidget): CSSProperties {
13
+ const gap = readNumber(widget.gap) ?? 8;
14
+ const padding = readNumber(widget.padding) ?? 0;
15
+ const background =
16
+ typeof widget.background === 'string' && widget.background.trim().length > 0
17
+ ? widget.background
18
+ : undefined;
19
+
20
+ if (widget.type === 'row') {
21
+ return {
22
+ display: 'flex',
23
+ flexDirection: 'row',
24
+ gap,
25
+ padding,
26
+ background,
27
+ alignItems: 'stretch',
28
+ minHeight: 24,
29
+ };
30
+ }
31
+
32
+ if (widget.type === 'column') {
33
+ return {
34
+ display: 'flex',
35
+ flexDirection: 'column',
36
+ gap,
37
+ padding,
38
+ background,
39
+ alignItems: 'stretch',
40
+ minHeight: 24,
41
+ };
42
+ }
43
+
44
+ if (widget.type === 'grid') {
45
+ const columns = readNumber(widget.columns) ?? 2;
46
+ return {
47
+ display: 'grid',
48
+ gridTemplateColumns: `repeat(${Math.max(1, columns)}, minmax(0, 1fr))`,
49
+ gap,
50
+ padding,
51
+ background,
52
+ minHeight: 24,
53
+ };
54
+ }
55
+
56
+ return {};
57
+ }
58
+
59
+ function childPlacementStyle(child: GenericWidget, parentType: string): CSSProperties {
60
+ if (parentType === 'grid') {
61
+ const col = readNumber(child.col);
62
+ const row = readNumber(child.row);
63
+ const colSpan = readNumber(child.colSpan);
64
+ const rowSpan = readNumber(child.rowSpan);
65
+
66
+ return {
67
+ gridColumn:
68
+ col !== undefined
69
+ ? colSpan !== undefined
70
+ ? `${col + 1} / span ${colSpan}`
71
+ : `${col + 1}`
72
+ : undefined,
73
+ gridRow:
74
+ row !== undefined
75
+ ? rowSpan !== undefined
76
+ ? `${row + 1} / span ${rowSpan}`
77
+ : `${row + 1}`
78
+ : undefined,
79
+ minWidth: 0,
80
+ };
81
+ }
82
+
83
+ if (parentType === 'row' || parentType === 'column') {
84
+ const flex = readNumber(child.flex);
85
+ const align = typeof child.align === 'string' ? child.align : undefined;
86
+
87
+ return {
88
+ flex: flex !== undefined ? flex : undefined,
89
+ alignSelf: align,
90
+ minWidth: 0,
91
+ };
92
+ }
93
+
94
+ return {};
95
+ }
96
+
97
+ export function renderBuiltinWidgetNode(widget: GenericWidget): ReactNode {
98
+ const leaf = renderBuiltinWidgetLeaf(widget);
99
+ if (leaf !== null) {
100
+ return leaf;
101
+ }
102
+
103
+ if (LAYOUT_TYPES.has(widget.type)) {
104
+ const children = getWidgetChildren(widget);
105
+ return createElement(
106
+ 'div',
107
+ {
108
+ 'data-widget-type': widget.type,
109
+ style: layoutContainerStyle(widget),
110
+ },
111
+ children.map((child, index) =>
112
+ createElement(
113
+ 'div',
114
+ {
115
+ key: index,
116
+ 'data-widget-child': true,
117
+ style: childPlacementStyle(child, widget.type),
118
+ },
119
+ renderBuiltinWidgetNode(child),
120
+ ),
121
+ ),
122
+ );
123
+ }
124
+
125
+ return null;
126
+ }
@@ -0,0 +1,111 @@
1
+ import type { WidgetRegistryContract, WidgetTypeShape } from '@workbench-kit/contracts';
2
+ import { createWidgetRegistry, type GenericWidget } from '@workbench-kit/jdw';
3
+
4
+ import { renderBuiltinWidgetNode } from './builtins/renderBuiltinWidgetNode.js';
5
+
6
+ const LAYOUT_INSPECTOR = [
7
+ {
8
+ title: 'Layout',
9
+ fields: [
10
+ { kind: 'number' as const, prop: 'gap', label: 'Gap', min: 0 },
11
+ { kind: 'number' as const, prop: 'padding', label: 'Padding', min: 0 },
12
+ { kind: 'color' as const, prop: 'background', label: 'Background' },
13
+ ],
14
+ },
15
+ ];
16
+
17
+ function build(widget: WidgetTypeShape) {
18
+ return renderBuiltinWidgetNode(widget as GenericWidget);
19
+ }
20
+
21
+ /** workbench-jdw-react-v1 builtin widget registry (text, row, column, grid). */
22
+ export function createBuiltinJdwRegistry(): WidgetRegistryContract<unknown> {
23
+ return createWidgetRegistry([
24
+ {
25
+ type: 'text',
26
+ build,
27
+ displayName: 'Text',
28
+ schema: {
29
+ type: 'object',
30
+ required: ['text'],
31
+ properties: {
32
+ text: { type: 'string' },
33
+ color: { type: 'string' },
34
+ background: { type: 'string' },
35
+ fontSize: { type: 'number' },
36
+ },
37
+ },
38
+ inspector: [
39
+ {
40
+ title: 'Text',
41
+ fields: [
42
+ { kind: 'text', prop: 'text', label: 'Content' },
43
+ { kind: 'color', prop: 'color', label: 'Color' },
44
+ { kind: 'number', prop: 'fontSize', label: 'Font size', min: 8, max: 96 },
45
+ ],
46
+ },
47
+ ],
48
+ },
49
+ {
50
+ type: 'row',
51
+ build,
52
+ displayName: 'Row',
53
+ schema: {
54
+ type: 'object',
55
+ properties: {
56
+ gap: { type: 'number', minimum: 0 },
57
+ padding: { type: 'number', minimum: 0 },
58
+ background: { type: 'string' },
59
+ children: { type: 'array' },
60
+ },
61
+ },
62
+ inspector: LAYOUT_INSPECTOR,
63
+ capabilities: ['container'],
64
+ },
65
+ {
66
+ type: 'column',
67
+ build,
68
+ displayName: 'Column',
69
+ schema: {
70
+ type: 'object',
71
+ properties: {
72
+ gap: { type: 'number', minimum: 0 },
73
+ padding: { type: 'number', minimum: 0 },
74
+ background: { type: 'string' },
75
+ children: { type: 'array' },
76
+ },
77
+ },
78
+ inspector: LAYOUT_INSPECTOR,
79
+ capabilities: ['container'],
80
+ },
81
+ {
82
+ type: 'grid',
83
+ build,
84
+ displayName: 'Grid',
85
+ schema: {
86
+ type: 'object',
87
+ properties: {
88
+ columns: { type: 'number', minimum: 1 },
89
+ gap: { type: 'number', minimum: 0 },
90
+ padding: { type: 'number', minimum: 0 },
91
+ background: { type: 'string' },
92
+ children: { type: 'array' },
93
+ },
94
+ },
95
+ inspector: [
96
+ {
97
+ title: 'Grid',
98
+ fields: [
99
+ { kind: 'number', prop: 'columns', label: 'Columns', min: 1, max: 12 },
100
+ { kind: 'number', prop: 'gap', label: 'Gap', min: 0 },
101
+ { kind: 'number', prop: 'padding', label: 'Padding', min: 0 },
102
+ { kind: 'color', prop: 'background', label: 'Background' },
103
+ ],
104
+ },
105
+ ],
106
+ capabilities: ['container'],
107
+ },
108
+ ]);
109
+ }
110
+
111
+ export const BUILTIN_JDW_REGISTRY = createBuiltinJdwRegistry();