Vercel/src/components/og/OGLoaderImage.tsx
Sam Becker d2e62a9091
Improve tooltip segues (#272)
* Update GH issue template

* Create custom tooltip display engine

* Fix tooltip cleanup behavior

* Make tooltip position size-aware

* Refine tooltip og positioning

* Refine og tooltip behavior

* Refine og image loading behavior
2025-06-22 15:03:18 -05:00

89 lines
2.3 KiB
TypeScript

'use client';
import { useEffect, useRef, useState } from 'react';
import { clsx } from 'clsx/lite';
import Spinner from '@/components/Spinner';
import { IMAGE_OG_DIMENSION } from '@/image-response';
import { TbPhotoQuestion } from 'react-icons/tb';
type LoadingState = 'loading' | 'loaded' | 'failed';
export default function OGLoaderImage({
title,
path,
retryTime,
className,
enabled = true,
}: {
title: string
path: string
retryTime?: number
className?: string
enabled?: boolean
}) {
const ref = useRef<HTMLImageElement>(null);
const [loadingState, setLoadingState] = useState<LoadingState>('loading');
const { width, height, aspectRatio } = IMAGE_OG_DIMENSION;
useEffect(() => {
if (!ref.current?.complete) {
setLoadingState('loading');
}
}, [path]);
return (
<div
key={path}
className={clsx(
'relative',
className,
)}
style={{ aspectRatio }}
>
{loadingState === 'loading' &&
<div className={clsx(
'absolute top-0 left-0 right-0 bottom-0 z-10',
'flex items-center justify-center',
)}>
<Spinner size={40} />
</div>}
{loadingState === 'failed' &&
<div className={clsx(
'absolute top-0 left-0 right-0 bottom-0 z-11',
'flex items-center justify-center',
'text-dim',
)}>
<TbPhotoQuestion size={28} />
</div>}
{(loadingState === 'loading' || loadingState === 'loaded') &&
<img
ref={ref}
alt={title}
className={clsx(
'absolute top-0 left-0 right-0 bottom-0 z-0',
'w-full',
loadingState === 'loading' && 'opacity-0',
'transition-opacity',
)}
src={enabled ? path : ''}
width={width}
height={height}
onLoadStart={() => setLoadingState('loading')}
onLoad={() => setLoadingState('loaded')}
onError={e => {
setLoadingState('failed');
if (retryTime !== undefined) {
setLoadingState('loading');
setTimeout(() => {
e.currentTarget.src = '';
e.currentTarget.src = path;
}, retryTime);
}
}}
/>}
</div>
);
};