@sproutsocial/seeds-react-form-field 1.1.20 → 1.1.22
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/dist/esm/index.js +179 -27
- package/dist/esm/index.js.map +1 -1
- package/dist/form-field.css +21 -0
- package/dist/index.d.mts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +180 -28
- package/dist/index.js.map +1 -1
- package/package.json +19 -8
- package/.eslintignore +0 -6
- package/.eslintrc.js +0 -4
- package/.turbo/turbo-build.log +0 -21
- package/CHANGELOG.md +0 -288
- package/jest.config.js +0 -9
- package/src/FormField.stories.tsx +0 -126
- package/src/FormField.tsx +0 -94
- package/src/FormFieldTypes.ts +0 -34
- package/src/__tests__/FormField.test.tsx +0 -115
- package/src/__tests__/FormField.typetest.tsx +0 -84
- package/src/index.ts +0 -5
- package/src/styled.d.ts +0 -7
- package/tsconfig.json +0 -15
- package/tsup.config.ts +0 -12
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
// src/FormField.tsx
|
|
2
|
-
import
|
|
2
|
+
import React4 from "react";
|
|
3
|
+
|
|
4
|
+
// src/FormFieldHybrid.tsx
|
|
5
|
+
import React3 from "react";
|
|
6
|
+
import { needsStyledComponents } from "@sproutsocial/seeds-react-system-props";
|
|
7
|
+
|
|
8
|
+
// src/FormFieldStyled.tsx
|
|
9
|
+
import React, { useMemo as useMemo2, useState as useState3 } from "react";
|
|
3
10
|
|
|
4
11
|
// ../seeds-react-hooks/dist/index.mjs
|
|
5
12
|
import * as c from "react";
|
|
@@ -13,50 +20,158 @@ function J(t) {
|
|
|
13
20
|
return o.current = r, o;
|
|
14
21
|
}
|
|
15
22
|
|
|
16
|
-
// src/
|
|
23
|
+
// src/FormFieldStyled.tsx
|
|
17
24
|
import Box from "@sproutsocial/seeds-react-box";
|
|
18
25
|
import Label from "@sproutsocial/seeds-react-label";
|
|
19
26
|
import Text from "@sproutsocial/seeds-react-text";
|
|
20
27
|
import { VisuallyHidden } from "@sproutsocial/seeds-react-visually-hidden";
|
|
21
|
-
|
|
28
|
+
|
|
29
|
+
// src/formFieldId.ts
|
|
22
30
|
var idCounter = 0;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
function nextFormFieldId() {
|
|
32
|
+
return `FormField-${idCounter++}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/FormFieldStyled.tsx
|
|
36
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
37
|
+
var FormFieldStyled = React.forwardRef(
|
|
38
|
+
({
|
|
39
|
+
children,
|
|
40
|
+
error,
|
|
41
|
+
helperText,
|
|
42
|
+
id: identifier,
|
|
43
|
+
isInvalid = false,
|
|
44
|
+
label,
|
|
45
|
+
mb = 400,
|
|
46
|
+
qa,
|
|
47
|
+
isLabelHidden = false,
|
|
48
|
+
required,
|
|
49
|
+
...rest
|
|
50
|
+
}, ref) => {
|
|
51
|
+
const [id] = useState3(identifier || nextFormFieldId());
|
|
52
|
+
const errorId = `Error-${id}`;
|
|
53
|
+
const helperTextId = `HelperText-${id}`;
|
|
54
|
+
const containerText = J("");
|
|
55
|
+
const errorContainerText = J("");
|
|
56
|
+
const ariaDescribedby = useMemo2(() => {
|
|
57
|
+
const ids = [
|
|
58
|
+
helperText && helperTextId,
|
|
59
|
+
isInvalid && error && errorId
|
|
60
|
+
].filter(Boolean);
|
|
61
|
+
return ids.length > 0 ? ids.join(" ") : void 0;
|
|
62
|
+
}, [helperText, helperTextId, isInvalid, error, errorId]);
|
|
63
|
+
return /* @__PURE__ */ jsxs(
|
|
64
|
+
Box,
|
|
65
|
+
{
|
|
66
|
+
ref,
|
|
67
|
+
...rest,
|
|
68
|
+
...qa,
|
|
69
|
+
mb,
|
|
70
|
+
"data-qa-formfield": qa && qa["data-qa-formfield"] || id || containerText.current,
|
|
71
|
+
"data-qa-formfield-isinvalid": isInvalid === true,
|
|
72
|
+
children: [
|
|
73
|
+
isLabelHidden ? /* @__PURE__ */ jsx(VisuallyHidden, { "data-testid": "visually-hidden", children: /* @__PURE__ */ jsx(Label, { htmlFor: id, required, children: label }) }) : /* @__PURE__ */ jsx(Label, { mb: helperText ? 100 : 300, htmlFor: id, required, children: label }),
|
|
74
|
+
helperText && /* @__PURE__ */ jsx(
|
|
75
|
+
Text,
|
|
76
|
+
{
|
|
77
|
+
as: "p",
|
|
78
|
+
fontSize: 200,
|
|
79
|
+
mb: 300,
|
|
80
|
+
color: "text.subtext",
|
|
81
|
+
id: helperTextId,
|
|
82
|
+
children: helperText
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
children({
|
|
86
|
+
id,
|
|
87
|
+
isInvalid,
|
|
88
|
+
ariaDescribedby,
|
|
89
|
+
...required !== void 0 && { required }
|
|
90
|
+
}),
|
|
91
|
+
isInvalid && error && /* @__PURE__ */ jsx(
|
|
92
|
+
Text,
|
|
93
|
+
{
|
|
94
|
+
as: "div",
|
|
95
|
+
fontSize: 200,
|
|
96
|
+
color: "text.error",
|
|
97
|
+
mt: 300,
|
|
98
|
+
id: errorId,
|
|
99
|
+
"data-qa-formfield-error": qa && qa["data-qa-formfield-error"] || errorContainerText.current,
|
|
100
|
+
children: error
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
FormFieldStyled.displayName = "FormField";
|
|
109
|
+
var FormFieldStyled_default = FormFieldStyled;
|
|
110
|
+
|
|
111
|
+
// src/FormFieldTailwind.tsx
|
|
112
|
+
import React2, { useMemo as useMemo3, useState as useState4 } from "react";
|
|
113
|
+
import Label2 from "@sproutsocial/seeds-react-label";
|
|
114
|
+
import Text2 from "@sproutsocial/seeds-react-text";
|
|
115
|
+
import { VisuallyHidden as VisuallyHidden2 } from "@sproutsocial/seeds-react-visually-hidden";
|
|
116
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
117
|
+
function cn(...inputs) {
|
|
118
|
+
const classes = [];
|
|
119
|
+
for (const input of inputs) {
|
|
120
|
+
if (!input) continue;
|
|
121
|
+
if (typeof input === "string") {
|
|
122
|
+
classes.push(input);
|
|
123
|
+
} else if (typeof input === "object") {
|
|
124
|
+
for (const [key, value] of Object.entries(input)) {
|
|
125
|
+
if (value) {
|
|
126
|
+
classes.push(key);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return classes.join(" ");
|
|
132
|
+
}
|
|
133
|
+
var FormFieldTailwind = React2.forwardRef((props, ref) => {
|
|
134
|
+
const {
|
|
135
|
+
children,
|
|
136
|
+
error,
|
|
137
|
+
helperText,
|
|
138
|
+
id: identifier,
|
|
139
|
+
isInvalid = false,
|
|
140
|
+
label,
|
|
141
|
+
mb = 400,
|
|
142
|
+
qa,
|
|
143
|
+
isLabelHidden = false,
|
|
144
|
+
required,
|
|
145
|
+
className,
|
|
146
|
+
style,
|
|
147
|
+
...rest
|
|
148
|
+
} = props;
|
|
149
|
+
const [id] = useState4(identifier || nextFormFieldId());
|
|
37
150
|
const errorId = `Error-${id}`;
|
|
38
151
|
const helperTextId = `HelperText-${id}`;
|
|
39
152
|
const containerText = J("");
|
|
40
153
|
const errorContainerText = J("");
|
|
41
|
-
const ariaDescribedby =
|
|
154
|
+
const ariaDescribedby = useMemo3(() => {
|
|
42
155
|
const ids = [
|
|
43
156
|
helperText && helperTextId,
|
|
44
157
|
isInvalid && error && errorId
|
|
45
158
|
].filter(Boolean);
|
|
46
159
|
return ids.length > 0 ? ids.join(" ") : void 0;
|
|
47
160
|
}, [helperText, helperTextId, isInvalid, error, errorId]);
|
|
48
|
-
return /* @__PURE__ */
|
|
49
|
-
|
|
161
|
+
return /* @__PURE__ */ jsxs2(
|
|
162
|
+
"div",
|
|
50
163
|
{
|
|
164
|
+
ref,
|
|
165
|
+
className: cn("seeds-form-field", className),
|
|
51
166
|
...rest,
|
|
52
167
|
...qa,
|
|
53
|
-
mb,
|
|
168
|
+
style: { marginBottom: `var(--space-${String(mb)})`, ...style },
|
|
54
169
|
"data-qa-formfield": qa && qa["data-qa-formfield"] || id || containerText.current,
|
|
55
170
|
"data-qa-formfield-isinvalid": isInvalid === true,
|
|
56
171
|
children: [
|
|
57
|
-
isLabelHidden ? /* @__PURE__ */
|
|
58
|
-
helperText && /* @__PURE__ */
|
|
59
|
-
|
|
172
|
+
isLabelHidden ? /* @__PURE__ */ jsx2(VisuallyHidden2, { "data-testid": "visually-hidden", children: /* @__PURE__ */ jsx2(Label2, { htmlFor: id, required, children: label }) }) : /* @__PURE__ */ jsx2(Label2, { mb: helperText ? 100 : 300, htmlFor: id, required, children: label }),
|
|
173
|
+
helperText && /* @__PURE__ */ jsx2(
|
|
174
|
+
Text2,
|
|
60
175
|
{
|
|
61
176
|
as: "p",
|
|
62
177
|
fontSize: 200,
|
|
@@ -72,8 +187,8 @@ var FormField = ({
|
|
|
72
187
|
ariaDescribedby,
|
|
73
188
|
...required !== void 0 && { required }
|
|
74
189
|
}),
|
|
75
|
-
isInvalid && error && /* @__PURE__ */
|
|
76
|
-
|
|
190
|
+
isInvalid && error && /* @__PURE__ */ jsx2(
|
|
191
|
+
Text2,
|
|
77
192
|
{
|
|
78
193
|
as: "div",
|
|
79
194
|
fontSize: 200,
|
|
@@ -87,7 +202,44 @@ var FormField = ({
|
|
|
87
202
|
]
|
|
88
203
|
}
|
|
89
204
|
);
|
|
90
|
-
};
|
|
205
|
+
});
|
|
206
|
+
FormFieldTailwind.displayName = "FormFieldTailwind";
|
|
207
|
+
|
|
208
|
+
// src/FormFieldHybrid.tsx
|
|
209
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
210
|
+
var SC_CLASS_PATTERN = /\bsc-[a-zA-Z0-9]+\b/;
|
|
211
|
+
var FormFieldHybrid = React3.forwardRef(
|
|
212
|
+
(props, ref) => {
|
|
213
|
+
const {
|
|
214
|
+
children,
|
|
215
|
+
error,
|
|
216
|
+
helperText,
|
|
217
|
+
id,
|
|
218
|
+
isInvalid,
|
|
219
|
+
label,
|
|
220
|
+
qa,
|
|
221
|
+
isLabelHidden,
|
|
222
|
+
required,
|
|
223
|
+
mb,
|
|
224
|
+
...rest
|
|
225
|
+
} = props;
|
|
226
|
+
const mbIsResponsive = mb !== void 0 && typeof mb !== "number" && typeof mb !== "string";
|
|
227
|
+
const isScExtension = SC_CLASS_PATTERN.test(props.className ?? "");
|
|
228
|
+
if (needsStyledComponents(rest) || mbIsResponsive || isScExtension) {
|
|
229
|
+
return /* @__PURE__ */ jsx3(FormFieldStyled_default, { ...props, ref });
|
|
230
|
+
}
|
|
231
|
+
return /* @__PURE__ */ jsx3(FormFieldTailwind, { ...props, ref });
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
FormFieldHybrid.displayName = "FormField";
|
|
235
|
+
var FormFieldHybrid_default = FormFieldHybrid;
|
|
236
|
+
|
|
237
|
+
// src/FormField.tsx
|
|
238
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
239
|
+
var FormField = React4.forwardRef(
|
|
240
|
+
(props, ref) => /* @__PURE__ */ jsx4(FormFieldHybrid_default, { ...props, ref })
|
|
241
|
+
);
|
|
242
|
+
FormField.displayName = "FormField";
|
|
91
243
|
var FormField_default = FormField;
|
|
92
244
|
|
|
93
245
|
// src/FormFieldTypes.ts
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/FormField.tsx","../../../seeds-react-hooks/src/useMeasure/useMeasure.ts","../../../seeds-react-hooks/src/useSelect/useSelect.ts","../../../seeds-react-hooks/src/useMultiselect/useMultiselect.ts","../../../seeds-react-hooks/src/useMutationObserver/useMutationObserver.ts","../../../seeds-react-hooks/src/useTextContent/useTextContent.ts","../../../seeds-react-hooks/src/useWhyDidYouUpdate/useWhyDidYouUpdate.ts","../../../seeds-react-hooks/src/useInteractiveColor/useInteractiveColor.ts","../../../seeds-react-hooks/src/useIsMobile/useIsMobile.ts","../../src/FormFieldTypes.ts","../../src/index.ts"],"sourcesContent":["import React, { useMemo, useState } from \"react\";\nimport { useTextContent } from \"@sproutsocial/seeds-react-hooks\";\nimport Box from \"@sproutsocial/seeds-react-box\";\nimport Label from \"@sproutsocial/seeds-react-label\";\nimport Text from \"@sproutsocial/seeds-react-text\";\nimport { VisuallyHidden } from \"@sproutsocial/seeds-react-visually-hidden\";\nimport type { TypeFormFieldProps } from \"./FormFieldTypes\";\n\nlet idCounter = 0;\n\nconst FormField = ({\n children,\n error,\n helperText,\n id: identifier,\n isInvalid = false,\n label,\n mb = 400,\n qa,\n isLabelHidden = false,\n required,\n ...rest\n}: TypeFormFieldProps) => {\n const [id] = useState(identifier || `FormField-${idCounter++}`);\n const errorId = `Error-${id}`;\n const helperTextId = `HelperText-${id}`;\n const containerText = useTextContent(\"\");\n const errorContainerText = useTextContent(\"\");\n\n const ariaDescribedby = useMemo(() => {\n const ids = [\n helperText && helperTextId,\n isInvalid && error && errorId,\n ].filter(Boolean);\n return ids.length > 0 ? ids.join(\" \") : undefined;\n }, [helperText, helperTextId, isInvalid, error, errorId]);\n\n return (\n <Box\n {...rest}\n {...qa}\n mb={mb}\n data-qa-formfield={\n (qa && qa[\"data-qa-formfield\"]) || id || containerText.current\n }\n data-qa-formfield-isinvalid={isInvalid === true}\n >\n {isLabelHidden ? (\n <VisuallyHidden data-testid=\"visually-hidden\">\n <Label htmlFor={id} required={required}>\n {label}\n </Label>\n </VisuallyHidden>\n ) : (\n <Label mb={helperText ? 100 : 300} htmlFor={id} required={required}>\n {label}\n </Label>\n )}\n {helperText && (\n <Text\n as=\"p\"\n fontSize={200}\n mb={300}\n color=\"text.subtext\"\n id={helperTextId}\n >\n {helperText}\n </Text>\n )}\n {children({\n id,\n isInvalid,\n ariaDescribedby,\n ...(required !== undefined && { required }),\n })}\n {isInvalid && error && (\n <Text\n as=\"div\"\n fontSize={200}\n color=\"text.error\"\n mt={300}\n id={errorId}\n data-qa-formfield-error={\n (qa && qa[\"data-qa-formfield-error\"]) || errorContainerText.current\n }\n >\n {error}\n </Text>\n )}\n </Box>\n );\n};\n\nexport default FormField;\n","import { useState, useLayoutEffect, type RefObject } from \"react\";\n\ninterface DOMRectObject {\n x: number;\n y: number;\n width: number;\n height: number;\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\nconst initialBounds = Object.freeze({\n x: 0,\n y: 0,\n width: 0,\n height: 0,\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n});\n\nexport function useMeasure<TElement extends Element>(ref: RefObject<TElement>) {\n const [bounds, setContentRect] =\n useState<Readonly<DOMRectObject>>(initialBounds);\n\n useLayoutEffect(() => {\n const element = ref.current;\n\n if (\n !element ||\n // in non-browser environments (e.g. Jest tests) ResizeObserver is not defined\n !(\"ResizeObserver\" in window)\n ) {\n return;\n }\n\n const resizeObserver = new ResizeObserver(([entry]) => {\n if (!entry) return;\n const { x, y, width, height, top, right, bottom, left } =\n entry.contentRect;\n setContentRect({\n x,\n y,\n width,\n height,\n top,\n right,\n bottom,\n left,\n });\n });\n resizeObserver.observe(ref.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, [ref]);\n\n return bounds;\n}\n","import { useState, useCallback } from \"react\";\n\ntype TypeSingleSelectProps<T extends string> = {\n initialValue?: T | \"\";\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onChange?: (value: string | T) => any;\n};\n\nexport const useSelect = <T extends string>(\n {\n initialValue = \"\",\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n onChange: userOnChange = () => {},\n }: TypeSingleSelectProps<T> = {\n initialValue: \"\",\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n onChange: () => {},\n }\n) => {\n const [value, setValue] = useState<string | T>(initialValue);\n\n const onChange = useCallback(\n (newValue: string) => {\n if (newValue !== value) {\n setValue(newValue);\n userOnChange(newValue);\n }\n },\n [userOnChange, value]\n );\n\n return { value, onChange };\n};\n","import { useCallback, useEffect, useReducer, useRef } from \"react\";\n\ntype TypeMultiSelectProps<T extends string> = {\n initialValue?: T[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onChange?: (value: Array<string | T>) => any;\n};\n\nconst valueReducer = (\n state: Set<string>,\n action: { type: string; value?: string }\n): Set<string> => {\n const newState = new Set(state);\n switch (action.type) {\n case \"reset\": {\n return new Set();\n }\n case \"toggle_item\":\n default: {\n if (action.value) {\n if (newState.has(action.value)) {\n newState.delete(action.value);\n } else {\n newState.add(action.value);\n }\n }\n return newState;\n }\n }\n};\n\nexport const useMultiselect = <T extends string>(\n {\n initialValue = [],\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n onChange: userOnChange = () => {},\n }: TypeMultiSelectProps<T> = {\n initialValue: [],\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n onChange: () => {},\n }\n) => {\n const [value, dispatch] = useReducer(valueReducer, new Set(initialValue));\n\n const getArrayValue = (value: Set<string | T>) =>\n Array.from<string | T>(value);\n\n const onChange = useCallback(\n (newValue: string) => {\n dispatch({ type: \"toggle_item\", value: newValue });\n },\n [dispatch]\n );\n\n const isFirstRun = useRef(true);\n\n useEffect(() => {\n if (isFirstRun.current) {\n isFirstRun.current = false;\n return;\n }\n userOnChange(getArrayValue(value));\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [userOnChange, value]);\n\n const onClear = useCallback(() => {\n dispatch({ type: \"reset\" });\n }, [dispatch]);\n\n return { value: getArrayValue(value), onChange, onClear };\n};\n","import { canUseDOM } from \"@sproutsocial/seeds-react-utilities\";\nimport { useEffect, useMemo, useState } from \"react\";\n\ntype TypeMutationObserverInitRequired =\n | {\n childList: true;\n }\n | {\n attributes: true;\n }\n | {\n characterData: true;\n };\n\ntype TypeMutationObserverInit = {\n subtree?: boolean;\n attributeOldValue?: boolean;\n characterDataOldValue?: boolean;\n attributeFilter?: Array<string>;\n} & TypeMutationObserverInitRequired;\n\ntype TypeMutationObserverCallback = (\n mutationList?: MutationRecord[],\n observer?: MutationObserver\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n) => any;\n\nconst defaultCallback: TypeMutationObserverCallback = (mutationList) =>\n mutationList;\n\nexport function useMutationObserver(\n targetNode: Node | null,\n config: TypeMutationObserverInit,\n callback: TypeMutationObserverCallback = defaultCallback\n) {\n if (!canUseDOM()) {\n return;\n }\n /* eslint-disable-next-line */\n const [value, setValue] = useState(undefined);\n /* eslint-disable-next-line */\n const observer = useMemo(\n () =>\n new MutationObserver((mutationList, observer) => {\n const result = callback(mutationList, observer);\n setValue(result);\n }),\n [callback]\n );\n /* eslint-disable-next-line */\n useEffect(() => {\n if (targetNode) {\n observer.observe(targetNode, config);\n return () => {\n observer.disconnect();\n };\n }\n }, [targetNode, config, observer]);\n\n return value;\n}\n\nexport function useMutationObserverOnce(\n targetNode: Node | null,\n config: TypeMutationObserverInit,\n callback: TypeMutationObserverCallback\n) {\n const [isObserving, setObserving] = useState(true);\n const node = isObserving ? targetNode : null;\n const value = useMutationObserver(node, config, callback);\n if (value !== undefined && isObserving) {\n setObserving(false);\n }\n return value;\n}\n","import { useCallback, useState } from \"react\";\n\nexport type textContentRef = ((node: Node) => void) & { current?: string };\nexport function useTextContent(initial: string) {\n const [textContent, setTextContent] = useState(initial);\n\n const ref: textContentRef = useCallback((node: Node) => {\n if (node && node.textContent !== null) {\n setTextContent(node.textContent);\n }\n }, []);\n\n ref.current = textContent;\n return ref;\n}\n","import { useRef, useEffect } from \"react\";\n\nexport function useWhyDidYouUpdate(\n name: string,\n props: { [key: string]: any }\n) {\n // Get a mutable ref object where we can store props ...\n // ... for comparison next time this hook runs.\n const previousProps = useRef<typeof props>({});\n\n useEffect(() => {\n if (previousProps.current) {\n // Get all keys from previous and current props\n const allKeys = Object.keys({ ...previousProps.current, ...props });\n // Use this object to keep track of changed props\n const changesObj: typeof props = {};\n // Iterate through keys\n allKeys.forEach((key) => {\n // If previous is different from current\n\n if (previousProps.current[key] !== props[key]) {\n // Add to changesObj\n\n changesObj[key] = {\n from: previousProps.current[key],\n\n to: props[key],\n };\n }\n });\n\n // If changesObj not empty then output to console\n if (Object.keys(changesObj).length) {\n // eslint-disable-next-line no-console\n console.log(\"[why-did-you-update]\", name, changesObj);\n }\n }\n\n // Finally update previousProps with current props for next hook call\n previousProps.current = props;\n });\n}\n","import { useTheme } from \"styled-components\";\nimport type { TypeTheme } from \"@sproutsocial/seeds-react-theme\";\n\n/**\n * The useInteractiveColor hook has context of theme mode (light or dark)\n * and can be used to lighten or darken a color dynamically\n *\n * note: colors are limited to our theme colors\n */\nconst useInteractiveColor = (themeColor: string): string => {\n // Throw error if used outside of a ThemeProvider (styled-components)\n if (!useTheme()) {\n throw new Error(\n \"useInteractiveColor() must be used within a Styled Components ThemeProvider\"\n );\n }\n\n // Get the current theme mode ie. 'light' or 'dark'\n const theme: TypeTheme = useTheme() as TypeTheme;\n const themeMode = theme.mode;\n\n // If the theme mode is dark, return a lightened version of the themeValue\n if (themeMode === \"dark\") {\n return `color-mix(in srgb, ${themeColor}, white 20%)`;\n } else {\n // If the theme mode is light, return a darkened version of the themeValue\n return `color-mix(in srgb, ${themeColor}, black 20%)`;\n }\n};\n\nexport { useInteractiveColor };\n","import * as React from \"react\";\nimport { useTheme } from \"styled-components\";\nimport type { TypeTheme } from \"@sproutsocial/seeds-react-theme\";\n\n/**\n * Returns `true` when the viewport matches a mobile-width media query.\n *\n * @param breakpoint Optional override. Accepts a CSS string (`\"640px\"`,\n * `\"40em\"`) or a number (interpreted as px). Defaults to the styled-components\n * theme's `breakpoints.sm` (falling back to `\"640px\"` if the theme is\n * unavailable).\n */\nexport function useIsMobile(breakpoint?: number | string): boolean {\n const styledTheme = useTheme() as TypeTheme | undefined;\n const themeSm = styledTheme?.breakpoints?.sm ?? \"640px\";\n const resolved =\n typeof breakpoint === \"number\" ? `${breakpoint}px` : breakpoint ?? themeSm;\n\n const [isMobile, setIsMobile] = React.useState(() => {\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return false;\n }\n return window.matchMedia(`(max-width: ${resolved})`).matches;\n });\n\n React.useEffect(() => {\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return;\n }\n const mq = window.matchMedia(`(max-width: ${resolved})`);\n setIsMobile(mq.matches);\n const handler = (e: MediaQueryListEvent) => setIsMobile(e.matches);\n mq.addEventListener?.(\"change\", handler);\n return () => mq.removeEventListener?.(\"change\", handler);\n }, [resolved]);\n\n return isMobile;\n}\n","import * as React from \"react\";\nimport type { TypeBoxProps } from \"@sproutsocial/seeds-react-box\";\n\nexport interface TypeFormFieldProps extends Omit<TypeBoxProps, \"children\"> {\n /** A function that receives props that need to be spread onto the child element */\n children: (options: {\n id: string;\n isInvalid: boolean;\n ariaDescribedby?: string;\n required?: boolean;\n }) => React.ReactNode;\n\n /** Text describing any error with the field's content */\n error?: React.ReactNode;\n\n /** Text acting as a description blurb below the main label **/\n helperText?: React.ReactNode;\n\n /** ID of the form element (will be auto-generated if not provided) */\n id?: string;\n\n /** Whether the current contents of the field are invalid */\n isInvalid?: boolean;\n\n /** Label text to display above the form field */\n label: React.ReactNode;\n qa?: Record<string, any>;\n\n /** Whether the label text should be visually hidden */\n isLabelHidden?: boolean;\n\n /** Whether the form element is required */\n required?: boolean;\n}\n","import FormField from \"./FormField\";\n\nexport default FormField;\nexport { FormField };\nexport * from \"./FormFieldTypes\";\n"],"mappings":";AAAA,SAAgB,WAAAA,UAAS,YAAAC,iBAAgB;A;;;;;ACYzC,IAAMC,IAAgB,OAAO,OAAO,EAClC,GAAG,GACH,GAAG,GACH,OAAO,GACP,QAAQ,GACR,KAAK,GACL,OAAO,GACP,QAAQ,GACR,MAAM,EACR,CAAC;AIlBM,SAASC,EAAeC,GAAiB;AAC9C,MAAM,CAACC,GAAaC,CAAc,IAAIC,UAASH,CAAO,GAEhDI,IAAsBC,YAAaC,OAAe;AAClDA,SAAQA,EAAK,gBAAgB,QAC/BJ,EAAeI,EAAK,WAAW;EAEnC,GAAG,CAAA,CAAE;AAEL,SAAAF,EAAI,UAAUH,GACPG;AACT;;;ALZA,OAAO,SAAS;AAChB,OAAO,WAAW;AAClB,OAAO,UAAU;AACjB,SAAS,sBAAsB;AAiC3B,SAWM,KAXN;AA9BJ,IAAI,YAAY;AAEhB,IAAM,YAAY,CAAC;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA,IAAI;AAAA,EACJ,YAAY;AAAA,EACZ;AAAA,EACA,KAAK;AAAA,EACL;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA,GAAG;AACL,MAA0B;AACxB,QAAM,CAAC,EAAE,IAAIG,UAAS,cAAc,aAAa,WAAW,EAAE;AAC9D,QAAM,UAAU,SAAS,EAAE;AAC3B,QAAM,eAAe,cAAc,EAAE;AACrC,QAAM,gBAAgB,EAAe,EAAE;AACvC,QAAM,qBAAqB,EAAe,EAAE;AAE5C,QAAM,kBAAkBC,SAAQ,MAAM;AACpC,UAAM,MAAM;AAAA,MACV,cAAc;AAAA,MACd,aAAa,SAAS;AAAA,IACxB,EAAE,OAAO,OAAO;AAChB,WAAO,IAAI,SAAS,IAAI,IAAI,KAAK,GAAG,IAAI;AAAA,EAC1C,GAAG,CAAC,YAAY,cAAc,WAAW,OAAO,OAAO,CAAC;AAExD,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACH,GAAG;AAAA,MACJ;AAAA,MACA,qBACG,MAAM,GAAG,mBAAmB,KAAM,MAAM,cAAc;AAAA,MAEzD,+BAA6B,cAAc;AAAA,MAE1C;AAAA,wBACC,oBAAC,kBAAe,eAAY,mBAC1B,8BAAC,SAAM,SAAS,IAAI,UACjB,iBACH,GACF,IAEA,oBAAC,SAAM,IAAI,aAAa,MAAM,KAAK,SAAS,IAAI,UAC7C,iBACH;AAAA,QAED,cACC;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,UAAU;AAAA,YACV,IAAI;AAAA,YACJ,OAAM;AAAA,YACN,IAAI;AAAA,YAEH;AAAA;AAAA,QACH;AAAA,QAED,SAAS;AAAA,UACR;AAAA,UACA;AAAA,UACA;AAAA,UACA,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,QAC3C,CAAC;AAAA,QACA,aAAa,SACZ;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,UAAU;AAAA,YACV,OAAM;AAAA,YACN,IAAI;AAAA,YACJ,IAAI;AAAA,YACJ,2BACG,MAAM,GAAG,yBAAyB,KAAM,mBAAmB;AAAA,YAG7D;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAEA,IAAO,oBAAQ;;;AS7Ff,OAAuB;;;ACEvB,IAAO,gBAAQ;","names":["useMemo","useState","initialBounds","useTextContent","initial","textContent","setTextContent","useState","ref","useCallback","node","useState","useMemo"]}
|
|
1
|
+
{"version":3,"sources":["../../src/FormField.tsx","../../src/FormFieldHybrid.tsx","../../src/FormFieldStyled.tsx","../../../seeds-react-hooks/src/useMeasure/useMeasure.ts","../../../seeds-react-hooks/src/useSelect/useSelect.ts","../../../seeds-react-hooks/src/useMultiselect/useMultiselect.ts","../../../seeds-react-hooks/src/useMutationObserver/useMutationObserver.ts","../../../seeds-react-hooks/src/useTextContent/useTextContent.ts","../../../seeds-react-hooks/src/useWhyDidYouUpdate/useWhyDidYouUpdate.ts","../../../seeds-react-hooks/src/useInteractiveColor/useInteractiveColor.ts","../../../seeds-react-hooks/src/useIsMobile/useIsMobile.ts","../../src/formFieldId.ts","../../src/FormFieldTailwind.tsx","../../src/FormFieldTypes.ts","../../src/index.ts"],"sourcesContent":["import React from \"react\";\nimport type { TypeFormFieldProps } from \"./FormFieldTypes\";\nimport FormFieldHybrid from \"./FormFieldHybrid\";\n\n/**\n * FormField component. Automatically routes between the Tailwind implementation\n * (preferred) and the styled-components implementation (for consumers using\n * system props or a styled() extension).\n */\nconst FormField = React.forwardRef<HTMLDivElement, TypeFormFieldProps>(\n (props, ref) => <FormFieldHybrid {...props} ref={ref} />\n);\n\nFormField.displayName = \"FormField\";\nexport default FormField;\n","/**\n * Hybrid FormField Component\n * Automatically chooses between Tailwind and styled-components based on props.\n *\n * FormField's intrinsic props (notably `mb`, which always has a default and is\n * itself a styled-system prop) MUST be destructured out before the routing\n * check — otherwise hasStyledProps would always be true and the Tailwind path\n * would be dead code.\n */\n\nimport React from \"react\";\nimport { needsStyledComponents } from \"@sproutsocial/seeds-react-system-props\";\nimport type { TypeFormFieldProps } from \"./FormFieldTypes\";\nimport FormFieldStyled from \"./FormFieldStyled\";\nimport { FormFieldTailwind } from \"./FormFieldTailwind\";\n\n// styled-components injects a generated `sc-<hash>` className on extended\n// components. Detect it so `styled(FormField)` extensions render via the SC\n// (Box) path even when no ThemeProvider is in scope (no `theme`/\n// `forwardedComponent` prop is passed in that case).\nconst SC_CLASS_PATTERN = /\\bsc-[a-zA-Z0-9]+\\b/;\n\nconst FormFieldHybrid = React.forwardRef<HTMLDivElement, TypeFormFieldProps>(\n (props, ref) => {\n const {\n children,\n error,\n helperText,\n id,\n isInvalid,\n label,\n qa,\n isLabelHidden,\n required,\n mb,\n ...rest\n } = props;\n\n // The Tailwind path maps a scalar mb to an inline space token; a responsive\n // mb (array/object) must go through Box on the styled path.\n const mbIsResponsive =\n mb !== undefined && typeof mb !== \"number\" && typeof mb !== \"string\";\n\n const isScExtension = SC_CLASS_PATTERN.test(props.className ?? \"\");\n\n if (needsStyledComponents(rest) || mbIsResponsive || isScExtension) {\n return <FormFieldStyled {...props} ref={ref} />;\n }\n\n return <FormFieldTailwind {...props} ref={ref} />;\n }\n);\n\nFormFieldHybrid.displayName = \"FormField\";\nexport default FormFieldHybrid;\n","/**\n * Styled-components implementation of FormField.\n * Preserves the original styled-system behavior: the root Box handles all\n * styled-system props (px, width, color, bg, responsive mb, …) and supports\n * styled(FormField) extensions. This is the legacy path the Hybrid routes to\n * when styled-components rendering is required.\n */\n\nimport React, { useMemo, useState } from \"react\";\nimport { useTextContent } from \"@sproutsocial/seeds-react-hooks\";\nimport Box from \"@sproutsocial/seeds-react-box\";\nimport Label from \"@sproutsocial/seeds-react-label\";\nimport Text from \"@sproutsocial/seeds-react-text\";\nimport { VisuallyHidden } from \"@sproutsocial/seeds-react-visually-hidden\";\nimport type { TypeFormFieldProps } from \"./FormFieldTypes\";\nimport { nextFormFieldId } from \"./formFieldId\";\n\nconst FormFieldStyled = React.forwardRef<HTMLDivElement, TypeFormFieldProps>(\n (\n {\n children,\n error,\n helperText,\n id: identifier,\n isInvalid = false,\n label,\n mb = 400,\n qa,\n isLabelHidden = false,\n required,\n ...rest\n },\n ref\n ) => {\n const [id] = useState(identifier || nextFormFieldId());\n const errorId = `Error-${id}`;\n const helperTextId = `HelperText-${id}`;\n const containerText = useTextContent(\"\");\n const errorContainerText = useTextContent(\"\");\n\n const ariaDescribedby = useMemo(() => {\n const ids = [\n helperText && helperTextId,\n isInvalid && error && errorId,\n ].filter(Boolean);\n return ids.length > 0 ? ids.join(\" \") : undefined;\n }, [helperText, helperTextId, isInvalid, error, errorId]);\n\n return (\n <Box\n ref={ref}\n {...rest}\n {...qa}\n mb={mb}\n data-qa-formfield={\n (qa && qa[\"data-qa-formfield\"]) || id || containerText.current\n }\n data-qa-formfield-isinvalid={isInvalid === true}\n >\n {isLabelHidden ? (\n <VisuallyHidden data-testid=\"visually-hidden\">\n <Label htmlFor={id} required={required}>\n {label}\n </Label>\n </VisuallyHidden>\n ) : (\n <Label mb={helperText ? 100 : 300} htmlFor={id} required={required}>\n {label}\n </Label>\n )}\n {helperText && (\n <Text\n as=\"p\"\n fontSize={200}\n mb={300}\n color=\"text.subtext\"\n id={helperTextId}\n >\n {helperText}\n </Text>\n )}\n {children({\n id,\n isInvalid,\n ariaDescribedby,\n ...(required !== undefined && { required }),\n })}\n {isInvalid && error && (\n <Text\n as=\"div\"\n fontSize={200}\n color=\"text.error\"\n mt={300}\n id={errorId}\n data-qa-formfield-error={\n (qa && qa[\"data-qa-formfield-error\"]) ||\n errorContainerText.current\n }\n >\n {error}\n </Text>\n )}\n </Box>\n );\n }\n);\n\nFormFieldStyled.displayName = \"FormField\";\nexport default FormFieldStyled;\n","import { useState, useLayoutEffect, type RefObject } from \"react\";\n\ninterface DOMRectObject {\n x: number;\n y: number;\n width: number;\n height: number;\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\nconst initialBounds = Object.freeze({\n x: 0,\n y: 0,\n width: 0,\n height: 0,\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n});\n\nexport function useMeasure<TElement extends Element>(ref: RefObject<TElement>) {\n const [bounds, setContentRect] =\n useState<Readonly<DOMRectObject>>(initialBounds);\n\n useLayoutEffect(() => {\n const element = ref.current;\n\n if (\n !element ||\n // in non-browser environments (e.g. Jest tests) ResizeObserver is not defined\n !(\"ResizeObserver\" in window)\n ) {\n return;\n }\n\n const resizeObserver = new ResizeObserver(([entry]) => {\n if (!entry) return;\n const { x, y, width, height, top, right, bottom, left } =\n entry.contentRect;\n setContentRect({\n x,\n y,\n width,\n height,\n top,\n right,\n bottom,\n left,\n });\n });\n resizeObserver.observe(ref.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, [ref]);\n\n return bounds;\n}\n","import { useState, useCallback } from \"react\";\n\ntype TypeSingleSelectProps<T extends string> = {\n initialValue?: T | \"\";\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onChange?: (value: string | T) => any;\n};\n\nexport const useSelect = <T extends string>(\n {\n initialValue = \"\",\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n onChange: userOnChange = () => {},\n }: TypeSingleSelectProps<T> = {\n initialValue: \"\",\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n onChange: () => {},\n }\n) => {\n const [value, setValue] = useState<string | T>(initialValue);\n\n const onChange = useCallback(\n (newValue: string) => {\n if (newValue !== value) {\n setValue(newValue);\n userOnChange(newValue);\n }\n },\n [userOnChange, value]\n );\n\n return { value, onChange };\n};\n","import { useCallback, useEffect, useReducer, useRef } from \"react\";\n\ntype TypeMultiSelectProps<T extends string> = {\n initialValue?: T[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onChange?: (value: Array<string | T>) => any;\n};\n\nconst valueReducer = (\n state: Set<string>,\n action: { type: string; value?: string }\n): Set<string> => {\n const newState = new Set(state);\n switch (action.type) {\n case \"reset\": {\n return new Set();\n }\n case \"toggle_item\":\n default: {\n if (action.value) {\n if (newState.has(action.value)) {\n newState.delete(action.value);\n } else {\n newState.add(action.value);\n }\n }\n return newState;\n }\n }\n};\n\nexport const useMultiselect = <T extends string>(\n {\n initialValue = [],\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n onChange: userOnChange = () => {},\n }: TypeMultiSelectProps<T> = {\n initialValue: [],\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n onChange: () => {},\n }\n) => {\n const [value, dispatch] = useReducer(valueReducer, new Set(initialValue));\n\n const getArrayValue = (value: Set<string | T>) =>\n Array.from<string | T>(value);\n\n const onChange = useCallback(\n (newValue: string) => {\n dispatch({ type: \"toggle_item\", value: newValue });\n },\n [dispatch]\n );\n\n const isFirstRun = useRef(true);\n\n useEffect(() => {\n if (isFirstRun.current) {\n isFirstRun.current = false;\n return;\n }\n userOnChange(getArrayValue(value));\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [userOnChange, value]);\n\n const onClear = useCallback(() => {\n dispatch({ type: \"reset\" });\n }, [dispatch]);\n\n return { value: getArrayValue(value), onChange, onClear };\n};\n","import { canUseDOM } from \"@sproutsocial/seeds-react-utilities\";\nimport { useEffect, useMemo, useState } from \"react\";\n\ntype TypeMutationObserverInitRequired =\n | {\n childList: true;\n }\n | {\n attributes: true;\n }\n | {\n characterData: true;\n };\n\ntype TypeMutationObserverInit = {\n subtree?: boolean;\n attributeOldValue?: boolean;\n characterDataOldValue?: boolean;\n attributeFilter?: Array<string>;\n} & TypeMutationObserverInitRequired;\n\ntype TypeMutationObserverCallback = (\n mutationList?: MutationRecord[],\n observer?: MutationObserver\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n) => any;\n\nconst defaultCallback: TypeMutationObserverCallback = (mutationList) =>\n mutationList;\n\nexport function useMutationObserver(\n targetNode: Node | null,\n config: TypeMutationObserverInit,\n callback: TypeMutationObserverCallback = defaultCallback\n) {\n if (!canUseDOM()) {\n return;\n }\n /* eslint-disable-next-line */\n const [value, setValue] = useState(undefined);\n /* eslint-disable-next-line */\n const observer = useMemo(\n () =>\n new MutationObserver((mutationList, observer) => {\n const result = callback(mutationList, observer);\n setValue(result);\n }),\n [callback]\n );\n /* eslint-disable-next-line */\n useEffect(() => {\n if (targetNode) {\n observer.observe(targetNode, config);\n return () => {\n observer.disconnect();\n };\n }\n }, [targetNode, config, observer]);\n\n return value;\n}\n\nexport function useMutationObserverOnce(\n targetNode: Node | null,\n config: TypeMutationObserverInit,\n callback: TypeMutationObserverCallback\n) {\n const [isObserving, setObserving] = useState(true);\n const node = isObserving ? targetNode : null;\n const value = useMutationObserver(node, config, callback);\n if (value !== undefined && isObserving) {\n setObserving(false);\n }\n return value;\n}\n","import { useCallback, useState } from \"react\";\n\nexport type textContentRef = ((node: Node) => void) & { current?: string };\nexport function useTextContent(initial: string) {\n const [textContent, setTextContent] = useState(initial);\n\n const ref: textContentRef = useCallback((node: Node) => {\n if (node && node.textContent !== null) {\n setTextContent(node.textContent);\n }\n }, []);\n\n ref.current = textContent;\n return ref;\n}\n","import { useRef, useEffect } from \"react\";\n\nexport function useWhyDidYouUpdate(\n name: string,\n props: { [key: string]: any }\n) {\n // Get a mutable ref object where we can store props ...\n // ... for comparison next time this hook runs.\n const previousProps = useRef<typeof props>({});\n\n useEffect(() => {\n if (previousProps.current) {\n // Get all keys from previous and current props\n const allKeys = Object.keys({ ...previousProps.current, ...props });\n // Use this object to keep track of changed props\n const changesObj: typeof props = {};\n // Iterate through keys\n allKeys.forEach((key) => {\n // If previous is different from current\n\n if (previousProps.current[key] !== props[key]) {\n // Add to changesObj\n\n changesObj[key] = {\n from: previousProps.current[key],\n\n to: props[key],\n };\n }\n });\n\n // If changesObj not empty then output to console\n if (Object.keys(changesObj).length) {\n // eslint-disable-next-line no-console\n console.log(\"[why-did-you-update]\", name, changesObj);\n }\n }\n\n // Finally update previousProps with current props for next hook call\n previousProps.current = props;\n });\n}\n","import { useTheme } from \"styled-components\";\nimport type { TypeTheme } from \"@sproutsocial/seeds-react-theme\";\n\n/**\n * The useInteractiveColor hook has context of theme mode (light or dark)\n * and can be used to lighten or darken a color dynamically\n *\n * note: colors are limited to our theme colors\n */\nconst useInteractiveColor = (themeColor: string): string => {\n // Throw error if used outside of a ThemeProvider (styled-components)\n if (!useTheme()) {\n throw new Error(\n \"useInteractiveColor() must be used within a Styled Components ThemeProvider\"\n );\n }\n\n // Get the current theme mode ie. 'light' or 'dark'\n const theme: TypeTheme = useTheme() as TypeTheme;\n const themeMode = theme.mode;\n\n // If the theme mode is dark, return a lightened version of the themeValue\n if (themeMode === \"dark\") {\n return `color-mix(in srgb, ${themeColor}, white 20%)`;\n } else {\n // If the theme mode is light, return a darkened version of the themeValue\n return `color-mix(in srgb, ${themeColor}, black 20%)`;\n }\n};\n\nexport { useInteractiveColor };\n","import * as React from \"react\";\nimport { useTheme } from \"styled-components\";\nimport type { TypeTheme } from \"@sproutsocial/seeds-react-theme\";\n\n/**\n * Returns `true` when the viewport matches a mobile-width media query.\n *\n * @param breakpoint Optional override. Accepts a CSS string (`\"640px\"`,\n * `\"40em\"`) or a number (interpreted as px). Defaults to the styled-components\n * theme's `breakpoints.sm` (falling back to `\"640px\"` if the theme is\n * unavailable).\n */\nexport function useIsMobile(breakpoint?: number | string): boolean {\n const styledTheme = useTheme() as TypeTheme | undefined;\n const themeSm = styledTheme?.breakpoints?.sm ?? \"640px\";\n const resolved =\n typeof breakpoint === \"number\" ? `${breakpoint}px` : breakpoint ?? themeSm;\n\n const [isMobile, setIsMobile] = React.useState(() => {\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return false;\n }\n return window.matchMedia(`(max-width: ${resolved})`).matches;\n });\n\n React.useEffect(() => {\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return;\n }\n const mq = window.matchMedia(`(max-width: ${resolved})`);\n setIsMobile(mq.matches);\n const handler = (e: MediaQueryListEvent) => setIsMobile(e.matches);\n mq.addEventListener?.(\"change\", handler);\n return () => mq.removeEventListener?.(\"change\", handler);\n }, [resolved]);\n\n return isMobile;\n}\n","/**\n * Single module-level counter shared by both FormField render paths\n * (FormFieldTailwind and FormFieldStyled). A page with mixed routing draws\n * sequential ids from this one counter, so auto-generated ids never collide\n * across the two paths. Preserves the pre-split `FormField-N` id format.\n */\n\nlet idCounter = 0;\n\nexport function nextFormFieldId(): string {\n return `FormField-${idCounter++}`;\n}\n","/**\n * Tailwind CSS implementation of FormField.\n * Uses the Seeds form-field CSS class from form-field.css. Only FormField's own\n * wrapper becomes a plain <div className=\"seeds-form-field\"> — the Label, helper\n * Text, error Text, VisuallyHidden, and children render-prop are composed exactly\n * as in the styled path so the accessibility/qa contract is preserved.\n */\n\nimport React, { useMemo, useState } from \"react\";\nimport { useTextContent } from \"@sproutsocial/seeds-react-hooks\";\nimport Label from \"@sproutsocial/seeds-react-label\";\nimport Text from \"@sproutsocial/seeds-react-text\";\nimport { VisuallyHidden } from \"@sproutsocial/seeds-react-visually-hidden\";\nimport type { TypeFormFieldProps } from \"./FormFieldTypes\";\nimport { nextFormFieldId } from \"./formFieldId\";\n\n// Utility for merging class names properly\nfunction cn(\n ...inputs: (string | undefined | null | false | Record<string, boolean>)[]\n): string {\n const classes: string[] = [];\n\n for (const input of inputs) {\n if (!input) continue;\n\n if (typeof input === \"string\") {\n classes.push(input);\n } else if (typeof input === \"object\") {\n for (const [key, value] of Object.entries(input)) {\n if (value) {\n classes.push(key);\n }\n }\n }\n }\n\n return classes.join(\" \");\n}\n\nexport const FormFieldTailwind = React.forwardRef<\n HTMLDivElement,\n TypeFormFieldProps\n>((props, ref) => {\n const {\n children,\n error,\n helperText,\n id: identifier,\n isInvalid = false,\n label,\n mb = 400,\n qa,\n isLabelHidden = false,\n required,\n className,\n style,\n ...rest\n } = props as TypeFormFieldProps & {\n className?: string;\n style?: React.CSSProperties;\n };\n\n const [id] = useState(identifier || nextFormFieldId());\n const errorId = `Error-${id}`;\n const helperTextId = `HelperText-${id}`;\n const containerText = useTextContent(\"\");\n const errorContainerText = useTextContent(\"\");\n\n const ariaDescribedby = useMemo(() => {\n const ids = [\n helperText && helperTextId,\n isInvalid && error && errorId,\n ].filter(Boolean);\n return ids.length > 0 ? ids.join(\" \") : undefined;\n }, [helperText, helperTextId, isInvalid, error, errorId]);\n\n return (\n <div\n ref={ref}\n className={cn(\"seeds-form-field\", className)}\n // `rest` is typed with the full styled-system Box prop surface (via\n // TypeFormFieldProps extends Omit<TypeBoxProps, \"children\">), but the\n // Hybrid routes any instance carrying styled-system props to the styled\n // path, so on the Tailwind path `rest` only ever holds native div\n // attributes at runtime. Widen the spread to the div's attribute type.\n {...(rest as unknown as React.HTMLAttributes<HTMLDivElement>)}\n {...qa}\n // FormField's only own style is the bottom margin; map the space token\n // inline so scalar mb matches the styled path (responsive mb is routed to\n // the styled path by FormFieldHybrid).\n style={{ marginBottom: `var(--space-${String(mb)})`, ...style }}\n data-qa-formfield={\n (qa && qa[\"data-qa-formfield\"]) || id || containerText.current\n }\n data-qa-formfield-isinvalid={isInvalid === true}\n >\n {isLabelHidden ? (\n <VisuallyHidden data-testid=\"visually-hidden\">\n <Label htmlFor={id} required={required}>\n {label}\n </Label>\n </VisuallyHidden>\n ) : (\n <Label mb={helperText ? 100 : 300} htmlFor={id} required={required}>\n {label}\n </Label>\n )}\n {helperText && (\n <Text\n as=\"p\"\n fontSize={200}\n mb={300}\n color=\"text.subtext\"\n id={helperTextId}\n >\n {helperText}\n </Text>\n )}\n {children({\n id,\n isInvalid,\n ariaDescribedby,\n ...(required !== undefined && { required }),\n })}\n {isInvalid && error && (\n <Text\n as=\"div\"\n fontSize={200}\n color=\"text.error\"\n mt={300}\n id={errorId}\n data-qa-formfield-error={\n (qa && qa[\"data-qa-formfield-error\"]) || errorContainerText.current\n }\n >\n {error}\n </Text>\n )}\n </div>\n );\n});\n\nFormFieldTailwind.displayName = \"FormFieldTailwind\";\n","import * as React from \"react\";\nimport type { TypeBoxProps } from \"@sproutsocial/seeds-react-box\";\n\nexport interface TypeFormFieldProps extends Omit<TypeBoxProps, \"children\"> {\n /** A function that receives props that need to be spread onto the child element */\n children: (options: {\n id: string;\n isInvalid: boolean;\n ariaDescribedby?: string;\n required?: boolean;\n }) => React.ReactNode;\n\n /** Text describing any error with the field's content */\n error?: React.ReactNode;\n\n /** Text acting as a description blurb below the main label **/\n helperText?: React.ReactNode;\n\n /** ID of the form element (will be auto-generated if not provided) */\n id?: string;\n\n /** Whether the current contents of the field are invalid */\n isInvalid?: boolean;\n\n /** Label text to display above the form field */\n label: React.ReactNode;\n qa?: Record<string, any>;\n\n /** Whether the label text should be visually hidden */\n isLabelHidden?: boolean;\n\n /** Whether the form element is required */\n required?: boolean;\n}\n","import FormField from \"./FormField\";\n\nexport default FormField;\nexport { FormField };\nexport * from \"./FormFieldTypes\";\n"],"mappings":"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","names":["React","React","useMemo","useState","initialBounds","useTextContent","initial","textContent","setTextContent","useState","ref","useCallback","node","useState","useMemo","React","useMemo","useState","Label","Text","VisuallyHidden","jsx","jsxs","React","useState","useMemo","VisuallyHidden","Label","Text","jsx","React","jsx","React"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seeds FormField component classes
|
|
3
|
+
* Use these instead of writing out individual Tailwind utility classes.
|
|
4
|
+
*
|
|
5
|
+
* Requires @sproutsocial/seeds-react-theme/dist/theme-all.css imported for
|
|
6
|
+
* CSS variable definitions and dark mode support.
|
|
7
|
+
*
|
|
8
|
+
* FormField only contributes the container's vertical layout/margin; the
|
|
9
|
+
* label, helper text, and error text colors and spacing are owned by the
|
|
10
|
+
* composed Label/Text children (via their styled-system tokens), so this
|
|
11
|
+
* class carries layout only and hard-codes no color tokens.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* <div class="seeds-form-field">…label, field, error…</div>
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
@layer components {
|
|
18
|
+
.seeds-form-field {
|
|
19
|
+
display: block;
|
|
20
|
+
}
|
|
21
|
+
}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
1
|
import * as React from 'react';
|
|
2
|
+
import React__default from 'react';
|
|
3
3
|
import { TypeBoxProps } from '@sproutsocial/seeds-react-box';
|
|
4
4
|
|
|
5
5
|
interface TypeFormFieldProps extends Omit<TypeBoxProps, "children"> {
|
|
@@ -27,6 +27,11 @@ interface TypeFormFieldProps extends Omit<TypeBoxProps, "children"> {
|
|
|
27
27
|
required?: boolean;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* FormField component. Automatically routes between the Tailwind implementation
|
|
32
|
+
* (preferred) and the styled-components implementation (for consumers using
|
|
33
|
+
* system props or a styled() extension).
|
|
34
|
+
*/
|
|
35
|
+
declare const FormField: React__default.ForwardRefExoticComponent<Omit<TypeFormFieldProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
|
|
31
36
|
|
|
32
37
|
export { FormField, type TypeFormFieldProps, FormField as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
1
|
import * as React from 'react';
|
|
2
|
+
import React__default from 'react';
|
|
3
3
|
import { TypeBoxProps } from '@sproutsocial/seeds-react-box';
|
|
4
4
|
|
|
5
5
|
interface TypeFormFieldProps extends Omit<TypeBoxProps, "children"> {
|
|
@@ -27,6 +27,11 @@ interface TypeFormFieldProps extends Omit<TypeBoxProps, "children"> {
|
|
|
27
27
|
required?: boolean;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* FormField component. Automatically routes between the Tailwind implementation
|
|
32
|
+
* (preferred) and the styled-components implementation (for consumers using
|
|
33
|
+
* system props or a styled() extension).
|
|
34
|
+
*/
|
|
35
|
+
declare const FormField: React__default.ForwardRefExoticComponent<Omit<TypeFormFieldProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
|
|
31
36
|
|
|
32
37
|
export { FormField, type TypeFormFieldProps, FormField as default };
|