forked from 77media/video-flow
227 lines
8.4 KiB
TypeScript
227 lines
8.4 KiB
TypeScript
'use client';
|
|
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { X } from 'lucide-react';
|
|
import { 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>
|
|
);
|
|
}
|