/** * @file Category tree component for hierarchical category navigation */ import * as React from "react"; import type { NestedNodeDefinitionCategory } from "../types"; import { CategoryIcon } from "./CategoryIcon"; import styles from "./CategoryTree.module.css"; export type CategoryTreeProps = { categories: NestedNodeDefinitionCategory[]; /** Selected category paths (empty set means "All" is selected) */ selectedPaths: Set; /** * Called when a category is clicked. * @param path - The clicked category path (null for "All") * @param multiSelect - Whether this is a multi-select action (Cmd/Ctrl+click) */ onSelect: (path: string | null, multiSelect: boolean) => void; expandedPaths: Set; onToggle: (path: string) => void; }; type CategoryTreeItemProps = { category: NestedNodeDefinitionCategory; isSelected: boolean; isExpanded: boolean; onSelect: (path: string, multiSelect: boolean) => void; onToggle: (path: string) => void; selectedPaths: Set; expandedPaths: Set; }; const CategoryTreeItem: React.FC = ({ category, isSelected, isExpanded, onSelect, onToggle, selectedPaths, expandedPaths, }) => { const hasChildren = category.children.length > 0; const handleClick = React.useCallback( (e: React.MouseEvent) => { const isMultiSelect = e.metaKey || e.ctrlKey; onSelect(category.path, isMultiSelect); }, [category.path, onSelect], ); const handleToggle = React.useCallback( (e: React.MouseEvent) => { e.stopPropagation(); onToggle(category.path); }, [category.path, onToggle], ); return (
{hasChildren ? ( ) : ( )} {category.icon != null && } {category.name} {category.totalNodeCount}
{hasChildren && isExpanded ? (
{category.children.map((child) => ( ))}
) : null}
); }; CategoryTreeItem.displayName = "CategoryTreeItem"; export const CategoryTree: React.FC = ({ categories, selectedPaths, onSelect, expandedPaths, onToggle, }) => { const totalNodeCount = React.useMemo(() => { return categories.reduce((sum, c) => sum + c.totalNodeCount, 0); }, [categories]); const handleAllClick = React.useCallback( (e: React.MouseEvent) => { const isMultiSelect = e.metaKey || e.ctrlKey; onSelect(null, isMultiSelect); }, [onSelect], ); const handleCategorySelect = React.useCallback( (path: string, multiSelect: boolean) => { onSelect(path, multiSelect); }, [onSelect], ); // "All" is selected when no specific categories are selected const isAllSelected = selectedPaths.size === 0; return (
All {totalNodeCount}
{categories.map((category) => ( ))}
); }; CategoryTree.displayName = "CategoryTree";