@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.
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const reactIntl = require("react-intl");
5
+ const designSystem = require("@strapi/design-system");
6
+ const react = require("react");
7
+ const AdvancedRadio = ({
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 } = reactIntl.useIntl();
20
+ const [selectedValues, setSelectedValues] = react.useState([]);
21
+ const [validationError, setValidationError] = react.useState(null);
22
+ const [hasInteracted, setHasInteracted] = react.useState(false);
23
+ const [isInitialized, setIsInitialized] = react.useState(false);
24
+ const {
25
+ selectionType = "single",
26
+ layout = "vertical",
27
+ minChoices = 0,
28
+ maxChoices = 0,
29
+ defaultSelected = "",
30
+ radioOptions = "",
31
+ customErrorMessage = "",
32
+ fieldNote = ""
33
+ } = attribute.options || attribute;
34
+ const fieldNoteFromAttribute = attribute.options?.fieldNote || "";
35
+ const options = radioOptions.split("\n").filter((opt) => opt.trim()).map((opt) => {
36
+ const [value2, label] = opt.split("|");
37
+ return { value: value2?.trim() || "", label: label?.trim() || value2?.trim() || "" };
38
+ });
39
+ react.useEffect(() => {
40
+ let initialValues = [];
41
+ if (value && Array.isArray(value)) {
42
+ initialValues = value;
43
+ } else if (value && typeof value === "string") {
44
+ initialValues = value.split(",").map((v) => v.trim()).filter((v) => v);
45
+ } else if (defaultSelected) {
46
+ initialValues = defaultSelected.split(",").map((v) => v.trim()).filter((v) => v);
47
+ }
48
+ setSelectedValues(initialValues);
49
+ const validationResult = validateSelection(initialValues);
50
+ setValidationError(validationResult);
51
+ if (onChange && initialValues.length > 0 && (!value || Array.isArray(value) && value.length === 0) && !isInitialized) {
52
+ setTimeout(() => {
53
+ onChange({
54
+ target: {
55
+ value: initialValues,
56
+ name,
57
+ id: name
58
+ }
59
+ });
60
+ setIsInitialized(true);
61
+ }, 0);
62
+ } else if (!isInitialized) {
63
+ setIsInitialized(true);
64
+ }
65
+ }, [value, defaultSelected, onChange, error]);
66
+ const validateSelection = (values) => {
67
+ const valArray = Array.isArray(values) ? values : [];
68
+ if (required && valArray.length === 0) {
69
+ return customErrorMessage || "This field is required";
70
+ }
71
+ if (valArray.length === 0) {
72
+ return null;
73
+ }
74
+ if (selectionType === "multiple") {
75
+ if (minChoices > 0 && valArray.length < minChoices) {
76
+ return customErrorMessage || `Please select at least ${minChoices} option${minChoices > 1 ? "s" : ""}`;
77
+ }
78
+ if (maxChoices > 0 && valArray.length > maxChoices) {
79
+ return customErrorMessage || `Please select at most ${maxChoices} option${maxChoices > 1 ? "s" : ""}`;
80
+ }
81
+ }
82
+ return null;
83
+ };
84
+ const handleRadioChange = (optionValue, isChecked) => {
85
+ let newValues;
86
+ if (selectionType === "single") {
87
+ newValues = isChecked ? [optionValue] : [];
88
+ } else {
89
+ if (isChecked) {
90
+ newValues = [...selectedValues, optionValue];
91
+ } else {
92
+ newValues = selectedValues.filter((val) => val !== optionValue);
93
+ }
94
+ }
95
+ setSelectedValues(newValues);
96
+ setHasInteracted(true);
97
+ const error2 = validateSelection(newValues);
98
+ setValidationError(error2);
99
+ if (onChange) {
100
+ const event = {
101
+ target: {
102
+ name,
103
+ id: name,
104
+ value: newValues
105
+ }
106
+ };
107
+ onChange(event);
108
+ }
109
+ };
110
+ const displayError = error || hasInteracted && validationError;
111
+ const renderRadios = () => {
112
+ const radioStyle = {
113
+ display: "flex",
114
+ alignItems: "center",
115
+ gap: "8px",
116
+ padding: "4px 0"
117
+ };
118
+ const radioInputStyle = {
119
+ width: "16px",
120
+ height: "16px",
121
+ accentColor: "#4945ff",
122
+ margin: "0",
123
+ padding: "0",
124
+ opacity: "1",
125
+ visibility: "visible",
126
+ display: "block",
127
+ position: "relative",
128
+ zIndex: "1",
129
+ cursor: "pointer",
130
+ border: "2px solid #dcdce4",
131
+ borderRadius: "50%",
132
+ backgroundColor: "white",
133
+ transition: "all 0.2s ease"
134
+ };
135
+ const customRadioStyle = {
136
+ width: "16px",
137
+ height: "16px",
138
+ borderRadius: "50%",
139
+ borderWidth: "1px",
140
+ borderStyle: "solid",
141
+ backgroundColor: "#ffffff",
142
+ cursor: "pointer",
143
+ transition: "all 0.2s ease",
144
+ position: "relative",
145
+ display: "flex",
146
+ alignItems: "center",
147
+ justifyContent: "center"
148
+ };
149
+ const customRadioCheckedStyle = {
150
+ ...customRadioStyle,
151
+ backgroundColor: "#ffffff",
152
+ borderColor: "#4945ff"
153
+ };
154
+ const customRadioDotStyle = {
155
+ width: "10px",
156
+ height: "10px",
157
+ borderRadius: "50%",
158
+ backgroundColor: "#4945ff"
159
+ };
160
+ const radioLabelStyle = {
161
+ fontSize: "14px",
162
+ fontFamily: "inherit",
163
+ cursor: "pointer",
164
+ userSelect: "none",
165
+ color: "#32324d",
166
+ fontWeight: "400",
167
+ lineHeight: "1.5",
168
+ marginLeft: "4px"
169
+ };
170
+ if (!options || options.length === 0) {
171
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "8px", color: "#666", fontStyle: "italic" }, children: formatMessage({
172
+ id: "advanced-fields.radio.no-options",
173
+ defaultMessage: "No options defined. Please configure this field in the content type settings."
174
+ }) });
175
+ }
176
+ if (layout === "horizontal") {
177
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { wrap: "wrap", gap: 2, children: options.map((option) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: radioStyle, children: [
178
+ selectionType === "multiple" ? (
179
+ // Custom radio button appearance for multiple selection
180
+ /* @__PURE__ */ jsxRuntime.jsx(
181
+ "div",
182
+ {
183
+ style: selectedValues.includes(option.value) ? customRadioCheckedStyle : customRadioStyle,
184
+ onClick: () => handleRadioChange(option.value, !selectedValues.includes(option.value)),
185
+ children: selectedValues.includes(option.value) && /* @__PURE__ */ jsxRuntime.jsx("div", { style: customRadioDotStyle })
186
+ }
187
+ )
188
+ ) : (
189
+ // Regular radio button for single selection
190
+ /* @__PURE__ */ jsxRuntime.jsx(
191
+ "input",
192
+ {
193
+ type: "radio",
194
+ id: `${name}-${option.value}`,
195
+ name,
196
+ checked: selectedValues.includes(option.value),
197
+ onChange: (e) => handleRadioChange(option.value, e.target.checked),
198
+ disabled,
199
+ style: radioInputStyle
200
+ }
201
+ )
202
+ ),
203
+ /* @__PURE__ */ jsxRuntime.jsx(
204
+ "label",
205
+ {
206
+ htmlFor: selectionType === "single" ? `${name}-${option.value}` : void 0,
207
+ style: radioLabelStyle,
208
+ onClick: selectionType === "multiple" ? () => handleRadioChange(option.value, !selectedValues.includes(option.value)) : void 0,
209
+ children: option.label
210
+ }
211
+ )
212
+ ] }, option.value)) });
213
+ }
214
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { children: options.map((option) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: radioStyle, children: [
215
+ selectionType === "multiple" ? (
216
+ // Custom radio button appearance for multiple selection
217
+ /* @__PURE__ */ jsxRuntime.jsx(
218
+ "div",
219
+ {
220
+ style: selectedValues.includes(option.value) ? customRadioCheckedStyle : customRadioStyle,
221
+ onClick: () => handleRadioChange(option.value, !selectedValues.includes(option.value)),
222
+ children: selectedValues.includes(option.value) && /* @__PURE__ */ jsxRuntime.jsx("div", { style: customRadioDotStyle })
223
+ }
224
+ )
225
+ ) : (
226
+ // Regular radio button for single selection
227
+ /* @__PURE__ */ jsxRuntime.jsx(
228
+ "input",
229
+ {
230
+ type: "radio",
231
+ id: `${name}-${option.value}`,
232
+ name,
233
+ checked: selectedValues.includes(option.value),
234
+ onChange: (e) => handleRadioChange(option.value, e.target.checked),
235
+ disabled,
236
+ style: radioInputStyle
237
+ }
238
+ )
239
+ ),
240
+ /* @__PURE__ */ jsxRuntime.jsx(
241
+ "label",
242
+ {
243
+ htmlFor: selectionType === "single" ? `${name}-${option.value}` : void 0,
244
+ style: radioLabelStyle,
245
+ onClick: selectionType === "multiple" ? () => handleRadioChange(option.value, !selectedValues.includes(option.value)) : void 0,
246
+ children: option.label
247
+ }
248
+ )
249
+ ] }, option.value)) });
250
+ };
251
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { col: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name, error: displayError, children: [
252
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Label, { children: [
253
+ intlLabel.id ? formatMessage(intlLabel) : intlLabel.defaultMessage || name,
254
+ required && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#d02b20", marginLeft: "4px" }, children: "*" })
255
+ ] }),
256
+ renderRadios(),
257
+ displayError && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, { children: displayError }),
258
+ description && (description.id || description.defaultMessage) && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, { children: description.id ? formatMessage(description) : description.defaultMessage }),
259
+ (fieldNote || fieldNoteFromAttribute) && /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
260
+ fontStyle: "italic",
261
+ color: "#666",
262
+ fontSize: "12px",
263
+ display: "block",
264
+ marginTop: "4px"
265
+ }, children: fieldNote || fieldNoteFromAttribute })
266
+ ] }) });
267
+ };
268
+ exports.default = AdvancedRadio;
@@ -1,10 +1,8 @@
1
- "use client";
2
-
1
+ import { jsx, jsxs } from "react/jsx-runtime";
3
2
  import { useIntl } from "react-intl";
