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

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,539 @@
1
+ export * from './structuredDataSchema';
2
+
3
+ import { useId, useMemo, useState, type FormEvent, type ReactNode } from 'react';
4
+ import { Button } from '../../primitives/Button';
5
+ import { Checkbox } from '../../primitives/Checkbox';
6
+ import { EmptyState } from '../../primitives/EmptyState';
7
+ import { WorkbenchStructuredDataTableView } from './StructuredDataTableView';
8
+ import { Field } from '../../primitives/Field';
9
+ import { IconButton } from '../../primitives/IconButton';
10
+ import { Select } from '../../primitives/Select';
11
+ import { TextArea } from '../../primitives/TextArea';
12
+ import { TextInput } from '../../primitives/TextInput';
13
+ import { cx } from '../../utils/cx';
14
+ import { WorkbenchSectionedPanel } from './SectionedPanel';
15
+ import {
16
+ coerceWorkbenchStructuredDataFormFieldValue,
17
+ coerceWorkbenchStructuredDataSchemaFieldValue,
18
+ booleanWorkbenchStructuredDataSchemaFieldValue,
19
+ formatWorkbenchStructuredDataSchemaLabel,
20
+ getWorkbenchStructuredDataFormErrors,
21
+ getWorkbenchStructuredDataSchemaFieldControl,
22
+ getWorkbenchStructuredDataValue,
23
+ isWorkbenchStructuredDataFormSubmittable,
24
+ normalizeWorkbenchStructuredDataFormData,
25
+ setWorkbenchStructuredDataValue,
26
+ stringifyWorkbenchStructuredDataSchemaFieldValue,
27
+ type WorkbenchStructuredDataFieldValue,
28
+ type WorkbenchStructuredDataFormErrors,
29
+ type WorkbenchStructuredDataFormField,
30
+ type WorkbenchStructuredDataFormFieldChangeContext,
31
+ type WorkbenchStructuredDataFormProps,
32
+ type WorkbenchStructuredDataFormSection,
33
+ type WorkbenchStructuredDataRecord,
34
+ type WorkbenchStructuredDataSchemaFieldInputProps,
35
+ type WorkbenchStructuredDataTextArrayInputProps,
36
+ } from './structuredDataSchema';
37
+
38
+ export function WorkbenchStructuredDataForm({
39
+ ariaLabel,
40
+ cancelLabel = 'Cancel',
41
+ className,
42
+ data,
43
+ defaultData = {},
44
+ disabled = false,
45
+ emptyLabel = 'No structured data sections',
46
+ errors,
47
+ onCancel,
48
+ onDataChange,
49
+ onFieldChange,
50
+ onSubmit,
51
+ readOnly = false,
52
+ sections,
53
+ showActions = true,
54
+ submitLabel = 'Save',
55
+ ...props
56
+ }: WorkbenchStructuredDataFormProps) {
57
+ const generatedId = useId().replace(/:/g, '');
58
+ const [uncontrolledData, setUncontrolledData] = useState(() =>
59
+ normalizeWorkbenchStructuredDataFormData(sections, defaultData),
60
+ );
61
+ const resolvedData = useMemo(
62
+ () => normalizeWorkbenchStructuredDataFormData(sections, data ?? uncontrolledData),
63
+ [data, sections, uncontrolledData],
64
+ );
65
+ const resolvedErrors = useMemo(
66
+ () => ({
67
+ ...getWorkbenchStructuredDataFormErrors(sections, resolvedData),
68
+ ...(errors ?? {}),
69
+ }),
70
+ [errors, resolvedData, sections],
71
+ );
72
+ const submittable = isWorkbenchStructuredDataFormSubmittable({
73
+ disabled,
74
+ errors: resolvedErrors,
75
+ readOnly,
76
+ });
77
+
78
+ const updateFieldValue = (
79
+ field: WorkbenchStructuredDataFormField,
80
+ value: WorkbenchStructuredDataFieldValue,
81
+ ) => {
82
+ const coercedValue = coerceWorkbenchStructuredDataFormFieldValue(field, value);
83
+ const nextData = setWorkbenchStructuredDataValue(resolvedData, field.path, coercedValue);
84
+ const context: WorkbenchStructuredDataFormFieldChangeContext = {
85
+ data: nextData,
86
+ field,
87
+ fieldId: field.id,
88
+ path: field.path,
89
+ value: coercedValue,
90
+ };
91
+
92
+ if (data === undefined) {
93
+ setUncontrolledData(nextData);
94
+ }
95
+
96
+ onFieldChange?.(context);
97
+ onDataChange?.(nextData, context);
98
+ };
99
+
100
+ const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
101
+ event.preventDefault();
102
+ if (!submittable) return;
103
+
104
+ onSubmit?.(resolvedData, { data: resolvedData, event });
105
+ };
106
+
107
+ return (
108
+ <form
109
+ className={cx('ui-workbench-structured-data-form', className)}
110
+ data-readonly={readOnly ? 'true' : undefined}
111
+ onSubmit={handleSubmit}
112
+ {...props}
113
+ >
114
+ {sections.length === 0 ? (
115
+ <EmptyState compact icon="codicon-settings-gear">
116
+ {emptyLabel}
117
+ </EmptyState>
118
+ ) : (
119
+ <WorkbenchSectionedPanel
120
+ ariaLabel={ariaLabel}
121
+ className="ui-workbench-structured-data-form__panel"
122
+ items={sections.map((section) => ({
123
+ anchorId: section.id,
124
+ count: section.count ?? section.fields?.length,
125
+ title: section.title,
126
+ render: () => (
127
+ <StructuredDataSection
128
+ key={section.id}
129
+ data={resolvedData}
130
+ disabled={disabled}
131
+ errors={resolvedErrors}
132
+ generatedId={generatedId}
133
+ readOnly={readOnly}
134
+ section={section}
135
+ onFieldChange={updateFieldValue}
136
+ />
137
+ ),
138
+ }))}
139
+ readOnly={readOnly}
140
+ />
141
+ )}
142
+ {showActions ? (
143
+ <div className="ui-workbench-structured-data-form__actions">
144
+ <Button disabled={disabled} onClick={() => onCancel?.({ data: resolvedData })}>
145
+ {cancelLabel}
146
+ </Button>
147
+ <Button disabled={!submittable} type="submit" variant="primary">
148
+ {submitLabel}
149
+ </Button>
150
+ </div>
151
+ ) : null}
152
+ </form>
153
+ );
154
+ }
155
+
156
+ function isRenderableWorkbenchStructuredDataFormError(error: ReactNode) {
157
+ return error !== undefined && error !== null && error !== false && error !== '';
158
+ }
159
+
160
+ function StructuredDataSection({
161
+ data,
162
+ disabled,
163
+ errors,
164
+ generatedId,
165
+ onFieldChange,
166
+ readOnly,
167
+ section,
168
+ }: {
169
+ data: WorkbenchStructuredDataRecord;
170
+ disabled: boolean;
171
+ errors: WorkbenchStructuredDataFormErrors;
172
+ generatedId: string;
173
+ onFieldChange: (
174
+ field: WorkbenchStructuredDataFormField,
175
+ value: WorkbenchStructuredDataFieldValue,
176
+ ) => void;
177
+ readOnly: boolean;
178
+ section: WorkbenchStructuredDataFormSection;
179
+ }) {
180
+ return (
181
+ <section
182
+ id={section.id}
183
+ className="ui-workbench-structured-data-form__section"
184
+ aria-labelledby={`${generatedId}-${section.id}-title`}
185
+ >
186
+ <h2 id={`${generatedId}-${section.id}-title`}>{section.title}</h2>
187
+ {section.description ? <p>{section.description}</p> : null}
188
+ {section.fields?.length ? (
189
+ <div className="ui-workbench-structured-data-form__fields">
190
+ {section.fields.map((field) => {
191
+ const fieldInputId = `${generatedId}-${field.id}`;
192
+ const error = errors[field.id];
193
+ const errorId = isRenderableWorkbenchStructuredDataFormError(error)
194
+ ? `${fieldInputId}-error`
195
+ : undefined;
196
+ const value = coerceWorkbenchStructuredDataFormFieldValue(
197
+ field,
198
+ getWorkbenchStructuredDataValue(data, field.path),
199
+ );
200
+
201
+ return (
202
+ <div
203
+ key={field.id}
204
+ className="ui-workbench-structured-data-form__field"
205
+ data-field-id={field.id}
206
+ data-field-type={field.type}
207
+ >
208
+ {renderStructuredDataField({
209
+ disabled: disabled || Boolean(field.disabled),
210
+ errorId,
211
+ field,
212
+ id: fieldInputId,
213
+ readOnly: readOnly || Boolean(field.readOnly),
214
+ value,
215
+ onChange: (nextValue) => onFieldChange(field, nextValue),
216
+ })}
217
+ {isRenderableWorkbenchStructuredDataFormError(error) ? (
218
+ <div
219
+ id={errorId}
220
+ className="ui-workbench-structured-data-form__error"
221
+ role="alert"
222
+ >
223
+ {error}
224
+ </div>
225
+ ) : null}
226
+ </div>
227
+ );
228
+ })}
229
+ </div>
230
+ ) : null}
231
+ {section.tables?.map((table) => (
232
+ <WorkbenchStructuredDataTableView
233
+ key={table.id}
234
+ className="ui-workbench-structured-data-form__table-section"
235
+ table={table}
236
+ />
237
+ ))}
238
+ </section>
239
+ );
240
+ }
241
+
242
+ function renderStructuredDataField({
243
+ disabled,
244
+ errorId,
245
+ field,
246
+ id,
247
+ onChange,
248
+ readOnly,
249
+ value,
250
+ }: {
251
+ disabled: boolean;
252
+ errorId?: string;
253
+ field: WorkbenchStructuredDataFormField;
254
+ id: string;
255
+ onChange: (value: WorkbenchStructuredDataFieldValue) => void;
256
+ readOnly: boolean;
257
+ value: WorkbenchStructuredDataFieldValue;
258
+ }) {
259
+ if (field.type === 'checkbox') {
260
+ return (
261
+ <Field description={field.description}>
262
+ <Checkbox
263
+ aria-describedby={errorId}
264
+ checked={Boolean(value)}
265
+ disabled={disabled || readOnly}
266
+ label={field.label}
267
+ onChange={(event) => onChange(event.currentTarget.checked)}
268
+ />
269
+ </Field>
270
+ );
271
+ }
272
+
273
+ if (field.type === 'select') {
274
+ return (
275
+ <Field description={field.description} htmlFor={id} label={field.label}>
276
+ <Select
277
+ id={id}
278
+ aria-describedby={errorId}
279
+ controlWidth="full"
280
+ disabled={disabled || readOnly}
281
+ value={String(value)}
282
+ onChange={(event) => onChange(event.currentTarget.value)}
283
+ >
284
+ {field.emptyOptionLabel ? <option value="">{field.emptyOptionLabel}</option> : null}
285
+ {field.options.map((option) => (
286
+ <option key={option.value} disabled={option.disabled} value={option.value}>
287
+ {option.label}
288
+ </option>
289
+ ))}
290
+ </Select>
291
+ </Field>
292
+ );
293
+ }
294
+
295
+ if (field.type === 'text-array') {
296
+ const itemLabel = field.itemLabel ?? getStructuredDataLabelText(field.label, 'Item');
297
+
298
+ return (
299
+ <Field
300
+ className="ui-workbench-structured-data-form__array-field"
301
+ description={field.description}
302
+ label={field.label}
303
+ >
304
+ <WorkbenchStructuredDataTextArrayInput
305
+ addLabel={field.addLabel}
306
+ ariaDescribedBy={errorId}
307
+ ariaLabel={itemLabel}
308
+ disabled={disabled}
309
+ emptyLabel={field.emptyLabel}
310
+ maxItems={field.maxItems}
311
+ minItems={field.minItems}
312
+ placeholder={field.itemPlaceholder}
313
+ readOnly={readOnly}
314
+ removeLabel={field.removeLabel}
315
+ value={value}
316
+ onValueChange={onChange}
317
+ />
318
+ </Field>
319
+ );
320
+ }
321
+
322
+ return (
323
+ <Field description={field.description} htmlFor={id} label={field.label}>
324
+ <TextInput
325
+ id={id}
326
+ aria-describedby={errorId}
327
+ autoComplete={field.type === 'text' ? field.autocomplete : undefined}
328
+ controlWidth="full"
329
+ disabled={disabled}
330
+ max={field.type === 'number' ? field.max : undefined}
331
+ min={field.type === 'number' ? field.min : undefined}
332
+ monospace={field.type === 'text' ? field.monospace : false}
333
+ placeholder={field.placeholder}
334
+ readOnly={readOnly}
335
+ step={field.type === 'number' ? field.step : undefined}
336
+ type={field.type}
337
+ value={String(value)}
338
+ onChange={(event) => onChange(event.currentTarget.value)}
339
+ />
340
+ </Field>
341
+ );
342
+ }
343
+
344
+ function structuredDataTextArrayItems(value: unknown) {
345
+ if (Array.isArray(value)) {
346
+ return value.map((item) => (item === null || item === undefined ? '' : String(item)));
347
+ }
348
+ if (value === '' || value === null || value === undefined) return [];
349
+ return [String(value)];
350
+ }
351
+
352
+ export function WorkbenchStructuredDataSchemaFieldInput({
353
+ addTextArrayLabel,
354
+ checkboxClassName,
355
+ className,
356
+ definition,
357
+ fieldPath,
358
+ onValueChange,
359
+ readOnly = false,
360
+ removeTextArrayLabel,
361
+ textareaClassName,
362
+ textArrayClassName,
363
+ value,
364
+ }: WorkbenchStructuredDataSchemaFieldInputProps) {
365
+ const control = getWorkbenchStructuredDataSchemaFieldControl(definition);
366
+ const label = definition?.title ?? formatWorkbenchStructuredDataSchemaLabel(fieldPath);
367
+ const stringValue = stringifyWorkbenchStructuredDataSchemaFieldValue(value, definition);
368
+
369
+ if (definition?.type === 'array' && definition.items?.type === 'string') {
370
+ return (
371
+ <WorkbenchStructuredDataTextArrayInput
372
+ addLabel={addTextArrayLabel}
373
+ ariaLabel={label}
374
+ className={cx(className, textArrayClassName)}
375
+ placeholder={definition.ui?.placeholder}
376
+ readOnly={readOnly}
377
+ removeLabel={removeTextArrayLabel}
378
+ value={value}
379
+ onValueChange={onValueChange}
380
+ />
381
+ );
382
+ }
383
+
384
+ if (control === 'select') {
385
+ return (
386
+ <Select
387
+ aria-label={label}
388
+ className={className}
389
+ controlWidth="full"
390
+ disabled={readOnly}
391
+ value={stringValue}
392
+ onChange={(event) =>
393
+ onValueChange?.(
394
+ coerceWorkbenchStructuredDataSchemaFieldValue(event.currentTarget.value, definition),
395
+ )
396
+ }
397
+ >
398
+ {(definition?.enum ?? []).map((option) => (
399
+ <option key={String(option)} value={String(option)}>
400
+ {String(option)}
401
+ </option>
402
+ ))}
403
+ </Select>
404
+ );
405
+ }
406
+
407
+ if (control === 'textarea') {
408
+ return (
409
+ <TextArea
410
+ aria-label={label}
411
+ className={cx(className, textareaClassName)}
412
+ controlWidth="full"
413
+ monospace={definition?.type === 'array' || definition?.type === 'object'}
414
+ placeholder={definition?.ui?.placeholder}
415
+ readOnly={readOnly}
416
+ resize="vertical"
417
+ rows={definition?.ui?.rows ?? (definition?.type === 'array' ? 3 : 4)}
418
+ value={stringValue}
419
+ onChange={(event) => {
420
+ if (!readOnly) {
421
+ onValueChange?.(
422
+ coerceWorkbenchStructuredDataSchemaFieldValue(event.currentTarget.value, definition),
423
+ );
424
+ }
425
+ }}
426
+ />
427
+ );
428
+ }
429
+
430
+ if (control === 'checkbox') {
431
+ return (
432
+ <Checkbox
433
+ checked={booleanWorkbenchStructuredDataSchemaFieldValue(value)}
434
+ className={checkboxClassName}
435
+ disabled={readOnly}
436
+ label={booleanWorkbenchStructuredDataSchemaFieldValue(value) ? 'Enabled' : 'Disabled'}
437
+ onChange={(event) => onValueChange?.(event.currentTarget.checked)}
438
+ />
439
+ );
440
+ }
441
+
442
+ return (
443
+ <TextInput
444
+ aria-label={label}
445
+ className={className}
446
+ controlWidth="full"
447
+ placeholder={definition?.ui?.placeholder}
448
+ readOnly={readOnly}
449
+ type={control === 'date' || control === 'number' ? control : 'text'}
450
+ value={stringValue}
451
+ onChange={(event) => {
452
+ if (!readOnly) {
453
+ onValueChange?.(
454
+ coerceWorkbenchStructuredDataSchemaFieldValue(event.currentTarget.value, definition),
455
+ );
456
+ }
457
+ }}
458
+ />
459
+ );
460
+ }
461
+
462
+ export function WorkbenchStructuredDataTextArrayInput({
463
+ addLabel = 'Add item',
464
+ ariaDescribedBy,
465
+ ariaLabel,
466
+ className,
467
+ disabled = false,
468
+ emptyLabel,
469
+ maxItems,
470
+ minItems,
471
+ placeholder,
472
+ readOnly = false,
473
+ removeLabel = 'Remove',
474
+ value,
475
+ onValueChange,
476
+ ...props
477
+ }: WorkbenchStructuredDataTextArrayInputProps) {
478
+ const generatedId = useId();
479
+ const items = structuredDataTextArrayItems(value);
480
+ const canEdit = !disabled && !readOnly;
481
+ const addDisabled = !canEdit || (maxItems !== undefined && items.length >= maxItems);
482
+ const removeDisabled = !canEdit || (minItems !== undefined && items.length <= minItems);
483
+
484
+ const updateItem = (index: number, nextValue: string) => {
485
+ onValueChange?.(items.map((item, itemIndex) => (itemIndex === index ? nextValue : item)));
486
+ };
487
+
488
+ const removeItem = (index: number) => {
489
+ onValueChange?.(items.filter((_, itemIndex) => itemIndex !== index));
490
+ };
491
+
492
+ return (
493
+ <div className={cx('ui-workbench-structured-data-form__array', className)} {...props}>
494
+ {items.length === 0 && emptyLabel ? (
495
+ <div className="ui-workbench-structured-data-form__array-empty">{emptyLabel}</div>
496
+ ) : null}
497
+ {items.map((item, index) => {
498
+ const itemNumber = index + 1;
499
+ const itemInputId = `${generatedId}-${itemNumber}`;
500
+
501
+ return (
502
+ <div className="ui-workbench-structured-data-form__array-row" key={itemInputId}>
503
+ <TextInput
504
+ id={itemInputId}
505
+ aria-describedby={ariaDescribedBy}
506
+ aria-label={`${ariaLabel} ${itemNumber}`}
507
+ controlWidth="full"
508
+ disabled={disabled}
509
+ placeholder={placeholder}
510
+ readOnly={readOnly}
511
+ value={item}
512
+ onChange={(event) => updateItem(index, event.currentTarget.value)}
513
+ />
514
+ <IconButton
515
+ compact
516
+ disabled={removeDisabled}
517
+ icon="codicon-trash"
518
+ label={`${removeLabel} ${ariaLabel} ${itemNumber}`}
519
+ onClick={() => removeItem(index)}
520
+ />
521
+ </div>
522
+ );
523
+ })}
524
+ <Button
525
+ compact
526
+ disabled={addDisabled}
527
+ icon="codicon-add"
528
+ onClick={() => onValueChange?.([...items, ''])}
529
+ >
530
+ {addLabel}
531
+ </Button>
532
+ </div>
533
+ );
534
+ }
535
+
536
+ function getStructuredDataLabelText(label: ReactNode, fallback: string) {
537
+ if (typeof label === 'string' || typeof label === 'number') return String(label);
538
+ return fallback;
539
+ }