@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.
- package/package.json +33 -9
- package/src/index.ts +545 -43
- package/src/jdw/JdwPreview.tsx +38 -0
- package/src/jdw/JsonCodeEditorPane.tsx +274 -0
- package/src/jdw/builtins/renderBuiltinWidgetLeaf.tsx +29 -0
- package/src/jdw/builtins/renderBuiltinWidgetNode.tsx +126 -0
- package/src/jdw/createBuiltinJdwRegistry.ts +111 -0
- package/src/jdw/cssRenderBackend.tsx +134 -0
- package/src/jdw/document.ts +38 -0
- package/src/jdw/fixtures/jdw-fixtures.ts +52 -0
- package/src/jdw/fixtures/jdw-sample-screens.ts +522 -0
- package/src/jdw/index.ts +45 -0
- package/src/jdw/parse.ts +1 -0
- package/src/jdw/renderJdw.tsx +32 -0
- package/src/json-config/JsonConfigWorkbench.tsx +316 -0
- package/src/json-config/index.ts +6 -0
- package/src/json-config/json-config-editor-state.ts +42 -0
- package/src/layout/Panel.tsx +146 -1
- package/src/layout/SideBarViewFrame.tsx +367 -328
- package/src/layout/WorkbenchCanvas.tsx +849 -0
- package/src/layout/WorkbenchFullscreen.tsx +368 -0
- package/src/layout/WorkbenchLayout.tsx +5 -0
- package/src/layout/WorkbenchLayoutBase.tsx +294 -0
- package/src/layout/WorkbenchPropertyPanel.tsx +497 -0
- package/src/layout/WorkbenchSidebarActions.tsx +274 -0
- package/src/layout/WorkbenchTree.tsx +351 -0
- package/src/layout/layoutHelpers.ts +25 -0
- package/src/modal/ConfirmDialog.tsx +61 -53
- package/src/modal/Modal.tsx +295 -35
- package/src/overlay/ContextMenu.tsx +146 -145
- package/src/primitives/AbsoluteBox.tsx +46 -0
- package/src/primitives/Badge.tsx +12 -12
- package/src/primitives/Button.tsx +44 -14
- package/src/primitives/Checkbox.tsx +11 -5
- package/src/primitives/EmptyState.tsx +27 -26
- package/src/primitives/Field.tsx +38 -38
- package/src/primitives/IconButton.tsx +5 -2
- package/src/primitives/List.tsx +111 -0
- package/src/primitives/NumberInput.tsx +38 -0
- package/src/primitives/ScrollArea.tsx +42 -0
- package/src/primitives/Select.tsx +1 -12
- package/src/primitives/StatusBar.tsx +46 -0
- package/src/primitives/TextArea.tsx +36 -0
- package/src/primitives/TextInput.tsx +11 -2
- package/src/primitives/WorkbenchChrome.tsx +424 -0
- package/src/primitives/WorkbenchEditor.tsx +394 -0
- package/src/primitives/index.ts +105 -0
- package/src/primitives/select/Select.tsx +266 -0
- package/src/primitives/select/index.ts +2 -0
- package/src/primitives/select/options.ts +38 -0
- package/src/primitives/select/overlay.ts +63 -0
- package/src/primitives/select/select.app.css +9 -0
- package/src/primitives/select/select.css +140 -0
- package/src/primitives/select/types.ts +18 -0
- package/src/primitives/styles.css +955 -0
- package/src/scrollbars.css +69 -0
- package/src/styles.css +7738 -1762
- package/src/utils/codicon.ts +17 -0
- package/src/widget-asset/WidgetAssetEditor.tsx +140 -0
- package/src/widget-asset/WidgetAssetMetadataForm.tsx +82 -0
- package/src/widget-asset/WidgetAssetModeControls.tsx +42 -0
- package/src/widget-asset/WidgetAssetSourceEditor.tsx +50 -0
- package/src/widget-asset/WidgetAssetWorkbench.tsx +112 -0
- package/src/widget-asset/index.ts +17 -0
- package/src/widget-asset/widget-asset-document.ts +37 -0
- package/src/widget-asset/widget-asset-mode.ts +3 -0
- package/src/widget-studio/builtin-widget-asset-catalog.ts +12 -0
- package/src/widget-studio/create-widget-studio-workspace-editor.tsx +79 -0
- package/src/widget-studio/index.ts +7 -0
- package/src/widget-studio/resolve-widget-studio-asset-catalog.ts +26 -0
- package/src/widget-tree/WidgetAssetPalette.tsx +77 -0
- package/src/widget-tree/WidgetInspectorPanel.tsx +266 -0
- package/src/widget-tree/WidgetSourceEditor.tsx +56 -0
- package/src/widget-tree/WidgetTreeLab.tsx +272 -0
- package/src/widget-tree/WidgetTreeModeControls.tsx +44 -0
- package/src/widget-tree/WidgetTreeSidePanel.tsx +49 -0
- package/src/widget-tree/WidgetTreeView.tsx +97 -0
- package/src/widget-tree/WidgetTreeWorkbench.tsx +111 -0
- package/src/widget-tree/WidgetTreeWorkspaceShell.tsx +154 -0
- package/src/widget-tree/demo-registry.ts +32 -0
- package/src/widget-tree/demo-widget-assets.ts +4 -0
- package/src/widget-tree/index.ts +27 -0
- package/src/widget-tree/widget-tree-document.ts +20 -0
- package/src/widget-tree/widget-tree-layout.ts +31 -0
- package/src/widget-tree/widget-tree-mode.ts +7 -0
- package/src/widget-tree/widget-tree-play-helpers.ts +74 -0
- package/src/workbench/ActivityBar.tsx +52 -52
- package/src/workbench/ArtifactShell.tsx +397 -0
- package/src/workbench/CommandPalette.tsx +819 -0
- package/src/workbench/ConfirmationFlow.tsx +245 -0
- package/src/workbench/MarkdownPreview.tsx +151 -0
- package/src/workbench/ShortcutCommandBridge.tsx +368 -0
- package/src/workbench/SplitView.tsx +136 -136
- package/src/workbench/StatusBar.tsx +178 -123
- package/src/workbench/StructuredArtifactEditor.tsx +205 -0
- package/src/workbench/Timeline.tsx +302 -0
- package/src/workbench/WorkbenchCanvasShell.tsx +941 -0
- package/src/workbench/WorkbenchShell.tsx +74 -70
- package/src/workbench/WorkbenchStandaloneShell.tsx +318 -291
- package/src/workbench/auth/WorkbenchAuthGate.tsx +61 -0
- package/src/workbench/auth/WorkbenchLoginView.tsx +635 -0
- package/src/workbench/auth/index.ts +19 -0
- package/src/workbench/chat/ChatComposer.tsx +165 -148
- package/src/workbench/chat/ChatMessageItem.tsx +10 -1
- package/src/workbench/chat/index.ts +9 -0
- package/src/workbench/chat/slashCommand.ts +28 -0
- package/src/workbench/chat/useChatRuntimeState.ts +76 -0
- package/src/workbench/chat/useSlashCommandSuggest.ts +101 -0
- package/src/workbench/commands.ts +520 -490
- package/src/workbench/index.ts +443 -98
- package/src/workbench/keyboard.ts +53 -0
- package/src/workbench/schema/index.ts +48 -0
- package/src/workbench/schema/workbenchDocument.ts +31 -0
- package/src/workbench/schema/workbenchDocumentActions.ts +6 -0
- package/src/workbench/schema/workbenchDocumentAdapter.ts +9 -0
- package/src/workbench/schema/workbenchDocumentPatch.ts +13 -0
- package/src/workbench/schema/workbenchDocumentRenderer.tsx +702 -0
- package/src/workbench/settings/NavigationPanel.tsx +47 -0
- package/src/workbench/settings/SchemaForm.tsx +420 -0
- package/src/workbench/settings/SectionedPanel.tsx +415 -0
- package/src/workbench/settings/StructuredDataForm.tsx +539 -0
- package/src/workbench/settings/StructuredDataSchemaPanel.tsx +559 -0
- package/src/workbench/settings/StructuredDataSchemaPanelEmbed.tsx +21 -0
- package/src/workbench/settings/StructuredDataSchemaPanelFrame.tsx +76 -0
- package/src/workbench/settings/StructuredDataTableView.tsx +127 -0
- package/src/workbench/settings/WorkbenchPanelRegion.tsx +46 -0
- package/src/workbench/settings/WorkbenchSettingsModal.tsx +21 -12
- package/src/workbench/settings/WorkbenchSettingsNav.tsx +38 -22
- package/src/workbench/settings/index.ts +135 -0
- package/src/workbench/settings/panel-region.css +21 -0
- package/src/workbench/settings/sectionedPanelScrollSpy.ts +143 -0
- package/src/workbench/settings/structured-data-schema-panel.css +330 -0
- package/src/workbench/settings/structured-data-table-view.css +85 -0
- package/src/workbench/settings/structuredDataSchema.ts +886 -0
- package/src/workbench/shell.ts +15 -0
- package/src/workbench/shellState.ts +203 -203
- package/src/workbench/standalone.ts +101 -94
- package/src/workbench/status.ts +129 -0
- package/src/workbench/theme.ts +44 -0
- package/src/workbench/workspace/MultiProviderExplorer.tsx +625 -0
- package/src/workbench/workspace/WorkspaceDraftsContext.tsx +111 -0
- package/src/workbench/workspace/WorkspaceEditor.tsx +47 -12
- package/src/workbench/workspace/WorkspaceEditorPanel.tsx +334 -353
- package/src/workbench/workspace/WorkspaceExplorer.tsx +532 -524
- package/src/workbench/workspace/WorkspaceFileIcon.tsx +32 -2
- package/src/workbench/workspace/WorkspaceSearchPanel.tsx +113 -113
- package/src/workbench/workspace/WorkspaceSearchResults.tsx +47 -47
- package/src/workbench/workspace/index.ts +156 -98
- package/src/workbench/workspace/mimeType.ts +45 -0
- package/src/workbench/workspace/path.ts +10 -10
- package/src/workbench/workspace/search.ts +6 -6
- package/src/workbench/workspace/tree.ts +1 -1
- package/src/workbench/workspace/types.ts +10 -10
- package/src/workbench/workspace/useVirtualWorkspace.ts +111 -98
- package/src/workbench/workspace/workspaceJsonDiagnostics.ts +83 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef, ReactNode } from 'react';
|
|
2
|
+
import { cx } from '../../utils/cx';
|
|
3
|
+
|
|
4
|
+
export interface WorkbenchNavigationPanelProps extends Omit<
|
|
5
|
+
ComponentPropsWithRef<'div'>,
|
|
6
|
+
'children' | 'content'
|
|
7
|
+
> {
|
|
8
|
+
content: ReactNode;
|
|
9
|
+
contentClassName?: string | undefined;
|
|
10
|
+
contentProps?: Omit<ComponentPropsWithRef<'div'>, 'children' | 'className'> | undefined;
|
|
11
|
+
nav?: ReactNode | undefined;
|
|
12
|
+
navClassName?: string | undefined;
|
|
13
|
+
navProps?: Omit<ComponentPropsWithRef<'nav'>, 'children' | 'className'> | undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function WorkbenchNavigationPanel({
|
|
17
|
+
className,
|
|
18
|
+
content,
|
|
19
|
+
contentClassName,
|
|
20
|
+
contentProps,
|
|
21
|
+
nav,
|
|
22
|
+
navClassName,
|
|
23
|
+
navProps,
|
|
24
|
+
...props
|
|
25
|
+
}: WorkbenchNavigationPanelProps) {
|
|
26
|
+
const hasNav = nav !== undefined && nav !== null && nav !== false;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
className={cx('ui-workbench-navigation-panel', className)}
|
|
31
|
+
data-has-nav={hasNav ? 'true' : undefined}
|
|
32
|
+
{...props}
|
|
33
|
+
>
|
|
34
|
+
{hasNav ? (
|
|
35
|
+
<nav className={cx('ui-workbench-navigation-panel__nav', navClassName)} {...navProps}>
|
|
36
|
+
{nav}
|
|
37
|
+
</nav>
|
|
38
|
+
) : null}
|
|
39
|
+
<div
|
|
40
|
+
className={cx('ui-workbench-navigation-panel__content', contentClassName)}
|
|
41
|
+
{...contentProps}
|
|
42
|
+
>
|
|
43
|
+
{content}
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useId,
|
|
3
|
+
useMemo,
|
|
4
|
+
useState,
|
|
5
|
+
type ComponentPropsWithRef,
|
|
6
|
+
type FormEvent,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import { Button } from '../../primitives/Button';
|
|
10
|
+
import { Checkbox } from '../../primitives/Checkbox';
|
|
11
|
+
import { EmptyState } from '../../primitives/EmptyState';
|
|
12
|
+
import { Field } from '../../primitives/Field';
|
|
13
|
+
import { Select } from '../../primitives/Select';
|
|
14
|
+
import { TextInput } from '../../primitives/TextInput';
|
|
15
|
+
import { cx } from '../../utils/cx';
|
|
16
|
+
|
|
17
|
+
export type WorkbenchSchemaFormFieldType = 'checkbox' | 'number' | 'select' | 'text';
|
|
18
|
+
|
|
19
|
+
export type WorkbenchSchemaFormFieldValue = boolean | number | string;
|
|
20
|
+
|
|
21
|
+
export type WorkbenchSchemaFormValues = Record<string, WorkbenchSchemaFormFieldValue>;
|
|
22
|
+
|
|
23
|
+
export interface WorkbenchSchemaFormOption {
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
label: ReactNode;
|
|
26
|
+
value: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface WorkbenchSchemaFormFieldBase {
|
|
30
|
+
defaultValue?: WorkbenchSchemaFormFieldValue;
|
|
31
|
+
description?: ReactNode;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
id: string;
|
|
34
|
+
label: ReactNode;
|
|
35
|
+
metadata?: Record<string, unknown>;
|
|
36
|
+
readOnly?: boolean;
|
|
37
|
+
required?: boolean;
|
|
38
|
+
validationMessage?: ReactNode;
|
|
39
|
+
validate?: (
|
|
40
|
+
value: WorkbenchSchemaFormFieldValue,
|
|
41
|
+
values: WorkbenchSchemaFormValues,
|
|
42
|
+
field: WorkbenchSchemaFormField,
|
|
43
|
+
) => ReactNode | undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface WorkbenchSchemaFormTextField extends WorkbenchSchemaFormFieldBase {
|
|
47
|
+
autocomplete?: string;
|
|
48
|
+
monospace?: boolean;
|
|
49
|
+
placeholder?: string;
|
|
50
|
+
type: 'text';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface WorkbenchSchemaFormNumberField extends WorkbenchSchemaFormFieldBase {
|
|
54
|
+
max?: number;
|
|
55
|
+
min?: number;
|
|
56
|
+
placeholder?: string;
|
|
57
|
+
step?: number;
|
|
58
|
+
type: 'number';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface WorkbenchSchemaFormSelectField extends WorkbenchSchemaFormFieldBase {
|
|
62
|
+
emptyOptionLabel?: ReactNode;
|
|
63
|
+
options: readonly WorkbenchSchemaFormOption[];
|
|
64
|
+
type: 'select';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface WorkbenchSchemaFormCheckboxField extends WorkbenchSchemaFormFieldBase {
|
|
68
|
+
type: 'checkbox';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type WorkbenchSchemaFormField =
|
|
72
|
+
| WorkbenchSchemaFormCheckboxField
|
|
73
|
+
| WorkbenchSchemaFormNumberField
|
|
74
|
+
| WorkbenchSchemaFormSelectField
|
|
75
|
+
| WorkbenchSchemaFormTextField;
|
|
76
|
+
|
|
77
|
+
export type WorkbenchSchemaFormErrors = Record<string, ReactNode>;
|
|
78
|
+
|
|
79
|
+
export interface WorkbenchSchemaFormFieldChangeContext {
|
|
80
|
+
field: WorkbenchSchemaFormField;
|
|
81
|
+
fieldId: string;
|
|
82
|
+
value: WorkbenchSchemaFormFieldValue;
|
|
83
|
+
values: WorkbenchSchemaFormValues;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface WorkbenchSchemaFormSubmitContext {
|
|
87
|
+
event: FormEvent<HTMLFormElement>;
|
|
88
|
+
values: WorkbenchSchemaFormValues;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface WorkbenchSchemaFormCancelContext {
|
|
92
|
+
values: WorkbenchSchemaFormValues;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface WorkbenchSchemaFormProps extends Omit<
|
|
96
|
+
ComponentPropsWithRef<'form'>,
|
|
97
|
+
'children' | 'defaultValue' | 'onCancel' | 'onChange' | 'onSubmit'
|
|
98
|
+
> {
|
|
99
|
+
cancelLabel?: ReactNode;
|
|
100
|
+
defaultValues?: WorkbenchSchemaFormValues;
|
|
101
|
+
disabled?: boolean;
|
|
102
|
+
emptyLabel?: ReactNode;
|
|
103
|
+
errors?: WorkbenchSchemaFormErrors;
|
|
104
|
+
fields: readonly WorkbenchSchemaFormField[];
|
|
105
|
+
onCancel?: (context: WorkbenchSchemaFormCancelContext) => void;
|
|
106
|
+
onFieldChange?: (context: WorkbenchSchemaFormFieldChangeContext) => void;
|
|
107
|
+
onSubmit?: (values: WorkbenchSchemaFormValues, context: WorkbenchSchemaFormSubmitContext) => void;
|
|
108
|
+
onValuesChange?: (
|
|
109
|
+
values: WorkbenchSchemaFormValues,
|
|
110
|
+
context: WorkbenchSchemaFormFieldChangeContext,
|
|
111
|
+
) => void;
|
|
112
|
+
readOnly?: boolean;
|
|
113
|
+
showActions?: boolean;
|
|
114
|
+
submitLabel?: ReactNode;
|
|
115
|
+
values?: WorkbenchSchemaFormValues;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function getWorkbenchSchemaFormFieldDefaultValue(
|
|
119
|
+
field: WorkbenchSchemaFormField,
|
|
120
|
+
): WorkbenchSchemaFormFieldValue {
|
|
121
|
+
if (field.defaultValue !== undefined) {
|
|
122
|
+
return coerceWorkbenchSchemaFormFieldValue(field, field.defaultValue);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (field.type === 'checkbox') return false;
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function coerceWorkbenchSchemaFormFieldValue(
|
|
130
|
+
field: WorkbenchSchemaFormField,
|
|
131
|
+
value: unknown,
|
|
132
|
+
): WorkbenchSchemaFormFieldValue {
|
|
133
|
+
if (field.type === 'checkbox') return Boolean(value);
|
|
134
|
+
if (field.type === 'number') {
|
|
135
|
+
if (value === '' || value === null || value === undefined) return '';
|
|
136
|
+
const numericValue = Number(value);
|
|
137
|
+
return Number.isFinite(numericValue) ? numericValue : '';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return value === null || value === undefined ? '' : String(value);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function normalizeWorkbenchSchemaFormValues(
|
|
144
|
+
fields: readonly WorkbenchSchemaFormField[],
|
|
145
|
+
values: Partial<WorkbenchSchemaFormValues> = {},
|
|
146
|
+
): WorkbenchSchemaFormValues {
|
|
147
|
+
return fields.reduce<WorkbenchSchemaFormValues>((normalizedValues, field) => {
|
|
148
|
+
const value =
|
|
149
|
+
values[field.id] === undefined
|
|
150
|
+
? getWorkbenchSchemaFormFieldDefaultValue(field)
|
|
151
|
+
: values[field.id];
|
|
152
|
+
|
|
153
|
+
normalizedValues[field.id] = coerceWorkbenchSchemaFormFieldValue(field, value);
|
|
154
|
+
return normalizedValues;
|
|
155
|
+
}, {});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function getWorkbenchSchemaFormFieldError({
|
|
159
|
+
field,
|
|
160
|
+
value,
|
|
161
|
+
values,
|
|
162
|
+
}: {
|
|
163
|
+
field: WorkbenchSchemaFormField;
|
|
164
|
+
value: WorkbenchSchemaFormFieldValue;
|
|
165
|
+
values: WorkbenchSchemaFormValues;
|
|
166
|
+
}) {
|
|
167
|
+
if (field.validationMessage) return field.validationMessage;
|
|
168
|
+
if (field.required && isWorkbenchSchemaFormEmptyValue(value)) {
|
|
169
|
+
return 'This field is required.';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return field.validate?.(value, values, field);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function getWorkbenchSchemaFormErrors(
|
|
176
|
+
fields: readonly WorkbenchSchemaFormField[],
|
|
177
|
+
values: Partial<WorkbenchSchemaFormValues> = {},
|
|
178
|
+
): WorkbenchSchemaFormErrors {
|
|
179
|
+
const normalizedValues = normalizeWorkbenchSchemaFormValues(fields, values);
|
|
180
|
+
|
|
181
|
+
return fields.reduce<WorkbenchSchemaFormErrors>((errors, field) => {
|
|
182
|
+
const error = getWorkbenchSchemaFormFieldError({
|
|
183
|
+
field,
|
|
184
|
+
value: normalizedValues[field.id],
|
|
185
|
+
values: normalizedValues,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
if (isRenderableWorkbenchSchemaFormError(error)) {
|
|
189
|
+
errors[field.id] = error;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return errors;
|
|
193
|
+
}, {});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function isWorkbenchSchemaFormSubmittable({
|
|
197
|
+
disabled = false,
|
|
198
|
+
errors = {},
|
|
199
|
+
readOnly = false,
|
|
200
|
+
}: {
|
|
201
|
+
disabled?: boolean;
|
|
202
|
+
errors?: WorkbenchSchemaFormErrors;
|
|
203
|
+
readOnly?: boolean;
|
|
204
|
+
}) {
|
|
205
|
+
return !disabled && !readOnly && Object.keys(errors).length === 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function isWorkbenchSchemaFormEmptyValue(value: WorkbenchSchemaFormFieldValue) {
|
|
209
|
+
return value === '' || value === false;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function isRenderableWorkbenchSchemaFormError(error: ReactNode) {
|
|
213
|
+
return error !== undefined && error !== null && error !== false && error !== '';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function WorkbenchSchemaForm({
|
|
217
|
+
cancelLabel = 'Cancel',
|
|
218
|
+
className,
|
|
219
|
+
defaultValues = {},
|
|
220
|
+
disabled = false,
|
|
221
|
+
emptyLabel = 'No settings fields',
|
|
222
|
+
errors,
|
|
223
|
+
fields,
|
|
224
|
+
onCancel,
|
|
225
|
+
onFieldChange,
|
|
226
|
+
onSubmit,
|
|
227
|
+
onValuesChange,
|
|
228
|
+
readOnly = false,
|
|
229
|
+
showActions = true,
|
|
230
|
+
submitLabel = 'Save',
|
|
231
|
+
values,
|
|
232
|
+
...props
|
|
233
|
+
}: WorkbenchSchemaFormProps) {
|
|
234
|
+
const generatedId = useId().replace(/:/g, '');
|
|
235
|
+
const [uncontrolledValues, setUncontrolledValues] = useState(() =>
|
|
236
|
+
normalizeWorkbenchSchemaFormValues(fields, defaultValues),
|
|
237
|
+
);
|
|
238
|
+
const resolvedValues = useMemo(
|
|
239
|
+
() => normalizeWorkbenchSchemaFormValues(fields, values ?? uncontrolledValues),
|
|
240
|
+
[fields, uncontrolledValues, values],
|
|
241
|
+
);
|
|
242
|
+
const resolvedErrors = useMemo(
|
|
243
|
+
() => ({
|
|
244
|
+
...getWorkbenchSchemaFormErrors(fields, resolvedValues),
|
|
245
|
+
...(errors ?? {}),
|
|
246
|
+
}),
|
|
247
|
+
[errors, fields, resolvedValues],
|
|
248
|
+
);
|
|
249
|
+
const submittable = isWorkbenchSchemaFormSubmittable({
|
|
250
|
+
disabled,
|
|
251
|
+
errors: resolvedErrors,
|
|
252
|
+
readOnly,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const updateFieldValue = (
|
|
256
|
+
field: WorkbenchSchemaFormField,
|
|
257
|
+
value: WorkbenchSchemaFormFieldValue,
|
|
258
|
+
) => {
|
|
259
|
+
const nextValues = {
|
|
260
|
+
...resolvedValues,
|
|
261
|
+
[field.id]: coerceWorkbenchSchemaFormFieldValue(field, value),
|
|
262
|
+
};
|
|
263
|
+
const context: WorkbenchSchemaFormFieldChangeContext = {
|
|
264
|
+
field,
|
|
265
|
+
fieldId: field.id,
|
|
266
|
+
value: nextValues[field.id],
|
|
267
|
+
values: nextValues,
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
if (values === undefined) {
|
|
271
|
+
setUncontrolledValues(nextValues);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
onFieldChange?.(context);
|
|
275
|
+
onValuesChange?.(nextValues, context);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
|
279
|
+
event.preventDefault();
|
|
280
|
+
if (!submittable) return;
|
|
281
|
+
|
|
282
|
+
onSubmit?.(resolvedValues, { event, values: resolvedValues });
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<form
|
|
287
|
+
className={cx('ui-workbench-schema-form', className)}
|
|
288
|
+
data-readonly={readOnly ? 'true' : undefined}
|
|
289
|
+
onSubmit={handleSubmit}
|
|
290
|
+
{...props}
|
|
291
|
+
>
|
|
292
|
+
{fields.length === 0 ? (
|
|
293
|
+
<EmptyState compact icon="codicon-settings-gear">
|
|
294
|
+
{emptyLabel}
|
|
295
|
+
</EmptyState>
|
|
296
|
+
) : (
|
|
297
|
+
<div className="ui-workbench-schema-form__fields">
|
|
298
|
+
{fields.map((field) => {
|
|
299
|
+
const fieldInputId = `${generatedId}-${field.id}`;
|
|
300
|
+
const error = resolvedErrors[field.id];
|
|
301
|
+
const errorId = isRenderableWorkbenchSchemaFormError(error)
|
|
302
|
+
? `${fieldInputId}-error`
|
|
303
|
+
: undefined;
|
|
304
|
+
const fieldDisabled = disabled || Boolean(field.disabled);
|
|
305
|
+
const fieldReadOnly = readOnly || Boolean(field.readOnly);
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<div
|
|
309
|
+
key={field.id}
|
|
310
|
+
className="ui-workbench-schema-form__field"
|
|
311
|
+
data-field-id={field.id}
|
|
312
|
+
data-field-type={field.type}
|
|
313
|
+
>
|
|
314
|
+
{renderWorkbenchSchemaFormField({
|
|
315
|
+
disabled: fieldDisabled,
|
|
316
|
+
errorId,
|
|
317
|
+
field,
|
|
318
|
+
id: fieldInputId,
|
|
319
|
+
readOnly: fieldReadOnly,
|
|
320
|
+
value: resolvedValues[field.id],
|
|
321
|
+
onChange: (value) => updateFieldValue(field, value),
|
|
322
|
+
})}
|
|
323
|
+
{isRenderableWorkbenchSchemaFormError(error) ? (
|
|
324
|
+
<div id={errorId} className="ui-workbench-schema-form__error" role="alert">
|
|
325
|
+
{error}
|
|
326
|
+
</div>
|
|
327
|
+
) : null}
|
|
328
|
+
</div>
|
|
329
|
+
);
|
|
330
|
+
})}
|
|
331
|
+
</div>
|
|
332
|
+
)}
|
|
333
|
+
{showActions ? (
|
|
334
|
+
<div className="ui-workbench-schema-form__actions">
|
|
335
|
+
<Button disabled={disabled} onClick={() => onCancel?.({ values: resolvedValues })}>
|
|
336
|
+
{cancelLabel}
|
|
337
|
+
</Button>
|
|
338
|
+
<Button disabled={!submittable} type="submit" variant="primary">
|
|
339
|
+
{submitLabel}
|
|
340
|
+
</Button>
|
|
341
|
+
</div>
|
|
342
|
+
) : null}
|
|
343
|
+
</form>
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function renderWorkbenchSchemaFormField({
|
|
348
|
+
disabled,
|
|
349
|
+
errorId,
|
|
350
|
+
field,
|
|
351
|
+
id,
|
|
352
|
+
onChange,
|
|
353
|
+
readOnly,
|
|
354
|
+
value,
|
|
355
|
+
}: {
|
|
356
|
+
disabled: boolean;
|
|
357
|
+
errorId?: string;
|
|
358
|
+
field: WorkbenchSchemaFormField;
|
|
359
|
+
id: string;
|
|
360
|
+
onChange: (value: WorkbenchSchemaFormFieldValue) => void;
|
|
361
|
+
readOnly: boolean;
|
|
362
|
+
value: WorkbenchSchemaFormFieldValue;
|
|
363
|
+
}) {
|
|
364
|
+
if (field.type === 'checkbox') {
|
|
365
|
+
return (
|
|
366
|
+
<Field description={field.description}>
|
|
367
|
+
<Checkbox
|
|
368
|
+
aria-describedby={errorId}
|
|
369
|
+
checked={Boolean(value)}
|
|
370
|
+
disabled={disabled || readOnly}
|
|
371
|
+
label={field.label}
|
|
372
|
+
onChange={(event) => onChange(event.currentTarget.checked)}
|
|
373
|
+
/>
|
|
374
|
+
</Field>
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (field.type === 'select') {
|
|
379
|
+
return (
|
|
380
|
+
<Field description={field.description} htmlFor={id} label={field.label}>
|
|
381
|
+
<Select
|
|
382
|
+
id={id}
|
|
383
|
+
aria-describedby={errorId}
|
|
384
|
+
controlWidth="full"
|
|
385
|
+
disabled={disabled || readOnly}
|
|
386
|
+
value={String(value)}
|
|
387
|
+
onChange={(event) => onChange(event.currentTarget.value)}
|
|
388
|
+
>
|
|
389
|
+
{field.emptyOptionLabel ? <option value="">{field.emptyOptionLabel}</option> : null}
|
|
390
|
+
{field.options.map((option) => (
|
|
391
|
+
<option key={option.value} disabled={option.disabled} value={option.value}>
|
|
392
|
+
{option.label}
|
|
393
|
+
</option>
|
|
394
|
+
))}
|
|
395
|
+
</Select>
|
|
396
|
+
</Field>
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return (
|
|
401
|
+
<Field description={field.description} htmlFor={id} label={field.label}>
|
|
402
|
+
<TextInput
|
|
403
|
+
id={id}
|
|
404
|
+
aria-describedby={errorId}
|
|
405
|
+
autoComplete={field.type === 'text' ? field.autocomplete : undefined}
|
|
406
|
+
controlWidth="full"
|
|
407
|
+
max={field.type === 'number' ? field.max : undefined}
|
|
408
|
+
min={field.type === 'number' ? field.min : undefined}
|
|
409
|
+
monospace={field.type === 'text' ? field.monospace : false}
|
|
410
|
+
placeholder={field.placeholder}
|
|
411
|
+
readOnly={readOnly}
|
|
412
|
+
step={field.type === 'number' ? field.step : undefined}
|
|
413
|
+
type={field.type}
|
|
414
|
+
value={String(value)}
|
|
415
|
+
disabled={disabled}
|
|
416
|
+
onChange={(event) => onChange(event.currentTarget.value)}
|
|
417
|
+
/>
|
|
418
|
+
</Field>
|
|
419
|
+
);
|
|
420
|
+
}
|