@coopdigital/react 0.8.1 → 0.9.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.
@@ -1,5 +1,6 @@
1
- import React, { AnchorHTMLAttributes, ButtonHTMLAttributes, DetailedHTMLProps, ForwardRefExoticComponent, JSX } from "react";
2
- export interface ButtonProps extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
1
+ import type { AnchorHTMLAttributes, ButtonHTMLAttributes, ForwardRefExoticComponent, JSX } from "react";
2
+ import React from "react";
3
+ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
3
4
  /** **(Optional)** Specifies a custom aria-label. */
4
5
  ariaLabel?: string;
5
6
  /** **(Optional)** Specifies the custom element to override default `a` or `button`. */
@@ -10,19 +11,23 @@ export interface ButtonProps extends DetailedHTMLProps<ButtonHTMLAttributes<HTML
10
11
  className?: string;
11
12
  /** **(Optional)** Specifies the URL that the Button component will link to when clicked. */
12
13
  href?: string;
13
- /** **(Optional)** Specifies whether the Button should look like disabled. Note that isDisabled will not actually disable the button, just make it look disabled. Disabled buttons can be bad UX. */
14
+ /** **(Optional)** Specifies whether the Button should be disabled. This will not actually disable the button, just make it look disabled. Disabled buttons can be bad UX. */
14
15
  isDisabled?: boolean;
15
16
  /** **(Optional)** Specifies whether the Button should be full width. */
16
17
  isFullWidth?: boolean;
17
18
  /** **(Optional)** Specifies whether the Button is loading. */
18
19
  isLoading?: boolean;
20
+ /** **(Optional)** Specifies loading text to show when the Button is in a pending state */
21
+ loadingText?: string;
22
+ /** **(Optional)** Callback to run when the button is pressed. If this is an async function, it will be awaited and the button will be in a pending state until the promise is resolved. */
23
+ onClick?: (event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;
19
24
  /** **(Optional)** Specifies the Button size. */
20
25
  size?: "sm" | "md" | "lg" | "xl";
21
26
  /** **(Optional)** Specifies the Button variant. */
22
- variant?: "primary" | "secondary" | "white" | "grey" | "text";
27
+ variant?: "primary" | "secondary" | "white" | "grey" | "ghost" | "text";
23
28
  }
24
29
  /**
25
30
  * The Button component is an interactive element that people can use to take an action.
26
31
  */
27
- export declare const Button: ({ ariaLabel, as, children, className, href, isDisabled, isFullWidth, isLoading, size, variant, ...props }: ButtonProps) => JSX.Element;
32
+ export declare const Button: ({ ariaLabel, as, children, className, href, isDisabled, isFullWidth, isLoading, loadingText, onClick, size, variant, ...props }: ButtonProps) => JSX.Element;
28
33
  export default Button;
@@ -1,17 +1,34 @@
1
- import { __rest } from '../../node_modules/tslib/tslib.es6.js';
2
- import React from 'react';
1
+ import { __rest, __awaiter } from '../../node_modules/tslib/tslib.es6.js';
2
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
+ import React, { useState, useCallback } from 'react';
3
4
 
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
+ };
4
8
  /**
5
9
  * The Button component is an interactive element that people can use to take an action.
6
10
  */
7
11
  const Button = (_a) => {
8
- var { ariaLabel, as, children, className = "", href, isDisabled = false, isFullWidth = false, isLoading = false, size = "md", variant = "primary" } = _a, props = __rest(_a, ["ariaLabel", "as", "children", "className", "href", "isDisabled", "isFullWidth", "isLoading", "size", "variant"]);
12
+ var { ariaLabel, as, children, className = "", href, isDisabled = false, isFullWidth = false, isLoading = false, loadingText = "Loading", onClick, size = "md", variant = "primary" } = _a, props = __rest(_a, ["ariaLabel", "as", "children", "className", "href", "isDisabled", "isFullWidth", "isLoading", "loadingText", "onClick", "size", "variant"]);
9
13
  let element = href ? "a" : "button";
10
14
  if (as) {
11
15
  element = as;
12
16
  }
13
- const componentProps = Object.assign({ "aria-disabled": isDisabled ? true : undefined, "aria-label": ariaLabel, className: `${variant == "text" ? "coop-link" : "coop-button"} ${isDisabled ? "grayscale" : ""} ${className}`, "data-loading": isLoading ? true : undefined, "data-size": size.length && size != "md" ? size : undefined, "data-variant": variant !== "text" ? variant : undefined, "data-width": isFullWidth ? "full" : undefined, href }, props);
14
- return React.createElement(element, Object.assign({}, componentProps), children);
17
+ const [isPending, setIsPending] = useState(false);
18
+ const handleClick = useCallback((event) => __awaiter(void 0, void 0, void 0, function* () {
19
+ if (isPending || !onClick)
20
+ return;
21
+ setIsPending(true);
22
+ try {
23
+ yield Promise.resolve(onClick(event));
24
+ }
25
+ finally {
26
+ setIsPending(false);
27
+ }
28
+ }), [onClick, isPending]);
29
+ const componentProps = Object.assign({ "aria-disabled": isDisabled ? true : undefined, "aria-label": ariaLabel, "aria-live": "assertive", className: `${variant == "text" ? "coop-link" : "coop-button"} ${className}`, "data-loading": isLoading || isPending ? true : undefined, "data-size": size.length && size != "md" ? size : undefined, "data-variant": variant !== "text" ? variant : undefined, "data-width": isFullWidth ? "full" : undefined, href, onClick: handleClick }, props);
30
+ const finalChildren = isPending || isLoading ? (jsxs(Fragment, { children: [loadingText, jsx(LoadingIcon, {})] })) : (children);
31
+ return React.createElement(element, Object.assign({}, componentProps), finalChildren);
15
32
  };
