Floating Windows¶
Draggable, resizable panels rendered as portals to document.body. They persist across in-page navigation and stay open while the user works elsewhere in the app.
Setup¶
FloatingWindowProvider and FloatingWindowOutlet are already mounted in ClientProviders. No per-page setup is needed. See UI Providers for the full provider tree.
useFloatingWindow()¶
import { useFloatingWindow } from "@/hooks/useFloatingWindow";
const win = useFloatingWindow();
win.open({
title: "Image Inspector",
content: <MyPanel />,
initialWidth: 600,
initialHeight: 400,
});
// win.close() — close the window
// win.isOpen — boolean
The hook auto-generates a stable ID per instance and auto-closes the window when the component unmounts.
FloatingWindowConfig¶
type FloatingWindowConfig = {
content: ReactNode; // Panel body
title: string; // Header title
initialWidth?: number; // px, default 480
initialHeight?: number; // px, default 360
minWidth?: number; // px, default 240
minHeight?: number; // px, default 180
initialPosition?: { x: number; y: number }; // viewport coords; centered if omitted
onClose?: () => void; // Called after the window closes
};
Shell Behavior¶
Each floating window is rendered in a FloatingWindowShell:
- Drag — grab the grip icon in the header to reposition
- Resize — 8-way handles (n / s / e / w / ne / nw / se / sw) with matching cursors
- Fullscreen toggle —
Maximize2expands to fill the viewport;Minimize2restores the previous rect - Escape — closes the window, unless a modal backdrop or compare mode is currently active (in which case Escape is forwarded to those first)
- Viewport clamping — windows cannot be dragged fully off-screen; minimum size is enforced during resize
Provider Tree¶
FloatingWindowProvider ← stores config map in memory
└── FloatingWindowOutlet ← renders portals via useSyncExternalStore
└── FloatingWindowShell (one per open window)
└── config.content
FloatingWindowOutlet subscribes to the provider via useSyncExternalStore so it re-renders only when the config version changes, not on every state update.
Current Usage¶
MediaClipboardFab is the only consumer. It opens the clipboard panel as a floating window:
const win = useFloatingWindow();
win.open({
title: "Media Clipboard",
content: <MediaClipboardContent onClose={win.close} />,
initialWidth: 700,
initialHeight: 500,
});
This provides a canonical implementation reference for adding new consumers.
Source Files¶
| File | Contents |
|---|---|
src/providers/FloatingWindowProvider.tsx |
Context, config store, useFloatingWindowConfigVersion |
src/components/FloatingWindow/FloatingWindowOutlet.tsx |
Portal renderer |
src/components/FloatingWindow/FloatingWindowShell.tsx |
Draggable/resizable shell |
src/hooks/useFloatingWindow.ts |
Consumer hook |
src/types/floatingWindow.ts |
FloatingWindowConfig type |