/** * @file Node list pane component for displaying nodes in a category */ import * as React from "react"; import type { NodeDefinition } from "../../../../types/NodeDefinition"; import { useI18n } from "../../../../i18n/context"; import { NodeCard } from "../../../node/cards/NodeCard"; import { PaneHeader } from "./PaneHeader"; import styles from "./NodeListPane.module.css"; export type NodeListPaneProps = { title: string; nodes: NodeDefinition[]; selectedNodeIndex: number; onNodeSelect: (nodeType: string) => void; onNodeHover: (index: number) => void; disabledNodeTypes: Set; nodeIndexByType: Map; /** When provided, nodes NOT in this set are shown as non-matching (dimmed) */ matchingNodeTypes?: Set; }; export const NodeListPane: React.FC = ({ title, nodes, selectedNodeIndex, onNodeSelect, onNodeHover, disabledNodeTypes, nodeIndexByType, matchingNodeTypes, }) => { const { t } = useI18n(); const handleNodeClick = React.useCallback( (node: NodeDefinition) => { if (!disabledNodeTypes.has(node.type)) { onNodeSelect(node.type); } }, [disabledNodeTypes, onNodeSelect], ); return (
{title} {nodes.length}
{nodes.map((node) => { const globalIndex = nodeIndexByType.get(node.type) ?? -1; const isSelected = globalIndex === selectedNodeIndex; const isDisabled = disabledNodeTypes.has(node.type); const isNonMatching = matchingNodeTypes !== undefined && !matchingNodeTypes.has(node.type); return ( handleNodeClick(node)} onPointerEnter={() => { if (globalIndex >= 0) { onNodeHover(globalIndex); } }} role="menuitem" tabIndex={-1} /> ); })} {nodes.length === 0 ?
{t("nodeSearchEmptyCategory")}
: null}
); }; NodeListPane.displayName = "NodeListPane";