@coopdigital/react 0.5.3 → 0.6.1
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/README.md +8 -3
- package/dist/components/AlertBanner/AlertBanner.d.ts +2 -2
- package/dist/components/AlertBanner/AlertBanner.js +22 -0
- package/dist/components/EditorialCard/EditorialCard.d.ts +23 -0
- package/dist/components/EditorialCard/EditorialCard.js +27 -0
- package/dist/components/EditorialCard/index.d.ts +4 -0
- package/dist/components/Image/Image.d.ts +15 -0
- package/dist/components/Image/Image.js +36 -0
- package/dist/components/Image/index.d.ts +4 -0
- package/dist/components/Pill/Pill.d.ts +5 -4
- package/dist/components/SkipNav/SkipNav.d.ts +2 -1
- package/dist/components/SkipNav/SkipNav.js +34 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/package.json +21 -20
- package/src/components/AlertBanner/AlertBanner.tsx +3 -3
- package/src/components/EditorialCard/EditorialCard.tsx +80 -0
- package/src/components/EditorialCard/index.ts +5 -0
- package/src/components/Image/Image.tsx +57 -0
- package/src/components/Image/index.ts +5 -0
- package/src/components/Pill/Pill.tsx +6 -4
- package/src/components/SkipNav/SkipNav.tsx +7 -4
- package/src/index.ts +4 -0
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# Experience Kit for React
|
|
8
8
|
|
|
9
9
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
10
|
-
[all_contributors_badge]: https://img.shields.io/badge/all_contributors-
|
|
10
|
+
[all_contributors_badge]: https://img.shields.io/badge/all_contributors-17-C08A00.svg ''
|
|
11
11
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
12
12
|
|
|
13
13
|
<a href="https://www.npmjs.com/package/@coopdigital/react"><img src="https://img.shields.io/npm/v/%40coopdigital%2Freact?color=E85A00" alt="npm version" /></a>
|
|
@@ -113,19 +113,24 @@ Thanks goes to these wonderful people:
|
|
|
113
113
|
<div><img src="https://secure.gravatar.com/avatar/faf90b70d749cf9460eb603fa1700add94fd7d3a32645b4be6fa12eb45c4ffa3?s=80&d=identicon" width="200px;" alt=""/></div>
|
|
114
114
|
<small>Omid Kashan</small>
|
|
115
115
|
<div><a href="#a11y-omid.kashan" title="Accessibility">️️️️♿️</a> <a href="#code-omid.kashan" title="Code">💻</a> <a href="#doc-omid.kashan" title="Documentation">📖</a> <a href="#test-omid.kashan" title="Tests">⚠️</a></div>
|
|
116
|
+
</a></td>
|
|
117
|
+
<td align="center" valign="top" width="14.28%"><a href="https://gitlab.com/phil.wolstenholme">
|
|
118
|
+
<div><img src="https://gitlab.com/uploads/-/system/user/avatar/25900363/avatar.png" width="200px;" alt=""/></div>
|
|
119
|
+
<small>Phil Wolstenholme</small>
|
|
120
|
+
<div><a href="#a11y-phil.wolstenholme" title="Accessibility">️️️️♿️</a></div>
|
|
116
121
|
</a></td>
|
|
117
122
|
<td align="center" valign="top" width="14.28%"><a href="https://gitlab.com/romain.chen">
|
|
118
123
|
<div><img src="https://s3.eu-west-1.amazonaws.com/assets.digital.coop.co.uk/oneweb/blank.jpg" width="200px;" alt=""/></div>
|
|
119
124
|
<small>Romain Chen</small>
|
|
120
125
|
<div><a href="#design-romain.chen" title="Design">🎨</a></div>
|
|
121
126
|
</a></td>
|
|
127
|
+
</tr><br />
|
|
128
|
+
<tr>
|
|
122
129
|
<td align="center" valign="top" width="14.28%"><a href="https://gitlab.com/sam.harden">
|
|
123
130
|
<div><img src="https://ca.slack-edge.com/T0C9E3ZF0-U03NT7D39L6-2fc82e7f7c6e-150" width="200px;" alt=""/></div>
|
|
124
131
|
<small>Sam Harden</small>
|
|
125
132
|
<div><a href="#a11y-sam.harden" title="Accessibility">️️️️♿️</a> <a href="#code-sam.harden" title="Code">💻</a> <a href="#doc-sam.harden" title="Documentation">📖</a> <a href="#test-sam.harden" title="Tests">⚠️</a></div>
|
|
126
133
|
</a></td>
|
|
127
|
-
</tr><br />
|
|
128
|
-
<tr>
|
|
129
134
|
<td align="center" valign="top" width="14.28%"><a href="">
|
|
130
135
|
<div><img src="https://s3.eu-west-1.amazonaws.com/assets.digital.coop.co.uk/oneweb/blank.jpg" width="200px;" alt=""/></div>
|
|
131
136
|
<small>Shweta Jaju</small>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReactNode } from "react";
|
|
1
|
+
import type { JSX, ReactNode } from "react";
|
|
2
2
|
export interface AlertBannerProps {
|
|
3
3
|
/** **(Optional)** Specifies a custom aria-label. */
|
|
4
4
|
ariaLabel?: string;
|
|
@@ -21,5 +21,5 @@ export interface AlertBannerProps {
|
|
|
21
21
|
* <p>Only use alert notifications when absolutely necessary. Using them too often could make the problem worse.<p/>
|
|
22
22
|
*
|
|
23
23
|
*/
|
|
24
|
-
export declare const AlertBanner: ({ ariaLabel, children, className, title, variant, }: AlertBannerProps) =>
|
|
24
|
+
export declare const AlertBanner: ({ ariaLabel, children, className, title, variant, }: AlertBannerProps) => JSX.Element;
|
|
25
25
|
export default AlertBanner;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Alert Banner component is used to highlight events that might limit availability of a service we provide.
|
|
5
|
+
* <br />
|
|
6
|
+
* - the service the user is trying to access is experiencing problems
|
|
7
|
+
* - planned system maintenance is coming soon
|
|
8
|
+
* - the user’s login session is about to expire
|
|
9
|
+
* <br />
|
|
10
|
+
* <p>Only use alert notifications when absolutely necessary. Using them too often could make the problem worse.<p/>
|
|
11
|
+
*
|
|
12
|
+
*/
|
|
13
|
+
const AlertBanner = ({ ariaLabel, children, className = "", title, variant = "default", }) => {
|
|
14
|
+
const componentProps = {
|
|
15
|
+
"aria-label": ariaLabel,
|
|
16
|
+
className: `coop-alert-banner ${className}`,
|
|
17
|
+
"data-variant": variant,
|
|
18
|
+
};
|
|
19
|
+
return (jsx("aside", Object.assign({}, componentProps, { children: jsxs("div", { className: "coop-alert-banner--inner", children: [jsx("h2", { id: "coop-alert-banner--headline", children: title }), children] }) })));
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export { AlertBanner, AlertBanner as default };
|
|
@@ -0,0 +1,23 @@
|
|
|
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;
|
|
@@ -0,0 +1,27 @@
|
|
|
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 };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { JSX } from "react";
|
|
2
|
+
export interface ImageProps {
|
|
3
|
+
/** Specifies text that can replace the Image in the page */
|
|
4
|
+
alt: string;
|
|
5
|
+
/** **(Optional)** Specifies the Image crop mode */
|
|
6
|
+
crop?: "square" | "wide" | "none";
|
|
7
|
+
/** **(Optional)** Specifies height of the Image */
|
|
8
|
+
height?: string;
|
|
9
|
+
/** Specifies the Image URL */
|
|
10
|
+
src: string;
|
|
11
|
+
/** **(Optional)** Specifies width of the Image */
|
|
12
|
+
width?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const Image: ({ alt, crop, height, src, width }: ImageProps) => JSX.Element;
|
|
15
|
+
export default Image;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
const getContentfulParams = (src, width, height) => {
|
|
4
|
+
var _a, _b;
|
|
5
|
+
const url = !src.startsWith("http") ? new URL("https://" + src) : new URL(src);
|
|
6
|
+
const params = [
|
|
7
|
+
["fm", "webp"],
|
|
8
|
+
["q", "70"],
|
|
9
|
+
["fit", "fill"],
|
|
10
|
+
["w", width],
|
|
11
|
+
...(height ? [["h", height]] : []),
|
|
12
|
+
];
|
|
13
|
+
params.forEach(([k, v]) => {
|
|
14
|
+
var _a;
|
|
15
|
+
(_a = url.searchParams.get(k)) !== null && _a !== void 0 ? _a : url.searchParams.set(k, v);
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
height: (_a = url.searchParams.get("h")) !== null && _a !== void 0 ? _a : height,
|
|
19
|
+
src: url.toString(),
|
|
20
|
+
width: (_b = url.searchParams.get("w")) !== null && _b !== void 0 ? _b : width,
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
const Image = ({ alt, crop, height, src, width = "640" }) => {
|
|
24
|
+
let params = { height, src, width };
|
|
25
|
+
if (src.includes("images.ctfassets.net")) {
|
|
26
|
+
params = getContentfulParams(src, width, height);
|
|
27
|
+
}
|
|
28
|
+
const dimensions = {
|
|
29
|
+
"data-crop": crop,
|
|
30
|
+
height: params.height,
|
|
31
|
+
width: params.width,
|
|
32
|
+
};
|
|
33
|
+
return (jsx("picture", { children: jsx("img", Object.assign({ alt: alt, loading: "lazy", src: params.src }, dimensions)) }));
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export { Image, Image as default };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { AnchorHTMLAttributes, ForwardRefExoticComponent, JSX } from "react";
|
|
2
|
+
import React from "react";
|
|
2
3
|
export interface PillProps {
|
|
3
|
-
/** **(Optional)** Specifies the custom element to override default `a` or `span`. */
|
|
4
|
-
as?: React.FC<AnchorHTMLAttributes<HTMLElement>> | ForwardRefExoticComponent<any> | string;
|
|
5
4
|
/** **(Optional)** Specifies a custom aria-label. */
|
|
6
5
|
ariaLabel?: string;
|
|
6
|
+
/** **(Optional)** Specifies the custom element to override default `a` or `span`. */
|
|
7
|
+
as?: React.FC<AnchorHTMLAttributes<HTMLElement>> | ForwardRefExoticComponent<any> | string;
|
|
7
8
|
/** **(Optional)** Specifies what text Pill should display on the badge. */
|
|
8
9
|
badge?: string;
|
|
9
10
|
/** **(Optional)** Specifies the badge background color from the available options. */
|
|
@@ -19,5 +20,5 @@ export interface PillProps {
|
|
|
19
20
|
/** **(Optional)** Specifies what should be the Pill size. */
|
|
20
21
|
size?: "sm" | "md" | "lg" | "xl";
|
|
21
22
|
}
|
|
22
|
-
export declare const Pill: ({ ariaLabel, as, badge, badgeColor, children, className, href, pillColor, size, }: PillProps) =>
|
|
23
|
+
export declare const Pill: ({ ariaLabel, as, badge, badgeColor, children, className, href, pillColor, size, }: PillProps) => JSX.Element;
|
|
23
24
|
export default Pill;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { JSX } from "react";
|
|
1
2
|
interface SkipNavLink {
|
|
2
3
|
href: string;
|
|
3
4
|
title: string;
|
|
@@ -32,5 +33,5 @@ export interface SkipNavProps {
|
|
|
32
33
|
* - Pressing enter will take the user to the correct part of the page and apply the focus ring to the first interactive element in that section.
|
|
33
34
|
* - If the user tabs through the skip navigation without selecting an option the Co‑op logo should be the next element that has the focus ring applied.
|
|
34
35
|
*/
|
|
35
|
-
export declare const SkipNav: ({ ariaLabel, className, isVisible, links, }: SkipNavProps) =>
|
|
36
|
+
export declare const SkipNav: ({ ariaLabel, className, isVisible, links, }: SkipNavProps) => JSX.Element;
|
|
36
37
|
export default SkipNav;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
const defaultLinks = [{ href: "#main", title: "Skip to main content" }];
|
|
4
|
+
/**
|
|
5
|
+
* The Skip Nav component allows screen reader and keyboard users to go directly to the main content.
|
|
6
|
+
*
|
|
7
|
+
* ### Links
|
|
8
|
+
* Links within the skip navigation component should take the user directly to the main areas of the page. These are usually the:
|
|
9
|
+
* - navigation
|
|
10
|
+
* - main content
|
|
11
|
+
* - footer
|
|
12
|
+
* - search (if applicable)
|
|
13
|
+
*
|
|
14
|
+
*
|
|
15
|
+
* ### Using skip navigation on a page
|
|
16
|
+
* - The skip navigation function should be the first thing that appears on a page when it has loaded and a user presses the tab key.
|
|
17
|
+
* - Pressing the tab key again should then cycle through 'skip to main content', 'skip to navigation' and 'skip to footer' links.
|
|
18
|
+
* - Pressing enter will take the user to the correct part of the page and apply the focus ring to the first interactive element in that section.
|
|
19
|
+
* - If the user tabs through the skip navigation without selecting an option the Co‑op logo should be the next element that has the focus ring applied.
|
|
20
|
+
*/
|
|
21
|
+
const SkipNav = ({ ariaLabel = "", className = "", isVisible = false, links = defaultLinks, }) => {
|
|
22
|
+
const navLinks = links.length > 0 ? links : defaultLinks;
|
|
23
|
+
return (jsx("nav", { "aria-label": ariaLabel, className: "coop-skip-nav", children: jsx("ul", { children: navLinks.map((link) => {
|
|
24
|
+
const linkProps = {
|
|
25
|
+
className: `${className}`,
|
|
26
|
+
"data-visible": isVisible,
|
|
27
|
+
href: link.href,
|
|
28
|
+
title: link.title,
|
|
29
|
+
};
|
|
30
|
+
return (jsx("li", { children: jsx("a", Object.assign({}, linkProps, { children: link.title })) }, link.href));
|
|
31
|
+
}) }) }));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export { SkipNav, SkipNav as default };
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
+
export { AlertBanner } from './components/AlertBanner/AlertBanner.js';
|
|
2
|
+
export { EditorialCard } from './components/EditorialCard/EditorialCard.js';
|
|
3
|
+
export { Image } from './components/Image/Image.js';
|
|
1
4
|
export { Pill } from './components/Pill/Pill.js';
|
|
5
|
+
export { SkipNav } from './components/SkipNav/SkipNav.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.6.1",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"private": false,
|
|
7
7
|
"publishConfig": {
|
|
@@ -25,10 +25,11 @@
|
|
|
25
25
|
"test": "vitest run",
|
|
26
26
|
"test:watch": "vitest",
|
|
27
27
|
"test:coverage": "vitest run --coverage",
|
|
28
|
-
"build": "tsx scripts build",
|
|
29
|
-
"build:storybook": "storybook build --disable-telemetry && npm run storybook:fix-paths",
|
|
30
28
|
"test:e2e": "npx playwright test",
|
|
31
29
|
"test:e2e:ui": "npx playwright test --ui",
|
|
30
|
+
"test:e2e:ci": "npx playwright test --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL",
|
|
31
|
+
"build": "tsx scripts build",
|
|
32
|
+
"build:storybook": "storybook build --disable-telemetry && npm run storybook:fix-paths",
|
|
32
33
|
"storybook": "storybook dev -p 6006",
|
|
33
34
|
"storybook:playwright": "storybook dev -p 9000",
|
|
34
35
|
"storybook:fix-paths": "tsx scripts/storybook-fix-paths.ts"
|
|
@@ -38,31 +39,31 @@
|
|
|
38
39
|
"description": "",
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@axe-core/playwright": "^4.10.1",
|
|
41
|
-
"@coopdigital/styles": "^0.5.
|
|
42
|
-
"@playwright/test": "^1.51.
|
|
42
|
+
"@coopdigital/styles": "^0.5.8",
|
|
43
|
+
"@playwright/test": "^1.51.1",
|
|
43
44
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
44
45
|
"@rollup/plugin-typescript": "^12.1.2",
|
|
45
|
-
"@storybook/addon-a11y": "^8.6.
|
|
46
|
-
"@storybook/addon-essentials": "^8.6.
|
|
47
|
-
"@storybook/addon-interactions": "^8.6.
|
|
48
|
-
"@storybook/addon-mdx-gfm": "^8.6.
|
|
49
|
-
"@storybook/addon-onboarding": "^8.6.
|
|
50
|
-
"@storybook/blocks": "^8.6.
|
|
51
|
-
"@storybook/manager-api": "^8.6.
|
|
52
|
-
"@storybook/react": "^8.6.
|
|
53
|
-
"@storybook/react-vite": "^8.6.
|
|
54
|
-
"@storybook/test": "^8.6.
|
|
55
|
-
"@storybook/theming": "^8.6.
|
|
46
|
+
"@storybook/addon-a11y": "^8.6.6",
|
|
47
|
+
"@storybook/addon-essentials": "^8.6.6",
|
|
48
|
+
"@storybook/addon-interactions": "^8.6.6",
|
|
49
|
+
"@storybook/addon-mdx-gfm": "^8.6.6",
|
|
50
|
+
"@storybook/addon-onboarding": "^8.6.6",
|
|
51
|
+
"@storybook/blocks": "^8.6.6",
|
|
52
|
+
"@storybook/manager-api": "^8.6.6",
|
|
53
|
+
"@storybook/react": "^8.6.6",
|
|
54
|
+
"@storybook/react-vite": "^8.6.6",
|
|
55
|
+
"@storybook/test": "^8.6.6",
|
|
56
|
+
"@storybook/theming": "^8.6.6",
|
|
56
57
|
"@testing-library/jest-dom": "^6.6.3",
|
|
57
58
|
"@testing-library/react": "^16.2.0",
|
|
58
|
-
"@types/react": "^19.0.
|
|
59
|
+
"@types/react": "^19.0.12",
|
|
59
60
|
"@types/react-dom": "^19.0.4",
|
|
60
|
-
"rollup": "^4.
|
|
61
|
-
"storybook": "^8.6.
|
|
61
|
+
"rollup": "^4.36.0",
|
|
62
|
+
"storybook": "^8.6.6"
|
|
62
63
|
},
|
|
63
64
|
"peerDependencies": {
|
|
64
65
|
"react": "^19.0.0",
|
|
65
66
|
"react-dom": "^19.0.0"
|
|
66
67
|
},
|
|
67
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "60cbbc73c648be7d728d36a700bcf2761579548f"
|
|
68
69
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReactNode } from "react"
|
|
1
|
+
import type { JSX, ReactNode } from "react"
|
|
2
2
|
|
|
3
3
|
export interface AlertBannerProps {
|
|
4
4
|
/** **(Optional)** Specifies a custom aria-label. */
|
|
@@ -29,7 +29,7 @@ export const AlertBanner = ({
|
|
|
29
29
|
className = "",
|
|
30
30
|
title,
|
|
31
31
|
variant = "default",
|
|
32
|
-
}: AlertBannerProps) => {
|
|
32
|
+
}: AlertBannerProps): JSX.Element => {
|
|
33
33
|
const componentProps = {
|
|
34
34
|
"aria-label": ariaLabel,
|
|
35
35
|
className: `coop-alert-banner ${className}`,
|
|
@@ -38,7 +38,7 @@ export const AlertBanner = ({
|
|
|
38
38
|
return (
|
|
39
39
|
<aside {...componentProps}>
|
|
40
40
|
<div className="coop-alert-banner--inner">
|
|
41
|
-
<
|
|
41
|
+
<h2 id="coop-alert-banner--headline">{title}</h2>
|
|
42
42
|
{children}
|
|
43
43
|
</div>
|
|
44
44
|
</aside>
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { JSX } from "react"
|
|
2
|
+
|
|
3
|
+
export interface ImageProps {
|
|
4
|
+
/** Specifies text that can replace the Image in the page */
|
|
5
|
+
alt: string
|
|
6
|
+
/** **(Optional)** Specifies the Image crop mode */
|
|
7
|
+
crop?: "square" | "wide" | "none"
|
|
8
|
+
/** **(Optional)** Specifies height of the Image */
|
|
9
|
+
height?: string
|
|
10
|
+
/** Specifies the Image URL */
|
|
11
|
+
src: string
|
|
12
|
+
/** **(Optional)** Specifies width of the Image */
|
|
13
|
+
width?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const getContentfulParams = (src: string, width: string, height: string | undefined) => {
|
|
17
|
+
const url = !src.startsWith("http") ? new URL("https://" + src) : new URL(src)
|
|
18
|
+
const params = [
|
|
19
|
+
["fm", "webp"],
|
|
20
|
+
["q", "70"],
|
|
21
|
+
["fit", "fill"],
|
|
22
|
+
["w", width],
|
|
23
|
+
...(height ? [["h", height]] : []),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
params.forEach(([k, v]) => {
|
|
27
|
+
url.searchParams.get(k) ?? url.searchParams.set(k, v)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
height: url.searchParams.get("h") ?? height,
|
|
32
|
+
src: url.toString(),
|
|
33
|
+
width: url.searchParams.get("w") ?? width,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const Image = ({ alt, crop, height, src, width = "640" }: ImageProps): JSX.Element => {
|
|
38
|
+
let params = { height, src, width }
|
|
39
|
+
|
|
40
|
+
if (src.includes("images.ctfassets.net")) {
|
|
41
|
+
params = getContentfulParams(src, width, height)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const dimensions = {
|
|
45
|
+
"data-crop": crop,
|
|
46
|
+
height: params.height,
|
|
47
|
+
width: params.width,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<picture>
|
|
52
|
+
<img alt={alt} loading="lazy" src={params.src} {...dimensions} />
|
|
53
|
+
</picture>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default Image
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { AnchorHTMLAttributes, ForwardRefExoticComponent, JSX } from "react"
|
|
2
|
+
|
|
3
|
+
import React from "react"
|
|
2
4
|
|
|
3
5
|
export interface PillProps {
|
|
6
|
+
/** **(Optional)** Specifies a custom aria-label. */
|
|
7
|
+
ariaLabel?: string
|
|
4
8
|
/** **(Optional)** Specifies the custom element to override default `a` or `span`. */
|
|
5
9
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
10
|
as?: React.FC<AnchorHTMLAttributes<HTMLElement>> | ForwardRefExoticComponent<any> | string
|
|
7
|
-
/** **(Optional)** Specifies a custom aria-label. */
|
|
8
|
-
ariaLabel?: string
|
|
9
11
|
/** **(Optional)** Specifies what text Pill should display on the badge. */
|
|
10
12
|
badge?: string
|
|
11
13
|
/** **(Optional)** Specifies the badge background color from the available options. */
|
|
@@ -32,7 +34,7 @@ export const Pill = ({
|
|
|
32
34
|
href,
|
|
33
35
|
pillColor = "blue",
|
|
34
36
|
size = "md",
|
|
35
|
-
}: PillProps) => {
|
|
37
|
+
}: PillProps): JSX.Element => {
|
|
36
38
|
let element: PillProps["as"] = href ? "a" : "span"
|
|
37
39
|
|
|
38
40
|
if (as) {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import type { JSX } from "react"
|
|
2
|
+
|
|
1
3
|
interface SkipNavLink {
|
|
2
4
|
href: string
|
|
3
5
|
title: string
|
|
4
6
|
}
|
|
7
|
+
|
|
5
8
|
export interface SkipNavProps {
|
|
6
9
|
/** **(Optional)** Specifies a custom aria-label. */
|
|
7
10
|
ariaLabel?: string
|
|
@@ -40,17 +43,17 @@ export const SkipNav = ({
|
|
|
40
43
|
className = "",
|
|
41
44
|
isVisible = false,
|
|
42
45
|
links = defaultLinks,
|
|
43
|
-
}: SkipNavProps) => {
|
|
46
|
+
}: SkipNavProps): JSX.Element => {
|
|
44
47
|
const navLinks = links.length > 0 ? links : defaultLinks
|
|
45
48
|
return (
|
|
46
|
-
<nav className="coop-skip-nav"
|
|
49
|
+
<nav aria-label={ariaLabel} className="coop-skip-nav">
|
|
47
50
|
<ul>
|
|
48
51
|
{navLinks.map((link) => {
|
|
49
52
|
const linkProps = {
|
|
50
|
-
href: link.href,
|
|
51
|
-
title: link.title,
|
|
52
53
|
className: `${className}`,
|
|
53
54
|
"data-visible": isVisible,
|
|
55
|
+
href: link.href,
|
|
56
|
+
title: link.title,
|
|
54
57
|
}
|
|
55
58
|
return (
|
|
56
59
|
<li key={link.href}>
|