"use client"; import { CompareIcon } from "../ui/icons.tsx"; import { useId, useRef, useState } from "react"; const whenBroken = (fail: () => void) => (img: HTMLImageElement | null) => { if (img?.complete && !img.naturalWidth) img.decode?.().catch(fail); }; export function ImageCompare({ before, after, value, onValueChange, label = "Image comparison", beforeLabel = "Before", afterLabel = "After", className = "", }: { before: { src: string; alt: string }; after: { src: string; alt: string }; value: number; onValueChange: (value: number) => void; label?: string; beforeLabel?: string; afterLabel?: string; className?: string; }) { const ref = useRef(null), id = useId(), [failed, setFailed] = useState(), current = Math.max(0, Math.min(100, Number.isFinite(value) ? value : 50)), broken = failed === before.src || failed === after.src; const update = (x: number) => { const r = ref.current!.getBoundingClientRect(); if (r.width) onValueChange( Math.round(Math.max(0, Math.min(100, ((x - r.left) / r.width) * 100))), ); }; return (
{label}
{ if (e.button === 0 && !broken) { e.currentTarget.setPointerCapture(e.pointerId); update(e.clientX); } }} onPointerMove={(e) => { if (e.currentTarget.hasPointerCapture(e.pointerId)) update(e.clientX); }} onPointerUp={(e) => { if (e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId); }} > {broken ? (

Comparison image unavailable.

) : ( <> setFailed(before.src))} src={before.src} alt={before.alt} onError={() => setFailed(before.src)} /> setFailed(after.src))} className="mizu-image-compare-after" src={after.src} alt={after.alt} style={{ clipPath: `inset(0 ${100 - current}% 0 0)` }} onError={() => setFailed(after.src)} /> )}
onValueChange(Number(e.target.value))} />
{beforeLabel} {current}% {afterLabel.toLowerCase()} {afterLabel}
); }