@sproutsocial/seeds-react-image 1.0.28 → 1.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,23 @@
1
1
  yarn run v1.22.22
2
- $ tsup --dts
2
+ $ tsup --dts && cp src/image.css dist/image.css
3
+ ▲ [WARNING] The condition "types" here will never be used as it comes after both "import" and "require" [package.json]
4
+
5
+ package.json:14:6:
6
+ 14 │ "types": "./dist/index.d.ts"
7
+ ╵ ~~~~~~~
8
+
9
+ The "import" condition comes earlier and will be used for all "import" statements:
10
+
11
+ package.json:12:6:
12
+ 12 │ "import": "./dist/esm/index.js",
13
+ ╵ ~~~~~~~~
14
+
15
+ The "require" condition comes earlier and will be used for all "require" calls:
16
+
17
+ package.json:13:6:
18
+ 13 │ "require": "./dist/index.js",
19
+ ╵ ~~~~~~~~~
20
+
3
21
  CLI Building entry: src/index.ts
4
22
  CLI Using tsconfig: tsconfig.json
5
23
  CLI tsup v8.5.0
@@ -8,14 +26,52 @@ $ tsup --dts
8
26
  CLI Cleaning output folder
9
27
  CJS Build start
10
28
  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 14ms
14
- ESM dist/esm/index.js 2.09 KB
15
- ESM dist/esm/index.js.map 5.20 KB
16
- ESM ⚡️ Build success in 14ms
29
+ [warn] ▲ [WARNING] The condition "types" here will never be used as it comes after both "import" and "require" [package.json]
30
+
31
+ package.json:14:6:
32
+  14 │ "types": "./dist/index.d.ts"
33
+ ~~~~~~~
34
+
35
+ The "import" condition comes earlier and will be used for all "import" statements:
36
+
37
+ package.json:12:6:
38
+  12 │ "import": "./dist/esm/index.js",
39
+ ╵ ~~~~~~~~
40
+
41
+ The "require" condition comes earlier and will be used for all "require" calls:
42
+
43
+ package.json:13:6:
44
+  13 │ "require": "./dist/index.js",
45
+ ╵ ~~~~~~~~~
46
+
47
+
48
+ [warn] ▲ [WARNING] The condition "types" here will never be used as it comes after both "import" and "require" [package.json]
49
+
50
+ package.json:14:6:
51
+  14 │ "types": "./dist/index.d.ts"
52
+ ╵ ~~~~~~~
53
+
54
+ The "import" condition comes earlier and will be used for all "import" statements:
55
+
56
+ package.json:12:6:
57
+  12 │ "import": "./dist/esm/index.js",
58
+ ╵ ~~~~~~~~
59
+
60
+ The "require" condition comes earlier and will be used for all "require" calls:
61
+
62
+ package.json:13:6:
63
+  13 │ "require": "./dist/index.js",
64
+ ╵ ~~~~~~~~~
65
+
66
+
67
+ CJS dist/index.js 5.74 KB
68
+ CJS dist/index.js.map 9.86 KB
69
+ CJS ⚡️ Build success in 90ms
70
+ ESM dist/esm/index.js 3.77 KB
71
+ ESM dist/esm/index.js.map 9.73 KB
72
+ ESM ⚡️ Build success in 91ms
17
73
  DTS Build start
18
- DTS ⚡️ Build success in 5608ms
19
- DTS dist/index.d.ts 1.50 KB
20
- DTS dist/index.d.mts 1.50 KB
21
- Done in 7.12s.
74
+ DTS ⚡️ Build success in 3441ms
75
+ DTS dist/index.d.ts 1.31 KB
76
+ DTS dist/index.d.mts 1.31 KB
77
+ Done in 4.82s.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @sproutsocial/seeds-react-image
2
2
 
3
+ ## 1.0.29
4
+
5
+ ### Patch Changes
6
+
7
+ - c36361c: Migrate seeds-react-image to the hybrid Tailwind pattern. Adds ImageTailwind
8
+ (pure CSS-class img leaf) and ImageHybrid (routes to the styled-components path
9
+ when styled-system props are present, Tailwind otherwise); Image now re-exports
10
+ the hybrid. Component CSS ships via the racine components.css aggregate. No
11
+ public API change — fully backward compatible.
12
+
3
13
  ## 1.0.28
4
14
 
5
15
  ### Patch Changes
package/dist/esm/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  // src/Image.tsx
2
- import * as React from "react";
2
+ import "react";
3
+
4
+ // src/ImageHybrid.tsx
5
+ import * as React2 from "react";
3
6
 
4
7
  // src/styles.ts
