@sproutsocial/seeds-react-image 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintignore ADDED
@@ -0,0 +1,6 @@
1
+ # Node modules
2
+ node_modules/
3
+
4
+ # Build output
5
+ dist/
6
+ coverage/
package/.eslintrc.js ADDED
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ root: true,
3
+ extends: ["eslint-config-seeds/racine"],
4
+ };
@@ -0,0 +1,21 @@
1
+ yarn run v1.22.22
2
+ $ tsup --dts
3
+ CLI Building entry: src/index.ts
4
+ CLI Using tsconfig: tsconfig.json
5
+ CLI tsup v8.0.2
6
+ CLI Using tsup config: /home/runner/work/seeds/seeds/seeds-react/seeds-react-image/tsup.config.ts
7
+ CLI Target: es2022
8
+ CLI Cleaning output folder
9
+ CJS Build start
10
+ ESM Build start
11
+ CJS dist/index.js 3.88 KB
12
+ CJS dist/index.js.map 5.26 KB
13
+ CJS ⚡️ Build success in 81ms
14
+ ESM dist/esm/index.js 2.09 KB
15
+ ESM dist/esm/index.js.map 5.20 KB
16
+ ESM ⚡️ Build success in 83ms
17
+ DTS Build start
18
+ DTS ⚡️ Build success in 22476ms
19
+ DTS dist/index.d.ts 1.50 KB
20
+ DTS dist/index.d.mts 1.50 KB
21
+ Done in 26.15s.
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @sproutsocial/seeds-react-image
2
+
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 426f572: Migrated Avatar and Image components into their own packages
@@ -0,0 +1,103 @@
1
+ // src/Image.tsx
2
+ import * as React from "react";
3
+
4
+ // src/styles.ts
5
+ import styled, { css } from "styled-components";
6
+ import { COMMON, LAYOUT } from "@sproutsocial/seeds-react-system-props";
7
+ var ImageContainer = styled.img.attrs({
8
+ className: "Image"
9
+ })`
10
+ display: block;
11
+
12
+ ${(props) => props.isLoading && css`
13
+ visibility: hidden;
14
+ `}
15
+
16
+ ${COMMON}
17
+ ${LAYOUT}
18
+ `;
19
+ var styles_default = ImageContainer;
20
+
21
+ // src/Image.tsx
22
+ import { jsx } from "react/jsx-runtime";
23
+ var noop = () => {
24
+ };
25
+ var Image = class extends React.Component {
26
+ static defaultProps = {
27
+ title: "",
28
+ onError: noop,
29
+ onClick: noop,
30
+ onLoad: noop
31
+ };
32
+ state = {
33
+ didError: false,
34
+ didLoad: false,
35
+ imageUrl: this.props.src
36
+ };
37
+ imageRef;
38
+ componentDidMount() {
39
+ if (this.imageRef) {
40
+ this.setState({
41
+ didLoad: this.imageRef.complete
42
+ });
43
+ }
44
+ }
45
+ componentDidUpdate(prevProps) {
46
+ if (this.props.src !== prevProps.src) {
47
+ this.setState({
48
+ didError: false,
49
+ didLoad: false,
50
+ imageUrl: this.props.src
51
+ });
52
+ }
53
+ }
54
+ onError = (e) => {
55
+ if (!this.state.didError && this.props.defaultImage) {
56
+ this.setState({
57
+ didError: true,
58
+ imageUrl: this.props.defaultImage
59
+ });
60
+ }
61
+ this.props.onError(e);
62
+ };
63
+ onLoad = () => {
64
+ this.setState({
65
+ didLoad: true
66
+ });
67
+ this.props.onLoad();
68
+ };
69
+ onClick = () => {
70
+ this.props.onClick();
71
+ };
72
+ render() {
73
+ const { alt, title, onClick, onError, onLoad, src, qa, ...rest } = this.props;
74
+ return /* @__PURE__ */ jsx(
75
+ styles_default,
76
+ {
77
+ isLoading: !this.state.didLoad,
78
+ ref: (img) => this.imageRef = img,
79
+ onClick: this.onClick,
80
+ src: this.state.imageUrl,
81
+ onError: (e) => this.onError(e),
82
+ onLoad: this.onLoad,
83
+ alt,
84
+ title,
85
+ "data-qa-image": title ? title : alt ? alt : "",
86
+ "data-qa-image-src": src || "",
87
+ ...qa,
88
+ ...rest
89
+ }
90
+ );
91
+ }
92
+ };
93
+
94
+ // src/ImageTypes.ts
95
+ import "react";
96
+
97
+ // src/index.ts
98
+ var src_default = Image;
99
+ export {
100
+ Image,
101
+ src_default as default
102
+ };
103
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/Image.tsx","../../src/styles.ts","../../src/ImageTypes.ts","../../src/index.ts"],"sourcesContent":["import * as React from \"react\";\nimport ImageContainer from \"./styles\";\nimport type { TypeImageProps } from \"./ImageTypes\";\n\ntype TypeState = {\n didError: boolean;\n didLoad: boolean;\n imageUrl: string;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-empty-function\nconst noop = () => {};\n\nexport default class Image extends React.Component<TypeImageProps, TypeState> {\n static defaultProps = {\n title: \"\",\n onError: noop,\n onClick: noop,\n onLoad: noop,\n };\n\n override state = {\n didError: false,\n didLoad: false,\n imageUrl: this.props.src,\n };\n\n imageRef?: HTMLImageElement | null;\n\n override componentDidMount() {\n // eslint-disable-next-line react/no-did-mount-set-state\n if (this.imageRef) {\n this.setState({\n didLoad: this.imageRef.complete,\n });\n }\n }\n\n override componentDidUpdate(prevProps: TypeImageProps) {\n if (this.props.src !== prevProps.src) {\n this.setState({\n didError: false,\n didLoad: false,\n imageUrl: this.props.src,\n });\n }\n }\n\n onError = (e: React.SyntheticEvent<HTMLImageElement, Event>) => {\n if (!this.state.didError && this.props.defaultImage) {\n this.setState({\n didError: true,\n imageUrl: this.props.defaultImage,\n });\n }\n\n this.props.onError(e);\n };\n\n onLoad = () => {\n this.setState({\n didLoad: true,\n });\n this.props.onLoad();\n };\n\n onClick = () => {\n this.props.onClick();\n };\n\n override render() {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { alt, title, onClick, onError, onLoad, src, qa, ...rest } =\n this.props;\n return (\n <ImageContainer\n isLoading={!this.state.didLoad}\n ref={(img) => (this.imageRef = img)}\n onClick={this.onClick}\n src={this.state.imageUrl}\n onError={(e: React.SyntheticEvent<HTMLImageElement, Event>) =>\n this.onError(e)\n }\n onLoad={this.onLoad}\n alt={alt}\n title={title}\n data-qa-image={title ? title : alt ? alt : \"\"}\n data-qa-image-src={src || \"\"}\n {...(qa as any)}\n {...rest}\n />\n );\n }\n}\n","import styled, { css } from \"styled-components\";\nimport { COMMON, LAYOUT } from \"@sproutsocial/seeds-react-system-props\";\n\ninterface ImageContainerProps {\n isLoading?: boolean;\n}\nconst ImageContainer = styled.img.attrs({\n className: \"Image\",\n})<ImageContainerProps>`\n display: block;\n\n ${(props) =>\n props.isLoading &&\n css`\n visibility: hidden;\n `}\n\n ${COMMON}\n ${LAYOUT}\n`;\n\nexport default ImageContainer;\n","import * as React from \"react\";\nimport type {\n TypeSystemCommonProps,\n TypeSystemLayoutProps,\n TypeStyledComponentsCommonProps,\n} from \"@sproutsocial/seeds-react-system-props\";\n\nexport interface TypeImageProps\n extends TypeStyledComponentsCommonProps,\n TypeSystemCommonProps,\n TypeSystemLayoutProps,\n Omit<React.ComponentPropsWithoutRef<\"img\">, \"color\" | \"height\" | \"width\"> {\n src: string;\n\n /** Alt text is required for non-decorative images */\n alt: string;\n title: string;\n onError: (e: React.SyntheticEvent<HTMLImageElement, Event>) => void;\n onClick: () => void;\n\n /** A URL for a default image to load if the source image is not available */\n defaultImage?: string;\n onLoad: () => void;\n qa?: object;\n}\n","import Image from \"./Image\";\n\nexport default Image;\nexport { Image };\nexport * from \"./ImageTypes\";\n"],"mappings":";AAAA,YAAY,WAAW;;;ACAvB,OAAO,UAAU,WAAW;AAC5B,SAAS,QAAQ,cAAc;AAK/B,IAAM,iBAAiB,OAAO,IAAI,MAAM;AAAA,EACtC,WAAW;AACb,CAAC;AAAA;AAAA;AAAA,IAGG,CAAC,UACD,MAAM,aACN;AAAA;AAAA,KAEC;AAAA;AAAA,IAED,MAAM;AAAA,IACN,MAAM;AAAA;AAGV,IAAO,iBAAQ;;;ADsDT;AAhEN,IAAM,OAAO,MAAM;AAAC;AAEpB,IAAqB,QAArB,cAAyC,gBAAqC;AAAA,EAC5E,OAAO,eAAe;AAAA,IACpB,OAAO;AAAA,IACP,SAAS;AAAA,IACT,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AAAA,EAES,QAAQ;AAAA,IACf,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU,KAAK,MAAM;AAAA,EACvB;AAAA,EAEA;AAAA,EAES,oBAAoB;AAE3B,QAAI,KAAK,UAAU;AACjB,WAAK,SAAS;AAAA,QACZ,SAAS,KAAK,SAAS;AAAA,MACzB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAES,mBAAmB,WAA2B;AACrD,QAAI,KAAK,MAAM,QAAQ,UAAU,KAAK;AACpC,WAAK,SAAS;AAAA,QACZ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,KAAK,MAAM;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,UAAU,CAAC,MAAqD;AAC9D,QAAI,CAAC,KAAK,MAAM,YAAY,KAAK,MAAM,cAAc;AACnD,WAAK,SAAS;AAAA,QACZ,UAAU;AAAA,QACV,UAAU,KAAK,MAAM;AAAA,MACvB,CAAC;AAAA,IACH;AAEA,SAAK,MAAM,QAAQ,CAAC;AAAA,EACtB;AAAA,EAEA,SAAS,MAAM;AACb,SAAK,SAAS;AAAA,MACZ,SAAS;AAAA,IACX,CAAC;AACD,SAAK,MAAM,OAAO;AAAA,EACpB;AAAA,EAEA,UAAU,MAAM;AACd,SAAK,MAAM,QAAQ;AAAA,EACrB;AAAA,EAES,SAAS;AAEhB,UAAM,EAAE,KAAK,OAAO,SAAS,SAAS,QAAQ,KAAK,IAAI,GAAG,KAAK,IAC7D,KAAK;AACP,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,CAAC,KAAK,MAAM;AAAA,QACvB,KAAK,CAAC,QAAS,KAAK,WAAW;AAAA,QAC/B,SAAS,KAAK;AAAA,QACd,KAAK,KAAK,MAAM;AAAA,QAChB,SAAS,CAAC,MACR,KAAK,QAAQ,CAAC;AAAA,QAEhB,QAAQ,KAAK;AAAA,QACb;AAAA,QACA;AAAA,QACA,iBAAe,QAAQ,QAAQ,MAAM,MAAM;AAAA,QAC3C,qBAAmB,OAAO;AAAA,QACzB,GAAI;AAAA,QACJ,GAAG;AAAA;AAAA,IACN;AAAA,EAEJ;AACF;;;AE7FA,OAAuB;;;ACEvB,IAAO,cAAQ;","names":[]}
@@ -0,0 +1,44 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+ import { TypeStyledComponentsCommonProps, TypeSystemCommonProps, TypeSystemLayoutProps } from '@sproutsocial/seeds-react-system-props';
4
+
5
+ interface TypeImageProps extends TypeStyledComponentsCommonProps, TypeSystemCommonProps, TypeSystemLayoutProps, Omit<React.ComponentPropsWithoutRef<"img">, "color" | "height" | "width"> {
6
+ src: string;
7
+ /** Alt text is required for non-decorative images */
8
+ alt: string;
9
+ title: string;
10
+ onError: (e: React.SyntheticEvent<HTMLImageElement, Event>) => void;
11
+ onClick: () => void;
12
+ /** A URL for a default image to load if the source image is not available */
13
+ defaultImage?: string;
14
+ onLoad: () => void;
15
+ qa?: object;
16
+ }
17
+
18
+ type TypeState = {
19
+ didError: boolean;
20
+ didLoad: boolean;
21
+ imageUrl: string;
22
+ };
23
+ declare class Image extends React.Component<TypeImageProps, TypeState> {
24
+ static defaultProps: {
25
+ title: string;
26
+ onError: () => void;
27
+ onClick: () => void;
28
+ onLoad: () => void;
29
+ };
30
+ state: {
31
+ didError: boolean;
32
+ didLoad: boolean;
33
+ imageUrl: string;
34
+ };
35
+ imageRef?: HTMLImageElement | null;
36
+ componentDidMount(): void;
37
+ componentDidUpdate(prevProps: TypeImageProps): void;
38
+ onError: (e: React.SyntheticEvent<HTMLImageElement, Event>) => void;
39
+ onLoad: () => void;
40
+ onClick: () => void;
41
+ render(): react_jsx_runtime.JSX.Element;
42
+ }
43
+
44
+ export { Image, type TypeImageProps, Image as default };
@@ -0,0 +1,44 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+ import { TypeStyledComponentsCommonProps, TypeSystemCommonProps, TypeSystemLayoutProps } from '@sproutsocial/seeds-react-system-props';
4
+
5
+ interface TypeImageProps extends TypeStyledComponentsCommonProps, TypeSystemCommonProps, TypeSystemLayoutProps, Omit<React.ComponentPropsWithoutRef<"img">, "color" | "height" | "width"> {
6
+ src: string;
7
+ /** Alt text is required for non-decorative images */
8
+ alt: string;
9
+ title: string;
10
+ onError: (e: React.SyntheticEvent<HTMLImageElement, Event>) => void;
11
+ onClick: () => void;
12
+ /** A URL for a default image to load if the source image is not available */
13
+ defaultImage?: string;
14
+ onLoad: () => void;
15
+ qa?: object;
16
+ }
17
+
18
+ type TypeState = {
19
+ didError: boolean;
20
+ didLoad: boolean;
21
+ imageUrl: string;
22
+ };
23
+ declare class Image extends React.Component<TypeImageProps, TypeState> {
24
+ static defaultProps: {
25
+ title: string;
26
+ onError: () => void;
27
+ onClick: () => void;
28
+ onLoad: () => void;
29
+ };
30
+ state: {
31
+ didError: boolean;
32
+ didLoad: boolean;
33
+ imageUrl: string;
34
+ };
35
+ imageRef?: HTMLImageElement | null;
36
+ componentDidMount(): void;
37
+ componentDidUpdate(prevProps: TypeImageProps): void;
38
+ onError: (e: React.SyntheticEvent<HTMLImageElement, Event>) => void;
39
+ onLoad: () => void;
40
+ onClick: () => void;
41
+ render(): react_jsx_runtime.JSX.Element;
42
+ }
43
+
44
+ export { Image, type TypeImageProps, Image as default };
package/dist/index.js ADDED
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ Image: () => Image,
34
+ default: () => src_default
35
+ });
36
+ module.exports = __toCommonJS(src_exports);
37
+
38
+ // src/Image.tsx
39
+ var React = __toESM(require("react"));
40
+
41
+ // src/styles.ts
42
+ var import_styled_components = __toESM(require("styled-components"));
43
+ var import_seeds_react_system_props = require("@sproutsocial/seeds-react-system-props");
44
+ var ImageContainer = import_styled_components.default.img.attrs({
45
+ className: "Image"
46
+ })`
47
+ display: block;
48
+
49
+ ${(props) => props.isLoading && import_styled_components.css`
50
+ visibility: hidden;
51
+ `}
52
+
53
+ ${import_seeds_react_system_props.COMMON}
54
+ ${import_seeds_react_system_props.LAYOUT}
55
+ `;
56
+ var styles_default = ImageContainer;
57
+
58
+ // src/Image.tsx
59
+ var import_jsx_runtime = require("react/jsx-runtime");
60
+ var noop = () => {
61
+ };
62
+ var Image = class extends React.Component {
63
+ static defaultProps = {
64
+ title: "",
65
+ onError: noop,
66
+ onClick: noop,
67
+ onLoad: noop
68
+ };
69
+ state = {
70
+ didError: false,
71
+ didLoad: false,
72
+ imageUrl: this.props.src
73
+ };
74
+ imageRef;
75
+ componentDidMount() {
76
+ if (this.imageRef) {
77
+ this.setState({
78
+ didLoad: this.imageRef.complete
79
+ });
80
+ }
81
+ }
82
+ componentDidUpdate(prevProps) {
83
+ if (this.props.src !== prevProps.src) {
84
+ this.setState({
85
+ didError: false,
86
+ didLoad: false,
87
+ imageUrl: this.props.src
88
+ });
89
+ }
90
+ }
91
+ onError = (e) => {
92
+ if (!this.state.didError && this.props.defaultImage) {
93
+ this.setState({
94
+ didError: true,
95
+ imageUrl: this.props.defaultImage
96
+ });
97
+ }
98
+ this.props.onError(e);
99
+ };
100
+ onLoad = () => {
101
+ this.setState({
102
+ didLoad: true
103
+ });
104
+ this.props.onLoad();
105
+ };
106
+ onClick = () => {
107
+ this.props.onClick();
108
+ };
109
+ render() {
110
+ const { alt, title, onClick, onError, onLoad, src, qa, ...rest } = this.props;
111
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
112
+ styles_default,
113
+ {
114
+ isLoading: !this.state.didLoad,
115
+ ref: (img) => this.imageRef = img,
116
+ onClick: this.onClick,
117
+ src: this.state.imageUrl,
118
+ onError: (e) => this.onError(e),
119
+ onLoad: this.onLoad,
120
+ alt,
121
+ title,
122
+ "data-qa-image": title ? title : alt ? alt : "",
123
+ "data-qa-image-src": src || "",
124
+ ...qa,
125
+ ...rest
126
+ }
127
+ );
128
+ }
129
+ };
130
+
131
+ // src/ImageTypes.ts
132
+ var React2 = require("react");
133
+
134
+ // src/index.ts
135
+ var src_default = Image;
136
+ // Annotate the CommonJS export names for ESM import in node:
137
+ 0 && (module.exports = {
138
+ Image
139
+ });
140
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/Image.tsx","../src/styles.ts","../src/ImageTypes.ts"],"sourcesContent":["import Image from \"./Image\";\n\nexport default Image;\nexport { Image };\nexport * from \"./ImageTypes\";\n","import * as React from \"react\";\nimport ImageContainer from \"./styles\";\nimport type { TypeImageProps } from \"./ImageTypes\";\n\ntype TypeState = {\n didError: boolean;\n didLoad: boolean;\n imageUrl: string;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-empty-function\nconst noop = () => {};\n\nexport default class Image extends React.Component<TypeImageProps, TypeState> {\n static defaultProps = {\n title: \"\",\n onError: noop,\n onClick: noop,\n onLoad: noop,\n };\n\n override state = {\n didError: false,\n didLoad: false,\n imageUrl: this.props.src,\n };\n\n imageRef?: HTMLImageElement | null;\n\n override componentDidMount() {\n // eslint-disable-next-line react/no-did-mount-set-state\n if (this.imageRef) {\n this.setState({\n didLoad: this.imageRef.complete,\n });\n }\n }\n\n override componentDidUpdate(prevProps: TypeImageProps) {\n if (this.props.src !== prevProps.src) {\n this.setState({\n didError: false,\n didLoad: false,\n imageUrl: this.props.src,\n });\n }\n }\n\n onError = (e: React.SyntheticEvent<HTMLImageElement, Event>) => {\n if (!this.state.didError && this.props.defaultImage) {\n this.setState({\n didError: true,\n imageUrl: this.props.defaultImage,\n });\n }\n\n this.props.onError(e);\n };\n\n onLoad = () => {\n this.setState({\n didLoad: true,\n });\n this.props.onLoad();\n };\n\n onClick = () => {\n this.props.onClick();\n };\n\n override render() {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { alt, title, onClick, onError, onLoad, src, qa, ...rest } =\n this.props;\n return (\n <ImageContainer\n isLoading={!this.state.didLoad}\n ref={(img) => (this.imageRef = img)}\n onClick={this.onClick}\n src={this.state.imageUrl}\n onError={(e: React.SyntheticEvent<HTMLImageElement, Event>) =>\n this.onError(e)\n }\n onLoad={this.onLoad}\n alt={alt}\n title={title}\n data-qa-image={title ? title : alt ? alt : \"\"}\n data-qa-image-src={src || \"\"}\n {...(qa as any)}\n {...rest}\n />\n );\n }\n}\n","import styled, { css } from \"styled-components\";\nimport { COMMON, LAYOUT } from \"@sproutsocial/seeds-react-system-props\";\n\ninterface ImageContainerProps {\n isLoading?: boolean;\n}\nconst ImageContainer = styled.img.attrs({\n className: \"Image\",\n})<ImageContainerProps>`\n display: block;\n\n ${(props) =>\n props.isLoading &&\n css`\n visibility: hidden;\n `}\n\n ${COMMON}\n ${LAYOUT}\n`;\n\nexport default ImageContainer;\n","import * as React from \"react\";\nimport type {\n TypeSystemCommonProps,\n TypeSystemLayoutProps,\n TypeStyledComponentsCommonProps,\n} from \"@sproutsocial/seeds-react-system-props\";\n\nexport interface TypeImageProps\n extends TypeStyledComponentsCommonProps,\n TypeSystemCommonProps,\n TypeSystemLayoutProps,\n Omit<React.ComponentPropsWithoutRef<\"img\">, \"color\" | \"height\" | \"width\"> {\n src: string;\n\n /** Alt text is required for non-decorative images */\n alt: string;\n title: string;\n onError: (e: React.SyntheticEvent<HTMLImageElement, Event>) => void;\n onClick: () => void;\n\n /** A URL for a default image to load if the source image is not available */\n defaultImage?: string;\n onLoad: () => void;\n qa?: object;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,YAAuB;;;ACAvB,+BAA4B;AAC5B,sCAA+B;AAK/B,IAAM,iBAAiB,yBAAAA,QAAO,IAAI,MAAM;AAAA,EACtC,WAAW;AACb,CAAC;AAAA;AAAA;AAAA,IAGG,CAAC,UACD,MAAM,aACN;AAAA;AAAA,KAEC;AAAA;AAAA,IAED,sCAAM;AAAA,IACN,sCAAM;AAAA;AAGV,IAAO,iBAAQ;;;ADsDT;AAhEN,IAAM,OAAO,MAAM;AAAC;AAEpB,IAAqB,QAArB,cAAyC,gBAAqC;AAAA,EAC5E,OAAO,eAAe;AAAA,IACpB,OAAO;AAAA,IACP,SAAS;AAAA,IACT,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AAAA,EAES,QAAQ;AAAA,IACf,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU,KAAK,MAAM;AAAA,EACvB;AAAA,EAEA;AAAA,EAES,oBAAoB;AAE3B,QAAI,KAAK,UAAU;AACjB,WAAK,SAAS;AAAA,QACZ,SAAS,KAAK,SAAS;AAAA,MACzB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAES,mBAAmB,WAA2B;AACrD,QAAI,KAAK,MAAM,QAAQ,UAAU,KAAK;AACpC,WAAK,SAAS;AAAA,QACZ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,KAAK,MAAM;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,UAAU,CAAC,MAAqD;AAC9D,QAAI,CAAC,KAAK,MAAM,YAAY,KAAK,MAAM,cAAc;AACnD,WAAK,SAAS;AAAA,QACZ,UAAU;AAAA,QACV,UAAU,KAAK,MAAM;AAAA,MACvB,CAAC;AAAA,IACH;AAEA,SAAK,MAAM,QAAQ,CAAC;AAAA,EACtB;AAAA,EAEA,SAAS,MAAM;AACb,SAAK,SAAS;AAAA,MACZ,SAAS;AAAA,IACX,CAAC;AACD,SAAK,MAAM,OAAO;AAAA,EACpB;AAAA,EAEA,UAAU,MAAM;AACd,SAAK,MAAM,QAAQ;AAAA,EACrB;AAAA,EAES,SAAS;AAEhB,UAAM,EAAE,KAAK,OAAO,SAAS,SAAS,QAAQ,KAAK,IAAI,GAAG,KAAK,IAC7D,KAAK;AACP,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,CAAC,KAAK,MAAM;AAAA,QACvB,KAAK,CAAC,QAAS,KAAK,WAAW;AAAA,QAC/B,SAAS,KAAK;AAAA,QACd,KAAK,KAAK,MAAM;AAAA,QAChB,SAAS,CAAC,MACR,KAAK,QAAQ,CAAC;AAAA,QAEhB,QAAQ,KAAK;AAAA,QACb;AAAA,QACA;AAAA,QACA,iBAAe,QAAQ,QAAQ,MAAM,MAAM;AAAA,QAC3C,qBAAmB,OAAO;AAAA,QACzB,GAAI;AAAA,QACJ,GAAG;AAAA;AAAA,IACN;AAAA,EAEJ;AACF;;;AE7FA,IAAAC,SAAuB;;;AHEvB,IAAO,cAAQ;","names":["styled","React"]}
package/jest.config.js ADDED
@@ -0,0 +1,9 @@
1
+ const baseConfig = require("@sproutsocial/seeds-testing");
2
+
3
+ /** * @type {import('jest').Config} */
4
+ const config = {
5
+ ...baseConfig,
6
+ displayName: "seeds-react-image",
7
+ };
8
+
9
+ module.exports = config;
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@sproutsocial/seeds-react-image",
3
+ "version": "1.0.0",
4
+ "description": "Seeds React Image",
5
+ "author": "Sprout Social, Inc.",
6
+ "license": "MIT",
7
+ "main": "dist/index.js",
8
+ "module": "dist/esm/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "scripts": {
11
+ "build": "tsup --dts",
12
+ "build:debug": "tsup --dts --metafile",
13
+ "dev": "tsup --watch --dts",
14
+ "clean": "rm -rf .turbo dist",
15
+ "clean:modules": "rm -rf node_modules",
16
+ "typecheck": "tsc --noEmit",
17
+ "test": "jest",
18
+ "test:watch": "jest --watch --coverage=false"
19
+ },
20
+ "dependencies": {
21
+ "@sproutsocial/seeds-react-theme": "^*",
22
+ "@sproutsocial/seeds-react-system-props": "^*"
23
+ },
24
+ "devDependencies": {
25
+ "@types/react": "^18.0.0",
26
+ "@types/styled-components": "^5.1.26",
27
+ "@sproutsocial/eslint-config-seeds": "*",
28
+ "react": "^18.0.0",
29
+ "styled-components": "^5.2.3",
30
+ "tsup": "^8.0.2",
31
+ "typescript": "^5.6.2",
32
+ "@sproutsocial/seeds-tsconfig": "*",
33
+ "@sproutsocial/seeds-testing": "*",
34
+ "@sproutsocial/seeds-react-testing-library": "*"
35
+ },
36
+ "peerDependencies": {
37
+ "styled-components": "^5.2.3"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ }
42
+ }
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import Image from "./Image";
4
+
5
+ const meta: Meta<typeof Image> = {
6
+ title: "Components/Image",
7
+ component: Image,
8
+ };
9
+ export default meta;
10
+
11
+ type Story = StoryObj<typeof Image>;
12
+
13
+ const backupImgData =
14
+ "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDY0IDY0IiB2aWV3Qm94PSIwIDAgNjQgNjQiPgogIDxwYXRoIGQ9Im0yOSAzMiAxMy4yIDkuN2MuNC4zLjYuOS4yIDEuNC00LjYgNi40LTEyLjggOS41LTIwLjcgNy4yLTYuOC0xLjktMTIuMS03LjctMTMuNC0xNC43QzYuMSAyMy42IDE1LjQgMTMgMjcgMTNjNi4zIDAgMTIuMiAzLjEgMTUuNyA4LjMuMy40LjIgMS4xLS4zIDEuNEwyOSAzMnoiIHN0eWxlPSJmaWxsOiNkOWRjZTEiLz4KICA8Y2lyY2xlIGN4PSI0MyIgY3k9IjMyIiByPSIzIiBjbGFzcz0ic3QwIi8+CiAgPGNpcmNsZSBjeD0iNTMiIGN5PSIzMiIgcj0iMyIgY2xhc3M9InN0MCIvPgogIDxjaXJjbGUgY3g9IjI4IiBjeT0iMjMiIHI9IjMiIHN0eWxlPSJmaWxsOiNmZmYiLz4KPC9zdmc+Cg==";
15
+
16
+ export const Example: Story = {
17
+ args: {
18
+ src: "http://static01.nyt.com/images/2015/10/24/arts/24drakeart2/24drakeart2-master675.jpg",
19
+ alt: "I know when that hotline bling!",
20
+ },
21
+ };
22
+
23
+ export const ExampleWithFallback: Story = {
24
+ args: {
25
+ width: 1 / 2,
26
+ src: "",
27
+ alt: "I know when that hotline bling!",
28
+ defaultImage: backupImgData,
29
+ },
30
+ };
package/src/Image.tsx ADDED
@@ -0,0 +1,94 @@
1
+ import * as React from "react";
2
+ import ImageContainer from "./styles";
3
+ import type { TypeImageProps } from "./ImageTypes";
4
+
5
+ type TypeState = {
6
+ didError: boolean;
7
+ didLoad: boolean;
8
+ imageUrl: string;
9
+ };
10
+
11
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
12
+ const noop = () => {};
13
+
14
+ export default class Image extends React.Component<TypeImageProps, TypeState> {
15
+ static defaultProps = {
16
+ title: "",
17
+ onError: noop,
18
+ onClick: noop,
19
+ onLoad: noop,
20
+ };
21
+
22
+ override state = {
23
+ didError: false,
24
+ didLoad: false,
25
+ imageUrl: this.props.src,
26
+ };
27
+
28
+ imageRef?: HTMLImageElement | null;
29
+
30
+ override componentDidMount() {
31
+ // eslint-disable-next-line react/no-did-mount-set-state
32
+ if (this.imageRef) {
33
+ this.setState({
34
+ didLoad: this.imageRef.complete,
35
+ });
36
+ }
37
+ }
38
+
39
+ override componentDidUpdate(prevProps: TypeImageProps) {
40
+ if (this.props.src !== prevProps.src) {
41
+ this.setState({
42
+ didError: false,
43
+ didLoad: false,
44
+ imageUrl: this.props.src,
45
+ });
46
+ }
47
+ }
48
+
49
+ onError = (e: React.SyntheticEvent<HTMLImageElement, Event>) => {
50
+ if (!this.state.didError && this.props.defaultImage) {
51
+ this.setState({
52
+ didError: true,
53
+ imageUrl: this.props.defaultImage,
54
+ });
55
+ }
56
+
57
+ this.props.onError(e);
58
+ };
59
+
60
+ onLoad = () => {
61
+ this.setState({
62
+ didLoad: true,
63
+ });
64
+ this.props.onLoad();
65
+ };
66
+
67
+ onClick = () => {
68
+ this.props.onClick();
69
+ };
70
+
71
+ override render() {
72
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
73
+ const { alt, title, onClick, onError, onLoad, src, qa, ...rest } =
74
+ this.props;
75
+ return (
76
+ <ImageContainer
77
+ isLoading={!this.state.didLoad}
78
+ ref={(img) => (this.imageRef = img)}
79
+ onClick={this.onClick}
80
+ src={this.state.imageUrl}
81
+ onError={(e: React.SyntheticEvent<HTMLImageElement, Event>) =>
82
+ this.onError(e)
83
+ }
84
+ onLoad={this.onLoad}
85
+ alt={alt}
86
+ title={title}
87
+ data-qa-image={title ? title : alt ? alt : ""}
88
+ data-qa-image-src={src || ""}
89
+ {...(qa as any)}
90
+ {...rest}
91
+ />
92
+ );
93
+ }
94
+ }
@@ -0,0 +1,25 @@
1
+ import * as React from "react";
2
+ import type {
3
+ TypeSystemCommonProps,
4
+ TypeSystemLayoutProps,
5
+ TypeStyledComponentsCommonProps,
6
+ } from "@sproutsocial/seeds-react-system-props";
7
+
8
+ export interface TypeImageProps
9
+ extends TypeStyledComponentsCommonProps,
10
+ TypeSystemCommonProps,
11
+ TypeSystemLayoutProps,
12
+ Omit<React.ComponentPropsWithoutRef<"img">, "color" | "height" | "width"> {
13
+ src: string;
14
+
15
+ /** Alt text is required for non-decorative images */
16
+ alt: string;
17
+ title: string;
18
+ onError: (e: React.SyntheticEvent<HTMLImageElement, Event>) => void;
19
+ onClick: () => void;
20
+
21
+ /** A URL for a default image to load if the source image is not available */
22
+ defaultImage?: string;
23
+ onLoad: () => void;
24
+ qa?: object;
25
+ }
@@ -0,0 +1,26 @@
1
+ import * as React from "react";
2
+ import { render } from "@sproutsocial/seeds-react-testing-library";
3
+ import Image from "../Image";
4
+
5
+ describe.skip("Image/types", () => {
6
+ it("should render valid props", () => {
7
+ render(
8
+ <>
9
+ <Image
10
+ src="http://static01.nyt.com/images/2015/10/24/arts/24drakeart2/24drakeart2-master675.jpg"
11
+ alt="I know when that hotline bling!"
12
+ />
13
+ <Image
14
+ width={1 / 2}
15
+ src="https://iwontload.abc/image-doesnt-exist.jpg"
16
+ alt="I know when that hotline bling!"
17
+ defaultImage="https://api.time.com/wp-content/uploads/2022/12/time-person-of-the-year-2022-volodymyr-zelensky-3.jpg?quality=75&w=400"
18
+ />
19
+ {/* @ts-expect-error - test that missing src is rejected */}
20
+ <Image alt="I know when that hotline bling!" />
21
+ {/* @ts-expect-error - test that missing alt is rejected */}
22
+ <Image src="http://static01.nyt.com/images/2015/10/24/arts/24drakeart2/24drakeart2-master675.jpg" />
23
+ </>
24
+ );
25
+ });
26
+ });
@@ -0,0 +1,94 @@
1
+ import React from "react";
2
+ import {
3
+ render,
4
+ fireEvent,
5
+ screen,
6
+ } from "@sproutsocial/seeds-react-testing-library";
7
+ import Image from "../Image";
8
+
9
+ describe("Image", () => {
10
+ it("should render properly", () => {
11
+ const { container } = render(
12
+ <Image
13
+ alt="image"
14
+ className="foo"
15
+ src="http://media3.giphy.com/media/jbxQLpOKN2URa/giphy.gif"
16
+ />
17
+ );
18
+ expect(container).toBeTruthy();
19
+ });
20
+
21
+ it("should find broken image src URL and render defaultImage (fallback) src URL", () => {
22
+ render(
23
+ <Image
24
+ className="foo"
25
+ src="https://iwontload.abc/image-doesnt-exist.jpg"
26
+ alt="Broken Image"
27
+ defaultImage="http://media3.giphy.com/media/jbxQLpOKN2URa/giphy.gif"
28
+ />
29
+ );
30
+ const img = screen.getByDataQaLabel({
31
+ "image-src": "https://iwontload.abc/image-doesnt-exist.jpg",
32
+ });
33
+ expect(img).toHaveAttribute(
34
+ "src",
35
+ "https://iwontload.abc/image-doesnt-exist.jpg"
36
+ );
37
+ fireEvent.error(img);
38
+ expect(img).toHaveAttribute(
39
+ "src",
40
+ "http://media3.giphy.com/media/jbxQLpOKN2URa/giphy.gif"
41
+ );
42
+ });
43
+
44
+ it("should attempt to load and display valid image", () => {
45
+ render(
46
+ <Image
47
+ className="foo"
48
+ src="http://media3.giphy.com/media/jbxQLpOKN2URa/giphy.gif"
49
+ alt="Broken Image"
50
+ defaultImage="https://iwontload.abc/image-doesnt-exist.jpg"
51
+ />
52
+ );
53
+ const img = screen.getByDataQaLabel({
54
+ "image-src": "http://media3.giphy.com/media/jbxQLpOKN2URa/giphy.gif",
55
+ });
56
+ fireEvent.load(img);
57
+ expect(img).toHaveAttribute(
58
+ "src",
59
+ "http://media3.giphy.com/media/jbxQLpOKN2URa/giphy.gif"
60
+ );
61
+ });
62
+
63
+ it("should update when the src prop changes", () => {
64
+ const src1 = "http://media3.giphy.com/media/jbxQLpOKN2URa/giphy.gif";
65
+ const src2 = "https://media3.giphy.com/media/gYZ7qO81g4dt6/giphy.gif";
66
+ const { rerender } = render(
67
+ <Image
68
+ className="foo"
69
+ src={src1}
70
+ alt="Broken Image"
71
+ defaultImage="https://iwontload.abc/image-doesnt-exist.jpg"
72
+ />
73
+ );
74
+ const img = screen.getByDataQaLabel({
75
+ "image-src": src1,
76
+ });
77
+ fireEvent.load(img);
78
+ expect(img).toHaveAttribute("src", src1);
79
+
80
+ rerender(
81
+ <Image
82
+ className="foo"
83
+ src={src2}
84
+ alt="Broken Image"
85
+ defaultImage="https://iwontload.abc/image-doesnt-exist.jpg"
86
+ />
87
+ );
88
+ const img2 = screen.getByDataQaLabel({
89
+ "image-src": src2,
90
+ });
91
+ fireEvent.load(img2);
92
+ expect(img).toHaveAttribute("src", src2);
93
+ });
94
+ });
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ import Image from "./Image";
2
+
3
+ export default Image;
4
+ export { Image };
5
+ export * from "./ImageTypes";
package/src/styles.ts ADDED
@@ -0,0 +1,22 @@
1
+ import styled, { css } from "styled-components";
2
+ import { COMMON, LAYOUT } from "@sproutsocial/seeds-react-system-props";
3
+
4
+ interface ImageContainerProps {
5
+ isLoading?: boolean;
6
+ }
7
+ const ImageContainer = styled.img.attrs({
8
+ className: "Image",
9
+ })<ImageContainerProps>`
10
+ display: block;
11
+
12
+ ${(props) =>
13
+ props.isLoading &&
14
+ css`
15
+ visibility: hidden;
16
+ `}
17
+
18
+ ${COMMON}
19
+ ${LAYOUT}
20
+ `;
21
+
22
+ export default ImageContainer;
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "@sproutsocial/seeds-tsconfig/bundler/dom/library-monorepo",
3
+ "compilerOptions": {
4
+ "jsx": "react-jsx",
5
+ "module": "esnext"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist", "coverage"]
9
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig((options) => ({
4
+ entry: ["src/index.ts"],
5
+ format: ["cjs", "esm"],
6
+ clean: true,
7
+ legacyOutput: true,
8
+ dts: options.dts,
9
+ external: ["react"],
10
+ sourcemap: true,
11
+ metafile: options.metafile,
12
+ }));