@coopdigital/react 0.40.1 → 0.41.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.
@@ -1,38 +1,77 @@
1
- import type { AnchorHTMLAttributes, ForwardRefExoticComponent, HTMLAttributes, JSX } from "react";
1
+ import type { ForwardRefExoticComponent, HTMLAttributes, JSX } from "react";
2
2
  import React from "react";
3
3
  import { Lights, Tints, White } from "../../types/colors";
4
4
  import { ImageProps } from "../Image";
5
5
  export interface CardProps extends HTMLAttributes<HTMLDivElement> {
6
- /** **(Optional)** Specify a custom element to override default `a`. */
7
- as?: React.FC<AnchorHTMLAttributes<HTMLElement>> | ForwardRefExoticComponent<any> | string;
8
6
  /** **(Optional)** Specify the Card background color. */
9
7
  background?: Tints | White;
10
- /** **(Optional)** Specify text to display inside the badge. */
11
- badge?: React.ReactNode;
12
- /** **(Optional)** Specify badge position relative to top right corner. */
13
- badgePosition?: "inset" | "popout";
14
8
  /** **(Optional)** Specify whether chevron is visible. */
15
9
  chevron?: boolean;
16
- /** **(Optional)** Main content inside the component. It can be any valid JSX or string. */
10
+ /** **(Optional)** Content inside the component. Children that are not assigned to a slot will be grouped together and rendered under the main Card content. */
17
11
  children?: React.ReactNode;
18
12
  /** **(Optional)** Specify additional CSS classes to be applied to the component. */
19
13
  className?: string;
20
- /** Specify the Card heading. */
21
- heading: string;
22
- /** **(Optional)** Specify the level of the Card heading. */
23
- headingLevel?: "h2" | "h3" | "h4" | "h5" | "h6";
24
- /** **(Optional)** Specify the URL that the Card component will link to when clicked. */
14
+ /** **(Optional)** Specify the URL that the Card will link to. */
25
15
  href?: string;
26
- /** Specify properties of the Card Image. */
27
- image: ImageProps;
16
+ /** **(Optional)** Specify a custom element to use instead of `a`. */
17
+ hrefAs?: React.FC<any> | ForwardRefExoticComponent<any> | string;
28
18
  /** **(Optional)** Specify the position of the image in the Card. */
29
19
  imagePosition?: "left" | "right";
30
- /** **(Optional)** Specify the Card label. */
31
- label?: string;
32
- /** **(Optional)** Specify the background color of the Card label. */
33
- labelBackground?: Lights;
34
20
  /** **(Optional)** Specify the orientation of the Card. */
35
21
  orientation?: "vertical" | "horizontal";
36
22
  }
