@coopdigital/react 1.22.5 → 1.24.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 (33) hide show
  1. package/dist/components/Combobox/Combobox.context.js +11 -0
  2. package/dist/components/Combobox/Combobox.d.ts +124 -0
  3. package/dist/components/Combobox/Combobox.js +166 -0
  4. package/dist/components/Combobox/combobox-utils.d.ts +20 -0
  5. package/dist/components/Combobox/combobox-utils.js +10 -0
  6. package/dist/components/Combobox/index.d.ts +2 -0
  7. package/dist/components/Combobox/index.js +5 -0
  8. package/dist/components/DatePicker/DatePicker.d.ts +3 -6
  9. package/dist/components/DatePicker/DatePicker.js +1 -1
  10. package/dist/components/DatePicker/datepicker-utils.d.ts +6 -0
  11. package/dist/components/DatePicker/index.d.ts +2 -1
  12. package/dist/components/Section/Section.d.ts +18 -0
  13. package/dist/components/Section/Section.js +16 -0
  14. package/dist/components/Section/index.d.ts +2 -0
  15. package/dist/components/Section/index.js +5 -0
  16. package/dist/components/Wrapper/Wrapper.d.ts +18 -0
  17. package/dist/components/Wrapper/Wrapper.js +16 -0
  18. package/dist/components/Wrapper/index.d.ts +2 -0
  19. package/dist/components/Wrapper/index.js +5 -0
  20. package/dist/index.d.ts +9 -2
  21. package/dist/index.js +5 -2
  22. package/package.json +9 -4
  23. package/src/components/Combobox/Combobox.context.ts +27 -0
  24. package/src/components/Combobox/Combobox.tsx +323 -0
  25. package/src/components/Combobox/combobox-utils.ts +39 -0
  26. package/src/components/Combobox/index.ts +5 -0
  27. package/src/components/DatePicker/DatePicker.tsx +4 -3
  28. package/src/components/DatePicker/datepicker-utils.ts +1 -0
  29. package/src/components/Section/Section.tsx +31 -0
  30. package/src/components/Section/index.ts +5 -0
  31. package/src/components/Wrapper/Wrapper.tsx +32 -0
  32. package/src/components/Wrapper/index.ts +5 -0
  33. package/src/index.ts +3 -0
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import { createContext, useContext } from "react";
3
+ //#region src/components/Combobox/Combobox.context.ts
4
+ const ComboboxContext = createContext({});
5
+ const useComboboxContext = () => {
6
+ const context = useContext(ComboboxContext);
7
+ if (!context) throw new Error("Combobox components must be used inside <Combobox>");
8
+ return context;
9
+ };
10
+ //#endregion
11
+ export { ComboboxContext, useComboboxContext };
@@ -0,0 +1,124 @@
1
+ import { ComboboxItem, ComboboxOption } from "./combobox-utils.js";
2
+ import { TextInputProps } from "../TextInput/TextInput.js";
3
+ import "../TextInput/index.js";
4
+ import { ButtonHTMLAttributes, HTMLAttributes, JSX, ReactNode, Ref } from "react";
5
+ import { UseComboboxHighlightedIndexChange, UseComboboxInputValueChange, UseComboboxIsOpenChange, UseComboboxSelectedItemChange, UseComboboxStateChange } from "downshift";
6
+ //#region src/components/Combobox/Combobox.d.ts
7
+ export interface ComboboxProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "onSelect" | "onToggle"> {
8
+ /** Specify whether custom values are accepted.
9
+ *
10
+ * Default: `false`
11
+ */
12
+ allowCustom?: boolean;
13
+ /** Content inside the component.
14
+ *
15
+ * This should be a set of `<Combobox.Controls>` and `<Combobox.Options>`.
16
+ */
17
+ children?: string | ReactNode;
18
+ /** Specify additional CSS classes. */
19
+ className?: string;
20
+ /** Specify the disabled state. */
21
+ disabled?: boolean;
22
+ /** Specify the component error state. */
23
+ error?: boolean;
24
+ /** Specify the component id. Will be auto-generated if not set. */
25
+ id?: string;
26
+ /** Custom function to filter the list of options.
27
+ *
28
+ * Takes a `ComboboxItem` array and an input string, and returns a filtered array.
29
+ *
30
+ * Default: Filters on item label or value: `item.label ?? item.value`.
31
+ */
32
+ inputFilter?: (items: ComboboxItem[], value: string) => ComboboxItem[];
33
+ /** Custom function to mark item as disabled.
34
+ *
35
+ * Default: `disabled` prop on `.ComboboxOption`
36
+ */
37
+ isItemDisabled?: (item: ComboboxItem) => boolean;
38
+ /** Custom function to render text in the input field after selecting a value.
39
+ *
40
+ * Default: Uses item label or value: `item.label ?? item.value`.
41
+ */
42
+ itemToString?: (item: ComboboxItem | null) => string;
43
+ /** Callback fired when the input value changes.
44
+ *
45
+ * This is a `downshift` event.
46
+ */
47
+ onChange?: (event: UseComboboxInputValueChange<ComboboxItem>) => void;
48
+ /** Callback fired when users highlight an option in the list using the mouse or keyboard.
49
+ *
50
+ * This is a `downshift` event.
51
+ */
52
+ onHighlight?: (event: UseComboboxHighlightedIndexChange<ComboboxItem>) => void;
53
+ /** Callback fired when users select an option from the list.
54
+ *
55
+ * This is a `downshift` event.
56
+ */
57
+ onSelect?: (event: UseComboboxSelectedItemChange<ComboboxItem>) => void;
58
+ /** Callback fired whenever the state changes.
59
+ *
60
+ * This is a `downshift` event.
61
+ */
62
+ onStateChange?: (event: UseComboboxStateChange<ComboboxItem>) => void;
63
+ /** Callback fired when the menu opens or closes.
64
+ *
65
+ * This is a `downshift` event.
66
+ */
67
+ onToggle?: (changes: UseComboboxIsOpenChange<ComboboxItem>) => void;
68
+ /** Specify whether the menu should appear immediately when clicking the input field.
69
+ *
70
+ * Default: `true`.
71
+ */
72
+ openOnClick?: boolean;
73
+ /** Specify placeholder text. Do not use in place of a form label. */
74
+ placeholder?: string;
75
+ /** Specify a custom React ref for this component. */
76
+ ref?: Ref<HTMLDivElement>;
77
+ /** Specify the component size. */
78
+ size?: "sm" | "md" | "lg";
79
+ }
80
+ export interface ComboboxControlsProps extends HTMLAttributes<HTMLDivElement> {
81
+ /** Content inside the component.
82
+ *
83
+ * This should be a set of `<Combobox.Input>` and `<Combobox.Button>`.
84
+ */
85
+ children?: React.ReactNode;
86
+ /** Specify additional CSS classes. */
87
+ className?: string;
88
+ }
89
+ export interface ComboboxOptionsProps extends HTMLAttributes<HTMLUListElement> {
90
+ /** Specify additional CSS classes. */
91
+ className?: string;
92
+ /** Specify text to display when there are no matching options. */
93
+ noResultsText?: string;
94
+ /** Specify array of options to choose from.
95
+ *
96
+ * Can be either a simple string array or a complex `ComboboxOption` array.
97
+ */
98
+ options: ComboboxOption[];
99
+ }
100
+ export type ComboboxInputProps = TextInputProps;
101
+ export declare const Combobox: {
102
+ ({ allowCustom, children, className, disabled, error, id, inputFilter, isItemDisabled, itemToString, onChange, onHighlight, onSelect, onStateChange, onToggle, openOnClick, ref, size, ...props }: ComboboxProps): JSX.Element | null;
103
+ slot: {
104
+ field: boolean;
105
+ name: string;
106
+ suffix: string;
107
+ };
108
+ Controls: {
109
+ ({ children, className, ...props }: ComboboxControlsProps): JSX.Element;
110
+ slot: {
111
+ name: string;
112
+ };
113
+ };
114
+ Input: ({ className, placeholder, ...props }: ComboboxInputProps) => JSX.Element;
115
+ Button: ({ children, className, ...props }: ButtonHTMLAttributes<HTMLButtonElement>) => JSX.Element;
116
+ Options: {
117
+ ({ className, noResultsText, options, ...props }: ComboboxOptionsProps): JSX.Element | null;
118
+ slot: {
119
+ name: string;
120
+ };
121
+ };
122
+ };
123
+ //#endregion
124
+ export { Combobox as default };
@@ -0,0 +1,166 @@
1
+ "use client";
2
+ import { ChevronDownIcon } from "../icons/ChevronDownIcon.js";
3
+ import { getSlotProp } from "../../utils/index.js";
4
+ import { useSlots } from "../../hooks/useSlots.js";
5
+ import { useField } from "../Field/Field.context.js";
6
+ import { defaultFilter, defaultIsItemDisabled, defaultItemToString, normalizeOptions } from "./combobox-utils.js";
7
+ import { ComboboxContext, useComboboxContext } from "./Combobox.context.js";
8
+ import { TextInput } from "../TextInput/TextInput.js";
9
+ import clsx from "clsx";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ import { useState } from "react";
12
+ import { useCombobox } from "downshift";
13
+ //#region src/components/Combobox/Combobox.tsx
14
+ const componentSlots = {
15
+ ComboboxControls: null,
16
+ ComboboxOptions: null
17
+ };
18
+ const Combobox = ({ allowCustom = false, children, className, disabled, error, id, inputFilter, isItemDisabled, itemToString, onChange, onHighlight, onSelect, onStateChange, onToggle, openOnClick = true, ref, size, ...props }) => {
19
+ const field = useField();
20
+ const uid = id ?? field?.id;
21
+ const slots = useSlots(componentSlots, children);
22
+ const userOptions = getSlotProp(slots.ComboboxOptions, "options") ?? [];
23
+ const items = normalizeOptions(userOptions);
24
+ const [inputItems, setInputItems] = useState(items);
25
+ const componentProps = {
26
+ className: clsx("coop-combobox", className),
27
+ "data-error": error ?? void 0,
28
+ "data-size": size ?? void 0,
29
+ id: uid,
30
+ ...props
31
+ };
32
+ const inputFilterActual = inputFilter ?? defaultFilter;
33
+ const itemToStringActual = itemToString ?? defaultItemToString;
34
+ const comboboxProps = useCombobox({
35
+ id: uid,
36
+ inputId: uid + "_input",
37
+ isItemDisabled: isItemDisabled ?? defaultIsItemDisabled,
38
+ items: inputItems,
39
+ itemToString: itemToStringActual,
40
+ labelId: uid + "_label",
41
+ menuId: uid + "_menu",
42
+ onHighlightedIndexChange: onHighlight,
43
+ onInputValueChange: (event) => {
44
+ setInputItems(inputFilterActual(items, event.inputValue));
45
+ onChange?.(event);
46
+ },
47
+ onIsOpenChange: onToggle,
48
+ onSelectedItemChange: (event) => {
49
+ onSelect?.(event);
50
+ },
51
+ onStateChange,
52
+ stateReducer(_, actionAndChanges) {
53
+ const { InputBlur, InputClick, InputKeyDownEnter } = useCombobox.stateChangeTypes;
54
+ const { changes: state, type } = actionAndChanges;
55
+ const currentInput = state.inputValue ?? "";
56
+ const hasMatch = items.some((item) => itemToStringActual(item) === currentInput);
57
+ switch (type) {
58
+ case openOnClick === false && InputClick: return {
59
+ ...state,
60
+ isOpen: false
61
+ };
62
+ case allowCustom === false && InputBlur: return hasMatch ? state : {
63
+ ...state,
64
+ inputValue: ""
65
+ };
66
+ case allowCustom === false && InputKeyDownEnter: return hasMatch ? state : {
67
+ ...state,
68
+ isOpen: true
69
+ };
70
+ default: return state;
71
+ }
72
+ }
73
+ });
74
+ return /* @__PURE__ */ jsx(ComboboxContext.Provider, {
75
+ value: {
76
+ disabled,
77
+ items: inputItems,
78
+ props: comboboxProps,
79
+ size
80
+ },
81
+ children: /* @__PURE__ */ jsxs("div", {
82
+ ...componentProps,
83
+ ref,
84
+ children: [slots.ComboboxControls, slots.ComboboxOptions]
85
+ })
86
+ });
87
+ };
88
+ const ComboboxInput = ({ className, placeholder, ...props }) => {
89
+ const combobox = useComboboxContext();
90
+ const componentProps = {
91
+ className,
92
+ disabled: combobox.disabled,
93
+ placeholder,
94
+ size: combobox.size,
95
+ ...props
96
+ };
97
+ return /* @__PURE__ */ jsx(TextInput, {
98
+ ...componentProps,
99
+ ...combobox.props?.getInputProps({})
100
+ });
101
+ };
102
+ const ComboboxButton = ({ children, className, ...props }) => {
103
+ const combobox = useComboboxContext();
104
+ const componentProps = {
105
+ "aria-label": props["aria-label"] ?? "Toggle menu",
106
+ className,
107
+ disabled: combobox.disabled,
108
+ ...props
109
+ };
110
+ return /* @__PURE__ */ jsx("button", {
111
+ type: "button",
112
+ ...componentProps,
113
+ ...combobox.props?.getToggleButtonProps(),
114
+ children: children ?? /* @__PURE__ */ jsx(ChevronDownIcon, {
115
+ stroke: "black",
116
+ strokeWidth: 1,
117
+ width: "1.25rem"
118
+ })
119
+ });
120
+ };
121
+ const ComboboxControls = ({ children, className, ...props }) => {
122
+ const componentProps = {
123
+ className: clsx("coop-combobox-controls", className),
124
+ ...props
125
+ };
126
+ return /* @__PURE__ */ jsx("div", {
127
+ ...componentProps,
128
+ children
129
+ });
130
+ };
131
+ const ComboboxOptions = ({ className, noResultsText = "No results", options, ...props }) => {
132
+ const combobox = useComboboxContext();
133
+ const componentProps = {
134
+ className: clsx("coop-combobox-options", className),
135
+ ...props
136
+ };
137
+ if (!options) return null;
138
+ return /* @__PURE__ */ jsxs("ul", {
139
+ ...componentProps,
140
+ ...combobox.props?.getMenuProps(),
141
+ children: [combobox.props?.isOpen && combobox.items?.length ? combobox.items.map((item, index) => {
142
+ const itemProps = { ...combobox.props?.getItemProps({
143
+ index,
144
+ item
145
+ }) };
146
+ return /* @__PURE__ */ jsxs("li", {
147
+ ...itemProps,
148
+ ...item,
149
+ children: [item?.icon, item.children ?? item.label ?? item.value]
150
+ }, item.value);
151
+ }) : null, combobox.props?.isOpen && !combobox.items?.length && /* @__PURE__ */ jsx("li", { children: noResultsText })]
152
+ });
153
+ };
154
+ Combobox.slot = {
155
+ field: true,
156
+ name: "Combobox",
157
+ suffix: "_input"
158
+ };
159
+ ComboboxControls.slot = { name: "ComboboxControls" };
160
+ ComboboxOptions.slot = { name: "ComboboxOptions" };
161
+ Combobox.Controls = ComboboxControls;
162
+ Combobox.Input = ComboboxInput;
163
+ Combobox.Button = ComboboxButton;
164
+ Combobox.Options = ComboboxOptions;
165
+ //#endregion
166
+ export { Combobox, Combobox as default };
@@ -0,0 +1,20 @@
1
+ import { HTMLAttributes } from "react";
2
+ //#region src/components/Combobox/combobox-utils.d.ts
3
+ export interface ComboboxItem extends HTMLAttributes<HTMLLIElement> {
4
+ /** Specify the disabled state.
5
+ *
6
+ * Options that are disabled still appear in the list but cannot be selected.
7
+ */
8
+ disabled?: boolean;
9
+ /** Specify an icon to render before the item text. */
10
+ icon?: React.ReactNode;
11
+ /** Specify text to display to users if the underlying value needs to be different.
12
+ *
13
+ * If this value is provided, this is the string that gets filtered when the user types.
14
+ */
15
+ label?: string;
16
+ /** Specify the option value. */
17
+ value: string;
18
+ }
19
+ export type ComboboxOption = ComboboxItem | string;
20
+ //#endregion
@@ -0,0 +1,10 @@
1
+ import { matchSorter } from "match-sorter";
2
+ //#region src/components/Combobox/combobox-utils.ts
3
+ const defaultFilter = (items, inputValue) => {
4
+ return inputValue !== "" ? matchSorter(items, inputValue, { keys: [(item) => [item.label ?? item.value]] }) : items;
5
+ };
6
+ const defaultIsItemDisabled = (item) => item.disabled ?? false;
7
+ const defaultItemToString = (item) => item?.label ?? item?.value ?? "";
8
+ const normalizeOptions = (options) => options.filter((option) => option && typeof option !== "number").map((option) => typeof option === "string" ? { value: option } : option);
9
+ //#endregion
10
+ export { defaultFilter, defaultIsItemDisabled, defaultItemToString, normalizeOptions };
@@ -0,0 +1,2 @@
1
+ import { Combobox, ComboboxControlsProps, ComboboxInputProps, ComboboxOptionsProps, ComboboxProps } from "./Combobox.js";
2
+ export { Combobox, Combobox as default, ComboboxControlsProps, ComboboxInputProps, ComboboxOptionsProps, ComboboxProps };
@@ -0,0 +1,5 @@
1
+ import { Combobox } from "./Combobox.js";
2
+ //#region src/components/Combobox/index.ts
3
+ var Combobox_default = Combobox;
4
+ //#endregion
5
+ export { Combobox, Combobox_default as default };
@@ -1,12 +1,9 @@
1
1
  import { StandardSizes } from "../../types/index.js";
