import { useMemo, useState, type ReactNode } from "react"; import { cn } from "./cn.js"; export interface InspectorSectionSpec { id: string; label: string; description?: string; group?: string; content: ReactNode; disabled?: boolean; tone?: "default" | "accent"; } export interface InspectorProps { sections: InspectorSectionSpec[]; defaultOpen?: string[]; activeId?: string; singleOpen?: boolean; className?: string; onActiveChange?: (id: string) => void; } export function Inspector({ sections, defaultOpen = [], activeId, singleOpen = false, className, onActiveChange, }: InspectorProps) { const [openIds, setOpenIds] = useState(() => new Set(defaultOpen)); const groups = useMemo(() => { const result: Array<{ label?: string; sections: InspectorSectionSpec[] }> = []; sections.forEach((section) => { const previous = result[result.length - 1]; if (previous && previous.label === section.group) { previous.sections.push(section); } else { result.push({ label: section.group, sections: [section] }); } }); return result; }, [sections]); const toggle = (id: string) => { setOpenIds((current) => { const next = singleOpen ? new Set() : new Set(current); if (current.has(id)) next.delete(id); else next.add(id); return next; }); onActiveChange?.(id); }; return (
{groups.map((group, groupIndex) => (
{group.sections.map((section) => { const isOpen = openIds.has(section.id); return (
{isOpen ?
{section.content}
: null}
); })}
))}
); } export interface ControlRowProps { label: ReactNode; children: ReactNode; layout?: "inline" | "stack"; className?: string; } export function ControlRow({ label, children, layout = "inline", className }: ControlRowProps) { return (
{label}
{children}
); }