fix: always-mounted modal — GPU keeps backdrop-filter composited, no blur delay
This commit is contained in:
@@ -373,29 +373,25 @@ export default function UnifiedSettingsModal() {
|
|||||||
}, [guardedAction, closeSettings])
|
}, [guardedAction, closeSettings])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
|
||||||
{isOpen && (
|
|
||||||
<>
|
<>
|
||||||
{/* Backdrop — solid scrim, no blur (modal handles its own glass) */}
|
{/* Backdrop — always mounted, visibility toggled via CSS */}
|
||||||
<motion.div
|
<div
|
||||||
initial={{ opacity: 1 }}
|
className={`fixed inset-0 z-[60] bg-black/50 transition-opacity duration-150 ${
|
||||||
animate={{ opacity: 1 }}
|
isOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
||||||
exit={{ opacity: 0 }}
|
}`}
|
||||||
transition={{ duration: 0.15 }}
|
|
||||||
className="fixed inset-0 z-[60] bg-black/50"
|
|
||||||
onClick={handleBackdropClick}
|
onClick={handleBackdropClick}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal — always mounted, blur always composited by GPU */}
|
||||||
<motion.div
|
<div
|
||||||
initial={{ scale: 0.97, y: 8 }}
|
className={`fixed inset-0 z-[61] flex items-center justify-center p-4 transition-all duration-200 ease-out ${
|
||||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
isOpen
|
||||||
exit={{ opacity: 0, scale: 0.97, y: 8 }}
|
? 'opacity-100 scale-100 translate-y-0 pointer-events-auto'
|
||||||
transition={{ type: 'spring', bounce: 0.15, duration: 0.35 }}
|
: 'opacity-0 scale-[0.97] translate-y-2 pointer-events-none'
|
||||||
className="fixed inset-0 z-[61] flex items-center justify-center p-4 pointer-events-none"
|
}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="pointer-events-auto w-full max-w-3xl h-[85vh] bg-neutral-900/65 backdrop-blur-3xl backdrop-saturate-150 supports-[backdrop-filter]:bg-neutral-900/60 border border-white/[0.08] rounded-2xl shadow-xl shadow-black/20 flex flex-col overflow-hidden"
|
className="w-full max-w-3xl h-[85vh] bg-neutral-900/65 backdrop-blur-3xl backdrop-saturate-150 supports-[backdrop-filter]:bg-neutral-900/60 border border-white/[0.08] rounded-2xl shadow-xl shadow-black/20 flex flex-col overflow-hidden"
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -423,8 +419,9 @@ export default function UnifiedSettingsModal() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content — only render tab content when open to avoid unnecessary SWR calls */}
|
||||||
<div className="flex-1 overflow-y-auto overflow-x-hidden">
|
<div className="flex-1 overflow-y-auto overflow-x-hidden">
|
||||||
|
{isOpen && (
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
<motion.div
|
<motion.div
|
||||||
key={`${context}-${activeTab}`}
|
key={`${context}-${activeTab}`}
|
||||||
@@ -437,9 +434,10 @@ export default function UnifiedSettingsModal() {
|
|||||||
<TabContent context={context} activeTab={activeTab} siteId={activeSiteId} onDirtyChange={handleDirtyChange} onRegisterSave={handleRegisterSave} />
|
<TabContent context={context} activeTab={activeTab} siteId={activeSiteId} onDirtyChange={handleDirtyChange} onRegisterSave={handleRegisterSave} />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Save bar — fixed at modal bottom, outside scroll */}
|
{/* Save bar */}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{isDirtyVisible && (
|
{isDirtyVisible && (
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -472,9 +470,7 @@ export default function UnifiedSettingsModal() {
|
|||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user