37
- export declare const Card: ({ as, background, badge, badgePosition, chevron, children, className, heading, headingLevel, href, image, imagePosition, label, labelBackground, orientation, ...props }: CardProps) => JSX.Element;
23
+ interface CardLabelProps extends HTMLAttributes<HTMLSpanElement> {
24
+ /** **(Optional)** Specify the Label background color. */
25
+ background?: Lights;
26
+ /** **(Optional)** Content inside the Label. */
27
+ children?: React.ReactNode;
28
+ /** **(Optional)** Specify additional CSS classes to be applied to the Label. */
29
+ className?: string;
30
+ }
31
+ interface CardHeadingProps extends HTMLAttributes<HTMLDivElement> {
32
+ /** **(Optional)** Specify a custom element to use instead of `h3`. */
33
+ as?: "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "span" | "div";
34
+ /** **(Optional)** Content inside the Heading. */
35
+ children?: React.ReactNode;
36
+ /** **(Optional)** Specify additional CSS classes to be applied to the Heading. */
37
+ className?: string;
38
+ }
39
+ interface CardBodyProps extends HTMLAttributes<HTMLDivElement> {
40
+ /** **(Optional)** Content inside the Body. */
41
+ children?: React.ReactNode;
42
+ }
43
+ interface CardBadgeProps extends HTMLAttributes<HTMLDivElement> {
44
+ /** **(Optional)** Specify on which side the Badge should appear. */
45
+ align?: "left" | "right";
46
+ /** **(Optional)** Content inside the Badge. */
47
+ children?: React.ReactNode;
48
+ /** **(Optional)** Specify additional CSS classes to be applied to the Badge. */
49
+ className?: string;
50
+ /** **(Optional)** Specify badge position relative to top right corner. */
51
+ position?: "inset" | "popout";
52
+ }
53
+ type CardImageProps = ImageProps;
54
+ export declare const Card: {
55
+ ({ background, chevron, children, className, href, hrefAs, imagePosition, orientation, ...props }: CardProps): JSX.Element;
56
+ Label: {
57
+ ({ background, children, className }: CardLabelProps): JSX.Element;
58
+ displayName: string;
59
+ };
60
+ Heading: {
61
+ ({ as, children, className }: CardHeadingProps): JSX.Element;
62
+ displayName: string;
63
+ };
64
+ Badge: {
65
+ ({ align, children, position, ...props }: CardBadgeProps): JSX.Element;
66
+ displayName: string;
67
+ };
68
+ Body: {
69
+ ({ children }: CardBodyProps): JSX.Element;
70
+ displayName: string;
71
+ };
72
+ Image: {
73
+ (props: CardImageProps): import("react/jsx-runtime").JSX.Element;
74
+ displayName: string;
75
+ };
76
+ };
38
77
  export default Card;
@@ -1,37 +1,69 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import clsx from 'clsx';
3
3
  import React from 'react';
4
4
  import { bgPropToClass } from '../../utils/index.js';
5
+ import { getSlots } from '../../utils/slots.js';
5
6
  import { ChevronRightIcon } from '../Icon/ChevronRightIcon.js';
6
7
  import { Image } from '../Image/Image.js';
7
8
 
9
+ const componentSlots = {
10
+ CardBadge: null,
11
+ CardBody: null,
12
+ CardHeading: null,
13
+ CardImage: null,
14
+ CardLabel: null,
15
+ Children: null,
16
+ };
8
17
  function getCardLinkElement(as, href) {
9
- let element = href ? "a" : "div";
10
- if (as) {
11
- element = as;
12
- }
13
18
  return {
14
- element,
19
+ element: as !== null && as !== void 0 ? as : "a",
15
20
  props: {
16
21
  className: "coop-card--link",
17
22
  href,
18
23
  },
19
24
  };
20
25
  }
