feat: add fullscreen toggle to Modal, enable in NodeEditorModal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useCallback, type ReactNode } from 'react'
|
import { useState, useEffect, useCallback, type ReactNode } from 'react'
|
||||||
import { X } from 'lucide-react'
|
import { X, Maximize2, Minimize2 } from 'lucide-react'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
@@ -10,9 +10,28 @@ interface ModalProps {
|
|||||||
/** Optional footer content that stays fixed at bottom (doesn't scroll) */
|
/** Optional footer content that stays fixed at bottom (doesn't scroll) */
|
||||||
footer?: ReactNode
|
footer?: ReactNode
|
||||||
size?: 'sm' | 'md' | 'lg' | 'xl'
|
size?: 'sm' | 'md' | 'lg' | 'xl'
|
||||||
|
/** If true, a fullscreen toggle button appears in the modal header */
|
||||||
|
allowFullScreen?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Modal({ isOpen, onClose, title, children, footer, size = 'md' }: ModalProps) {
|
export function Modal({ isOpen, onClose, title, children, footer, size = 'md', allowFullScreen = false }: ModalProps) {
|
||||||
|
const [isFullScreen, setIsFullScreen] = useState(() => {
|
||||||
|
if (!allowFullScreen) return false
|
||||||
|
try {
|
||||||
|
return localStorage.getItem('rf-editor-fullscreen') === 'true'
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const toggleFullScreen = () => {
|
||||||
|
const next = !isFullScreen
|
||||||
|
setIsFullScreen(next)
|
||||||
|
try {
|
||||||
|
localStorage.setItem('rf-editor-fullscreen', String(next))
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
// Close on Escape key
|
// Close on Escape key
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
@@ -61,9 +80,13 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md' }:
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative flex w-full flex-col border border-border bg-card shadow-lg',
|
'relative flex w-full flex-col border border-border bg-card shadow-lg',
|
||||||
'max-h-[100vh] rounded-t-2xl sm:max-h-[85vh] sm:rounded-2xl',
|
'animate-scale-in transition-all duration-200',
|
||||||
'animate-scale-in',
|
isFullScreen
|
||||||
sizeClasses[size]
|
? 'fixed inset-4 max-w-none w-auto h-auto rounded-2xl'
|
||||||
|
: cn(
|
||||||
|
'max-h-[100vh] rounded-t-2xl sm:max-h-[85vh] sm:rounded-2xl',
|
||||||
|
sizeClasses[size]
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Header - Fixed at top */}
|
{/* Header - Fixed at top */}
|
||||||
@@ -71,17 +94,32 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md' }:
|
|||||||
<h2 id="modal-title" className="text-lg font-semibold text-foreground">
|
<h2 id="modal-title" className="text-lg font-semibold text-foreground">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<div className="flex items-center gap-1">
|
||||||
onClick={onClose}
|
{allowFullScreen && (
|
||||||
className={cn(
|
<button
|
||||||
'rounded-md p-1.5 text-muted-foreground transition-colors sm:p-1',
|
type="button"
|
||||||
'hover:bg-accent hover:text-foreground',
|
onClick={toggleFullScreen}
|
||||||
'focus:outline-none focus:ring-2 focus:ring-primary/20'
|
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
|
title={isFullScreen ? 'Exit full screen' : 'Full screen'}
|
||||||
|
>
|
||||||
|
{isFullScreen
|
||||||
|
? <Minimize2 className="h-4 w-4" />
|
||||||
|
: <Maximize2 className="h-4 w-4" />
|
||||||
|
}
|
||||||
|
</button>
|
||||||
)}
|
)}
|
||||||
aria-label="Close modal"
|
<button
|
||||||
>
|
onClick={onClose}
|
||||||
<X className="h-5 w-5" />
|
className={cn(
|
||||||
</button>
|
'rounded-md p-1.5 text-muted-foreground transition-colors sm:p-1',
|
||||||
|
'hover:bg-accent hover:text-foreground',
|
||||||
|
'focus:outline-none focus:ring-2 focus:ring-primary/20'
|
||||||
|
)}
|
||||||
|
aria-label="Close modal"
|
||||||
|
>
|
||||||
|
<X className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Body - Scrollable */}
|
{/* Body - Scrollable */}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export function NodeEditorModal({ node, onClose, isNewNode = false }: NodeEditor
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={true} onClose={onClose} title={getTitle()} size="lg" footer={footerContent}>
|
<Modal isOpen={true} onClose={onClose} title={getTitle()} size="lg" footer={footerContent} allowFullScreen={true}>
|
||||||
{/* Node ID display */}
|
{/* Node ID display */}
|
||||||
<div className="mb-4 text-xs text-muted-foreground">
|
<div className="mb-4 text-xs text-muted-foreground">
|
||||||
Node ID: <code className="rounded bg-accent px-1 py-0.5">{node.id}</code>
|
Node ID: <code className="rounded bg-accent px-1 py-0.5">{node.id}</code>
|
||||||
|
|||||||
Reference in New Issue
Block a user