fix: replace DropdownPanel with inline conditional rendering matching ImageStudio

Removes the DropdownPanel component that depended on the external `glass`
CSS class. Uses inline {open && <div bg-[#111]>} pattern and a single
dropdownRef, consistent with ImageStudio.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Anil Matcha 2026-03-24 04:36:02 +05:30
parent 11f850abfc
commit 0c29954889

View file

@ -174,18 +174,6 @@ function ControlBtn({ icon, label, onClick, style }) {
// Dropdown panel // Dropdown panel
// Rendered inside a `relative` wrapper div; floats above the anchor button. // Rendered inside a `relative` wrapper div; floats above the anchor button.
function DropdownPanel({ type, open, onClose, children }) {
const isModel = type === 'model';
return (
<div
onClick={e => e.stopPropagation()}
className={`absolute bottom-[calc(100%+8px)] left-0 z-50 transition-all origin-bottom-left glass rounded-3xl p-3 shadow-4xl border border-white/10 flex flex-col ${isModel ? 'w-[calc(100vw-3rem)] max-w-xs' : 'w-52 max-w-[240px]'} ${open ? 'opacity-100 pointer-events-auto scale-100' : 'opacity-0 pointer-events-none scale-95'}`}
>
{children}
</div>
);
}
// Main component // Main component
@ -242,12 +230,7 @@ export default function VideoStudio({ apiKey, onGenerationComplete, historyItems
// refs // refs
const containerRef = useRef(null); const containerRef = useRef(null);
const textareaRef = useRef(null); const textareaRef = useRef(null);
const modelBtnRef = useRef(null); const dropdownRef = useRef(null);
const arBtnRef = useRef(null);
const durationBtnRef = useRef(null);
const resolutionBtnRef = useRef(null);
const qualityBtnRef = useRef(null);
const modeBtnRef = useRef(null);
const imageFileInputRef = useRef(null); const imageFileInputRef = useRef(null);
const videoFileInputRef = useRef(null); const videoFileInputRef = useRef(null);
const resultVideoRef = useRef(null); const resultVideoRef = useRef(null);
@ -319,9 +302,9 @@ export default function VideoStudio({ apiKey, onGenerationComplete, historyItems
useEffect(() => { useEffect(() => {
if (!openDropdown) return; if (!openDropdown) return;
const handler = (e) => { const handler = (e) => {
const isInsideDropdown = [modelBtnRef, arBtnRef, durationBtnRef, resolutionBtnRef, qualityBtnRef, modeBtnRef] if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
.some(ref => ref.current && ref.current.contains(e.target)); setOpenDropdown(null);
if (!isInsideDropdown) setOpenDropdown(null); }
}; };
window.addEventListener('click', handler); window.addEventListener('click', handler);
return () => window.removeEventListener('click', handler); return () => window.removeEventListener('click', handler);
@ -845,7 +828,7 @@ export default function VideoStudio({ apiKey, onGenerationComplete, historyItems
<div className="flex items-center gap-1.5 md:gap-2.5 relative flex-wrap"> <div className="flex items-center gap-1.5 md:gap-2.5 relative flex-wrap">
{/* Model btn */} {/* Model btn */}
<div ref={modelBtnRef} className="relative"> <div className="relative">
<ControlBtn <ControlBtn
icon={ icon={
<div className="w-5 h-5 bg-primary rounded-md flex items-center justify-center shadow-lg shadow-primary/20"> <div className="w-5 h-5 bg-primary rounded-md flex items-center justify-center shadow-lg shadow-primary/20">
@ -855,120 +838,132 @@ export default function VideoStudio({ apiKey, onGenerationComplete, historyItems
label={selectedModelName} label={selectedModelName}
onClick={toggleDropdown('model')} onClick={toggleDropdown('model')}
/> />
<DropdownPanel type="model" open={openDropdown === 'model'} onClose={() => setOpenDropdown(null)}> {openDropdown === 'model' && (
<ModelDropdown <div ref={dropdownRef} onClick={e => e.stopPropagation()} className="absolute bottom-[calc(100%+8px)] left-0 z-50 bg-[#111] rounded-3xl p-3 border border-white/10 flex flex-col w-[calc(100vw-3rem)] max-w-xs">
imageMode={imageMode} <ModelDropdown
selectedModel={selectedModel} imageMode={imageMode}
onSelect={handleModelSelect} selectedModel={selectedModel}
onClose={() => setOpenDropdown(null)} onSelect={handleModelSelect}
/> onClose={() => setOpenDropdown(null)}
</DropdownPanel> />
</div>
)}
</div> </div>
{/* Aspect ratio btn */} {/* Aspect ratio btn */}
{showAr && ( {showAr && (
<div ref={arBtnRef} className="relative"> <div className="relative">
<ControlBtn <ControlBtn
icon={<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className="opacity-60 text-secondary"><rect x="3" y="3" width="18" height="18" rx="2" ry="2" /></svg>} icon={<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className="opacity-60 text-secondary"><rect x="3" y="3" width="18" height="18" rx="2" ry="2" /></svg>}
label={selectedAr} label={selectedAr}
onClick={toggleDropdown('ar')} onClick={toggleDropdown('ar')}
/> />
<DropdownPanel type="ar" open={openDropdown === 'ar'} onClose={() => setOpenDropdown(null)}> {openDropdown === 'ar' && (
<div className="text-[10px] font-bold text-muted uppercase tracking-widest px-3 py-2 border-b border-white/5 mb-2">Aspect Ratio</div> <div ref={dropdownRef} onClick={e => e.stopPropagation()} className="absolute bottom-[calc(100%+8px)] left-0 z-50 bg-[#111] rounded-3xl p-3 border border-white/10 flex flex-col w-52 max-w-[240px]">
<div className="flex flex-col gap-1"> <div className="text-[10px] font-bold text-muted uppercase tracking-widest px-3 py-2 border-b border-white/5 mb-2">Aspect Ratio</div>
{getCurrentAspectRatios(selectedModel).map(r => ( <div className="flex flex-col gap-1">
<div {getCurrentAspectRatios(selectedModel).map(r => (
key={r} <div
className="flex items-center justify-between p-3.5 hover:bg-white/5 rounded-2xl cursor-pointer transition-all group" key={r}
onClick={(e) => { e.stopPropagation(); setSelectedAr(r); setOpenDropdown(null); }} className="flex items-center justify-between p-3.5 hover:bg-white/5 rounded-2xl cursor-pointer transition-all group"
> onClick={(e) => { e.stopPropagation(); setSelectedAr(r); setOpenDropdown(null); }}
<div className="flex items-center gap-4"> >
<div className="w-6 h-6 border-2 border-white/20 rounded-md shadow-inner flex items-center justify-center group-hover:border-primary/50 transition-colors"> <div className="flex items-center gap-4">
<div className="w-3 h-3 bg-white/10 rounded-sm" /> <div className="w-6 h-6 border-2 border-white/20 rounded-md shadow-inner flex items-center justify-center group-hover:border-primary/50 transition-colors">
<div className="w-3 h-3 bg-white/10 rounded-sm" />
</div>
<span className="text-xs font-bold text-white opacity-80 group-hover:opacity-100 transition-opacity">{r}</span>
</div> </div>
<span className="text-xs font-bold text-white opacity-80 group-hover:opacity-100 transition-opacity">{r}</span> {selectedAr === r && <CheckSvg />}
</div> </div>
{selectedAr === r && <CheckSvg />} ))}
</div> </div>
))}
</div> </div>
</DropdownPanel> )}
</div> </div>
)} )}
{/* Duration btn */} {/* Duration btn */}
{showDuration && ( {showDuration && (
<div ref={durationBtnRef} className="relative"> <div className="relative">
<ControlBtn <ControlBtn
icon={<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className="opacity-60 text-secondary"><circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /></svg>} icon={<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className="opacity-60 text-secondary"><circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /></svg>}
label={`${selectedDuration}s`} label={`${selectedDuration}s`}
onClick={toggleDropdown('duration')} onClick={toggleDropdown('duration')}
/> />
<DropdownPanel type="duration" open={openDropdown === 'duration'} onClose={() => setOpenDropdown(null)}> {openDropdown === 'duration' && (
<div className="text-[10px] font-bold text-secondary uppercase tracking-widest px-3 py-2 border-b border-white/5 mb-2">Duration</div> <div ref={dropdownRef} onClick={e => e.stopPropagation()} className="absolute bottom-[calc(100%+8px)] left-0 z-50 bg-[#111] rounded-3xl p-3 border border-white/10 flex flex-col w-52 max-w-[240px]">
<div className="flex flex-col gap-1"> <div className="text-[10px] font-bold text-secondary uppercase tracking-widest px-3 py-2 border-b border-white/5 mb-2">Duration</div>
{getCurrentDurations(selectedModel).map(d => ( <div className="flex flex-col gap-1">
<DropdownItem key={d} label={`${d}s`} selected={selectedDuration === d} onClick={(e) => { e.stopPropagation(); setSelectedDuration(d); setOpenDropdown(null); }} /> {getCurrentDurations(selectedModel).map(d => (
))} <DropdownItem key={d} label={`${d}s`} selected={selectedDuration === d} onClick={(e) => { e.stopPropagation(); setSelectedDuration(d); setOpenDropdown(null); }} />
))}
</div>
</div> </div>
</DropdownPanel> )}
</div> </div>
)} )}
{/* Resolution btn */} {/* Resolution btn */}
{showResolution && ( {showResolution && (
<div ref={resolutionBtnRef} className="relative"> <div className="relative">
<ControlBtn <ControlBtn
icon={<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className="opacity-60 text-secondary"><path d="M6 2L3 6v15a2 2 0 002 2h14a2 2 0 002-2V6l-3-4H6z" /></svg>} icon={<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className="opacity-60 text-secondary"><path d="M6 2L3 6v15a2 2 0 002 2h14a2 2 0 002-2V6l-3-4H6z" /></svg>}
label={selectedResolution || '720p'} label={selectedResolution || '720p'}
onClick={toggleDropdown('resolution')} onClick={toggleDropdown('resolution')}
/> />
<DropdownPanel type="resolution" open={openDropdown === 'resolution'} onClose={() => setOpenDropdown(null)}> {openDropdown === 'resolution' && (
<div className="text-[10px] font-bold text-secondary uppercase tracking-widest px-3 py-2 border-b border-white/5 mb-2">Resolution</div> <div ref={dropdownRef} onClick={e => e.stopPropagation()} className="absolute bottom-[calc(100%+8px)] left-0 z-50 bg-[#111] rounded-3xl p-3 border border-white/10 flex flex-col w-52 max-w-[240px]">
<div className="flex flex-col gap-1"> <div className="text-[10px] font-bold text-secondary uppercase tracking-widest px-3 py-2 border-b border-white/5 mb-2">Resolution</div>
{getCurrentResolutions(selectedModel).map(r => ( <div className="flex flex-col gap-1">
<DropdownItem key={r} label={r} selected={selectedResolution === r} onClick={(e) => { e.stopPropagation(); setSelectedResolution(r); setOpenDropdown(null); }} /> {getCurrentResolutions(selectedModel).map(r => (
))} <DropdownItem key={r} label={r} selected={selectedResolution === r} onClick={(e) => { e.stopPropagation(); setSelectedResolution(r); setOpenDropdown(null); }} />
))}
</div>
</div> </div>
</DropdownPanel> )}
</div> </div>
)} )}
{/* Quality btn */} {/* Quality btn */}
{showQuality && ( {showQuality && (
<div ref={qualityBtnRef} className="relative"> <div className="relative">
<ControlBtn <ControlBtn
icon={<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className="opacity-60 text-secondary"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" /></svg>} icon={<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className="opacity-60 text-secondary"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" /></svg>}
label={selectedQuality || 'basic'} label={selectedQuality || 'basic'}
onClick={toggleDropdown('quality')} onClick={toggleDropdown('quality')}
/> />
<DropdownPanel type="quality" open={openDropdown === 'quality'} onClose={() => setOpenDropdown(null)}> {openDropdown === 'quality' && (
<div className="text-[10px] font-bold text-secondary uppercase tracking-widest px-3 py-2 border-b border-white/5 mb-2">Quality</div> <div ref={dropdownRef} onClick={e => e.stopPropagation()} className="absolute bottom-[calc(100%+8px)] left-0 z-50 bg-[#111] rounded-3xl p-3 border border-white/10 flex flex-col w-52 max-w-[240px]">
<div className="flex flex-col gap-1"> <div className="text-[10px] font-bold text-secondary uppercase tracking-widest px-3 py-2 border-b border-white/5 mb-2">Quality</div>
{getQualitiesForModel(getCurrentModels(), selectedModel).map(q => ( <div className="flex flex-col gap-1">
<DropdownItem key={q} label={q} selected={selectedQuality === q} onClick={(e) => { e.stopPropagation(); setSelectedQuality(q); setOpenDropdown(null); }} /> {getQualitiesForModel(getCurrentModels(), selectedModel).map(q => (
))} <DropdownItem key={q} label={q} selected={selectedQuality === q} onClick={(e) => { e.stopPropagation(); setSelectedQuality(q); setOpenDropdown(null); }} />
))}
</div>
</div> </div>
</DropdownPanel> )}
</div> </div>
)} )}
{/* Mode btn */} {/* Mode btn */}
{showMode && ( {showMode && (
<div ref={modeBtnRef} className="relative"> <div className="relative">
<ControlBtn <ControlBtn
icon={<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className="opacity-60 text-secondary"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" /></svg>} icon={<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className="opacity-60 text-secondary"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" /></svg>}
label={selectedMode || 'normal'} label={selectedMode || 'normal'}
onClick={toggleDropdown('mode')} onClick={toggleDropdown('mode')}
/> />
<DropdownPanel type="mode" open={openDropdown === 'mode'} onClose={() => setOpenDropdown(null)}> {openDropdown === 'mode' && (
<div className="text-[10px] font-bold text-secondary uppercase tracking-widest px-3 py-2 border-b border-white/5 mb-2">Mode</div> <div ref={dropdownRef} onClick={e => e.stopPropagation()} className="absolute bottom-[calc(100%+8px)] left-0 z-50 bg-[#111] rounded-3xl p-3 border border-white/10 flex flex-col w-52 max-w-[240px]">
<div className="flex flex-col gap-1"> <div className="text-[10px] font-bold text-secondary uppercase tracking-widest px-3 py-2 border-b border-white/5 mb-2">Mode</div>
{getModesForModel(selectedModel).map(m => ( <div className="flex flex-col gap-1">
<DropdownItem key={m} label={m} selected={selectedMode === m} onClick={(e) => { e.stopPropagation(); setSelectedMode(m); setOpenDropdown(null); }} /> {getModesForModel(selectedModel).map(m => (
))} <DropdownItem key={m} label={m} selected={selectedMode === m} onClick={(e) => { e.stopPropagation(); setSelectedMode(m); setOpenDropdown(null); }} />
))}
</div>
</div> </div>
</DropdownPanel> )}
</div> </div>
)} )}
</div> </div>