5
8
  import styled, { css } from "styled-components";
@@ -18,11 +21,46 @@ var ImageContainer = styled.img.attrs({
18
21
  `;
19
22
  var styles_default = ImageContainer;
20
23
 
21
- // src/Image.tsx
24
+ // src/ImageTailwind.tsx
25
+ import React from "react";
22
26
  import { jsx } from "react/jsx-runtime";
27
+ function cn(...inputs) {
28
+ const classes = [];
29
+ for (const input of inputs) {
30
+ if (!input) continue;
31
+ if (typeof input === "string") {
32
+ classes.push(input);
33
+ } else if (typeof input === "object") {
34
+ for (const [key, value] of Object.entries(input)) {
35
+ if (value) {
36
+ classes.push(key);
37
+ }
38
+ }
39
+ }
40
+ }
41
+ return classes.join(" ");
42
+ }
43
+ var ImageTailwind = React.forwardRef(({ isLoading, className, ...rest }, ref) => /* @__PURE__ */ jsx(
44
+ "img",
45
+ {
46
+ ref,
47
+ className: cn(
48
+ "Image",
49
+ "seeds-image",
50
+ { "seeds-image-loading": !!isLoading },
51
+ className
52
+ ),
53
+ ...rest
54
+ }
55
+ ));
56
+ ImageTailwind.displayName = "ImageTailwind";
57
+
58
+ // src/ImageHybrid.tsx
59
+ import { hasStyledProps } from "@sproutsocial/seeds-react-system-props";
60
+ import { jsx as jsx2 } from "react/jsx-runtime";
23
61
  var noop = () => {
24
62
  };
25
- var Image = class extends React.Component {
63
+ var ImageHybrid = class extends React2.Component {
26
64
  static defaultProps = {
27
65
  title: "",
28
66
  onError: noop,
@@ -70,34 +108,62 @@ var Image = class extends React.Component {
70
108
  this.props.onClick();
71
109
  };
72
110
  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
- );
111
+ const {
112
+ alt,
113
+ title,
114
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
115
+ onClick,
116
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
117
+ onError,
118
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
119
+ onLoad,
120
+ src,
121
+ qa,
122
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
123
+ defaultImage,
124
+ ...rest
125
+ } = this.props;
126
+ const isLoading = !this.state.didLoad;
127
+ const refCb = (img) => this.imageRef = img;
128
+ const shared = {
129
+ onClick: this.onClick,
130
+ src: this.state.imageUrl,
131
+ onError: (e) => this.onError(e),
132
+ onLoad: this.onLoad,
133
+ alt,
134
+ title,
135
+ "data-qa-image": title ? title : alt ? alt : "",
136
+ "data-qa-image-src": src || "",
137
+ ...qa,
138
+ ...rest
139
+ };
140
+ if (hasStyledProps(rest)) {
141
+ return /* @__PURE__ */ jsx2(styles_default, { ref: refCb, isLoading, ...shared });
142
+ }
143
+ return /* @__PURE__ */ jsx2(ImageTailwind, { ref: refCb, isLoading, ...shared });
91
144
  }
92
145
  };
93
146
 
147
+ // src/Image.tsx
148
+ import { jsx as jsx3 } from "react/jsx-runtime";
149
+ var noop2 = () => {
150
+ };
151
+ var Image = (props) => /* @__PURE__ */ jsx3(ImageHybrid, { ...props });
152
+ Image.defaultProps = {
153
+ title: "",
154
+ onError: noop2,
155
+ onClick: noop2,
156
+ onLoad: noop2
157
+ };
158
+ var Image_default = Image;
159
+
94
160
  // src/ImageTypes.ts
95
161
  import "react";
96
162
 
97
163
  // src/index.ts
98
- var index_default = Image;
164
+ var index_default = Image_default;
99
165
  export {
100
- Image,
166
+ Image_default as Image,
101
167
  index_default as default
102
168
  };
103
169
  //# sourceMappingURL=index.js.map
@@ -1 +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 opacity: 0;\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,gBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../src/Image.tsx","../../src/ImageHybrid.tsx","../../src/styles.ts","../../src/ImageTailwind.tsx","../../src/ImageTypes.ts","../../src/index.ts"],"sourcesContent":["import * as React from \"react\";\nimport ImageHybrid from \"./ImageHybrid\";\nimport type { TypeImageProps } from \"./ImageTypes\";\n\n// eslint-disable-next-line @typescript-eslint/no-empty-function\nconst noop = () => {};\n\n/**\n * Image component. Automatically routes between the Tailwind implementation\n * (preferred) and the styled-components implementation (for consumers using\n * system props or a styled() extension). The load/error state lives in\n * ImageHybrid so behavior is identical on both paths.\n */\nconst Image = (props: TypeImageProps) => <ImageHybrid {...props} />;\n\n// Preserve the original public type surface: defaultProps keep title/onError/\n// onClick/onLoad optional at the JSX call site (these are required in\n// TypeImageProps). The defaults are applied before reaching ImageHybrid.\nImage.defaultProps = {\n title: \"\",\n onError: noop,\n onClick: noop,\n onLoad: noop,\n};\n\nexport default Image;\n","/**\n * Hybrid Image Component\n * Owns the load/error state + lifecycle in ONE place and switches only the\n * rendered leaf: the styled-components element when styled-system props are\n * present, otherwise the lighter Tailwind <img>.\n */\n\nimport * as React from \"react\";\nimport ImageContainer from \"./styles\"; // Styled-components version\nimport { ImageTailwind } from \"./ImageTailwind\"; // Tailwind version\nimport { hasStyledProps } from \"@sproutsocial/seeds-react-system-props\";\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 ImageHybrid extends React.Component<\n TypeImageProps,\n TypeState\n> {\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 // defaultImage is consumed by onError (this.props.defaultImage) and must be\n // pulled out of rest so it never reaches the DOM <img> on the Tailwind path\n // (the styled-components leaf filtered it implicitly; a plain <img> does not).\n const {\n alt,\n title,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onClick,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onError,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onLoad,\n src,\n qa,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n defaultImage,\n ...rest\n } = this.props;\n const isLoading = !this.state.didLoad;\n const refCb = (img: HTMLImageElement | null) => (this.imageRef = img);\n const shared = {\n onClick: this.onClick,\n src: this.state.imageUrl,\n onError: (e: React.SyntheticEvent<HTMLImageElement, Event>) =>\n this.onError(e),\n onLoad: this.onLoad,\n alt,\n title,\n \"data-qa-image\": title ? title : alt ? alt : \"\",\n \"data-qa-image-src\": src || \"\",\n ...(qa as any),\n ...rest,\n };\n\n if (hasStyledProps(rest)) {\n // Legacy styled-components leaf — styles.ts unchanged.\n return <ImageContainer ref={refCb} isLoading={isLoading} {...shared} />;\n }\n\n // Tailwind leaf (preferred - lighter, no styled-components runtime).\n return <ImageTailwind ref={refCb} isLoading={isLoading} {...shared} />;\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 opacity: 0;\n `}\n\n ${COMMON}\n ${LAYOUT}\n`;\n\nexport default ImageContainer;\n","/**\n * Tailwind CSS implementation of Image component\n * Uses Seeds image CSS classes from image.css\n */\n\nimport React from \"react\";\n\n// Utility for merging class names properly\nfunction cn(\n ...inputs: (string | undefined | null | false | Record<string, boolean>)[]\n): string {\n const classes: string[] = [];\n\n for (const input of inputs) {\n if (!input) continue;\n\n if (typeof input === \"string\") {\n classes.push(input);\n } else if (typeof input === \"object\") {\n for (const [key, value] of Object.entries(input)) {\n if (value) {\n classes.push(key);\n }\n }\n }\n }\n\n return classes.join(\" \");\n}\n\ntype TypeImageTailwindProps = React.ComponentPropsWithoutRef<\"img\"> & {\n isLoading?: boolean;\n};\n\nexport const ImageTailwind = React.forwardRef<\n HTMLImageElement,\n TypeImageTailwindProps\n>(({ isLoading, className, ...rest }, ref) => (\n <img\n ref={ref}\n // Preserve the legacy \"Image\" marker class (set by styles.ts's .attrs)\n // for any consumer selectors targeting it.\n className={cn(\n \"Image\",\n \"seeds-image\",\n { \"seeds-image-loading\": !!isLoading },\n className\n )}\n {...rest}\n />\n));\n\nImageTailwind.displayName = \"ImageTailwind\";\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":"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","names":["React","jsx","jsx","noop"]}
package/dist/image.css ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Seeds Image component classes
3
+ * Use these instead of writing out individual Tailwind utility classes.
4
+ *
5
+ * Usage:
6
+ * <img class="seeds-image" />
7
+ * <img class="seeds-image seeds-image-loading" />
8
+ */
9
+
10
+ @layer components {
11
+ .seeds-image {
12
+ display: block;
13
+ }
14
+
15
+ .seeds-image-loading {
16
+ opacity: 0;
17
+ }
18
+ }
package/dist/index.d.mts CHANGED
@@ -15,30 +15,20 @@ interface TypeImageProps extends TypeStyledComponentsCommonProps, TypeSystemComm
15
15
  qa?: object;
16
16
  }
17
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: {
18
+ /**
19
+ * Image component. Automatically routes between the Tailwind implementation
20
+ * (preferred) and the styled-components implementation (for consumers using
21
+ * system props or a styled() extension). The load/error state lives in
22
+ * ImageHybrid so behavior is identical on both paths.
23
+ */
24
+ declare const Image: {
25
+ (props: TypeImageProps): react_jsx_runtime.JSX.Element;
26
+ defaultProps: {
25
27
  title: string;
26
28
  onError: () => void;
27
29
  onClick: () => void;
28
30
  onLoad: () => void;
29
31
  };
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
- }
32
+ };
43
33
 
44
34
  export { Image, type TypeImageProps, Image as default };
package/dist/index.d.ts CHANGED
@@ -15,30 +15,20 @@ interface TypeImageProps extends TypeStyledComponentsCommonProps, TypeSystemComm
15
15
  qa?: object;
16
16
  }
17
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: {
18
+ /**
19
+ * Image component. Automatically routes between the Tailwind implementation
20
+ * (preferred) and the styled-components implementation (for consumers using
21
+ * system props or a styled() extension). The load/error state lives in
22
+ * ImageHybrid so behavior is identical on both paths.
23
+ */
24
+ declare const Image: {
25
+ (props: TypeImageProps): react_jsx_runtime.JSX.Element;
26
+ defaultProps: {
25
27
  title: string;
26
28
  onError: () => void;
27
29
  onClick: () => void;
28
30
  onLoad: () => void;
29
31
  };
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
- }
32
+ };
43
33
 
44
34
  export { Image, type TypeImageProps, Image as default };
package/dist/index.js CHANGED
@@ -30,13 +30,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- Image: () => Image,
33
+ Image: () => Image_default,
34
34
  default: () => index_default
35
35
  });
36
36
  module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/Image.tsx
39
- var React = __toESM(require("react"));
39
+ var React3 = require("react");
40
+
41
+ // src/ImageHybrid.tsx
42
+ var React2 = __toESM(require("react"));
40
43
 
41
44
  // src/styles.ts
42
45
  var import_styled_components = __toESM(require("styled-components"));
@@ -55,11 +58,46 @@ var ImageContainer = import_styled_components.default.img.attrs({
55
58
  `;
56
59
  var styles_default = ImageContainer;
57
60
 
58
- // src/Image.tsx
61
+ // src/ImageTailwind.tsx
62
+ var import_react = __toESM(require("react"));
59
63
  var import_jsx_runtime = require("react/jsx-runtime");
64
+ function cn(...inputs) {
65
+ const classes = [];
66
+ for (const input of inputs) {
67
+ if (!input) continue;
68
+ if (typeof input === "string") {
69
+ classes.push(input);
70
+ } else if (typeof input === "object") {
71
+ for (const [key, value] of Object.entries(input)) {
72
+ if (value) {
73
+ classes.push(key);
74
+ }
75
+ }
76
+ }
77
+ }
78
+ return classes.join(" ");
79
+ }
80
+ var ImageTailwind = import_react.default.forwardRef(({ isLoading, className, ...rest }, ref) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
81
+ "img",
82
+ {
83
+ ref,
84
+ className: cn(
85
+ "Image",
86
+ "seeds-image",
87
+ { "seeds-image-loading": !!isLoading },
88
+ className
89
+ ),
90
+ ...rest
91
+ }
92
+ ));
93
+ ImageTailwind.displayName = "ImageTailwind";
94
+
95
+ // src/ImageHybrid.tsx
96
+ var import_seeds_react_system_props2 = require("@sproutsocial/seeds-react-system-props");
97
+ var import_jsx_runtime2 = require("react/jsx-runtime");
60
98
  var noop = () => {
61
99
  };
