@coopdigital/react 0.44.0 → 0.46.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 (85) hide show
  1. package/dist/components/Button/Button.d.ts +6 -4
  2. package/dist/components/Button/Button.js +4 -6
  3. package/dist/components/Card/Card.js +2 -2
  4. package/dist/components/Checkbox/Checkbox.d.ts +4 -24
  5. package/dist/components/Checkbox/Checkbox.js +7 -14
  6. package/dist/components/Checkbox/index.d.ts +1 -2
  7. package/dist/components/Expandable/Expandable.js +2 -2
  8. package/dist/components/Field/Field.d.ts +44 -5
  9. package/dist/components/Field/Field.js +68 -5
  10. package/dist/components/FieldMarkers/Error.d.ts +9 -0
  11. package/dist/components/{FieldError/FieldError.js → FieldMarkers/Error.js} +2 -2
  12. package/dist/components/FieldMarkers/Hint.d.ts +9 -0
  13. package/dist/components/{FieldHint/FieldHint.js → FieldMarkers/Hint.js} +2 -2
  14. package/dist/components/FieldMarkers/Label.d.ts +11 -0
  15. package/dist/components/FieldMarkers/Label.js +8 -0
  16. package/dist/components/FieldMarkers/Legend.d.ts +11 -0
  17. package/dist/components/FieldMarkers/Legend.js +13 -0
  18. package/dist/components/Fieldset/Fieldset.d.ts +54 -0
  19. package/dist/components/Fieldset/Fieldset.js +44 -0
  20. package/dist/components/Fieldset/index.d.ts +4 -0
  21. package/dist/components/Pill/Pill.js +2 -2
  22. package/dist/components/Radio/Radio.d.ts +18 -0
  23. package/dist/components/Radio/Radio.js +22 -0
  24. package/dist/components/Radio/index.d.ts +4 -0
  25. package/dist/components/{SearchBox/SearchBox.d.ts → Searchbox/Searchbox.d.ts} +7 -7
  26. package/dist/components/{SearchBox/SearchBox.js → Searchbox/Searchbox.js} +6 -6
  27. package/dist/components/Searchbox/index.d.ts +4 -0
  28. package/dist/components/TextInput/TextInput.d.ts +5 -19
  29. package/dist/components/TextInput/TextInput.js +7 -11
  30. package/dist/components/Textarea/Textarea.d.ts +4 -18
  31. package/dist/components/Textarea/Textarea.js +8 -9
  32. package/dist/hooks/useId.d.ts +2 -0
  33. package/dist/hooks/useId.js +8 -0
  34. package/dist/{utils/slots.d.ts → hooks/useSlots.d.ts} +3 -1
  35. package/dist/{utils/slots.js → hooks/useSlots.js} +9 -3
  36. package/dist/index.d.ts +3 -5
  37. package/dist/index.js +4 -8
  38. package/package.json +10 -10
  39. package/src/components/Button/Button.tsx +10 -14
  40. package/src/components/Card/Card.tsx +2 -3
  41. package/src/components/Checkbox/Checkbox.tsx +8 -64
  42. package/src/components/Checkbox/index.ts +1 -2
  43. package/src/components/Expandable/Expandable.tsx +2 -2
  44. package/src/components/Field/Field.tsx +144 -8
  45. package/src/components/{FieldError/FieldError.tsx → FieldMarkers/Error.tsx} +4 -4
  46. package/src/components/{FieldHint/FieldHint.tsx → FieldMarkers/Hint.tsx} +5 -9
  47. package/src/components/FieldMarkers/Label.tsx +21 -0
  48. package/src/components/FieldMarkers/Legend.tsx +28 -0
  49. package/src/components/Fieldset/Fieldset.tsx +98 -0
  50. package/src/components/Fieldset/index.ts +5 -0
  51. package/src/components/Pill/Pill.tsx +2 -2
  52. package/src/components/Radio/Radio.tsx +47 -0
  53. package/src/components/Radio/index.ts +5 -0
  54. package/src/components/{SearchBox/SearchBox.tsx → Searchbox/Searchbox.tsx} +15 -13
  55. package/src/components/Searchbox/index.ts +5 -0
  56. package/src/components/TextInput/TextInput.tsx +25 -46
  57. package/src/components/Textarea/Textarea.tsx +12 -40
  58. package/src/hooks/useId.ts +9 -0
  59. package/src/{utils/slots.ts → hooks/useSlots.ts} +10 -2
  60. package/src/index.ts +3 -5
  61. package/dist/components/Checkbox/CheckboxGroup.d.ts +0 -32
  62. package/dist/components/Checkbox/CheckboxGroup.js +0 -21
  63. package/dist/components/FieldError/FieldError.d.ts +0 -9
  64. package/dist/components/FieldError/index.d.ts +0 -4
  65. package/dist/components/FieldHint/FieldHint.d.ts +0 -9
  66. package/dist/components/FieldHint/index.d.ts +0 -4
  67. package/dist/components/FieldLabel/FieldLabel.d.ts +0 -13
  68. package/dist/components/FieldLabel/FieldLabel.js +0 -13
  69. package/dist/components/FieldLabel/index.d.ts +0 -4
  70. package/dist/components/RadioButton/RadioButton.d.ts +0 -36
  71. package/dist/components/RadioButton/RadioButton.js +0 -26
  72. package/dist/components/RadioButton/RadioButtonGroup.d.ts +0 -32
  73. package/dist/components/RadioButton/RadioButtonGroup.js +0 -21
  74. package/dist/components/RadioButton/index.d.ts +0 -5
  75. package/dist/components/SearchBox/index.d.ts +0 -4
  76. package/dist/components/Tag/index.js +0 -5
  77. package/src/components/Checkbox/CheckboxGroup.tsx +0 -73
  78. package/src/components/FieldError/index.ts +0 -5
  79. package/src/components/FieldHint/index.ts +0 -5
  80. package/src/components/FieldLabel/FieldLabel.tsx +0 -31
  81. package/src/components/FieldLabel/index.ts +0 -5
  82. package/src/components/RadioButton/RadioButton.tsx +0 -97
  83. package/src/components/RadioButton/RadioButtonGroup.tsx +0 -73
  84. package/src/components/RadioButton/index.ts +0 -6
  85. package/src/components/SearchBox/index.ts +0 -5
