"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.
)}