2
- import { DateRange } from "./datepicker-utils.js";
2
+ import { DateRange, DateType, DateTypeStr } from "./datepicker-utils.js";
3
3
  import { InputHTMLAttributes, JSX, Ref } from "react";
4
4
  import { DayPickerProps, Mode } from "react-day-picker";
5
5
  //#region src/components/DatePicker/DatePicker.d.ts
6
- type OnDateSelectHandler = (selected: string | string[] | {
7
- from?: string;
8
- to?: string;
9
- } | undefined) => void;
6
+ type OnDateSelectHandler = (selected: DateTypeStr) => void;
10
7
  export interface DatePickerProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "onSelect" | "onChange" | "onChangeCapture" | "size" | "type"> {
11
8
  /** Specify additional CSS classes. */
12
9
  className?: string;
@@ -65,4 +62,4 @@ declare const DatePicker: {
65
62
  };
66
63
  };
67
64
  //#endregion
68
- export { DatePicker, DatePicker as default };
65
+ export { DatePicker, DatePicker as default, type DateType, type DateTypeStr };
@@ -4,8 +4,8 @@ import { CrossIcon } from "../icons/CrossIcon.js";
4
4
  import { TickIcon } from "../icons/TickIcon.js";
5
5
  import { Button } from "../Button/Button.js";
