@webbycrown/advanced-fields 1.0.0 → 1.0.1
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/admin/jsconfig.json +10 -0
- package/admin/src/components/AdvancedCheckbox/index.jsx +397 -0
- package/admin/src/components/AdvancedInput/index.jsx +163 -0
- package/admin/src/components/AdvancedRadio/index.jsx +344 -0
- package/admin/src/components/Initializer.jsx +18 -0
- package/admin/src/components/PluginIcon.jsx +115 -0
- package/admin/src/index.js +739 -0
- package/admin/src/pages/App.jsx +13 -0
- package/admin/src/pluginId.js +3 -0
- package/admin/src/translations/en.json +86 -0
- package/admin/src/utils/getTranslation.js +5 -0
- package/index.js +202 -0
- package/package.json +5 -1
- package/strapi-server.js +186 -0
- package/strapi-server.mjs +74 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useIntl } from "react-intl";
|
|
4
|
+
import { Field, Box, Flex, Typography } from "@strapi/design-system";
|
|
5
|
+
import { useState, useEffect } from "react";
|
|
6
|
+
|
|
7
|
+
const CheckboxInput = ({
|
|
8
|
+
attribute = {},
|
|
9
|
+
description = { id: "", defaultMessage: "" },
|
|
10
|
+
disabled,
|
|
11
|
+
error,
|
|
12
|
+
intlLabel = { id: "", defaultMessage: "" },
|
|
13
|
+
labelAction,
|
|
14
|
+
name,
|
|
15
|
+
onChange,
|
|
16
|
+
required,
|
|
17
|
+
value,
|
|
18
|
+
}) => {
|
|
19
|
+
const { formatMessage } = useIntl();
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
checkboxType = "multiple",
|
|
23
|
+
layout = "vertical",
|
|
24
|
+
minChoices = 0,
|
|
25
|
+
maxChoices = 0,
|
|
26
|
+
defaultSelected = "",
|
|
27
|
+
checkboxOptions = "",
|
|
28
|
+
customErrorMessage = "",
|
|
29
|
+
} = attribute.options || attribute;
|
|
30
|
+
|
|
31
|
+
// Debug logging for checkboxType detection
|
|
32
|
+
// console.log('AdvancedCheckbox options detection:', {
|
|
33
|
+
// attribute,
|
|
34
|
+
// attributeOptions: attribute.options,
|
|
35
|
+
// checkboxType,
|
|
36
|
+
// checkboxOptions,
|
|
37
|
+
// allOptions: attribute.options || attribute
|
|
38
|
+
// });
|
|
39
|
+
|
|
40
|
+
// Initialize with default values if available
|
|
41
|
+
const getInitialValues = () => {
|
|
42
|
+
if (checkboxType === "single") {
|
|
43
|
+
// For single checkbox mode, return array with one value (like radio button)
|
|
44
|
+
if (value && Array.isArray(value) && value.length > 0) {
|
|
45
|
+
return value;
|
|
46
|
+
} else if (value && typeof value === "string" && value.trim()) {
|
|
47
|
+
return [value.trim()];
|
|
48
|
+
} else if (defaultSelected && typeof defaultSelected === "string" && defaultSelected.trim()) {
|
|
49
|
+
return [defaultSelected.trim()];
|
|
50
|
+
} else if (defaultSelected && Array.isArray(defaultSelected) && defaultSelected.length > 0) {
|
|
51
|
+
return defaultSelected;
|
|
52
|
+
}
|
|
53
|
+
return [];
|
|
54
|
+
} else {
|
|
55
|
+
// For multiple checkboxes, return array of selected values
|
|
56
|
+
if (value && Array.isArray(value)) {
|
|
57
|
+
return value;
|
|
58
|
+
} else if (value && typeof value === "string") {
|
|
59
|
+
return value.split(",").map(v => v.trim()).filter(v => v);
|
|
60
|
+
} else if (defaultSelected) {
|
|
61
|
+
return defaultSelected.split(",").map(v => v.trim()).filter(v => v);
|
|
62
|
+
}
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const [fieldValue, setFieldValue] = useState(getInitialValues);
|
|
68
|
+
const [validationError, setValidationError] = useState(null);
|
|
69
|
+
const [hasInteracted, setHasInteracted] = useState(false);
|
|
70
|
+
const [isInitialized, setIsInitialized] = useState(false);
|
|
71
|
+
|
|
72
|
+
// Parse checkbox options
|
|
73
|
+
const options = checkboxOptions
|
|
74
|
+
.split("\n")
|
|
75
|
+
.filter((opt) => opt.trim())
|
|
76
|
+
.map((opt) => {
|
|
77
|
+
const [value, label] = opt.split("|");
|
|
78
|
+
return { value: value?.trim() || "", label: label?.trim() || value?.trim() || "" };
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Initialize selected values - only run once on mount
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
const initialValues = getInitialValues();
|
|
84
|
+
|
|
85
|
+
console.log('AdvancedCheckbox initialization:', {
|
|
86
|
+
value,
|
|
87
|
+
defaultSelected,
|
|
88
|
+
initialValues,
|
|
89
|
+
required,
|
|
90
|
+
error
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
setFieldValue(initialValues);
|
|
94
|
+
|
|
95
|
+
// Validate the initial values
|
|
96
|
+
const validationResult = validateSelection(initialValues);
|
|
97
|
+
setValidationError(validationResult);
|
|
98
|
+
|
|
99
|
+
// Only trigger onChange if we have initial values and it's different from current value
|
|
100
|
+
// AND only if the value prop is not already set (to avoid overriding during publish)
|
|
101
|
+
// AND only if this is the first initialization
|
|
102
|
+
if (onChange && initialValues.length > 0 && (!value || (Array.isArray(value) && value.length === 0)) && !isInitialized) {
|
|
103
|
+
// Use setTimeout to ensure this happens after the component is fully mounted
|
|
104
|
+
setTimeout(() => {
|
|
105
|
+
onChange({
|
|
106
|
+
target: {
|
|
107
|
+
value: initialValues,
|
|
108
|
+
name: name,
|
|
109
|
+
id: name
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
setIsInitialized(true);
|
|
113
|
+
}, 0);
|
|
114
|
+
} else if (!isInitialized) {
|
|
115
|
+
setIsInitialized(true);
|
|
116
|
+
}
|
|
117
|
+
}, []); // Remove dependencies to only run once on mount
|
|
118
|
+
|
|
119
|
+
// Handle external value changes (like when loading existing data)
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
// Only update if the value prop has changed and it's not empty
|
|
122
|
+
// This handles cases like loading existing content
|
|
123
|
+
if (value && Array.isArray(value) && value.length > 0) {
|
|
124
|
+
setFieldValue(value);
|
|
125
|
+
const validationResult = validateSelection(value);
|
|
126
|
+
setValidationError(validationResult);
|
|
127
|
+
}
|
|
128
|
+
}, [value]);
|
|
129
|
+
|
|
130
|
+
// Validation function - this should match server-side validation
|
|
131
|
+
const validateSelection = (val) => {
|
|
132
|
+
const values = Array.isArray(val) ? val : [];
|
|
133
|
+
|
|
134
|
+
// Check required validation first
|
|
135
|
+
if (required && values.length === 0) {
|
|
136
|
+
return customErrorMessage || 'This field is required';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// If field is empty and not required, no validation error
|
|
140
|
+
if (values.length === 0) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Check min/max choices validation (only for multiple mode)
|
|
145
|
+
if (checkboxType === "multiple") {
|
|
146
|
+
if (minChoices > 0 && values.length < minChoices) {
|
|
147
|
+
return customErrorMessage || `Please select at least ${minChoices} option${minChoices > 1 ? 's' : ''}`;
|
|
148
|
+
}
|
|
149
|
+
if (maxChoices > 0 && values.length > maxChoices) {
|
|
150
|
+
return customErrorMessage || `Please select at most ${maxChoices} option${maxChoices > 1 ? 's' : ''}`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return null;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const handleCheckboxChange = (optionValue, isChecked) => {
|
|
158
|
+
let newValue;
|
|
159
|
+
|
|
160
|
+
if (checkboxType === "single") {
|
|
161
|
+
// For single checkbox mode, work like radio button - only one option can be selected
|
|
162
|
+
if (isChecked) {
|
|
163
|
+
// If checking an option, set it as the only selected value
|
|
164
|
+
newValue = [optionValue];
|
|
165
|
+
} else {
|
|
166
|
+
// If unchecking, clear the selection
|
|
167
|
+
newValue = [];
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
// For multiple checkboxes, handle array of values
|
|
171
|
+
if (isChecked) {
|
|
172
|
+
newValue = [...fieldValue, optionValue];
|
|
173
|
+
} else {
|
|
174
|
+
newValue = fieldValue.filter(val => val !== optionValue);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
setFieldValue(newValue);
|
|
179
|
+
setHasInteracted(true);
|
|
180
|
+
|
|
181
|
+
// Validate selection only after user interaction
|
|
182
|
+
const error = validateSelection(newValue);
|
|
183
|
+
setValidationError(error);
|
|
184
|
+
|
|
185
|
+
console.log('AdvancedCheckbox handleCheckboxChange:', {
|
|
186
|
+
optionValue,
|
|
187
|
+
isChecked,
|
|
188
|
+
newValue,
|
|
189
|
+
error,
|
|
190
|
+
hasInteracted: true,
|
|
191
|
+
checkboxType,
|
|
192
|
+
isInitialized
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Always trigger onChange for user interactions
|
|
196
|
+
if (onChange) {
|
|
197
|
+
console.log(onChange)
|
|
198
|
+
onChange({
|
|
199
|
+
target: {
|
|
200
|
+
value: newValue,
|
|
201
|
+
name: name,
|
|
202
|
+
id: name
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// Show validation error - prioritize Strapi's error, then our validation only after user interaction
|
|
209
|
+
const displayError = error || (hasInteracted && validationError);
|
|
210
|
+
console.log('displayError', displayError);
|
|
211
|
+
const renderCheckboxes = () => {
|
|
212
|
+
// Debug logging
|
|
213
|
+
// console.log('AdvancedCheckbox renderCheckboxes:', {
|
|
214
|
+
// checkboxType,
|
|
215
|
+
// checkboxOptions,
|
|
216
|
+
// options,
|
|
217
|
+
// attribute,
|
|
218
|
+
// attributeOptions: attribute.options,
|
|
219
|
+
// fieldValue
|
|
220
|
+
// });
|
|
221
|
+
|
|
222
|
+
const checkboxStyle = {
|
|
223
|
+
display: "flex",
|
|
224
|
+
alignItems: "center",
|
|
225
|
+
gap: "8px",
|
|
226
|
+
marginBottom: "8px",
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const checkboxInputStyle = {
|
|
230
|
+
width: "16px",
|
|
231
|
+
height: "16px",
|
|
232
|
+
accentColor: "#4945ff",
|
|
233
|
+
margin: "0",
|
|
234
|
+
padding: "0",
|
|
235
|
+
opacity: "1",
|
|
236
|
+
visibility: "visible",
|
|
237
|
+
display: "block",
|
|
238
|
+
position: "relative",
|
|
239
|
+
zIndex: "1",
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const checkboxLabelStyle = {
|
|
243
|
+
fontSize: "14px",
|
|
244
|
+
fontFamily: "inherit",
|
|
245
|
+
cursor: "pointer",
|
|
246
|
+
userSelect: "none",
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Single checkbox mode - show multiple options but only one selectable (like radio buttons)
|
|
250
|
+
if (checkboxType === "single") {
|
|
251
|
+
// If no options are defined for single mode, show a message
|
|
252
|
+
if (!options || options.length === 0) {
|
|
253
|
+
return (
|
|
254
|
+
<div style={{ padding: "8px", color: "#666", fontStyle: "italic" }}>
|
|
255
|
+
{formatMessage({
|
|
256
|
+
id: 'advanced-fields.checkbox.options.checkboxOptions.description',
|
|
257
|
+
defaultMessage: 'Define available options for multiple checkboxes (one per line: value|label)'
|
|
258
|
+
})}
|
|
259
|
+
</div>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (layout === "horizontal") {
|
|
264
|
+
return (
|
|
265
|
+
<Flex wrap="wrap" gap={2}>
|
|
266
|
+
{options.map((option) => (
|
|
267
|
+
<div key={option.value} style={checkboxStyle}>
|
|
268
|
+
<input
|
|
269
|
+
type="checkbox"
|
|
270
|
+
id={`${name}-${option.value}`}
|
|
271
|
+
checked={fieldValue.includes(option.value)}
|
|
272
|
+
onChange={(e) => handleCheckboxChange(option.value, e.target.checked)}
|
|
273
|
+
disabled={disabled}
|
|
274
|
+
style={checkboxInputStyle}
|
|
275
|
+
/>
|
|
276
|
+
<label
|
|
277
|
+
htmlFor={`${name}-${option.value}`}
|
|
278
|
+
style={checkboxLabelStyle}
|
|
279
|
+
>
|
|
280
|
+
{option.label}
|
|
281
|
+
</label>
|
|
282
|
+
</div>
|
|
283
|
+
))}
|
|
284
|
+
</Flex>
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<div>
|
|
290
|
+
{options.map((option) => (
|
|
291
|
+
<div key={option.value} style={checkboxStyle}>
|
|
292
|
+
<input
|
|
293
|
+
type="checkbox"
|
|
294
|
+
id={`${name}-${option.value}`}
|
|
295
|
+
checked={fieldValue.includes(option.value)}
|
|
296
|
+
onChange={(e) => handleCheckboxChange(option.value, e.target.checked)}
|
|
297
|
+
disabled={disabled}
|
|
298
|
+
style={checkboxInputStyle}
|
|
299
|
+
/>
|
|
300
|
+
<label
|
|
301
|
+
htmlFor={`${name}-${option.value}`}
|
|
302
|
+
style={checkboxLabelStyle}
|
|
303
|
+
>
|
|
304
|
+
{option.label}
|
|
305
|
+
</label>
|
|
306
|
+
</div>
|
|
307
|
+
))}
|
|
308
|
+
</div>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Multiple checkbox mode
|
|
313
|
+
// If no options are defined, show a message
|
|
314
|
+
if (!options || options.length === 0) {
|
|
315
|
+
return (
|
|
316
|
+
<div style={{ padding: "8px", color: "#666", fontStyle: "italic" }}>
|
|
317
|
+
{formatMessage({
|
|
318
|
+
id: 'advanced-fields.checkbox.options.checkboxOptions.description',
|
|
319
|
+
defaultMessage: 'Define available options for multiple checkboxes (one per line: value|label)'
|
|
320
|
+
})}
|
|
321
|
+
</div>
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (layout === "horizontal") {
|
|
326
|
+
return (
|
|
327
|
+
<Flex wrap="wrap" gap={2}>
|
|
328
|
+
{options.map((option) => (
|
|
329
|
+
<div key={option.value} style={checkboxStyle}>
|
|
330
|
+
<input
|
|
331
|
+
type="checkbox"
|
|
332
|
+
id={`${name}-${option.value}`}
|
|
333
|
+
checked={fieldValue.includes(option.value)}
|
|
334
|
+
onChange={(e) => handleCheckboxChange(option.value, e.target.checked)}
|
|
335
|
+
disabled={disabled}
|
|
336
|
+
style={checkboxInputStyle}
|
|
337
|
+
/>
|
|
338
|
+
<label
|
|
339
|
+
htmlFor={`${name}-${option.value}`}
|
|
340
|
+
style={checkboxLabelStyle}
|
|
341
|
+
>
|
|
342
|
+
{option.label}
|
|
343
|
+
</label>
|
|
344
|
+
</div>
|
|
345
|
+
))}
|
|
346
|
+
</Flex>
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<div>
|
|
352
|
+
{options.map((option) => (
|
|
353
|
+
<div key={option.value} style={checkboxStyle}>
|
|
354
|
+
<input
|
|
355
|
+
type="checkbox"
|
|
356
|
+
id={`${name}-${option.value}`}
|
|
357
|
+
checked={fieldValue.includes(option.value)}
|
|
358
|
+
onChange={(e) => handleCheckboxChange(option.value, e.target.checked)}
|
|
359
|
+
disabled={disabled}
|
|
360
|
+
style={checkboxInputStyle}
|
|
361
|
+
/>
|
|
362
|
+
<label
|
|
363
|
+
htmlFor={`${name}-${option.value}`}
|
|
364
|
+
style={checkboxLabelStyle}
|
|
365
|
+
>
|
|
366
|
+
{option.label}
|
|
367
|
+
</label>
|
|
368
|
+
</div>
|
|
369
|
+
))}
|
|
370
|
+
</div>
|
|
371
|
+
);
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
return (
|
|
375
|
+
<Box col={6}>
|
|
376
|
+
<Field.Root name={name} error={displayError}>
|
|
377
|
+
<Field.Label>
|
|
378
|
+
{intlLabel.id ? formatMessage(intlLabel) : intlLabel.defaultMessage || name}
|
|
379
|
+
{required && <span style={{ color: "#d02b20", marginLeft: "4px" }}>*</span>}
|
|
380
|
+
</Field.Label>
|
|
381
|
+
{renderCheckboxes()}
|
|
382
|
+
{displayError && (
|
|
383
|
+
<Field.Error>
|
|
384
|
+
{displayError}
|
|
385
|
+
</Field.Error>
|
|
386
|
+
)}
|
|
387
|
+
{description && (description.id || description.defaultMessage) && (
|
|
388
|
+
<Field.Hint>
|
|
389
|
+
{description.id ? formatMessage(description) : description.defaultMessage}
|
|
390
|
+
</Field.Hint>
|
|
391
|
+
)}
|
|
392
|
+
</Field.Root>
|
|
393
|
+
</Box>
|
|
394
|
+
);
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
export default CheckboxInput;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useIntl } from "react-intl";
|
|
4
|
+
import { Field, Box } from "@strapi/design-system";
|
|
5
|
+
import { Cross } from "@strapi/icons";
|
|
6
|
+
import { useState, useEffect } from "react";
|
|
7
|
+
|
|
8
|
+
const TextInput = ({
|
|
9
|
+
attribute = {},
|
|
10
|
+
description = { id: "", defaultMessage: "" },
|
|
11
|
+
disabled,
|
|
12
|
+
error,
|
|
13
|
+
intlLabel = { id: "", defaultMessage: "" },
|
|
14
|
+
labelAction,
|
|
15
|
+
name,
|
|
16
|
+
onChange,
|
|
17
|
+
required,
|
|
18
|
+
value,
|
|
19
|
+
}) => {
|
|
20
|
+
const { formatMessage } = useIntl();
|
|
21
|
+
const [inputValue, setInputValue] = useState(value || "");
|
|
22
|
+
const [validationError, setValidationError] = useState(null);
|
|
23
|
+
const [hasInteracted, setHasInteracted] = useState(false);
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
minLength = 0,
|
|
27
|
+
maxLength = 0,
|
|
28
|
+
min = 0,
|
|
29
|
+
max = 0,
|
|
30
|
+
step = 1,
|
|
31
|
+
rows = 4,
|
|
32
|
+
options = {},
|
|
33
|
+
} = attribute;
|
|
34
|
+
|
|
35
|
+
// Extract options with defaults
|
|
36
|
+
const {
|
|
37
|
+
placeholder = '',
|
|
38
|
+
defaultValue = '',
|
|
39
|
+
customErrorMessage = '',
|
|
40
|
+
regex = '',
|
|
41
|
+
} = options;
|
|
42
|
+
|
|
43
|
+
// Initialize input value
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const initialValue = value === undefined ? defaultValue : value;
|
|
46
|
+
setInputValue(initialValue);
|
|
47
|
+
|
|
48
|
+
// Don't validate on initial load unless there's an error from Strapi
|
|
49
|
+
if (error) {
|
|
50
|
+
setValidationError(error);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (onChange) {
|
|
54
|
+
onChange({ target: { value: initialValue } });
|
|
55
|
+
}
|
|
56
|
+
}, [value, defaultValue, onChange, error]);
|
|
57
|
+
|
|
58
|
+
// Validation function - this should match server-side validation
|
|
59
|
+
const validateInput = (val) => {
|
|
60
|
+
// Check required validation first
|
|
61
|
+
if (required && (!val || val.toString().trim().length === 0)) {
|
|
62
|
+
return customErrorMessage || "This field is required";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// If no value, no additional validation needed
|
|
66
|
+
if (!val || val.toString().trim().length === 0) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const stringValue = val.toString().trim();
|
|
71
|
+
|
|
72
|
+
// Check min/max length validation
|
|
73
|
+
if (minLength > 0 && stringValue.length < minLength) {
|
|
74
|
+
return customErrorMessage || `Minimum length is ${minLength} characters`;
|
|
75
|
+
}
|
|
76
|
+
if (maxLength > 0 && stringValue.length > maxLength) {
|
|
77
|
+
return customErrorMessage || `Maximum length is ${maxLength} characters`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check regex validation if provided
|
|
81
|
+
if (regex && stringValue) {
|
|
82
|
+
try {
|
|
83
|
+
const regexPattern = new RegExp(regex);
|
|
84
|
+
if (!regexPattern.test(stringValue)) {
|
|
85
|
+
return customErrorMessage || "Invalid format";
|
|
86
|
+
}
|
|
87
|
+
} catch (e) {
|
|
88
|
+
// Invalid regex pattern, skip validation
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const handleChange = (e) => {
|
|
96
|
+
const newValue = e.target.value;
|
|
97
|
+
setInputValue(newValue);
|
|
98
|
+
setHasInteracted(true);
|
|
99
|
+
|
|
100
|
+
// Validate input only after user interaction
|
|
101
|
+
const error = validateInput(newValue);
|
|
102
|
+
setValidationError(error);
|
|
103
|
+
|
|
104
|
+
if (onChange) {
|
|
105
|
+
onChange(e);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Show validation error - prioritize Strapi's error, then our validation only after user interaction
|
|
110
|
+
const displayError = error || (hasInteracted && validationError);
|
|
111
|
+
|
|
112
|
+
const renderInput = () => {
|
|
113
|
+
const commonProps = {
|
|
114
|
+
name,
|
|
115
|
+
value: inputValue,
|
|
116
|
+
onChange: handleChange,
|
|
117
|
+
disabled,
|
|
118
|
+
placeholder: placeholder || (intlLabel.id ? formatMessage(intlLabel) : intlLabel.defaultMessage || ""),
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const inputStyle = {
|
|
122
|
+
width: "100%",
|
|
123
|
+
padding: "8px 12px",
|
|
124
|
+
border: `1px solid ${displayError ? "#d02b20" : "#dcdce4"}`,
|
|
125
|
+
borderRadius: "4px",
|
|
126
|
+
fontSize: "14px",
|
|
127
|
+
fontFamily: "inherit",
|
|
128
|
+
backgroundColor: disabled ? "#f6f6f9" : "#ffffff",
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<input
|
|
133
|
+
{...commonProps}
|
|
134
|
+
type="text"
|
|
135
|
+
style={inputStyle}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<Box col={6}>
|
|
142
|
+
<Field.Root name={name} error={displayError}>
|
|
143
|
+
<Field.Label>
|
|
144
|
+
{intlLabel.id ? formatMessage(intlLabel) : intlLabel.defaultMessage || name}
|
|
145
|
+
{required && <span style={{ color: "#d02b20", marginLeft: "4px" }}>*</span>}
|
|
146
|
+
</Field.Label>
|
|
147
|
+
{renderInput()}
|
|
148
|
+
{displayError && (
|
|
149
|
+
<Field.Error>
|
|
150
|
+
{displayError}
|
|
151
|
+
</Field.Error>
|
|
152
|
+
)}
|
|
153
|
+
{description && (description.id || description.defaultMessage) && (
|
|
154
|
+
<Field.Hint>
|
|
155
|
+
{description.id ? formatMessage(description) : description.defaultMessage}
|
|
156
|
+
</Field.Hint>
|
|
157
|
+
)}
|
|
158
|
+
</Field.Root>
|
|
159
|
+
</Box>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export default TextInput;
|