@vendure/dashboard 3.4.1-master-202508040248 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vendure/dashboard",
3
3
  "private": false,
4
- "version": "3.4.1-master-202508040248",
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-202508040248",
105
- "@vendure/core": "^3.4.1-master-202508040248",
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": "db37b25aa195de9bbb384986f18e49c85e4a0ac7"
157
+ "gitHead": "46607c032b55593eedba7adb159940e8ea24bf97"
158
158
  }
@@ -50,6 +50,7 @@ function MoneyInputInternal({ value, currency, onChange }: DataInputComponentPro
50
50
  return (
51
51
  <AffixedInput
52
52
  type="text"
53
+ className="bg-background"
53
54
  value={displayValue}
54
55
  onChange={e => {
55
56
  const inputValue = e.target.value;
@@ -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
- isInternalUpdate.current = true;
39
- onChange(editor.getHTML());
40
+ if (!disabled) {
41
+ isInternalUpdate.current = true;
42
+ onChange(editor.getHTML());
43
+ }
40
44
  },
41
45
  editorProps: {
42
46
  attributes: {
43
- 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',
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
- <AssetBulkActions selection={selected} bulkActions={bulkActions} refetch={refetch} />
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 data-asset-gallery 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">
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" />
@@ -50,6 +50,7 @@ export function AssetPickerDialog({
50
50
  multiSelect="manual"
51
51
  initialSelectedAssets={initialSelectedAssets}
52
52
  fixedHeight={true}
53
+ displayBulkActions={false}
53
54
  />
54
55
  </div>
55
56
 
@@ -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 { RelationCustomFieldConfig } from '@vendure/common/lib/generated-types';
4
- import { ConfigArgType } from '@vendure/core';
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 uiComponent = (definition.ui as any)?.component;
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
- <AffixedInput
317
- type="number"
318
- value={numericValue}
319
- onChange={e => {
320
- const val = e.target.valueAsNumber;
321
- onChange(isNaN(val) ? '0' : val.toString());
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
- min={0}
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
  }