@coopdigital/react 0.34.0 → 0.36.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.
Files changed (42) hide show
  1. package/dist/components/Checkbox/Checkbox.d.ts +30 -0
  2. package/dist/components/Checkbox/Checkbox.js +23 -0
  3. package/dist/components/Checkbox/CheckboxGroup.d.ts +26 -0
  4. package/dist/components/Checkbox/CheckboxGroup.js +19 -0
  5. package/dist/components/Checkbox/index.d.ts +5 -0
  6. package/dist/components/Field/Field.js +1 -1
  7. package/dist/components/FieldHint/FieldHint.js +1 -1
  8. package/dist/components/RadioButton/RadioButton.d.ts +30 -0
  9. package/dist/components/RadioButton/RadioButton.js +23 -0
  10. package/dist/components/RadioButton/RadioButtonGroup.d.ts +26 -0
  11. package/dist/components/RadioButton/RadioButtonGroup.js +19 -0
  12. package/dist/components/RadioButton/index.d.ts +5 -0
  13. package/dist/components/SearchBox/SearchBox.d.ts +3 -3
  14. package/dist/components/SearchBox/SearchBox.js +2 -2
  15. package/dist/components/{Input/Input.d.ts → TextInput/TextInput.d.ts} +15 -11
  16. package/dist/components/TextInput/TextInput.js +26 -0
  17. package/dist/components/TextInput/index.d.ts +4 -0
  18. package/dist/components/TextInput/index.js +5 -0
  19. package/dist/components/Textarea/Textarea.d.ts +3 -3
  20. package/dist/components/Textarea/Textarea.js +7 -7
  21. package/dist/index.d.ts +3 -1
  22. package/dist/index.js +5 -1
  23. package/dist/types/index.d.ts +1 -1
  24. package/package.json +10 -10
  25. package/src/components/Checkbox/Checkbox.tsx +81 -0
  26. package/src/components/Checkbox/CheckboxGroup.tsx +63 -0
  27. package/src/components/Checkbox/index.ts +6 -0
  28. package/src/components/Field/Field.tsx +1 -1
  29. package/src/components/FieldHint/FieldHint.tsx +1 -1
  30. package/src/components/RadioButton/RadioButton.tsx +79 -0
  31. package/src/components/RadioButton/RadioButtonGroup.tsx +63 -0
  32. package/src/components/RadioButton/index.ts +6 -0
  33. package/src/components/SearchBox/SearchBox.tsx +7 -7
  34. package/src/components/{Input/Input.tsx → TextInput/TextInput.tsx} +25 -16
  35. package/src/components/TextInput/index.ts +5 -0
  36. package/src/components/Textarea/Textarea.tsx +16 -17
  37. package/src/index.ts +3 -1
  38. package/src/types/index.ts +1 -1
  39. package/dist/components/Input/Input.js +0 -26
  40. package/dist/components/Input/index.d.ts +0 -4
  41. package/dist/components/Input/index.js +0 -5
  42. package/src/components/Input/index.ts +0 -5
@@ -0,0 +1,30 @@
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 size. */
27
+ size?: StandardSizes;
28
+ }
29
+ export declare const Checkbox: ({ className, error, hint, id, label, labelVisible, name, size, ...props }: CheckboxProps) => JSX.Element;
30
+ export default Checkbox;
@@ -0,0 +1,23 @@
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 Checkbox = ({ className, error = false, hint, id, label, labelVisible = true, name, size = "md", ...props }) => {
9
+ const internalId = useId();
10
+ id = id !== null && id !== void 0 ? id : internalId;
11
+ const componentProps = {
12
+ className: clsx("coop-checkbox", className),
13
+ "data-error": error ? "" : undefined,
14
+ "data-size": size.length && size !== "md" ? size : undefined,
15
+ id,
16
+ name,
17
+ type: "checkbox",
18
+ ...props,
19
+ };
20
+ 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 })] }));
21
+ };
22
+
23
+ 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 };
@@ -0,0 +1,5 @@
1
+ import Checkbox from "./Checkbox";
2
+ import CheckboxGroup from "./CheckboxGroup";
3
+ export default Checkbox;
4
+ export { Checkbox, CheckboxGroup };
5
+ export * from "./Checkbox";
@@ -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-field ", className),
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("p", { ...componentProps, children: children }) : null;
9
+ return children ? jsx("div", { ...componentProps, children: children }) : null;
10
10
  };
