"use client"; import { useEffect, useRef, useState } from "react"; import { useSpring, animated } from "@react-spring/web"; import useMeasure from "react-use-measure"; // The read-only "ALT" pill that expands to reveal alt text. Shared by the // editor (ImageAltButton's reader mode) and published posts so both render alt // text identically. Purely presentational — takes the alt string directly so it // has no replicache dependency and is safe to render on public post pages. export function ReadOnlyAltText(props: { alt: string; className?: string; onSeeMore?: () => void; }) { let [showAlt, setShowAlt] = useState(false); let [altRef, { height: altHeight }] = useMeasure(); let altStyle = useSpring({ height: showAlt ? altHeight : 0, opacity: showAlt ? 1 : 0, config: { tension: 450, friction: 30, clamp: true }, }); if (!props.alt) return null; return (
{ let group = e.currentTarget; requestAnimationFrame(() => { if (!group.contains(document.activeElement)) setShowAlt(false); }); }} className={`absolute bottom-1.5 right-1.5 left-1.5 flex flex-col h-fit ${props.className || ""}`} >
); } // The alt text itself, clamped on the image. Alt longer than the clamp gets a // "See more" that hands off to the lightbox, which can give it the full height. export function AltTextBody(props: { alt: string; onSeeMore?: () => void }) { let [overflows, setOverflows] = useState(false); let textRef = useRef(null); useEffect(() => { let el = textRef.current; if (!el) return; let measure = () => setOverflows(el.scrollHeight > el.clientHeight + 1); measure(); let observer = new ResizeObserver(measure); observer.observe(el); return () => observer.disconnect(); }, [props.alt]); return (
{props.alt}
{overflows && props.onSeeMore && ( // Sits on the clipped last line, over the text it truncates. )}
); }