62
- var Image = class extends React.Component {
100
+ var ImageHybrid = class extends React2.Component {
63
101
  static defaultProps = {
64
102
  title: "",
65
103
  onError: noop,
@@ -107,32 +145,60 @@ var Image = class extends React.Component {
107
145
  this.props.onClick();
108
146
  };
109
147
  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
- );
148
+ const {
149
+ alt,
150
+ title,
151
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
152
+ onClick,
153
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
154
+ onError,
155
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
156
+ onLoad,
157
+ src,
158
+ qa,
159
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
160
+ defaultImage,
161
+ ...rest
162
+ } = this.props;
163
+ const isLoading = !this.state.didLoad;
164
+ const refCb = (img) => this.imageRef = img;
165
+ const shared = {
166
+ onClick: this.onClick,
167
+ src: this.state.imageUrl,
168
+ onError: (e) => this.onError(e),
169
+ onLoad: this.onLoad,
170
+ alt,
171
+ title,
172
+ "data-qa-image": title ? title : alt ? alt : "",
173
+ "data-qa-image-src": src || "",
174
+ ...qa,
175
+ ...rest
176
+ };
177
+ if ((0, import_seeds_react_system_props2.hasStyledProps)(rest)) {
178
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(styles_default, { ref: refCb, isLoading, ...shared });
179
+ }
180
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ImageTailwind, { ref: refCb, isLoading, ...shared });
128
181
  }
