feat: add size='settings' variant to Modal component

This commit is contained in:
Jannis Braun
2026-03-22 02:13:49 +01:00
parent 4c4bd8dec6
commit 9bacc8fb3d
+31 -4
View File
@@ -7,11 +7,12 @@ interface ModalProps {
title?: string; title?: string;
children: React.ReactNode; children: React.ReactNode;
maxWidth?: string; maxWidth?: string;
size?: 'settings';
/** Mobile display style: 'fullscreen' fills the screen, 'sheet' anchors to bottom, 'default' stays centered */ /** Mobile display style: 'fullscreen' fills the screen, 'sheet' anchors to bottom, 'default' stays centered */
mobileStyle?: 'fullscreen' | 'sheet' | 'default'; mobileStyle?: 'fullscreen' | 'sheet' | 'default';
} }
export function Modal({ isOpen, onClose, title, children, maxWidth = 'max-w-md', mobileStyle = 'default' }: ModalProps) { export function Modal({ isOpen, onClose, title, children, maxWidth = 'max-w-md', size, mobileStyle = 'default' }: ModalProps) {
const isMobile = useUIStore((s) => s.isMobile); const isMobile = useUIStore((s) => s.isMobile);
const handleKeyDown = useCallback((e: KeyboardEvent) => { const handleKeyDown = useCallback((e: KeyboardEvent) => {
@@ -33,12 +34,13 @@ export function Modal({ isOpen, onClose, title, children, maxWidth = 'max-w-md',
if (isMobile && mobileStyle === 'fullscreen') { if (isMobile && mobileStyle === 'fullscreen') {
return ( return (
<div className="fixed inset-0 z-[200] flex flex-col bg-surface-base animate-fade-in"> <div className="fixed inset-0 z-[200] flex flex-col bg-surface-base animate-fade-in">
{title && ( {(title || size === 'settings') && (
<div className="flex items-center justify-between px-4 pt-4 flex-shrink-0" style={{ paddingTop: 'calc(16px + env(safe-area-inset-top))' }}> <div className="flex items-center justify-between px-4 pt-4 flex-shrink-0" style={{ paddingTop: 'calc(16px + env(safe-area-inset-top))' }}>
<h2 className="text-xl font-bold text-txt-primary">{title}</h2> {title ? <h2 className="text-xl font-bold text-txt-primary">{title}</h2> : <div />}
<button <button
onClick={onClose} onClick={onClose}
className="text-txt-tertiary hover:text-txt-primary transition-colors p-1" className="text-txt-tertiary hover:text-txt-primary transition-colors p-1"
aria-label={size === 'settings' ? 'Close settings' : undefined}
> >
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"> <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z" /> <path d="M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z" />
@@ -46,7 +48,7 @@ export function Modal({ isOpen, onClose, title, children, maxWidth = 'max-w-md',
</button> </button>
</div> </div>
)} )}
<div className="p-4 overflow-y-auto scrollbar-thin flex-1 min-h-0" style={{ paddingBottom: 'calc(16px + env(safe-area-inset-bottom))' }}> <div className={`${size === 'settings' ? '' : 'p-4 overflow-y-auto scrollbar-thin'} flex-1 min-h-0`} style={size === 'settings' ? undefined : { paddingBottom: 'calc(16px + env(safe-area-inset-bottom))' }}>
{children} {children}
</div> </div>
</div> </div>
@@ -83,6 +85,31 @@ export function Modal({ isOpen, onClose, title, children, maxWidth = 'max-w-md',
); );
} }
// Settings size variant — large glass overlay for settings screens
if (size === 'settings') {
return (
<div className="fixed inset-0 z-[200] flex items-center justify-center animate-fade-in">
<div
className="absolute inset-0 bg-black/50"
onClick={onClose}
/>
<div className="relative w-[90vw] max-w-6xl h-[85vh] flex flex-col glass-modal rounded-xl animate-slide-up overflow-hidden">
{/* Floating close button */}
<button
onClick={onClose}
className="absolute top-4 right-4 z-10 p-1.5 rounded-full bg-surface-elevated/50 backdrop-blur-sm text-txt-tertiary hover:text-txt-primary transition-colors"
aria-label="Close settings"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z" />
</svg>
</button>
{children}
</div>
</div>
);
}
// Default centered dialog (desktop and mobile default) // Default centered dialog (desktop and mobile default)
return ( return (
<div className="fixed inset-0 z-[200] flex items-center justify-center animate-fade-in"> <div className="fixed inset-0 z-[200] flex items-center justify-center animate-fade-in">