video-flow-b/components/ui/settings-tab-content.tsx
2025-07-03 05:51:09 +08:00

319 lines
12 KiB
TypeScript

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">Basic settings</h3>
</div>
<div className="grid grid-cols-2 gap-4">
{/* 工作模式 */}
<div className="space-y-2">
<label className="text-sm text-white/70">Work mode</label>
{renderDropdown('mode', 'Mode', modeOptions, selectedMode, setSelectedMode)}
</div>
{/* 分辨率 */}
<div className="space-y-2">
<label className="text-sm text-white/70">Resolution</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">Visual effects</h3>
</div>
<div className="grid grid-cols-2 gap-4">
{/* 叠加效果 */}
<div className="space-y-2">
<label className="text-sm text-white/70">Overlay preset</label>
{renderDropdown('overlay', 'Overlay Preset', overlayOptions, selectedOverlay, setSelectedOverlay)}
</div>
{/* 转场设定 */}
<div className="space-y-2">
<label className="text-sm text-white/70">Transition preset</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">Text style</h3>
</div>
<div className="grid grid-cols-2 gap-4">
{/* 字幕风格 */}
<div className="space-y-2">
<label className="text-sm text-white/70">Subtitle preset</label>
{renderDropdown('subtitle', 'Subtitle Preset', subtitleOptions, selectedSubtitle, setSelectedSubtitle)}
</div>
{/* 文字效果 */}
<div className="space-y-2">
<label className="text-sm text-white/70">Text preset</label>
{renderDropdown('textEffect', 'Text Preset', textEffectOptions, selectedTextEffect, setSelectedTextEffect)}
</div>
{/* 贴纸预设 */}
<div className="space-y-2">
<label className="text-sm text-white/70">Sticker preset</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">Audio settings</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" />,
"SFX volume",
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" />,
"Voice volume",
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" />,
"Media volume",
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" />,
"Music volume",
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">Volume mixing tips</p>
<p className="text-xs text-amber-500/80 mt-0.5">
It is recommended to maintain a balance between SFX, voice, media audio, and music to achieve the best auditory experience
</p>
</div>
</div>
</div>
</div>
</motion.div>
);
}