| 1 |
import { loadImage } from '@shared/api/wp'; |
| 2 |
import { Spinner } from '@wordpress/components'; |
| 3 |
import { useEffect, useInsertionEffect, useState } from '@wordpress/element'; |
| 4 |
import classNames from 'classnames'; |
| 5 |
import { AnimatePresence, motion } from 'framer-motion'; |
| 6 |
|
| 7 |
export const UnsplashImage = ({ |
| 8 |
image, |
| 9 |
skeletonHeight, |
| 10 |
isInsertingImage, |
| 11 |
onClick, |
| 12 |
}) => { |
| 13 |
const [authorUrl, setAuthorUrl] = useState(''); |
| 14 |
const [loaded, setLoaded] = useState(false); |
| 15 |
const aspectRatio = image?.width |
| 16 |
? Number(image?.width) / Number(image?.height) |
| 17 |
: 122 / skeletonHeight; |
| 18 |
|
| 19 |
useEffect(() => { |
| 20 |
if (!image?.user?.links?.html) { |
| 21 |
setAuthorUrl(''); |
| 22 |
return; |
| 23 |
} |
| 24 |
const authorUrl = new URL(image.user.links.html); |
| 25 |
authorUrl.searchParams.set('utm_source', 'extendify'); |
| 26 |
authorUrl.searchParams.set('utm_medium', 'referral'); |
| 27 |
setAuthorUrl(authorUrl.toString()); |
| 28 |
}, [image]); |
| 29 |
|
| 30 |
useInsertionEffect(() => { |
| 31 |
if (!image?.urls || loaded) return; |
| 32 |
const img = new Image(); |
| 33 |
img.src = image.urls.thumb || image.urls.small; |
| 34 |
loadImage(img).then(() => setLoaded(true)); |
| 35 |
}, [image, loaded]); |
| 36 |
|
| 37 |
return ( |
| 38 |
<motion.div |
| 39 |
className="relative mb-1" |
| 40 |
initial={{ aspectRatio }} |
| 41 |
animate={{ aspectRatio }} |
| 42 |
> |
| 43 |
<AnimatePresence> |
| 44 |
{loaded ? null : ( |
| 45 |
<motion.div |
| 46 |
className="absolute inset-0 z-10 bg-white" |
| 47 |
initial={{ opacity: 1 }} |
| 48 |
animate={{ opacity: 1 }} |
| 49 |
exit={{ opacity: 0 }} |
| 50 |
> |
| 51 |
<div className="animate-pulse absolute inset-0 z-10 bg-gray-150" /> |
| 52 |
</motion.div> |
| 53 |
)} |
| 54 |
</AnimatePresence> |
| 55 |
<div className="group relative"> |
| 56 |
<button |
| 57 |
type="button" |
| 58 |
className={classNames('relative block border-0 p-0', { |
| 59 |
'bg-transparent': !isInsertingImage, |
| 60 |
'bg-black': isInsertingImage, |
| 61 |
})} |
| 62 |
onClick={() => onClick(image)} |
| 63 |
disabled={isInsertingImage} |
| 64 |
> |
| 65 |
{isInsertingImage && isInsertingImage?.id === image?.id && ( |
| 66 |
<div className="absolute inset-0 flex items-center justify-center"> |
| 67 |
<Spinner style={{ height: '24px', width: '24px' }} /> |
| 68 |
</div> |
| 69 |
)} |
| 70 |
<img |
| 71 |
src={image?.urls?.thumb || image?.urls?.small} |
| 72 |
className={classNames('block transition-opacity duration-300', { |
| 73 |
'opacity-50': isInsertingImage, |
| 74 |
})} |
| 75 |
alt={image?.alt_description} |
| 76 |
/> |
| 77 |
</button> |
| 78 |
{image?.user?.name && authorUrl ? ( |
| 79 |
<a |
| 80 |
href={authorUrl} |
| 81 |
target="_blank" |
| 82 |
className={classNames( |
| 83 |
'absolute bottom-1 bg-black/70 px-1 text-white no-underline opacity-0', |
| 84 |
{ |
| 85 |
'group-focus-within:opacity-100 group-hover:opacity-100': |
| 86 |
!isInsertingImage, |
| 87 |
}, |
| 88 |
)} |
| 89 |
>{`${image.user?.name}`}</a> |
| 90 |
) : null} |
| 91 |
</div> |
| 92 |
</motion.div> |
| 93 |
); |
| 94 |
}; |
| 95 |
|