@coopdigital/react 0.43.0 → 0.45.0
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/components/Checkbox/Checkbox.d.ts +4 -24
- package/dist/components/Checkbox/Checkbox.js +7 -14
- package/dist/components/Checkbox/index.d.ts +1 -2
- package/dist/components/Field/Field.d.ts +44 -5
- package/dist/components/Field/Field.js +68 -5
- package/dist/components/FieldMarkers/Error.d.ts +9 -0
- package/dist/components/{FieldError/FieldError.js → FieldMarkers/Error.js} +2 -2
- package/dist/components/FieldMarkers/Hint.d.ts +9 -0
- package/dist/components/{FieldHint/FieldHint.js → FieldMarkers/Hint.js} +2 -2
- package/dist/components/FieldMarkers/Label.d.ts +11 -0
- package/dist/components/FieldMarkers/Label.js +8 -0
- package/dist/components/FieldMarkers/Legend.d.ts +11 -0
- package/dist/components/FieldMarkers/Legend.js +13 -0
- package/dist/components/Fieldset/Fieldset.d.ts +54 -0
- package/dist/components/Fieldset/Fieldset.js +44 -0
- package/dist/components/Fieldset/index.d.ts +4 -0
- package/dist/components/RadioButton/RadioButton.d.ts +4 -22
- package/dist/components/RadioButton/RadioButton.js +5 -9
- package/dist/components/RadioButton/index.d.ts +1 -2
- package/dist/components/SearchBox/SearchBox.js +3 -3
- package/dist/components/TextInput/TextInput.d.ts +4 -18
- package/dist/components/TextInput/TextInput.js +7 -11
- package/dist/components/Textarea/Textarea.d.ts +4 -18
- package/dist/components/Textarea/Textarea.js +8 -9
- package/dist/hooks/useId.d.ts +2 -0
- package/dist/hooks/useId.js +8 -0
- package/dist/index.d.ts +1 -3
- package/dist/index.js +2 -6
- package/dist/utils/slots.d.ts +3 -1
- package/dist/utils/slots.js +8 -2
- package/package.json +10 -10
- package/src/components/Checkbox/Checkbox.tsx +8 -64
- package/src/components/Checkbox/index.ts +1 -2
- package/src/components/Field/Field.tsx +144 -8
- package/src/components/{FieldError/FieldError.tsx → FieldMarkers/Error.tsx} +4 -4
- package/src/components/{FieldHint/FieldHint.tsx → FieldMarkers/Hint.tsx} +5 -9
- package/src/components/FieldMarkers/Label.tsx +21 -0
- package/src/components/FieldMarkers/Legend.tsx +28 -0
- package/src/components/Fieldset/Fieldset.tsx +98 -0
- package/src/components/Fieldset/index.ts +5 -0
- package/src/components/RadioButton/RadioButton.tsx +6 -55
- package/src/components/RadioButton/index.ts +1 -2
- package/src/components/SearchBox/SearchBox.tsx +4 -2
- package/src/components/TextInput/TextInput.tsx +12 -45
- package/src/components/Textarea/Textarea.tsx +12 -40
- package/src/hooks/useId.ts +9 -0
- package/src/index.ts +1 -3
- package/src/utils/slots.ts +10 -2
- package/dist/components/Checkbox/CheckboxGroup.d.ts +0 -32
- package/dist/components/Checkbox/CheckboxGroup.js +0 -21
- package/dist/components/FieldError/FieldError.d.ts +0 -9
- package/dist/components/FieldError/index.d.ts +0 -4
- package/dist/components/FieldHint/FieldHint.d.ts +0 -9
- package/dist/components/FieldHint/index.d.ts +0 -4
- package/dist/components/FieldLabel/FieldLabel.d.ts +0 -13
- package/dist/components/FieldLabel/FieldLabel.js +0 -13
- package/dist/components/FieldLabel/index.d.ts +0 -4
- package/dist/components/RadioButton/RadioButtonGroup.d.ts +0 -32
- package/dist/components/RadioButton/RadioButtonGroup.js +0 -21
- package/dist/components/Tag/index.js +0 -5
- package/src/components/Checkbox/CheckboxGroup.tsx +0 -73
- package/src/components/FieldError/index.ts +0 -5
- package/src/components/FieldHint/index.ts +0 -5
- package/src/components/FieldLabel/FieldLabel.tsx +0 -31
- package/src/components/FieldLabel/index.ts +0 -5
- package/src/components/RadioButton/RadioButtonGroup.tsx +0 -73
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { JSX, TextareaHTMLAttributes } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { StandardSizes } from "../../types";
|
|
3
3
|
export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
4
4
|
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
5
5
|
className?: string;
|
|
@@ -16,24 +16,10 @@ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElemen
|
|
|
16
16
|
cutoff?: boolean;
|
|
17
17
|
/** **(Optional)** Specify whether the Textarea should be disabled. Refer to Experience Library guidance on disabled form controls and accessibility. */
|
|
18
18
|
disabled?: boolean;
|
|
19
|
-
/** **(Optional)** Specify the Textarea error state
|
|
20
|
-
|
|
21
|
-
* This is an instance of `FormFieldError`. You can provide either an object with a `message` key, or a boolean value if you need to render the message independently.
|
|
22
|
-
*/
|
|
23
|
-
error?: FormFieldError;
|
|
24
|
-
/** **(Optional)** Specify the Textarea hint.
|
|
25
|
-
*
|
|
26
|
-
* This text is rendered under the label to provide further guidance for users.
|
|
27
|
-
*/
|
|
28
|
-
hint?: string;
|
|
19
|
+
/** **(Optional)** Specify the Textarea error state */
|
|
20
|
+
error?: boolean;
|
|
29
21
|
/** **(Optional)** Specify the Textarea id. Will be auto-generated if not set. */
|
|
30
22
|
id?: string;
|
|
31
|
-
/** **(Optional)** Specify the Textarea label.
|
|
32
|
-
*
|
|
33
|
-
* This property is optional in case you need to render your own label, but all form elements *must* provide a label. */
|
|
34
|
-
label?: string;
|
|
35
|
-
/** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
|
|
36
|
-
labelVisible?: boolean;
|
|
37
23
|
/** **(Optional)** Specify the Textarea maxLength. This is the maximum number of characters users can enter. */
|
|
38
24
|
maxLength?: number;
|
|
39
25
|
/** Specify the Textarea name. */
|
|
@@ -45,5 +31,5 @@ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElemen
|
|
|
45
31
|
/** **(Optional)** Specify the Textarea size. */
|
|
46
32
|
size?: StandardSizes;
|
|
47
33
|
}
|
|
48
|
-
export declare const Textarea: ({ "aria-placeholder": ariaPlaceholder, className, cols, counter, cutoff, disabled, error,
|
|
34
|
+
export declare const Textarea: ({ "aria-placeholder": ariaPlaceholder, className, cols, counter, cutoff, disabled, error, id, maxLength, name, onChange: userOnChange, placeholder, rows, size, ...props }: TextareaProps) => JSX.Element;
|
|
49
35
|
export default Textarea;
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
|
-
import {
|
|
3
|
+
import { useState } from 'react';
|
|
4
4
|
import { useDebounce } from '../../hooks/useDebounce.js';
|
|
5
|
-
import {
|
|
6
|
-
import { FieldHint } from '../FieldHint/FieldHint.js';
|
|
7
|
-
import { FieldLabel } from '../FieldLabel/FieldLabel.js';
|
|
5
|
+
import { useId } from '../../hooks/useId.js';
|
|
8
6
|
|
|
9
7
|
const DEBOUNCE_DELAY = 750;
|
|
10
8
|
const charCountMessage = (remaining) => {
|
|
11
9
|
return `You have ${Math.abs(remaining).toLocaleString()} ${Math.abs(remaining) === 1 ? "character" : "characters"} ${remaining < 0 ? "too many" : "remaining"}`;
|
|
12
10
|
};
|
|
13
|
-
const Textarea = ({ "aria-placeholder": ariaPlaceholder, className, cols = 30, counter = false, cutoff = false, disabled = false, error = false,
|
|
11
|
+
const Textarea = ({ "aria-placeholder": ariaPlaceholder, className, cols = 30, counter = false, cutoff = false, disabled = false, error = false, id, maxLength, name, onChange: userOnChange = undefined, placeholder, rows = 4, size = "md", ...props }) => {
|
|
14
12
|
var _a;
|
|
15
13
|
const internalId = useId();
|
|
16
14
|
id = id !== null && id !== void 0 ? id : internalId;
|
|
@@ -18,7 +16,7 @@ const Textarea = ({ "aria-placeholder": ariaPlaceholder, className, cols = 30, c
|
|
|
18
16
|
"aria-placeholder": (_a = placeholder !== null && placeholder !== void 0 ? placeholder : ariaPlaceholder) !== null && _a !== void 0 ? _a : undefined,
|
|
19
17
|
className: clsx("coop-textarea", className),
|
|
20
18
|
cols,
|
|
21
|
-
"data-error": error ?
|
|
19
|
+
"data-error": error !== null && error !== void 0 ? error : undefined,
|
|
22
20
|
"data-size": size.length && size !== "md" ? size : undefined,
|
|
23
21
|
disabled,
|
|
24
22
|
id,
|
|
@@ -30,14 +28,15 @@ const Textarea = ({ "aria-placeholder": ariaPlaceholder, className, cols = 30, c
|
|
|
30
28
|
};
|
|
31
29
|
const [remaining, setRemaining] = useState(maxLength);
|
|
32
30
|
const debouncedRemaining = useDebounce(remaining, DEBOUNCE_DELAY);
|
|
31
|
+
const showCounter = !disabled && counter && maxLength && remaining != null && debouncedRemaining != null;
|
|
33
32
|
const handleChange = (e) => {
|
|
34
33
|
maxLength && e.target && setRemaining(maxLength - e.target.value.length);
|
|
35
34
|
};
|
|
36
|
-
const formItemProps = { "aria-disabled": disabled ? true : undefined }
|
|
37
|
-
return (jsxs("div", { className: "coop-
|
|
35
|
+
// const formItemProps = { "aria-disabled": disabled ? true : undefined }
|
|
36
|
+
return (jsxs("div", { className: "coop-textarea-wrapper", children: [jsx("textarea", { ...componentProps, onChange: (e) => {
|
|
38
37
|
userOnChange === null || userOnChange === void 0 ? void 0 : userOnChange(e);
|
|
39
38
|
handleChange(e);
|
|
40
|
-
} }),
|
|
39
|
+
} }), showCounter && (jsxs(Fragment, { children: [jsx("small", { "aria-hidden": "true", className: "coop-textarea-counter", ...(remaining < 0 && { "data-error": true }), children: charCountMessage(remaining) }), jsx("span", { "aria-live": "polite", className: "sr-only", children: charCountMessage(debouncedRemaining) })] }))] }));
|
|
41
40
|
};
|
|
42
41
|
|
|
43
42
|
export { Textarea, Textarea as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,9 +5,7 @@ export * from "./components/Card";
|
|
|
5
5
|
export * from "./components/Checkbox";
|
|
6
6
|
export * from "./components/Expandable";
|
|
7
7
|
export * from "./components/Field";
|
|
8
|
-
export * from "./components/
|
|
9
|
-
export * from "./components/FieldHint";
|
|
10
|
-
export * from "./components/FieldLabel";
|
|
8
|
+
export * from "./components/Fieldset";
|
|
11
9
|
export * from "./components/Flourish";
|
|
12
10
|
export * from "./components/Image";
|
|
13
11
|
export * from "./components/Pill";
|
package/dist/index.js
CHANGED
|
@@ -3,17 +3,13 @@ export { Author } from './components/Author/Author.js';
|
|
|
3
3
|
export { Button } from './components/Button/Button.js';
|
|
4
4
|
export { Card } from './components/Card/Card.js';
|
|
5
5
|
export { Checkbox } from './components/Checkbox/Checkbox.js';
|
|
6
|
-
export { CheckboxGroup } from './components/Checkbox/CheckboxGroup.js';
|
|
7
6
|
export { Expandable } from './components/Expandable/Expandable.js';
|
|
8
|
-
export { Field } from './components/Field/Field.js';
|
|
9
|
-
export {
|
|
10
|
-
export { FieldHint } from './components/FieldHint/FieldHint.js';
|
|
11
|
-
export { FieldLabel } from './components/FieldLabel/FieldLabel.js';
|
|
7
|
+
export { Field, FieldControl } from './components/Field/Field.js';
|
|
8
|
+
export { Fieldset, FieldsetFields } from './components/Fieldset/Fieldset.js';
|
|
12
9
|
export { Flourish } from './components/Flourish/Flourish.js';
|
|
13
10
|
export { Image } from './components/Image/Image.js';
|
|
14
11
|
export { Pill } from './components/Pill/Pill.js';
|
|
15
12
|
export { RadioButton } from './components/RadioButton/RadioButton.js';
|
|
16
|
-
export { RadioButtonGroup } from './components/RadioButton/RadioButtonGroup.js';
|
|
17
13
|
export { RootSVG } from './components/RootSVG/RootSVG.js';
|
|
18
14
|
export { SearchBox } from './components/SearchBox/SearchBox.js';
|
|
19
15
|
export { Signpost } from './components/Signpost/Signpost.js';
|
package/dist/utils/slots.d.ts
CHANGED
|
@@ -2,5 +2,7 @@ import React from "react";
|
|
|
2
2
|
type Slots<T> = Record<keyof T, React.ReactNode>;
|
|
3
3
|
export declare function isKey<T extends object>(x: T, k: PropertyKey): k is keyof T;
|
|
4
4
|
export declare function getSlotName(node: React.ReactNode): string | false;
|
|
5
|
-
export declare function getSlots<T>(componentSlots: Slots<T>, children: React.ReactNode
|
|
5
|
+
export declare function getSlots<T>(componentSlots: Slots<T>, children: React.ReactNode, options?: {
|
|
6
|
+
collect?: string[];
|
|
7
|
+
}): Slots<T>;
|
|
6
8
|
export {};
|
package/dist/utils/slots.js
CHANGED
|
@@ -8,11 +8,17 @@ function getSlotName(node) {
|
|
|
8
8
|
? node.type.name
|
|
9
9
|
: false;
|
|
10
10
|
}
|
|
11
|
-
function getSlots(componentSlots, children) {
|
|
11
|
+
function getSlots(componentSlots, children, options) {
|
|
12
12
|
return React.Children.toArray(children).reduce((slots, child) => {
|
|
13
|
+
var _a;
|
|
13
14
|
const slotName = getSlotName(child);
|
|
14
15
|
if (child && slotName && isKey(componentSlots, slotName)) {
|
|
15
|
-
|
|
16
|
+
if ((_a = void 0 ) === null || _a === void 0 ? void 0 : _a.includes(slotName)) {
|
|
17
|
+
slots[slotName] = slots[slotName] ? [...[slots[slotName]].flat(), child] : [child];
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
slots[slotName] = child;
|
|
21
|
+
}
|
|
16
22
|
}
|
|
17
23
|
else if ("Children" in slots) {
|
|
18
24
|
slots.Children = slots.Children ? [...[slots.Children].flat(), child] : [child];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coopdigital/react",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.45.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -57,18 +57,18 @@
|
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@axe-core/playwright": "^4.11.0",
|
|
59
59
|
"@playwright/test": "^1.56.1",
|
|
60
|
-
"@storybook/addon-a11y": "^10.0.
|
|
61
|
-
"@storybook/addon-docs": "^10.0.
|
|
62
|
-
"@storybook/addon-onboarding": "^10.0.
|
|
63
|
-
"@storybook/react-vite": "^10.0.
|
|
60
|
+
"@storybook/addon-a11y": "^10.0.7",
|
|
61
|
+
"@storybook/addon-docs": "^10.0.7",
|
|
62
|
+
"@storybook/addon-onboarding": "^10.0.7",
|
|
63
|
+
"@storybook/react-vite": "^10.0.7",
|
|
64
64
|
"@testing-library/jest-dom": "^6.9.1",
|
|
65
65
|
"@testing-library/react": "^16.3.0",
|
|
66
|
-
"@types/react": "^19.2.
|
|
67
|
-
"@types/react-dom": "^19.2.
|
|
66
|
+
"@types/react": "^19.2.4",
|
|
67
|
+
"@types/react-dom": "^19.2.3",
|
|
68
68
|
"react": "^19.2.0",
|
|
69
69
|
"react-dom": "^19.2.0",
|
|
70
70
|
"serve": "^14.2.5",
|
|
71
|
-
"storybook": "^10.0.
|
|
71
|
+
"storybook": "^10.0.7"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
74
|
"react": "^19.1.0",
|
|
@@ -78,8 +78,8 @@
|
|
|
78
78
|
"storybook": "$storybook"
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"@coopdigital/styles": "^0.
|
|
81
|
+
"@coopdigital/styles": "^0.39.0",
|
|
82
82
|
"clsx": "^2.1.1"
|
|
83
83
|
},
|
|
84
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "113b5fc44feee8b4e899c75c303f1ed26f9f2629"
|
|
85
85
|
}
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import type { InputHTMLAttributes, JSX } from "react"
|
|
2
2
|
|
|
3
3
|
import clsx from "clsx"
|
|
4
|
-
import { useId } from "react"
|
|
5
4
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { FieldHint } from "../FieldHint"
|
|
9
|
-
import { FieldLabel } from "../FieldLabel"
|
|
10
|
-
import Tag from "../Tag"
|
|
5
|
+
import { StandardSizes } from "../../../src/types"
|
|
6
|
+
import { useId } from "../../hooks/useId"
|
|
11
7
|
|
|
12
8
|
export interface CheckboxProps
|
|
13
9
|
extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
|
|
@@ -15,89 +11,37 @@ export interface CheckboxProps
|
|
|
15
11
|
className?: string
|
|
16
12
|
/** **(Optional)** Specify whether the Checkbox should be disabled. Refer to Experience Library guidance on disabled form controls and accessibility. */
|
|
17
13
|
disabled?: boolean
|
|
18
|
-
/** **(Optional)** Specify the Checkbox error state.
|
|
19
|
-
|
|
20
|
-
* This is an instance of `FormFieldError`. You can provide either an object with a `message` key, or a boolean value if you need to render the message independently.
|
|
21
|
-
*/
|
|
22
|
-
error?: FormFieldError
|
|
23
|
-
/** **(Optional)** Specify the Checkbox hint.
|
|
24
|
-
*
|
|
25
|
-
* This text is rendered under the label to provide further guidance for users.
|
|
26
|
-
*/
|
|
27
|
-
hint?: string
|
|
14
|
+
/** **(Optional)** Specify the Checkbox error state. */
|
|
15
|
+
error?: boolean
|
|
28
16
|
/** **(Optional)** Specify the Checkbox id. Will be auto-generated if not set. */
|
|
29
17
|
id?: string
|
|
30
|
-
/** **(Optional)** Specify the Checkbox label.
|
|
31
|
-
*
|
|
32
|
-
* This property is optional in case you need to render your own label, but all form elements *must* provide a label. */
|
|
33
|
-
label: string
|
|
34
|
-
/** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
|
|
35
|
-
labelVisible?: boolean
|
|
36
18
|
/** Specify the Checkbox name. */
|
|
37
19
|
name: string
|
|
38
20
|
/** **(Optional)** Specify the Checkbox size. */
|
|
39
21
|
size?: StandardSizes
|
|
40
|
-
/** **(Optional)** Specify the Checkbox tag text. */
|
|
41
|
-
tag?: string
|
|
42
|
-
/** **(Optional)** Specify the Checkbox tag className. */
|
|
43
|
-
tagClassName?: string
|
|
44
|
-
/** **(Optional)** Specify the Checkbox variant. */
|
|
45
|
-
variant?: "default" | "boxed"
|
|
46
22
|
}
|
|
47
23
|
|
|
48
24
|
export const Checkbox = ({
|
|
49
25
|
className,
|
|
50
26
|
disabled,
|
|
51
27
|
error = false,
|
|
52
|
-
hint,
|
|
53
28
|
id,
|
|
54
|
-
label,
|
|
55
|
-
labelVisible = true,
|
|
56
29
|
name,
|
|
57
30
|
size = "md",
|
|
58
|
-
tag,
|
|
59
|
-
tagClassName,
|
|
60
|
-
variant = "default",
|
|
61
31
|
...props
|
|
62
32
|
}: CheckboxProps): JSX.Element => {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
id = id ?? internalId
|
|
66
|
-
|
|
33
|
+
const uid = useId(id)
|
|
67
34
|
const componentProps = {
|
|
68
35
|
className: clsx("coop-checkbox", className),
|
|
69
|
-
"data-error": error
|
|
36
|
+
"data-error": error || undefined,
|
|
70
37
|
"data-size": size.length && size !== "md" ? size : undefined,
|
|
71
|
-
"data-variant": variant !== "default" ? variant : undefined,
|
|
72
38
|
disabled,
|
|
73
|
-
id,
|
|
39
|
+
id: uid,
|
|
74
40
|
name,
|
|
75
41
|
type: "checkbox",
|
|
76
42
|
...props,
|
|
77
43
|
}
|
|
78
|
-
|
|
79
|
-
return (
|
|
80
|
-
<div className="coop-form-item" {...formItemProps}>
|
|
81
|
-
<div className="coop-checkbox-wrapper">
|
|
82
|
-
<div className="coop-checkbox-input-wrapper">
|
|
83
|
-
<input {...componentProps} />
|
|
84
|
-
|
|
85
|
-
{label && (
|
|
86
|
-
<FieldLabel htmlFor={id} isVisible={labelVisible}>
|
|
87
|
-
<span>{label}</span>{" "}
|
|
88
|
-
{tag && (
|
|
89
|
-
<Tag className={tagClassName} size="sm">
|
|
90
|
-
{tag}
|
|
91
|
-
</Tag>
|
|
92
|
-
)}
|
|
93
|
-
</FieldLabel>
|
|
94
|
-
)}
|
|
95
|
-
</div>
|
|
96
|
-
{hint && <FieldHint>{hint}</FieldHint>}
|
|
97
|
-
</div>
|
|
98
|
-
{typeof error === "object" && error?.message && <FieldError>{error.message}</FieldError>}
|
|
99
|
-
</div>
|
|
100
|
-
)
|
|
44
|
+
return <input {...componentProps} />
|
|
101
45
|
}
|
|
102
46
|
|
|
103
47
|
export default Checkbox
|
|
@@ -1,20 +1,156 @@
|
|
|
1
|
-
import type { HTMLAttributes,
|
|
1
|
+
import type { HTMLAttributes, HTMLProps, JSX } from "react"
|
|
2
2
|
|
|
3
3
|
import clsx from "clsx"
|
|
4
|
+
import React from "react"
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { useId } from "../../hooks/useId"
|
|
7
|
+
import { getSlots } from "../../utils/slots"
|
|
8
|
+
import { Error as BaseError, type ErrorProps } from "../FieldMarkers/Error"
|
|
9
|
+
import { Hint as BaseHint, type HintProps } from "../FieldMarkers/Hint"
|
|
10
|
+
import { Label as BaseLabel, type LabelProps } from "../FieldMarkers/Label"
|
|
11
|
+
|
|
12
|
+
const componentSlots = {
|
|
13
|
+
Checkbox: null,
|
|
14
|
+
Children: null,
|
|
15
|
+
Control: null,
|
|
16
|
+
FieldError: null,
|
|
17
|
+
FieldHint: null,
|
|
18
|
+
FieldLabel: null,
|
|
19
|
+
RadioButton: null,
|
|
20
|
+
Textarea: null,
|
|
21
|
+
TextInput: null,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const standardFields = ["Children", "FieldError", "FieldHint", "FieldLabel"]
|
|
25
|
+
const inlineFields = ["Checkbox", "RadioButton"]
|
|
26
|
+
|
|
27
|
+
interface FieldProps extends HTMLAttributes<HTMLDivElement> {
|
|
28
|
+
/** **(Optional)** Specify whether the Field should render inside a box. */
|
|
29
|
+
boxed?: boolean
|
|
30
|
+
/** Form elements inside the Field.
|
|
31
|
+
*
|
|
32
|
+
* Usually this will be a combination of `Field.Label`, `Field.Hint`, `Field.Error` and an associated control such as `TextInput` or `Checkbox`. */
|
|
33
|
+
children: React.ReactNode
|
|
8
34
|
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
9
|
-
|
|
35
|
+
classname?: string
|
|
36
|
+
/** **(Optional)** Specify the Field error state. Use this to apply visual styling to the field control. */
|
|
37
|
+
error?: boolean
|
|
38
|
+
/** **(Optional)** Specify whether the Field should show the left-hand error bar if it is in an error state. */
|
|
39
|
+
hideErrorBar?: boolean
|
|
10
40
|
}
|
|
11
41
|
|
|
12
|
-
|
|
42
|
+
const Root = ({
|
|
43
|
+
boxed = false,
|
|
44
|
+
children,
|
|
45
|
+
className,
|
|
46
|
+
error = false,
|
|
47
|
+
hideErrorBar = false,
|
|
48
|
+
...props
|
|
49
|
+
}: FieldProps) => {
|
|
50
|
+
const slots = getSlots(componentSlots, children)
|
|
51
|
+
const slotsArray = Object.entries(slots)
|
|
52
|
+
|
|
53
|
+
const uid = useId()
|
|
54
|
+
|
|
55
|
+
const isInlineControl = slotsArray.some((s) => s[1] !== null && inlineFields.includes(s[0]))
|
|
56
|
+
const hasFieldMarkers = !!(slots.FieldLabel ?? slots.FieldError ?? slots.FieldHint)
|
|
57
|
+
const [, control] =
|
|
58
|
+
slotsArray.find((s) => (!standardFields.includes(s[0]) && s[1] !== null ? s : null)) ?? []
|
|
59
|
+
|
|
60
|
+
let controlId = uid
|
|
61
|
+
let controlDisabled = false
|
|
62
|
+
|
|
63
|
+
if (control) {
|
|
64
|
+
if (typeof control === "object" && "props" in control) {
|
|
65
|
+
const controlProps = control.props as HTMLProps<HTMLElement>
|
|
66
|
+
controlId = controlProps.id ?? controlId
|
|
67
|
+
controlDisabled = controlProps.disabled ?? false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
slots.Control = React.cloneElement(control as React.ReactElement<HTMLInputElement>, {
|
|
71
|
+
id: controlId,
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (slots.FieldLabel && React.isValidElement(slots.FieldLabel)) {
|
|
76
|
+
slots.FieldLabel = React.cloneElement(slots.FieldLabel as React.ReactElement<LabelProps>, {
|
|
77
|
+
htmlFor: (slots.FieldLabel.props as LabelProps).htmlFor ?? controlId,
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
13
81
|
const componentProps = {
|
|
14
|
-
className: clsx("coop-form-item ", className),
|
|
15
82
|
...props,
|
|
83
|
+
"aria-disabled": controlDisabled || undefined,
|
|
84
|
+
className: clsx("coop-field", isInlineControl && "coop-field-inline", className),
|
|
85
|
+
"data-boxed": boxed || undefined,
|
|
86
|
+
"data-error": error || undefined,
|
|
87
|
+
"data-hide-error": hideErrorBar || undefined,
|
|
16
88
|
}
|
|
17
|
-
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div {...componentProps}>
|
|
92
|
+
{!isInlineControl && hasFieldMarkers && (
|
|
93
|
+
<div className="coop-field-markers">
|
|
94
|
+
{slots.FieldLabel}
|
|
95
|
+
{slots.FieldHint}
|
|
96
|
+
{slots.FieldError}
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
{slots.Control && (
|
|
100
|
+
<div className="coop-field-control">
|
|
101
|
+
{slots.Control}
|
|
102
|
+
{isInlineControl && hasFieldMarkers && (
|
|
103
|
+
<div className="coop-field-markers">
|
|
104
|
+
{slots.FieldLabel}
|
|
105
|
+
{slots.FieldHint}
|
|
106
|
+
{slots.FieldError}
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
)}
|
|
111
|
+
{slots.Children}
|
|
112
|
+
</div>
|
|
113
|
+
)
|
|
18
114
|
}
|
|
19
115
|
|
|
116
|
+
interface ControlProps extends HTMLAttributes<HTMLDivElement> {
|
|
117
|
+
/** **(Optional)** Elements inside the form control. */
|
|
118
|
+
children?: string | React.ReactNode
|
|
119
|
+
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
120
|
+
className?: string
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export const FieldControl = ({ children, className, ...props }: ControlProps): JSX.Element => {
|
|
124
|
+
return (
|
|
125
|
+
<div className={clsx("coop-field-control ", className)} {...props}>
|
|
126
|
+
{children}
|
|
127
|
+
</div>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
FieldControl.displayName = "Field.Control"
|
|
132
|
+
|
|
133
|
+
const FieldLabel = (props: LabelProps) => <BaseLabel {...props} />
|
|
134
|
+
|
|
135
|
+
FieldLabel.displayName = "Field.Label"
|
|
136
|
+
|
|
137
|
+
const FieldHint = (props: HintProps) => <BaseHint {...props} />
|
|
138
|
+
|
|
139
|
+
FieldHint.displayName = "Field.Hint"
|
|
140
|
+
|
|
141
|
+
const FieldError = (props: ErrorProps) => <BaseError {...props} />
|
|
142
|
+
|
|
143
|
+
FieldError.displayName = "Field.Error"
|
|
144
|
+
|
|
145
|
+
export const Field = Object.assign(Root, {
|
|
146
|
+
Control: FieldControl,
|
|
147
|
+
Error: FieldError,
|
|
148
|
+
Hint: FieldHint,
|
|
149
|
+
Label: FieldLabel,
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// Field.Label = FieldLabel
|
|
153
|
+
// Field.Hint = FieldHint
|
|
154
|
+
// Field.Error = FieldError
|
|
155
|
+
|
|
20
156
|
export default Field
|
|
@@ -2,14 +2,14 @@ import type { HTMLAttributes, JSX, ReactNode } from "react"
|
|
|
2
2
|
|
|
3
3
|
import clsx from "clsx"
|
|
4
4
|
|
|
5
|
-
export interface
|
|
6
|
-
/** **(Optional)**
|
|
5
|
+
export interface ErrorProps extends HTMLAttributes<HTMLSpanElement> {
|
|
6
|
+
/** **(Optional)** Error text for the form element. Accepts any valid JSX or string. */
|
|
7
7
|
children?: string | ReactNode
|
|
8
8
|
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
9
9
|
className?: string
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export const
|
|
12
|
+
export const Error = ({ children, className, ...props }: ErrorProps): JSX.Element => {
|
|
13
13
|
const componentProps = {
|
|
14
14
|
className: clsx("coop-field-error ", className),
|
|
15
15
|
...props,
|
|
@@ -17,4 +17,4 @@ export const FieldError = ({ children, className, ...props }: FieldErrorProps):
|
|
|
17
17
|
return <span {...componentProps}>{children}</span>
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export default
|
|
20
|
+
export default Error
|
|
@@ -2,18 +2,14 @@ import type { HTMLAttributes, JSX, ReactNode } from "react"
|
|
|
2
2
|
|
|
3
3
|
import clsx from "clsx"
|
|
4
4
|
|
|
5
|
-
export interface
|
|
6
|
-
/**
|
|
7
|
-
children
|
|
5
|
+
export interface HintProps extends HTMLAttributes<HTMLParagraphElement> {
|
|
6
|
+
/** **(Optional)** Hint text for the form element. Accepts any valid JSX or string. */
|
|
7
|
+
children?: string | ReactNode
|
|
8
8
|
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
9
9
|
className?: string
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export const
|
|
13
|
-
children,
|
|
14
|
-
className,
|
|
15
|
-
...props
|
|
16
|
-
}: FieldHintProps): JSX.Element | null => {
|
|
12
|
+
export const Hint = ({ children, className, ...props }: HintProps): JSX.Element | null => {
|
|
17
13
|
const componentProps = {
|
|
18
14
|
className: clsx("coop-field-hint ", className),
|
|
19
15
|
...props,
|
|
@@ -22,4 +18,4 @@ export const FieldHint = ({
|
|
|
22
18
|
return children ? <div {...componentProps}>{children}</div> : null
|
|
23
19
|
}
|
|
24
20
|
|
|
25
|
-
export default
|
|
21
|
+
export default Hint
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import clsx from "clsx"
|
|
2
|
+
import { LabelHTMLAttributes } from "react"
|
|
3
|
+
|
|
4
|
+
export interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {
|
|
5
|
+
/** **(Optional)** Main label for the form element. Accepts any valid JSX or string. */
|
|
6
|
+
children?: string | React.ReactNode
|
|
7
|
+
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
8
|
+
className?: string
|
|
9
|
+
/** **(Optional)** Specify whether the label is visible for humans or only for screen readers. */
|
|
10
|
+
isVisible?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const Label = ({ children, className, isVisible = true, ...props }: LabelProps) => {
|
|
14
|
+
return (
|
|
15
|
+
<label {...props} className={clsx("coop-field-label", !isVisible && "sr-only", className)}>
|
|
16
|
+
<span>{children}</span>
|
|
17
|
+
</label>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default Label
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import clsx from "clsx"
|
|
2
|
+
import { HTMLAttributes } from "react"
|
|
3
|
+
|
|
4
|
+
export interface LegendProps extends HTMLAttributes<HTMLLegendElement> {
|
|
5
|
+
/** **(Optional)** Main legend for the fieldset. Accepts any valid JSX or string. */
|
|
6
|
+
children?: string | React.ReactNode
|
|
7
|
+
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
8
|
+
className?: string
|
|
9
|
+
/** **(Optional)** Specify whether the legend should be visible to humans or screen readers. */
|
|
10
|
+
isVisible?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const Legend = ({ children, className, isVisible = true, ...props }: LegendProps) => {
|
|
14
|
+
const componentProps = {
|
|
15
|
+
"aria-hidden": true,
|
|
16
|
+
className: clsx("coop-field-label", !isVisible && "sr-only", className),
|
|
17
|
+
...props,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<legend className="sr-only">{children}</legend>
|
|
23
|
+
{isVisible && <div {...componentProps}>{children}</div>}
|
|
24
|
+
</>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default Legend
|