21
- const Card = ({ as, background = "white", badge, badgePosition = "inset", chevron = false, children, className, heading, headingLevel = "h3", href, image, imagePosition = "left", label = "", labelBackground, orientation = "vertical", ...props }) => {
22
- const linkElement = getCardLinkElement(as, href);
23
- const imageProps = {
24
- crop: "wide",
25
- ...image,
26
- };
26
+ const Card = ({ background = "white", chevron = false, children, className, href, hrefAs, imagePosition = "left", orientation = "vertical", ...props }) => {
27
+ const { element: linkElement, props: linkProps } = getCardLinkElement(hrefAs, href);
28
+ const slots = getSlots(componentSlots, children);
29
+ const innerProps = { className: "coop-card--inner" };
30
+ const hasLinkWrapper = href && !slots.CardHeading;
27
31
  const componentProps = {
28
32
  className: clsx("coop-card", background && bgPropToClass(background, className), className),
29
- "data-badge-pos": badgePosition,
30
33
  "data-image-pos": imagePosition,
31
34
  "data-orientation": orientation !== "vertical" ? orientation : undefined,
32
35
  ...props,
33
36
  };
34
- return (jsxs("article", { ...componentProps, children: [image && jsx(Image, { ...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("span", { className: clsx("coop-card--label", labelBackground && bgPropToClass(labelBackground, className)), children: label })), React.createElement(linkElement.element, linkElement.props, React.createElement(headingLevel, { className: "coop-card--heading" }, heading)), children] }), chevron && (jsx("span", { "aria-hidden": "true", className: "coop-card--icon", role: "presentation", children: jsx(ChevronRightIcon, {}) }))] })] }));
37
+ if (href && slots.CardHeading) {
38
+ slots.CardHeading = React.createElement(linkElement, linkProps, slots.CardHeading);
39
+ }
40
+ return (jsxs("article", { ...componentProps, children: [slots.CardImage, slots.CardBadge, React.createElement(hasLinkWrapper ? linkElement : "div", hasLinkWrapper ? { href, ...innerProps } : innerProps, jsxs("div", { className: "coop-card--content", children: [slots.CardLabel, slots.CardHeading, slots.CardBody, slots.Children] }), chevron && (jsx("span", { "aria-hidden": "true", className: "coop-card--icon", role: "presentation", children: jsx(ChevronRightIcon, {}) })))] }));
41
+ };
42
+ const CardHeading = ({ as = "h3", children, className }) => {
43
+ return React.createElement(as, { className: clsx("coop-card--heading", className) }, children);
44
+ };
45
+ CardHeading.displayName = "Card.Heading";
46
+ const CardLabel = ({ background, children, className }) => {
47
+ return (jsx("span", { className: clsx("coop-card--label", className, background && bgPropToClass(background, className)), children: children }));
48
+ };
49
+ CardLabel.displayName = "Card.Label";
50
+ const CardBadge = ({ align = "right", children, position = "popout", ...props }) => {
51
+ return (jsx("div", { className: "coop-card--badge", "data-align": align, "data-position": position, ...props, children: children }));
52
+ };
53
+ CardBadge.displayName = "Card.Badge";
54
+ const CardBody = ({ children }) => {
55
+ return jsx(Fragment, { children: children });
56
+ };
57
+ CardBody.displayName = "Card.Body";
58
+ const CardImage = (props) => {
59
+ var _a;
60
+ return jsx(Image, { ...props, crop: (_a = props.crop) !== null && _a !== void 0 ? _a : "wide" });
35
61
  };
62
+ CardImage.displayName = "Card.Image";
63
+ Card.Label = CardLabel;
64
+ Card.Heading = CardHeading;
65
+ Card.Badge = CardBadge;
66
+ Card.Body = CardBody;
67
+ Card.Image = CardImage;
36
68
 
37
69
  export { Card, Card as default };
@@ -1,6 +1,6 @@
1
1
  import type { InputHTMLAttributes, JSX } from "react";
2
2
  import { FormFieldError, StandardSizes } from "../../../src/types";
3
- export interface TextInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
3
+ export interface TextInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "prefix" | "size" | "type"> {
4
4
  /** **(Optional)** Specify additional CSS classes to be applied to the component. */
5
5
  className?: string;
6
6
  /** **(Optional)** Specify whether the TextInput should be disabled. Refer to Experience Library guidance on disabled form controls and accessibility. */
@@ -27,12 +27,12 @@ export interface TextInputProps extends Omit<InputHTMLAttributes<HTMLInputElemen
27
27
  name: string;
28
28
  /** **(Optional)** Specify the TextInput placeholder text. Do not use in place of a form label. */
29
29
  placeholder?: string;
30
- /** **(Optional)** Specify the prefix. */
31
- prefix?: string;
30
+ /** **(Optional)** Specify the prefix. It can be any valid JSX or string. */
31
+ prefix?: React.ReactNode;
32
32
  /** **(Optional)** Specify the TextInput size. */
33
33
  size?: StandardSizes;
34
- /** **(Optional)** Specify the suffix. */
35
- suffix?: string;
34
+ /** **(Optional)** Specify the suffix. It can be any valid JSX or string. */
35
+ suffix?: React.ReactNode;
36
36
  /** **(Optional)** Specify the TextInput type. */
37
37
  type?: "text" | "email" | "number" | "password" | "search" | "tel" | "url";
38
38
  }
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ type Slots<T> = Record<keyof T, React.ReactNode>;
3
+ export declare function isKey<T extends object>(x: T, k: PropertyKey): k is keyof T;
4
+ export declare function getSlotName(node: React.ReactNode): string | false;
5
+ export declare function getSlots<T>(componentSlots: Slots<T>, children: React.ReactNode): Slots<T>;
6
+ export {};
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+
3
+ function isKey(x, k) {
4
+ return k in x;
5
+ }
6
+ function getSlotName(node) {
7
+ return React.isValidElement(node) && node.type && typeof node.type !== "string"
8
+ ? node.type.name
9
+ : false;
10
+ }
11
+ function getSlots(componentSlots, children) {
12
+ return React.Children.toArray(children).reduce((slots, child) => {
13
+ const slotName = getSlotName(child);
14
+ if (child && slotName && isKey(componentSlots, slotName)) {
15
+ slots[slotName] = child;
16
+ }
17
+ else if ("Children" in slots) {
18
+ slots.Children = slots.Children ? [...[slots.Children].flat(), child] : [child];
19
+ }
20
+ return slots;
21
+ }, { ...componentSlots });
22
+ }
23
+
24
+ export { getSlotName, getSlots, isKey };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coopdigital/react",
3
3
  "type": "module",