6
6
  import { useField } from "../Field/Field.context.js";
7
- import { Popover } from "../Popover/Popover.js";
8
7
  import { TextInput } from "../TextInput/TextInput.js";
8
+ import { Popover } from "../Popover/Popover.js";
9
9
  import { getDateValues, getDefaultMonth, getEndDate, getInitialDate, getStartDate, shouldClose, shouldSkipParsing } from "./datepicker-utils.js";
10
10
  import clsx from "clsx";
11
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -1,7 +1,13 @@
1
1
  import { Mode } from "react-day-picker";
2
2
  //#region src/components/DatePicker/datepicker-utils.d.ts
3
+ export interface DateRangeStr {
4
+ from?: string;
5
+ to?: string;
6
+ }
3
7
  export interface DateRange {
4
8
  from: Date | undefined;
5
9
  to?: Date | undefined;
6
10
  }
11
+ export type DateType = Date | Date[] | DateRange | string | undefined;
12
+ export type DateTypeStr = string | string[] | DateRangeStr | undefined;
7
13
  //#endregion
@@ -1,2 +1,3 @@
1
+ import { DateType, DateTypeStr } from "./datepicker-utils.js";
1
2
  import { DatePicker, DatePickerProps } from "./DatePicker.js";
2
- export { DatePicker, DatePicker as default, DatePickerProps };
3
+ export { DatePicker, DatePicker as default, DatePickerProps, type DateType, type DateTypeStr };
@@ -0,0 +1,18 @@
1
+ import React, { HTMLAttributes, JSX, ReactNode, Ref } from "react";
2
+ //#region src/components/Section/Section.d.ts
3
+ export interface SectionProps extends HTMLAttributes<HTMLDivElement> {
4
+ /** Specify a custom element for the component
5
+ *
6
+ * Default: `<section>`
7
+ */
8
+ as?: React.FC<any> | string;
9
+ /** **(Optional)** Content inside the component. */
10
+ children?: ReactNode;
11
+ /** **(Optional)** Specify additional CSS classes. */
12
+ className?: string;
13
+ /** **(Optional)** Specify a custom React ref for this component. */
14
+ ref?: Ref<HTMLDivElement>;
15
+ }
16
+ export declare const Section: ({ as, children, className, ref, ...props }: SectionProps) => JSX.Element;
17
+ //#endregion
18
+ export { Section as default };
@@ -0,0 +1,16 @@
1
+ import clsx from "clsx";
2
+ import React from "react";
3
+ //#region src/components/Section/Section.tsx
4
+ const Section = ({ as, children, className, ref, ...props }) => {
5
+ const componentProps = {
6
+ className: clsx("coop-section", className),
7
+ ...props
8
+ };
9
+ const element = as ?? "section";
10
+ return React.createElement(element, {
11
+ ...componentProps,
12
+ ref
13
+ }, children);
14
+ };
15
+ //#endregion
16
+ export { Section, Section as default };
@@ -0,0 +1,2 @@
1
+ import { Section, SectionProps } from "./Section.js";
2
+ export { Section, Section as default, SectionProps };
@@ -0,0 +1,5 @@
1
+ import { Section } from "./Section.js";
2
+ //#region src/components/Section/index.ts
3
+ var Section_default = Section;
4
+ //#endregion
5
+ export { Section, Section_default as default };
@@ -0,0 +1,18 @@
1
+ import React, { HTMLAttributes, JSX, ReactNode, Ref } from "react";
2
+ //#region src/components/Wrapper/Wrapper.d.ts
3
+ export interface WrapperProps extends HTMLAttributes<HTMLDivElement> {
4
+ /** Specify a custom element for the component
5
+ *
6
+ * Default: `<div>`
7
+ */
8
+ as?: React.FC<any> | string;
9
+ /** **(Optional)** Content inside the component. */
10
+ children?: ReactNode;
11
+ /** **(Optional)** Specify additional CSS classes. */
12
+ className?: string;
13
+ /** **(Optional)** Specify a custom React ref for this component. */
14
+ ref?: Ref<HTMLDivElement>;
15
+ }
16
+ export declare const Wrapper: ({ as, children, className, ref, ...props }: WrapperProps) => JSX.Element;
17
+ //#endregion
18
+ export { Wrapper as default };
@@ -0,0 +1,16 @@
1
+ import clsx from "clsx";
2
+ import React from "react";
3
+ //#region src/components/Wrapper/Wrapper.tsx
4
+ const Wrapper = ({ as, children, className, ref, ...props }) => {
5
+ const componentProps = {
6
+ className: clsx("coop-wrap", className),
7
+ ...props
8
+ };
9
+ const element = as ?? "div";
10
+ return React.createElement(element, {
11
+ ...componentProps,
12
+ ref
13
+ }, children);
14
+ };
15
+ //#endregion
16
+ export { Wrapper, Wrapper as default };
@@ -0,0 +1,2 @@
1
+ import { Wrapper, WrapperProps } from "./Wrapper.js";
2
+ export { Wrapper, Wrapper as default, WrapperProps };
@@ -0,0 +1,5 @@
1
+ import { Wrapper } from "./Wrapper.js";
2
+ //#region src/components/Wrapper/index.ts
3
+ var Wrapper_default = Wrapper;
4
+ //#endregion
5
+ export { Wrapper, Wrapper_default as default };
package/dist/index.d.ts CHANGED
@@ -8,6 +8,10 @@ import { Picture, PictureProps } from "./components/Picture/Picture.js";
8
8
  import "./components/Picture/index.js";
