import { type CSSProperties, type ReactNode, useEffect } from 'react' import { TITLEBAR_HEIGHT } from '@/app/shell/titlebar' import { TitlebarIcon } from '@/app/shell/titlebar-icon' import { Button } from '@/components/ui/button' import { translateNow } from '@/i18n' import { ESCAPE_PRIORITY, isTopEscapeLayer, pushEscapeLayer } from '@/lib/escape-layers' import { triggerHaptic } from '@/lib/haptics' import { cn } from '@/lib/utils' // Shared top clearance for overlay content that sits *beside* the floating // close button (which is absolute at `0.1875rem + titlebar/2`, -translate-y-1/2, // so it costs no layout space): a Panel's header and the split layout's left // sidebar links. They ride up next to the X on the same line across every // overlay (settings, system, agents, cron, …) — change it here, not per-surface. // Main content sits *under* the X (top-right) and keeps its own taller pad. export const OVERLAY_TOP_CLEARANCE = 'pt-[calc(var(--titlebar-height)/2-0.4375rem)]' interface OverlayViewProps { children: ReactNode onClose: () => void closeLabel?: string contentClassName?: string /** Chrome pinned to the card's top edge, horizontally centered and riding * the border half-in half-out (e.g. the Settings search pill). Rendered * beside the card, not inside it — the card clips its own overflow. */ edgeBadge?: ReactNode headerContent?: ReactNode rootClassName?: string /** Controls rendered on the close button's row, to its left. They ride the * titlebar strip, so keep them titlebar-sized and quiet. */ titlebarActions?: ReactNode } export function OverlayView({ children, onClose, closeLabel = translateNow('common.close'), contentClassName, edgeBadge, headerContent, rootClassName, titlebarActions }: OverlayViewProps) { const closeOverlay = () => { triggerHaptic('close') onClose() } // Esc dismisses every OverlayView-based overlay. Nested Radix dialogs // stop propagation themselves, so opening (e.g.) the model picker inside // Settings still closes the picker first instead of the underlying overlay. useEffect(() => { const releaseLayer = pushEscapeLayer(ESCAPE_PRIORITY.overlay) const onKeyDown = (event: KeyboardEvent) => { if (event.key !== 'Escape' || event.defaultPrevented || !isTopEscapeLayer(ESCAPE_PRIORITY.overlay)) { return } event.preventDefault() triggerHaptic('close') onClose() } window.addEventListener('keydown', onKeyDown) return () => { window.removeEventListener('keydown', onKeyDown) releaseLayer() } }, [onClose]) return (