4
- "version": "0.40.1",
4
+ "version": "0.41.1",
5
5
  "private": false,
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -57,10 +57,10 @@
57
57
  "devDependencies": {
58
58
  "@axe-core/playwright": "^4.11.0",
59
59
  "@playwright/test": "^1.56.1",
60
- "@storybook/addon-a11y": "^9.1.13",
61
- "@storybook/addon-docs": "^9.1.13",
62
- "@storybook/addon-onboarding": "^9.1.13",
63
- "@storybook/react-vite": "^9.1.13",
60
+ "@storybook/addon-a11y": "^10.0.3",
61
+ "@storybook/addon-docs": "^10.0.3",
62
+ "@storybook/addon-onboarding": "^10.0.3",
63
+ "@storybook/react-vite": "^10.0.3",
64
64
  "@testing-library/jest-dom": "^6.9.1",
65
65
  "@testing-library/react": "^16.3.0",
66
66
  "@types/react": "^19.2.2",
@@ -68,7 +68,7 @@
68
68
  "react": "^19.2.0",
69
69
  "react-dom": "^19.2.0",
70
70
  "serve": "^14.2.5",
71
- "storybook": "^9.1.13"
71
+ "storybook": "^10.0.3"
72
72
  },
73
73
  "peerDependencies": {
74
74
  "react": "^19.1.0",
@@ -78,8 +78,8 @@
78
78
  "storybook": "$storybook"
79
79
  },
80
80
  "dependencies": {
81
- "@coopdigital/styles": "^0.35.1",
81
+ "@coopdigital/styles": "^0.36.0",
82
82
  "clsx": "^2.1.1"
83
83
  },
84
- "gitHead": "f37b74793a00f0bac49a02d9b3a3b6c1506c404f"
84
+ "gitHead": "32143908f0d9ebc5555ac7ec4c1e57eb3f81a35f"
85
85
  }
@@ -1,57 +1,83 @@
1
- import type { AnchorHTMLAttributes, ForwardRefExoticComponent, HTMLAttributes, JSX } from "react"
1
+ import type { ForwardRefExoticComponent, HTMLAttributes, JSX } from "react"
2
2
 
3
3
  import clsx from "clsx"
4
4
  import React from "react"
5
5
 
6
6
  import { bgPropToClass } from "../../../src/utils"
7
7
  import { Lights, Tints, White } from "../../types/colors"
8
- import { ChevronRightIcon } from "../Icon"
8
+ import { getSlots } from "../../utils/slots"
9
+ import { ChevronRightIcon } from "../Icon/ChevronRightIcon"
9
10
  import { Image, ImageProps } from "../Image"
10
11
 
