video-flow-b/components/ui/settings-tab-content.tsx
2025-06-30 12:48:25 +08:00

319 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Check, ChevronDown, Volume2, Music, Mic, Radio } from 'lucide-react';
import { cn } from '@/public/lib/utils';
interface SettingsTabContentProps {
onSettingChange?: (key: string, value: any) => void;
}
interface SettingOption {
label: string;
value: string;
}
const modeOptions: SettingOption[] = [
{ label: 'Auto Mode', value: 'auto' },
{ label: 'Manual Mode', value: 'manual' },
];
const resolutionOptions: SettingOption[] = [
{ label: '720P', value: '720p' },
{ label: '1080P', value: '1080p' },
];
const overlayOptions: SettingOption[] = [
{ label: 'None', value: 'none' },
{ label: 'Light Leaks', value: 'light_leaks' },
{ label: 'Film Grain', value: 'film_grain' },
{ label: 'Dust', value: 'dust' },
];
const transitionOptions: SettingOption[] = [
{ label: 'None', value: 'none' },
{ label: 'Dynamic Cut', value: 'dynamic_cut' },
{ label: 'Zoom Pop', value: 'zoom_pop' },
{ label: 'Smooth Fade', value: 'smooth_fade' },
];
const subtitleOptions: SettingOption[] = [
{ label: 'None', value: 'none' },
{ label: 'Hormozi', value: 'hormozi' },
{ label: 'Castor', value: 'castor' },
{ label: 'Modern', value: 'modern' },
];
const watermarkOptions: SettingOption[] = [
{ label: 'None', value: 'none' },
{ label: 'Simple', value: 'simple' },
{ label: 'Branded', value: 'branded' },
];
const textEffectOptions: SettingOption[] = [
{ label: 'Minimal', value: 'minimal' },
{ label: 'Plain', value: 'plain' },
{ label: 'Dramatic', value: 'dramatic' },
{ label: 'Corporate', value: 'corporate' },
];
export function SettingsTabContent({ onSettingChange }: SettingsTabContentProps) {
const [selectedMode, setSelectedMode] = useState('auto');
const [selectedResolution, setSelectedResolution] = useState('1080p');
const [selectedOverlay, setSelectedOverlay] = useState('light_leaks');
const [selectedTransition, setSelectedTransition] = useState('none');
const [selectedSubtitle, setSelectedSubtitle] = useState('hormozi');
const [selectedWatermark, setSelectedWatermark] = useState('none');
const [selectedTextEffect, setSelectedTextEffect] = useState('minimal');
const [sfxVolume, setSfxVolume] = useState(75);
const [mediaVolume, setMediaVolume] = useState(75);
const [musicVolume, setMusicVolume] = useState(80);
const [voiceVolume, setVoiceVolume] = useState(80);
const [openDropdown, setOpenDropdown] = useState<string | null>(null);
const handleDropdownToggle = (key: string) => {
setOpenDropdown(openDropdown === key ? null : key);
};
const renderDropdown = (
key: string,
label: string,
options: SettingOption[],
value: string,
onChange: (value: string) => void
) => (
<div className="relative">
<motion.button
className={cn(
"w-full px-4 py-2 rounded-lg border text-left flex items-center justify-between",
openDropdown === key
? "border-blue-500 bg-blue-500/10"
: "border-white/10 hover:border-white/20"
)}
onClick={() => handleDropdownToggle(key)}
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
>
<span>{options.find(opt => opt.value === value)?.label || value}</span>
<motion.div
animate={{ rotate: openDropdown === key ? 180 : 0 }}
transition={{ duration: 0.2 }}
>
<ChevronDown className="w-4 h-4" />
</motion.div>
</motion.button>
<AnimatePresence>
{openDropdown === key && (
<motion.div
className="absolute z-10 w-full mt-1 py-1 rounded-lg border border-white/10 bg-black/90 backdrop-blur-xl"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }}
>
{options.map((option) => (
<motion.button
key={option.value}
className={cn(
"w-full px-4 py-2 text-left flex items-center justify-between hover:bg-white/5",
value === option.value && "text-blue-500"
)}
onClick={() => {
onChange(option.value);
handleDropdownToggle(key);
onSettingChange?.(key, option.value);
}}
whileHover={{ x: 4 }}
>
{option.label}
{value === option.value && <Check className="w-4 h-4" />}
</motion.button>
))}
</motion.div>
)}
</AnimatePresence>
</div>
);
const renderVolumeSlider = (
icon: React.ReactNode,
label: string,
value: number,
onChange: (value: number) => void
) => (
<div className="space-y-2">
<div className="flex items-center gap-2 text-sm text-white/70">
{icon}
<span>{label}</span>
</div>
<div className="flex items-center gap-4">
<input
type="range"
min="0"
max="100"
value={value}
className="flex-1 h-2 rounded-full bg-white/10 cursor-pointer
[&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4
[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-blue-500
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:cursor-pointer
[&::-webkit-slider-thumb]:transition-all [&::-webkit-slider-thumb]:duration-200
[&::-webkit-slider-thumb]:hover:scale-110"
onChange={(e) => {
const newValue = parseInt(e.target.value);
onChange(newValue);
onSettingChange?.(label, newValue);
}}
/>
<span className="w-12 text-right text-sm">{value}%</span>
</div>
</div>
);
return (
<motion.div
className="space-y-8 px-1"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
{/* 基础设置组 */}
<div className="space-y-4">
<div className="flex items-center gap-2 pb-2 border-b border-white/10">
<div className="h-4 w-1 rounded-full bg-blue-500" />
<h3 className="text-sm font-medium text-white/90"></h3>
</div>
<div className="grid grid-cols-2 gap-4">
{/* 工作模式 */}
<div className="space-y-2">
<label className="text-sm text-white/70"></label>
{renderDropdown('mode', 'Mode', modeOptions, selectedMode, setSelectedMode)}
</div>
{/* 分辨率 */}
<div className="space-y-2">
<label className="text-sm text-white/70"></label>
{renderDropdown('resolution', 'Resolution', resolutionOptions, selectedResolution, setSelectedResolution)}
</div>
</div>
</div>
{/* 视觉效果组 */}
<div className="space-y-4">
<div className="flex items-center gap-2 pb-2 border-b border-white/10">
<div className="h-4 w-1 rounded-full bg-emerald-500" />
<h3 className="text-sm font-medium text-white/90"></h3>
</div>
<div className="grid grid-cols-2 gap-4">
{/* 叠加效果 */}
<div className="space-y-2">
<label className="text-sm text-white/70"></label>
{renderDropdown('overlay', 'Overlay Preset', overlayOptions, selectedOverlay, setSelectedOverlay)}
</div>
{/* 转场设定 */}
<div className="space-y-2">
<label className="text-sm text-white/70"></label>
{renderDropdown('transition', 'Transition Preset', transitionOptions, selectedTransition, setSelectedTransition)}
</div>
</div>
</div>
{/* 文字样式组 */}
<div className="space-y-4">
<div className="flex items-center gap-2 pb-2 border-b border-white/10">
<div className="h-4 w-1 rounded-full bg-purple-500" />
<h3 className="text-sm font-medium text-white/90"></h3>
</div>
<div className="grid grid-cols-2 gap-4">
{/* 字幕风格 */}
<div className="space-y-2">
<label className="text-sm text-white/70"></label>
{renderDropdown('subtitle', 'Subtitle Preset', subtitleOptions, selectedSubtitle, setSelectedSubtitle)}
</div>
{/* 文字效果 */}
<div className="space-y-2">
<label className="text-sm text-white/70"></label>
{renderDropdown('textEffect', 'Text Preset', textEffectOptions, selectedTextEffect, setSelectedTextEffect)}
</div>
{/* 贴纸预设 */}
<div className="space-y-2">
<label className="text-sm text-white/70"></label>
{renderDropdown('watermark', 'Sticker Preset', watermarkOptions, selectedWatermark, setSelectedWatermark)}
</div>
</div>
</div>
{/* 音频设置组 */}
<div className="space-y-4">
<div className="flex items-center gap-2 pb-2 border-b border-white/10">
<div className="h-4 w-1 rounded-full bg-amber-500" />
<h3 className="text-sm font-medium text-white/90"></h3>
</div>
<div className="grid grid-cols-1 gap-6">
<div className="grid grid-cols-2 gap-6">
{/* 音效主音量 */}
<div className="p-4 rounded-xl bg-white/5 hover:bg-white/[0.07] transition-colors duration-200">
{renderVolumeSlider(
<Volume2 className="w-4 h-4 text-amber-500" />,
"音效主音量",
sfxVolume,
setSfxVolume
)}
</div>
{/* 配音主音量 */}
<div className="p-4 rounded-xl bg-white/5 hover:bg-white/[0.07] transition-colors duration-200">
{renderVolumeSlider(
<Mic className="w-4 h-4 text-amber-500" />,
"配音主音量",
voiceVolume,
setVoiceVolume
)}
</div>
</div>
<div className="grid grid-cols-2 gap-6">
{/* 媒体音频主音量 */}
<div className="p-4 rounded-xl bg-white/5 hover:bg-white/[0.07] transition-colors duration-200">
{renderVolumeSlider(
<Radio className="w-4 h-4 text-amber-500" />,
"媒体音频主音量",
mediaVolume,
setMediaVolume
)}
</div>
{/* 音乐主音量 */}
<div className="p-4 rounded-xl bg-white/5 hover:bg-white/[0.07] transition-colors duration-200">
{renderVolumeSlider(
<Music className="w-4 h-4 text-amber-500" />,
"音乐主音量",
musicVolume,
setMusicVolume
)}
</div>
</div>
{/* 音量混合提示 */}
<div className="flex items-center gap-3 px-4 py-3 rounded-lg bg-amber-500/10 border border-amber-500/20">
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-amber-500/20">
<Volume2 className="w-4 h-4 text-amber-500" />
</div>
<div className="flex-1">
<p className="text-sm text-amber-500"></p>
<p className="text-xs text-amber-500/80 mt-0.5">
</p>
</div>
</div>
</div>
</div>
</motion.div>
);
}