mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
555838ecbe
## Summary Correct spelling errors in the cms-agilitycms example, including the image prop spelling and dependency imports. Bug Fixes: - Fix misspelled intersectionTreshold prop to intersectionThreshold in the Image component and its useInView configuration Enhancements: - Rename dependancies.ts to dependencies.ts and update requireComponentDependancyByName to requireComponentDependencyByName across API and component imports ### Adding or Updating Examples - [x] The "examples guidelines" are followed from our contributing doc https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md - [x] Make sure the linting passes by running `pnpm build && pnpm lint`. See https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md Co-authored-by: Joseph <joseph.chamochumbi@vercel.com>
225 lines
4.5 KiB
TypeScript
225 lines
4.5 KiB
TypeScript
import React, { useCallback, useState } from "react";
|
|
import { useInView } from "react-intersection-observer";
|
|
|
|
type State = {
|
|
lazyLoad: boolean;
|
|
isSsr: boolean;
|
|
isIntersectionObserverAvailable: boolean;
|
|
inView?: boolean;
|
|
loaded: boolean;
|
|
};
|
|
|
|
const imageAddStrategy = ({
|
|
lazyLoad,
|
|
isSsr,
|
|
isIntersectionObserverAvailable,
|
|
inView,
|
|
loaded,
|
|
}: State) => {
|
|
if (!lazyLoad) {
|
|
return true;
|
|
}
|
|
|
|
if (isSsr) {
|
|
return false;
|
|
}
|
|
|
|
if (isIntersectionObserverAvailable) {
|
|
return inView || loaded;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
const imageShowStrategy = ({
|
|
lazyLoad,
|
|
isSsr,
|
|
isIntersectionObserverAvailable,
|
|
loaded,
|
|
}: State) => {
|
|
if (!lazyLoad) {
|
|
return true;
|
|
}
|
|
|
|
if (isSsr) {
|
|
return false;
|
|
}
|
|
|
|
if (isIntersectionObserverAvailable) {
|
|
return loaded;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
type ImageData = {
|
|
aspectRatio: number;
|
|
base64?: string;
|
|
height?: number;
|
|
width: number;
|
|
sizes?: string;
|
|
src?: string;
|
|
srcSet?: string;
|
|
webpSrcSet?: string;
|
|
bgColor?: string;
|
|
alt?: string;
|
|
title?: string;
|
|
};
|
|
|
|
type ImageProps = {
|
|
data: ImageData;
|
|
className?: string;
|
|
pictureClassName?: string;
|
|
fadeInDuration?: number;
|
|
intersectionThreshold?: number;
|
|
intersectionMargin?: string;
|
|
lazyLoad?: boolean;
|
|
style?: React.CSSProperties;
|
|
pictureStyle?: React.CSSProperties;
|
|
explicitWidth?: boolean;
|
|
};
|
|
|
|
const Image = function ({
|
|
className,
|
|
fadeInDuration,
|
|
intersectionThreshold,
|
|
intersectionMargin,
|
|
pictureClassName,
|
|
lazyLoad = true,
|
|
style,
|
|
pictureStyle,
|
|
explicitWidth,
|
|
data,
|
|
}: ImageProps) {
|
|
const [loaded, setLoaded] = useState(false);
|
|
|
|
const handleLoad = useCallback(() => {
|
|
setLoaded(true);
|
|
}, []);
|
|
|
|
const [ref, inView] = useInView({
|
|
threshold: intersectionThreshold || 0,
|
|
rootMargin: intersectionMargin || "0px 0px 0px 0px",
|
|
triggerOnce: true,
|
|
});
|
|
|
|
const isSsr = typeof window === "undefined";
|
|
|
|
const isIntersectionObserverAvailable = isSsr
|
|
? false
|
|
: !!window.IntersectionObserver;
|
|
|
|
const absolutePositioning: React.CSSProperties = {
|
|
position: "absolute",
|
|
left: 0,
|
|
top: 0,
|
|
bottom: 0,
|
|
right: 0,
|
|
};
|
|
|
|
const addImage = imageAddStrategy({
|
|
lazyLoad,
|
|
isSsr,
|
|
isIntersectionObserverAvailable,
|
|
inView,
|
|
loaded,
|
|
});
|
|
const showImage = imageShowStrategy({
|
|
lazyLoad,
|
|
isSsr,
|
|
isIntersectionObserverAvailable,
|
|
inView,
|
|
loaded,
|
|
});
|
|
|
|
const webpSource = data.webpSrcSet && (
|
|
<source srcSet={data.webpSrcSet} sizes={data.sizes} type="image/webp" />
|
|
);
|
|
|
|
const regularSource = data.srcSet && (
|
|
<source srcSet={data.srcSet} sizes={data.sizes} />
|
|
);
|
|
|
|
const placeholder = (
|
|
<div
|
|
style={{
|
|
backgroundImage: data.base64 ? `url(${data.base64})` : null,
|
|
backgroundColor: data.bgColor,
|
|
backgroundSize: "cover",
|
|
opacity: showImage ? 0 : 1,
|
|
transition:
|
|
!fadeInDuration || fadeInDuration > 0
|
|
? `opacity ${fadeInDuration || 500}ms ${fadeInDuration || 500}ms`
|
|
: null,
|
|
...absolutePositioning,
|
|
}}
|
|
/>
|
|
);
|
|
|
|
const { width, aspectRatio } = data;
|
|
const height = data.height || width / aspectRatio;
|
|
|
|
const sizer = (
|
|
<svg
|
|
className={pictureClassName}
|
|
style={{
|
|
width: explicitWidth ? `${width}px` : "100%",
|
|
height: "auto",
|
|
display: "block",
|
|
...pictureStyle,
|
|
}}
|
|
height={height}
|
|
width={width}
|
|
/>
|
|
);
|
|
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
className={className}
|
|
style={{
|
|
display: "inline-block",
|
|
overflow: "hidden",
|
|
...style,
|
|
position: "relative",
|
|
}}
|
|
>
|
|
{sizer}
|
|
{placeholder}
|
|
{addImage && (
|
|
<picture
|
|
style={{
|
|
...absolutePositioning,
|
|
opacity: showImage ? 1 : 0,
|
|
transition:
|
|
!fadeInDuration || fadeInDuration > 0
|
|
? `opacity ${fadeInDuration || 500}ms`
|
|
: null,
|
|
}}
|
|
>
|
|
{webpSource}
|
|
{regularSource}
|
|
{data.src && (
|
|
<img
|
|
src={data.src}
|
|
alt={data.alt}
|
|
title={data.title}
|
|
onLoad={handleLoad}
|
|
style={{ width: "100%" }}
|
|
/>
|
|
)}
|
|
</picture>
|
|
)}
|
|
<noscript>
|
|
<picture className={pictureClassName} style={{ ...pictureStyle }}>
|
|
{webpSource}
|
|
{regularSource}
|
|
{data.src && <img src={data.src} alt={data.alt} title={data.title} />}
|
|
</picture>
|
|
</noscript>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Image;
|