11
11
 
12
12
  export { FieldHint, FieldHint as default };
@@ -0,0 +1,30 @@
1
+ import { type InputHTMLAttributes, type JSX } from "react";
2
+ import { FormFieldError, StandardSizes } from "src/types";
3
+ export interface RadioButtonProps 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 RadioButton 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 RadioButton hint.
12
+ *
13
+ * This text is rendered under the label to provide further guidance for users.
14
+ */
15
+ hint?: string;
16
+ /** **(Optional)** Specify the RadioButton id. Will be auto-generated if not set. */
17
+ id?: string;
18
+ /** **(Optional)** Specify the RadioButton 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 RadioButton name. */
25
+ name: string;
26
+ /** **(Optional)** Specify the RadioButton size. */
27
+ size?: StandardSizes;
28
+ }
29
+ export declare const RadioButton: ({ className, error, hint, id, label, labelVisible, name, size, ...props }: RadioButtonProps) => JSX.Element;
30
+ export default RadioButton;
@@ -0,0 +1,23 @@
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 RadioButton = ({ className, error = false, hint, id, label, labelVisible = true, name, size = "md", ...props }) => {
9
+ const internalId = useId();
10
+ id = id !== null && id !== void 0 ? id : internalId;
11
+ const componentProps = {
12
+ className: clsx("coop-radio-button", className),
13
+ "data-error": error ? "" : undefined,
14
+ "data-size": size.length && size !== "md" ? size : undefined,
15
+ id,
16
+ name,
17
+ type: "radio",
18
+ ...props,
19
+ };
20
+ return (jsxs("div", { className: "coop-form-item", children: [jsxs("div", { className: "coop-radio-button-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 })] }));
21
+ };
22
+
23
+ export { RadioButton, RadioButton as default };
@@ -0,0 +1,26 @@
1
+ import type { FieldsetHTMLAttributes, JSX } from "react";
2
+ import { FormFieldError, StandardSizes } from "../../types";
3
+ export interface RadioButtonGroupProps 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 RadioButtonGroup 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 RadioButtonGroup 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 RadioButtonGroup. 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 RadioButtonGroup size. */
23
+ size?: StandardSizes;
24
+ }
25
+ export declare const RadioButtonGroup: ({ children, className, error, hint, label, labelVisible, size, ...props }: RadioButtonGroupProps) => JSX.Element;
26
+ export default RadioButtonGroup;
@@ -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 RadioButtonGroup = ({ children, className, error = false, hint, label, labelVisible = true, size = "md", ...props }) => {
7
+ const componentProps = {
8
+ className: clsx("coop-fieldset", "coop-radio-button-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 { RadioButtonGroup, RadioButtonGroup as default };
@@ -0,0 +1,5 @@
1
+ import RadioButton from "./RadioButton";
2
+ import RadioButtonGroup from "./RadioButtonGroup";
3
+ export default RadioButton;
4
+ export { RadioButton, RadioButtonGroup };
5
+ export * from "./RadioButton";
@@ -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 Input id. Will be auto-generated if not set. */
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 Input name, also used as the URL search parameter. Defaults to `query`. */
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 Input placeholder text Do not use in place of a form label. */
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 { Input } from '../Input/Input.js';
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(Input, { ...inputProps }), jsx(Button, { ...buttonProps, children: button.label })] })] }));
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 };
@@ -1,34 +1,38 @@
1
1
  import type { InputHTMLAttributes, JSX } from "react";
2
2
  import { FormFieldError, StandardSizes } from "../../../src/types";
3
- export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
3
+ export interface TextInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
4
4
  /** **(Optional)** Specify additional CSS classes to be applied to the component. */
5
5
  className?: string;
6
- /** **(Optional)** Specify the Input error state.
6
+ /** **(Optional)** Specify the TextInput error state.
7
7
  *
8
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
9
  */