@@ -4,7 +4,7 @@ import { jsxs, jsx } from 'react/jsx-runtime';
4
4
  import clsx from 'clsx';
5
5
  import React, { useState, useId, useCallback } from 'react';
6
6
  import { Button } from '../Button/Button.js';
7
- import { FieldLabel } from '../FieldLabel/FieldLabel.js';
7
+ import { Label } from '../FieldMarkers/Label.js';
8
8
  import { SearchIcon } from '../Icon/SearchIcon.js';
9
9
  import { TextInput } from '../TextInput/TextInput.js';
10
10
 
@@ -12,7 +12,7 @@ const defaultButtonProps = {
12
12
  label: React.createElement(SearchIcon, { alt: "Search", stroke: "currentColor", strokeWidth: 2 }),
13
13
  loadingText: "",
14
14
  };
15
- const SearchBox = ({ action, "aria-placeholder": ariaPlaceholder, autoCapitalize = "off", autoComplete = "off", button = defaultButtonProps, className, id, label, labelVisible = false, name = "query", onSubmit, placeholder, size = "md", variant = "green", ...props }) => {
15
+ const Searchbox = ({ action, "aria-placeholder": ariaPlaceholder, autoCapitalize = "off", autoComplete = "off", button = defaultButtonProps, className, id, label, labelVisible = false, name = "query", onSubmit, placeholder, size = "md", variant = "green", ...props }) => {
16
16
  var _a, _b;
17
17
  const [isPending, setIsPending] = useState(false);
18
18
  const internalId = useId();
@@ -31,8 +31,8 @@ const SearchBox = ({ action, "aria-placeholder": ariaPlaceholder, autoCapitalize
31
31
  }, [onSubmit, isPending]);
32
32
  const formProps = {
33
33
  action: action !== null && action !== void 0 ? action : undefined,
34
- className: clsx("coop-search-box", className),
35
- //"data-size": size && size !== "md" ? size : undefined,
34
+ className: clsx("coop-searchbox", className),
35
+ "data-size": size && size !== "md" ? size : undefined,
36
36
  "data-variant": variant.length && variant !== "green" ? variant : undefined,
37
37
  onSubmit: onSubmit ? handleSubmit : undefined,
38
38
  };
@@ -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(TextInput, { ...inputProps }), jsx(Button, { ...buttonProps, children: button.label })] })] }));
57
+ return (jsxs("form", { ...formProps, children: [label && (jsx(Label, { htmlFor: id, isVisible: labelVisible, children: label })), jsxs("div", { className: "coop-searchbox--inner", children: [jsx(TextInput, { ...inputProps }), jsx(Button, { ...buttonProps, children: button.label })] })] }));
58
58
  };
59
59
 
