// Sidebar — search + collapsible tree, dense Windows-explorer style.
const Sidebar = ({ tree, activeNodeId, expanded, setExpanded, onNodeClick, query, setQuery }) => {
return (
{/* Search bar */}
{/* Tree */}
{tree.map((node) => (
))}
);
};
const matches = (node, q) => {
if (!q) return true;
const t = q.toLowerCase();
if ((node.label || "").toLowerCase().includes(t)) return true;
if (node.children) return node.children.some((c) => matches(c, q));
return false;
};
const TreeNode = ({ node, depth, activeNodeId, expanded, setExpanded, onNodeClick, query }) => {
const hasChildren = node.children && node.children.length > 0;
const isExpanded = expanded[node.id] || (query && matches(node, query) && hasChildren);
const isActive = activeNodeId === node.id;
const visible = matches(node, query);
if (!visible) return null;
const click = () => {
if (hasChildren) {
setExpanded((s) => ({ ...s, [node.id]: !isExpanded }));
}
if (node.leaf || !hasChildren) {
onNodeClick(node);
}
};
return (
{ if (!isActive) e.currentTarget.style.background = "var(--bg-row-hover)"; }}
onMouseLeave={(e) => { if (!isActive) e.currentTarget.style.background = "transparent"; }}
>
{hasChildren ? (
{isExpanded ? : }
) : (
)}
{node.label}
{isExpanded && hasChildren ? (
{node.children.map((child) => (
))}
) : null}
);
};
window.Sidebar = Sidebar;