v8-ui-atoms
Version:
A library of common base components for building ui
117 lines • 5.96 kB
JavaScript
import * as React from "react";
import * as s from "./Image.styles";
import { bpMobile, bpTablet } from "../../assets/styles/layout/grid";
import SharedContext from "../../Context";
// some links for image hydration issues and workarounds
// https://github.com/facebook/react/issues/15446
// https://codesandbox.io/s/54xr4k8w4?file=/src/index.js
// https://rants.broonix.ca/nextjs-fallback-image
const defaultAltTag = "missing image";
const hasSSRImageFailed = (src) => {
const { __failedImgs__ = {} } = window;
const failed = __failedImgs__[src] === true;
// no need to keep this key, image may be available in an upcoming request
delete __failedImgs__[src];
return failed;
};
class ImageClient extends React.Component {
constructor(props) {
super(props);
this.state = {
imageFailed: false,
};
}
// Build the media queries for <picture /> element from breakpoint prop
static getMediaAttr(breakpoint) {
switch (breakpoint) {
case "mobile":
return `(max-width: ${bpMobile}px)`;
case "tablet":
return `(min-width: ${bpMobile + 1}px) and (max-width: ${bpTablet - 1}px)`;
case "desktop":
return `(min-width: ${bpTablet}px)`;
default:
return "";
}
}
// Build the srcset attribute for <img />
getSrcSetAttr(sources) {
if (!sources || this.state.imageFailed) {
return;
}
return sources
.map((source) => `${source.src} ${source.width}w`)
.join(",");
}
render() {
const MissingImageObject = !!this.props.imagesOverride
? this.props.imagesOverride
: this.context.images;
const defaultMissingImage = !!this.props.fallbackSrc
? this.props.fallbackSrc
: MissingImageObject.missingImg.square;
const { src = defaultMissingImage } = this.props;
const fallback = !src || this.state.imageFailed || hasSSRImageFailed(src);
const imgSrc = fallback ? defaultMissingImage : src;
const imgSet = this.getSrcSetAttr(fallback ? this.props.fallbackSrcSet : this.props.srcSet);
return (React.createElement(React.Fragment, null, this.props.sourcesDifferBy === "resolution" ? (React.createElement(s.Image, { alt: this.props.alt, loading: "lazy", className: this.props.className, objectFit: this.props.objectFit, role: !!this.props.role ? this.props.role : "img", sizes: this.props.sizes, src: imgSrc, srcSet: imgSet, cssWidth: this.props.width, cssHeight: this.props.height, cssMaxHeight: this.props.maxHeight, onError: () => this.setState({ imageFailed: true }), "data-testid": !!this.props["data-testid"]
? this.props["data-testid"]
: ImageTestId })) : (React.createElement(s.Picture, { className: this.props.className },
this.props.srcSet.map((image) => {
return (React.createElement("source", { srcSet: image.src, media: ImageClient.getMediaAttr(image.breakpoint), key: `${image.src}-${image.breakpoint}` }));
}),
React.createElement(s.Image, { alt: this.props.alt, loading: "lazy", className: `${this.props.className}-fallback`, cssHeight: this.props.height, objectFit: this.props.objectFit, role: this.props.role, src: imgSrc, title: this.props.title, cssWidth: this.props.width, cssMaxHeight: this.props.maxHeight, "data-testid": !!this.props["data-testid"]
? this.props["data-testid"]
: ImageTestId })))));
}
}
ImageClient.contextType = SharedContext;
ImageClient.defaultProps = {
sourcesDifferBy: "resolution",
fallbackSrcSet: [],
srcSet: [],
alt: defaultAltTag,
};
const escapeQuote = (st = "") => st.replace(/"/gm, """);
const addAttr = (attr, value) => value !== undefined ? `${attr}="${escapeQuote(String(value))}"` : "";
const ImageSSR = ({ src, alt = "missing image", className, height, maxHeight, role, width, objectFit, fallbackSrc, imagesOverride, }) => {
const { images } = React.useContext(SharedContext);
const MissingImageObject = !!imagesOverride ? imagesOverride : images;
const FallbackImageSrc = !!fallbackSrc
? fallbackSrc
: MissingImageObject.missingImg.square;
const Source = !!src ? src : FallbackImageSrc;
// registers on __failedImgs__ the images that failed to load, so on hydration, we don't need to request them again
const onerror = [
"this.onerror=null;",
"window.__failedImgs__ = window.__failedImgs__ || {};",
"window.__failedImgs__[this.src] = true;",
"this.src = this.dataset.fallbacksrc;",
].join(" ");
return (React.createElement("span", { dangerouslySetInnerHTML: {
__html: `<img ${[
addAttr("data-removeme", '"escapeQuote()}"'),
addAttr("class", className),
addAttr("alt", !!alt ? alt : defaultAltTag),
addAttr("src", Source),
addAttr("data-fallbacksrc", FallbackImageSrc),
addAttr("width", width),
addAttr("height", height),
addAttr("max-height", maxHeight),
addAttr("role", role),
addAttr("onerror", onerror),
addAttr("loading", "lazy"),
objectFit ? addAttr("style", `object-fit: ${objectFit};`) : "",
].join(" ")}/>`,
} }));
};
export default (props) => {
if (typeof window !== "undefined" && window.document !== undefined) {
return React.createElement(ImageClient, Object.assign({}, props));
}
else {
return React.createElement(ImageSSR, Object.assign({}, props));
}
};
export const ImageTestId = "ImageComponentTestId";
//# sourceMappingURL=index.js.map