60
- export { SearchBox, SearchBox as default };
60
+ export { Searchbox, Searchbox as default };
@@ -0,0 +1,4 @@
1
+ import Searchbox from "./Searchbox";
2
+ export default Searchbox;
3
+ export { Searchbox };
4
+ export * from "./Searchbox";
@@ -1,28 +1,14 @@
1
1
  import type { InputHTMLAttributes, JSX } from "react";
2
- import { FormFieldError, StandardSizes } from "../../../src/types";
2
+ import { StandardSizes } from "../../../src/types";
3
3
  export interface TextInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "prefix" | "size" | "type"> {
4
4
  /** **(Optional)** Specify additional CSS classes to be applied to the component. */
5
5
  className?: string;
6
6
  /** **(Optional)** Specify whether the TextInput should be disabled. Refer to Experience Library guidance on disabled form controls and accessibility. */
7
7
  disabled?: boolean;
8
- /** **(Optional)** Specify the TextInput 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 TextInput hint.
14
- *
15
- * This text is rendered under the label to provide further guidance for users.
16
- */
17
- hint?: string;
8
+ /** **(Optional)** Specify the TextInput error state. */
9
+ error?: boolean;
18
10
  /** **(Optional)** Specify the TextInput id. Will be auto-generated if not set. */
19
11
  id?: string;
20
- /** **(Optional)** Specify the TextInput label.
21
- *
22
- * This property is optional in case you need to render your own label, but all form elements *must* provide a label. */
23
- label?: string;
24
- /** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
25
- labelVisible?: boolean;
26
12
  /** Specify the TextInput name. */
27
13
  name: string;
28
14
  /** **(Optional)** Specify the TextInput placeholder text. Do not use in place of a form label. */
@@ -34,7 +20,7 @@ export interface TextInputProps extends Omit<InputHTMLAttributes<HTMLInputElemen
34
20
  /** **(Optional)** Specify the suffix. It can be any valid JSX or string. */
35
21
  suffix?: React.ReactNode;
36
22
  /** **(Optional)** Specify the TextInput type. */
37
- type?: "text" | "email" | "number" | "password" | "search" | "tel" | "url";
23
+ type?: "text" | "email" | "number" | "password" | "search" | "tel" | "url" | "date" | "datetime-local" | "week" | "month" | "time";
38
24
  }
39
- export declare const TextInput: ({ "aria-placeholder": ariaPlaceholder, className, disabled, error, hint, id, label, labelVisible, name, placeholder, prefix, size, suffix, type, ...props }: TextInputProps) => JSX.Element;
25
+ export declare const TextInput: ({ "aria-placeholder": ariaPlaceholder, className, disabled, error, id, name, placeholder, prefix, size, suffix, type, ...props }: TextInputProps) => JSX.Element;
40
26
  export default TextInput;
@@ -1,28 +1,24 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import clsx from 'clsx';
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';
3
+ import { useId } from '../../hooks/useId.js';
7
4
 
8
- const TextInput = ({ "aria-placeholder": ariaPlaceholder, className, disabled, error = false, hint, id, label, labelVisible = true, name, placeholder, prefix, size = "md", suffix, type = "text", ...props }) => {
5
+ const TextInput = ({ "aria-placeholder": ariaPlaceholder, className, disabled, error = false, id, name, placeholder, prefix, size = "md", suffix, type = "text", ...props }) => {
9
6
  var _a;
10
- const internalId = useId();
11
- id = id !== null && id !== void 0 ? id : internalId;
7
+ const uid = useId(id);
12
8
  const componentProps = {
13
9
  "aria-placeholder": (_a = placeholder !== null && placeholder !== void 0 ? placeholder : ariaPlaceholder) !== null && _a !== void 0 ? _a : undefined,
14
10
  className: clsx("coop-text-input", className),
15
- "data-error": error ? "" : undefined,
11
+ "data-error": error || undefined,
16
12
  "data-size": size.length && size !== "md" ? size : undefined,
17
13
  disabled,
18
- id,
14
+ id: uid,
19
15
  name,
20
16
  placeholder,
21
17
  type,
22
18
  ...props,
23
19
  };
24
- const formItemProps = { "aria-disabled": disabled ? true : undefined };
25
- return (jsxs("div", { className: "coop-form-item", ...formItemProps, 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 })] })] }));
20
+ //const formItemProps = { "aria-disabled": disabled ? true : undefined }
21
+ return (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 })] }));
26
22
  };
27
23
 
28
24
  export { TextInput, TextInput as default };
@@ -1,5 +1,5 @@
1
1
  import type { JSX, TextareaHTMLAttributes } from "react";
