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}
);
})}
);
}