"use client"; import { useState, type ReactNode } from "react"; import { cn } from "@/shared/utils/cn"; interface CollapsibleProps { /** Header content (always visible, click-to-toggle). */ title: ReactNode; /** Optional secondary line under the title. */ subtitle?: ReactNode; /** Material symbol name shown left of the title. */ icon?: string; /** Trailing element rendered next to the chevron (badges, op counts, etc). */ trailing?: ReactNode; /** Whether the section is open on first render. Defaults to false. */ defaultOpen?: boolean; /** Visual variant. `default` for top-level sections; `inline` for nested rows. */ variant?: "default" | "inline"; /** Custom class for the wrapper. */ className?: string; /** Content rendered when expanded. */ children: ReactNode; } /** * Minimal click-to-expand section. Stateless from the caller's perspective * (open/closed lives in local state — does NOT survive page refresh, per the * UX brief). Uses material-symbols-outlined chevrons to match the rest of * the OmniRoute UI. */ export default function Collapsible({ title, subtitle, icon, trailing, defaultOpen = false, variant = "default", className, children, }: CollapsibleProps) { const [open, setOpen] = useState(defaultOpen); const wrapperClasses = cn( variant === "default" ? "rounded-lg border border-black/5 dark:border-white/5 bg-surface" : "rounded-md border border-black/5 dark:border-white/5 bg-black/[0.02] dark:bg-white/[0.02]", className ); const headerRowClasses = cn( "flex items-center gap-3", variant === "default" ? "p-4" : "p-3", "hover:bg-black/[0.02] dark:hover:bg-white/[0.02] transition-colors", open && "border-b border-black/5 dark:border-white/5" ); // The chevron + title region is the click target. Trailing interactive // controls (Toggle, Button) live OUTSIDE the toggle button so we never nest // {trailing &&
{trailing}
} {open &&
{children}
} ); }