Skip to content
File

Blob: src/client/sites/islands/sites-image.tsx

typescript63 lines
1import { useEffect, useRef, useState } from "react";
2import {
3 ImageElement,
4 ImageErrorState,
5 ImageFigureFrame,
6 ImageLoadingSkeleton,
7 resolveImageFrameStyle,
8} from "@/shared/editor/presentation/image";
9import type { SitesImageProps } from "@/shared/sites/island-schemas";
10 
11export function SitesImage({
12 bid,
13 src,
14 alt,
15 title,
16 align,
17 width,
18 naturalWidth,
19 naturalHeight,
20 brightness,
21}: SitesImageProps) {
22 const imgRef = useRef<HTMLImageElement>(null);
23 const [loaded, setLoaded] = useState(false);
24 const [errored, setErrored] = useState(false);
25 
26 useEffect(() => {
27 if (imgRef.current?.complete && imgRef.current.naturalWidth > 0) {
28 setLoaded(true);
29 }
30 }, []);
31 
32 const isLoading = !loaded && !errored;
33 const frameStyle = resolveImageFrameStyle({ width, naturalWidth, naturalHeight });
34 const sizedStyle = width && width > 0 ? { width: `${width}px` } : undefined;
35 const containerStyle = isLoading || errored ? frameStyle : sizedStyle;
36 const containerClassName = [isLoading ? "is-loading" : "", errored ? "is-errored" : ""].filter(Boolean).join(" ");
37 
38 return (
39 <ImageFigureFrame
40 bid={bid}
41 align={align}
42 containerClassName={containerClassName}
43 containerStyle={containerStyle}
44 caption={!isLoading && !errored ? alt : null}
45 >
46 {!errored ? (
47 <ImageElement
48 ref={imgRef}
49 src={src}
50 brightness={brightness}
51 alt={alt ?? undefined}
52 title={title ?? undefined}
53 style={width && width > 0 && !isLoading ? { width: "100%" } : undefined}
54 onLoad={() => setLoaded(true)}
55 onError={() => setErrored(true)}
56 />
57 ) : null}
58 {isLoading ? <ImageLoadingSkeleton /> : null}
59 {errored ? <ImageErrorState /> : null}
60 </ImageFigureFrame>
61 );
62}