trptk/components/artist/ArtistCard.tsx
2026-02-24 17:14:07 +01:00

76 lines
2 KiB
TypeScript

"use client";
import Link from "next/link";
import Image from "next/image";
import { useMemo, useState } from "react";
import { urlFor } from "@/lib/sanityImage";
import { ARTIST_PLACEHOLDER_SRC } from "@/lib/constants";
import type { SanityImageSource } from "@sanity/image-url";
export type ArtistCardProps = {
name?: string;
subtitle?: string;
image?: SanityImageSource;
href: string;
label?: string;
className?: string;
};
export function useCardImage(image?: SanityImageSource) {
const [imgError, setImgError] = useState(false);
const src = useMemo(() => {
if (imgError || !image) return ARTIST_PLACEHOLDER_SRC;
return urlFor(image).url();
}, [image, imgError]);
return { src, onError: () => setImgError(true) };
}
export function ArtistCard({
name,
subtitle,
image,
href,
label = "Artist",
className = "",
}: ArtistCardProps) {
const { src, onError } = useCardImage(image);
return (
<Link
href={href}
className={[
"group relative z-10 rounded-xl bg-lightbg shadow-lg ring-1 ring-lightline dark:bg-darkbg dark:ring-darkline",
"hover:text-trptkblue hover:ring-lightline-hover dark:hover:text-white dark:hover:ring-darkline-hover",
"transition-color duration-300 ease-in-out",
className,
].join(" ")}
aria-label={name ? `${label}: ${name}` : label}
>
<div className="relative aspect-square w-full overflow-hidden rounded-xl">
<Image
src={src}
alt={name ? `Photo of ${name}` : `${label} photo`}
fill
sizes="(max-width: 560px) calc(100vw - 32px), 450px"
className="object-cover"
onError={onError}
/>
</div>
<div className="p-3 sm:p-5">
<h3 className="text-sm break-words sm:mb-1 sm:text-base md:mb-2">
{name ?? `Untitled ${label.toLowerCase()}`}
</h3>
{subtitle ? (
<h4 className="hidden text-sm break-words text-lightsec sm:block dark:text-darksec">
{subtitle}
</h4>
) : null}
</div>
</Link>
);
}