2
- import { FormFieldError, StandardSizes } from "../../types";
2
+ import { StandardSizes } from "../../types";
3
3
  export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
4
4
  /** **(Optional)** Specify additional CSS classes to be applied to the component. */
5
5
  className?: string;
@@ -16,24 +16,10 @@ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElemen
16
16
  cutoff?: boolean;
17
17
  /** **(Optional)** Specify whether the Textarea should be disabled. Refer to Experience Library guidance on disabled form controls and accessibility. */
18
18
  disabled?: boolean;
19
- /** **(Optional)** Specify the Textarea error state.
20
- *
21
- * This is an instance of `FormFieldError`. You can provide either an object with a `message` key, or a boolean value if you need to render the message independently.
22
- */
23
- error?: FormFieldError;
24
- /** **(Optional)** Specify the Textarea hint.
25
- *
26
- * This text is rendered under the label to provide further guidance for users.
27
- */
28
- hint?: string;
19
+ /** **(Optional)** Specify the Textarea error state */
20
+ error?: boolean;
29
21
  /** **(Optional)** Specify the Textarea id. Will be auto-generated if not set. */
30
22
  id?: string;
31
- /** **(Optional)** Specify the Textarea label.
32
- *
33
- * This property is optional in case you need to render your own label, but all form elements *must* provide a label. */
34
- label?: string;
35
- /** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
36
- labelVisible?: boolean;
37
23
  /** **(Optional)** Specify the Textarea maxLength. This is the maximum number of characters users can enter. */
38
24
  maxLength?: number;
39
25
  /** Specify the Textarea name. */
@@ -45,5 +31,5 @@ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElemen
45
31
  /** **(Optional)** Specify the Textarea size. */
46
32
  size?: StandardSizes;
47
33
  }
48
- export declare const Textarea: ({ "aria-placeholder": ariaPlaceholder, className, cols, counter, cutoff, disabled, error, hint, id, label, labelVisible, maxLength, name, onChange: userOnChange, placeholder, rows, size, ...props }: TextareaProps) => JSX.Element;
34
+ export declare const Textarea: ({ "aria-placeholder": ariaPlaceholder, className, cols, counter, cutoff, disabled, error, id, maxLength, name, onChange: userOnChange, placeholder, rows, size, ...props }: TextareaProps) => JSX.Element;
49
35
  export default Textarea;
@@ -1,16 +1,14 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import clsx from 'clsx';
3
- import { useId, useState } from 'react';
3
+ import { useState } from 'react';
4
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';
5
+ import { useId } from '../../hooks/useId.js';
8
6
 
9
7
  const DEBOUNCE_DELAY = 750;
10
8
  const charCountMessage = (remaining) => {
11
9
  return `You have ${Math.abs(remaining).toLocaleString()} ${Math.abs(remaining) === 1 ? "character" : "characters"} ${remaining < 0 ? "too many" : "remaining"}`;
12
10
  };