11
12
  export interface CardProps extends HTMLAttributes<HTMLDivElement> {
12
- //export interface CardProps {
13
- /** **(Optional)** Specify a custom element to override default `a`. */
14
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
- as?: React.FC<AnchorHTMLAttributes<HTMLElement>> | ForwardRefExoticComponent<any> | string
16
13
  /** **(Optional)** Specify the Card background color. */
17
14
  background?: Tints | White
18
- /** **(Optional)** Specify text to display inside the badge. */
19
- badge?: React.ReactNode
20
- /** **(Optional)** Specify badge position relative to top right corner. */
21
- badgePosition?: "inset" | "popout"
22
15
  /** **(Optional)** Specify whether chevron is visible. */
23
16
  chevron?: boolean
24
- /** **(Optional)** Main content inside the component. It can be any valid JSX or string. */
17
+ /** **(Optional)** Content inside the component. Children that are not assigned to a slot will be grouped together and rendered under the main Card content. */
25
18
  children?: React.ReactNode
26
19
  /** **(Optional)** Specify additional CSS classes to be applied to the component. */
27
20
  className?: string
28
- /** Specify the Card heading. */
29
- heading: string
30
- /** **(Optional)** Specify the level of the Card heading. */
31
- headingLevel?: "h2" | "h3" | "h4" | "h5" | "h6"
32
- /** **(Optional)** Specify the URL that the Card component will link to when clicked. */
21
+ /** **(Optional)** Specify the URL that the Card will link to. */
33
22
  href?: string
34
- /** Specify properties of the Card Image. */
35
- image: ImageProps
23
+ /* eslint-disable @typescript-eslint/no-explicit-any */
24
+ /** **(Optional)** Specify a custom element to use instead of `a`. */
25
+ hrefAs?: React.FC<any> | ForwardRefExoticComponent<any> | string
26
+ /* eslint-enable */
36
27
  /** **(Optional)** Specify the position of the image in the Card. */
37
28
  imagePosition?: "left" | "right"
38
- /** **(Optional)** Specify the Card label. */
39
- label?: string
40
- /** **(Optional)** Specify the background color of the Card label. */
41
- labelBackground?: Lights
42
29
  /** **(Optional)** Specify the orientation of the Card. */
43
30
  orientation?: "vertical" | "horizontal"
44
31
  }
45
32
 