16
33
 
17
34
  export { Button, Button as default };
@@ -0,0 +1,35 @@
1
+ import type { AnchorHTMLAttributes, ForwardRefExoticComponent, HTMLAttributes, JSX } from "react";
2
+ import React from "react";
3
+ import { ImageProps } from "../Image";
4
+ export interface CardProps extends HTMLAttributes<HTMLDivElement> {
5
+ /** **(Optional)** Specifies the custom element to override default `a` */
6
+ as?: React.FC<AnchorHTMLAttributes<HTMLElement>> | ForwardRefExoticComponent<any> | string;
7
+ /** **(Optional)** Specifies the background color of the card. */
8
+ background?: "purple" | "pink" | "green" | "orange" | "red" | "yellow" | "lilac" | "blue";
9
+ /** **(Optional)** Represents the content inside the badge. */
10
+ badge?: React.ReactNode;
11
+ /** **(Optional)** Specify badge position relative to top right corner. */
12
+ badgePosition?: "inset" | "popout";
13
+ /** **(Optional)** Specifies if chevron will be visible. */
14
+ chevron?: boolean;
15
+ /** **(Optional)** Represents the content inside the Card component. It can be any valid JSX or string. */
16
+ children?: React.ReactNode;
17
+ /** **(Optional)** Specifies the heading level of the card's title. */
18
+ headingLevel?: "h2" | "h3" | "h4" | "h5" | "h6";
19
+ /** **(Optional)** Specifies the URL that the Card component will link to when clicked. */
20
+ href?: string;
21
+ /** Specifies the image URL and alt text of the Card */
22
+ image: ImageProps;
23
+ /** **(Optional)** Specifies the position of the image in the Card */
24
+ imagePosition?: "left" | "right";
25
+ /** **(Optional)** Specifies the label of the Card */
26
+ label?: string;
27
+ /** **(Optional)** Specifies the label background of the Card */
28
+ labelBackground?: "yellow" | "green" | "pink" | "purple" | "orange" | "red" | "lilac" | "blue";
29
+ /** **(Optional)** Specifies the layout of the Card */
30
+ layout?: "vertical" | "horizontal";
31
+ /** Specifies the title of the Card */
32
+ title: string;
33
+ }
34
+ export declare const Card: ({ as, background, badge, badgePosition, chevron, children, headingLevel, href, image, imagePosition, label, labelBackground, layout, title, ...props }: CardProps) => JSX.Element;
35
+ export default Card;
@@ -0,0 +1,26 @@
1
+ import { __rest } from '../../node_modules/tslib/tslib.es6.js';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import React from 'react';
4
+ import { Image } from '../Image/Image.js';
5
+
6
+ function getCardLinkElement(as, href) {
7
+ let element = href ? "a" : "div";
8
+ if (as) {
9
+ element = as;
10
+ }
11
+ return {
12
+ element,
13
+ props: {
14
+ href,
15
+ },
16
+ };
17
+ }
18
+ const Card = (_a) => {
19
+ var { as, background, badge, badgePosition = "inset", chevron = false, children, headingLevel = "h3", href, image, imagePosition = "left", label = "", labelBackground, layout = "vertical", title } = _a, props = __rest(_a, ["as", "background", "badge", "badgePosition", "chevron", "children", "headingLevel", "href", "image", "imagePosition", "label", "labelBackground", "layout", "title"]);
20
+ const linkElement = getCardLinkElement(as, href);
21
+ const imageProps = Object.assign({ crop: "wide" }, image);
22
+ const componentProps = Object.assign({ className: "coop-card", "data-background": background, "data-badge-pos": badgePosition, "data-image-pos": imagePosition, "data-label-background": labelBackground, "data-layout": layout }, props);
23
+ return (jsxs("article", Object.assign({}, componentProps, { children: [image && jsx(Image, Object.assign({}, imageProps)), badge && jsx("div", { className: "coop-card--badge", children: badge }), jsxs("div", { className: "coop-card--inner", children: [jsxs("div", { className: "coop-card--content", children: [label && (jsx("p", { className: "coop-card--label", children: jsx("span", { children: label }) })), React.createElement(linkElement.element, linkElement.props, React.createElement(headingLevel, {}, title)), children] }), chevron && (jsx("span", { "aria-hidden": "true", className: "coop-card--icon", role: "presentation", children: jsx("svg", { viewBox: "0 0 16 29", children: jsx("path", { d: "M2 28.1a1.6 1.6 0 0 1-1.2-2.7l11-10.9-11-11A1.6 1.6 0 1 1 3 1.5l12 12a1.6 1.6 0 0 1 0 2.2l-12 12c-.3.4-.7.5-1 .5z" }) }) }))] })] })));
24
+ };
25
+
26
+ export { Card, Card as default };
@@ -0,0 +1,4 @@
1
+ import Card from "./Card";
2
+ export default Card;
3
+ export { Card };
4
+ export * from "./Card";
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from "./components/AlertBanner";
2
2
  export * from "./components/Button";
3
- export * from "./components/EditorialCard";
3
+ export * from "./components/Card";
4
4
  export * from "./components/Image";
5
5
  export * from "./components/Pill";
6
6
  export * from "./components/SkipNav";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { AlertBanner } from './components/AlertBanner/AlertBanner.js';
2
2
  export { Button } from './components/Button/Button.js';
3
- export { EditorialCard } from './components/EditorialCard/EditorialCard.js';
3
+ export { Card } from './components/Card/Card.js';
4
4
  export { Image } from './components/Image/Image.js';
5
5
  export { Pill } from './components/Pill/Pill.js';
6
6
  export { SkipNav } from './components/SkipNav/SkipNav.js';
