UNPKG

v8-ui-atoms

Version:

A library of common base components for building ui

117 lines 5.96 kB
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, "&quot;"); 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