9
9
  import { Card, CardBadge, CardContent, CardHeading, CardIcon, CardImage, CardLink, CardPicture, CardProps } from "./components/Card/Card.js";
10
10
  import { Checkbox, CheckboxProps } from "./components/Checkbox/Checkbox.js";
11
+ import { TextInput, TextInputProps } from "./components/TextInput/TextInput.js";
12
+ import { Combobox, ComboboxControlsProps, ComboboxInputProps, ComboboxOptionsProps, ComboboxProps } from "./components/Combobox/Combobox.js";
13
+ import "./components/Combobox/index.js";
14
+ import { DateType, DateTypeStr } from "./components/DatePicker/datepicker-utils.js";
11
15
  import { DatePicker, DatePickerProps } from "./components/DatePicker/DatePicker.js";
12
16
  import { Dialog, DialogBody, DialogHeader, DialogProps } from "./components/Dialog/Dialog.js";
13
17
  import { Expandable, ExpandableContent, ExpandableProps, ExpandableSummary } from "./components/Expandable/Expandable.js";
@@ -17,13 +21,14 @@ import { Flourish, FlourishProps } from "./components/Flourish/Flourish.js";
17
21
  import { Form, FormProps } from "./components/Form/Form.js";
18
22
  import { LoadingSpinner, LoadingSpinnerProps } from "./components/LoadingSpinner/LoadingSpinner.js";
19
23
  import { Logo, LogoProps } from "./components/Logo/Logo.js";
20
- import { TextInput, TextInputProps } from "./components/TextInput/TextInput.js";
21
24
  import { PasswordInput, PasswordInputProps } from "./components/PasswordInput/PasswordInput.js";
22
25
  import { Pill, PillBadge, PillProps } from "./components/Pill/Pill.js";
23
26
  import { Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverProps, PopoverTrigger } from "./components/Popover/Popover.js";
24
27
  import { Radio, RadioProps } from "./components/Radio/Radio.js";
25
28
  import { RootSVG } from "./components/RootSVG/RootSVG.js";
26
29
  import { Searchbox, SearchboxButton, SearchboxInput, SearchboxProps } from "./components/Searchbox/Searchbox.js";
30
+ import { Section, SectionProps } from "./components/Section/Section.js";
31
+ import "./components/Section/index.js";
27
32
  import { Select, SelectOption, SelectOptionGroup, SelectProps } from "./components/Select/Select.js";
28
33
  import { Signpost, SignpostProps } from "./components/Signpost/Signpost.js";
29
34
  import { SkipNav, SkipNavProps } from "./components/SkipNav/SkipNav.js";
@@ -31,4 +36,6 @@ import { Squircle, SquircleProps, SquircleSvgProps } from "./components/Squircle
31
36
  import { Tag, TagProps } from "./components/Tag/Tag.js";
32
37
  import { Textarea, TextareaProps } from "./components/Textarea/Textarea.js";
33
38
  import { ValidationSummary, ValidationSummaryErrors, ValidationSummaryHeading, ValidationSummaryPrompt, ValidationSummaryProps } from "./components/ValidationSummary/ValidationSummary.js";