10
10
  error?: FormFieldError;
11
- /** **(Optional)** Specify the Input hint.
11
+ /** **(Optional)** Specify the TextInput hint.
12
12
  *
13
13
  * This text is rendered under the label to provide further guidance for users.
14
14
  */
15
15
  hint?: string;
16
- /** **(Optional)** Specify the Input id. Will be auto-generated if not set. */
16
+ /** **(Optional)** Specify the TextInput id. Will be auto-generated if not set. */
17
17
  id?: string;
18
- /** **(Optional)** Specify the Input label.
18
+ /** **(Optional)** Specify the TextInput label.
19
19
  *
20
20
  * This property is optional in case you need to render your own label, but all form elements *must* provide a label. */
21
21
  label?: string;
22
22
  /** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
23
23
  labelVisible?: boolean;
24
- /** Specify the Input name. */
24
+ /** Specify the TextInput name. */
25
25
  name: string;
26
- /** **(Optional)** Specify the Input placeholder text. Do not use in place of a form label. */
26
+ /** **(Optional)** Specify the TextInput placeholder text. Do not use in place of a form label. */
27
27
  placeholder?: string;
28
- /** **(Optional)** Specify the Input size. */
28
+ /** **(Optional)** Specify the prefix. */
29
+ prefix?: string;
30
+ /** **(Optional)** Specify the TextInput size. */
29
31
  size?: StandardSizes;
30
- /** **(Optional)** Specify the Input type. */
32
+ /** **(Optional)** Specify the suffix. */
33
+ suffix?: string;
34
+ /** **(Optional)** Specify the TextInput type. */
31
35
  type?: "text" | "email" | "number" | "password" | "search" | "tel" | "url";
32
36
  }
33
- export declare const Input: ({ "aria-placeholder": ariaPlaceholder, className, error, hint, id, label, labelVisible, name, placeholder, size, type, ...props }: InputProps) => JSX.Element;
34
- export default Input;
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,4 @@
1
+ import TextInput from "./TextInput";
2
+ export default TextInput;
3
+ export { TextInput };
4
+ export * from "./TextInput";
@@ -0,0 +1,5 @@
1
+ import { TextInput } from './TextInput.js';
2
+
3
+
4
+
5
+ export { TextInput, TextInput as default };
@@ -5,6 +5,8 @@ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElemen
5
5
  className?: string;
6
6
  /** Specify the number of columns (characters per row) in the Textarea. Defaults to `30`. */
7
7
  cols?: number;
8
+ /** **(Optional)** Specify whether to show the character counter underneath the Textarea. */
9
+ counter?: boolean;
8
10
  /** **(Optional)** Specify whether the Textarea should allow more characters than its `maxLength` value.
9
11
  *
10
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.
@@ -38,10 +40,8 @@ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElemen
38
40
  placeholder?: string;
39
41
  /** Specify the number of rows (lines of text) in the Textarea. Defaults to `4`. */
40
42
  rows?: number;
41
- /** **(Optional)** Specify whether to show the remaining character count underneath the Textarea. */
42
- showRemaining?: boolean;
43
43
  /** **(Optional)** Specify the Textarea size. */
44
44
  size?: StandardSizes;
45
45
  }
46
- export declare const Textarea: ({ "aria-placeholder": ariaPlaceholder, className, cols, cutoff, error, hint, id, label, labelVisible, maxLength, name, onChange: userOnChange, placeholder, rows, showRemaining, size, ...props }: TextareaProps) => JSX.Element;
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
47
  export default Textarea;
@@ -1,4 +1,4 @@
1
- import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
1
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import { clsx } from '../../node_modules/clsx/dist/clsx.js';
3
3
  import { useId, useState } from 'react';
4
4
  import { useDebounce } from '../../hooks/useDebounce.js';
@@ -8,9 +8,9 @@ import { FieldLabel } from '../FieldLabel/FieldLabel.js';
8
8
 
9
9
  const DEBOUNCE_DELAY = 750;
