@vendure/dashboard 3.4.1-master-202508050244 → 3.4.1-master-202508070243
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/lib/components/data-input/money-input.tsx +1 -0
- package/src/lib/components/data-input/rich-text-input.tsx +21 -7
- package/src/lib/components/shared/asset/asset-gallery.tsx +9 -2
- package/src/lib/components/shared/asset/asset-picker-dialog.tsx +1 -0
- package/src/lib/components/shared/configurable-operation-arg-input.tsx +13 -372
- package/src/lib/components/shared/custom-fields-form.tsx +19 -143
- package/src/lib/components/shared/direct-form-component-map.tsx +393 -0
- package/src/lib/components/shared/universal-field-definition.ts +118 -0
- package/src/lib/components/shared/universal-form-input.tsx +175 -0
- package/src/lib/components/shared/universal-input-components.tsx +291 -0
- package/src/lib/components/shared/value-transformers.ts +143 -0
- package/src/lib/framework/form-engine/form-schema-tools.spec.ts +138 -0
- package/src/lib/framework/form-engine/form-schema-tools.ts +4 -4
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure/dashboard",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.4.1-master-
|
|
4
|
+
"version": "3.4.1-master-202508070243",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -101,8 +101,8 @@
|
|
|
101
101
|
"@types/react-dom": "^19.0.4",
|
|
102
102
|
"@types/react-grid-layout": "^1.3.5",
|
|
103
103
|
"@uidotdev/usehooks": "^2.4.1",
|
|
104
|
-
"@vendure/common": "^3.4.1-master-
|
|
105
|
-
"@vendure/core": "^3.4.1-master-
|
|
104
|
+
"@vendure/common": "^3.4.1-master-202508070243",
|
|
105
|
+
"@vendure/core": "^3.4.1-master-202508070243",
|
|
106
106
|
"@vitejs/plugin-react": "^4.3.4",
|
|
107
107
|
"acorn": "^8.11.3",
|
|
108
108
|
"acorn-walk": "^8.3.2",
|
|
@@ -154,5 +154,5 @@
|
|
|
154
154
|
"lightningcss-linux-arm64-musl": "^1.29.3",
|
|
155
155
|
"lightningcss-linux-x64-musl": "^1.29.1"
|
|
156
156
|
},
|
|
157
|
-
"gitHead": "
|
|
157
|
+
"gitHead": "46607c032b55593eedba7adb159940e8ea24bf97"
|
|
158
158
|
}
|
|
@@ -22,10 +22,11 @@ const extensions = [
|
|
|
22
22
|
|
|
23
23
|
export interface RichTextInputProps {
|
|
24
24
|
value: string;
|
|
25
|
+
disabled?: boolean;
|
|
25
26
|
onChange: (value: string) => void;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
export function RichTextInput({ value, onChange }: Readonly<RichTextInputProps>) {
|
|
29
|
+
export function RichTextInput({ value, onChange, disabled }: Readonly<RichTextInputProps>) {
|
|
29
30
|
const isInternalUpdate = useRef(false);
|
|
30
31
|
|
|
31
32
|
const editor = useEditor({
|
|
@@ -34,13 +35,16 @@ export function RichTextInput({ value, onChange }: Readonly<RichTextInputProps>)
|
|
|
34
35
|
},
|
|
35
36
|
extensions: extensions,
|
|
36
37
|
content: value,
|
|
38
|
+
editable: !disabled,
|
|
37
39
|
onUpdate: ({ editor }) => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
if (!disabled) {
|
|
41
|
+
isInternalUpdate.current = true;
|
|
42
|
+
onChange(editor.getHTML());
|
|
43
|
+
}
|
|
40
44
|
},
|
|
41
45
|
editorProps: {
|
|
42
46
|
attributes: {
|
|
43
|
-
class:
|
|
47
|
+
class: `border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/10 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm max-h-[500px] overflow-y-auto ${disabled ? 'cursor-not-allowed opacity-50' : ''}`,
|
|
44
48
|
},
|
|
45
49
|
},
|
|
46
50
|
});
|
|
@@ -57,6 +61,13 @@ export function RichTextInput({ value, onChange }: Readonly<RichTextInputProps>)
|
|
|
57
61
|
isInternalUpdate.current = false;
|
|
58
62
|
}, [value, editor]);
|
|
59
63
|
|
|
64
|
+
// Update editor's editable state when disabled prop changes
|
|
65
|
+
useLayoutEffect(() => {
|
|
66
|
+
if (editor) {
|
|
67
|
+
editor.setEditable(!disabled);
|
|
68
|
+
}
|
|
69
|
+
}, [disabled, editor]);
|
|
70
|
+
|
|
60
71
|
if (!editor) {
|
|
61
72
|
return null;
|
|
62
73
|
}
|
|
@@ -64,13 +75,13 @@ export function RichTextInput({ value, onChange }: Readonly<RichTextInputProps>)
|
|
|
64
75
|
return (
|
|
65
76
|
<>
|
|
66
77
|
<EditorContent editor={editor} />
|
|
67
|
-
<CustomBubbleMenu editor={editor} />
|
|
78
|
+
<CustomBubbleMenu editor={editor} disabled={disabled} />
|
|
68
79
|
</>
|
|
69
80
|
);
|
|
70
81
|
}
|
|
71
82
|
|
|
72
|
-
function CustomBubbleMenu({ editor }: { editor: Editor | null }) {
|
|
73
|
-
if (!editor) return null;
|
|
83
|
+
function CustomBubbleMenu({ editor, disabled }: { editor: Editor | null; disabled?: boolean }) {
|
|
84
|
+
if (!editor || disabled) return null;
|
|
74
85
|
return (
|
|
75
86
|
<BubbleMenu editor={editor}>
|
|
76
87
|
<div className="flex items-center gap-2 bg-background p-2 rounded-md border">
|
|
@@ -80,6 +91,7 @@ function CustomBubbleMenu({ editor }: { editor: Editor | null }) {
|
|
|
80
91
|
size="icon"
|
|
81
92
|
onClick={() => editor.chain().focus().toggleBold().run()}
|
|
82
93
|
className={editor.isActive('bold') ? 'bg-accent' : ''}
|
|
94
|
+
disabled={disabled}
|
|
83
95
|
>
|
|
84
96
|
<BoldIcon className="w-4 h-4" />
|
|
85
97
|
</Button>
|
|
@@ -89,6 +101,7 @@ function CustomBubbleMenu({ editor }: { editor: Editor | null }) {
|
|
|
89
101
|
size="icon"
|
|
90
102
|
onClick={() => editor.chain().focus().toggleItalic().run()}
|
|
91
103
|
className={editor.isActive('italic') ? 'bg-accent' : ''}
|
|
104
|
+
disabled={disabled}
|
|
92
105
|
>
|
|
93
106
|
<ItalicIcon className="w-4 h-4" />
|
|
94
107
|
</Button>
|
|
@@ -98,6 +111,7 @@ function CustomBubbleMenu({ editor }: { editor: Editor | null }) {
|
|
|
98
111
|
size="icon"
|
|
99
112
|
onClick={() => editor.chain().focus().toggleStrike().run()}
|
|
100
113
|
className={editor.isActive('strike') ? 'bg-accent' : ''}
|
|
114
|
+
disabled={disabled}
|
|
101
115
|
>
|
|
102
116
|
<StrikethroughIcon className="w-4 h-4" />
|
|
103
117
|
</Button>
|
|
@@ -89,6 +89,7 @@ export interface AssetGalleryProps {
|
|
|
89
89
|
className?: string;
|
|
90
90
|
onFilesDropped?: (files: File[]) => void;
|
|
91
91
|
bulkActions?: AssetBulkAction[];
|
|
92
|
+
displayBulkActions?: boolean;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
export function AssetGallery({
|
|
@@ -102,6 +103,7 @@ export function AssetGallery({
|
|
|
102
103
|
className = '',
|
|
103
104
|
onFilesDropped,
|
|
104
105
|
bulkActions,
|
|
106
|
+
displayBulkActions = true,
|
|
105
107
|
}: AssetGalleryProps) {
|
|
106
108
|
// State
|
|
107
109
|
const [page, setPage] = useState(1);
|
|
@@ -272,7 +274,9 @@ export function AssetGallery({
|
|
|
272
274
|
)}
|
|
273
275
|
|
|
274
276
|
{/* Bulk actions bar */}
|
|
275
|
-
|
|
277
|
+
{displayBulkActions ? (
|
|
278
|
+
<AssetBulkActions selection={selected} bulkActions={bulkActions} refetch={refetch} />
|
|
279
|
+
) : null}
|
|
276
280
|
|
|
277
281
|
<div
|
|
278
282
|
{...getRootProps()}
|
|
@@ -291,7 +295,10 @@ export function AssetGallery({
|
|
|
291
295
|
</div>
|
|
292
296
|
)}
|
|
293
297
|
|
|
294
|
-
<div
|
|
298
|
+
<div
|
|
299
|
+
data-asset-gallery
|
|
300
|
+
className="grid grid-cols-1 xs:grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-3 p-1"
|
|
301
|
+
>
|
|
295
302
|
{isLoading ? (
|
|
296
303
|
<div className="col-span-full flex justify-center py-12">
|
|
297
304
|
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
import { InputComponent } from '@/vdb/framework/component-registry/dynamic-component.js';
|
|
2
1
|
import { ConfigurableOperationDefFragment } from '@/vdb/graphql/fragments.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { AffixedInput } from '../data-input/affixed-input.js';
|
|
6
|
-
import { ConfigurableOperationListInput } from '../data-input/configurable-operation-list-input.js';
|
|
7
|
-
import { DateTimeInput } from '../data-input/datetime-input.js';
|
|
8
|
-
import { DefaultRelationInput } from '../data-input/default-relation-input.js';
|
|
9
|
-
import { FacetValueInput } from '../data-input/facet-value-input.js';
|
|
10
|
-
import { Input } from '../ui/input.js';
|
|
11
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select.js';
|
|
12
|
-
import { Switch } from '../ui/switch.js';
|
|
13
|
-
import { Textarea } from '../ui/textarea.js';
|
|
2
|
+
import { configArgToUniversal } from './universal-field-definition.js';
|
|
3
|
+
import { UniversalFormInput } from './universal-form-input.js';
|
|
14
4
|
|
|
15
5
|
export interface ConfigurableOperationArgInputProps {
|
|
16
6
|
definition: ConfigurableOperationDefFragment['args'][number];
|
|
@@ -20,30 +10,6 @@ export interface ConfigurableOperationArgInputProps {
|
|
|
20
10
|
position?: number;
|
|
21
11
|
}
|
|
22
12
|
|
|
23
|
-
/**
|
|
24
|
-
* Maps Vendure UI component names to their corresponding Dashboard input component IDs
|
|
25
|
-
*/
|
|
26
|
-
const UI_COMPONENT_MAP = {
|
|
27
|
-
'number-form-input': 'vendure:numberInput',
|
|
28
|
-
'currency-form-input': 'vendure:currencyInput',
|
|
29
|
-
'facet-value-form-input': 'facet-value-input',
|
|
30
|
-
'product-selector-form-input': 'vendure:productSelectorInput',
|
|
31
|
-
'customer-group-form-input': 'vendure:customerGroupInput',
|
|
32
|
-
'date-form-input': 'date-input',
|
|
33
|
-
'textarea-form-input': 'textarea-input',
|
|
34
|
-
'password-form-input': 'vendure:passwordInput',
|
|
35
|
-
'json-editor-form-input': 'vendure:jsonEditorInput',
|
|
36
|
-
'html-editor-form-input': 'vendure:htmlEditorInput',
|
|
37
|
-
'rich-text-form-input': 'vendure:richTextInput',
|
|
38
|
-
'boolean-form-input': 'boolean-input',
|
|
39
|
-
'select-form-input': 'select-input',
|
|
40
|
-
'text-form-input': 'vendure:textInput',
|
|
41
|
-
'product-multi-form-input': 'vendure:productMultiInput',
|
|
42
|
-
'combination-mode-form-input': 'vendure:combinationModeInput',
|
|
43
|
-
'relation-form-input': 'vendure:relationInput',
|
|
44
|
-
'struct-form-input': 'vendure:structInput',
|
|
45
|
-
} as const;
|
|
46
|
-
|
|
47
13
|
export function ConfigurableOperationArgInput({
|
|
48
14
|
definition,
|
|
49
15
|
value,
|
|
@@ -51,345 +17,20 @@ export function ConfigurableOperationArgInput({
|
|
|
51
17
|
readOnly,
|
|
52
18
|
position,
|
|
53
19
|
}: Readonly<ConfigurableOperationArgInputProps>) {
|
|
54
|
-
const
|
|
55
|
-
const argType = definition.type as ConfigArgType;
|
|
56
|
-
const isList = definition.list ?? false;
|
|
57
|
-
|
|
58
|
-
// Handle specific UI components first
|
|
59
|
-
if (uiComponent) {
|
|
60
|
-
switch (uiComponent) {
|
|
61
|
-
case 'product-selector-form-input': {
|
|
62
|
-
const entityType =
|
|
63
|
-
(definition.ui as any)?.selectionMode === 'variant' ? 'ProductVariant' : 'Product';
|
|
64
|
-
const isMultiple = (definition.ui as any)?.multiple ?? false;
|
|
65
|
-
return (
|
|
66
|
-
<DefaultRelationInput
|
|
67
|
-
fieldDef={
|
|
68
|
-
{
|
|
69
|
-
entity: entityType,
|
|
70
|
-
list: isMultiple,
|
|
71
|
-
} as RelationCustomFieldConfig
|
|
72
|
-
}
|
|
73
|
-
field={{
|
|
74
|
-
value,
|
|
75
|
-
onChange,
|
|
76
|
-
onBlur: () => {},
|
|
77
|
-
name: '',
|
|
78
|
-
ref: () => {},
|
|
79
|
-
}}
|
|
80
|
-
disabled={readOnly}
|
|
81
|
-
/>
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
case 'customer-group-form-input': {
|
|
85
|
-
const isCustomerGroupMultiple = (definition.ui as any)?.multiple ?? false;
|
|
86
|
-
return (
|
|
87
|
-
<DefaultRelationInput
|
|
88
|
-
fieldDef={
|
|
89
|
-
{
|
|
90
|
-
entity: 'CustomerGroup',
|
|
91
|
-
list: isCustomerGroupMultiple,
|
|
92
|
-
} as RelationCustomFieldConfig
|
|
93
|
-
}
|
|
94
|
-
field={{
|
|
95
|
-
value,
|
|
96
|
-
onChange,
|
|
97
|
-
onBlur: () => {},
|
|
98
|
-
name: '',
|
|
99
|
-
ref: () => {},
|
|
100
|
-
}}
|
|
101
|
-
disabled={readOnly}
|
|
102
|
-
/>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
case 'facet-value-form-input': {
|
|
106
|
-
return <FacetValueInput value={value} onChange={onChange} readOnly={readOnly} />;
|
|
107
|
-
}
|
|
108
|
-
case 'select-form-input': {
|
|
109
|
-
return (
|
|
110
|
-
<SelectInput
|
|
111
|
-
definition={definition}
|
|
112
|
-
value={value}
|
|
113
|
-
onChange={onChange}
|
|
114
|
-
readOnly={readOnly}
|
|
115
|
-
/>
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
case 'textarea-form-input': {
|
|
119
|
-
return (
|
|
120
|
-
<TextareaInput
|
|
121
|
-
definition={definition}
|
|
122
|
-
value={value}
|
|
123
|
-
onChange={onChange}
|
|
124
|
-
readOnly={readOnly}
|
|
125
|
-
/>
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
case 'date-form-input': {
|
|
129
|
-
return <DateTimeInput value={value} onChange={onChange} disabled={readOnly} />;
|
|
130
|
-
}
|
|
131
|
-
case 'boolean-form-input': {
|
|
132
|
-
return <BooleanInput value={value} onChange={onChange} readOnly={readOnly} />;
|
|
133
|
-
}
|
|
134
|
-
case 'number-form-input': {
|
|
135
|
-
return (
|
|
136
|
-
<NumberInput
|
|
137
|
-
definition={definition}
|
|
138
|
-
value={value}
|
|
139
|
-
onChange={onChange}
|
|
140
|
-
readOnly={readOnly}
|
|
141
|
-
/>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
case 'currency-form-input': {
|
|
145
|
-
return (
|
|
146
|
-
<CurrencyInput
|
|
147
|
-
definition={definition}
|
|
148
|
-
value={value}
|
|
149
|
-
onChange={onChange}
|
|
150
|
-
readOnly={readOnly}
|
|
151
|
-
/>
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
default: {
|
|
155
|
-
// Try to use the component registry for other UI components
|
|
156
|
-
const componentId = UI_COMPONENT_MAP[uiComponent as keyof typeof UI_COMPONENT_MAP];
|
|
157
|
-
if (componentId) {
|
|
158
|
-
try {
|
|
159
|
-
return (
|
|
160
|
-
<InputComponent
|
|
161
|
-
id={componentId}
|
|
162
|
-
value={value}
|
|
163
|
-
onChange={onChange}
|
|
164
|
-
readOnly={readOnly}
|
|
165
|
-
position={position}
|
|
166
|
-
definition={definition}
|
|
167
|
-
{...(definition.ui as any)}
|
|
168
|
-
/>
|
|
169
|
-
);
|
|
170
|
-
} catch (error) {
|
|
171
|
-
console.warn(
|
|
172
|
-
`Failed to load UI component ${uiComponent}, falling back to type-based input`,
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Handle list fields with array wrapper
|
|
181
|
-
if (isList) {
|
|
182
|
-
return (
|
|
183
|
-
<ConfigurableOperationListInput
|
|
184
|
-
definition={definition}
|
|
185
|
-
value={value}
|
|
186
|
-
onChange={onChange}
|
|
187
|
-
readOnly={readOnly}
|
|
188
|
-
/>
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Fall back to type-based rendering
|
|
193
|
-
switch (argType) {
|
|
194
|
-
case 'boolean':
|
|
195
|
-
return <BooleanInput value={value} onChange={onChange} readOnly={readOnly} />;
|
|
196
|
-
|
|
197
|
-
case 'int':
|
|
198
|
-
case 'float':
|
|
199
|
-
return (
|
|
200
|
-
<NumberInput definition={definition} value={value} onChange={onChange} readOnly={readOnly} />
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
case 'datetime':
|
|
204
|
-
return <DateTimeInput value={value} onChange={onChange} disabled={readOnly} />;
|
|
205
|
-
|
|
206
|
-
case 'ID':
|
|
207
|
-
// ID fields typically need specialized selectors
|
|
208
|
-
return (
|
|
209
|
-
<Input
|
|
210
|
-
type="text"
|
|
211
|
-
value={value || ''}
|
|
212
|
-
onChange={e => onChange(e.target.value)}
|
|
213
|
-
disabled={readOnly}
|
|
214
|
-
placeholder="Enter ID..."
|
|
215
|
-
className="bg-background"
|
|
216
|
-
/>
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
case 'string':
|
|
220
|
-
default:
|
|
221
|
-
return (
|
|
222
|
-
<Input
|
|
223
|
-
type="text"
|
|
224
|
-
value={value || ''}
|
|
225
|
-
onChange={e => onChange(e.target.value)}
|
|
226
|
-
disabled={readOnly}
|
|
227
|
-
className="bg-background"
|
|
228
|
-
/>
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Boolean input component
|
|
235
|
-
*/
|
|
236
|
-
function BooleanInput({
|
|
237
|
-
value,
|
|
238
|
-
onChange,
|
|
239
|
-
readOnly,
|
|
240
|
-
}: Readonly<{
|
|
241
|
-
value: string;
|
|
242
|
-
onChange: (value: string) => void;
|
|
243
|
-
readOnly?: boolean;
|
|
244
|
-
}>) {
|
|
245
|
-
const boolValue = value === 'true';
|
|
246
|
-
|
|
247
|
-
return (
|
|
248
|
-
<Switch
|
|
249
|
-
checked={boolValue}
|
|
250
|
-
onCheckedChange={checked => onChange(checked.toString())}
|
|
251
|
-
disabled={readOnly}
|
|
252
|
-
/>
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Number input component with support for UI configuration
|
|
258
|
-
*/
|
|
259
|
-
function NumberInput({
|
|
260
|
-
definition,
|
|
261
|
-
value,
|
|
262
|
-
onChange,
|
|
263
|
-
readOnly,
|
|
264
|
-
}: Readonly<{
|
|
265
|
-
definition: ConfigurableOperationDefFragment['args'][number];
|
|
266
|
-
value: string;
|
|
267
|
-
onChange: (value: string) => void;
|
|
268
|
-
readOnly?: boolean;
|
|
269
|
-
}>) {
|
|
270
|
-
const ui = definition.ui as any;
|
|
271
|
-
const isFloat = (definition.type as ConfigArgType) === 'float';
|
|
272
|
-
const min = ui?.min;
|
|
273
|
-
const max = ui?.max;
|
|
274
|
-
const step = ui?.step || (isFloat ? 0.01 : 1);
|
|
275
|
-
const prefix = ui?.prefix;
|
|
276
|
-
const suffix = ui?.suffix;
|
|
277
|
-
|
|
278
|
-
const numericValue = value ? parseFloat(value) : '';
|
|
279
|
-
|
|
280
|
-
return (
|
|
281
|
-
<AffixedInput
|
|
282
|
-
type="number"
|
|
283
|
-
value={numericValue}
|
|
284
|
-
onChange={e => {
|
|
285
|
-
const val = e.target.valueAsNumber;
|
|
286
|
-
onChange(isNaN(val) ? '' : val.toString());
|
|
287
|
-
}}
|
|
288
|
-
disabled={readOnly}
|
|
289
|
-
min={min}
|
|
290
|
-
max={max}
|
|
291
|
-
step={step}
|
|
292
|
-
prefix={prefix}
|
|
293
|
-
suffix={suffix}
|
|
294
|
-
className="bg-background"
|
|
295
|
-
/>
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Currency input component
|
|
301
|
-
*/
|
|
302
|
-
function CurrencyInput({
|
|
303
|
-
definition,
|
|
304
|
-
value,
|
|
305
|
-
onChange,
|
|
306
|
-
readOnly,
|
|
307
|
-
}: Readonly<{
|
|
308
|
-
definition: ConfigurableOperationDefFragment['args'][number];
|
|
309
|
-
value: string;
|
|
310
|
-
onChange: (value: string) => void;
|
|
311
|
-
readOnly?: boolean;
|
|
312
|
-
}>) {
|
|
313
|
-
const numericValue = value ? parseInt(value, 10) : '';
|
|
314
|
-
|
|
20
|
+
const universalFieldDef = configArgToUniversal(definition);
|
|
315
21
|
return (
|
|
316
|
-
<
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
22
|
+
<UniversalFormInput
|
|
23
|
+
fieldDef={universalFieldDef}
|
|
24
|
+
field={{
|
|
25
|
+
value,
|
|
26
|
+
onChange,
|
|
27
|
+
onBlur: () => {},
|
|
28
|
+
name: definition.name,
|
|
29
|
+
ref: () => {},
|
|
322
30
|
}}
|
|
31
|
+
valueMode="json-string"
|
|
323
32
|
disabled={readOnly}
|
|
324
|
-
|
|
325
|
-
step={1}
|
|
326
|
-
prefix="$"
|
|
327
|
-
className="bg-background"
|
|
328
|
-
/>
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Select input component with options
|
|
334
|
-
*/
|
|
335
|
-
function SelectInput({
|
|
336
|
-
definition,
|
|
337
|
-
value,
|
|
338
|
-
onChange,
|
|
339
|
-
readOnly,
|
|
340
|
-
}: Readonly<{
|
|
341
|
-
definition: ConfigurableOperationDefFragment['args'][number];
|
|
342
|
-
value: string;
|
|
343
|
-
onChange: (value: string) => void;
|
|
344
|
-
readOnly?: boolean;
|
|
345
|
-
}>) {
|
|
346
|
-
const ui = definition.ui as any;
|
|
347
|
-
const options = ui?.options || [];
|
|
348
|
-
|
|
349
|
-
return (
|
|
350
|
-
<Select value={value} onValueChange={onChange} disabled={readOnly}>
|
|
351
|
-
<SelectTrigger className="bg-background mb-0">
|
|
352
|
-
<SelectValue placeholder="Select an option..." />
|
|
353
|
-
</SelectTrigger>
|
|
354
|
-
<SelectContent>
|
|
355
|
-
{options.map((option: any) => (
|
|
356
|
-
<SelectItem key={option.value} value={option.value}>
|
|
357
|
-
{typeof option.label === 'string'
|
|
358
|
-
? option.label
|
|
359
|
-
: option.label?.[0]?.value || option.value}
|
|
360
|
-
</SelectItem>
|
|
361
|
-
))}
|
|
362
|
-
</SelectContent>
|
|
363
|
-
</Select>
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Textarea input component
|
|
369
|
-
*/
|
|
370
|
-
function TextareaInput({
|
|
371
|
-
definition,
|
|
372
|
-
value,
|
|
373
|
-
onChange,
|
|
374
|
-
readOnly,
|
|
375
|
-
}: Readonly<{
|
|
376
|
-
definition: ConfigurableOperationDefFragment['args'][number];
|
|
377
|
-
value: string;
|
|
378
|
-
onChange: (value: string) => void;
|
|
379
|
-
readOnly?: boolean;
|
|
380
|
-
}>) {
|
|
381
|
-
const ui = definition.ui as any;
|
|
382
|
-
const spellcheck = ui?.spellcheck ?? true;
|
|
383
|
-
|
|
384
|
-
return (
|
|
385
|
-
<Textarea
|
|
386
|
-
value={value || ''}
|
|
387
|
-
onChange={e => onChange(e.target.value)}
|
|
388
|
-
disabled={readOnly}
|
|
389
|
-
spellCheck={spellcheck}
|
|
390
|
-
placeholder="Enter text..."
|
|
391
|
-
rows={4}
|
|
392
|
-
className="bg-background"
|
|
33
|
+
position={position}
|
|
393
34
|
/>
|
|
394
35
|
);
|
|
395
36
|
}
|