/* global React, Icon */ /* Overlays: Modal, Drawer, Popover, Toast, Empty, Loading, Error, Skeleton */ const { useState, useEffect, useRef, useCallback } = React; function Modal({ open, onClose, title, eyebrow, children, footer, width = 520 }) { useEffect(() => { if (!open) return; const onKey = (e) => e.key === "Escape" && onClose && onClose(); window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open, onClose]); if (!open) return null; return (
e.stopPropagation()} style={{ background: "var(--bg)", color: "var(--ink)", maxWidth: width, width: "100%", border: "1px solid var(--ink)", borderRadius: "var(--r-sharp)", animation: "popIn 220ms cubic-bezier(.2,.7,.2,1)", maxHeight: "85vh", overflow: "auto", }} className="scroll" >
{eyebrow && {eyebrow}}
{title &&

{title}

} {children}
{footer && (
{footer}
)}
); } function Drawer({ open, onClose, title, eyebrow, children, footer, width = 520 }) { useEffect(() => { if (!open) return; const onKey = (e) => e.key === "Escape" && onClose && onClose(); window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open, onClose]); if (!open) return null; return (
e.stopPropagation()} style={{ position: "absolute", right: 0, top: 0, bottom: 0, width, maxWidth: "94vw", background: "var(--bg)", borderLeft: "1px solid var(--line-2)", display: "flex", flexDirection: "column", animation: "slideInR 280ms cubic-bezier(.2,.7,.2,1)", }}>
{eyebrow &&
{eyebrow}
}

{title}

{children}
{footer && (
{footer}
)}
); } function Popover({ trigger, children, align = "right" }) { const [open, setOpen] = useState(false); const ref = useRef(); useEffect(() => { if (!open) return; const onClick = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; setTimeout(() => document.addEventListener("click", onClick), 0); return () => document.removeEventListener("click", onClick); }, [open]); return (
setOpen(!open)} style={{ display: "inline-block" }}>{trigger} {open && (
{typeof children === "function" ? children({ close: () => setOpen(false) }) : children}
)}
); } function PopoverItem({ icon, label, shortcut, danger, onClick }) { return ( ); } /* ---------- Toasts ---------- */ const ToastCtx = React.createContext(null); function ToastHost({ children }) { const [items, setItems] = useState([]); const push = useCallback((t) => { const id = Math.random().toString(36).slice(2); setItems((prev) => [...prev, { id, ...t }]); setTimeout(() => setItems((prev) => prev.filter(x => x.id !== id)), t.duration || 3500); }, []); return ( {children}
{items.map(t => (
{t.title &&
{t.title}
} {t.body &&
{t.body}
}
{t.action && ( )}
))}
); } function useToast() { return React.useContext(ToastCtx); } /* ---------- Empty / Loading / Error states ---------- */ function EmptyState({ eyebrow = "Nothing yet", title, body, action, illustration }) { return (
{illustration || (
)} {eyebrow}

{title}

{body &&

{body}

} {action &&
{action}
}
); } function LoadingState({ label = "Loading", body }) { return (
{label} {body &&

{body}

}
); } function ErrorState({ title = "Something went wrong", body, action }) { return (
Error

{title}

{body &&

{body}

} {action &&
{action}
}
); } function Skeleton({ w = "100%", h = 14, radius = 2, circle, style }) { return ( ); } /* ---------- ErrorBoundary ---------- */ class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, info) { if (this.props.onError) this.props.onError(error, info); } reset = () => this.setState({ hasError: false, error: null }); render() { if (this.state.hasError) { if (this.props.fallback) { return typeof this.props.fallback === "function" ? this.props.fallback({ error: this.state.error, reset: this.reset }) : this.props.fallback; } return (
Component error
{String(this.state.error?.message || this.state.error || "Something went wrong")}
); } return this.props.children; } } /* ---------- Banner ---------- */ function Banner({ tone = "info", title, body, action, onClose }) { const tones = { info: { color: "var(--info)", bg: "var(--info-bg)" }, pos: { color: "var(--pos)", bg: "var(--pos-bg)" }, warn: { color: "var(--warn)", bg: "var(--warn-bg)" }, neg: { color: "var(--neg)", bg: "var(--neg-bg)" }, grad: { color: "white", bg: "var(--brand-grad-band)" }, }; const t = tones[tone]; return (
{title &&
{title}
} {body &&
{body}
}
{action} {onClose && ( )}
); } Object.assign(window, { Modal, Drawer, Popover, PopoverItem, ToastHost, useToast, EmptyState, LoadingState, ErrorState, Skeleton, Banner, ErrorBoundary });