129
182
  };
130
183
 
184
+ // src/Image.tsx
185
+ var import_jsx_runtime3 = require("react/jsx-runtime");
186
+ var noop2 = () => {
187
+ };
188
+ var Image = (props) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ImageHybrid, { ...props });
189
+ Image.defaultProps = {
190
+ title: "",
191
+ onError: noop2,
192
+ onClick: noop2,
193
+ onLoad: noop2
194
+ };
195
+ var Image_default = Image;
196
+
131
197
  // src/ImageTypes.ts
132
- var React2 = require("react");
198
+ var React4 = require("react");
133
199
 
134
200
  // src/index.ts
135
- var index_default = Image;
201
+ var index_default = Image_default;
136
202
  // Annotate the CommonJS export names for ESM import in node:
137
203
  0 && (module.exports = {
138
204
  Image
package/dist/index.js.map CHANGED
@@ -1 +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 opacity: 0;\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,gBAAQ;","names":["styled","React"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/Image.tsx","../src/ImageHybrid.tsx","../src/styles.ts","../src/ImageTailwind.tsx","../src/ImageTypes.ts"],"sourcesContent":["import Image from \"./Image\";\n\nexport default Image;\nexport { Image };\nexport * from \"./ImageTypes\";\n","import * as React from \"react\";\nimport ImageHybrid from \"./ImageHybrid\";\nimport type { TypeImageProps } from \"./ImageTypes\";\n\n// eslint-disable-next-line @typescript-eslint/no-empty-function\nconst noop = () => {};\n\n/**\n * Image component. Automatically routes between the Tailwind implementation\n * (preferred) and the styled-components implementation (for consumers using\n * system props or a styled() extension). The load/error state lives in\n * ImageHybrid so behavior is identical on both paths.\n */\nconst Image = (props: TypeImageProps) => <ImageHybrid {...props} />;\n\n// Preserve the original public type surface: defaultProps keep title/onError/\n// onClick/onLoad optional at the JSX call site (these are required in\n// TypeImageProps). The defaults are applied before reaching ImageHybrid.\nImage.defaultProps = {\n title: \"\",\n onError: noop,\n onClick: noop,\n onLoad: noop,\n};\n\nexport default Image;\n","/**\n * Hybrid Image Component\n * Owns the load/error state + lifecycle in ONE place and switches only the\n * rendered leaf: the styled-components element when styled-system props are\n * present, otherwise the lighter Tailwind <img>.\n */\n\nimport * as React from \"react\";\nimport ImageContainer from \"./styles\"; // Styled-components version\nimport { ImageTailwind } from \"./ImageTailwind\"; // Tailwind version\nimport { hasStyledProps } from \"@sproutsocial/seeds-react-system-props\";\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 ImageHybrid extends React.Component<\n TypeImageProps,\n TypeState\n> {\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 // defaultImage is consumed by onError (this.props.defaultImage) and must be\n // pulled out of rest so it never reaches the DOM <img> on the Tailwind path\n // (the styled-components leaf filtered it implicitly; a plain <img> does not).\n const {\n alt,\n title,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onClick,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onError,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onLoad,\n src,\n qa,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n defaultImage,\n ...rest\n } = this.props;\n const isLoading = !this.state.didLoad;\n const refCb = (img: HTMLImageElement | null) => (this.imageRef = img);\n const shared = {\n onClick: this.onClick,\n src: this.state.imageUrl,\n onError: (e: React.SyntheticEvent<HTMLImageElement, Event>) =>\n this.onError(e),\n onLoad: this.onLoad,\n alt,\n title,\n \"data-qa-image\": title ? title : alt ? alt : \"\",\n \"data-qa-image-src\": src || \"\",\n ...(qa as any),\n ...rest,\n };\n\n if (hasStyledProps(rest)) {\n // Legacy styled-components leaf — styles.ts unchanged.\n return <ImageContainer ref={refCb} isLoading={isLoading} {...shared} />;\n }\n\n // Tailwind leaf (preferred - lighter, no styled-components runtime).\n return <ImageTailwind ref={refCb} isLoading={isLoading} {...shared} />;\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 opacity: 0;\n `}\n\n ${COMMON}\n ${LAYOUT}\n`;\n\nexport default ImageContainer;\n","/**\n * Tailwind CSS implementation of Image component\n * Uses Seeds image CSS classes from image.css\n */\n\nimport React from \"react\";\n\n// Utility for merging class names properly\nfunction cn(\n ...inputs: (string | undefined | null | false | Record<string, boolean>)[]\n): string {\n const classes: string[] = [];\n\n for (const input of inputs) {\n if (!input) continue;\n\n if (typeof input === \"string\") {\n classes.push(input);\n } else if (typeof input === \"object\") {\n for (const [key, value] of Object.entries(input)) {\n if (value) {\n classes.push(key);\n }\n }\n }\n }\n\n return classes.join(\" \");\n}\n\ntype TypeImageTailwindProps = React.ComponentPropsWithoutRef<\"img\"> & {\n isLoading?: boolean;\n};\n\nexport const ImageTailwind = React.forwardRef<\n HTMLImageElement,\n TypeImageTailwindProps\n>(({ isLoading, className, ...rest }, ref) => (\n <img\n ref={ref}\n // Preserve the legacy \"Image\" marker class (set by styles.ts's .attrs)\n // for any consumer selectors targeting it.\n className={cn(\n \"Image\",\n \"seeds-image\",\n { \"seeds-image-loading\": !!isLoading },\n className\n )}\n {...rest}\n />\n));\n\nImageTailwind.displayName = \"ImageTailwind\";\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":"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","names":["React","React","styled","React","import_seeds_react_system_props","import_jsx_runtime","import_jsx_runtime","noop","React"]}
package/package.json CHANGED
@@ -1,15 +1,23 @@
1
1
  {
2
2
  "name": "@sproutsocial/seeds-react-image",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
4
4
  "description": "Seeds React Image",
5
5
  "author": "Sprout Social, Inc.",
6
6
  "license": "MIT",
7
7
  "main": "dist/index.js",
8
8
  "module": "dist/esm/index.js",
9
9
  "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/esm/index.js",
13
+ "require": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ },
16
+ "./dist/image.css": "./dist/image.css"
17
+ },
10
18
  "scripts": {
11
- "build": "tsup --dts",
12
- "build:debug": "tsup --dts --metafile",
19
+ "build": "tsup --dts && cp src/image.css dist/image.css",
20
+ "build:debug": "tsup --dts --metafile && cp src/image.css dist/image.css",
13
21
  "dev": "tsup --watch --dts",
14
22
  "clean": "rm -rf .turbo dist",
15
23
  "clean:modules": "rm -rf node_modules",
@@ -0,0 +1,6 @@
1
+ // Empty PostCSS config — prevents tsup from picking up the root postcss.config.js
2
+ // (which runs Tailwind CSS and would fail without a tailwind.config.js in scope).
3
+ // image.css uses plain CSS and needs no PostCSS processing.
4
+ export default {
5
+ plugins: {},
6
+ };
@@ -28,3 +28,18 @@ export const ExampleWithFallback: Story = {
28
28
  defaultImage: backupImgData,
29
29
  },
30
30
  };
