@startsimpli/ui 0.4.59 → 0.4.60
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startsimpli/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.60",
|
|
4
4
|
"description": "Shared UI components package for StartSimpli applications",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
43
43
|
"react": "^18.0.0 || ^19.0.0",
|
|
44
44
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
45
|
-
"@startsimpli/
|
|
46
|
-
"@startsimpli/
|
|
45
|
+
"@startsimpli/api": "0.5.44",
|
|
46
|
+
"@startsimpli/auth": "0.4.30"
|
|
47
47
|
},
|
|
48
48
|
"peerDependenciesMeta": {
|
|
49
49
|
"@startsimpli/api": {
|
|
@@ -24,18 +24,42 @@ function fieldId(nodeId: string, key: string): string {
|
|
|
24
24
|
return `nodeparam-${nodeId}-${key}`;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** Field label with a required-marker asterisk when the param is required. */
|
|
28
|
+
function FieldLabel({
|
|
29
|
+
id,
|
|
30
|
+
label,
|
|
31
|
+
required,
|
|
32
|
+
}: {
|
|
33
|
+
id: string;
|
|
34
|
+
label: string;
|
|
35
|
+
required?: boolean;
|
|
36
|
+
}) {
|
|
37
|
+
return (
|
|
38
|
+
<label htmlFor={id} className="text-xs font-medium text-foreground">
|
|
39
|
+
{label}
|
|
40
|
+
{required && (
|
|
41
|
+
<span className="ml-0.5 text-destructive" aria-hidden="true">
|
|
42
|
+
*
|
|
43
|
+
</span>
|
|
44
|
+
)}
|
|
45
|
+
</label>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
27
49
|
function SchemaField({
|
|
28
50
|
nodeId,
|
|
29
51
|
name,
|
|
30
52
|
schema,
|
|
31
53
|
value,
|
|
32
54
|
onChange,
|
|
55
|
+
required,
|
|
33
56
|
}: {
|
|
34
57
|
nodeId: string;
|
|
35
58
|
name: string;
|
|
36
59
|
schema: JsonSchema;
|
|
37
60
|
value: unknown;
|
|
38
61
|
onChange: (next: unknown) => void;
|
|
62
|
+
required?: boolean;
|
|
39
63
|
}) {
|
|
40
64
|
const id = fieldId(nodeId, name);
|
|
41
65
|
const label = schema.title ?? name;
|
|
@@ -43,11 +67,10 @@ function SchemaField({
|
|
|
43
67
|
if (schema.enum && schema.enum.length > 0) {
|
|
44
68
|
return (
|
|
45
69
|
<div className="flex flex-col gap-1">
|
|
46
|
-
<
|
|
47
|
-
{label}
|
|
48
|
-
</label>
|
|
70
|
+
<FieldLabel id={id} label={label} required={required} />
|
|
49
71
|
<select
|
|
50
72
|
id={id}
|
|
73
|
+
aria-required={required || undefined}
|
|
51
74
|
value={String(value ?? '')}
|
|
52
75
|
onChange={(e) => onChange(e.target.value)}
|
|
53
76
|
className="rounded-md border border-border bg-background px-2 py-1.5 text-sm outline-none focus:ring-2 focus:ring-primary"
|
|
@@ -67,14 +90,13 @@ function SchemaField({
|
|
|
67
90
|
<div className="flex items-center gap-2">
|
|
68
91
|
<input
|
|
69
92
|
id={id}
|
|
93
|
+
aria-required={required || undefined}
|
|
70
94
|
type="checkbox"
|
|
71
95
|
checked={Boolean(value)}
|
|
72
96
|
onChange={(e) => onChange(e.target.checked)}
|
|
73
97
|
className="h-4 w-4 rounded border-border"
|
|
74
98
|
/>
|
|
75
|
-
<
|
|
76
|
-
{label}
|
|
77
|
-
</label>
|
|
99
|
+
<FieldLabel id={id} label={label} required={required} />
|
|
78
100
|
</div>
|
|
79
101
|
);
|
|
80
102
|
}
|
|
@@ -82,11 +104,10 @@ function SchemaField({
|
|
|
82
104
|
if (schema.type === 'number' || schema.type === 'integer') {
|
|
83
105
|
return (
|
|
84
106
|
<div className="flex flex-col gap-1">
|
|
85
|
-
<
|
|
86
|
-
{label}
|
|
87
|
-
</label>
|
|
107
|
+
<FieldLabel id={id} label={label} required={required} />
|
|
88
108
|
<input
|
|
89
109
|
id={id}
|
|
110
|
+
aria-required={required || undefined}
|
|
90
111
|
type="number"
|
|
91
112
|
value={value === undefined || value === null ? '' : Number(value)}
|
|
92
113
|
onChange={(e) =>
|
|
@@ -101,11 +122,10 @@ function SchemaField({
|
|
|
101
122
|
// default: string
|
|
102
123
|
return (
|
|
103
124
|
<div className="flex flex-col gap-1">
|
|
104
|
-
<
|
|
105
|
-
{label}
|
|
106
|
-
</label>
|
|
125
|
+
<FieldLabel id={id} label={label} required={required} />
|
|
107
126
|
<input
|
|
108
127
|
id={id}
|
|
128
|
+
aria-required={required || undefined}
|
|
109
129
|
type="text"
|
|
110
130
|
value={value === undefined || value === null ? '' : String(value)}
|
|
111
131
|
onChange={(e) => onChange(e.target.value)}
|
|
@@ -142,6 +162,7 @@ function ParamForm({
|
|
|
142
162
|
const params = (node.parameters ?? {}) as Record<string, unknown>;
|
|
143
163
|
const properties = schema?.properties ?? {};
|
|
144
164
|
const keys = Object.keys(properties);
|
|
165
|
+
const requiredKeys = new Set(schema?.required ?? []);
|
|
145
166
|
|
|
146
167
|
const handleChange = (key: string, next: unknown) => {
|
|
147
168
|
const updated = { ...params, [key]: next };
|
|
@@ -165,6 +186,7 @@ function ParamForm({
|
|
|
165
186
|
name={key}
|
|
166
187
|
schema={properties[key]}
|
|
167
188
|
value={params[key]}
|
|
189
|
+
required={requiredKeys.has(key)}
|
|
168
190
|
onChange={(next) => handleChange(key, next)}
|
|
169
191
|
/>
|
|
170
192
|
))}
|
|
@@ -71,4 +71,30 @@ describe('NodeInspector', () => {
|
|
|
71
71
|
await user.click(screen.getByRole('tab', { name: /output/i }));
|
|
72
72
|
expect(screen.getByText(/"foo": "out"/)).toBeInTheDocument();
|
|
73
73
|
});
|
|
74
|
+
|
|
75
|
+
it('marks required fields (schema.required[]) with an indicator', () => {
|
|
76
|
+
const requiredType: typeof nodeType = {
|
|
77
|
+
...nodeType,
|
|
78
|
+
parameterSchema: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
required: ['to'],
|
|
81
|
+
properties: {
|
|
82
|
+
to: { type: 'string', title: 'Recipient' },
|
|
83
|
+
retries: { type: 'number', title: 'Retries' },
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
render(<NodeInspector node={node} nodeType={requiredType} />);
|
|
88
|
+
// required field exposes aria-required + a visible "*" marker
|
|
89
|
+
expect(screen.getByLabelText(/Recipient/)).toHaveAttribute(
|
|
90
|
+
'aria-required',
|
|
91
|
+
'true'
|
|
92
|
+
);
|
|
93
|
+
// non-required field is not marked
|
|
94
|
+
expect(screen.getByLabelText('Retries')).not.toHaveAttribute(
|
|
95
|
+
'aria-required',
|
|
96
|
+
'true'
|
|
97
|
+
);
|
|
98
|
+
expect(screen.getByText('*')).toBeInTheDocument();
|
|
99
|
+
});
|
|
74
100
|
});
|