@@ -27,9 +27,19 @@ function __rest(s, e) {
27
27
  return t;
28
28
  }
29
29
 
30
+ function __awaiter(thisArg, _arguments, P, generator) {
31
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
32
+ return new (P || (P = Promise))(function (resolve, reject) {
33
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
34
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
35
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
36
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
37
+ });
38
+ }
39
+
30
40
  typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
31
41
  var e = new Error(message);
32
42
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
33
43
  };
34
44
 
35
- export { __rest };
45
+ export { __awaiter, __rest };
package/package.json CHANGED
@@ -1,15 +1,20 @@
1
1
  {
2
2
  "name": "@coopdigital/react",
3
3
  "type": "module",
4
- "version": "0.8.1",
5
- "main": "dist/index.js",
4
+ "version": "0.9.0",
6
5
  "private": false,
7
6
  "publishConfig": {
8
7
  "access": "public"
9
8
  },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
10
15
  "files": [
11
16
  "dist",
12
- "src",
17
+ "src/**/*.{tsx,ts}",
13
18
  "!**/__{screenshots,tests}__",
14
19
  "!**/*.{stories,test}.*"
15
20
  ],
@@ -39,31 +44,28 @@
39
44
  "description": "",
40
45
  "devDependencies": {
41
46
  "@axe-core/playwright": "^4.10.1",
42
- "@coopdigital/styles": "^0.8.0",
47
+ "@coopdigital/styles": "^0.9.0",
43
48
  "@playwright/test": "^1.51.1",
44
- "@rollup/plugin-node-resolve": "^16.0.1",
45
- "@rollup/plugin-typescript": "^12.1.2",
46
- "@storybook/addon-a11y": "^8.6.7",
47
- "@storybook/addon-essentials": "^8.6.7",
48
- "@storybook/addon-interactions": "^8.6.7",
49
- "@storybook/addon-mdx-gfm": "^8.6.7",
50
- "@storybook/addon-onboarding": "^8.6.7",
51
- "@storybook/blocks": "^8.6.7",
52
- "@storybook/manager-api": "^8.6.7",
53
- "@storybook/react": "^8.6.7",
54
- "@storybook/react-vite": "^8.6.7",
55
- "@storybook/test": "^8.6.7",
56
- "@storybook/theming": "^8.6.7",
49
+ "@storybook/addon-a11y": "^8.6.11",
50
+ "@storybook/addon-essentials": "^8.6.11",
51
+ "@storybook/addon-interactions": "^8.6.11",
52
+ "@storybook/addon-mdx-gfm": "^8.6.11",
53
+ "@storybook/addon-onboarding": "^8.6.11",
54
+ "@storybook/blocks": "^8.6.11",
55
+ "@storybook/manager-api": "^8.6.11",
56
+ "@storybook/react": "^8.6.11",
57
+ "@storybook/react-vite": "^8.6.11",
58
+ "@storybook/test": "^8.6.11",
59
+ "@storybook/theming": "^8.6.11",
57
60
  "@testing-library/jest-dom": "^6.6.3",
58
61
  "@testing-library/react": "^16.2.0",
59
62
  "@types/react": "^19.0.12",
60
63
  "@types/react-dom": "^19.0.4",
61
- "rollup": "^4.36.0",
62
- "storybook": "^8.6.7"
64
+ "storybook": "^8.6.11"
63
65
  },
64
66
  "peerDependencies": {
65
67
  "react": "^19.0.0",
66
68
  "react-dom": "^19.0.0"
67
69
  },
68
- "gitHead": "d113e05914bad6b718e531e27c21a6db1c58f774"
70
+ "gitHead": "1c68d0c54618e299a4e5d7a89bf43ff17841a3e0"
69
71
  }
