'use client'; import { GripVerticalIcon } from 'lucide-react'; import { type MotionValue, motion, useMotionValue, useSpring, useTransform, } from 'motion/react'; import { type ComponentProps, createContext, type HTMLAttributes, type MouseEventHandler, type ReactNode, type TouchEventHandler, useContext, useState, } from 'react'; import { cn } from '@/lib/utils'; type ImageComparisonContextType = { sliderPosition: number; setSliderPosition: (pos: number) => void; motionSliderPosition: MotionValue; mode: 'hover' | 'drag'; }; const ImageComparisonContext = createContext< ImageComparisonContextType | undefined >(undefined); const useImageComparisonContext = () => { const context = useContext(ImageComparisonContext); if (!context) { throw new Error( 'useImageComparisonContext must be used within a ImageComparison' ); } return context; }; export type ComparisonProps = HTMLAttributes & { mode?: 'hover' | 'drag'; onDragStart?: () => void; onDragEnd?: () => void; }; export const Comparison = ({ className, mode = 'drag', onDragStart, onDragEnd, ...props }: ComparisonProps) => { const [isDragging, setIsDragging] = useState(false); const motionValue = useMotionValue(50); const motionSliderPosition = useSpring(motionValue, { bounce: 0, duration: 0, }); const [sliderPosition, setSliderPosition] = useState(50); const handleDrag = (domRect: DOMRect, clientX: number) => { if (!isDragging && mode === 'drag') { return; } const x = clientX - domRect.left; const percentage = Math.min(Math.max((x / domRect.width) * 100, 0), 100); motionValue.set(percentage); setSliderPosition(percentage); }; const handleMouseDrag: MouseEventHandler = (event) => { if (!event) { return; } const containerRect = event.currentTarget.getBoundingClientRect(); handleDrag(containerRect, event.clientX); }; const handleTouchDrag: TouchEventHandler = (event) => { if (!event) { return; } const containerRect = event.currentTarget.getBoundingClientRect(); const touches = Array.from(event.touches); handleDrag(containerRect, touches.at(0)?.clientX ?? 0); }; const handleDragStart = () => { if (mode === 'drag') { setIsDragging(true); onDragStart?.(); } }; const handleDragEnd = () => { if (mode === 'drag') { setIsDragging(false); onDragEnd?.(); } }; return (
); }; export type ComparisonItemProps = ComponentProps & { position: 'left' | 'right'; }; export const ComparisonItem = ({ className, position, ...props }: ComparisonItemProps) => { const { motionSliderPosition } = useImageComparisonContext(); const leftClipPath = useTransform( motionSliderPosition, (value) => `inset(0 0 0 ${value}%)` ); const rightClipPath = useTransform( motionSliderPosition, (value) => `inset(0 ${100 - value}% 0 0)` ); return (