34
- export { AlertBanner, AlertBannerHeading, AlertBannerProps, Author, AuthorProps, Box, BoxProps, Button, ButtonProps, Card, CardBadge, CardContent, CardHeading, CardIcon, CardImage, CardLink, CardPicture, CardProps, Checkbox, CheckboxProps, DatePicker, DatePickerProps, Dialog, DialogBody, DialogHeader, DialogProps, Expandable, ExpandableContent, ExpandableProps, ExpandableSummary, Field, FieldError, FieldHint, FieldLabel, Fieldset, FieldsetError, FieldsetFields, FieldsetHint, FieldsetLegend, FieldsetProps, Flourish, FlourishProps, Form, FormProps, Image, ImageProps, LoadingSpinner, LoadingSpinnerProps, Logo, LogoProps, PasswordInput, PasswordInputProps, Picture, PictureProps, Pill, PillBadge, PillProps, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverProps, PopoverTrigger, Radio, RadioProps, RootSVG, Searchbox, SearchboxButton, SearchboxInput, SearchboxProps, Select, SelectOption, SelectOptionGroup, SelectProps, Signpost, SignpostProps, SkipNav, SkipNavProps, Squircle, SquircleProps, SquircleSvgProps, Tag, TagProps, TextInput, TextInputProps, Textarea, TextareaProps, ValidationSummary, ValidationSummaryErrors, ValidationSummaryHeading, ValidationSummaryPrompt, ValidationSummaryProps };
39
+ import { Wrapper, WrapperProps } from "./components/Wrapper/Wrapper.js";
40
+ import "./components/Wrapper/index.js";
41
+ export { AlertBanner, AlertBannerHeading, AlertBannerProps, Author, AuthorProps, Box, BoxProps, Button, ButtonProps, Card, CardBadge, CardContent, CardHeading, CardIcon, CardImage, CardLink, CardPicture, CardProps, Checkbox, CheckboxProps, Combobox, ComboboxControlsProps, ComboboxInputProps, ComboboxOptionsProps, ComboboxProps, DatePicker, DatePickerProps, type DateType, type DateTypeStr, Dialog, DialogBody, DialogHeader, DialogProps, Expandable, ExpandableContent, ExpandableProps, ExpandableSummary, Field, FieldError, FieldHint, FieldLabel, Fieldset, FieldsetError, FieldsetFields, FieldsetHint, FieldsetLegend, FieldsetProps, Flourish, FlourishProps, Form, FormProps, Image, ImageProps, LoadingSpinner, LoadingSpinnerProps, Logo, LogoProps, PasswordInput, PasswordInputProps, Picture, PictureProps, Pill, PillBadge, PillProps, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverProps, PopoverTrigger, Radio, RadioProps, RootSVG, Searchbox, SearchboxButton, SearchboxInput, SearchboxProps, Section, SectionProps, Select, SelectOption, SelectOptionGroup, SelectProps, Signpost, SignpostProps, SkipNav, SkipNavProps, Squircle, SquircleProps, SquircleSvgProps, Tag, TagProps, TextInput, TextInputProps, Textarea, TextareaProps, ValidationSummary, ValidationSummaryErrors, ValidationSummaryHeading, ValidationSummaryPrompt, ValidationSummaryProps, Wrapper, WrapperProps };
package/dist/index.js CHANGED
@@ -7,8 +7,9 @@ import { Button } from "./components/Button/Button.js";
7
7
  import { Picture } from "./components/Picture/Picture.js";
8
8
  import { Card, CardBadge, CardContent, CardHeading, CardIcon, CardImage, CardLink, CardPicture } from "./components/Card/Card.js";
9
9
  import { Checkbox } from "./components/Checkbox/Checkbox.js";
10
- import { Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger } from "./components/Popover/Popover.js";
11
10
  import { TextInput } from "./components/TextInput/TextInput.js";
11
+ import { Combobox } from "./components/Combobox/Combobox.js";
12
+ import { Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger } from "./components/Popover/Popover.js";
12
13
  import { DatePicker } from "./components/DatePicker/DatePicker.js";
13
14
  import { Dialog, DialogBody, DialogHeader } from "./components/Dialog/Dialog.js";
14
15
  import { Expandable, ExpandableContent, ExpandableSummary } from "./components/Expandable/Expandable.js";
@@ -22,6 +23,7 @@ import { Pill, PillBadge } from "./components/Pill/Pill.js";
22
23
  import { Radio } from "./components/Radio/Radio.js";
23
24
  import { RootSVG } from "./components/RootSVG/RootSVG.js";
24
25
  import { Searchbox, SearchboxButton, SearchboxInput } from "./components/Searchbox/Searchbox.js";
26
+ import { Section } from "./components/Section/Section.js";
25
27
  import { Select, SelectOption, SelectOptionGroup } from "./components/Select/Select.js";
26
28
  import { Signpost } from "./components/Signpost/Signpost.js";
27
29
  import { SkipNav } from "./components/SkipNav/SkipNav.js";
@@ -29,4 +31,5 @@ import { Squircle } from "./components/Squircle/Squircle.js";
29
31
  import { Tag } from "./components/Tag/Tag.js";
30
32
  import { Textarea } from "./components/Textarea/Textarea.js";
31
33
  import { ValidationSummary, ValidationSummaryErrors, ValidationSummaryHeading, ValidationSummaryPrompt } from "./components/ValidationSummary/ValidationSummary.js";
32
- export { AlertBanner, AlertBannerHeading, Author, Box, Button, Card, CardBadge, CardContent, CardHeading, CardIcon, CardImage, CardLink, CardPicture, Checkbox, DatePicker, Dialog, DialogBody, DialogHeader, Expandable, ExpandableContent, ExpandableSummary, Field, FieldError, FieldHint, FieldLabel, Fieldset, FieldsetError, FieldsetFields, FieldsetHint, FieldsetLegend, Flourish, Form, Image, LoadingSpinner, Logo, PasswordInput, Picture, Pill, PillBadge, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Radio, RootSVG, Searchbox, SearchboxButton, SearchboxInput, Select, SelectOption, SelectOptionGroup, Signpost, SkipNav, Squircle, Tag, TextInput, Textarea, ValidationSummary, ValidationSummaryErrors, ValidationSummaryHeading, ValidationSummaryPrompt };
34
+ import { Wrapper } from "./components/Wrapper/Wrapper.js";
35
+ export { AlertBanner, AlertBannerHeading, Author, Box, Button, Card, CardBadge, CardContent, CardHeading, CardIcon, CardImage, CardLink, CardPicture, Checkbox, Combobox, DatePicker, Dialog, DialogBody, DialogHeader, Expandable, ExpandableContent, ExpandableSummary, Field, FieldError, FieldHint, FieldLabel, Fieldset, FieldsetError, FieldsetFields, FieldsetHint, FieldsetLegend, Flourish, Form, Image, LoadingSpinner, Logo, PasswordInput, Picture, Pill, PillBadge, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Radio, RootSVG, Searchbox, SearchboxButton, SearchboxInput, Section, Select, SelectOption, SelectOptionGroup, Signpost, SkipNav, Squircle, Tag, TextInput, Textarea, ValidationSummary, ValidationSummaryErrors, ValidationSummaryHeading, ValidationSummaryPrompt, Wrapper };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coopdigital/react",
3
3
  "type": "module",
4
- "version": "1.22.5",
4
+ "version": "1.24.0",
5
5
  "private": false,
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -41,12 +41,15 @@
41
41
  "test:coverage": "vitest run --coverage",
42
42
  "test:behaviour": "./test-utils/run-playwright-tests.sh -g behaviour",
43
43
  "test:behaviour:ui": "npm run test:behaviour -- --ui",
44
- "test:avt": "./test-utils/run-playwright-tests.sh -g avt",
44
+ "test:avt": "./test-utils/run-playwright-tests.sh -g avt --project=chromium",
45
45
  "test:avt:ui": "npm run test:avt -- --ui",
46
46
  "test:vrt": "ENVIRONMENT=docker ./test-utils/run-playwright-tests.sh -g vrt --workers 4 --timeout 5000 --fully-parallel",
47
47
  "test:vrt:ui": "npm run test:vrt -- --ui",
48
48
  "test:e2e": "./test-utils/run-e2e-tests.sh",
49
49
  "test:e2e:ci": "npx playwright test --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL",