31
+
32
+ /**
33
+ * Passing Tailwind utility classes via `className` on a bare `<Image>` (no
34
+ * styled-system props) routes to the Tailwind leaf. Because `image.css` wraps
35
+ * its rules in `@layer components`, the unlayered utilities (`mx-200`, `p-300`,
36
+ * `inline-flex`) win the cascade and visibly override the component's own
37
+ * `.seeds-image` styles — proving the `@layer components` fix.
38
+ */
39
+ export const TailwindOverrides: Story = {
40
+ args: {
41
+ src: "http://static01.nyt.com/images/2015/10/24/arts/24drakeart2/24drakeart2-master675.jpg",
42
+ alt: "Tailwind className overrides applied via @layer components",
43
+ className: "mx-200 p-300 inline-flex",
44
+ },
45
+ };
package/src/Image.tsx CHANGED
@@ -1,94 +1,26 @@
1
1
  import * as React from "react";
2
- import ImageContainer from "./styles";
2
+ import ImageHybrid from "./ImageHybrid";
3
3
  import type { TypeImageProps } from "./ImageTypes";
4
4
 
5
- type TypeState = {
6
- didError: boolean;
7
- didLoad: boolean;
8
- imageUrl: string;
9
- };
10
-
11
5
  // eslint-disable-next-line @typescript-eslint/no-empty-function