@@ -1,13 +1,13 @@
1
- import React, {
1
+ import type {
2
2
  AnchorHTMLAttributes,
3
3
  ButtonHTMLAttributes,
4
- DetailedHTMLProps,
5
4
  ForwardRefExoticComponent,
6
5
  JSX,
7
6
  } from "react"
8
7
 
9
- export interface ButtonProps
10
- extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
8
+ import React, { useCallback, useState } from "react"
9
+
10
+ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
11
11
  /** **(Optional)** Specifies a custom aria-label. */
12
12
  ariaLabel?: string
13
13
  /** **(Optional)** Specifies the custom element to override default `a` or `button`. */
@@ -19,16 +19,40 @@ export interface ButtonProps
19
19
  className?: string
20
20
  /** **(Optional)** Specifies the URL that the Button component will link to when clicked. */
21
21
  href?: string
22
- /** **(Optional)** Specifies whether the Button should look like disabled. Note that isDisabled will not actually disable the button, just make it look disabled. Disabled buttons can be bad UX. */
22
+ /** **(Optional)** Specifies whether the Button should be disabled. This will not actually disable the button, just make it look disabled. Disabled buttons can be bad UX. */
23
23
  isDisabled?: boolean
24
24
  /** **(Optional)** Specifies whether the Button should be full width. */
25
25
  isFullWidth?: boolean
26
26
  /** **(Optional)** Specifies whether the Button is loading. */
27
27
  isLoading?: boolean
28
+ /** **(Optional)** Specifies loading text to show when the Button is in a pending state */
29
+ loadingText?: string
30
+ /** **(Optional)** Callback to run when the button is pressed. If this is an async function, it will be awaited and the button will be in a pending state until the promise is resolved. */
31
+ onClick?: (event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void
28
32
  /** **(Optional)** Specifies the Button size. */
29
33
  size?: "sm" | "md" | "lg" | "xl"
30
34
  /** **(Optional)** Specifies the Button variant. */
31
- variant?: "primary" | "secondary" | "white" | "grey" | "text"
35
+ variant?: "primary" | "secondary" | "white" | "grey" | "ghost" | "text"
36
+ }
37
+
38
+ type OnClickHandler =
39
+ | React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>
40
+ | ((event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => Promise<void>)
41
+
42
+ const LoadingIcon = () => {
43
+ return (
44
+ <svg
45
+ fill="none"
46
+ stroke="currentColor"
47
+ strokeLinecap="round"
48
+ strokeLinejoin="round"
49
+ strokeWidth="2"
50
+ viewBox="0 0 24 24"
51
+ xmlns="http://www.w3.org/2000/svg"
52
+ >
53
+ <path d="M21 12a9 9 0 1 1-6.219-8.56" />
54
+ </svg>
55
+ )
32
56
  }
33
57
 
34
58
  /**
@@ -43,6 +67,8 @@ export const Button = ({
43
67
  isDisabled = false,
44
68
  isFullWidth = false,
45
69
  isLoading = false,
70
+ loadingText = "Loading",
71
+ onClick,
46
72
  size = "md",
47
73
  variant = "primary",
48
74
  ...props
@@ -52,17 +78,47 @@ export const Button = ({
52
78
  element = as
53
79
  }
54
80
 
81
+ const [isPending, setIsPending] = useState(false)
82
+
83
+ const handleClick: OnClickHandler = useCallback(
84
+ async (event) => {
85
+ if (isPending || !onClick) return
86
+
87
+ setIsPending(true)
88
+
89
+ try {
90
+ await Promise.resolve(onClick(event as React.MouseEvent<HTMLButtonElement>))
91
+ } finally {
92
+ setIsPending(false)
93
+ }
94
+ },
95
+ [onClick, isPending]
96
+ )
97
+
55
98
  const componentProps = {
56
99
  "aria-disabled": isDisabled ? true : undefined,
57
100
  "aria-label": ariaLabel,
58
- className: `${variant == "text" ? "coop-link" : "coop-button"} ${isDisabled ? "grayscale" : ""} ${className}`,
59
- "data-loading": isLoading ? true : undefined,
101
+ "aria-live": "assertive" as keyof ButtonHTMLAttributes<HTMLButtonElement>["aria-live"],
102
+ className: `${variant == "text" ? "coop-link" : "coop-button"} ${className}`,
103
+ "data-loading": isLoading || isPending ? true : undefined,
60
104
  "data-size": size.length && size != "md" ? size : undefined,
61
105
  "data-variant": variant !== "text" ? variant : undefined,
62
106
  "data-width": isFullWidth ? "full" : undefined,
63
107
  href,
108
+ onClick: handleClick,
64
109
  ...props,
65
110
  }
66
- return React.createElement(element, { ...componentProps }, children)
111
+
112
+ const finalChildren =
113
+ isPending || isLoading ? (
114
+ <>
115
+ {loadingText}
116
+ <LoadingIcon />
117
+ </>
118
+ ) : (
119
+ children
120
+ )
121
+
122
+ return React.createElement(element, { ...componentProps }, finalChildren)
67
123
  }
68
124
  export default Button
@@ -0,0 +1,120 @@
1
+ import type { AnchorHTMLAttributes, ForwardRefExoticComponent, HTMLAttributes, JSX } from "react"
2
+
3
+ import React from "react"
4
+
5
+ import { Image, ImageProps } from "../Image"
6
+
7
+ export interface CardProps extends HTMLAttributes<HTMLDivElement> {
8
+ //export interface CardProps {
9
+ /** **(Optional)** Specifies the custom element to override default `a` */
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ as?: React.FC<AnchorHTMLAttributes<HTMLElement>> | ForwardRefExoticComponent<any> | string
12
+ /** **(Optional)** Specifies the background color of the card. */
13
+ background?: "purple" | "pink" | "green" | "orange" | "red" | "yellow" | "lilac" | "blue"
14
+ /** **(Optional)** Represents the content inside the badge. */
15
+ badge?: React.ReactNode
16
+ /** **(Optional)** Specify badge position relative to top right corner. */
17
+ badgePosition?: "inset" | "popout"
18
+ /** **(Optional)** Specifies if chevron will be visible. */
19
+ chevron?: boolean
20
+ /** **(Optional)** Represents the content inside the Card component. It can be any valid JSX or string. */
21
+ children?: React.ReactNode
22
+ /** **(Optional)** Specifies the heading level of the card's title. */
23
+ headingLevel?: "h2" | "h3" | "h4" | "h5" | "h6"
24
+ /** **(Optional)** Specifies the URL that the Card component will link to when clicked. */
25
+ href?: string
26
+ /** Specifies the image URL and alt text of the Card */
27
+ image: ImageProps
28
+ /** **(Optional)** Specifies the position of the image in the Card */
29
+ imagePosition?: "left" | "right"
30
+ /** **(Optional)** Specifies the label of the Card */
31
+ label?: string
32
+ /** **(Optional)** Specifies the label background of the Card */
33
+ labelBackground?: "yellow" | "green" | "pink" | "purple" | "orange" | "red" | "lilac" | "blue"
34
+ /** **(Optional)** Specifies the layout of the Card */
35
+ layout?: "vertical" | "horizontal"
36
+
37
+ /** Specifies the title of the Card */
38
+ title: string
39
+ }
40
+
41
+ function getCardLinkElement(as: CardProps["as"], href?: string) {
42
+ let element: CardProps["as"] = href ? "a" : "div"
43
+
44
+ if (as) {
45
+ element = as
46
+ }
47
+
48
+ return {
49
+ element,
50
+ props: {
51
+ href,
52
+ },
53
+ }
54
+ }
55
+
56
+ export const Card = ({
57
+ as,
58
+ background,
59
+ badge,
60
+ badgePosition = "inset",
61
+ chevron = false,
62
+ children,
63
+ headingLevel = "h3",
64
+ href,
65
+ image,
66
+ imagePosition = "left",
67
+ label = "",
68
+ labelBackground,
69
+ layout = "vertical",
70
+ title,
71
+ ...props
72
+ }: CardProps): JSX.Element => {
73
+ const linkElement = getCardLinkElement(as, href)
74
+
75
+ const imageProps: ImageProps = {
76
+ crop: "wide",
77
+ ...image,
78
+ }
79
+
80
+ const componentProps = {
81
+ className: "coop-card",
82
+ "data-background": background,
83
+ "data-badge-pos": badgePosition,
84
+ "data-image-pos": imagePosition,
85
+ "data-label-background": labelBackground,
86
+ "data-layout": layout,
87
+ ...props,
88
+ }
89
+
90
+ return (
91
+ <article {...componentProps}>
92
+ {image && <Image {...imageProps} />}
93
+ {badge && <div className="coop-card--badge">{badge}</div>}
94
+ <div className="coop-card--inner">
95
+ <div className="coop-card--content">
96
+ {label && (
97
+ <p className="coop-card--label">
98
+ <span>{label}</span>
99
+ </p>
100
+ )}
101
+ {React.createElement(
102
+ linkElement.element,
103
+ linkElement.props,
104
+ React.createElement(headingLevel, {}, title)
105
+ )}
106
+ {children}
107
+ </div>
108
+ {chevron && (
109
+ <span aria-hidden="true" className="coop-card--icon" role="presentation">
110
+ <svg viewBox="0 0 16 29">
111
+ <path d="M2 28.1a1.6 1.6 0 0 1-1.2-2.7l11-10.9-11-11A1.6 1.6 0 1 1 3 1.5l12 12a1.6 1.6 0 0 1 0 2.2l-12 12c-.3.4-.7.5-1 .5z" />
112
+ </svg>
113
+ </span>
114
+ )}
115
+ </div>
116
+ </article>
117
+ )
118
+ }
119
+
120
+ export default Card
@@ -0,0 +1,5 @@
1
+ import Card from "./Card"
2
+
3
+ export default Card
4
+ export { Card }
5
+ export * from "./Card"
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from "./components/AlertBanner"
2
2
  export * from "./components/Button"
3
- export * from "./components/EditorialCard"
3
+ export * from "./components/Card"
4
4
  export * from "./components/Image"
5
5
  export * from "./components/Pill"
6
6
  export * from "./components/SkipNav"
@@ -1,23 +0,0 @@
1
- import type { AnchorHTMLAttributes, ForwardRefExoticComponent, JSX } from "react";
2
- import React from "react";
3
- import { ImageProps } from "../Image";
4
- export interface EditorialCardProps {
5
- /** **(Optional)** Specifies the custom element to override default `a` */
6
- as?: React.FC<AnchorHTMLAttributes<HTMLElement>> | ForwardRefExoticComponent<any> | string;
7
- /** **(Optional)** Represents the content inside the EditorialCard component. It can be any valid JSX or string. */
8
- children?: React.ReactNode;
9
- /** **(Optional)** Specifies the heading level of the card's title. */
10
- headingLevel?: "h2" | "h3" | "h4" | "h5" | "h6";
11
- /** **(Optional)** Specifies the URL that the EditorialCard component will link to when clicked. */
12
- href?: string;
13
- /** Specifies the image URL and alt text of the EditorialCard */
14
- image: ImageProps;
15
- /** **(Optional)** Specifies the label of the EditorialCard */
16
- label?: string;
17
- /** **(Optional)** Specifies the layout of the EditorialCard */
18
- layout?: "vertical" | "horizontal";
19
- /** Specifies the title of the EditorialCard */
20
- title: string;
21
- }
22
- export declare const EditorialCard: ({ as, children, headingLevel, href, image, label, layout, title, }: EditorialCardProps) => JSX.Element;
23
- export default EditorialCard;
@@ -1,27 +0,0 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
2
- import React from 'react';
3
- import { Image } from '../Image/Image.js';
4
-
5
- function getCardLinkElement(as, href) {
6
- let element = href ? "a" : "div";
7
- if (as) {
8
- element = as;
9
- }
10
- return {
11
- element,
12
- props: {
13
- href,
14
- },
15
- };
16
- }
17
- const EditorialCard = ({ as, children, headingLevel = "h3", href, image, label = "", layout = "vertical", title, }) => {
18
- const linkElement = getCardLinkElement(as, href);
19
- const imageProps = Object.assign({ crop: "wide" }, image);
20
- const componentProps = {
21
- className: "coop-editorial-card",
22
- "data-layout": layout,
23
- };
24
- return (jsxs("article", Object.assign({}, componentProps, { children: [jsx(Image, Object.assign({}, imageProps)), jsxs("div", { className: "coop-editorial-card--content", children: [label && jsx("span", { children: label }), React.createElement(linkElement.element, linkElement.props, React.createElement(headingLevel, {}, title)), children] })] })));
25
- };
26
-
27
- export { EditorialCard, EditorialCard as default };
@@ -1,4 +0,0 @@
1
- import EditorialCard from "./EditorialCard";
2
- export default EditorialCard;
3
- export { EditorialCard };
4
- export * from "./EditorialCard";
@@ -1,80 +0,0 @@
1
- import type { AnchorHTMLAttributes, ForwardRefExoticComponent, JSX } from "react"
2
-
3
- import React from "react"
4
-
5
- import { Image, ImageProps } from "../Image"
6
-
7
- export interface EditorialCardProps {
8
- /** **(Optional)** Specifies the custom element to override default `a` */
9
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
- as?: React.FC<AnchorHTMLAttributes<HTMLElement>> | ForwardRefExoticComponent<any> | string
11
- /** **(Optional)** Represents the content inside the EditorialCard component. It can be any valid JSX or string. */
12
- children?: React.ReactNode
13
- /** **(Optional)** Specifies the heading level of the card's title. */
14
- headingLevel?: "h2" | "h3" | "h4" | "h5" | "h6"
15
- /** **(Optional)** Specifies the URL that the EditorialCard component will link to when clicked. */
16
- href?: string
17
- /** Specifies the image URL and alt text of the EditorialCard */
18
- image: ImageProps
19
- /** **(Optional)** Specifies the label of the EditorialCard */
20
- label?: string
21
- /** **(Optional)** Specifies the layout of the EditorialCard */
22
- layout?: "vertical" | "horizontal"
23
- /** Specifies the title of the EditorialCard */
24
- title: string
25
- }
26
-
27
- function getCardLinkElement(as: EditorialCardProps["as"], href?: string) {
28
- let element: EditorialCardProps["as"] = href ? "a" : "div"
29
-
30
- if (as) {
31
- element = as
32
- }
33
-
34
- return {
35
- element,
36
- props: {
37
- href,
38
- },
39
- }
40
- }
41
-
42
- export const EditorialCard = ({
43
- as,
44
- children,
45
- headingLevel = "h3",
46
- href,
47
- image,
48
- label = "",
49
- layout = "vertical",
50
- title,
51
- }: EditorialCardProps): JSX.Element => {
52
- const linkElement = getCardLinkElement(as, href)
53
-
54
- const imageProps: ImageProps = {
55
- crop: "wide",
56
- ...image,
57
- }
58
-
59
- const componentProps = {
60
- className: "coop-editorial-card",
61
- "data-layout": layout,
62
- }
63
-
64
- return (
65
- <article {...componentProps}>
66
- <Image {...imageProps} />
67
- <div className="coop-editorial-card--content">
68
- {label && <span>{label}</span>}
69
- {React.createElement(
70
- linkElement.element,
71
- linkElement.props,
72
- React.createElement(headingLevel, {}, title)
73
- )}
74
- {children}
75
- </div>
76
- </article>
77
- )
78
- }
79
-
80
- export default EditorialCard
@@ -1,5 +0,0 @@
1
- import EditorialCard from "./EditorialCard"
2
-
3
- export default EditorialCard
4
- export { EditorialCard }
5
- export * from "./EditorialCard"
@@ -1,160 +0,0 @@
1
- import { Meta, ColorPalette, ColorItem } from "@storybook/blocks"
2
-
3
- export const compStyle = getComputedStyle(document.documentElement)
4
-
5
- <Meta title="Styles / Colours" />
6
-
7
- # Colours
8
-
9
- <br />
10
-
11
- ## Brand colours
12
-
13
- <br />
14
- <br />
15
-
16
- <ColorPalette>
17
- <ColorItem
18
- title="Co-op blue"
19
- subtitle="Co-op blue"
20
- colors={{ "--color-brand-coop": compStyle.getPropertyValue("--color-brand-coop") }}
21
- />
22
- <ColorItem
23
- title="Co-op deal red"
24
- subtitle="Co-op brand deals"
25
- colors={{ "--color-brand-deals": compStyle.getPropertyValue("--color-brand-deals") }}
26
- />
27
-
28
- <ColorItem
29
- title="Text colours"
30
- colors={{
31
- "--color-white": compStyle.getPropertyValue("--color-white"),
32
- "--color-text-alt": compStyle.getPropertyValue("--color-text-alt"),
33
- "--color-text": compStyle.getPropertyValue("--color-text"),
34
- "--color-black": compStyle.getPropertyValue("--color-black"),
35
- }}
36
- />
37
-
38
- <ColorItem
39
- title="Tint colours"
40
- colors={{
41
- "--color-tint-purple": compStyle.getPropertyValue("--color-tint-purple"),
42
- "--color-tint-pink": compStyle.getPropertyValue("--color-tint-pink"),
43
- "--color-tint-green": compStyle.getPropertyValue("--color-tint-green"),
44
- "--color-tint-orange": compStyle.getPropertyValue("--color-tint-orange"),
45
- "--color-tint-red": compStyle.getPropertyValue("--color-tint-red"),
46
- "--color-tint-yellow": compStyle.getPropertyValue("--color-tint-yellow"),
47
- "--color-tint-lilac": compStyle.getPropertyValue("--color-tint-lilac"),
48
- "--color-tint-blue": compStyle.getPropertyValue("--color-tint-blue"),
49
- }}
50
- />
51
-
52
- <ColorItem
53
- title="Light colours"
54
- colors={{
55
- "--color-light-purple": compStyle.getPropertyValue("--color-light-purple"),
56
- "--color-light-pink": compStyle.getPropertyValue("--color-light-pink"),
57
- "--color-light-green": compStyle.getPropertyValue("--color-light-green"),
58
- "--color-light-orange": compStyle.getPropertyValue("--color-light-orange"),
59
- "--color-light-red": compStyle.getPropertyValue("--color-light-red"),
60
- "--color-light-yellow": compStyle.getPropertyValue("--color-light-yellow"),
61
- "--color-light-lilac": compStyle.getPropertyValue("--color-light-lilac"),
62
- "--color-light-blue": compStyle.getPropertyValue("--color-light-blue"),
63
- }}
64
- />
65
-
66
- <ColorItem
67
- title="Dark colours"
68
- colors={{
69
- "--color-dark-purple": compStyle.getPropertyValue("--color-dark-purple"),
70
- "--color-dark-pink": compStyle.getPropertyValue("--color-dark-pink"),
71
- "--color-dark-green": compStyle.getPropertyValue("--color-dark-green"),
72
- "--color-dark-orange": compStyle.getPropertyValue("--color-dark-orange"),
73
- "--color-dark-red": compStyle.getPropertyValue("--color-dark-red"),
74
- "--color-dark-yellow": compStyle.getPropertyValue("--color-dark-yellow"),
75
- "--color-dark-lilac": compStyle.getPropertyValue("--color-dark-lilac"),
76
- "--color-navy": compStyle.getPropertyValue("--color-navy"),
77
- }}
78
- />
79
-
80
- <ColorItem
81
- title="Grey backgrounds"
82
- colors={{
83
- "--color-grey-neutral-light": compStyle.getPropertyValue("--color-grey-neutral-light"),
84
- "--color-grey-neutral-warm": compStyle.getPropertyValue("--color-grey-neutral-warm"),
85
- "--color-grey-neutral-cool": compStyle.getPropertyValue("--color-grey-neutral-cool"),
86
- "--color-grey-neutral-cool-light": compStyle.getPropertyValue(
87
- "--color-grey-neutral-cool-light"
88
- ),
89
- "--color-grey-mid-light": compStyle.getPropertyValue("--color-grey-mid-light"),
90
- "--color-grey-mid": compStyle.getPropertyValue("--color-grey-mid"),
91
- "--color-grey-dark": compStyle.getPropertyValue("--color-grey-dark"),
92
- }}
93
- />
94
-
95
- <br />
96
- <br />
97
- <br />
98
- <br />
99
- ## UI colors
100
-
101
- <br />
102
- ### Link
103
- <br />
104
- <ColorItem
105
- title="Link colours"
106
- colors={{
107
- "--color-link": compStyle.getPropertyValue("--color-link"),
108
- "--color-link-hover": compStyle.getPropertyValue("--color-link-hover"),
109
- "--color-link-active": compStyle.getPropertyValue("--color-link-active"),
110
- "--color-link-visited": compStyle.getPropertyValue("--color-link-visited"),
111
- "--color-link-focus": compStyle.getPropertyValue("--color-link-focus"),
112
- "--color-focus-ring": compStyle.getPropertyValue("--color-focus-ring"),
113
- }}
114
- />
115
-
116
- <br />
117
- ### Button
118
- <br />
119
- <ColorItem
120
- title="Primary green button"
121
- colors={{
122
- "--color-button-green-primary": compStyle.getPropertyValue("--color-button-green-primary"),
123
- "--color-button-green-primary-hover": compStyle.getPropertyValue(
124
- "--color-button-green-primary-hover"
125
- ),
126
- "--color-button-green-primary-active": compStyle.getPropertyValue(
127
- "--color-button-green-primary-active"
128
- ),
129
- }}
130
- />
131
- <ColorItem
132
- title="Blue button (default)"
133
- colors={{
134
- "--color-button / --color-button-blue": compStyle.getPropertyValue("--color-button-blue"),
135
- "--color-button-hover / --color-button-blue-hover": compStyle.getPropertyValue(
136
- "--color-button-blue-hover"
137
- ),
138
- "--color-button-active / --color-button-blue-active": compStyle.getPropertyValue(
139
- "--color-button-blue-active"
140
- ),
141
- }}
142
- />
143
- <ColorItem
144
- title="Grey button"
145
- colors={{
146
- "--color-button-grey": compStyle.getPropertyValue("--color-button-grey"),
147
- "--color-button-grey-hover": compStyle.getPropertyValue("--color-button-grey-hover"),
148
- "--color-button-grey-active": compStyle.getPropertyValue("--color-button-grey-active"),
149
- }}
150
- />
151
- <ColorItem
152
- title="White button"
153
- colors={{
154
- "--color-button-white": compStyle.getPropertyValue("--color-button-white"),
155
- "--color-button-white-hover": compStyle.getPropertyValue("--color-button-white-hover"),
156
- "--color-button-white-active": compStyle.getPropertyValue("--color-button-white-active"),
157
- }}
158
- />
159
-
160
- </ColorPalette>
@@ -1,119 +0,0 @@
1
- import { Meta, Unstyled } from "@storybook/blocks"
2
-
3
- <Meta title="Styles / Text" />
4
-
5
- export const SampleText = "Lorem ipsum dolor..."
6
-
7
- # Text
8
-
9
- <br />
10
-
11
- <Unstyled>
12
-
13
- ## Using Co-op Headline font
14
-
15
- <br />
16
-
17
- > <h1 className="coop-t-headline coop-t-headline-upper">h1: {SampleText}</h1>
18
- > <h2 className="coop-t-headline coop-t-headline-upper coop-t-headline-blue">h2: {SampleText}</h2>
19
- > <h3 className="coop-t-headline coop-t-headline-upper coop-t-headline-blue">h3: {SampleText}</h3>
20
- > <h4 className="coop-t-headline coop-t-headline-upper coop-t-headline-blue">h4: {SampleText}</h4>
21
- > <h5 className="coop-t-headline coop-t-headline-upper coop-t-headline-blue">h5: {SampleText}</h5>
22
- > <h6 className="coop-t-headline coop-t-headline-upper coop-t-headline-blue">h6: {SampleText}</h6>
23
-
24
- > ```jsx
25
- > <h1 className="coop-t-headline coop-t-headline-upper">h1: {SampleText}</h1>
26
- > <h2 className="coop-t-headline coop-t-headline-upper coop-t-headline-blue">h2: {SampleText}</h2>
27
- > <h3 className="coop-t-headline coop-t-headline-upper coop-t-headline-blue">h3: {SampleText}</h3>
28
- > <h4 className="coop-t-headline coop-t-headline-upper coop-t-headline-blue">h4: {SampleText}</h4>
29
- > <h5 className="coop-t-headline coop-t-headline-upper coop-t-headline-blue">h5: {SampleText}</h5>
30
- > <h6 className="coop-t-headline coop-t-headline-upper coop-t-headline-blue">h6: {SampleText}</h6>
31
- > ```
32
-
33
- <br />
34
-
35
- > <h1 className="coop-t-headline coop-t-headline-blue">h1: {SampleText}</h1>
36
- > <h2 className="coop-t-headline coop-t-headline-blue">h2: {SampleText}</h2>
37
- > <h3 className="coop-t-headline coop-t-headline-blue">h3: {SampleText}</h3>
38
- > <h4 className="coop-t-headline coop-t-headline-blue">h4: {SampleText}</h4>
39
- > <h5 className="coop-t-headline coop-t-headline-blue">h5: {SampleText}</h5>
40
- > <h6 className="coop-t-headline coop-t-headline-blue">h6: {SampleText}</h6>
41
-
42
- > ```jsx
43
- > <h1 className="coop-t-headline coop-t-headline-blue">h1: {SampleText}</h1>
44
- > <h2 className="coop-t-headline coop-t-headline-blue">h2: {SampleText}</h2>
45
- > <h3 className="coop-t-headline coop-t-headline-blue">h3: {SampleText}</h3>
46
- > <h4 className="coop-t-headline coop-t-headline-blue">h4: {SampleText}</h4>
47
- > <h5 className="coop-t-headline coop-t-headline-blue">h5: {SampleText}</h5>
48
- > <h6 className="coop-t-headline coop-t-headline-blue">h6: {SampleText}</h6>
49
- > ```
50
-
51
- <br />
52
- <br />
53
- <br />
54
- ## Using Primary font (Avenir Next)
55
- <br />
56
-
57
- > <h1>h1: {SampleText}</h1>
58
- > <h2>h2: {SampleText}</h2>
59
- > <h3>h3: {SampleText}</h3>
60
- > <h4>h4: {SampleText}</h4>
61
- > <h5>h5: {SampleText}</h5>
62
- > <h6>h6: {SampleText}</h6>
63
-
64
- > ```jsx
65
- > <h1>h1: {SampleText}</h1>
66
- > <h2>h2: {SampleText}</h2>
67
- > <h3>h3: {SampleText}</h3>
68
- > <h4>h4: {SampleText}</h4>
69
- > <h5>h5: {SampleText}</h5>
70
- > <h6>h6: {SampleText}</h6>
71
- > ```
72
-
73
- > <p class="coop-t-lead-p">Lead paragraph</p>
74
- > <hr />
75
- > <p>Paragraph text: {SampleText}</p>
76
- > <a href="">Link</a>
77
-
78
- > ```jsx
79
- > <p class="coop-t-lead-p">Lead paragraph</p>
80
- > <hr />
81
- > <p>Paragraph text: {SampleText}</p>
82
- > <a href="">Link</a>
83
- > ```
84
-
85
- > <p>Lists:</p>
86
- > <ul>
87
- > <li>Option 1</li>
88
- > <li>Option 1</li>
89
- > </ul>
90
- > <ol>
91
- > <li>Option 1</li>
92
- > <li>Option 1</li>
93
- > </ol>
94
-
95
- > ```jsx
96
- > <p>Lists:</p>
97
- > <ul>
98
- > <li>Option 1</li>
99
- > <li>Option 1</li>
100
- > </ul>
101
- > <ol>
102
- > <li>Option 1</li>
103
- > <li>Option 1</li>
104
- > </ol>
105
- > ```
106
-
107
- </Unstyled>
108
-
109
- <style>
110
- {`
111
- .toc-wrapper {
112
- display: none;
113
- }
114
- `}
115
- </style>
116
-
117
- ```
118
-
119
- ```
@@ -1,49 +0,0 @@
1
- import { Meta, Typeset } from "@storybook/blocks"
2
-
3
- <Meta title="Styles / Typography" />
4
-
5
- export const compStyle = getComputedStyle(document.documentElement)
6
-
7
- export const typography = {
8
- type: {
9
- primary: compStyle.getPropertyValue("--font-family"),
10
- headline: compStyle.getPropertyValue("--font-family-headline"),
11
- },
12
- weight: {
13
- regular: "400",
14
- medium: "500",
15
- demi: "600",
16
- bold: "700",
17
- },
18
- }
19
-
20
- export const SampleText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
21
- export const SampleTextUppercase = SampleText.toUpperCase()
22
-
23
- # Typography
24
-
25
- **Font:** Avenir Next ( as `--font-family`)<br />
26
- **Weights:** 400 (regular), 500 (medium), 600 (demi)
27
-
28
- <Typeset
29
- fontSizes={[10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 40, 46, 48, 56, 64, 82]}
30
- fontWeight={typography.weight.regular}
31
- sampleText={SampleText}
32
- fontFamily={typography.type.primary}
33
- />
34
-
35
- **Font:** Co-op Headline ( as `--font-family-headline`)<br />
36
- **Weights:** 700 (bold)
37
-
38
- <Typeset
39
- fontSizes={[18, 20, 22, 24, 26, 28, 30, 32, 40, 46, 48, 56, 64, 82]}
40
- fontWeight={typography.weight.bold}
41
- sampleText={SampleTextUppercase}
42
- fontFamily={typography.type.headline}
43
- />
44
-
45
- <p>
46
- <b>Note:</b>
47
- The type sizes shown above have been chosen as our standard type sizes.
48
- <br /> They are available as CSS variables, e.g. `--type-size-10`, `--type-size-12`, etc.
49
- </p>