reshaped 3.4.4 → 3.4.5

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.
@@ -5,8 +5,8 @@ import Icon from "../Icon/index.js";
5
5
  import View from "../View/index.js";
6
6
  import s from "./Avatar.module.css";
7
7
  const Avatar = (props) => {
8
- const { color = "neutral", variant, src, size = 12, squared, initials, icon, className, imageAttributes, attributes, } = props;
9
- const alt = props.alt || imageAttributes?.alt;
8
+ const { color = "neutral", variant, src, size = 12, squared, initials, icon, className, renderImage, imageAttributes: passedImageAttributes, attributes, } = props;
9
+ const alt = props.alt || passedImageAttributes?.alt;
10
10
  const radius = squared
11
11
  ? responsivePropDependency(size, (size) => {
12
12
  if (size >= 24)
@@ -19,10 +19,24 @@ const Avatar = (props) => {
19
19
  const heightStyles = getHeightStyles(size);
20
20
  const rootClassNames = classNames(s.root, className, heightStyles?.classNames, color && s[`--color-${color}`], variant && s[`--variant-${variant}`]);
21
21
  const renderContent = () => {
22
- if (src)
23
- return (_jsx("img", { ...imageAttributes, role: !alt ? "presentation" : undefined, src: src, alt: alt, className: s.img }));
24
- if (icon)
22
+ if (src) {
23
+ /**
24
+ * Not all img attributes might be supported by custom Image components
25
+ * Here is an example from Next: https://nextjs.org/docs/pages/api-reference/components/image#required-props
26
+ */
27
+ const imageAttributes = {
28
+ ...passedImageAttributes,
29
+ role: !alt ? "presentation" : undefined,
30
+ src,
31
+ alt,
32
+ className: s.img,
33
+ };
34
+ // eslint-disable-next-line jsx-a11y/alt-text
35
+ return renderImage ? renderImage(imageAttributes) : _jsx("img", { ...imageAttributes });
36
+ }
37
+ if (icon) {
25
38
  return (_jsx(Icon, { svg: icon, size: responsivePropDependency(size, (size) => Math.ceil(size * 0.4)) }));
39
+ }
26
40
  return initials;
27
41
  };
28
42
  return (_jsx(View, { borderRadius: radius, attributes: { ...attributes, style: { ...heightStyles?.variables } }, backgroundColor: variant === "faded" ? `${color}-${variant}` : color, className: rootClassNames, children: renderContent() }));
@@ -4,6 +4,7 @@ export type Props = {
4
4
  src?: string;
5
5
  alt?: string;
6
6
  imageAttributes?: G.Attributes<"img">;
7
+ renderImage?: (attributes: G.Attributes<"img">) => React.ReactNode;
7
8
  initials?: string;
8
9
  icon?: IconProps["svg"];
9
10
  squared?: boolean;
@@ -30,4 +30,5 @@ export declare const colors: {
30
30
  export declare const fallback: StoryObj<{
31
31
  handleError: Mock;
32
32
  }>;
33
+ export declare const renderImage: StoryObj;
33
34
  export declare const className: StoryObj;
@@ -145,6 +145,20 @@ export const fallback = {
145
145
  });
146
146
  },
147
147
  };
148
+ export const renderImage = {
149
+ name: "renderImage",
150
+ render: () => (<Example>
151
+ <Example.Item title="renderImage">
152
+ <Avatar src="https://images.unsplash.com/photo-1536880756060-98a6a140f0a7?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1600&q=80" alt="Amsterdam canal" renderImage={(attributes) => <img {...attributes} id="test-image"/>}/>
153
+ </Example.Item>
154
+ </Example>),
155
+ play: ({ canvas }) => {
156
+ const img = canvas.getByRole("img");
157
+ expect(img).toBeInTheDocument();
158
+ expect(img).toHaveAccessibleName("Amsterdam canal");
159
+ expect(img).toHaveAttribute("id", "test-image");
160
+ },
161
+ };
148
162
  export const className = {
149
163
  name: "className, attributes, imageAttributes",
150
164
  render: () => (<div data-testid="root">
@@ -1,6 +1,7 @@
1
+ import React from "react";
1
2
  import * as T from "./Image.types";
2
3
  declare const Image: {
3
- (props: T.Props): import("react/jsx-runtime").JSX.Element;
4
+ (props: T.Props): string | number | bigint | boolean | import("react/jsx-runtime").JSX.Element | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | null | undefined;
4
5
  displayName: string;
5
6
  };
6
7
  export default Image;
@@ -7,7 +7,7 @@ import getRadiusStyles from "../../styles/radius/index.js";
7
7
  import getWidthStyles from "../../styles/width/index.js";
8
8
  import getHeightStyles from "../../styles/height/index.js";
9
9
  const Image = (props) => {
10
- const { src, alt, width, height, onLoad, onError, fallback, displayMode = "cover", borderRadius, className, attributes, imageAttributes, } = props;
10
+ const { src, alt, width, height, onLoad, onError, fallback, displayMode = "cover", borderRadius, className, attributes, imageAttributes: passedImageAttributes, renderImage, } = props;
11
11
  const [status, setStatus] = React.useState("loading");
12
12
  const radiusStyles = getRadiusStyles(borderRadius);
13
13
  const widthStyles = getWidthStyles(width);
@@ -34,11 +34,32 @@ const Image = (props) => {
34
34
  }, [src]);
35
35
  if (isFallback) {
36
36
  if (typeof fallback === "string") {
37
- return (_jsx("img", { ...attributes, src: fallback, alt: alt, role: alt ? undefined : "presentation", className: fallbackClassNames, style: style }));
37
+ const imageAttributes = {
38
+ ...attributes,
39
+ src: fallback,
40
+ alt,
41
+ role: alt ? undefined : "presentation",
42
+ className: fallbackClassNames,
43
+ style,
44
+ };
45
+ // eslint-disable-next-line jsx-a11y/alt-text
46
+ return renderImage ? renderImage(imageAttributes) : _jsx("img", { ...imageAttributes });
38
47
  }
39
48
  return (_jsx("div", { ...attributes, className: fallbackClassNames, style: style, children: fallback }));
40
49
  }
41
- return (_jsx("img", { ...attributes, ...imageAttributes, src: src, alt: alt, role: alt ? undefined : "presentation", onLoad: handleLoad, onError: handleError, className: imgClassNames, style: style }));
50
+ const imageAttributes = {
51
+ ...attributes,
52
+ ...passedImageAttributes,
53
+ src,
54
+ alt,
55
+ role: alt ? undefined : "presentation",
56
+ onLoad: handleLoad,
57
+ onError: handleError,
58
+ className: imgClassNames,
59
+ style,
60
+ };
61
+ // eslint-disable-next-line jsx-a11y/alt-text
62
+ return renderImage ? renderImage(imageAttributes) : _jsx("img", { ...imageAttributes });
42
63
  };
43
64
  Image.displayName = "Image";
44
65
  export default Image;
@@ -6,12 +6,13 @@ export type Props = {
6
6
  alt?: string;
7
7
  width?: G.Responsive<string | number>;
8
8
  height?: G.Responsive<string | number>;
9
+ borderRadius?: Extract<TStyles.Radius, "small" | "medium" | "large">;
10
+ displayMode?: "cover" | "contain";
9
11
  onLoad?: (e: React.SyntheticEvent) => void;
10
12
  onError?: (e: React.SyntheticEvent) => void;
11
13
  fallback?: string | React.ReactNode | boolean;
12
- displayMode?: "cover" | "contain";
13
- borderRadius?: Extract<TStyles.Radius, "small" | "medium" | "large">;
14
+ renderImage?: (attributes: G.Attributes<"img">) => React.ReactNode;
15
+ imageAttributes?: G.Attributes<"img">;
14
16
  className?: G.ClassName;
15
17
  attributes?: G.Attributes<"div"> & G.Attributes<"img">;
16
- imageAttributes?: G.Attributes<"img">;
17
18
  };
@@ -1,7 +1,9 @@
1
+ import { StoryObj } from "@storybook/react";
2
+ import { fn, Mock } from "@storybook/test";
1
3
  declare const _default: {
2
4
  title: string;
3
5
  component: {
4
- (props: import("..").ImageProps): import("react").JSX.Element;
6
+ (props: import("..").ImageProps): string | number | bigint | boolean | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<import("react").ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
5
7
  displayName: string;
6
8
  };
7
9
  parameters: {
@@ -11,10 +13,7 @@ declare const _default: {
11
13
  };
12
14
  };
13
15
  export default _default;
14
- export declare const base: {
15
- name: string;
16
- render: () => import("react").JSX.Element;
17
- };
16
+ export declare const src: StoryObj;
18
17
  export declare const size: {
19
18
  name: string;
20
19
  render: () => import("react").JSX.Element;
@@ -27,11 +26,19 @@ export declare const displayMode: {
27
26
  name: string;
28
27
  render: () => import("react").JSX.Element;
29
28
  };
30
- export declare const ratio: {
29
+ export declare const onLoad: StoryObj<{
30
+ handleLoad: ReturnType<typeof fn>;
31
+ }>;
32
+ export declare const onError: StoryObj<{
33
+ handleError: Mock;
34
+ }>;
35
+ export declare const fallback: {
31
36
  name: string;
32
37
  render: () => import("react").JSX.Element;
33
38
  };
34
- export declare const fallback: {
39
+ export declare const renderImage: StoryObj;
40
+ export declare const imageAttributes: StoryObj;
41
+ export declare const ratio: {
35
42
  name: string;
36
43
  render: () => import("react").JSX.Element;
37
44
  };
@@ -1,3 +1,4 @@
1
+ import { expect, fn, waitFor } from "@storybook/test";
1
2
  import { Example } from "../../../utilities/storybook/index.js";
2
3
  import View from "../../View/index.js";
3
4
  import Icon from "../../Icon/index.js";
@@ -13,13 +14,24 @@ export default {
13
14
  },
14
15
  };
15
16
  const imgUrl = "https://images.unsplash.com/photo-1536880756060-98a6a140f0a7?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1600&q=80";
16
- export const base = {
17
- name: "base",
17
+ export const src = {
18
+ name: "src, alt",
18
19
  render: () => (<Example>
19
- <Example.Item title="base">
20
+ <Example.Item title="src, alt">
20
21
  <Image src={imgUrl} alt="Image alt"/>
21
22
  </Example.Item>
23
+
24
+ <Example.Item title="src">
25
+ <Image src={imgUrl}/>
26
+ </Example.Item>
22
27
  </Example>),
28
+ play: ({ canvas }) => {
29
+ const presentation = canvas.getByRole("presentation");
30
+ const img = canvas.getByRole("img");
31
+ expect(presentation).toBeInTheDocument();
32
+ expect(img).toBeInTheDocument();
33
+ expect(img).toHaveAccessibleName("Image alt");
34
+ },
23
35
  };
24
36
  export const size = {
25
37
  name: "width, height",
@@ -66,20 +78,35 @@ export const displayMode = {
66
78
  </Example.Item>
67
79
  </Example>),
68
80
  };
69
- export const ratio = {
70
- name: "composition, aspectRatio",
71
- render: () => (<Example>
72
- <Example.Item title="ratio: 16/9">
73
- <View aspectRatio={16 / 9}>
74
- <Image src={imgUrl}/>
75
- </View>
76
- </Example.Item>
77
- <Example.Item title="ratio: 16/9, displayMode: contain">
78
- <View aspectRatio={16 / 9}>
79
- <Image src={imgUrl} displayMode="contain"/>
80
- </View>
81
- </Example.Item>
82
- </Example>),
81
+ export const onLoad = {
82
+ name: "onLoad",
83
+ args: {
84
+ handleLoad: fn(),
85
+ },
86
+ render: (args) => <Image src={imgUrl} alt="photo" onLoad={args.handleLoad}/>,
87
+ play: async ({ canvas, args }) => {
88
+ const { handleLoad } = args;
89
+ const img = canvas.getByRole("img");
90
+ await waitFor(() => {
91
+ expect(handleLoad).toHaveBeenCalledTimes(1);
92
+ expect(handleLoad).toHaveBeenCalledWith(expect.objectContaining({ target: img, type: "load" }));
93
+ });
94
+ },
95
+ };
96
+ export const onError = {
97
+ name: "onError",
98
+ args: {
99
+ handleError: fn(),
100
+ },
101
+ render: (args) => <Image src="/invalid.png" alt="photo" onError={args.handleError}/>,
102
+ play: async ({ canvas, args }) => {
103
+ const { handleError } = args;
104
+ const img = canvas.getByRole("img");
105
+ await waitFor(() => {
106
+ expect(handleError).toHaveBeenCalledTimes(1);
107
+ expect(handleError).toHaveBeenCalledWith(expect.objectContaining({ target: img, type: "error" }));
108
+ });
109
+ },
83
110
  };
84
111
  export const fallback = {
85
112
  name: "fallback",
@@ -115,3 +142,49 @@ export const fallback = {
115
142
  </Example.Item>
116
143
  </Example>),
117
144
  };
145
+ export const renderImage = {
146
+ name: "renderImage",
147
+ render: () => (<Example>
148
+ <Example.Item title="renderImage">
149
+ <Image src={imgUrl} alt="Amsterdam canal" renderImage={(attributes) => <img {...attributes} id="test-image"/>}/>
150
+ </Example.Item>
151
+ <Example.Item title="renderImage, fallback">
152
+ <Image src="error" fallback={imgUrl} alt="Amsterdam canal 2" renderImage={(attributes) => <img {...attributes} id="test-image-fallback"/>}/>
153
+ </Example.Item>
154
+ </Example>),
155
+ play: ({ canvas }) => {
156
+ const images = canvas.getAllByRole("img");
157
+ expect(images[0]).toHaveAccessibleName("Amsterdam canal");
158
+ expect(images[0]).toHaveAttribute("id", "test-image");
159
+ expect(images[1]).toHaveAccessibleName("Amsterdam canal 2");
160
+ expect(images[1]).toHaveAttribute("id", "test-image-fallback");
161
+ },
162
+ };
163
+ export const imageAttributes = {
164
+ name: "className, attributes,imageAttributes",
165
+ render: () => (<div data-testid="root">
166
+ <Image src={imgUrl} alt="photo" className="test-classname" attributes={{ id: "test-id" }} imageAttributes={{ "data-testid": "test-img-id" }}/>
167
+ </div>),
168
+ play: async ({ canvas }) => {
169
+ const img = canvas.getByRole("img");
170
+ const root = canvas.getByTestId("root").firstChild;
171
+ expect(root).toHaveClass("test-classname");
172
+ expect(root).toHaveAttribute("id", "test-id");
173
+ expect(img).toHaveAttribute("data-testid", "test-img-id");
174
+ },
175
+ };
176
+ export const ratio = {
177
+ name: "test: aspectRatio",
178
+ render: () => (<Example>
179
+ <Example.Item title="ratio: 16/9">
180
+ <View aspectRatio={16 / 9}>
181
+ <Image src={imgUrl}/>
182
+ </View>
183
+ </Example.Item>
184
+ <Example.Item title="ratio: 16/9, displayMode: contain">
185
+ <View aspectRatio={16 / 9}>
186
+ <Image src={imgUrl} displayMode="contain"/>
187
+ </View>
188
+ </Example.Item>
189
+ </Example>),
190
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "reshaped",
3
3
  "description": "Professionally crafted design system in React & Figma for building products of any scale and complexity",
4
- "version": "3.4.4",
4
+ "version": "3.4.5",
5
5
  "license": "MIT",
6
6
  "email": "hello@reshaped.so",
7
7
  "homepage": "https://reshaped.so",
@@ -1,28 +0,0 @@
1
- import { StoryObj } from "@storybook/react";
2
- import { fn } from "@storybook/test";
3
- declare const _default: {
4
- title: string;
5
- component: {
6
- (props: import("./..").ImageProps): import("react").JSX.Element;
7
- displayName: string;
8
- };
9
- parameters: {
10
- iframe: {
11
- url: string;
12
- };
13
- chromatic: {
14
- disableSnapshot: boolean;
15
- };
16
- };
17
- };
18
- export default _default;
19
- export declare const src: StoryObj;
20
- export declare const ariaLabel: StoryObj;
21
- export declare const onLoad: StoryObj<{
22
- handleLoad: ReturnType<typeof fn>;
23
- }>;
24
- export declare const onError: StoryObj<{
25
- handleError: ReturnType<typeof fn>;
26
- }>;
27
- export declare const className: StoryObj;
28
- export declare const imageAttributes: StoryObj;
@@ -1,80 +0,0 @@
1
- import { expect, fn, waitFor } from "@storybook/test";
2
- import Image from "../index.js";
3
- export default {
4
- title: "Utility components/Image/tests",
5
- component: Image,
6
- parameters: {
7
- iframe: {
8
- url: "https://reshaped.so/docs/utilities/image",
9
- },
10
- chromatic: { disableSnapshot: true },
11
- },
12
- };
13
- const imgUrl = "https://images.unsplash.com/photo-1536880756060-98a6a140f0a7?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1600&q=80";
14
- export const src = {
15
- name: "src, presentation",
16
- render: () => <Image src={imgUrl}/>,
17
- play: ({ canvas }) => {
18
- const img = canvas.getByRole("presentation");
19
- expect(img).toHaveAttribute("src", imgUrl);
20
- },
21
- };
22
- export const ariaLabel = {
23
- name: "src, alt",
24
- render: () => <Image src={imgUrl} alt="photo"/>,
25
- play: ({ canvas }) => {
26
- const img = canvas.getByRole("img");
27
- expect(img).toHaveAccessibleName("photo");
28
- },
29
- };
30
- export const onLoad = {
31
- name: "onLoad",
32
- args: {
33
- handleLoad: fn(),
34
- },
35
- render: (args) => <Image src={imgUrl} alt="photo" onLoad={args.handleLoad}/>,
36
- play: async ({ canvas, args }) => {
37
- const { handleLoad } = args;
38
- const img = canvas.getByRole("img");
39
- await waitFor(() => {
40
- expect(handleLoad).toHaveBeenCalledTimes(1);
41
- expect(handleLoad).toHaveBeenCalledWith(expect.objectContaining({ target: img, type: "load" }));
42
- });
43
- },
44
- };
45
- export const onError = {
46
- name: "onError",
47
- args: {
48
- handleError: fn(),
49
- },
50
- render: (args) => <Image src="/invalid.png" alt="photo" onError={args.handleError}/>,
51
- play: async ({ canvas, args }) => {
52
- const { handleError } = args;
53
- const img = canvas.getByRole("img");
54
- await waitFor(() => {
55
- expect(handleError).toHaveBeenCalledTimes(1);
56
- expect(handleError).toHaveBeenCalledWith(expect.objectContaining({ target: img, type: "error" }));
57
- });
58
- },
59
- };
60
- export const className = {
61
- name: "className, attributes",
62
- render: () => (<div data-testid="root">
63
- <Image src={imgUrl} className="test-classname" attributes={{ id: "test-id" }}/>
64
- </div>),
65
- play: async ({ canvas }) => {
66
- const root = canvas.getByTestId("root").firstChild;
67
- expect(root).toHaveClass("test-classname");
68
- expect(root).toHaveAttribute("id", "test-id");
69
- },
70
- };
71
- export const imageAttributes = {
72
- name: "imageAttributes",
73
- render: () => (<div data-testid="root">
74
- <Image src={imgUrl} alt="photo" attributes={{ id: "test-id" }} imageAttributes={{ id: "test-img-id" }}/>
75
- </div>),
76
- play: async ({ canvas }) => {
77
- const img = canvas.getByRole("img");
78
- expect(img).toHaveAttribute("id", "test-img-id");
79
- },
80
- };