10
10
  const charCountMessage = (remaining) => {
11
- return `You have ${Math.abs(remaining)} ${Math.abs(remaining) === 1 ? "character" : "characters"} ${remaining < 0 ? "too many" : "remaining"}`;
11
+ return `You have ${Math.abs(remaining).toLocaleString()} ${Math.abs(remaining) === 1 ? "character" : "characters"} ${remaining < 0 ? "too many" : "remaining"}`;
12
12
  };
13
- const Textarea = ({ "aria-placeholder": ariaPlaceholder, className, cols = 30, cutoff = false, error = false, hint, id, label, labelVisible = true, maxLength, name, onChange: userOnChange = undefined, placeholder, rows = 4, showRemaining = false, size = "md", ...props }) => {
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
14
  var _a;
15
15
  const internalId = useId();
16
16
  id = id !== null && id !== void 0 ? id : internalId;
@@ -32,10 +32,10 @@ const Textarea = ({ "aria-placeholder": ariaPlaceholder, className, cols = 30, c
32
32
  const handleChange = (e) => {
33
33
  maxLength && e.target && setRemaining(maxLength - e.target.value.length);
34
34
  };
35
- return (jsxs(Fragment, { 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("div", { className: "coop-field-control", children: jsx("textarea", { ...componentProps, onChange: (e) => {
36
- userOnChange === null || userOnChange === void 0 ? void 0 : userOnChange(e);
37
- handleChange(e);
38
- } }) }), showRemaining && 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) })] }))] }));
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
39
  };
40
40
 
41
41
  export { Textarea, Textarea as default };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ 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";
5
6
  export * from "./components/Expandable";
6
7
  export * from "./components/Field";
7
8
  export * from "./components/FieldError";
@@ -9,8 +10,8 @@ export * from "./components/FieldHint";
9
10
  export * from "./components/FieldLabel";
10
11
  export * from "./components/Flourish";
11
12
  export * from "./components/Image";
12
- export * from "./components/Input";
13
13
  export * from "./components/Pill";
14
+ export * from "./components/RadioButton";
14
15
  export * from "./components/RootSVG";
15
16
  export * from "./components/SearchBox";
16
17
  export * from "./components/Signpost";
@@ -18,3 +19,4 @@ export * from "./components/SkipNav";
18
19
  export * from "./components/Squircle";
19
20
  export * from "./components/Tag";
20
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,8 +11,9 @@ 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';
15
+ export { RadioButton } from './components/RadioButton/RadioButton.js';
16
+ export { RadioButtonGroup } from './components/RadioButton/RadioButtonGroup.js';
14
17
  export { RootSVG } from './components/RootSVG/RootSVG.js';
15
18
  export { SearchBox } from './components/SearchBox/SearchBox.js';
16
19
  export { Signpost } from './components/Signpost/Signpost.js';
@@ -18,3 +21,4 @@ export { SkipNav } from './components/SkipNav/SkipNav.js';
18
21
  export { Squircle } from './components/Squircle/Squircle.js';
19
22
  export { Tag } from './components/Tag/Tag.js';
20
23
  export { Textarea } from './components/Textarea/Textarea.js';
24
+ export { TextInput } from './components/TextInput/TextInput.js';
@@ -1,5 +1,5 @@
1
1
  export type StandardSizes = "sm" | "md" | "lg";
2
2
  export type FormFieldError = {
3
- message: string;
3
+ message?: string;
4
4
  type?: string;
5
5
  } | boolean | null | undefined;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coopdigital/react",
3
3
  "type": "module",
4
- "version": "0.34.0",
4
+ "version": "0.36.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.3",
61
- "@storybook/addon-docs": "^9.1.3",
62
- "@storybook/addon-onboarding": "^9.1.3",
63
- "@storybook/react-vite": "^9.1.3",
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.12",
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.4",
72
- "storybook": "^9.1.3"
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.30.0"
83
+ "@coopdigital/styles": "^0.31.1"
84
84
  },
85
- "gitHead": "858102db210474fc31e01a2f4dc97551af2621d3"
85
+ "gitHead": "a88bc2694b91f0c180f02d39786a1323e6632254"
86
86
  }
