@webbycrown/advanced-fields 1.0.1 → 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,11 +1,9 @@
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
- const TextInput = ({
6
+ const AdvancedInput = ({
9
7
  attribute = {},
10
8
  description = { id: "", defaultMessage: "" },
11
9
  disabled,
@@ -15,13 +13,12 @@ const TextInput = ({
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,55 +26,40 @@ const TextInput = ({
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 = '',
32
+ placeholder = "",
33
+ defaultValue = "",
34
+ customErrorMessage = "",
35
+ regex = "",
36
+ fieldNote = ""
41
37
  } = options;
42
-
43
- // Initialize input value
38
+ const fieldNoteFromAttribute = attribute.options?.fieldNote || "";
44
39
  useEffect(() => {
45
- const initialValue = value === undefined ? defaultValue : value;
40
+ const initialValue = value === void 0 ? defaultValue : value;
46
41
  setInputValue(initialValue);
47
-
48
- // Don't validate on initial load unless there's an error from Strapi
49
42
  if (error) {
50
43
  setValidationError(error);
51
44
  }
52
-
53
45
  if (onChange) {
54
46
  onChange({ target: { value: initialValue } });
55
47
  }
56
48
  }, [value, defaultValue, onChange, error]);
57
-
58
- // Validation function - this should match server-side validation
59
49
  const validateInput = (val) => {
60
- // Check required validation first
61
50
  if (required && (!val || val.toString().trim().length === 0)) {
62
51
  return customErrorMessage || "This field is required";
63
52
  }
64
-
65
- // If no value, no additional validation needed
66
53
  if (!val || val.toString().trim().length === 0) {
67
54
  return null;
68
55
  }
69
-
70
56
  const stringValue = val.toString().trim();
71
-
72
- // Check min/max length validation
73
57
  if (minLength > 0 && stringValue.length < minLength) {
74
58
  return customErrorMessage || `Minimum length is ${minLength} characters`;
75
59
  }
76
60
  if (maxLength > 0 && stringValue.length > maxLength) {
77
61
  return customErrorMessage || `Maximum length is ${maxLength} characters`;
78
62
  }
79
-
80
- // Check regex validation if provided
81
63
  if (regex && stringValue) {
82
64
  try {
83
65
  const regexPattern = new RegExp(regex);
@@ -85,39 +67,29 @@ const TextInput = ({
85
67
  return customErrorMessage || "Invalid format";
86
68
  }
87
69
  } catch (e) {
88
- // Invalid regex pattern, skip validation
89
70
  }
90
71
  }
91
-
92
72
  return null;
93
73
  };
94
-
95
74
  const handleChange = (e) => {
96
75
  const newValue = e.target.value;
97
76
  setInputValue(newValue);
98
77
  setHasInteracted(true);
99
-
100
- // Validate input only after user interaction
101
- const error = validateInput(newValue);
102
- setValidationError(error);
103
-
78
+ const error2 = validateInput(newValue);
79
+ setValidationError(error2);
104
80
  if (onChange) {
105
81
  onChange(e);
106
82
  }
107
83
  };
108
-
109
- // Show validation error - prioritize Strapi's error, then our validation only after user interaction
110
- const displayError = error || (hasInteracted && validationError);
111
-
84
+ const displayError = error || hasInteracted && validationError;
112
85
  const renderInput = () => {
113
86
  const commonProps = {
114
87
  name,
115
88
  value: inputValue,
116
89
  onChange: handleChange,
117
90
  disabled,
118
- placeholder: placeholder || (intlLabel.id ? formatMessage(intlLabel) : intlLabel.defaultMessage || ""),
91
+ placeholder: placeholder || (intlLabel.id ? formatMessage(intlLabel) : intlLabel.defaultMessage || "")
119
92
  };
120
-
121
93
  const inputStyle = {
122
94
  width: "100%",
123
95
  padding: "8px 12px",
@@ -125,39 +97,34 @@ const TextInput = ({
125
97
  borderRadius: "4px",
126
98
  fontSize: "14px",
127
99
  fontFamily: "inherit",
128
- backgroundColor: disabled ? "#f6f6f9" : "#ffffff",
100
+ backgroundColor: disabled ? "#f6f6f9" : "#ffffff"
129
101
  };
130
-
131
- return (
132
- <input
133
- {...commonProps}
134
- type="text"
135
- style={inputStyle}
136
- />
102
+ return /* @__PURE__ */ jsx(
103
+ "input",
104
+ {
105
+ ...commonProps,
106
+ type: "text",
107
+ style: inputStyle
108
+ }
137
109
  );
138
110
  };
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
- );
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
161
130
  };
162
-
163
- export default TextInput;