12
6
  const noop = () => {};
13
7
 
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
- };
8
+ /**
9
+ * Image component. Automatically routes between the Tailwind implementation
10
+ * (preferred) and the styled-components implementation (for consumers using
11
+ * system props or a styled() extension). The load/error state lives in
12
+ * ImageHybrid so behavior is identical on both paths.
13
+ */
14
+ const Image = (props: TypeImageProps) => <ImageHybrid {...props} />;
15
+
16
+ // Preserve the original public type surface: defaultProps keep title/onError/
17
+ // onClick/onLoad optional at the JSX call site (these are required in
18
+ // TypeImageProps). The defaults are applied before reaching ImageHybrid.
19
+ Image.defaultProps = {
20
+ title: "",
21
+ onError: noop,
22
+ onClick: noop,
23
+ onLoad: noop,
24
+ };
70
25
 
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
- }
26
+ export default Image;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Hybrid Image Component
3
+ * Owns the load/error state + lifecycle in ONE place and switches only the
4
+ * rendered leaf: the styled-components element when styled-system props are
5
+ * present, otherwise the lighter Tailwind <img>.
6
+ */
7
+
8
+ import * as React from "react";
9
+ import ImageContainer from "./styles"; // Styled-components version
10
+ import { ImageTailwind } from "./ImageTailwind"; // Tailwind version
11
+ import { hasStyledProps } from "@sproutsocial/seeds-react-system-props";
12
+ import type { TypeImageProps } from "./ImageTypes";
13
+
14
+ type TypeState = {
15
+ didError: boolean;
16
+ didLoad: boolean;
17
+ imageUrl: string;
18
+ };
19
+
20
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
21
+ const noop = () => {};
22
+
23
+ export default class ImageHybrid extends React.Component<
24
+ TypeImageProps,
25
+ TypeState
26
+ > {
27
+ static defaultProps = {
28
+ title: "",
29
+ onError: noop,
30
+ onClick: noop,
31
+ onLoad: noop,
32
+ };
33
+
34
+ override state = {
35
+ didError: false,
36
+ didLoad: false,
37
+ imageUrl: this.props.src,
38
+ };
39
+
40
+ imageRef?: HTMLImageElement | null;
41
+
42
+ override componentDidMount() {
43
+ // eslint-disable-next-line react/no-did-mount-set-state
44
+ if (this.imageRef) {
45
+ this.setState({
46
+ didLoad: this.imageRef.complete,
47
+ });
48
+ }
49
+ }
50
+
51
+ override componentDidUpdate(prevProps: TypeImageProps) {
52
+ if (this.props.src !== prevProps.src) {
53
+ this.setState({
54
+ didError: false,
55
+ didLoad: false,
56
+ imageUrl: this.props.src,
57
+ });
58
+ }
59
+ }
60
+
61
+ onError = (e: React.SyntheticEvent<HTMLImageElement, Event>) => {
62
+ if (!this.state.didError && this.props.defaultImage) {
63
+ this.setState({
64
+ didError: true,
65
+ imageUrl: this.props.defaultImage,
66
+ });
67
+ }
68
+
69
+ this.props.onError(e);
70
+ };
71
+
72
+ onLoad = () => {
73
+ this.setState({
74
+ didLoad: true,
75
+ });
76
+ this.props.onLoad();
77
+ };
78
+
79
+ onClick = () => {
80
+ this.props.onClick();
81
+ };
82
+
83
+ override render() {
84
+ // defaultImage is consumed by onError (this.props.defaultImage) and must be
85
+ // pulled out of rest so it never reaches the DOM <img> on the Tailwind path
86
+ // (the styled-components leaf filtered it implicitly; a plain <img> does not).
87
+ const {
88
+ alt,
89
+ title,
90
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
91
+ onClick,
92
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
93
+ onError,
94
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
95
+ onLoad,
96
+ src,
97
+ qa,
98
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
99
+ defaultImage,
100
+ ...rest
101
+ } = this.props;
102
+ const isLoading = !this.state.didLoad;
103
+ const refCb = (img: HTMLImageElement | null) => (this.imageRef = img);
104
+ const shared = {
105
+ onClick: this.onClick,
106
+ src: this.state.imageUrl,
107
+ onError: (e: React.SyntheticEvent<HTMLImageElement, Event>) =>
108
+ this.onError(e),
109
+ onLoad: this.onLoad,
110
+ alt,
111
+ title,
112
+ "data-qa-image": title ? title : alt ? alt : "",
113
+ "data-qa-image-src": src || "",
114
+ ...(qa as any),
115
+ ...rest,
116
+ };
117
+
118
+ if (hasStyledProps(rest)) {
119
+ // Legacy styled-components leaf — styles.ts unchanged.
120
+ return <ImageContainer ref={refCb} isLoading={isLoading} {...shared} />;
121
+ }
122
+
123
+ // Tailwind leaf (preferred - lighter, no styled-components runtime).
124
+ return <ImageTailwind ref={refCb} isLoading={isLoading} {...shared} />;
125
+ }
126
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Tailwind CSS implementation of Image component
3
+ * Uses Seeds image CSS classes from image.css
4
+ */
5
+
6
+ import React from "react";
7
+
8
+ // Utility for merging class names properly
9
+ function cn(
10
+ ...inputs: (string | undefined | null | false | Record<string, boolean>)[]
11
+ ): string {
12
+ const classes: string[] = [];
13
+
14
+ for (const input of inputs) {
15
+ if (!input) continue;
16
+
17
+ if (typeof input === "string") {
18
+ classes.push(input);
19
+ } else if (typeof input === "object") {
20
+ for (const [key, value] of Object.entries(input)) {
21
+ if (value) {
22
+ classes.push(key);
23
+ }
24
+ }
25
+ }
26
+ }
27
+
28
+ return classes.join(" ");
29
+ }
30
+
31
+ type TypeImageTailwindProps = React.ComponentPropsWithoutRef<"img"> & {
32
+ isLoading?: boolean;
33
+ };
34
+
35
+ export const ImageTailwind = React.forwardRef<
36
+ HTMLImageElement,
37
+ TypeImageTailwindProps
38
+ >(({ isLoading, className, ...rest }, ref) => (
39
+ <img
40
+ ref={ref}
41
+ // Preserve the legacy "Image" marker class (set by styles.ts's .attrs)
42
+ // for any consumer selectors targeting it.
43
+ className={cn(
44
+ "Image",
45
+ "seeds-image",
46
+ { "seeds-image-loading": !!isLoading },
47
+ className
48
+ )}
49
+ {...rest}
50
+ />
51
+ ));
52
+
53
+ ImageTailwind.displayName = "ImageTailwind";
@@ -60,6 +60,39 @@ describe("Image", () => {
60
60
  );
61
61
  });