@@ -0,0 +1,81 @@
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 size. */
36
+ size?: StandardSizes
37
+ }
38
+
39
+ export const Checkbox = ({
40
+ className,
41
+ error = false,
42
+ hint,
43
+ id,
44
+ label,
45
+ labelVisible = true,
46
+ name,
47
+ size = "md",
48
+ ...props
49
+ }: CheckboxProps): JSX.Element => {
50
+ const internalId = useId()
51
+
52
+ id = id ?? internalId
53
+
54
+ const componentProps = {
55
+ className: clsx("coop-checkbox", className),
56
+ "data-error": error ? "" : undefined,
57
+ "data-size": size.length && size !== "md" ? size : undefined,
58
+ id,
59
+ name,
60
+ type: "checkbox",
61
+ ...props,
62
+ }
63
+
64
+ return (
65
+ <div className="coop-form-item">
66
+ <div className="coop-checkbox-wrapper">
67
+ <input {...componentProps} />
68
+
69
+ {label && (
70
+ <FieldLabel htmlFor={id} isVisible={labelVisible}>
71
+ {label}
72
+ </FieldLabel>
73
+ )}
74
+ {hint && <FieldHint>{hint}</FieldHint>}
75
+ </div>
76
+ {typeof error === "object" && error?.message && <FieldError>{error.message}</FieldError>}
77
+ </div>
78
+ )
79
+ }
80
+
81
+ 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
@@ -0,0 +1,6 @@
1
+ import Checkbox from "./Checkbox"
2
+ import CheckboxGroup from "./CheckboxGroup"
3
+
4
+ export default Checkbox
5
+ export { Checkbox, CheckboxGroup }
6
+ export * from "./Checkbox"
@@ -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-field ", className),
14
+ className: clsx("coop-form-item ", className),
15
15
  ...props,
16
16
  }
17
17
  return <div {...componentProps}>{children}</div>
@@ -19,7 +19,7 @@ export const FieldHint = ({
19
19
  ...props,
20
20
  }
21
21
 
22
- return children ? <p {...componentProps}>{children}</p> : null
22
+ return children ? <div {...componentProps}>{children}</div> : null
23
23
  }
24
24
 
25
25
  export default FieldHint
