import { X } from "lucide-react" import { AnimatePresence, motion } from "motion/react" import type { ReactNode } from "react" import { useEffect, useState } from "react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Sheet, SheetContent, SheetTitle } from "@/components/ui/sheet" import { useIsMobile } from "@/hooks/useMobile" import { duration, easeEmphasized, easeStandard } from "@/lib/motion" import { cn } from "@/lib/utils" /** Same grammar as the shared `slideUp`, on the axis this panel travels. */ const panelSlide = { hidden: { opacity: 0, x: 16 }, visible: { opacity: 1, x: 0, transition: { duration: duration.base, ease: easeEmphasized }, }, exit: { opacity: 0, x: 16, transition: { duration: duration.fast, ease: easeStandard }, }, } export const PANEL_SECTION = "text-xs font-medium uppercase tracking-[0.5px] text-muted-foreground" /** * What the thing in this panel is called, and the one place to rename it. * * Renaming is an explicit act: the field keeps what is typed until it is * confirmed, so a half-typed name never reaches the canvas. */ export function PanelTitle({ value, placeholder, label, onConfirm, }: { value: string placeholder?: string /** Names the field for screen readers. */ label: string onConfirm: (next: string) => void }) { const [draft, setDraft] = useState(value) // Another thing to name is another field. useEffect(() => setDraft(value), [value]) const next = draft.trim() const changed = next !== value return ( <> setDraft(event.target.value)} onKeyDown={(event) => { if (event.key === "Enter" && changed) onConfirm(next) }} /> {changed ? ( ) : null} > ) } /** * The editor's settings panel: floating over the canvas so the graph stays * visible and running behind it, a full-screen sheet where there is no room * for that. * * Node settings and flow settings share it, so the two read as one surface. */ export function SidePanel({ open, label, testId, bodyKey, expanded = false, header, footer, children, onClose, }: { open: boolean /** Names the panel for screen readers. */ label: string testId: string /** Remounts the contents when the thing being edited changes. */ bodyKey: string /** Take the width a code editor needs, still floating over the canvas. */ expanded?: boolean header: ReactNode footer?: ReactNode children: ReactNode onClose: () => void }) { const isMobile = useIsMobile() useEffect(() => { if (!open) return const onKey = (event: KeyboardEvent) => { if (event.key === "Escape") onClose() } window.addEventListener("keydown", onKey) return () => window.removeEventListener("keydown", onKey) }, [open, onClose]) const contents = ( <>