@coopdigital/react 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Button/Button.d.ts +1 -1
- package/dist/components/Button/Button.js +2 -4
- package/dist/components/Card/Card.d.ts +1 -1
- package/dist/components/Expandable/Expandable.d.ts +1 -1
- package/dist/components/Expandable/Expandable.js +1 -1
- package/dist/components/Icon/LoadingIcon.d.ts +6 -0
- package/dist/components/Icon/LoadingIcon.js +12 -0
- package/dist/components/Icon/SearchIcon.js +12 -0
- package/dist/components/Icon/index.d.ts +1 -0
- package/dist/components/SearchBox/SearchBox.d.ts +48 -0
- package/dist/components/SearchBox/SearchBox.js +67 -0
- package/dist/components/SearchBox/index.d.ts +4 -0
- package/dist/components/Tag/Tag.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +9 -9
- package/src/components/Button/Button.tsx +13 -18
- package/src/components/Card/Card.tsx +11 -1
- package/src/components/Expandable/Expandable.tsx +14 -2
- package/src/components/Icon/AddIcon.tsx +1 -0
- package/src/components/Icon/ArrowDownIcon.tsx +1 -0
- package/src/components/Icon/ArrowLeftIcon.tsx +1 -0
- package/src/components/Icon/ArrowRightIcon.tsx +1 -0
- package/src/components/Icon/ArrowUpIcon.tsx +1 -0
- package/src/components/Icon/AvatarAltIcon.tsx +1 -0
- package/src/components/Icon/AvatarIcon.tsx +1 -0
- package/src/components/Icon/BasketIcon.tsx +1 -0
- package/src/components/Icon/CalendarIcon.tsx +1 -0
- package/src/components/Icon/ChevronDownIcon.tsx +1 -0
- package/src/components/Icon/ChevronLeftIcon.tsx +1 -0
- package/src/components/Icon/ChevronRightIcon.tsx +1 -0
- package/src/components/Icon/ChevronUpIcon.tsx +1 -0
- package/src/components/Icon/ClockIcon.tsx +1 -0
- package/src/components/Icon/CloseAltIcon.tsx +1 -0
- package/src/components/Icon/CloseIcon.tsx +1 -0
- package/src/components/Icon/CoopCardIcon.tsx +1 -0
- package/src/components/Icon/CoopIcon.tsx +1 -0
- package/src/components/Icon/CoopLocationIcon.tsx +1 -0
- package/src/components/Icon/DownloadIcon.tsx +1 -0
- package/src/components/Icon/HomeIcon.tsx +1 -0
- package/src/components/Icon/InformationIcon.tsx +1 -0
- package/src/components/Icon/LoadingIcon.tsx +27 -0
- package/src/components/Icon/LocationIcon.tsx +1 -0
- package/src/components/Icon/MailIcon.tsx +1 -0
- package/src/components/Icon/MenuIcon.tsx +1 -0
- package/src/components/Icon/MessageIcon.tsx +1 -0
- package/src/components/Icon/MinusIcon.tsx +1 -0
- package/src/components/Icon/OpenNewIcon.tsx +1 -0
- package/src/components/Icon/PencilIcon.tsx +1 -0
- package/src/components/Icon/PhoneIcon.tsx +1 -0
- package/src/components/Icon/QuestionIcon.tsx +1 -0
- package/src/components/Icon/ScooterIcon.tsx +1 -0
- package/src/components/Icon/SearchIcon.tsx +1 -0
- package/src/components/Icon/SettingsIcon.tsx +1 -0
- package/src/components/Icon/TickAltIcon.tsx +1 -0
- package/src/components/Icon/TickIcon.tsx +1 -0
- package/src/components/Icon/VanIcon.tsx +1 -0
- package/src/components/Icon/WarningIcon.tsx +1 -0
- package/src/components/Icon/WriteIcon.tsx +1 -0
- package/src/components/Icon/index.tsx +1 -0
- package/src/components/SearchBox/SearchBox.tsx +139 -0
- package/src/components/SearchBox/index.ts +5 -0
- package/src/components/Tag/Tag.tsx +2 -1
- package/src/index.ts +1 -0
|
@@ -22,7 +22,7 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
22
22
|
/** **(Optional)** Specifies the Button size. */
|
|
23
23
|
size?: "sm" | "md" | "lg" | "xl";
|
|
24
24
|
/** **(Optional)** Specifies the Button variant. */
|
|
25
|
-
variant?: "
|
|
25
|
+
variant?: "green" | "blue" | "white" | "grey" | "green-ghost" | "blue-ghost" | "white-ghost" | "grey-ghost" | "text";
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* The Button component is an interactive element that people can use to take an action.
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { __rest, __awaiter } from '../../node_modules/tslib/tslib.es6.js';
|
|
2
2
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
3
3
|
import React, { useState, useCallback } from 'react';
|
|
4
|
+
import { LoadingIcon } from '../Icon/LoadingIcon.js';
|
|
4
5
|
|
|
5
|
-
const LoadingIcon = () => {
|
|
6
|
-
return (jsx("svg", { fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }));
|
|
7
|
-
};
|
|
8
6
|
/**
|
|
9
7
|
* The Button component is an interactive element that people can use to take an action.
|
|
10
8
|
*/
|
|
11
9
|
const Button = (_a) => {
|
|
12
|
-
var { as, children, className = "", href, isDisabled = false, isFullWidth = false, isLoading = false, loadingText = "Loading", onClick, size = "md", variant = "
|
|
10
|
+
var { as, children, className = "", href, isDisabled = false, isFullWidth = false, isLoading = false, loadingText = "Loading", onClick, size = "md", variant = "green" } = _a, props = __rest(_a, ["as", "children", "className", "href", "isDisabled", "isFullWidth", "isLoading", "loadingText", "onClick", "size", "variant"]);
|
|
13
11
|
let element = href ? "a" : "button";
|
|
14
12
|
if (as) {
|
|
15
13
|
element = as;
|
|
@@ -5,7 +5,7 @@ export interface CardProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
5
5
|
/** **(Optional)** Specifies the custom element to override default `a` */
|
|
6
6
|
as?: React.FC<AnchorHTMLAttributes<HTMLElement>> | ForwardRefExoticComponent<any> | string;
|
|
7
7
|
/** **(Optional)** Specifies the background color of the card. */
|
|
8
|
-
background?: "purple" | "pink" | "green" | "orange" | "red" | "yellow" | "lilac" | "blue";
|
|
8
|
+
background?: "purple" | "pink" | "green" | "orange" | "red" | "yellow" | "lilac" | "blue" | "brown" | "grey";
|
|
9
9
|
/** **(Optional)** Represents the content inside the badge. */
|
|
10
10
|
badge?: React.ReactNode;
|
|
11
11
|
/** **(Optional)** Specify badge position relative to top right corner. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DetailsHTMLAttributes, JSX } from "react";
|
|
2
2
|
export interface ExpandableProps extends DetailsHTMLAttributes<HTMLDetailsElement> {
|
|
3
3
|
/** **(Optional)** Specifies the Expandable background color from the available options. */
|
|
4
|
-
background?: "
|
|
4
|
+
background?: "purple" | "pink" | "green" | "orange" | "red" | "yellow" | "lilac" | "blue" | "white" | "transparent" | "brown" | "grey";
|
|
5
5
|
/** Represents the content inside the Expandable component, only visible when expanded. It can be any valid JSX or string. */
|
|
6
6
|
children: React.ReactNode;
|
|
7
7
|
/** **(Optional)** Receives any className to be applied to Expandable component. */
|
|
@@ -6,7 +6,7 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
6
6
|
* It can be used to reveal more context for a specific issue or action.
|
|
7
7
|
*/
|
|
8
8
|
const Expandable = (_a) => {
|
|
9
|
-
var { background, children, className = "", summary } = _a, props = __rest(_a, ["background", "children", "className", "summary"]);
|
|
9
|
+
var { background = "grey", children, className = "", summary } = _a, props = __rest(_a, ["background", "children", "className", "summary"]);
|
|
10
10
|
const componentProps = Object.assign({ className: `coop-expandable ${className}`, "data-bg": background }, props);
|
|
11
11
|
return (jsxs("details", Object.assign({}, componentProps, { children: [jsxs("summary", { children: [summary, jsx("svg", { fill: "none", height: "24", width: "24", xmlns: "http://www.w3.org/2000/svg", children: jsx("path", { d: "m20.25 9.75-6.22 6.22a.75.75 0 0 1-1.06 0L6.75 9.75", stroke: "black", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5" }) })] }), jsx("div", { className: "coop-expandable--content", children: children })] })));
|
|
12
12
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { __rest } from '../../node_modules/tslib/tslib.es6.js';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { useId } from 'react';
|
|
4
|
+
|
|
5
|
+
const LoadingIcon = (_a) => {
|
|
6
|
+
var { alt, className } = _a, props = __rest(_a, ["alt", "className"]);
|
|
7
|
+
const id = useId();
|
|
8
|
+
const componentProps = Object.assign({ "aria-labelledby": alt ? id : undefined, className: `coop-icon ${className !== null && className !== void 0 ? className : ""}`.trim(), "data-icon": "loading", fill: "none", role: alt ? "img" : undefined, viewBox: "0 0 24 24" }, props);
|
|
9
|
+
return (jsxs("svg", Object.assign({}, componentProps, { children: [alt ? jsx("title", { id: id, children: alt }) : null, jsx("path", { d: "M10.72 19.9a8 8 0 0 1-6.5-9.79 7.77 7.77 0 0 1 6.18-5.95 8 8 0 0 1 9.49 6.52A1.54 1.54 0 0 0 21.38 12h.13a1.37 1.37 0 0 0 1.38-1.54 11 11 0 1 0-12.7 12.39A1.54 1.54 0 0 0 12 21.34a1.47 1.47 0 0 0-1.28-1.44", fill: "currentColor" })] })));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { LoadingIcon };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { __rest } from '../../node_modules/tslib/tslib.es6.js';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { useId } from 'react';
|
|
4
|
+
|
|
5
|
+
const SearchIcon = (_a) => {
|
|
6
|
+
var { alt, className } = _a, props = __rest(_a, ["alt", "className"]);
|
|
7
|
+
const id = useId();
|
|
8
|
+
const componentProps = Object.assign({ "aria-labelledby": alt ? id : undefined, className: `coop-icon ${className !== null && className !== void 0 ? className : ""}`.trim(), "data-icon": "search", fill: "none", role: alt ? "img" : undefined, viewBox: "0 0 32 32" }, props);
|
|
9
|
+
return (jsxs("svg", Object.assign({}, componentProps, { children: [alt ? jsx("title", { id: id, children: alt }) : null, jsx("path", { d: "M13.25 25.49a12.25 12.25 0 1 1 12.24-12.24 12.25 12.25 0 0 1-12.24 12.24m0-22.61a10.37 10.37 0 1 0 10.36 10.37A10.38 10.38 0 0 0 13.25 2.88", fill: "currentColor" }), jsx("path", { d: "M30.06 31a.94.94 0 0 1-.67-.28l-8.81-8.81a.94.94 0 1 1 1.33-1.33l8.81 8.81a.94.94 0 0 1 0 1.33.9.9 0 0 1-.66.28", fill: "currentColor" })] })));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { SearchIcon };
|
|
@@ -20,6 +20,7 @@ export { CoopLocationIcon } from "./CoopLocationIcon";
|
|
|
20
20
|
export { DownloadIcon } from "./DownloadIcon";
|
|
21
21
|
export { HomeIcon } from "./HomeIcon";
|
|
22
22
|
export { InformationIcon } from "./InformationIcon";
|
|
23
|
+
export { LoadingIcon } from "./LoadingIcon";
|
|
23
24
|
export { LocationIcon } from "./LocationIcon";
|
|
24
25
|
export { MailIcon } from "./MailIcon";
|
|
25
26
|
export { MenuIcon } from "./MenuIcon";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { HTMLAttributes } from "react";
|
|
2
|
+
import { type JSX } from "react";
|
|
3
|
+
import { type ButtonProps } from "../Button";
|
|
4
|
+
export interface SearchBoxProps extends HTMLAttributes<HTMLInputElement> {
|
|
5
|
+
/** **(Optional)** Server endpoint to submit the form. Will be ignored if onSubmit is also set. */
|
|
6
|
+
action?: string;
|
|
7
|
+
/** **(Optional)** Whether or not to enable auto complete on the input field. Default: `off`. */
|
|
8
|
+
autoComplete?: "off" | "on";
|
|
9
|
+
/** **(Optional)** Props to forward to the Button element. Use `label` to set Button text. */
|
|
10
|
+
button?: Pick<ButtonProps, "className" | "loadingText"> & {
|
|
11
|
+
label?: React.ReactNode;
|
|
12
|
+
};
|
|
13
|
+
/** **(Optional)** Receives any className to be applied to SearchBox component. */
|
|
14
|
+
className?: string;
|
|
15
|
+
/** Receives the label to be applied to SearchBox component. */
|
|
16
|
+
label: string;
|
|
17
|
+
/** **(Optional)** Receives whether label is visible to human or only screen readers. */
|
|
18
|
+
labelVisible?: boolean;
|
|
19
|
+
/** **(Optional)** Name of the input element, also used as the URL search parameter. Defaults to `query` */
|
|
20
|
+
name?: string;
|
|
21
|
+
/** **(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. */
|
|
22
|
+
onSubmit?: React.FormEventHandler<HTMLElement> | undefined;
|
|
23
|
+
/** **(Optional)** Receives any placeholder for SearchBox component. */
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
/** **(Optional)** Receives any size to be applied to SearchBox component. */
|
|
26
|
+
size?: string;
|
|
27
|
+
/** **(Optional)** Receives the variant to be applied to SearchBox component. */
|
|
28
|
+
variant?: "green" | "blue" | "white" | "grey" | "green-ghost" | "blue-ghost" | "white-ghost" | "grey-ghost";
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* SearchBox component allows a person to enter a word or a phrase to find relevant content.
|
|
32
|
+
*
|
|
33
|
+
* The `label` prop is mandatory but not visible by default (it is visible for screen readers). You can make it visible to humans by setting `labelVisible` to true.
|
|
34
|
+
*
|
|
35
|
+
* If you are using a single icon with no text for `button.label`, ensure you pass an `alt` property to the icon for accessibility purposes.
|
|
36
|
+
*
|
|
37
|
+
* You can provide an `action` to submit input like a normal HTML form, or an `onSubmit` callback to handle submissions client-side.
|
|
38
|
+
*
|
|
39
|
+
* **Good to know:** SearchBox requires both `SearchBox` and `Button` css/scss files.
|
|
40
|
+
*
|
|
41
|
+
* ```
|
|
42
|
+
* import "@coopdigital/styles/components/Searchbox.css"
|
|
43
|
+
* import "@coopdigital/styles/components/Button.css"
|
|
44
|
+
*
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare const SearchBox: ({ action, "aria-placeholder": ariaPlaceholder, autoCapitalize, autoComplete, button, className, label, labelVisible, name, onSubmit, placeholder, size, variant, ...props }: SearchBoxProps) => JSX.Element;
|
|
48
|
+
export default SearchBox;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { __rest, __awaiter } from '../../node_modules/tslib/tslib.es6.js';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import React, { useState, useCallback, useId } from 'react';
|
|
4
|
+
import { Button } from '../Button/Button.js';
|
|
5
|
+
import { SearchIcon } from '../Icon/SearchIcon.js';
|
|
6
|
+
|
|
7
|
+
const defaultButtonProps = {
|
|
8
|
+
label: React.createElement(SearchIcon, { alt: "Search", stroke: "currentColor", strokeWidth: 2 }),
|
|
9
|
+
loadingText: "",
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* SearchBox component allows a person to enter a word or a phrase to find relevant content.
|
|
13
|
+
*
|
|
14
|
+
* The `label` prop is mandatory but not visible by default (it is visible for screen readers). You can make it visible to humans by setting `labelVisible` to true.
|
|
15
|
+
*
|
|
16
|
+
* If you are using a single icon with no text for `button.label`, ensure you pass an `alt` property to the icon for accessibility purposes.
|
|
17
|
+
*
|
|
18
|
+
* You can provide an `action` to submit input like a normal HTML form, or an `onSubmit` callback to handle submissions client-side.
|
|
19
|
+
*
|
|
20
|
+
* **Good to know:** SearchBox requires both `SearchBox` and `Button` css/scss files.
|
|
21
|
+
*
|
|
22
|
+
* ```
|
|
23
|
+
* import "@coopdigital/styles/components/Searchbox.css"
|
|
24
|
+
* import "@coopdigital/styles/components/Button.css"
|
|
25
|
+
*
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
const SearchBox = (_a) => {
|
|
29
|
+
var _b, _c;
|
|
30
|
+
var { action, "aria-placeholder": ariaPlaceholder, autoCapitalize = "off", autoComplete = "off", button = defaultButtonProps, className, label, labelVisible = false, name = "query", onSubmit, placeholder, size = "md", variant = "green" } = _a, props = __rest(_a, ["action", "aria-placeholder", "autoCapitalize", "autoComplete", "button", "className", "label", "labelVisible", "name", "onSubmit", "placeholder", "size", "variant"]);
|
|
31
|
+
const [isPending, setIsPending] = useState(false);
|
|
32
|
+
const handleSubmit = useCallback((event) => __awaiter(void 0, void 0, void 0, function* () {
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
if (isPending || !onSubmit)
|
|
35
|
+
return;
|
|
36
|
+
setIsPending(true);
|
|
37
|
+
try {
|
|
38
|
+
yield Promise.resolve(onSubmit(event));
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
setIsPending(false);
|
|
42
|
+
}
|
|
43
|
+
}), [onSubmit, isPending]);
|
|
44
|
+
const id = useId();
|
|
45
|
+
const formProps = {
|
|
46
|
+
action: action !== null && action !== void 0 ? action : undefined,
|
|
47
|
+
className: `coop-search-box ${className !== null && className !== void 0 ? className : ""}`.trim(),
|
|
48
|
+
"data-size": size.length && size !== "md" ? size : undefined,
|
|
49
|
+
"data-variant": variant.length && variant !== "green" ? variant : undefined,
|
|
50
|
+
onSubmit: onSubmit ? handleSubmit : undefined,
|
|
51
|
+
};
|
|
52
|
+
const buttonProps = {
|
|
53
|
+
className: button === null || button === void 0 ? void 0 : button.className,
|
|
54
|
+
isLoading: isPending,
|
|
55
|
+
loadingText: (_b = button === null || button === void 0 ? void 0 : button.loadingText) !== null && _b !== void 0 ? _b : "",
|
|
56
|
+
size: size,
|
|
57
|
+
variant: variant,
|
|
58
|
+
};
|
|
59
|
+
const inputProps = Object.assign({ "aria-placeholder": (_c = placeholder !== null && placeholder !== void 0 ? placeholder : ariaPlaceholder) !== null && _c !== void 0 ? _c : undefined, autoCapitalize,
|
|
60
|
+
autoComplete,
|
|
61
|
+
id,
|
|
62
|
+
name,
|
|
63
|
+
placeholder, type: "search" }, props);
|
|
64
|
+
return (jsxs("form", Object.assign({}, formProps, { children: [jsx("label", { className: labelVisible ? "" : "sr-only", htmlFor: id, children: label }), jsxs("div", { className: "coop-search-box--inner", children: [jsx("input", Object.assign({}, inputProps)), jsx(Button, Object.assign({}, buttonProps, { children: button.label }))] })] })));
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export { SearchBox, SearchBox as default };
|
|
@@ -10,7 +10,7 @@ export interface TagProps extends HTMLAttributes<HTMLAnchorElement> {
|
|
|
10
10
|
/** **(Optional)** Specifies the URL that the Tag component will link to when clicked. */
|
|
11
11
|
href?: string;
|
|
12
12
|
/** **(Optional)** Specifies the Tag background color from the available options. */
|
|
13
|
-
tagColor?: "white" | "
|
|
13
|
+
tagColor?: "white" | "purple" | "pink" | "green" | "orange" | "red" | "yellow" | "lilac" | "blue" | "brown" | "grey";
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* Tag is a simple component that is meant to communicate a brief message such as categories.
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./components/Expandable";
|
|
|
6
6
|
export * from "./components/Image";
|
|
7
7
|
export * from "./components/Pill";
|
|
8
8
|
export * from "./components/RootSVG";
|
|
9
|
+
export * from "./components/SearchBox";
|
|
9
10
|
export * from "./components/Signpost";
|
|
10
11
|
export * from "./components/SkipNav";
|
|
11
12
|
export * from "./components/Tag";
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { Expandable } from './components/Expandable/Expandable.js';
|
|
|
6
6
|
export { Image } from './components/Image/Image.js';
|
|
7
7
|
export { Pill } from './components/Pill/Pill.js';
|
|
8
8
|
export { RootSVG } from './components/RootSVG/RootSVG.js';
|
|
9
|
+
export { SearchBox } from './components/SearchBox/SearchBox.js';
|
|
9
10
|
export { Signpost } from './components/Signpost/Signpost.js';
|
|
10
11
|
export { SkipNav } from './components/SkipNav/SkipNav.js';
|
|
11
12
|
export { Tag } from './components/Tag/Tag.js';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coopdigital/react",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.19.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"description": "",
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@axe-core/playwright": "^4.10.1",
|
|
48
|
-
"@coopdigital/styles": "^0.
|
|
49
|
-
"@playwright/test": "^1.
|
|
48
|
+
"@coopdigital/styles": "^0.16.0",
|
|
49
|
+
"@playwright/test": "^1.52.0",
|
|
50
50
|
"@storybook/addon-a11y": "^8.6.12",
|
|
51
51
|
"@storybook/addon-essentials": "^8.6.12",
|
|
52
52
|
"@storybook/addon-interactions": "^8.6.12",
|
|
@@ -58,14 +58,14 @@
|
|
|
58
58
|
"@storybook/test": "^8.6.12",
|
|
59
59
|
"@storybook/theming": "^8.6.12",
|
|
60
60
|
"@testing-library/jest-dom": "^6.6.3",
|
|
61
|
-
"@testing-library/react": "^16.
|
|
62
|
-
"@types/react": "^19.
|
|
63
|
-
"@types/react-dom": "^19.
|
|
61
|
+
"@testing-library/react": "^16.3.0",
|
|
62
|
+
"@types/react": "^19.1.2",
|
|
63
|
+
"@types/react-dom": "^19.1.2",
|
|
64
64
|
"storybook": "^8.6.12"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
|
-
"react": "^19.
|
|
68
|
-
"react-dom": "^19.
|
|
67
|
+
"react": "^19.1.0",
|
|
68
|
+
"react-dom": "^19.1.0"
|
|
69
69
|
},
|
|
70
|
-
"gitHead": "
|
|
70
|
+
"gitHead": "c3b9ce837060a373926286d2a770796f75f8c168"
|
|
71
71
|
}
|
|
@@ -7,6 +7,8 @@ import type {
|
|
|
7
7
|
|
|
8
8
|
import React, { useCallback, useState } from "react"
|
|
9
9
|
|
|
10
|
+
import { LoadingIcon } from "../Icon"
|
|
11
|
+
|
|
10
12
|
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
11
13
|
/** **(Optional)** Specifies the custom element to override default `a` or `button`. */
|
|
12
14
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -30,29 +32,22 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
30
32
|
/** **(Optional)** Specifies the Button size. */
|
|
31
33
|
size?: "sm" | "md" | "lg" | "xl"
|
|
32
34
|
/** **(Optional)** Specifies the Button variant. */
|
|
33
|
-
variant?:
|
|
35
|
+
variant?:
|
|
36
|
+
| "green"
|
|
37
|
+
| "blue"
|
|
38
|
+
| "white"
|
|
39
|
+
| "grey"
|
|
40
|
+
| "green-ghost"
|
|
41
|
+
| "blue-ghost"
|
|
42
|
+
| "white-ghost"
|
|
43
|
+
| "grey-ghost"
|
|
44
|
+
| "text"
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
type OnClickHandler =
|
|
37
48
|
| React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>
|
|
38
49
|
| ((event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => Promise<void>)
|
|
39
50
|
|
|
40
|
-
const LoadingIcon = () => {
|
|
41
|
-
return (
|
|
42
|
-
<svg
|
|
43
|
-
fill="none"
|
|
44
|
-
stroke="currentColor"
|
|
45
|
-
strokeLinecap="round"
|
|
46
|
-
strokeLinejoin="round"
|
|
47
|
-
strokeWidth="2"
|
|
48
|
-
viewBox="0 0 24 24"
|
|
49
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
50
|
-
>
|
|
51
|
-
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
52
|
-
</svg>
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
51
|
/**
|
|
57
52
|
* The Button component is an interactive element that people can use to take an action.
|
|
58
53
|
*/
|
|
@@ -67,7 +62,7 @@ export const Button = ({
|
|
|
67
62
|
loadingText = "Loading",
|
|
68
63
|
onClick,
|
|
69
64
|
size = "md",
|
|
70
|
-
variant = "
|
|
65
|
+
variant = "green",
|
|
71
66
|
...props
|
|
72
67
|
}: ButtonProps): JSX.Element => {
|
|
73
68
|
let element: ButtonProps["as"] = href ? "a" : "button"
|
|
@@ -10,7 +10,17 @@ export interface CardProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
10
10
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
11
|
as?: React.FC<AnchorHTMLAttributes<HTMLElement>> | ForwardRefExoticComponent<any> | string
|
|
12
12
|
/** **(Optional)** Specifies the background color of the card. */
|
|
13
|
-
background?:
|
|
13
|
+
background?:
|
|
14
|
+
| "purple"
|
|
15
|
+
| "pink"
|
|
16
|
+
| "green"
|
|
17
|
+
| "orange"
|
|
18
|
+
| "red"
|
|
19
|
+
| "yellow"
|
|
20
|
+
| "lilac"
|
|
21
|
+
| "blue"
|
|
22
|
+
| "brown"
|
|
23
|
+
| "grey"
|
|
14
24
|
/** **(Optional)** Represents the content inside the badge. */
|
|
15
25
|
badge?: React.ReactNode
|
|
16
26
|
/** **(Optional)** Specify badge position relative to top right corner. */
|
|
@@ -2,7 +2,19 @@ import type { DetailsHTMLAttributes, JSX } from "react"
|
|
|
2
2
|
|
|
3
3
|
export interface ExpandableProps extends DetailsHTMLAttributes<HTMLDetailsElement> {
|
|
4
4
|
/** **(Optional)** Specifies the Expandable background color from the available options. */
|
|
5
|
-
background?:
|
|
5
|
+
background?:
|
|
6
|
+
| "purple"
|
|
7
|
+
| "pink"
|
|
8
|
+
| "green"
|
|
9
|
+
| "orange"
|
|
10
|
+
| "red"
|
|
11
|
+
| "yellow"
|
|
12
|
+
| "lilac"
|
|
13
|
+
| "blue"
|
|
14
|
+
| "white"
|
|
15
|
+
| "transparent"
|
|
16
|
+
| "brown"
|
|
17
|
+
| "grey"
|
|
6
18
|
/** Represents the content inside the Expandable component, only visible when expanded. It can be any valid JSX or string. */
|
|
7
19
|
children: React.ReactNode
|
|
8
20
|
/** **(Optional)** Receives any className to be applied to Expandable component. */
|
|
@@ -17,7 +29,7 @@ export interface ExpandableProps extends DetailsHTMLAttributes<HTMLDetailsElemen
|
|
|
17
29
|
*/
|
|
18
30
|
|
|
19
31
|
export const Expandable = ({
|
|
20
|
-
background,
|
|
32
|
+
background = "grey",
|
|
21
33
|
children,
|
|
22
34
|
className = "",
|
|
23
35
|
summary,
|
|
@@ -9,6 +9,7 @@ export const AddIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "add",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const ArrowDownIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "arrow-down",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const ArrowLeftIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "arrow-left",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const ArrowRightIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "arrow-right",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const ArrowUpIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "arrow-up",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const AvatarAltIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "avatar-alt",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const AvatarIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "avatar",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const BasketIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "basket",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const CalendarIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "calendar",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const ChevronDownIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "chevron-down",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const ChevronLeftIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "chevron-left",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const ChevronRightIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "chevron-right",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const ChevronUpIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "chevron-up",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const ClockIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "clock",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const CloseAltIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "close-alt",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const CloseIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "close",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const CoopCardIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "coop-card",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const CoopIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "coop",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const CoopLocationIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "coop-location",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const DownloadIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "download",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const HomeIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "home",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const InformationIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "information",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type SVGProps, useId } from "react"
|
|
2
|
+
|
|
3
|
+
interface IconProps extends SVGProps<SVGSVGElement> {
|
|
4
|
+
alt?: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const LoadingIcon = ({ alt, className, ...props }: IconProps) => {
|
|
8
|
+
const id = useId()
|
|
9
|
+
const componentProps = {
|
|
10
|
+
"aria-labelledby": alt ? id : undefined,
|
|
11
|
+
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "loading",
|
|
13
|
+
fill: "none",
|
|
14
|
+
role: alt ? "img" : undefined,
|
|
15
|
+
viewBox: "0 0 24 24",
|
|
16
|
+
...props,
|
|
17
|
+
}
|
|
18
|
+
return (
|
|
19
|
+
<svg {...componentProps}>
|
|
20
|
+
{alt ? <title id={id}>{alt}</title> : null}
|
|
21
|
+
<path
|
|
22
|
+
d="M10.72 19.9a8 8 0 0 1-6.5-9.79 7.77 7.77 0 0 1 6.18-5.95 8 8 0 0 1 9.49 6.52A1.54 1.54 0 0 0 21.38 12h.13a1.37 1.37 0 0 0 1.38-1.54 11 11 0 1 0-12.7 12.39A1.54 1.54 0 0 0 12 21.34a1.47 1.47 0 0 0-1.28-1.44"
|
|
23
|
+
fill="currentColor"
|
|
24
|
+
/>
|
|
25
|
+
</svg>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -9,6 +9,7 @@ export const LocationIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "location",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const MailIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "mail",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const MenuIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "menu",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const MessageIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "message",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const MinusIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "minus",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const OpenNewIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "open-new",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const PencilIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "pencil",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const PhoneIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "phone",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const QuestionIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "question",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const ScooterIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "scooter",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const SearchIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "search",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const SettingsIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "settings",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const TickAltIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "tick-alt",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const TickIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "tick",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const VanIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "van",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const WarningIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "warning",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -9,6 +9,7 @@ export const WriteIcon = ({ alt, className, ...props }: IconProps) => {
|
|
|
9
9
|
const componentProps = {
|
|
10
10
|
"aria-labelledby": alt ? id : undefined,
|
|
11
11
|
className: `coop-icon ${className ?? ""}`.trim(),
|
|
12
|
+
"data-icon": "write",
|
|
12
13
|
fill: "none",
|
|
13
14
|
role: alt ? "img" : undefined,
|
|
14
15
|
viewBox: "0 0 32 32",
|
|
@@ -20,6 +20,7 @@ export { CoopLocationIcon } from "./CoopLocationIcon"
|
|
|
20
20
|
export { DownloadIcon } from "./DownloadIcon"
|
|
21
21
|
export { HomeIcon } from "./HomeIcon"
|
|
22
22
|
export { InformationIcon } from "./InformationIcon"
|
|
23
|
+
export { LoadingIcon } from "./LoadingIcon"
|
|
23
24
|
export { LocationIcon } from "./LocationIcon"
|
|
24
25
|
export { MailIcon } from "./MailIcon"
|
|
25
26
|
export { MenuIcon } from "./MenuIcon"
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React, { HTMLAttributes, useCallback, useId, useState } from "react"
|
|
2
|
+
import { type JSX } from "react"
|
|
3
|
+
|
|
4
|
+
import { Button, type ButtonProps } from "../Button"
|
|
5
|
+
import { SearchIcon } from "../Icon"
|
|
6
|
+
|
|
7
|
+
export interface SearchBoxProps extends HTMLAttributes<HTMLInputElement> {
|
|
8
|
+
/** **(Optional)** Server endpoint to submit the form. Will be ignored if onSubmit is also set. */
|
|
9
|
+
action?: string
|
|
10
|
+
/** **(Optional)** Whether or not to enable auto complete on the input field. Default: `off`. */
|
|
11
|
+
autoComplete?: "off" | "on"
|
|
12
|
+
/** **(Optional)** Props to forward to the Button element. Use `label` to set Button text. */
|
|
13
|
+
button?: Pick<ButtonProps, "className" | "loadingText"> & { label?: React.ReactNode }
|
|
14
|
+
/** **(Optional)** Receives any className to be applied to SearchBox component. */
|
|
15
|
+
className?: string
|
|
16
|
+
/** Receives the label to be applied to SearchBox component. */
|
|
17
|
+
label: string
|
|
18
|
+
/** **(Optional)** Receives whether label is visible to human or only screen readers. */
|
|
19
|
+
labelVisible?: boolean
|
|
20
|
+
/** **(Optional)** Name of the input element, also used as the URL search parameter. Defaults to `query` */
|
|
21
|
+
name?: string
|
|
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
|
+
onSubmit?: React.FormEventHandler<HTMLElement> | undefined
|
|
24
|
+
/** **(Optional)** Receives any placeholder for SearchBox component. */
|
|
25
|
+
placeholder?: string
|
|
26
|
+
/** **(Optional)** Receives any size to be applied to SearchBox component. */
|
|
27
|
+
size?: string
|
|
28
|
+
/** **(Optional)** Receives the variant to be applied to SearchBox component. */
|
|
29
|
+
variant?:
|
|
30
|
+
| "green"
|
|
31
|
+
| "blue"
|
|
32
|
+
| "white"
|
|
33
|
+
| "grey"
|
|
34
|
+
| "green-ghost"
|
|
35
|
+
| "blue-ghost"
|
|
36
|
+
| "white-ghost"
|
|
37
|
+
| "grey-ghost"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const defaultButtonProps: SearchBoxProps["button"] = {
|
|
41
|
+
label: React.createElement(SearchIcon, { alt: "Search", stroke: "currentColor", strokeWidth: 2 }),
|
|
42
|
+
loadingText: "",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* SearchBox component allows a person to enter a word or a phrase to find relevant content.
|
|
47
|
+
*
|
|
48
|
+
* The `label` prop is mandatory but not visible by default (it is visible for screen readers). You can make it visible to humans by setting `labelVisible` to true.
|
|
49
|
+
*
|
|
50
|
+
* If you are using a single icon with no text for `button.label`, ensure you pass an `alt` property to the icon for accessibility purposes.
|
|
51
|
+
*
|
|
52
|
+
* You can provide an `action` to submit input like a normal HTML form, or an `onSubmit` callback to handle submissions client-side.
|
|
53
|
+
*
|
|
54
|
+
* **Good to know:** SearchBox requires both `SearchBox` and `Button` css/scss files.
|
|
55
|
+
*
|
|
56
|
+
* ```
|
|
57
|
+
* import "@coopdigital/styles/components/Searchbox.css"
|
|
58
|
+
* import "@coopdigital/styles/components/Button.css"
|
|
59
|
+
*
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export const SearchBox = ({
|
|
63
|
+
action,
|
|
64
|
+
"aria-placeholder": ariaPlaceholder,
|
|
65
|
+
autoCapitalize = "off",
|
|
66
|
+
autoComplete = "off",
|
|
67
|
+
button = defaultButtonProps,
|
|
68
|
+
className,
|
|
69
|
+
label,
|
|
70
|
+
labelVisible = false,
|
|
71
|
+
name = "query",
|
|
72
|
+
onSubmit,
|
|
73
|
+
placeholder,
|
|
74
|
+
size = "md",
|
|
75
|
+
variant = "green",
|
|
76
|
+
...props
|
|
77
|
+
}: SearchBoxProps): JSX.Element => {
|
|
78
|
+
const [isPending, setIsPending] = useState(false)
|
|
79
|
+
|
|
80
|
+
const handleSubmit = useCallback(
|
|
81
|
+
async (event: React.FormEvent<HTMLFormElement>) => {
|
|
82
|
+
event.preventDefault()
|
|
83
|
+
|
|
84
|
+
if (isPending || !onSubmit) return
|
|
85
|
+
|
|
86
|
+
setIsPending(true)
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await Promise.resolve(onSubmit(event))
|
|
90
|
+
} finally {
|
|
91
|
+
setIsPending(false)
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
[onSubmit, isPending]
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const id = useId()
|
|
98
|
+
|
|
99
|
+
const formProps = {
|
|
100
|
+
action: action ?? undefined,
|
|
101
|
+
className: `coop-search-box ${className ?? ""}`.trim(),
|
|
102
|
+
"data-size": size.length && size !== "md" ? size : undefined,
|
|
103
|
+
"data-variant": variant.length && variant !== "green" ? variant : undefined,
|
|
104
|
+
onSubmit: onSubmit ? handleSubmit : undefined,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const buttonProps = {
|
|
108
|
+
className: button?.className,
|
|
109
|
+
isLoading: isPending,
|
|
110
|
+
loadingText: button?.loadingText ?? "",
|
|
111
|
+
size: size as keyof ButtonProps["size"],
|
|
112
|
+
variant: variant as keyof ButtonProps["variant"],
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const inputProps = {
|
|
116
|
+
"aria-placeholder": placeholder ?? ariaPlaceholder ?? undefined,
|
|
117
|
+
autoCapitalize,
|
|
118
|
+
autoComplete,
|
|
119
|
+
id,
|
|
120
|
+
name,
|
|
121
|
+
placeholder,
|
|
122
|
+
type: "search",
|
|
123
|
+
...props,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<form {...formProps}>
|
|
128
|
+
<label className={labelVisible ? "" : "sr-only"} htmlFor={id}>
|
|
129
|
+
{label}
|
|
130
|
+
</label>
|
|
131
|
+
<div className="coop-search-box--inner">
|
|
132
|
+
<input {...inputProps}></input>
|
|
133
|
+
<Button {...buttonProps}>{button.label}</Button>
|
|
134
|
+
</div>
|
|
135
|
+
</form>
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export default SearchBox
|
|
@@ -15,7 +15,6 @@ export interface TagProps extends HTMLAttributes<HTMLAnchorElement> {
|
|
|
15
15
|
/** **(Optional)** Specifies the Tag background color from the available options. */
|
|
16
16
|
tagColor?:
|
|
17
17
|
| "white"
|
|
18
|
-
| "grey"
|
|
19
18
|
| "purple"
|
|
20
19
|
| "pink"
|
|
21
20
|
| "green"
|
|
@@ -24,6 +23,8 @@ export interface TagProps extends HTMLAttributes<HTMLAnchorElement> {
|
|
|
24
23
|
| "yellow"
|
|
25
24
|
| "lilac"
|
|
26
25
|
| "blue"
|
|
26
|
+
| "brown"
|
|
27
|
+
| "grey"
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
/**
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./components/Expandable"
|
|
|
6
6
|
export * from "./components/Image"
|
|
7
7
|
export * from "./components/Pill"
|
|
8
8
|
export * from "./components/RootSVG"
|
|
9
|
+
export * from "./components/SearchBox"
|
|
9
10
|
export * from "./components/Signpost"
|
|
10
11
|
export * from "./components/SkipNav"
|
|
11
12
|
export * from "./components/Tag"
|