50
+ "test:avt:ci": "npm run test:e2e:ci -- -g avt --project=chromium",
51
+ "test:vrt:ci": "npm run test:e2e:ci -- -g vrt",
52
+ "test:behaviour:ci": "npm run test:e2e:ci -- -g behaviour",
50
53
  "build": "tsx scripts build",
51
54
  "build:docs": "tsx scripts build-docs",
52
55
  "build:docs:all": "tsx scripts build-docs --all",
@@ -103,12 +106,14 @@
103
106
  "storybook": "$storybook"
104
107
  },
105
108
  "dependencies": {
106
- "@coopdigital/styles": "^1.16.0",
109
+ "@coopdigital/styles": "^1.17.0",
107
110
  "@radix-ui/react-popover": "^1.1.23",
108
111
  "clsx": "^2.1.1",
109
112
  "date-fns": "^4.4.0",
113
+ "downshift": "^9.4.0",
114
+ "match-sorter": "^8.3.0",
110
115
  "merge-refs": "^2.0.0",
111
116
  "react-day-picker": "^10.0.1"
112
117
  },
113
- "gitHead": "bbdf07997fcf805aa2603062e190b6eaacc3f3f6"
118
+ "gitHead": "74c62ece6c7a4f0770bf52b466ccd98b36550233"
114
119
  }
