@webbycrown/advanced-fields 1.0.2 → 1.0.3

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