13
- const Textarea = ({ "aria-placeholder": ariaPlaceholder, className, cols = 30, counter = false, cutoff = false, disabled = false, error = false, hint, id, label, labelVisible = true, maxLength, name, onChange: userOnChange = undefined, placeholder, rows = 4, size = "md", ...props }) => {
11
+ const Textarea = ({ "aria-placeholder": ariaPlaceholder, className, cols = 30, counter = false, cutoff = false, disabled = false, error = false, id, maxLength, name, onChange: userOnChange = undefined, placeholder, rows = 4, size = "md", ...props }) => {
14
12
  var _a;
15
13
  const internalId = useId();
16
14
  id = id !== null && id !== void 0 ? id : internalId;
@@ -18,7 +16,7 @@ const Textarea = ({ "aria-placeholder": ariaPlaceholder, className, cols = 30, c
18
16
  "aria-placeholder": (_a = placeholder !== null && placeholder !== void 0 ? placeholder : ariaPlaceholder) !== null && _a !== void 0 ? _a : undefined,
19
17
  className: clsx("coop-textarea", className),
20
18
  cols,
21
- "data-error": error ? "" : undefined,
19
+ "data-error": error !== null && error !== void 0 ? error : undefined,
22
20
  "data-size": size.length && size !== "md" ? size : undefined,
23
21
  disabled,
24
22
  id,
@@ -30,14 +28,15 @@ const Textarea = ({ "aria-placeholder": ariaPlaceholder, className, cols = 30, c
30
28
  };
31
29
  const [remaining, setRemaining] = useState(maxLength);
32
30
  const debouncedRemaining = useDebounce(remaining, DEBOUNCE_DELAY);
31
+ const showCounter = !disabled && counter && maxLength && remaining != null && debouncedRemaining != null;
33
32
  const handleChange = (e) => {
34
33
  maxLength && e.target && setRemaining(maxLength - e.target.value.length);
35
34
  };
36
- const formItemProps = { "aria-disabled": disabled ? true : undefined };
37
- return (jsxs("div", { className: "coop-form-item", ...formItemProps, 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) => {
35
+ // const formItemProps = { "aria-disabled": disabled ? true : undefined }
36
+ return (jsxs("div", { className: "coop-textarea-wrapper", children: [jsx("textarea", { ...componentProps, onChange: (e) => {
38
37
  userOnChange === null || userOnChange === void 0 ? void 0 : userOnChange(e);
39
38
  handleChange(e);
40
- } }), !disabled && 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
+ } }), showCounter && (jsxs(Fragment, { children: [jsx("small", { "aria-hidden": "true", className: "coop-textarea-counter", ...(remaining < 0 && { "data-error": true }), children: charCountMessage(remaining) }), jsx("span", { "aria-live": "polite", className: "sr-only", children: charCountMessage(debouncedRemaining) })] }))] }));
41
40
  };
42
41
 
43
42
  export { Textarea, Textarea as default };
@@ -0,0 +1,2 @@
1
+ export type UseId = (id?: string) => string;
2
+ export declare const useId: UseId;
@@ -0,0 +1,8 @@
1
+ import { useId as useId$1 } from 'react';
2
+
3
+ const useId = (id) => {
4
+ const uniqueId = useId$1();
5
+ return id !== null && id !== void 0 ? id : uniqueId;
6
+ };
7
+
8
+ export { useId };
@@ -2,5 +2,7 @@ import React from "react";
2
2
  type Slots<T> = Record<keyof T, React.ReactNode>;
3
3
  export declare function isKey<T extends object>(x: T, k: PropertyKey): k is keyof T;
4
4
  export declare function getSlotName(node: React.ReactNode): string | false;
5
- export declare function getSlots<T>(componentSlots: Slots<T>, children: React.ReactNode): Slots<T>;
5
+ export declare function useSlots<T>(componentSlots: Slots<T>, children: React.ReactNode, options?: {
6
+ collect?: string[];
7
+ }): Slots<T>;
6
8
  export {};
@@ -8,11 +8,17 @@ function getSlotName(node) {
8
8
  ? node.type.name
9
9
  : false;
10
10
  }
11
- function getSlots(componentSlots, children) {
11
+ function useSlots(componentSlots, children, options) {
12
12
  return React.Children.toArray(children).reduce((slots, child) => {
13
+ var _a;
13
14
  const slotName = getSlotName(child);
14
15
  if (child && slotName && isKey(componentSlots, slotName)) {
15
- slots[slotName] = child;
16
+ if ((_a = void 0 ) === null || _a === void 0 ? void 0 : _a.includes(slotName)) {
17
+ slots[slotName] = slots[slotName] ? [...[slots[slotName]].flat(), child] : [child];
18
+ }
19
+ else {
20
+ slots[slotName] = child;
21
+ }
16
22
  }
17
23
  else if ("Children" in slots) {
18
24
  slots.Children = slots.Children ? [...[slots.Children].flat(), child] : [child];
@@ -21,4 +27,4 @@ function getSlots(componentSlots, children) {
21
27
  }, { ...componentSlots });
22
28
  }
23
29
 
24
- export { getSlotName, getSlots, isKey };
30
+ export { getSlotName, isKey, useSlots };
package/dist/index.d.ts CHANGED
@@ -5,15 +5,13 @@ export * from "./components/Card";
5
5
  export * from "./components/Checkbox";
6
6
  export * from "./components/Expandable";
7
7
  export * from "./components/Field";
8
- export * from "./components/FieldError";
9
- export * from "./components/FieldHint";
10
- export * from "./components/FieldLabel";
8
+ export * from "./components/Fieldset";
11
9
  export * from "./components/Flourish";
12
10
  export * from "./components/Image";
13
11
  export * from "./components/Pill";
14
- export * from "./components/RadioButton";
12
+ export * from "./components/Radio";
15
13
  export * from "./components/RootSVG";
16
- export * from "./components/SearchBox";
14
+ export * from "./components/Searchbox";
17
15
  export * from "./components/Signpost";
18
16
  export * from "./components/SkipNav";
19
17
  export * from "./components/Squircle";
package/dist/index.js CHANGED
@@ -3,19 +3,15 @@ export { Author } from './components/Author/Author.js';
3
3
  export { Button } from './components/Button/Button.js';
4
4
  export { Card } from './components/Card/Card.js';
5
5
  export { Checkbox } from './components/Checkbox/Checkbox.js';
6
- export { CheckboxGroup } from './components/Checkbox/CheckboxGroup.js';
7
6
  export { Expandable } from './components/Expandable/Expandable.js';
8
- export { Field } from './components/Field/Field.js';
9
- export { FieldError } from './components/FieldError/FieldError.js';
10
- export { FieldHint } from './components/FieldHint/FieldHint.js';
11
- export { FieldLabel } from './components/FieldLabel/FieldLabel.js';
7
+ export { Field, FieldControl } from './components/Field/Field.js';
8
+ export { Fieldset, FieldsetFields } from './components/Fieldset/Fieldset.js';
12
9
  export { Flourish } from './components/Flourish/Flourish.js';
13
10
  export { Image } from './components/Image/Image.js';
14
11
  export { Pill } from './components/Pill/Pill.js';
15
- export { RadioButton } from './components/RadioButton/RadioButton.js';
16
- export { RadioButtonGroup } from './components/RadioButton/RadioButtonGroup.js';
12
+ export { Radio } from './components/Radio/Radio.js';
17
13
  export { RootSVG } from './components/RootSVG/RootSVG.js';
18
- export { SearchBox } from './components/SearchBox/SearchBox.js';
14
+ export { Searchbox } from './components/Searchbox/Searchbox.js';
19
15
  export { Signpost } from './components/Signpost/Signpost.js';
20
16
  export { SkipNav } from './components/SkipNav/SkipNav.js';
21
17
  export { Squircle } from './components/Squircle/Squircle.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coopdigital/react",
3
3
  "type": "module",
4
- "version": "0.44.0",
4
+ "version": "0.46.0",
5
5
  "private": false,
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -57,18 +57,18 @@
57
57
  "devDependencies": {
58
58
  "@axe-core/playwright": "^4.11.0",
59
59
  "@playwright/test": "^1.56.1",
60
- "@storybook/addon-a11y": "^10.0.5",
61
- "@storybook/addon-docs": "^10.0.5",
62
- "@storybook/addon-onboarding": "^10.0.5",
63
- "@storybook/react-vite": "^10.0.5",
60
+ "@storybook/addon-a11y": "^10.0.7",
61
+ "@storybook/addon-docs": "^10.0.7",
62
+ "@storybook/addon-onboarding": "^10.0.7",
63
+ "@storybook/react-vite": "^10.0.7",
64
64
  "@testing-library/jest-dom": "^6.9.1",
65
65
  "@testing-library/react": "^16.3.0",
66
- "@types/react": "^19.2.2",
67
- "@types/react-dom": "^19.2.2",
66
+ "@types/react": "^19.2.4",
67
+ "@types/react-dom": "^19.2.3",
68
68
  "react": "^19.2.0",
69
69
  "react-dom": "^19.2.0",
70
70
  "serve": "^14.2.5",
71
- "storybook": "^10.0.5"
71
+ "storybook": "^10.0.7"
72
72
  },
73
73
  "peerDependencies": {
74
74
  "react": "^19.1.0",
@@ -78,8 +78,8 @@
78
78
  "storybook": "$storybook"
79
79
  },
80
80
  "dependencies": {
81
- "@coopdigital/styles": "^0.38.0",
81
+ "@coopdigital/styles": "^0.40.0",
82
82
  "clsx": "^2.1.1"
83
83
  },
84
- "gitHead": "076bf8aa7441434b71bfc244c699b79397a8b0fd"
84
+ "gitHead": "9020e79195ead8270e9a2c39209142a02160bae5"
85
85
  }
@@ -1,11 +1,6 @@
1
1
  "use client"
2
2
 
3
- import type {
4
- AnchorHTMLAttributes,
5
- ButtonHTMLAttributes,
6
- ForwardRefExoticComponent,
7
- JSX,
8
- } from "react"
3
+ import type { ButtonHTMLAttributes, ForwardRefExoticComponent, JSX, Ref } from "react"
9
4
 
10
5
  import clsx from "clsx"
11
6
  import React, { useCallback, useState } from "react"
@@ -15,7 +10,7 @@ import { LoadingIcon } from "../Icon"
15
10
  export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
16
11
  /** **(Optional)** Specify a custom element to override default `a` or `button`. */
17
12
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
- as?: React.FC<AnchorHTMLAttributes<HTMLElement>> | ForwardRefExoticComponent<any> | string
13
+ as?: React.FC<any> | ForwardRefExoticComponent<any> | string
19
14
  /** **(Optional)** Main content inside the component. It can be any valid JSX or string. */
20
15
  children?: React.ReactNode
21
16
  /** **(Optional)** Specify additional CSS classes to be applied to the component. */
@@ -31,7 +26,9 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
31
26
  /** **(Optional)** Specify text to show when the Button is in a loading state. */
32
27
  loadingText?: string
33
28
  /** **(Optional)** Callback to run when the button is pressed. If this is an async function, it will be awaited and the button will be in a pending state until the promise is resolved. */
34
- onClick?: (event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void
29
+ onClick?: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>
30
+ /** **(Optional)** Specify a custom React ref for this component. */
31
+ ref?: Ref<HTMLButtonElement | HTMLAnchorElement>
35
32
  /** **(Optional)** Specify the Button size. */
36
33
  size?: "sm" | "md" | "lg"
37
34
  /** **(Optional)** Specify the Button variant. */
@@ -61,14 +58,12 @@ export const Button = ({
61
58
  isLoading = false,
62
59
  loadingText = "Loading",
63
60
  onClick,
61
+ ref,
64
62
  size = "md",
65
63
  variant = "green",
66
64
  ...props
67
65
  }: ButtonProps): JSX.Element => {
68
- let element: ButtonProps["as"] = href ? "a" : "button"
69
- if (as) {
70
- element = as
71
- }
66
+ const element: ButtonProps["as"] = as ?? (href ? "a" : "button")
72
67
 
73
68
  const [isPending, setIsPending] = useState(false)
74
69
 
@@ -79,7 +74,7 @@ export const Button = ({
79
74
  setIsPending(true)
80
75
 
81
76
  try {
82
- await Promise.resolve(onClick(event as React.MouseEvent<HTMLButtonElement>))
77
+ await Promise.resolve(onClick(event))
83
78
  } finally {
84
79
  setIsPending(false)
85
80
  }
@@ -96,7 +91,8 @@ export const Button = ({
96
91
  "data-variant": variant !== "text" ? variant : undefined,
97
92
  "data-width": isFullWidth ? "full" : undefined,
98
93
  href,
99
- onClick: handleClick,
94
+ onClick: onClick ? handleClick : undefined,
95
+ ref,
100
96
  ...props,
101
97
  }
102
98
 
@@ -3,7 +3,7 @@ import type { ForwardRefExoticComponent, HTMLAttributes, JSX } from "react"
3
3
  import clsx from "clsx"
4
4
  import React from "react"
5
5
 
6
- import { getSlots } from "../../utils/slots"
6
+ import { useSlots } from "../../hooks/useSlots"
7
7
  import { ChevronRightIcon } from "../Icon/ChevronRightIcon"
8
8
  import { Image, ImageProps } from "../Image"
9
9
 
@@ -91,8 +91,7 @@ export const Card = ({
91
91
  }: CardProps): JSX.Element => {
92
92
  const { element: linkElement, props: linkProps } = getCardLinkElement(hrefAs, href)
93
93
 
94
- const slots = getSlots(componentSlots, children)
95
-
94
+ const slots = { ...useSlots(componentSlots, children) }
96
95
  const innerProps = { className: "coop-card--inner" }
97
96
  const hasLinkWrapper = href && !slots.CardHeading
98
97
 
@@ -1,13 +1,9 @@
1
1
  import type { InputHTMLAttributes, JSX } from "react"
2
2
 
3
3
  import clsx from "clsx"
4
- import { useId } from "react"
5
4
 
6
- import { FormFieldError, StandardSizes } from "../../../src/types"
7
- import { FieldError } from "../FieldError"
8
- import { FieldHint } from "../FieldHint"
9
- import { FieldLabel } from "../FieldLabel"
10
- import Tag from "../Tag"
5
+ import { StandardSizes } from "../../../src/types"
6
+ import { useId } from "../../hooks/useId"
11
7
 
12
8
  export interface CheckboxProps
13
9
  extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
@@ -15,89 +11,37 @@ export interface CheckboxProps
15
11
  className?: string
16
12
  /** **(Optional)** Specify whether the Checkbox should be disabled. Refer to Experience Library guidance on disabled form controls and accessibility. */
17
13
  disabled?: boolean
18
- /** **(Optional)** Specify the Checkbox error state.
19
- *
20
- * This is an instance of `FormFieldError`. You can provide either an object with a `message` key, or a boolean value if you need to render the message independently.
21
- */
22
- error?: FormFieldError
23
- /** **(Optional)** Specify the Checkbox hint.
24
- *
25
- * This text is rendered under the label to provide further guidance for users.
26
- */
27
- hint?: string
14
+ /** **(Optional)** Specify the Checkbox error state. */
15
+ error?: boolean
28
16
  /** **(Optional)** Specify the Checkbox id. Will be auto-generated if not set. */
29
17
  id?: string
30
- /** **(Optional)** Specify the Checkbox label.
31
- *
32
- * This property is optional in case you need to render your own label, but all form elements *must* provide a label. */
33
- label: string
34
- /** **(Optional)** Specify whether the label should be visible to humans or screen readers. */
35
- labelVisible?: boolean
36
18
  /** Specify the Checkbox name. */
37
19
  name: string
38
20
  /** **(Optional)** Specify the Checkbox size. */
39
21
  size?: StandardSizes
40
- /** **(Optional)** Specify the Checkbox tag text. */
41
- tag?: string
42
- /** **(Optional)** Specify the Checkbox tag className. */
43
- tagClassName?: string
44
- /** **(Optional)** Specify the Checkbox variant. */
45
- variant?: "default" | "boxed"
46
22
  }
47
23
 
48
24
  export const Checkbox = ({
49
25
  className,
50
26
  disabled,
51
27
  error = false,
52
- hint,
53
28
  id,
54
- label,
55
- labelVisible = true,
56
29
  name,
57
30
  size = "md",
58
- tag,
59
- tagClassName,
60
- variant = "default",
61
31
  ...props
62
32
  }: CheckboxProps): JSX.Element => {
63
- const internalId = useId()
64
-
65
- id = id ?? internalId
66
-
33
+ const uid = useId(id)
67
34
  const componentProps = {
68
35
  className: clsx("coop-checkbox", className),
69
- "data-error": error ? "" : undefined,
36
+ "data-error": error || undefined,
70
37
  "data-size": size.length && size !== "md" ? size : undefined,
71
- "data-variant": variant !== "default" ? variant : undefined,
72
38
  disabled,
73
- id,
39
+ id: uid,
74
40
  name,
75
41
  type: "checkbox",
76
42
  ...props,
77
43
  }
78
- const formItemProps = { "aria-disabled": disabled ? true : undefined }
79
- return (
80
- <div className="coop-form-item" {...formItemProps}>
81
- <div className="coop-checkbox-wrapper">
82
- <div className="coop-checkbox-input-wrapper">
83
- <input {...componentProps} />
84
-
85
- {label && (
86
- <FieldLabel htmlFor={id} isVisible={labelVisible}>
87
- <span>{label}</span>{" "}
88
- {tag && (
89
- <Tag className={tagClassName} size="sm">
90
- {tag}
91
- </Tag>
92
- )}
93
- </FieldLabel>
94
- )}
95
- </div>
96
- {hint && <FieldHint>{hint}</FieldHint>}
97
- </div>
98
- {typeof error === "object" && error?.message && <FieldError>{error.message}</FieldError>}
99
- </div>
100
- )
44
+ return <input {...componentProps} />
101
45
  }
102
46
 
103
47
  export default Checkbox
@@ -1,6 +1,5 @@
1
1
  import Checkbox from "./Checkbox"
2
- import CheckboxGroup from "./CheckboxGroup"
3
2
 
4
3
  export default Checkbox
5
- export { Checkbox, CheckboxGroup }
4
+ export { Checkbox }
6
5
  export * from "./Checkbox"
@@ -2,8 +2,8 @@ import type { DetailsHTMLAttributes, HTMLAttributes, JSX } from "react"
2
2
 
3
3
  import { clsx } from "clsx"
4
4
 
5
+ import { useSlots } from "../../hooks/useSlots"
5
6
  import { bgClassToColor, hasUserBg } from "../../utils"
6
- import { getSlots } from "../../utils/slots"
7
7
  import { ChevronDownIcon } from "../Icon"
8
8
 
9
9
  export interface ExpandableProps extends DetailsHTMLAttributes<HTMLDetailsElement> {
@@ -26,7 +26,7 @@ const componentSlots = {
26
26
  }
27
27
 
28
28
  export const Expandable = ({ children, className, ...props }: ExpandableProps): JSX.Element => {
29
- const slots = getSlots(componentSlots, children)
29
+ const slots = useSlots(componentSlots, children)
30
30
 
31
31
  const componentProps = {
32
32
  className: clsx("coop-expandable", !hasUserBg(className) && "bg-tint-grey", className),