'use client'; import { useRef } from 'react'; import { Plus, Trash2 } from 'lucide-react'; import { cn } from '@/lib/utils/cn'; import type { Keyframe } from '@/types/animate'; interface Props { keyframes: Keyframe[]; selectedId: string | null; onSelect: (id: string) => void; onAdd: (offset: number) => void; onDelete: (id: string) => void; onMove: (id: string, newOffset: number) => void; embedded?: boolean; // when true, no glass card wrapper (use inside another card) } const TICKS = [25, 50, 75]; const iconBtn = (disabled?: boolean) => cn( 'w-6 h-6 flex items-center justify-center rounded-md glass border border-border/30 text-muted-foreground hover:text-primary hover:border-primary/30 transition-all', disabled && 'opacity-30 cursor-not-allowed pointer-events-none' ); export function KeyframeTimeline({ keyframes, selectedId, onSelect, onAdd, onDelete, onMove, embedded = false }: Props) { const trackRef = useRef(null); const getOffsetFromEvent = (clientX: number): number => { if (!trackRef.current) return 0; const rect = trackRef.current.getBoundingClientRect(); const pct = ((clientX - rect.left) / rect.width) * 100; return Math.round(Math.min(100, Math.max(0, pct))); }; const handleTrackClick = (e: React.MouseEvent) => { if ((e.target as HTMLElement).closest('[data-keyframe-marker]')) return; onAdd(getOffsetFromEvent(e.clientX)); }; const handlePointerDown = (e: React.PointerEvent, id: string) => { e.preventDefault(); onSelect(id); const el = e.currentTarget as HTMLElement; el.setPointerCapture(e.pointerId); const handleMove = (me: PointerEvent) => onMove(id, getOffsetFromEvent(me.clientX)); const handleUp = () => { el.removeEventListener('pointermove', handleMove); el.removeEventListener('pointerup', handleUp); }; el.addEventListener('pointermove', handleMove); el.addEventListener('pointerup', handleUp); }; const sorted = [...keyframes].sort((a, b) => a.offset - b.offset); const selectedKf = keyframes.find((k) => k.id === selectedId); const content = (
{/* Header */}
Keyframes {keyframes.length} kf{selectedKf ? ` · ${selectedKf.offset}%` : ''}
{/* Track */}
{TICKS.map((tick) => (
{tick}%
))} {sorted.map((kf) => (
{/* Offset labels */}
{sorted.map((kf) => ( {kf.offset}% ))}
); if (embedded) return
{content}
; return (
{content}
); }