video-flow-b/components/script-edit-dialog.tsx
2025-08-03 18:34:29 +08:00

227 lines
8.4 KiB
TypeScript

'use client';
import { motion, AnimatePresence } from 'framer-motion';
import { X } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import { Button } from './ui/button';
import { Input } from './ui/input';
interface ScriptEditDialogProps {
isOpen: boolean;
onClose: () => void;
onConfirm?: (content: string) => void;
}
export function ScriptEditDialog({ isOpen, onClose, onConfirm }: ScriptEditDialogProps) {
const [suggestion, setSuggestion] = useState('');
const [isUpdating, setIsUpdating] = useState(false);
const handleUpdate = () => {
if (!suggestion.trim()) return;
setIsUpdating(true);
// 模拟更新延迟
setTimeout(() => {
setIsUpdating(false);
setSuggestion('');
}, 1000);
};
const handleReset = () => {
setSuggestion('');
};
return (
<AnimatePresence mode="wait">
{isOpen && (
<>
{/* 背景遮罩 */}
<motion.div
className="fixed inset-0 bg-black/20 backdrop-blur-sm z-50"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
onClick={onClose}
/>
{/* 弹窗内容 */}
<motion.div
className="fixed inset-0 z-50 flex items-center justify-center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
>
<motion.div
className="relative w-7/12 h-[90vh] bg-white/80 dark:bg-[#5b75ac4d] backdrop-blur-xl rounded-2xl shadow-2xl overflow-hidden flex flex-col"
initial={{ scale: 0.95, y: 10, opacity: 0 }}
animate={{
scale: 1,
y: 0,
opacity: 1,
transition: {
type: "spring",
duration: 0.3,
bounce: 0.15,
stiffness: 300,
damping: 25
}
}}
exit={{
scale: 0.95,
y: 10,
opacity: 0,
transition: {
type: "tween",
duration: 0.1,
ease: "easeOut"
}
}}
>
{/* 关闭按钮 */}
<motion.button
className="absolute z-50 top-4 right-4 p-2 rounded-full bg-gray-100/80 dark:bg-gray-800/80 hover:bg-gray-200/80 dark:hover:bg-gray-700/80 transition-colors"
onClick={onClose}
whileHover={{ rotate: 90 }}
whileTap={{ scale: 0.9 }}
transition={{ duration: 0.1 }}
>
<X className="w-5 h-5 text-gray-600 dark:text-gray-300" />
</motion.button>
{/* 标题 */}
<motion.div
className="flex-none px-6 py-4"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
>
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">
Edit Script
</h2>
</motion.div>
{/* 内容区域 */}
<motion.div
className="flex-1 overflow-auto p-6 pt-0 pb-0"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.1, duration: 0.2 }}
>
{/* TypingEditor */}
<motion.div
style={{
height: 'calc(100% - 88px)'
}}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
>
</motion.div>
{/* 修改建议输入区域 */}
<motion.div
className="sticky bottom-0 bg-gradient-to-t from-white via-white to-transparent dark:from-[#5b75ac4d] dark:via-[#5b75ac4d] dark:to-transparent pt-8 pb-4 rounded-sm"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
>
<div className="flex items-center space-x-2 px-4">
<div className="flex-1">
<Input
placeholder="Enter your modification suggestion and press Enter to send..."
value={suggestion}
onChange={(e) => setSuggestion(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleUpdate();
}
}}
className="outline-none box-shadow-none bg-white/50 dark:bg-[#5b75ac20] border-0 focus:ring-2 focus:ring-blue-500/20 transition-all duration-200"
/>
</div>
<motion.div
initial={false}
animate={{
scale: suggestion.trim() ? 1 : 0.8,
opacity: suggestion.trim() ? 1 : 0.5,
}}
transition={{
type: "spring",
stiffness: 500,
damping: 30
}}
>
<Button
variant="ghost"
size="icon"
onClick={handleUpdate}
disabled={!suggestion.trim() || isUpdating}
className="relative w-9 h-9 rounded-full bg-blue-500/10 hover:bg-blue-500/20 text-blue-600 dark:text-blue-400"
>
<motion.span
initial={false}
animate={{
opacity: isUpdating ? 0 : 1,
scale: isUpdating ? 0.5 : 1,
}}
>
<svg
className="w-5 h-5 transform rotate-90"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
/>
</svg>
</motion.span>
{isUpdating && (
<motion.div
className="absolute inset-0 flex items-center justify-center"
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.5 }}
>
<div className="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" />
</motion.div>
)}
</Button>
</motion.div>
</div>
</motion.div>
</motion.div>
{/* 底部按钮 */}
<motion.div
className="flex-none px-6 py-4 flex justify-end space-x-4"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4 }}
>
<Button
variant="ghost"
onClick={handleReset}
className="min-w-[80px] bg-white/10 text-white hover:bg-white/20 transition-colors"
>
Reset
</Button>
<Button
onClick={() => onConfirm?.('')}
className="min-w-[80px] bg-blue-500 text-white hover:bg-blue-600 transition-colors"
>
Confirm
</Button>
</motion.div>
</motion.div>
</motion.div>
</>
)}
</AnimatePresence>
);
}