@vendure/dashboard 3.5.1-master-202511010232 → 3.5.1-master-202511040232
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/index.ts +3 -0
- package/src/lib/components/data-input/string-list-input.tsx +106 -0
- package/src/lib/framework/extension-api/logic/navigation.ts +3 -0
- package/src/lib/framework/form-engine/form-control-adapter.tsx +2 -1
- package/src/lib/graphql/api.ts +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure/dashboard",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.5.1-master-
|
|
4
|
+
"version": "3.5.1-master-202511040232",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -154,8 +154,8 @@
|
|
|
154
154
|
"@storybook/addon-vitest": "^10.0.0-beta.9",
|
|
155
155
|
"@storybook/react-vite": "^10.0.0-beta.9",
|
|
156
156
|
"@types/node": "^22.13.4",
|
|
157
|
-
"@vendure/common": "^3.5.1-master-
|
|
158
|
-
"@vendure/core": "^3.5.1-master-
|
|
157
|
+
"@vendure/common": "^3.5.1-master-202511040232",
|
|
158
|
+
"@vendure/core": "^3.5.1-master-202511040232",
|
|
159
159
|
"@vitest/browser": "^3.2.4",
|
|
160
160
|
"@vitest/coverage-v8": "^3.2.4",
|
|
161
161
|
"eslint": "^9.19.0",
|
|
@@ -172,5 +172,5 @@
|
|
|
172
172
|
"lightningcss-linux-arm64-musl": "^1.29.3",
|
|
173
173
|
"lightningcss-linux-x64-musl": "^1.29.1"
|
|
174
174
|
},
|
|
175
|
-
"gitHead": "
|
|
175
|
+
"gitHead": "fe1c9639d991ff7ebfb1188c4d9a62f1b7c74a04"
|
|
176
176
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { X } from 'lucide-react';
|
|
2
|
+
import { KeyboardEvent, useId, useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { Badge } from '@/vdb/components/ui/badge.js';
|
|
5
|
+
import { Input } from '@/vdb/components/ui/input.js';
|
|
6
|
+
import type { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
7
|
+
import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
|
|
8
|
+
import { cn } from '@/vdb/lib/utils.js';
|
|
9
|
+
import { useLingui } from '@lingui/react';
|
|
10
|
+
|
|
11
|
+
export function StringListInput({
|
|
12
|
+
value,
|
|
13
|
+
onChange,
|
|
14
|
+
onBlur,
|
|
15
|
+
disabled,
|
|
16
|
+
name,
|
|
17
|
+
fieldDef,
|
|
18
|
+
}: DashboardFormComponentProps) {
|
|
19
|
+
const [inputValue, setInputValue] = useState('');
|
|
20
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
21
|
+
const { i18n } = useLingui();
|
|
22
|
+
const isDisabled = isReadonlyField(fieldDef) || disabled;
|
|
23
|
+
const id = useId();
|
|
24
|
+
|
|
25
|
+
const items = Array.isArray(value) ? value : [];
|
|
26
|
+
|
|
27
|
+
const addItem = (item: string) => {
|
|
28
|
+
const trimmedItem = item.trim();
|
|
29
|
+
if (trimmedItem) {
|
|
30
|
+
onChange([...items, trimmedItem]);
|
|
31
|
+
setInputValue('');
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const removeItem = (indexToRemove: number) => {
|
|
36
|
+
onChange(items.filter((_, index) => index !== indexToRemove));
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
|
40
|
+
if (e.key === 'Enter' || e.key === ',') {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
addItem(inputValue);
|
|
43
|
+
} else if (e.key === 'Backspace' && !inputValue && items.length > 0) {
|
|
44
|
+
// Remove last item when backspace is pressed on empty input
|
|
45
|
+
removeItem(items.length - 1);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const handleInputBlur = () => {
|
|
50
|
+
// Add current input value as item on blur if there's any
|
|
51
|
+
if (inputValue.trim()) {
|
|
52
|
+
addItem(inputValue);
|
|
53
|
+
}
|
|
54
|
+
onBlur?.();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div
|
|
59
|
+
className={cn(
|
|
60
|
+
'flex min-h-10 w-full flex-wrap gap-2',
|
|
61
|
+
isDisabled && 'cursor-not-allowed opacity-50',
|
|
62
|
+
)}
|
|
63
|
+
>
|
|
64
|
+
{!isDisabled && (
|
|
65
|
+
<Input
|
|
66
|
+
ref={inputRef}
|
|
67
|
+
type="text"
|
|
68
|
+
value={inputValue}
|
|
69
|
+
onChange={e => setInputValue(e.target.value)}
|
|
70
|
+
onKeyDown={handleKeyDown}
|
|
71
|
+
onBlur={handleInputBlur}
|
|
72
|
+
name={name}
|
|
73
|
+
placeholder={i18n.t('Type and press Enter or comma to add...')}
|
|
74
|
+
className="min-w-[120px]"
|
|
75
|
+
/>
|
|
76
|
+
)}
|
|
77
|
+
<div className="flex flex-wrap gap-1 items-start justify-start">
|
|
78
|
+
{items.map((item, index) => (
|
|
79
|
+
<Badge key={id + index} variant="secondary">
|
|
80
|
+
<span>{item}</span>
|
|
81
|
+
{!isDisabled && (
|
|
82
|
+
<button
|
|
83
|
+
type="button"
|
|
84
|
+
onClick={e => {
|
|
85
|
+
e.stopPropagation();
|
|
86
|
+
removeItem(index);
|
|
87
|
+
}}
|
|
88
|
+
className={cn(
|
|
89
|
+
'ml-1 rounded-full outline-none ring-offset-background',
|
|
90
|
+
'hover:bg-muted focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
|
91
|
+
)}
|
|
92
|
+
aria-label={`Remove ${item}`}
|
|
93
|
+
>
|
|
94
|
+
<X className="h-3 w-3" />
|
|
95
|
+
</button>
|
|
96
|
+
)}
|
|
97
|
+
</Badge>
|
|
98
|
+
))}
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
StringListInput.metadata = {
|
|
105
|
+
isListInput: true,
|
|
106
|
+
};
|
|
@@ -26,6 +26,9 @@ export function registerNavigationExtensions(
|
|
|
26
26
|
id: route.navMenuItem.id ?? route.path,
|
|
27
27
|
title: route.navMenuItem.title ?? route.path,
|
|
28
28
|
order: route.navMenuItem.order,
|
|
29
|
+
requiresPermission: route.navMenuItem.requiresPermission,
|
|
30
|
+
icon: route.navMenuItem.icon,
|
|
31
|
+
placement: route.navMenuItem.placement,
|
|
29
32
|
};
|
|
30
33
|
addNavMenuItem(item, route.navMenuItem.sectionId);
|
|
31
34
|
}
|
|
@@ -2,6 +2,7 @@ import { JSX, useMemo } from 'react';
|
|
|
2
2
|
import { ControllerRenderProps } from 'react-hook-form';
|
|
3
3
|
|
|
4
4
|
import { CustomFieldListInput } from '@/vdb/components/data-input/custom-field-list-input.js';
|
|
5
|
+
import { StringListInput } from '@/vdb/components/data-input/string-list-input.js';
|
|
5
6
|
import { StructFormInput } from '@/vdb/components/data-input/struct-form-input.js';
|
|
6
7
|
import { ConfigurableOperationListInput } from '../../components/data-input/configurable-operation-list-input.js';
|
|
7
8
|
|
|
@@ -124,7 +125,7 @@ function renderListField(
|
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
if (fieldDef.type === 'string') {
|
|
127
|
-
return <
|
|
128
|
+
return <StringListInput {...fieldWithTransform} fieldDef={fieldDef} />;
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
return (
|
package/src/lib/graphql/api.ts
CHANGED
|
@@ -60,7 +60,7 @@ const awesomeClient = new AwesomeGraphQLClient({
|
|
|
60
60
|
credentials: 'include',
|
|
61
61
|
mode: 'cors',
|
|
62
62
|
}).then(res => {
|
|
63
|
-
const authToken = res.headers.get(
|
|
63
|
+
const authToken = res.headers.get(uiConfig.api.authTokenHeaderKey);
|
|
64
64
|
if (authToken) {
|
|
65
65
|
localStorage.setItem(LS_KEY_SESSION_TOKEN, authToken);
|
|
66
66
|
}
|