@@ -0,0 +1,79 @@
1
+ import clsx from "clsx"
2
+ import { type InputHTMLAttributes, type JSX, useId } from "react"
3
+ import { FormFieldError, StandardSizes } from "src/types"
4
+
5
+ import { FieldError } from "../FieldError"
6
+ import { FieldHint } from "../FieldHint"
7
+ import { FieldLabel } from "../FieldLabel"
8
+
9
+ export interface RadioButtonProps
10
+ extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
11
+ /** **(Optional)** Specify additional CSS classes to be applied to the component. */
12
+ className?: string
13
+ /** **(Optional)** Specify the RadioButton error state.
14
+ *
15
+ * 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.
16
+ */
17
+ error?: FormFieldError
18
+ /** **(Optional)** Specify the RadioButton hint.
19
+ *
20
+ * This text is rendered under the label to provide further guidance for users.
21
+ */
22
+ hint?: string
23
+ /** **(Optional)** Specify the RadioButton id. Will be auto-generated if not set. */
24
+ id?: string
25
+ /** **(Optional)** Specify the RadioButton label.
26
+ *
27
+ * This property is optional in case you need to render your own label, but all form elements *must* provide a label. */
28
+ label: string
29
+ /** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
30
+ labelVisible?: boolean
31
+ /** Specify the RadioButton name. */
32
+ name: string
33
+ /** **(Optional)** Specify the RadioButton size. */
34
+ size?: StandardSizes
35
+ }
36
+
37
+ export const RadioButton = ({
38
+ className,
39
+ error = false,
40
+ hint,
41
+ id,
42
+ label,
43
+ labelVisible = true,
44
+ name,
45
+ size = "md",
46
+ ...props
47
+ }: RadioButtonProps): JSX.Element => {
48
+ const internalId = useId()
49
+
50
+ id = id ?? internalId
51
+
52
+ const componentProps = {
53
+ className: clsx("coop-radio-button", className),
54
+ "data-error": error ? "" : undefined,
55
+ "data-size": size.length && size !== "md" ? size : undefined,
56
+ id,
57
+ name,
58
+ type: "radio",
59
+ ...props,
60
+ }
61
+
62
+ return (
63
+ <div className="coop-form-item">
64
+ <div className="coop-radio-button-wrapper">
65
+ <input {...componentProps} />
66
+
67
+ {label && (
68
+ <FieldLabel htmlFor={id} isVisible={labelVisible}>
69
+ {label}
70
+ </FieldLabel>
71
+ )}
72
+ {hint && <FieldHint>{hint}</FieldHint>}
73
+ </div>
74
+ {typeof error === "object" && error?.message && <FieldError>{error.message}</FieldError>}
75
+ </div>
76
+ )
77
+ }
78
+
79
+ export default RadioButton
@@ -0,0 +1,63 @@
1
+ import type { FieldsetHTMLAttributes, JSX } from "react"
2
+
3
+ import clsx from "clsx"
4
+
5
+ import { FormFieldError, StandardSizes } from "../../types"
6
+ import { FieldError } from "../FieldError"
7
+ import { FieldHint } from "../FieldHint"
8
+
9
+ export interface RadioButtonGroupProps 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 RadioButtonGroup 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 RadioButtonGroup 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 RadioButtonGroup. 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 RadioButtonGroup size. */
29
+ size?: StandardSizes
30
+ }
31
+
32
+ export const RadioButtonGroup = ({
33
+ children,
34
+ className,
35
+ error = false,
36
+ hint,
37
+ label,
38
+ labelVisible = true,
39
+ size = "md",
40
+ ...props
41
+ }: RadioButtonGroupProps): JSX.Element => {
42
+ const componentProps = {
43
+ className: clsx("coop-fieldset", "coop-radio-button-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 RadioButtonGroup
@@ -0,0 +1,6 @@
1
+ import RadioButton from "./RadioButton"
2
+ import RadioButtonGroup from "./RadioButtonGroup"
3
+
4
+ export default RadioButton
5
+ export { RadioButton, RadioButtonGroup }
6
+ export * from "./RadioButton"
@@ -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 Input, { InputProps } from "../Input"
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 Input id. Will be auto-generated if not set. */
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 Input name, also used as the URL search parameter. Defaults to `query`. */
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 Input placeholder text Do not use in place of a form label. */
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 InputProps["size"],
116
- type: "search" as keyof InputProps["type"],
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
- <Input {...inputProps} />
128
+ <TextInput {...inputProps} />
129
129
  <Button {...buttonProps}>{button.label}</Button>
130
130
  </div>
131
131
  </form>
@@ -8,38 +8,43 @@ import { FieldError } from "../FieldError"
8
8
  import { FieldHint } from "../FieldHint"
9
9
  import { FieldLabel } from "../FieldLabel"
10
10
 
11
- export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
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 Input error state.
15
+ /** **(Optional)** Specify the TextInput error state.
15
16
  *
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.
17
18
  */
18
19
  error?: FormFieldError
19
- /** **(Optional)** Specify the Input hint.
20
+ /** **(Optional)** Specify the TextInput hint.
20
21
  *
21
22
  * This text is rendered under the label to provide further guidance for users.
22
23
  */
23
24
  hint?: string
24
- /** **(Optional)** Specify the Input id. Will be auto-generated if not set. */
25
+ /** **(Optional)** Specify the TextInput id. Will be auto-generated if not set. */
25
26
  id?: string
26
- /** **(Optional)** Specify the Input label.
27
+ /** **(Optional)** Specify the TextInput label.
27
28
  *
28
29
  * This property is optional in case you need to render your own label, but all form elements *must* provide a label. */
29
30
  label?: string
30
31
  /** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
31
32
  labelVisible?: boolean
32
- /** Specify the Input name. */
33
+ /** Specify the TextInput name. */
33
34
  name: string
34
- /** **(Optional)** Specify the Input placeholder text. Do not use in place of a form label. */
35
+ /** **(Optional)** Specify the TextInput placeholder text. Do not use in place of a form label. */
35
36
  placeholder?: string
36
- /** **(Optional)** Specify the Input size. */
37
+ /** **(Optional)** Specify the prefix. */
38
+ prefix?: string
39
+ /** **(Optional)** Specify the TextInput size. */
37
40
  size?: StandardSizes
38
- /** **(Optional)** Specify the Input type. */
41
+ /** **(Optional)** Specify the suffix. */
42
+ suffix?: string
43
+ /** **(Optional)** Specify the TextInput type. */
39
44
  type?: "text" | "email" | "number" | "password" | "search" | "tel" | "url"
40
45
  }
41
46
 
42
- export const Input = ({
47
+ export const TextInput = ({
43
48
  "aria-placeholder": ariaPlaceholder,
44
49
  className,
45
50
  error = false,
@@ -49,17 +54,19 @@ export const Input = ({
49
54
  labelVisible = true,
50
55
  name,
51
56
  placeholder,
57
+ prefix,
52
58
  size = "md",
59
+ suffix,
53
60
  type = "text",
54
61
  ...props
55
- }: InputProps): JSX.Element => {
62
+ }: TextInputProps): JSX.Element => {
56
63
  const internalId = useId()
57
64
 
58
65
  id = id ?? internalId
59
66
 
60
67
  const componentProps = {
61
68
  "aria-placeholder": placeholder ?? ariaPlaceholder ?? undefined,
62
- className: clsx("coop-input", className),
69
+ className: clsx("coop-text-input", className),
63
70
  "data-error": error ? "" : undefined,
64
71
  "data-size": size.length && size !== "md" ? size : undefined,
65
72
  id,
@@ -70,7 +77,7 @@ export const Input = ({
70
77
  }
71
78
 
72
79
  return (
73
- <>
80
+ <div className="coop-form-item">
74
81
  {label && (
75
82
  <FieldLabel htmlFor={id} isVisible={labelVisible}>
76
83
  {label}
@@ -80,11 +87,13 @@ export const Input = ({
80
87
  {hint && <FieldHint>{hint}</FieldHint>}
81
88
 
82
89
  {typeof error === "object" && error?.message && <FieldError>{error.message}</FieldError>}
83
- <div className="coop-field-control">
90
+ <div className="coop-text-input-wrapper">
91
+ {prefix && <span className="coop-text-input--prefix">{prefix}</span>}
84
92
  <input {...componentProps} />
93
+ {suffix && <span className="coop-text-input--suffix">{suffix}</span>}
85
94
  </div>
86
- </>
95
+ </div>
87
96
  )
88
97
  }
89
98
 
90
- export default Input
99
+ export default TextInput
@@ -0,0 +1,5 @@
1
+ import TextInput from "./TextInput"
2
+
3
+ export default TextInput
4
+ export { TextInput }
5
+ export * from "./TextInput"
@@ -12,7 +12,7 @@ import { FieldLabel } from "../FieldLabel"
12
12
  const DEBOUNCE_DELAY = 750
13
13
 
14
14
  const charCountMessage = (remaining: number) => {
15
- return `You have ${Math.abs(remaining)} ${Math.abs(remaining) === 1 ? "character" : "characters"} ${remaining < 0 ? "too many" : "remaining"}`
15
+ return `You have ${Math.abs(remaining).toLocaleString()} ${Math.abs(remaining) === 1 ? "character" : "characters"} ${remaining < 0 ? "too many" : "remaining"}`
16
16
  }
17
17
 
18
18
  export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
@@ -20,6 +20,8 @@ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElemen
20
20
  className?: string
21
21
  /** Specify the number of columns (characters per row) in the Textarea. Defaults to `30`. */
22
22
  cols?: number
23
+ /** **(Optional)** Specify whether to show the character counter underneath the Textarea. */
24
+ counter?: boolean
23
25
  /** **(Optional)** Specify whether the Textarea should allow more characters than its `maxLength` value.
24
26
  *
25
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.
@@ -53,8 +55,6 @@ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElemen
53
55
  placeholder?: string
54
56
  /** Specify the number of rows (lines of text) in the Textarea. Defaults to `4`. */
55
57
  rows?: number
56
- /** **(Optional)** Specify whether to show the remaining character count underneath the Textarea. */
57
- showRemaining?: boolean
58
58
  /** **(Optional)** Specify the Textarea size. */
59
59
  size?: StandardSizes
60
60
  }
@@ -63,6 +63,7 @@ export const Textarea = ({
63
63
  "aria-placeholder": ariaPlaceholder,
64
64
  className,
65
65
  cols = 30,
66
+ counter = false,
66
67
  cutoff = false,
67
68
  error = false,
68
69
  hint,
@@ -74,7 +75,6 @@ export const Textarea = ({
74
75
  onChange: userOnChange = undefined,
75
76
  placeholder,
76
77
  rows = 4,
77
- showRemaining = false,
78
78
  size = "md",
79
79
  ...props
80
80
  }: TextareaProps): JSX.Element => {
@@ -104,7 +104,7 @@ export const Textarea = ({
104
104
  }
105
105
 
106
106
  return (
107
- <>
107
+ <div className="coop-form-item">
108
108
  {label && (
109
109
  <FieldLabel htmlFor={id} isVisible={labelVisible}>
110
110
  {label}
@@ -114,17 +114,16 @@ export const Textarea = ({
114
114
  {hint && <FieldHint>{hint}</FieldHint>}
115
115
 
116
116
  {typeof error === "object" && error?.message && <FieldError>{error.message}</FieldError>}
117
- <div className="coop-field-control">
118
- <textarea
119
- {...componentProps}
120
- onChange={(e) => {
121
- userOnChange?.(e)
122
- handleChange(e)
123
- }}
124
- ></textarea>
125
- </div>
126
-
127
- {showRemaining && maxLength && remaining != null && debouncedRemaining != null && (
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 && (
128
127
  <>
129
128
  <small
130
129
  aria-hidden="true"
@@ -138,7 +137,7 @@ export const Textarea = ({
138
137
  </span>
139
138
  </>
140
139
  )}
141
- </>
140
+ </div>
142
141
  )
143
142
  }
144
143
 
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ 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"
5
6
  export * from "./components/Expandable"
6
7
  export * from "./components/Field"
7
8
  export * from "./components/FieldError"
@@ -9,8 +10,8 @@ export * from "./components/FieldHint"
9
10
  export * from "./components/FieldLabel"
10
11
  export * from "./components/Flourish"
11
12
  export * from "./components/Image"
12
- export * from "./components/Input"
13
13
  export * from "./components/Pill"
14
+ export * from "./components/RadioButton"
14
15
  export * from "./components/RootSVG"
15
16
  export * from "./components/SearchBox"
16
17
  export * from "./components/Signpost"
@@ -18,3 +19,4 @@ export * from "./components/SkipNav"
18
19
  export * from "./components/Squircle"
19
20
  export * from "./components/Tag"
20
21
  export * from "./components/Textarea"
22
+ export * from "./components/TextInput"
@@ -1,3 +1,3 @@
1
1
  export type StandardSizes = "sm" | "md" | "lg"
2
2
 
3
- export type FormFieldError = { message: string; type?: string } | boolean | null | undefined
3
+ export type FormFieldError = { message?: string; type?: string } | boolean | null | undefined
@@ -1,26 +0,0 @@
1
- import { jsxs, Fragment, 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 Input = ({ "aria-placeholder": ariaPlaceholder, className, error = false, hint, id, label, labelVisible = true, name, placeholder, size = "md", 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-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(Fragment, { 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("div", { className: "coop-field-control", children: jsx("input", { ...componentProps }) })] }));
24
- };
25
-
26
- export { Input, Input as default };
@@ -1,4 +0,0 @@
1
- import Input from "./Input";
2
- export default Input;
3
- export { Input };
4
- export * from "./Input";
@@ -1,5 +0,0 @@
1
- import { Input } from './Input.js';
2
-
3
-
4
-
5
- export { Input, Input as default };
@@ -1,5 +0,0 @@
1
- import Input from "./Input"
2
-
3
- export default Input
4
- export { Input }
5
- export * from "./Input"