trptk/lib/portableTextComponents.tsx
2026-02-24 17:14:07 +01:00

53 lines
1.7 KiB
TypeScript

import Image from "next/image";
import type { PortableTextComponents } from "@portabletext/react";
import { urlFor } from "@/lib/sanityImage";
function extractYouTubeId(url: string): string | null {
const match = url.match(
/(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/,
);
return match?.[1] ?? null;
}
export const portableTextComponents: PortableTextComponents = {
types: {
image: ({ value }) => {
if (!value?.asset) return null;
const src = urlFor(value).url();
return (
<figure className="my-8">
<div className="relative aspect-video w-full overflow-hidden rounded-xl">
<Image
src={src}
alt={value.alt || ""}
fill
className="rounded-xl object-cover"
sizes="(max-width: 800px) 100vw, 800px"
/>
</div>
{value.caption && (
<figcaption className="mt-2 text-center text-sm text-lightsec dark:text-darksec">
{value.caption}
</figcaption>
)}
</figure>
);
},
youtube: ({ value }) => {
if (!value?.url) return null;
const videoId = extractYouTubeId(value.url);
if (!videoId) return null;
return (
<div className="my-8 aspect-video w-full overflow-hidden rounded-xl">
<iframe
src={`https://www.youtube-nocookie.com/embed/${videoId}`}
title="YouTube video"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
className="h-full w-full"
/>
</div>
);
},
},
};