import { useState, useRef, useEffect } from 'react'; import { motion, useAnimation, useMotionValue, useTransform, useSpring } from 'framer-motion'; import { ChevronLeft, ChevronRight, Upload, BookOpen, Brain, Users, Film, Flag } from 'lucide-react'; // 定义步骤数据结构 interface Step { id: string; icon: JSX.Element; title: string; subtitle: string; description: string; } // 步骤配置 const STEPS: Step[] = [ { id: 'overview', icon: , title: '剧本大纲', subtitle: 'Script Overview', description: '提取剧本结构和关键要素' }, { id: 'storyboard', icon: , title: '分镜草图', subtitle: 'Storyboard', description: '可视化场景设计和转场' }, { id: 'character', icon: , title: '演员角色', subtitle: 'Character Design', description: '定制角色形象和个性' }, { id: 'post', icon: , title: '后期制作', subtitle: 'Post Production', description: '音效配乐和特效处理' }, { id: 'output', icon: , title: '最终成品', subtitle: 'Final Output', description: '预览和导出作品' } ]; interface FilmstripStepperProps { currentStep: string; onStepChange: (stepId: string) => void; } export function FilmstripStepper({ currentStep, onStepChange }: FilmstripStepperProps) { const [isDragging, setIsDragging] = useState(false); const containerRef = useRef(null); const scrollRef = useRef(null); const controls = useAnimation(); // 滚动位置状态 const x = useMotionValue(0); const springX = useSpring(x, { stiffness: 300, damping: 30 }); // 处理滚动边界 useEffect(() => { if (!containerRef.current || !scrollRef.current) return; const container = containerRef.current; const scroll = scrollRef.current; const maxScroll = -(scroll.scrollWidth - container.clientWidth); x.set(Math.max(Math.min(x.get(), 0), maxScroll)); }, [x]); // 处理拖拽结束 const handleDragEnd = () => { setIsDragging(false); if (!containerRef.current || !scrollRef.current) return; const container = containerRef.current; const scroll = scrollRef.current; const maxScroll = -(scroll.scrollWidth - container.clientWidth); // 确保不会过度滚动 if (x.get() > 0) { controls.start({ x: 0 }); } else if (x.get() < maxScroll) { controls.start({ x: maxScroll }); } }; // 滚动到指定步骤 const scrollToStep = (stepId: string) => { if (!containerRef.current || !scrollRef.current) return; const stepElement = document.getElementById(`step-${stepId}`); if (!stepElement) return; const container = containerRef.current; const stepLeft = stepElement.offsetLeft; const stepWidth = stepElement.offsetWidth; const containerWidth = container.clientWidth; const targetX = -(stepLeft - (containerWidth - stepWidth) / 2); controls.start({ x: targetX }); }; return (
{/* 滚动箭头 */} {/* 胶片容器 */}
setIsDragging(true)} onDragEnd={handleDragEnd} animate={controls} style={{ x: springX }} className="flex gap-6 px-4 py-8" > {STEPS.map((step, index) => { const isActive = currentStep === step.id; return ( { if (!isDragging) { onStepChange(step.id); scrollToStep(step.id); } }} > {/* 胶片打孔效果 */}
{/* 卡片内容 */}
{step.icon}

{step.title}

{step.subtitle}

{step.description}

{/* 步骤序号 */}
{index + 1}
); })}
); }