Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | /**
* Navigator — tree view for hierarchical data navigation.
* Typically placed on the left side of an Explorer.
*/
import {Tree, type TreeNode} from '../../primereact/index.js';
import React, {useState} from 'react';
interface INavigatorProps {
/** Fetch function returning tree nodes */
fetch?: () => Promise<Record<string, unknown[]>>;
/** Called with the selected node value */
onSelect?: (node: Record<string, unknown> | null) => void;
keyField?: string;
parentField?: string;
field?: string;
title?: string;
/** Expected key in fetch result */
resultSet?: string;
/** Data passed directly (alternative to fetch) */
data?: Record<string, unknown>[];
className?: string;
}
function buildTree(
items: Record<string, unknown>[],
keyField: string,
parentField: string,
labelField: string,
): TreeNode[] {
const roots: TreeNode[] = [];
const map = new Map<unknown, TreeNode>();
for (const item of items) {
const node: TreeNode = {
key: String(item[keyField]),
label: String(item[labelField] ?? item[keyField]),
data: item,
children: [],
};
map.set(item[keyField], node);
}
for (const item of items) {
const parentKey = item[parentField];
const node = map.get(item[keyField])!;
if (parentKey == null || !map.has(parentKey)) {
roots.push(node);
} else {
const parent = map.get(parentKey)!;
parent.children = [...(parent.children ?? []), node];
}
}
return roots;
}
export function Navigator({
data: staticData,
fetch: fetchFn,
onSelect,
keyField = 'id',
parentField = 'parentId',
field = 'name',
title,
resultSet,
className,
}: INavigatorProps) {
const [treeData, setTreeData] = useState<TreeNode[]>(() => {
if (staticData) return buildTree(staticData, keyField, parentField, field);
return [];
});
const [loading, setLoading] = useState(false);
const [selectedKey, setSelectedKey] = useState<string | null>(null);
React.useEffect(() => {
if (!fetchFn) return;
// eslint-disable-next-line @eslint-react/set-state-in-effect
setLoading(true);
void fetchFn()
.then(result => {
const items = resultSet
? (result[resultSet] as Record<string, unknown>[])
: (Object.values(result)[0] as Record<string, unknown>[]);
setTreeData(buildTree(items ?? [], keyField, parentField, field));
})
.finally(() => setLoading(false));
}, [fetchFn, keyField, parentField, field, resultSet]);
return (
<div className={`blong-navigator ${className ?? ''}`}>
{title && <h4 className="blong-navigator__title">{title}</h4>}
<Tree
value={treeData}
loading={loading}
selectionKeys={selectedKey ?? undefined}
onSelectionChange={e => {
const key = e.value as string;
setSelectedKey(key);
// Find the node data
const findNode = (nodes: TreeNode[]): TreeNode | undefined => {
for (const n of nodes) {
if (n.key === key) return n;
const found = findNode(n.children ?? []);
if (found) return found;
}
};
const node = findNode(treeData);
onSelect?.((node?.data as Record<string, unknown>) ?? null);
}}
className="blong-navigator__tree"
filter
filterPlaceholder="Search..."
/>
</div>
);
}
|