@@ -0,0 +1,27 @@
1
+ "use client"
2
+ import { createContext, useContext } from "react"
3
+
4
+ import type { ComboboxItem } from "@/components/Combobox/combobox-utils"
5
+
6
+ import { StandardSizes } from "@/types"
7
+
8
+ interface ComboboxContextValue {
9
+ disabled?: boolean
10
+ items?: ComboboxItem[]
11
+ props?: {
12
+ getInputProps: Function
13
+ getItemProps: Function
14
+ getMenuProps: Function
15
+ getToggleButtonProps: Function
16
+ isOpen: boolean
17
+ }
18
+ size?: StandardSizes
19
+ }
20
+
21
+ export const ComboboxContext = createContext<ComboboxContextValue>({})
22
+
23
+ export const useComboboxContext = () => {
24
+ const context = useContext(ComboboxContext)
25
+ if (!context) throw new Error("Combobox components must be used inside <Combobox>")
26
+ return context
27
+ }
@@ -0,0 +1,323 @@
1
+ "use client"
2
+ import type { ButtonHTMLAttributes, HTMLAttributes, JSX, ReactNode, Ref } from "react"
3
+
4
+ import clsx from "clsx"
5
+ import {
6
+ useCombobox,
7
+ UseComboboxHighlightedIndexChange,
8
+ UseComboboxInputValueChange,
9
+ UseComboboxIsOpenChange,
10
+ UseComboboxSelectedItemChange,
11
+ UseComboboxStateChange,
12
+ } from "downshift"
13
+ import { useState } from "react"
14
+
15
+ import {
16
+ type ComboboxItem,
17
+ type ComboboxOption,
18
+ defaultFilter,
19
+ defaultIsItemDisabled,
20
+ defaultItemToString,
21
+ normalizeOptions,
22
+ } from "@/components/Combobox/combobox-utils"
23
+ import { ComboboxContext, useComboboxContext } from "@/components/Combobox/Combobox.context"
24
+ import { useField } from "@/components/Field/Field.context"
25
+ import { ChevronDownIcon } from "@/components/icons"
26
+ import { TextInput, TextInputProps } from "@/components/TextInput"
27
+ import { useSlots } from "@/hooks"
28
+ import { getSlotProp } from "@/utils"
29
+
30
+ export interface ComboboxProps extends Omit<
31
+ HTMLAttributes<HTMLDivElement>,
32
+ "onChange" | "onSelect" | "onToggle"
33
+ > {
34
+ /** Specify whether custom values are accepted.
35
+ *
36
+ * Default: `false`
37
+ */
38
+ allowCustom?: boolean
39
+ /** Content inside the component.
40
+ *
41
+ * This should be a set of `<Combobox.Controls>` and `<Combobox.Options>`.
42
+ */
43
+ children?: string | ReactNode
44
+ /** Specify additional CSS classes. */
45
+ className?: string
46
+ /** Specify the disabled state. */
47
+ disabled?: boolean
48
+ /** Specify the component error state. */
49
+ error?: boolean
50
+ /** Specify the component id. Will be auto-generated if not set. */
51
+ id?: string
52
+ /** Custom function to filter the list of options.
53
+ *
54
+ * Takes a `ComboboxItem` array and an input string, and returns a filtered array.
55
+ *
56
+ * Default: Filters on item label or value: `item.label ?? item.value`.
57
+ */
58
+ inputFilter?: (items: ComboboxItem[], value: string) => ComboboxItem[]
59
+ /** Custom function to mark item as disabled.
60
+ *
61
+ * Default: `disabled` prop on `.ComboboxOption`
62
+ */
63
+ isItemDisabled?: (item: ComboboxItem) => boolean
64
+ /** Custom function to render text in the input field after selecting a value.
65
+ *
66
+ * Default: Uses item label or value: `item.label ?? item.value`.
67
+ */
68
+ itemToString?: (item: ComboboxItem | null) => string
69
+ /** Callback fired when the input value changes.
70
+ *
71
+ * This is a `downshift` event.
72
+ */
73
+ onChange?: (event: UseComboboxInputValueChange<ComboboxItem>) => void
74
+ /** Callback fired when users highlight an option in the list using the mouse or keyboard.
75
+ *
76
+ * This is a `downshift` event.
77
+ */
78
+ onHighlight?: (event: UseComboboxHighlightedIndexChange<ComboboxItem>) => void
79
+ /** Callback fired when users select an option from the list.
80
+ *
81
+ * This is a `downshift` event.
82
+ */
83
+ onSelect?: (event: UseComboboxSelectedItemChange<ComboboxItem>) => void
84
+ /** Callback fired whenever the state changes.
85
+ *
86
+ * This is a `downshift` event.
87
+ */
88
+ onStateChange?: (event: UseComboboxStateChange<ComboboxItem>) => void
89
+ /** Callback fired when the menu opens or closes.
90
+ *
91
+ * This is a `downshift` event.
92
+ */
93
+ onToggle?: (changes: UseComboboxIsOpenChange<ComboboxItem>) => void
94
+ /** Specify whether the menu should appear immediately when clicking the input field.
95
+ *
96
+ * Default: `true`.
97
+ */
98
+ openOnClick?: boolean
99
+ /** Specify placeholder text. Do not use in place of a form label. */
100
+ placeholder?: string
101
+ /** Specify a custom React ref for this component. */
102
+ ref?: Ref<HTMLDivElement>
103
+ /** Specify the component size. */
104
+ size?: "sm" | "md" | "lg"
105
+ }
106
+
107
+ export interface ComboboxControlsProps extends HTMLAttributes<HTMLDivElement> {
108
+ /** Content inside the component.
109
+ *
110
+ * This should be a set of `<Combobox.Input>` and `<Combobox.Button>`.
111
+ */
112
+ children?: React.ReactNode
113
+ /** Specify additional CSS classes. */
114
+ className?: string
115
+ }
116
+
117
+ export interface ComboboxOptionsProps extends HTMLAttributes<HTMLUListElement> {
118
+ /** Specify additional CSS classes. */
119
+ className?: string
120
+ /** Specify text to display when there are no matching options. */
121
+ noResultsText?: string
122
+ /** Specify array of options to choose from.
123
+ *
124
+ * Can be either a simple string array or a complex `ComboboxOption` array.
125
+ */
126
+ options: ComboboxOption[]
127
+ }
128
+
129
+ export type ComboboxInputProps = TextInputProps
130
+
131
+ const componentSlots = {
132
+ ComboboxControls: null,
133
+ ComboboxOptions: null,
134
+ }
135
+
136
+ export const Combobox = ({
137
+ allowCustom = false,
138
+ children,
139
+ className,
140
+ disabled,
141
+ error,
142
+ id,
143
+ inputFilter,
144
+ isItemDisabled,
145
+ itemToString,
146
+ onChange,
147
+ onHighlight,
148
+ onSelect,
149
+ onStateChange,
150
+ onToggle,
151
+ openOnClick = true,
152
+ ref,
153
+ size,
154
+ ...props
155
+ }: ComboboxProps): JSX.Element | null => {
156
+ const field = useField()
157
+ const uid = id ?? field?.id
158
+
159
+ const slots = useSlots(componentSlots, children)
160
+
161
+ const userOptions = getSlotProp<ComboboxOption[]>(slots.ComboboxOptions, "options") ?? []
162
+ const items = normalizeOptions(userOptions)
163
+
164
+ const [inputItems, setInputItems] = useState(items)
165
+
166
+ const componentProps = {
167
+ className: clsx("coop-combobox", className),
168
+ "data-error": error ?? undefined,
169
+ "data-size": size ?? undefined,
170
+ id: uid,
171
+ ...props,
172
+ }
173
+
174
+ const inputFilterActual = inputFilter ?? defaultFilter
175
+ const itemToStringActual = itemToString ?? defaultItemToString
176
+
177
+ const comboboxProps = useCombobox<ComboboxItem>({
178
+ id: uid,
179
+ inputId: uid + "_input",
180
+ isItemDisabled: isItemDisabled ?? defaultIsItemDisabled,
181
+ items: inputItems,
182
+ itemToString: itemToStringActual,
183
+ labelId: uid + "_label",
184
+ menuId: uid + "_menu",
185
+ onHighlightedIndexChange: onHighlight,
186
+ onInputValueChange: (event) => {
187
+ setInputItems(inputFilterActual(items, event.inputValue))
188
+ onChange?.(event)
189
+ },
190
+ onIsOpenChange: onToggle,
191
+ onSelectedItemChange: (event) => {
192
+ onSelect?.(event) // This might need to broadcast just the value to work with RHF - need to check
193
+ },
194
+ onStateChange,
195
+ stateReducer(_, actionAndChanges) {
196
+ const { InputBlur, InputClick, InputKeyDownEnter } = useCombobox.stateChangeTypes
197
+
198
+ const { changes: state, type } = actionAndChanges
199
+ const currentInput = state.inputValue ?? ""
200
+ const hasMatch = items.some((item) => itemToStringActual(item) === currentInput)
201
+
202
+ switch (type) {
203
+ case openOnClick === false && InputClick:
204
+ return { ...state, isOpen: false }
205
+ case allowCustom === false && InputBlur:
206
+ return hasMatch ? state : { ...state, inputValue: "" }
207
+ case allowCustom === false && InputKeyDownEnter:
208
+ return hasMatch ? state : { ...state, isOpen: true }
209
+ default:
210
+ return state
211
+ }
212
+ },
213
+ })
214
+
215
+ // if (!slots.ComboboxControls || !slots.ComboboxOptions) {
216
+ // console.warn("Combobox must contain Combobox.Controls and Combobox.Options")
217
+ // return null
218
+ // }
219
+
220
+ return (
221
+ <ComboboxContext.Provider value={{ disabled, items: inputItems, props: comboboxProps, size }}>
222
+ <div {...componentProps} ref={ref}>
223
+ {slots.ComboboxControls}
224
+ {slots.ComboboxOptions}
225
+ </div>
226
+ </ComboboxContext.Provider>
227
+ )
228
+ }
229
+
230
+ const ComboboxInput = ({ className, placeholder, ...props }: ComboboxInputProps): JSX.Element => {
231
+ const combobox = useComboboxContext()
232
+ const componentProps = {
233
+ className,
234
+ disabled: combobox.disabled,
235
+ placeholder,
236
+ size: combobox.size,
237
+ ...props,
238
+ }
239
+ return <TextInput {...componentProps} {...combobox.props?.getInputProps({})} />
240
+ }
241
+
242
+ const ComboboxButton = ({
243
+ children,
244
+ className,
245
+ ...props
246
+ }: ButtonHTMLAttributes<HTMLButtonElement>): JSX.Element => {
247
+ const combobox = useComboboxContext()
248
+ const componentProps = {
249
+ "aria-label": props["aria-label"] ?? "Toggle menu",
250
+ className,
251
+ disabled: combobox.disabled,
252
+ ...props,
253
+ }
254
+
255
+ return (
256
+ <button type="button" {...componentProps} {...combobox.props?.getToggleButtonProps()}>
257
+ {children ?? <ChevronDownIcon stroke="black" strokeWidth={1} width={"1.25rem"} />}
258
+ </button>
259
+ )
260
+ }
261
+
262
+ const ComboboxControls = ({
263
+ children,
264
+ className,
265
+ ...props
266
+ }: ComboboxControlsProps): JSX.Element => {
267
+ const componentProps = {
268
+ className: clsx("coop-combobox-controls", className),
269
+ ...props,
270
+ }
271
+ return <div {...componentProps}>{children}</div>
272
+ }
273
+
274
+ const ComboboxOptions = ({
275
+ className,
276
+ noResultsText = "No results",
277
+ options,
278
+ ...props
279
+ }: ComboboxOptionsProps): JSX.Element | null => {
280
+ const combobox = useComboboxContext()
281
+
282
+ const componentProps = {
283
+ className: clsx("coop-combobox-options", className),
284
+ ...props,
285
+ }
286
+
287
+ if (!options) {
288
+ return null
289
+ }
290
+
291
+ return (
292
+ <ul {...componentProps} {...combobox.props?.getMenuProps()}>
293
+ {combobox.props?.isOpen && combobox.items?.length
294
+ ? combobox.items.map((item, index) => {
295
+ const itemProps = { ...combobox.props?.getItemProps({ index, item }) }
296
+ return (
297
+ <li key={item.value} {...itemProps} {...item}>
298
+ {item?.icon}
299
+ {item.children ?? item.label ?? item.value}
300
+ </li>
301
+ )
302
+ })
303
+ : null}
304
+ {combobox.props?.isOpen && !combobox.items?.length && <li>{noResultsText}</li>}
305
+ </ul>
306
+ )
307
+ }
308
+
309
+ Combobox.slot = {
310
+ field: true,
311
+ name: "Combobox",
312
+ suffix: "_input",
313
+ }
314
+
315
+ ComboboxControls.slot = { name: "ComboboxControls" }
316
+ ComboboxOptions.slot = { name: "ComboboxOptions" }
317
+
318
+ Combobox.Controls = ComboboxControls
319
+ Combobox.Input = ComboboxInput
320
+ Combobox.Button = ComboboxButton
321
+ Combobox.Options = ComboboxOptions
322
+
323
+ export default Combobox
@@ -0,0 +1,39 @@
1
+ import { matchSorter } from "match-sorter"
2
+ import { HTMLAttributes } from "react"
3
+
4
+ export interface ComboboxItem extends HTMLAttributes<HTMLLIElement> {
5
+ /** Specify the disabled state.
6
+ *
7
+ * Options that are disabled still appear in the list but cannot be selected.
8
+ */
9
+ disabled?: boolean
10
+ /** Specify an icon to render before the item text. */
11
+ icon?: React.ReactNode
12
+ /** Specify text to display to users if the underlying value needs to be different.
13
+ *
14
+ * If this value is provided, this is the string that gets filtered when the user types.
15
+ */
16
+ label?: string
17
+ /** Specify the option value. */
18
+ value: string
19
+ }
20
+
21
+ export type ComboboxOption = ComboboxItem | string
22
+
23
+ export const defaultFilter = (items: ComboboxItem[], inputValue: string): ComboboxItem[] => {
24
+ return inputValue !== ""
25
+ ? matchSorter(items, inputValue, {
26
+ keys: [(item) => [item.label ?? item.value]],
27
+ })
28
+ : items
29
+ }
30
+
31
+ export const defaultIsItemDisabled = (item: ComboboxItem): boolean => item.disabled ?? false
32
+
33
+ export const defaultItemToString = (item: ComboboxItem | null): string =>
34
+ item?.label ?? item?.value ?? ""
35
+
36
+ export const normalizeOptions = (options: ComboboxOption[]): ComboboxItem[] =>
37
+ options
38
+ .filter((option) => option && typeof option !== "number")
39
+ .map((option) => (typeof option === "string" ? { value: option } : option))
@@ -0,0 +1,5 @@
1
+ import Combobox from "./Combobox"
2
+
3
+ export default Combobox
4
+ export { Combobox }
5
+ export * from "./Combobox"
@@ -25,6 +25,7 @@ import type {
25
25
  DateRange,
26
26
  DateRangeStr,
27
27
  DateType,
28
+ DateTypeStr,
28
29
  TDateMultiple,
29
30
  TDateRange,
30
31
  TDateSingle,
@@ -40,9 +41,7 @@ import {
40
41
  shouldSkipParsing,
41
42
  } from "./datepicker-utils"
42
43
 
43
- type OnDateSelectHandler = (
44
- selected: string | string[] | { from?: string; to?: string } | undefined
45
- ) => void
44
+ type OnDateSelectHandler = (selected: DateTypeStr) => void
46
45
 
47
46
  interface PickerState {
48
47
  message?: string
@@ -402,3 +401,5 @@ DatePicker.slot = {
402
401
 
403
402
  export default DatePicker
404
403
  export { DatePicker }
404
+
405
+ export type { DateType, DateTypeStr }
@@ -19,6 +19,7 @@ interface DateReturn {
19
19
 
20
20
  export type DateBase = string | undefined
21
21
  export type DateType = Date | Date[] | DateRange | string | undefined
22
+ export type DateTypeStr = string | string[] | DateRangeStr | undefined
22
23
 
23
24
  export interface TDateSingle {
24
25
  event?: string
@@ -0,0 +1,31 @@
1
+ import type { HTMLAttributes, JSX, ReactNode, Ref } from "react"
2
+
3
+ import clsx from "clsx"
4
+ import React from "react"
5
+
6
+ export interface SectionProps extends HTMLAttributes<HTMLDivElement> {
7
+ /** Specify a custom element for the component
8
+ *
9
+ * Default: `<section>`
10
+ */
11
+ // oxlint-disable-next-line typescript/no-explicit-any
12
+ as?: React.FC<any> | string
13
+ /** **(Optional)** Content inside the component. */
14
+ children?: ReactNode
15
+ /** **(Optional)** Specify additional CSS classes. */
16
+ className?: string
17
+ /** **(Optional)** Specify a custom React ref for this component. */
18
+ ref?: Ref<HTMLDivElement>
19
+ }
20
+
21
+ export const Section = ({ as, children, className, ref, ...props }: SectionProps): JSX.Element => {
22
+ const componentProps = {
23
+ className: clsx("coop-section", className),
24
+ ...props,
25
+ }
26
+ const element: SectionProps["as"] = as ?? "section"
27
+
28
+ return React.createElement(element, { ...componentProps, ref }, children)
29
+ }
30
+
31
+ export default Section
@@ -0,0 +1,5 @@
1
+ import Section from "./Section"
2
+
3
+ export default Section
4
+ export { Section }
5
+ export * from "./Section"
@@ -0,0 +1,32 @@
1
+ import type { HTMLAttributes, JSX, ReactNode, Ref } from "react"
2
+
3
+ import clsx from "clsx"
4
+ import React from "react"
5
+
6
+ export interface WrapperProps extends HTMLAttributes<HTMLDivElement> {
7
+ /** Specify a custom element for the component
8
+ *
9
+ * Default: `<div>`
10
+ */
11
+ // oxlint-disable-next-line typescript/no-explicit-any
12
+ as?: React.FC<any> | string
13
+ /** **(Optional)** Content inside the component. */
14
+ children?: ReactNode
15
+ /** **(Optional)** Specify additional CSS classes. */
16
+ className?: string
17
+ /** **(Optional)** Specify a custom React ref for this component. */
18
+ ref?: Ref<HTMLDivElement>
19
+ }
20
+
21
+ export const Wrapper = ({ as, children, className, ref, ...props }: WrapperProps): JSX.Element => {
22
+ const componentProps = {
23
+ className: clsx("coop-wrap", className),
24
+ ...props,
25
+ }
26
+
27
+ const element: WrapperProps["as"] = as ?? "div"
28
+
29
+ return React.createElement(element, { ...componentProps, ref }, children)
30
+ }
31
+
32
+ export default Wrapper
@@ -0,0 +1,5 @@
1
+ import Wrapper from "./Wrapper"
2
+
3
+ export default Wrapper
4
+ export { Wrapper }
5
+ export * from "./Wrapper"
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export * from "./components/Box"
4
4
  export * from "./components/Button/Button"
5
5
  export * from "./components/Card/Card"
6
6
  export * from "./components/Checkbox/Checkbox"
7
+ export * from "./components/Combobox"
7
8
  export * from "./components/DatePicker/DatePicker"
8
9
  export * from "./components/Dialog/Dialog"
9
10
  export * from "./components/Expandable/Expandable"
@@ -21,6 +22,7 @@ export * from "./components/Popover/Popover"
21
22
  export * from "./components/Radio/Radio"
22
23
  export * from "./components/RootSVG/RootSVG"
23
24
  export * from "./components/Searchbox/Searchbox"
25
+ export * from "./components/Section"
24
26
  export * from "./components/Select/Select"
25
27
  export * from "./components/Signpost/Signpost"
26
28
  export * from "./components/SkipNav/SkipNav"
@@ -29,3 +31,4 @@ export * from "./components/Tag/Tag"
29
31
  export * from "./components/Textarea/Textarea"
30
32
  export * from "./components/TextInput/TextInput"
31
33
  export * from "./components/ValidationSummary/ValidationSummary"
34
+ export * from "./components/Wrapper"