4
- import { Field, Box } from "@strapi/design-system";
5
- import { Cross } from "@strapi/icons";
3
+ import { Box, Field } from "@strapi/design-system";
4
+ import "@strapi/icons";
6
5
  import { useState, useEffect } from "react";
7
-
8
6
  const AdvancedInput = ({
9
7
  attribute = {},
10
8
  description = { id: "", defaultMessage: "" },
@@ -15,13 +13,12 @@ const AdvancedInput = ({
15
13
  name,
16
14
  onChange,
17
15
  required,
18
- value,
16
+ value
19
17
  }) => {
20
18
  const { formatMessage } = useIntl();
21
19
  const [inputValue, setInputValue] = useState(value || "");
22
20
  const [validationError, setValidationError] = useState(null);
23
21
  const [hasInteracted, setHasInteracted] = useState(false);
24
-
25
22
  const {
26
23
  minLength = 0,
27
24
  maxLength = 0,
@@ -29,60 +26,40 @@ const AdvancedInput = ({
29
26
  max = 0,
30
27
  step = 1,
31
28
  rows = 4,
32
- options = {},
29
+ options = {}
33
30
  } = attribute;
34
-
35
- // Extract options with defaults
36
31
  const {
37
- placeholder = '',
38
- defaultValue = '',
39
- customErrorMessage = '',
40
- regex = '',
41
- fieldNote = '',
32
+ placeholder = "",
33
+ defaultValue = "",
34
+ customErrorMessage = "",
35
+ regex = "",
36
+ fieldNote = ""
42
37
  } = options;
43
-
44
- // Also check attribute.options for fieldNote
45
- const fieldNoteFromAttribute = attribute.options?.fieldNote || '';
46
-
47
-
48
- // Initialize input value
38
+ const fieldNoteFromAttribute = attribute.options?.fieldNote || "";
49
39
  useEffect(() => {
50
- const initialValue = value === undefined ? defaultValue : value;
40
+ const initialValue = value === void 0 ? defaultValue : value;
51
41
  setInputValue(initialValue);
52
-
53
- // Don't validate on initial load unless there's an error from Strapi
54
42
  if (error) {
55
43
  setValidationError(error);
56
44
  }
57
-
58
45
  if (onChange) {
59
46
  onChange({ target: { value: initialValue } });
60
47
  }
61
48
  }, [value, defaultValue, onChange, error]);
62
-
63
- // Validation function - this should match server-side validation
64
49
  const validateInput = (val) => {
65
- // Check required validation first
66
50
  if (required && (!val || val.toString().trim().length === 0)) {
67
51
  return customErrorMessage || "This field is required";
68
52
  }
69
-
70
- // If no value, no additional validation needed
71
53
  if (!val || val.toString().trim().length === 0) {
72
54
  return null;
73
55
  }
74
-
75
56
  const stringValue = val.toString().trim();
76
-
77
- // Check min/max length validation
78
57
  if (minLength > 0 && stringValue.length < minLength) {
79
58
  return customErrorMessage || `Minimum length is ${minLength} characters`;
80
59
  }
81
60
  if (maxLength > 0 && stringValue.length > maxLength) {
82
61
  return customErrorMessage || `Maximum length is ${maxLength} characters`;
83
62
  }
84
-
85
- // Check regex validation if provided
86
63
  if (regex && stringValue) {
87
64
  try {
88
65
  const regexPattern = new RegExp(regex);
@@ -90,39 +67,29 @@ const AdvancedInput = ({
90
67
  return customErrorMessage || "Invalid format";
91
68
  }
92
69
  } catch (e) {
93
- // Invalid regex pattern, skip validation
94
70
  }
95
71
  }
96
-
97
72
  return null;
98
73
  };
99
-
100
74
  const handleChange = (e) => {
101
75
  const newValue = e.target.value;
102
76
  setInputValue(newValue);
103
77
  setHasInteracted(true);
104
-
105
- // Validate input only after user interaction
106
- const error = validateInput(newValue);
107
- setValidationError(error);
108
-
78
+ const error2 = validateInput(newValue);
79
+ setValidationError(error2);
109
80
  if (onChange) {
110
81
  onChange(e);
111
82
  }
112
83
  };
113
-
114
- // Show validation error - prioritize Strapi's error, then our validation only after user interaction
115
- const displayError = error || (hasInteracted && validationError);
116
-
84
+ const displayError = error || hasInteracted && validationError;
117
85
  const renderInput = () => {
118
86
  const commonProps = {
119
87
  name,
120
88
  value: inputValue,
121
89
  onChange: handleChange,
122
90
  disabled,
123
- placeholder: placeholder || (intlLabel.id ? formatMessage(intlLabel) : intlLabel.defaultMessage || ""),
91
+ placeholder: placeholder || (intlLabel.id ? formatMessage(intlLabel) : intlLabel.defaultMessage || "")
124
92
  };
125
-
126
93
  const inputStyle = {
127
94
  width: "100%",
128
95
  padding: "8px 12px",
@@ -130,50 +97,34 @@ const AdvancedInput = ({
130
97
  borderRadius: "4px",
131
98
  fontSize: "14px",
132
99
  fontFamily: "inherit",
133
- backgroundColor: disabled ? "#f6f6f9" : "#ffffff",
100
+ backgroundColor: disabled ? "#f6f6f9" : "#ffffff"
134
101
  };
135
-
136
- return (
137
- <input
138
- {...commonProps}
139
- type="text"
140
- style={inputStyle}
141
- />
102
+ return /* @__PURE__ */ jsx(
103
+ "input",
104
+ {
105
+ ...commonProps,
106
+ type: "text",
107
+ style: inputStyle
108
+ }
142
109
  );
143
110
  };
144
-
145
- return (
146
- <Box col={6}>
147
- <Field.Root name={name} error={displayError}>
148
- <Field.Label>
149
- {intlLabel.id ? formatMessage(intlLabel) : intlLabel.defaultMessage || name}
150
- {required && <span style={{ color: "#d02b20", marginLeft: "4px" }}>*</span>}
151
- </Field.Label>
152
- {renderInput()}
153
- {displayError && (
154
- <Field.Error>
155
- {displayError}
156
- </Field.Error>
157
- )}
158
- {description && (description.id || description.defaultMessage) && (
159
- <Field.Hint>
160
- {description.id ? formatMessage(description) : description.defaultMessage}
161
- </Field.Hint>
162
- )}
163
- {(fieldNote || fieldNoteFromAttribute) && (
164
- <span style={{
165
- fontStyle: 'italic',
166
- color: '#666',
167
- fontSize: '12px',
168
- display: 'block',
169
- marginTop: '4px'
170
- }}>
171
- {fieldNote || fieldNoteFromAttribute}
172
- </span>
173
- )}
174
- </Field.Root>
175
- </Box>
176
- );
111
+ return /* @__PURE__ */ jsx(Box, { col: 6, children: /* @__PURE__ */ jsxs(Field.Root, { name, error: displayError, children: [
112
+ /* @__PURE__ */ jsxs(Field.Label, { children: [
113
+ intlLabel.id ? formatMessage(intlLabel) : intlLabel.defaultMessage || name,
114
+ required && /* @__PURE__ */ jsx("span", { style: { color: "#d02b20", marginLeft: "4px" }, children: "*" })
115
+ ] }),
116
+ renderInput(),
117
+ displayError && /* @__PURE__ */ jsx(Field.Error, { children: displayError }),
118
+ description && (description.id || description.defaultMessage) && /* @__PURE__ */ jsx(Field.Hint, { children: description.id ? formatMessage(description) : description.defaultMessage }),
119
+ (fieldNote || fieldNoteFromAttribute) && /* @__PURE__ */ jsx("span", { style: {
120
+ fontStyle: "italic",
121
+ color: "#666",
122
+ fontSize: "12px",
123
+ display: "block",
124
+ marginTop: "4px"
125
+ }, children: fieldNote || fieldNoteFromAttribute })
126
+ ] }) });
127
+ };
128
+ export {
129
+ AdvancedInput as default
177
130
  };
178
-
179
- export default AdvancedInput;