"use client"; import { useState, type ReactNode } from "react"; import { motion, useMotionTemplate, useMotionValue, useMotionValueEvent, useReducedMotion, useSpring, useTransform } from "motion/react"; type HoverCompareProps = { /** Shown left of the line. Same size as `after`. */ before: ReactNode; /** Shown right of the line. */ after: ReactNode; /** Where the line starts, 0 to 100. */ start?: number; beforeLabel?: string; afterLabel?: string; /** Name read out by screen readers. */ label?: string; /** Set the size here. */ className?: string; }; const FOLLOW = { stiffness: 500, damping: 45 }; const PILL = "pointer-events-none absolute top-3 rounded-full bg-black/55 px-2.5 py-1 text-xs font-medium text-white"; /* HoverCompare: two versions of one picture with a line between them. The line follows the mouse, a finger or the arrow keys. */ export function HoverCompare({ before, after, start = 50, beforeLabel = "Before", afterLabel = "After", label = "Compare two versions", className = "aspect-video", }: HoverCompareProps) { const reduce = useReducedMotion(); const target = useMotionValue(start); const smooth = useSpring(target, FOLLOW); const line = reduce ? target : smooth; const rest = useTransform(line, (v) => 100 - v); const clipPath = useMotionTemplate`inset(0 ${rest}% 0 0)`; const left = useMotionTemplate`${line}%`; const [value, setValue] = useState(start); useMotionValueEvent(target, "change", (v) => setValue(Math.round(v))); const place = (e: React.PointerEvent) => { const r = e.currentTarget.getBoundingClientRect(); target.set(Math.max(0, Math.min(100, ((e.clientX - r.left) / r.width) * 100))); }; const onKey = (e: React.KeyboardEvent) => { const keys: Record = { ArrowLeft: value - 5, ArrowDown: value - 5, ArrowRight: value + 5, ArrowUp: value + 5, Home: 0, End: 100, }; if (!(e.key in keys)) return; e.preventDefault(); target.set(Math.max(0, Math.min(100, keys[e.key]))); }; return (
{after}
{afterLabel}
{before}
{beforeLabel}
); }