62
62
 
63
+ it("should render a plain img with the seeds-image class when no styled props are passed (Tailwind path)", () => {
64
+ render(
65
+ <Image
66
+ alt="image"
67
+ src="http://media3.giphy.com/media/jbxQLpOKN2URa/giphy.gif"
68
+ />
69
+ );
70
+ const img = screen.getByDataQaLabel({
71
+ "image-src": "http://media3.giphy.com/media/jbxQLpOKN2URa/giphy.gif",
72
+ });
73
+ expect(img.tagName).toBe("IMG");
74
+ expect(img).toHaveClass("seeds-image");
75
+ });
76
+
77
+ it("should route to the styled-components leaf when a styled-system prop (width) is passed", () => {
78
+ render(
79
+ <Image
80
+ alt="image"
81
+ src="http://media3.giphy.com/media/jbxQLpOKN2URa/giphy.gif"
82
+ width={1 / 2}
83
+ />
84
+ );
85
+ const img = screen.getByDataQaLabel({
86
+ "image-src": "http://media3.giphy.com/media/jbxQLpOKN2URa/giphy.gif",
87
+ });
88
+ // styled-components emits its generated class, never the static seeds-image class.
89
+ expect(img).not.toHaveClass("seeds-image");
90
+ expect(img).toHaveAttribute(
91
+ "src",
92
+ "http://media3.giphy.com/media/jbxQLpOKN2URa/giphy.gif"
93
+ );
94
+ });
95
+
63
96
  it("should update when the src prop changes", () => {
64
97
  const src1 = "http://media3.giphy.com/media/jbxQLpOKN2URa/giphy.gif";
65
98
  const src2 = "https://media3.giphy.com/media/gYZ7qO81g4dt6/giphy.gif";
package/src/css.d.ts ADDED
@@ -0,0 +1 @@
1
+ declare module "*.css";
package/src/image.css ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Seeds Image component classes
3
+ * Use these instead of writing out individual Tailwind utility classes.
4
+ *
5
+ * Usage:
6
+ * <img class="seeds-image" />
7
+ * <img class="seeds-image seeds-image-loading" />
8
+ */
9
+
10
+ @layer components {
11
+ .seeds-image {
12
+ display: block;
13
+ }
14
+
15
+ .seeds-image-loading {
16
+ opacity: 0;
17
+ }
18
+ }