@coopdigital/react 0.33.1 → 0.35.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 +32 -0
- package/dist/components/{Input/Input.js → Checkbox/Checkbox.js} +6 -6
- package/dist/components/Checkbox/CheckboxGroup.d.ts +26 -0
- package/dist/components/Checkbox/CheckboxGroup.js +19 -0
- package/dist/components/Checkbox/index.d.ts +4 -0
- package/dist/components/Field/Field.js +1 -1
- package/dist/components/FieldHint/FieldHint.js +1 -1
- package/dist/components/SearchBox/SearchBox.d.ts +3 -3
- package/dist/components/SearchBox/SearchBox.js +2 -2
- package/dist/components/TextInput/TextInput.d.ts +38 -0
- package/dist/components/TextInput/TextInput.js +26 -0
- package/dist/components/TextInput/index.d.ts +4 -0
- package/dist/components/TextInput/index.js +5 -0
- package/dist/components/Textarea/Textarea.d.ts +47 -0
- package/dist/components/Textarea/Textarea.js +41 -0
- package/dist/components/Textarea/index.d.ts +4 -0
- package/dist/hooks/useDebounce.d.ts +1 -0
- package/dist/hooks/useDebounce.js +16 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +10 -10
- package/src/components/Checkbox/Checkbox.tsx +87 -0
- package/src/components/Checkbox/CheckboxGroup.tsx +63 -0
- package/src/components/Checkbox/index.ts +5 -0
- package/src/components/Field/Field.tsx +1 -1
- package/src/components/FieldHint/FieldHint.tsx +1 -1
- package/src/components/SearchBox/SearchBox.tsx +7 -7
- package/src/components/{Input/Input.tsx → TextInput/TextInput.tsx} +28 -16
- package/src/components/TextInput/index.ts +5 -0
- package/src/components/Textarea/Textarea.tsx +144 -0
- package/src/components/Textarea/index.ts +5 -0
- package/src/hooks/useDebounce.ts +16 -0
- package/src/index.ts +4 -1
- package/src/types/index.ts +1 -1
- package/dist/components/Input/Input.d.ts +0 -31
- package/dist/components/Input/index.d.ts +0 -4
- package/dist/components/Input/index.js +0 -5
- package/src/components/Input/index.ts +0 -5
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { InputHTMLAttributes, JSX } from "react";
|
|
2
|
+
import { FormFieldError, StandardSizes } from "../../../src/types";
|
|
3
|
+
export interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
|
|
4
|
+
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
5
|
+
className?: string;
|
|
6
|
+
/** **(Optional)** Specify the Checkbox error state.
|
|
7
|
+
*
|
|
8
|
+
* 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.
|
|
9
|
+
*/
|
|
10
|
+
error?: FormFieldError;
|
|
11
|
+
/** **(Optional)** Specify the Checkbox hint.
|
|
12
|
+
*
|
|
13
|
+
* This text is rendered under the label to provide further guidance for users.
|
|
14
|
+
*/
|
|
15
|
+
hint?: string;
|
|
16
|
+
/** **(Optional)** Specify the Checkbox id. Will be auto-generated if not set. */
|
|
17
|
+
id?: string;
|
|
18
|
+
/** **(Optional)** Specify the Checkbox label.
|
|
19
|
+
*
|
|
20
|
+
* This property is optional in case you need to render your own label, but all form elements *must* provide a label. */
|
|
21
|
+
label: string;
|
|
22
|
+
/** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
|
|
23
|
+
labelVisible?: boolean;
|
|
24
|
+
/** Specify the Checkbox name. */
|
|
25
|
+
name: string;
|
|
26
|
+
/** **(Optional)** Specify the Checkbox placeholder text. Do not use in place of a form label. */
|
|
27
|
+
placeholder?: string;
|
|
28
|
+
/** **(Optional)** Specify the Checkbox size. */
|
|
29
|
+
size?: StandardSizes;
|
|
30
|
+
}
|
|
31
|
+
export declare const Checkbox: ({ "aria-placeholder": ariaPlaceholder, className, error, hint, id, label, labelVisible, name, placeholder, size, ...props }: CheckboxProps) => JSX.Element;
|
|
32
|
+
export default Checkbox;
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import { jsxs,
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { clsx } from '../../node_modules/clsx/dist/clsx.js';
|
|
3
3
|
import { useId } from 'react';
|
|
4
4
|
import { FieldError } from '../FieldError/FieldError.js';
|
|
5
5
|
import { FieldHint } from '../FieldHint/FieldHint.js';
|
|
6
6
|
import { FieldLabel } from '../FieldLabel/FieldLabel.js';
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const Checkbox = ({ "aria-placeholder": ariaPlaceholder, className, error = false, hint, id, label, labelVisible = true, name, placeholder, size = "md", ...props }) => {
|
|
9
9
|
var _a;
|
|
10
10
|
const internalId = useId();
|
|
11
11
|
id = id !== null && id !== void 0 ? id : internalId;
|
|
12
12
|
const componentProps = {
|
|
13
13
|
"aria-placeholder": (_a = placeholder !== null && placeholder !== void 0 ? placeholder : ariaPlaceholder) !== null && _a !== void 0 ? _a : undefined,
|
|
14
|
-
className: clsx("coop-
|
|
14
|
+
className: clsx("coop-checkbox", className),
|
|
15
15
|
"data-error": error ? "" : undefined,
|
|
16
16
|
"data-size": size.length && size !== "md" ? size : undefined,
|
|
17
17
|
id,
|
|
18
18
|
name,
|
|
19
19
|
placeholder,
|
|
20
|
-
type,
|
|
20
|
+
type: "checkbox",
|
|
21
21
|
...props,
|
|
22
22
|
};
|
|
23
|
-
return (jsxs(
|
|
23
|
+
return (jsxs("div", { className: "coop-form-item", children: [jsxs("div", { className: "coop-checkbox-wrapper", children: [jsx("input", { ...componentProps }), label && (jsx(FieldLabel, { htmlFor: id, isVisible: labelVisible, children: label })), hint && jsx(FieldHint, { children: hint })] }), typeof error === "object" && (error === null || error === void 0 ? void 0 : error.message) && jsx(FieldError, { children: error.message })] }));
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
export {
|
|
26
|
+
export { Checkbox, Checkbox as default };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { FieldsetHTMLAttributes, JSX } from "react";
|
|
2
|
+
import { FormFieldError, StandardSizes } from "../../../src/types";
|
|
3
|
+
export interface CheckboxGroupProps extends FieldsetHTMLAttributes<HTMLFieldSetElement> {
|
|
4
|
+
/** **(Optional)** Main content inside the component. It can be any valid JSX or string. */
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
7
|
+
className?: string;
|
|
8
|
+
/** **(Optional)** Specify the CheckboxGroup error state.
|
|
9
|
+
*
|
|
10
|
+
* 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.
|
|
11
|
+
*/
|
|
12
|
+
error?: FormFieldError;
|
|
13
|
+
/** **(Optional)** Specify the CheckboxGroup hint.
|
|
14
|
+
*
|
|
15
|
+
* This text is rendered under the label to provide further guidance for users.
|
|
16
|
+
*/
|
|
17
|
+
hint?: string;
|
|
18
|
+
/** **(Optional)** Specify the label for the CheckboxGroup. This will be rendered as a fieldset legend. */
|
|
19
|
+
label?: string;
|
|
20
|
+
/** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
|
|
21
|
+
labelVisible?: boolean;
|
|
22
|
+
/** **(Optional)** Specify the CheckboxGroup size. */
|
|
23
|
+
size?: StandardSizes;
|
|
24
|
+
}
|
|
25
|
+
export declare const CheckboxGroup: ({ children, className, error, hint, label, labelVisible, size, ...props }: CheckboxGroupProps) => JSX.Element;
|
|
26
|
+
export default CheckboxGroup;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { clsx } from '../../node_modules/clsx/dist/clsx.js';
|
|
3
|
+
import { FieldError } from '../FieldError/FieldError.js';
|
|
4
|
+
import { FieldHint } from '../FieldHint/FieldHint.js';
|
|
5
|
+
|
|
6
|
+
const CheckboxGroup = ({ children, className, error = false, hint, label, labelVisible = true, size = "md", ...props }) => {
|
|
7
|
+
const componentProps = {
|
|
8
|
+
className: clsx("coop-fieldset", "coop-checkbox-group", className),
|
|
9
|
+
"data-error": error ? "" : undefined,
|
|
10
|
+
"data-size": size.length && size !== "md" ? size : undefined,
|
|
11
|
+
...props,
|
|
12
|
+
};
|
|
13
|
+
const legendProps = {
|
|
14
|
+
className: clsx("coop-field-label", !labelVisible && "sr-only"),
|
|
15
|
+
};
|
|
16
|
+
return (jsxs("fieldset", { ...componentProps, children: [label && jsx("legend", { ...legendProps, children: label }), hint && jsx(FieldHint, { children: hint }), typeof error === "object" && (error === null || error === void 0 ? void 0 : error.message) && jsx(FieldError, { children: error.message }), children] }));
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export { CheckboxGroup, CheckboxGroup as default };
|
|
@@ -3,7 +3,7 @@ import { clsx } from '../../node_modules/clsx/dist/clsx.js';
|
|
|
3
3
|
|
|
4
4
|
const Field = ({ children, className, ...props }) => {
|
|
5
5
|
const componentProps = {
|
|
6
|
-
className: clsx("coop-
|
|
6
|
+
className: clsx("coop-form-item ", className),
|
|
7
7
|
...props,
|
|
8
8
|
};
|
|
9
9
|
return jsx("div", { ...componentProps, children: children });
|
|
@@ -6,7 +6,7 @@ const FieldHint = ({ children, className, ...props }) => {
|
|
|
6
6
|
className: clsx("coop-field-hint ", className),
|
|
7
7
|
...props,
|
|
8
8
|
};
|
|
9
|
-
return children ? jsx("
|
|
9
|
+
return children ? jsx("div", { ...componentProps, children: children }) : null;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export { FieldHint, FieldHint as default };
|
|
@@ -11,17 +11,17 @@ export interface SearchBoxProps extends Omit<InputHTMLAttributes<HTMLInputElemen
|
|
|
11
11
|
};
|
|
12
12
|
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
13
13
|
className?: string;
|
|
14
|
-
/** **(Optional)** Specify the
|
|
14
|
+
/** **(Optional)** Specify the TextInput id. Will be auto-generated if not set. */
|
|
15
15
|
id?: string;
|
|
16
16
|
/** Specify the label displayed above the search field. Hidden by default, but visible to screen readers. */
|
|
17
17
|
label: string;
|
|
18
18
|
/** **(Optional)** Specify whether the label should be visible to humans or screenreaders. */
|
|
19
19
|
labelVisible?: boolean;
|
|
20
|
-
/** **(Optional)** Specify the
|
|
20
|
+
/** **(Optional)** Specify the TextInput name, also used as the URL search parameter. Defaults to `query`. */
|
|
21
21
|
name?: string;
|
|
22
22
|
/** **(Optional)** Callback to run when the form is submitted. If this is an async function, it will be awaited and the SearchBox will be in a pending state until the promise is resolved. */
|
|
23
23
|
onSubmit?: React.FormEventHandler<HTMLElement> | undefined;
|
|
24
|
-
/** **(Optional)** Specify the
|
|
24
|
+
/** **(Optional)** Specify the TextInput placeholder text Do not use in place of a form label. */
|
|
25
25
|
placeholder?: string;
|
|
26
26
|
/** **(Optional)** Specify the SearchBox size. */
|
|
27
27
|
size?: StandardSizes;
|
|
@@ -6,7 +6,7 @@ import React, { useState, useId, useCallback } from 'react';
|
|
|
6
6
|
import { Button } from '../Button/Button.js';
|
|
7
7
|
import { FieldLabel } from '../FieldLabel/FieldLabel.js';
|
|
8
8
|
import { SearchIcon } from '../Icon/SearchIcon.js';
|
|
9
|
-
import {
|
|
9
|
+
import { TextInput } from '../TextInput/TextInput.js';
|
|
10
10
|
|
|
11
11
|
const defaultButtonProps = {
|
|
12
12
|
label: React.createElement(SearchIcon, { alt: "Search", stroke: "currentColor", strokeWidth: 2 }),
|
|
@@ -54,7 +54,7 @@ const SearchBox = ({ action, "aria-placeholder": ariaPlaceholder, autoCapitalize
|
|
|
54
54
|
type: "search",
|
|
55
55
|
...props,
|
|
56
56
|
};
|
|
57
|
-
return (jsxs("form", { ...formProps, children: [label && (jsx(FieldLabel, { htmlFor: id, isVisible: labelVisible, children: label })), jsxs("div", { className: "coop-search-box--inner", children: [jsx(
|
|
57
|
+
return (jsxs("form", { ...formProps, children: [label && (jsx(FieldLabel, { htmlFor: id, isVisible: labelVisible, children: label })), jsxs("div", { className: "coop-search-box--inner", children: [jsx(TextInput, { ...inputProps }), jsx(Button, { ...buttonProps, children: button.label })] })] }));
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
export { SearchBox, SearchBox as default };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { InputHTMLAttributes, JSX } from "react";
|
|
2
|
+
import { FormFieldError, StandardSizes } from "../../../src/types";
|
|
3
|
+
export interface TextInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
|
|
4
|
+
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
5
|
+
className?: string;
|
|
6
|
+
/** **(Optional)** Specify the TextInput error state.
|
|
7
|
+
*
|
|
8
|
+
* 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.
|
|
9
|
+
*/
|
|
10
|
+
error?: FormFieldError;
|
|
11
|
+
/** **(Optional)** Specify the TextInput hint.
|
|
12
|
+
*
|
|
13
|
+
* This text is rendered under the label to provide further guidance for users.
|
|
14
|
+
*/
|
|
15
|
+
hint?: string;
|
|
16
|
+
/** **(Optional)** Specify the TextInput id. Will be auto-generated if not set. */
|
|
17
|
+
id?: string;
|
|
18
|
+
/** **(Optional)** Specify the TextInput label.
|
|
19
|
+
*
|
|
20
|
+
* This property is optional in case you need to render your own label, but all form elements *must* provide a label. */
|
|
21
|
+
label?: string;
|
|
22
|
+
/** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
|
|
23
|
+
labelVisible?: boolean;
|
|
24
|
+
/** Specify the TextInput name. */
|
|
25
|
+
name: string;
|
|
26
|
+
/** **(Optional)** Specify the TextInput placeholder text. Do not use in place of a form label. */
|
|
27
|
+
placeholder?: string;
|
|
28
|
+
/** **(Optional)** Specify the prefix. */
|
|
29
|
+
prefix?: string;
|
|
30
|
+
/** **(Optional)** Specify the TextInput size. */
|
|
31
|
+
size?: StandardSizes;
|
|
32
|
+
/** **(Optional)** Specify the suffix. */
|
|
33
|
+
suffix?: string;
|
|
34
|
+
/** **(Optional)** Specify the TextInput type. */
|
|
35
|
+
type?: "text" | "email" | "number" | "password" | "search" | "tel" | "url";
|
|
36
|
+
}
|
|
37
|
+
export declare const TextInput: ({ "aria-placeholder": ariaPlaceholder, className, error, hint, id, label, labelVisible, name, placeholder, prefix, size, suffix, type, ...props }: TextInputProps) => JSX.Element;
|
|
38
|
+
export default TextInput;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { clsx } from '../../node_modules/clsx/dist/clsx.js';
|
|
3
|
+
import { useId } from 'react';
|
|
4
|
+
import { FieldError } from '../FieldError/FieldError.js';
|
|
5
|
+
import { FieldHint } from '../FieldHint/FieldHint.js';
|
|
6
|
+
import { FieldLabel } from '../FieldLabel/FieldLabel.js';
|
|
7
|
+
|
|
8
|
+
const TextInput = ({ "aria-placeholder": ariaPlaceholder, className, error = false, hint, id, label, labelVisible = true, name, placeholder, prefix, size = "md", suffix, type = "text", ...props }) => {
|
|
9
|
+
var _a;
|
|
10
|
+
const internalId = useId();
|
|
11
|
+
id = id !== null && id !== void 0 ? id : internalId;
|
|
12
|
+
const componentProps = {
|
|
13
|
+
"aria-placeholder": (_a = placeholder !== null && placeholder !== void 0 ? placeholder : ariaPlaceholder) !== null && _a !== void 0 ? _a : undefined,
|
|
14
|
+
className: clsx("coop-text-input", className),
|
|
15
|
+
"data-error": error ? "" : undefined,
|
|
16
|
+
"data-size": size.length && size !== "md" ? size : undefined,
|
|
17
|
+
id,
|
|
18
|
+
name,
|
|
19
|
+
placeholder,
|
|
20
|
+
type,
|
|
21
|
+
...props,
|
|
22
|
+
};
|
|
23
|
+
return (jsxs("div", { className: "coop-form-item", children: [label && (jsx(FieldLabel, { htmlFor: id, isVisible: labelVisible, children: label })), hint && jsx(FieldHint, { children: hint }), typeof error === "object" && (error === null || error === void 0 ? void 0 : error.message) && jsx(FieldError, { children: error.message }), jsxs("div", { className: "coop-text-input-wrapper", children: [prefix && jsx("span", { className: "coop-text-input--prefix", children: prefix }), jsx("input", { ...componentProps }), suffix && jsx("span", { className: "coop-text-input--suffix", children: suffix })] })] }));
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { TextInput, TextInput as default };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { JSX, TextareaHTMLAttributes } from "react";
|
|
2
|
+
import { FormFieldError, StandardSizes } from "../../types";
|
|
3
|
+
export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
4
|
+
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
5
|
+
className?: string;
|
|
6
|
+
/** Specify the number of columns (characters per row) in the Textarea. Defaults to `30`. */
|
|
7
|
+
cols?: number;
|
|
8
|
+
/** **(Optional)** Specify whether to show the character counter underneath the Textarea. */
|
|
9
|
+
counter?: boolean;
|
|
10
|
+
/** **(Optional)** Specify whether the Textarea should allow more characters than its `maxLength` value.
|
|
11
|
+
*
|
|
12
|
+
* Defaults to `false`, meaning users can enter more characters than the maximum but will be warned if they go over the limit. When set to `true`, users will be blocked from typing once they hit the character limit. This can be an accessiblity anti-pattern, so only use this option when absolutely necessary.
|
|
13
|
+
*
|
|
14
|
+
* Remember it is still your responsibility to handle validation on submission, this is simply a hint for the user.
|
|
15
|
+
*/
|
|
16
|
+
cutoff?: boolean;
|
|
17
|
+
/** **(Optional)** Specify the Textarea error state.
|
|
18
|
+
*
|
|
19
|
+
* 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.
|
|
20
|
+
*/
|
|
21
|
+
error?: FormFieldError;
|
|
22
|
+
/** **(Optional)** Specify the Textarea hint.
|
|
23
|
+
*
|
|
24
|
+
* This text is rendered under the label to provide further guidance for users.
|
|
25
|
+
*/
|
|
26
|
+
hint?: string;
|
|
27
|
+
/** **(Optional)** Specify the Textarea id. Will be auto-generated if not set. */
|
|
28
|
+
id?: string;
|
|
29
|
+
/** **(Optional)** Specify the Textarea label.
|
|
30
|
+
*
|
|
31
|
+
* This property is optional in case you need to render your own label, but all form elements *must* provide a label. */
|
|
32
|
+
label?: string;
|
|
33
|
+
/** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
|
|
34
|
+
labelVisible?: boolean;
|
|
35
|
+
/** **(Optional)** Specify the Textarea maxLength. This is the maximum number of characters users can enter. */
|
|
36
|
+
maxLength?: number;
|
|
37
|
+
/** Specify the Textarea name. */
|
|
38
|
+
name: string;
|
|
39
|
+
/** **(Optional)** Specify the Textarea placeholder text. Do not use in place of a form label. */
|
|
40
|
+
placeholder?: string;
|
|
41
|
+
/** Specify the number of rows (lines of text) in the Textarea. Defaults to `4`. */
|
|
42
|
+
rows?: number;
|
|
43
|
+
/** **(Optional)** Specify the Textarea size. */
|
|
44
|
+
size?: StandardSizes;
|
|
45
|
+
}
|
|
46
|
+
export declare const Textarea: ({ "aria-placeholder": ariaPlaceholder, className, cols, counter, cutoff, error, hint, id, label, labelVisible, maxLength, name, onChange: userOnChange, placeholder, rows, size, ...props }: TextareaProps) => JSX.Element;
|
|
47
|
+
export default Textarea;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { clsx } from '../../node_modules/clsx/dist/clsx.js';
|
|
3
|
+
import { useId, useState } from 'react';
|
|
4
|
+
import { useDebounce } from '../../hooks/useDebounce.js';
|
|
5
|
+
import { FieldError } from '../FieldError/FieldError.js';
|
|
6
|
+
import { FieldHint } from '../FieldHint/FieldHint.js';
|
|
7
|
+
import { FieldLabel } from '../FieldLabel/FieldLabel.js';
|
|
8
|
+
|
|
9
|
+
const DEBOUNCE_DELAY = 750;
|
|
10
|
+
const charCountMessage = (remaining) => {
|
|
11
|
+
return `You have ${Math.abs(remaining).toLocaleString()} ${Math.abs(remaining) === 1 ? "character" : "characters"} ${remaining < 0 ? "too many" : "remaining"}`;
|
|
12
|
+
};
|
|
13
|
+
const Textarea = ({ "aria-placeholder": ariaPlaceholder, className, cols = 30, counter = false, cutoff = false, error = false, hint, id, label, labelVisible = true, maxLength, name, onChange: userOnChange = undefined, placeholder, rows = 4, size = "md", ...props }) => {
|
|
14
|
+
var _a;
|
|
15
|
+
const internalId = useId();
|
|
16
|
+
id = id !== null && id !== void 0 ? id : internalId;
|
|
17
|
+
const componentProps = {
|
|
18
|
+
"aria-placeholder": (_a = placeholder !== null && placeholder !== void 0 ? placeholder : ariaPlaceholder) !== null && _a !== void 0 ? _a : undefined,
|
|
19
|
+
className: clsx("coop-textarea", className),
|
|
20
|
+
cols,
|
|
21
|
+
"data-error": error ? "" : undefined,
|
|
22
|
+
"data-size": size.length && size !== "md" ? size : undefined,
|
|
23
|
+
id,
|
|
24
|
+
maxLength: cutoff ? maxLength : undefined,
|
|
25
|
+
name,
|
|
26
|
+
placeholder,
|
|
27
|
+
rows,
|
|
28
|
+
...props,
|
|
29
|
+
};
|
|
30
|
+
const [remaining, setRemaining] = useState(maxLength);
|
|
31
|
+
const debouncedRemaining = useDebounce(remaining, DEBOUNCE_DELAY);
|
|
32
|
+
const handleChange = (e) => {
|
|
33
|
+
maxLength && e.target && setRemaining(maxLength - e.target.value.length);
|
|
34
|
+
};
|
|
35
|
+
return (jsxs("div", { className: "coop-form-item", children: [label && (jsx(FieldLabel, { htmlFor: id, isVisible: labelVisible, children: label })), hint && jsx(FieldHint, { children: hint }), typeof error === "object" && (error === null || error === void 0 ? void 0 : error.message) && jsx(FieldError, { children: error.message }), jsx("textarea", { ...componentProps, onChange: (e) => {
|
|
36
|
+
userOnChange === null || userOnChange === void 0 ? void 0 : userOnChange(e);
|
|
37
|
+
handleChange(e);
|
|
38
|
+
} }), counter && maxLength && remaining != null && debouncedRemaining != null && (jsxs(Fragment, { children: [jsx("small", { "aria-hidden": "true", className: "coop-textarea-counter", ...(remaining < 0 && { "data-error": "" }), children: charCountMessage(remaining) }), jsx("span", { "aria-live": "polite", className: "sr-only", children: charCountMessage(debouncedRemaining) })] }))] }));
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export { Textarea, Textarea as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useDebounce<T>(value: T, delay: number): T;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
function useDebounce(value, delay) {
|
|
4
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const handler = setTimeout(() => {
|
|
7
|
+
setDebouncedValue(value);
|
|
8
|
+
}, delay);
|
|
9
|
+
return () => {
|
|
10
|
+
clearTimeout(handler);
|
|
11
|
+
};
|
|
12
|
+
}, [value, delay]);
|
|
13
|
+
return debouncedValue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { useDebounce };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export * from "./components/AlertBanner";
|
|
|
2
2
|
export * from "./components/Author";
|
|
3
3
|
export * from "./components/Button";
|
|
4
4
|
export * from "./components/Card";
|
|
5
|
+
export * from "./components/Checkbox";
|
|
6
|
+
export * from "./components/Checkbox/CheckboxGroup";
|
|
5
7
|
export * from "./components/Expandable";
|
|
6
8
|
export * from "./components/Field";
|
|
7
9
|
export * from "./components/FieldError";
|
|
@@ -9,7 +11,6 @@ export * from "./components/FieldHint";
|
|
|
9
11
|
export * from "./components/FieldLabel";
|
|
10
12
|
export * from "./components/Flourish";
|
|
11
13
|
export * from "./components/Image";
|
|
12
|
-
export * from "./components/Input";
|
|
13
14
|
export * from "./components/Pill";
|
|
14
15
|
export * from "./components/RootSVG";
|
|
15
16
|
export * from "./components/SearchBox";
|
|
@@ -17,3 +18,5 @@ export * from "./components/Signpost";
|
|
|
17
18
|
export * from "./components/SkipNav";
|
|
18
19
|
export * from "./components/Squircle";
|
|
19
20
|
export * from "./components/Tag";
|
|
21
|
+
export * from "./components/Textarea";
|
|
22
|
+
export * from "./components/TextInput";
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,8 @@ export { AlertBanner } from './components/AlertBanner/AlertBanner.js';
|
|
|
2
2
|
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
|
+
export { Checkbox } from './components/Checkbox/Checkbox.js';
|
|
6
|
+
export { CheckboxGroup } from './components/Checkbox/CheckboxGroup.js';
|
|
5
7
|
export { Expandable } from './components/Expandable/Expandable.js';
|
|
6
8
|
export { Field } from './components/Field/Field.js';
|
|
7
9
|
export { FieldError } from './components/FieldError/FieldError.js';
|
|
@@ -9,7 +11,6 @@ export { FieldHint } from './components/FieldHint/FieldHint.js';
|
|
|
9
11
|
export { FieldLabel } from './components/FieldLabel/FieldLabel.js';
|
|
10
12
|
export { Flourish } from './components/Flourish/Flourish.js';
|
|
11
13
|
export { Image } from './components/Image/Image.js';
|
|
12
|
-
export { Input } from './components/Input/Input.js';
|
|
13
14
|
export { Pill } from './components/Pill/Pill.js';
|
|
14
15
|
export { RootSVG } from './components/RootSVG/RootSVG.js';
|
|
15
16
|
export { SearchBox } from './components/SearchBox/SearchBox.js';
|
|
@@ -17,3 +18,5 @@ export { Signpost } from './components/Signpost/Signpost.js';
|
|
|
17
18
|
export { SkipNav } from './components/SkipNav/SkipNav.js';
|
|
18
19
|
export { Squircle } from './components/Squircle/Squircle.js';
|
|
19
20
|
export { Tag } from './components/Tag/Tag.js';
|
|
21
|
+
export { Textarea } from './components/Textarea/Textarea.js';
|
|
22
|
+
export { TextInput } from './components/TextInput/TextInput.js';
|
package/dist/types/index.d.ts
CHANGED
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.35.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -57,19 +57,19 @@
|
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@axe-core/playwright": "^4.10.2",
|
|
59
59
|
"@playwright/test": "^1.55.0",
|
|
60
|
-
"@storybook/addon-a11y": "^9.1.
|
|
61
|
-
"@storybook/addon-docs": "^9.1.
|
|
62
|
-
"@storybook/addon-onboarding": "^9.1.
|
|
63
|
-
"@storybook/react-vite": "^9.1.
|
|
60
|
+
"@storybook/addon-a11y": "^9.1.5",
|
|
61
|
+
"@storybook/addon-docs": "^9.1.5",
|
|
62
|
+
"@storybook/addon-onboarding": "^9.1.5",
|
|
63
|
+
"@storybook/react-vite": "^9.1.5",
|
|
64
64
|
"@testing-library/jest-dom": "^6.8.0",
|
|
65
65
|
"@testing-library/react": "^16.3.0",
|
|
66
|
-
"@types/react": "^19.1.
|
|
66
|
+
"@types/react": "^19.1.13",
|
|
67
67
|
"@types/react-dom": "^19.1.9",
|
|
68
68
|
"clsx": "^2.1.1",
|
|
69
69
|
"react": "^19.1.1",
|
|
70
70
|
"react-dom": "^19.1.1",
|
|
71
|
-
"serve": "^14.2.
|
|
72
|
-
"storybook": "^9.1.
|
|
71
|
+
"serve": "^14.2.5",
|
|
72
|
+
"storybook": "^9.1.5"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
75
|
"clsx": "^2.1.1",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"storybook": "$storybook"
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
|
-
"@coopdigital/styles": "^0.
|
|
83
|
+
"@coopdigital/styles": "^0.31.0"
|
|
84
84
|
},
|
|
85
|
-
"gitHead": "
|
|
85
|
+
"gitHead": "eedf8c0062e77a007636c54ec5776aa6cc670cee"
|
|
86
86
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { InputHTMLAttributes, JSX } from "react"
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx"
|
|
4
|
+
import { useId } from "react"
|
|
5
|
+
|
|
6
|
+
import { FormFieldError, StandardSizes } from "../../../src/types"
|
|
7
|
+
import { FieldError } from "../FieldError"
|
|
8
|
+
import { FieldHint } from "../FieldHint"
|
|
9
|
+
import { FieldLabel } from "../FieldLabel"
|
|
10
|
+
|
|
11
|
+
export interface CheckboxProps
|
|
12
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
|
|
13
|
+
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
14
|
+
className?: string
|
|
15
|
+
/** **(Optional)** Specify the Checkbox error state.
|
|
16
|
+
*
|
|
17
|
+
* 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.
|
|
18
|
+
*/
|
|
19
|
+
error?: FormFieldError
|
|
20
|
+
/** **(Optional)** Specify the Checkbox hint.
|
|
21
|
+
*
|
|
22
|
+
* This text is rendered under the label to provide further guidance for users.
|
|
23
|
+
*/
|
|
24
|
+
hint?: string
|
|
25
|
+
/** **(Optional)** Specify the Checkbox id. Will be auto-generated if not set. */
|
|
26
|
+
id?: string
|
|
27
|
+
/** **(Optional)** Specify the Checkbox label.
|
|
28
|
+
*
|
|
29
|
+
* This property is optional in case you need to render your own label, but all form elements *must* provide a label. */
|
|
30
|
+
label: string
|
|
31
|
+
/** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
|
|
32
|
+
labelVisible?: boolean
|
|
33
|
+
/** Specify the Checkbox name. */
|
|
34
|
+
name: string
|
|
35
|
+
/** **(Optional)** Specify the Checkbox placeholder text. Do not use in place of a form label. */
|
|
36
|
+
placeholder?: string
|
|
37
|
+
/** **(Optional)** Specify the Checkbox size. */
|
|
38
|
+
size?: StandardSizes
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const Checkbox = ({
|
|
42
|
+
"aria-placeholder": ariaPlaceholder,
|
|
43
|
+
className,
|
|
44
|
+
error = false,
|
|
45
|
+
hint,
|
|
46
|
+
id,
|
|
47
|
+
label,
|
|
48
|
+
labelVisible = true,
|
|
49
|
+
name,
|
|
50
|
+
placeholder,
|
|
51
|
+
size = "md",
|
|
52
|
+
...props
|
|
53
|
+
}: CheckboxProps): JSX.Element => {
|
|
54
|
+
const internalId = useId()
|
|
55
|
+
|
|
56
|
+
id = id ?? internalId
|
|
57
|
+
|
|
58
|
+
const componentProps = {
|
|
59
|
+
"aria-placeholder": placeholder ?? ariaPlaceholder ?? undefined,
|
|
60
|
+
className: clsx("coop-checkbox", className),
|
|
61
|
+
"data-error": error ? "" : undefined,
|
|
62
|
+
"data-size": size.length && size !== "md" ? size : undefined,
|
|
63
|
+
id,
|
|
64
|
+
name,
|
|
65
|
+
placeholder,
|
|
66
|
+
type: "checkbox",
|
|
67
|
+
...props,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div className="coop-form-item">
|
|
72
|
+
<div className="coop-checkbox-wrapper">
|
|
73
|
+
<input {...componentProps} />
|
|
74
|
+
|
|
75
|
+
{label && (
|
|
76
|
+
<FieldLabel htmlFor={id} isVisible={labelVisible}>
|
|
77
|
+
{label}
|
|
78
|
+
</FieldLabel>
|
|
79
|
+
)}
|
|
80
|
+
{hint && <FieldHint>{hint}</FieldHint>}
|
|
81
|
+
</div>
|
|
82
|
+
{typeof error === "object" && error?.message && <FieldError>{error.message}</FieldError>}
|
|
83
|
+
</div>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default Checkbox
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { FieldsetHTMLAttributes, JSX } from "react"
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx"
|
|
4
|
+
|
|
5
|
+
import { FormFieldError, StandardSizes } from "../../../src/types"
|
|
6
|
+
import { FieldError } from "../FieldError"
|
|
7
|
+
import { FieldHint } from "../FieldHint"
|
|
8
|
+
|
|
9
|
+
export interface CheckboxGroupProps extends FieldsetHTMLAttributes<HTMLFieldSetElement> {
|
|
10
|
+
/** **(Optional)** Main content inside the component. It can be any valid JSX or string. */
|
|
11
|
+
children?: React.ReactNode
|
|
12
|
+
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
13
|
+
className?: string
|
|
14
|
+
/** **(Optional)** Specify the CheckboxGroup error state.
|
|
15
|
+
*
|
|
16
|
+
* 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.
|
|
17
|
+
*/
|
|
18
|
+
error?: FormFieldError
|
|
19
|
+
/** **(Optional)** Specify the CheckboxGroup hint.
|
|
20
|
+
*
|
|
21
|
+
* This text is rendered under the label to provide further guidance for users.
|
|
22
|
+
*/
|
|
23
|
+
hint?: string
|
|
24
|
+
/** **(Optional)** Specify the label for the CheckboxGroup. This will be rendered as a fieldset legend. */
|
|
25
|
+
label?: string
|
|
26
|
+
/** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
|
|
27
|
+
labelVisible?: boolean
|
|
28
|
+
/** **(Optional)** Specify the CheckboxGroup size. */
|
|
29
|
+
size?: StandardSizes
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const CheckboxGroup = ({
|
|
33
|
+
children,
|
|
34
|
+
className,
|
|
35
|
+
error = false,
|
|
36
|
+
hint,
|
|
37
|
+
label,
|
|
38
|
+
labelVisible = true,
|
|
39
|
+
size = "md",
|
|
40
|
+
...props
|
|
41
|
+
}: CheckboxGroupProps): JSX.Element => {
|
|
42
|
+
const componentProps = {
|
|
43
|
+
className: clsx("coop-fieldset", "coop-checkbox-group", className),
|
|
44
|
+
"data-error": error ? "" : undefined,
|
|
45
|
+
"data-size": size.length && size !== "md" ? size : undefined,
|
|
46
|
+
...props,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const legendProps = {
|
|
50
|
+
className: clsx("coop-field-label", !labelVisible && "sr-only"),
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<fieldset {...componentProps}>
|
|
55
|
+
{label && <legend {...legendProps}>{label}</legend>}
|
|
56
|
+
{hint && <FieldHint>{hint}</FieldHint>}
|
|
57
|
+
{typeof error === "object" && error?.message && <FieldError>{error.message}</FieldError>}
|
|
58
|
+
{children}
|
|
59
|
+
</fieldset>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default CheckboxGroup
|
|
@@ -11,7 +11,7 @@ export interface FieldProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
11
11
|
|
|
12
12
|
export const Field = ({ children, className, ...props }: FieldProps): JSX.Element => {
|
|
13
13
|
const componentProps = {
|
|
14
|
-
className: clsx("coop-
|
|
14
|
+
className: clsx("coop-form-item ", className),
|
|
15
15
|
...props,
|
|
16
16
|
}
|
|
17
17
|
return <div {...componentProps}>{children}</div>
|
|
@@ -9,7 +9,7 @@ import { StandardSizes } from "../../../src/types"
|
|
|
9
9
|
import { Button, type ButtonProps } from "../Button"
|
|
10
10
|
import { FieldLabel } from "../FieldLabel"
|
|
11
11
|
import { SearchIcon } from "../Icon"
|
|
12
|
-
import
|
|
12
|
+
import TextInput, { TextInputProps } from "../TextInput"
|
|
13
13
|
|
|
14
14
|
export interface SearchBoxProps
|
|
15
15
|
extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
|
|
@@ -19,17 +19,17 @@ export interface SearchBoxProps
|
|
|
19
19
|
button?: Pick<ButtonProps, "className" | "loadingText"> & { label?: React.ReactNode }
|
|
20
20
|
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
21
21
|
className?: string
|
|
22
|
-
/** **(Optional)** Specify the
|
|
22
|
+
/** **(Optional)** Specify the TextInput id. Will be auto-generated if not set. */
|
|
23
23
|
id?: string
|
|
24
24
|
/** Specify the label displayed above the search field. Hidden by default, but visible to screen readers. */
|
|
25
25
|
label: string
|
|
26
26
|
/** **(Optional)** Specify whether the label should be visible to humans or screenreaders. */
|
|
27
27
|
labelVisible?: boolean
|
|
28
|
-
/** **(Optional)** Specify the
|
|
28
|
+
/** **(Optional)** Specify the TextInput name, also used as the URL search parameter. Defaults to `query`. */
|
|
29
29
|
name?: string
|
|
30
30
|
/** **(Optional)** Callback to run when the form is submitted. If this is an async function, it will be awaited and the SearchBox will be in a pending state until the promise is resolved. */
|
|
31
31
|
onSubmit?: React.FormEventHandler<HTMLElement> | undefined
|
|
32
|
-
/** **(Optional)** Specify the
|
|
32
|
+
/** **(Optional)** Specify the TextInput placeholder text Do not use in place of a form label. */
|
|
33
33
|
placeholder?: string
|
|
34
34
|
/** **(Optional)** Specify the SearchBox size. */
|
|
35
35
|
size?: StandardSizes
|
|
@@ -112,8 +112,8 @@ export const SearchBox = ({
|
|
|
112
112
|
id,
|
|
113
113
|
name,
|
|
114
114
|
placeholder,
|
|
115
|
-
size: size as keyof
|
|
116
|
-
type: "search" as keyof
|
|
115
|
+
size: size as keyof TextInputProps["size"],
|
|
116
|
+
type: "search" as keyof TextInputProps["type"],
|
|
117
117
|
...props,
|
|
118
118
|
}
|
|
119
119
|
|
|
@@ -125,7 +125,7 @@ export const SearchBox = ({
|
|
|
125
125
|
</FieldLabel>
|
|
126
126
|
)}
|
|
127
127
|
<div className="coop-search-box--inner">
|
|
128
|
-
<
|
|
128
|
+
<TextInput {...inputProps} />
|
|
129
129
|
<Button {...buttonProps}>{button.label}</Button>
|
|
130
130
|
</div>
|
|
131
131
|
</form>
|
|
@@ -8,35 +8,43 @@ import { FieldError } from "../FieldError"
|
|
|
8
8
|
import { FieldHint } from "../FieldHint"
|
|
9
9
|
import { FieldLabel } from "../FieldLabel"
|
|
10
10
|
|
|
11
|
-
export interface
|
|
11
|
+
export interface TextInputProps
|
|
12
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
|
|
12
13
|
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
13
14
|
className?: string
|
|
14
|
-
/** **(Optional)** Specify the
|
|
15
|
+
/** **(Optional)** Specify the TextInput error state.
|
|
16
|
+
*
|
|
17
|
+
* 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.
|
|
18
|
+
*/
|
|
15
19
|
error?: FormFieldError
|
|
16
|
-
/** **(Optional)** Specify the
|
|
20
|
+
/** **(Optional)** Specify the TextInput hint.
|
|
17
21
|
*
|
|
18
22
|
* This text is rendered under the label to provide further guidance for users.
|
|
19
23
|
*/
|
|
20
24
|
hint?: string
|
|
21
|
-
/** **(Optional)** Specify the
|
|
25
|
+
/** **(Optional)** Specify the TextInput id. Will be auto-generated if not set. */
|
|
22
26
|
id?: string
|
|
23
|
-
/** **(Optional)** Specify the
|
|
27
|
+
/** **(Optional)** Specify the TextInput label.
|
|
24
28
|
*
|
|
25
29
|
* This property is optional in case you need to render your own label, but all form elements *must* provide a label. */
|
|
26
30
|
label?: string
|
|
27
31
|
/** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
|
|
28
32
|
labelVisible?: boolean
|
|
29
|
-
/** Specify the
|
|
33
|
+
/** Specify the TextInput name. */
|
|
30
34
|
name: string
|
|
31
|
-
/** **(Optional)** Specify the
|
|
35
|
+
/** **(Optional)** Specify the TextInput placeholder text. Do not use in place of a form label. */
|
|
32
36
|
placeholder?: string
|
|
33
|
-
/** **(Optional)** Specify the
|
|
37
|
+
/** **(Optional)** Specify the prefix. */
|
|
38
|
+
prefix?: string
|
|
39
|
+
/** **(Optional)** Specify the TextInput size. */
|
|
34
40
|
size?: StandardSizes
|
|
35
|
-
/** **(Optional)** Specify the
|
|
41
|
+
/** **(Optional)** Specify the suffix. */
|
|
42
|
+
suffix?: string
|
|
43
|
+
/** **(Optional)** Specify the TextInput type. */
|
|
36
44
|
type?: "text" | "email" | "number" | "password" | "search" | "tel" | "url"
|
|
37
45
|
}
|
|
38
46
|
|
|
39
|
-
export const
|
|
47
|
+
export const TextInput = ({
|
|
40
48
|
"aria-placeholder": ariaPlaceholder,
|
|
41
49
|
className,
|
|
42
50
|
error = false,
|
|
@@ -46,17 +54,19 @@ export const Input = ({
|
|
|
46
54
|
labelVisible = true,
|
|
47
55
|
name,
|
|
48
56
|
placeholder,
|
|
57
|
+
prefix,
|
|
49
58
|
size = "md",
|
|
59
|
+
suffix,
|
|
50
60
|
type = "text",
|
|
51
61
|
...props
|
|
52
|
-
}:
|
|
62
|
+
}: TextInputProps): JSX.Element => {
|
|
53
63
|
const internalId = useId()
|
|
54
64
|
|
|
55
65
|
id = id ?? internalId
|
|
56
66
|
|
|
57
67
|
const componentProps = {
|
|
58
68
|
"aria-placeholder": placeholder ?? ariaPlaceholder ?? undefined,
|
|
59
|
-
className: clsx("coop-input", className),
|
|
69
|
+
className: clsx("coop-text-input", className),
|
|
60
70
|
"data-error": error ? "" : undefined,
|
|
61
71
|
"data-size": size.length && size !== "md" ? size : undefined,
|
|
62
72
|
id,
|
|
@@ -67,7 +77,7 @@ export const Input = ({
|
|
|
67
77
|
}
|
|
68
78
|
|
|
69
79
|
return (
|
|
70
|
-
|
|
80
|
+
<div className="coop-form-item">
|
|
71
81
|
{label && (
|
|
72
82
|
<FieldLabel htmlFor={id} isVisible={labelVisible}>
|
|
73
83
|
{label}
|
|
@@ -77,11 +87,13 @@ export const Input = ({
|
|
|
77
87
|
{hint && <FieldHint>{hint}</FieldHint>}
|
|
78
88
|
|
|
79
89
|
{typeof error === "object" && error?.message && <FieldError>{error.message}</FieldError>}
|
|
80
|
-
<div className="coop-
|
|
90
|
+
<div className="coop-text-input-wrapper">
|
|
91
|
+
{prefix && <span className="coop-text-input--prefix">{prefix}</span>}
|
|
81
92
|
<input {...componentProps} />
|
|
93
|
+
{suffix && <span className="coop-text-input--suffix">{suffix}</span>}
|
|
82
94
|
</div>
|
|
83
|
-
|
|
95
|
+
</div>
|
|
84
96
|
)
|
|
85
97
|
}
|
|
86
98
|
|
|
87
|
-
export default
|
|
99
|
+
export default TextInput
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { ChangeEvent, JSX, TextareaHTMLAttributes } from "react"
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx"
|
|
4
|
+
import { useId, useState } from "react"
|
|
5
|
+
|
|
6
|
+
import { useDebounce } from "../../hooks/useDebounce"
|
|
7
|
+
import { FormFieldError, StandardSizes } from "../../types"
|
|
8
|
+
import { FieldError } from "../FieldError"
|
|
9
|
+
import { FieldHint } from "../FieldHint"
|
|
10
|
+
import { FieldLabel } from "../FieldLabel"
|
|
11
|
+
|
|
12
|
+
const DEBOUNCE_DELAY = 750
|
|
13
|
+
|
|
14
|
+
const charCountMessage = (remaining: number) => {
|
|
15
|
+
return `You have ${Math.abs(remaining).toLocaleString()} ${Math.abs(remaining) === 1 ? "character" : "characters"} ${remaining < 0 ? "too many" : "remaining"}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
19
|
+
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
20
|
+
className?: string
|
|
21
|
+
/** Specify the number of columns (characters per row) in the Textarea. Defaults to `30`. */
|
|
22
|
+
cols?: number
|
|
23
|
+
/** **(Optional)** Specify whether to show the character counter underneath the Textarea. */
|
|
24
|
+
counter?: boolean
|
|
25
|
+
/** **(Optional)** Specify whether the Textarea should allow more characters than its `maxLength` value.
|
|
26
|
+
*
|
|
27
|
+
* Defaults to `false`, meaning users can enter more characters than the maximum but will be warned if they go over the limit. When set to `true`, users will be blocked from typing once they hit the character limit. This can be an accessiblity anti-pattern, so only use this option when absolutely necessary.
|
|
28
|
+
*
|
|
29
|
+
* Remember it is still your responsibility to handle validation on submission, this is simply a hint for the user.
|
|
30
|
+
*/
|
|
31
|
+
cutoff?: boolean
|
|
32
|
+
/** **(Optional)** Specify the Textarea error state.
|
|
33
|
+
*
|
|
34
|
+
* 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.
|
|
35
|
+
*/
|
|
36
|
+
error?: FormFieldError
|
|
37
|
+
/** **(Optional)** Specify the Textarea hint.
|
|
38
|
+
*
|
|
39
|
+
* This text is rendered under the label to provide further guidance for users.
|
|
40
|
+
*/
|
|
41
|
+
hint?: string
|
|
42
|
+
/** **(Optional)** Specify the Textarea id. Will be auto-generated if not set. */
|
|
43
|
+
id?: string
|
|
44
|
+
/** **(Optional)** Specify the Textarea label.
|
|
45
|
+
*
|
|
46
|
+
* This property is optional in case you need to render your own label, but all form elements *must* provide a label. */
|
|
47
|
+
label?: string
|
|
48
|
+
/** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
|
|
49
|
+
labelVisible?: boolean
|
|
50
|
+
/** **(Optional)** Specify the Textarea maxLength. This is the maximum number of characters users can enter. */
|
|
51
|
+
maxLength?: number
|
|
52
|
+
/** Specify the Textarea name. */
|
|
53
|
+
name: string
|
|
54
|
+
/** **(Optional)** Specify the Textarea placeholder text. Do not use in place of a form label. */
|
|
55
|
+
placeholder?: string
|
|
56
|
+
/** Specify the number of rows (lines of text) in the Textarea. Defaults to `4`. */
|
|
57
|
+
rows?: number
|
|
58
|
+
/** **(Optional)** Specify the Textarea size. */
|
|
59
|
+
size?: StandardSizes
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const Textarea = ({
|
|
63
|
+
"aria-placeholder": ariaPlaceholder,
|
|
64
|
+
className,
|
|
65
|
+
cols = 30,
|
|
66
|
+
counter = false,
|
|
67
|
+
cutoff = false,
|
|
68
|
+
error = false,
|
|
69
|
+
hint,
|
|
70
|
+
id,
|
|
71
|
+
label,
|
|
72
|
+
labelVisible = true,
|
|
73
|
+
maxLength,
|
|
74
|
+
name,
|
|
75
|
+
onChange: userOnChange = undefined,
|
|
76
|
+
placeholder,
|
|
77
|
+
rows = 4,
|
|
78
|
+
size = "md",
|
|
79
|
+
...props
|
|
80
|
+
}: TextareaProps): JSX.Element => {
|
|
81
|
+
const internalId = useId()
|
|
82
|
+
|
|
83
|
+
id = id ?? internalId
|
|
84
|
+
|
|
85
|
+
const componentProps = {
|
|
86
|
+
"aria-placeholder": placeholder ?? ariaPlaceholder ?? undefined,
|
|
87
|
+
className: clsx("coop-textarea", className),
|
|
88
|
+
cols,
|
|
89
|
+
"data-error": error ? "" : undefined,
|
|
90
|
+
"data-size": size.length && size !== "md" ? size : undefined,
|
|
91
|
+
id,
|
|
92
|
+
maxLength: cutoff ? maxLength : undefined,
|
|
93
|
+
name,
|
|
94
|
+
placeholder,
|
|
95
|
+
rows,
|
|
96
|
+
...props,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const [remaining, setRemaining] = useState(maxLength)
|
|
100
|
+
const debouncedRemaining = useDebounce(remaining, DEBOUNCE_DELAY)
|
|
101
|
+
|
|
102
|
+
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
103
|
+
maxLength && e.target && setRemaining(maxLength - e.target.value.length)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div className="coop-form-item">
|
|
108
|
+
{label && (
|
|
109
|
+
<FieldLabel htmlFor={id} isVisible={labelVisible}>
|
|
110
|
+
{label}
|
|
111
|
+
</FieldLabel>
|
|
112
|
+
)}
|
|
113
|
+
|
|
114
|
+
{hint && <FieldHint>{hint}</FieldHint>}
|
|
115
|
+
|
|
116
|
+
{typeof error === "object" && error?.message && <FieldError>{error.message}</FieldError>}
|
|
117
|
+
|
|
118
|
+
<textarea
|
|
119
|
+
{...componentProps}
|
|
120
|
+
onChange={(e) => {
|
|
121
|
+
userOnChange?.(e)
|
|
122
|
+
handleChange(e)
|
|
123
|
+
}}
|
|
124
|
+
></textarea>
|
|
125
|
+
|
|
126
|
+
{counter && maxLength && remaining != null && debouncedRemaining != null && (
|
|
127
|
+
<>
|
|
128
|
+
<small
|
|
129
|
+
aria-hidden="true"
|
|
130
|
+
className="coop-textarea-counter"
|
|
131
|
+
{...(remaining < 0 && { "data-error": "" })}
|
|
132
|
+
>
|
|
133
|
+
{charCountMessage(remaining)}
|
|
134
|
+
</small>
|
|
135
|
+
<span aria-live="polite" className="sr-only">
|
|
136
|
+
{charCountMessage(debouncedRemaining)}
|
|
137
|
+
</span>
|
|
138
|
+
</>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default Textarea
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useEffect, useState } from "react"
|
|
2
|
+
|
|
3
|
+
export function useDebounce<T>(value: T, delay: number) {
|
|
4
|
+
const [debouncedValue, setDebouncedValue] = useState(value)
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const handler = setTimeout(() => {
|
|
7
|
+
setDebouncedValue(value)
|
|
8
|
+
}, delay)
|
|
9
|
+
|
|
10
|
+
return () => {
|
|
11
|
+
clearTimeout(handler)
|
|
12
|
+
}
|
|
13
|
+
}, [value, delay])
|
|
14
|
+
|
|
15
|
+
return debouncedValue
|
|
16
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,8 @@ export * from "./components/AlertBanner"
|
|
|
2
2
|
export * from "./components/Author"
|
|
3
3
|
export * from "./components/Button"
|
|
4
4
|
export * from "./components/Card"
|
|
5
|
+
export * from "./components/Checkbox"
|
|
6
|
+
export * from "./components/Checkbox/CheckboxGroup"
|
|
5
7
|
export * from "./components/Expandable"
|
|
6
8
|
export * from "./components/Field"
|
|
7
9
|
export * from "./components/FieldError"
|
|
@@ -9,7 +11,6 @@ export * from "./components/FieldHint"
|
|
|
9
11
|
export * from "./components/FieldLabel"
|
|
10
12
|
export * from "./components/Flourish"
|
|
11
13
|
export * from "./components/Image"
|
|
12
|
-
export * from "./components/Input"
|
|
13
14
|
export * from "./components/Pill"
|
|
14
15
|
export * from "./components/RootSVG"
|
|
15
16
|
export * from "./components/SearchBox"
|
|
@@ -17,3 +18,5 @@ export * from "./components/Signpost"
|
|
|
17
18
|
export * from "./components/SkipNav"
|
|
18
19
|
export * from "./components/Squircle"
|
|
19
20
|
export * from "./components/Tag"
|
|
21
|
+
export * from "./components/Textarea"
|
|
22
|
+
export * from "./components/TextInput"
|
package/src/types/index.ts
CHANGED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { InputHTMLAttributes, JSX } from "react";
|
|
2
|
-
import { FormFieldError, StandardSizes } from "../../../src/types";
|
|
3
|
-
export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
|
|
4
|
-
/** **(Optional)** Specify additional CSS classes to be applied to the component. */
|
|
5
|
-
className?: string;
|
|
6
|
-
/** **(Optional)** Specify the Input error. */
|
|
7
|
-
error?: FormFieldError;
|
|
8
|
-
/** **(Optional)** Specify the Input hint.
|
|
9
|
-
*
|
|
10
|
-
* This text is rendered under the label to provide further guidance for users.
|
|
11
|
-
*/
|
|
12
|
-
hint?: string;
|
|
13
|
-
/** **(Optional)** Specify the Input id. Will be auto-generated if not set. */
|
|
14
|
-
id?: string;
|
|
15
|
-
/** **(Optional)** Specify the Input label.
|
|
16
|
-
*
|
|
17
|
-
* This property is optional in case you need to render your own label, but all form elements *must* provide a label. */
|
|
18
|
-
label?: string;
|
|
19
|
-
/** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
|
|
20
|
-
labelVisible?: boolean;
|
|
21
|
-
/** Specify the Input name. */
|
|
22
|
-
name: string;
|
|
23
|
-
/** **(Optional)** Specify the Input placeholder text. Do not use in place of a form label. */
|
|
24
|
-
placeholder?: string;
|
|
25
|
-
/** **(Optional)** Specify the Input size. */
|
|
26
|
-
size?: StandardSizes;
|
|
27
|
-
/** **(Optional)** Specify the Input type. */
|
|
28
|
-
type?: "text" | "email" | "number" | "password" | "search" | "tel" | "url";
|
|
29
|
-
}
|
|
30
|
-
export declare const Input: ({ "aria-placeholder": ariaPlaceholder, className, error, hint, id, label, labelVisible, name, placeholder, size, type, ...props }: InputProps) => JSX.Element;
|
|
31
|
-
export default Input;
|