46
- function getCardLinkElement(as: CardProps["as"], href?: string) {
47
- let element: CardProps["as"] = href ? "a" : "div"
33
+ interface CardLabelProps extends HTMLAttributes<HTMLSpanElement> {
34
+ /** **(Optional)** Specify the Label background color. */
35
+ background?: Lights
36
+ /** **(Optional)** Content inside the Label. */
37
+ children?: React.ReactNode
38
+ /** **(Optional)** Specify additional CSS classes to be applied to the Label. */
39
+ className?: string
40
+ }
48
41
 
49
- if (as) {
50
- element = as
51
- }
42
+ interface CardHeadingProps extends HTMLAttributes<HTMLDivElement> {
43
+ /** **(Optional)** Specify a custom element to use instead of `h3`. */
44
+ as?: "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "span" | "div"
45
+ /** **(Optional)** Content inside the Heading. */
46
+ children?: React.ReactNode
47
+ /** **(Optional)** Specify additional CSS classes to be applied to the Heading. */
48
+ className?: string
49
+ }
50
+
51
+ interface CardBodyProps extends HTMLAttributes<HTMLDivElement> {
52
+ /** **(Optional)** Content inside the Body. */
53
+ children?: React.ReactNode
54
+ }
52
55
 
56
+ interface CardBadgeProps extends HTMLAttributes<HTMLDivElement> {
57
+ /** **(Optional)** Specify on which side the Badge should appear. */
58
+ align?: "left" | "right"
59
+ /** **(Optional)** Content inside the Badge. */
60
+ children?: React.ReactNode
61
+ /** **(Optional)** Specify additional CSS classes to be applied to the Badge. */
62
+ className?: string
63
+ /** **(Optional)** Specify badge position relative to top right corner. */
64
+ position?: "inset" | "popout"
65
+ }
66
+
67
+ type CardImageProps = ImageProps
68
+
69
+ const componentSlots = {
70
+ CardBadge: null,
71
+ CardBody: null,
72
+ CardHeading: null,
73
+ CardImage: null,
74
+ CardLabel: null,
75
+ Children: null,
76
+ }
77
+
78
+ function getCardLinkElement(as: CardProps["hrefAs"], href?: string) {
53
79
  return {
54
- element,
80
+ element: as ?? "a",
55
81
  props: {
56
82
  className: "coop-card--link",
57
83
  href,
@@ -60,69 +86,110 @@ function getCardLinkElement(as: CardProps["as"], href?: string) {
60
86
  }
61
87
 
62
88
  export const Card = ({
63
- as,
64
89
  background = "white",
65
- badge,
66
- badgePosition = "inset",
67
90
  chevron = false,
68
91
  children,
69
92
  className,
70
- heading,
71
- headingLevel = "h3",
72
93
  href,
73
- image,
94
+ hrefAs,
74
95
  imagePosition = "left",
75
- label = "",
76
- labelBackground,
77
96
  orientation = "vertical",
78
97
  ...props
79
98
  }: CardProps): JSX.Element => {
80
- const linkElement = getCardLinkElement(as, href)
99
+ const { element: linkElement, props: linkProps } = getCardLinkElement(hrefAs, href)
81
100
 
82
- const imageProps: ImageProps = {
83
- crop: "wide",
84
- ...image,
85
- }
101
+ const slots = getSlots(componentSlots, children)
102
+
103
+ const innerProps = { className: "coop-card--inner" }
104
+ const hasLinkWrapper = href && !slots.CardHeading
86
105
 
87
106
  const componentProps = {
88
107
  className: clsx("coop-card", background && bgPropToClass(background, className), className),
89
- "data-badge-pos": badgePosition,
90
108
  "data-image-pos": imagePosition,
91
109
  "data-orientation": orientation !== "vertical" ? orientation : undefined,
92
110
  ...props,
93
111
  }
94
112
 
113
+ if (href && slots.CardHeading) {
114
+ slots.CardHeading = React.createElement(linkElement, linkProps, slots.CardHeading)
115
+ }
116
+
95
117
  return (
96
118
  <article {...componentProps}>
97
- {image && <Image {...imageProps} />}
98
- {badge && <div className="coop-card--badge">{badge}</div>}
99
- <div className="coop-card--inner">
119
+ {slots.CardImage}
120
+ {slots.CardBadge}
121
+ {React.createElement(
122
+ hasLinkWrapper ? linkElement : "div",
123
+ hasLinkWrapper ? { href, ...innerProps } : innerProps,
100
124
  <div className="coop-card--content">
101
- {label && (
102
- <span
103
- className={clsx(
104
- "coop-card--label",
105
- labelBackground && bgPropToClass(labelBackground, className)
106
- )}
107
- >
108
- {label}
109
- </span>
110
- )}
111
- {React.createElement(
112
- linkElement.element,
113
- linkElement.props,
114
- React.createElement(headingLevel, { className: "coop-card--heading" }, heading)
115
- )}
116
- {children}
117
- </div>
118
- {chevron && (
125
+ {slots.CardLabel}
126
+ {slots.CardHeading}
127
+ {slots.CardBody}
128
+ {slots.Children}
129
+ </div>,
130
+ chevron && (
119
131
  <span aria-hidden="true" className="coop-card--icon" role="presentation">
120
132
  <ChevronRightIcon />
121
133
  </span>
122
- )}
123
- </div>
134
+ )
135
+ )}
124
136
  </article>
125
137
  )
126
138
  }
127
139
 
140
+ const CardHeading = ({ as = "h3", children, className }: CardHeadingProps): JSX.Element => {
141
+ return React.createElement(as, { className: clsx("coop-card--heading", className) }, children)
142
+ }
143
+
144
+ CardHeading.displayName = "Card.Heading"
145
+
146
+ const CardLabel = ({ background, children, className }: CardLabelProps): JSX.Element => {
147
+ return (
148
+ <span
149
+ className={clsx(
150
+ "coop-card--label",
151
+ className,
152
+ background && bgPropToClass(background, className)
153
+ )}
154
+ >
155
+ {children}
156
+ </span>
157
+ )
158
+ }
159
+
160
+ CardLabel.displayName = "Card.Label"
161
+
162
+ const CardBadge = ({
163
+ align = "right",
164
+ children,
165
+ position = "popout",
166
+ ...props
167
+ }: CardBadgeProps): JSX.Element => {
168
+ return (
169
+ <div className="coop-card--badge" data-align={align} data-position={position} {...props}>
170
+ {children}
171
+ </div>
172
+ )
173
+ }
174
+
175
+ CardBadge.displayName = "Card.Badge"
176
+
177
+ const CardBody = ({ children }: CardBodyProps): JSX.Element => {
178
+ return <>{children}</>
179
+ }
180
+
181
+ CardBody.displayName = "Card.Body"
182
+
183
+ const CardImage = (props: CardImageProps) => {
184
+ return <Image {...{ ...props, crop: props.crop ?? "wide" }} />
185
+ }
186
+
187
+ CardImage.displayName = "Card.Image"
188
+
189
+ Card.Label = CardLabel
190
+ Card.Heading = CardHeading
191
+ Card.Badge = CardBadge
192
+ Card.Body = CardBody
193
+ Card.Image = CardImage
194
+
128
195
  export default Card
@@ -9,7 +9,7 @@ import { FieldHint } from "../FieldHint"
9
9
  import { FieldLabel } from "../FieldLabel"
10
10
 
11
11
  export interface TextInputProps
12
- extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
12
+ extends Omit<InputHTMLAttributes<HTMLInputElement>, "prefix" | "size" | "type"> {
13
13
  /** **(Optional)** Specify additional CSS classes to be applied to the component. */
14
14
  className?: string
15
15
  /** **(Optional)** Specify whether the TextInput should be disabled. Refer to Experience Library guidance on disabled form controls and accessibility. */
@@ -36,12 +36,12 @@ export interface TextInputProps
36
36
  name: string
37
37
  /** **(Optional)** Specify the TextInput placeholder text. Do not use in place of a form label. */
38
38
  placeholder?: string
39
- /** **(Optional)** Specify the prefix. */
40
- prefix?: string
39
+ /** **(Optional)** Specify the prefix. It can be any valid JSX or string. */
40
+ prefix?: React.ReactNode
41
41
  /** **(Optional)** Specify the TextInput size. */
42
42
  size?: StandardSizes
43
- /** **(Optional)** Specify the suffix. */
44
- suffix?: string
43
+ /** **(Optional)** Specify the suffix. It can be any valid JSX or string. */
44
+ suffix?: React.ReactNode
45
45
  /** **(Optional)** Specify the TextInput type. */
46
46
  type?: "text" | "email" | "number" | "password" | "search" | "tel" | "url"
47
47
  }
@@ -0,0 +1,30 @@
1
+ import React from "react"
2
+
3
+ type Slots<T> = Record<keyof T, React.ReactNode>
4
+
5
+ export function isKey<T extends object>(x: T, k: PropertyKey): k is keyof T {
6
+ return k in x
7
+ }
8
+
9
+ export function getSlotName(node: React.ReactNode): string | false {
10
+ return React.isValidElement(node) && node.type && typeof node.type !== "string"
11
+ ? node.type.name
12
+ : false
13
+ }
14
+
15
+ export function getSlots<T>(componentSlots: Slots<T>, children: React.ReactNode): Slots<T> {
16
+ return React.Children.toArray(children).reduce(
17
+ (slots, child: React.ReactNode) => {
18
+ const slotName = getSlotName(child)
19
+
20
+ if (child && slotName && isKey(componentSlots, slotName)) {
21
+ slots[slotName] = child
22
+ } else if ("Children" in slots) {
23
+ slots.Children = slots.Children ? [...[slots.Children].flat(), child] : [child]
24
+ }
25
+
26
+ return slots
27
+ },
28
+ { ...componentSlots }
29
+ )
30
+ }