85 lines
3.0 KiB
TypeScript
85 lines
3.0 KiB
TypeScript
import type { CSSProperties, ReactNode } from "react";
|
|
|
|
export type ConnectionTreeColumn<T> = {
|
|
id: string;
|
|
header: ReactNode;
|
|
width?: string;
|
|
align?: "left" | "center" | "right";
|
|
render: (row: T, depth: number) => ReactNode;
|
|
};
|
|
|
|
export type ConnectionTreeProps<T> = {
|
|
rows: T[];
|
|
columns: ConnectionTreeColumn<T>[];
|
|
getRowKey: (row: T) => string;
|
|
getChildren?: (row: T) => T[];
|
|
renderActions?: (row: T, depth: number) => ReactNode;
|
|
emptyText?: ReactNode;
|
|
className?: string;
|
|
rowClassName?: (row: T, depth: number) => string;
|
|
};
|
|
|
|
type FlatRow<T> = {
|
|
row: T;
|
|
depth: number;
|
|
};
|
|
|
|
export default function ConnectionTree<T>({
|
|
rows,
|
|
columns,
|
|
getRowKey,
|
|
getChildren,
|
|
renderActions,
|
|
emptyText = "i18n:govoplan-core.no_entries_configured.48e7b97c",
|
|
className = "",
|
|
rowClassName
|
|
}: ConnectionTreeProps<T>) {
|
|
const flatRows = flattenRows(rows, getChildren);
|
|
const gridTemplateColumns = `${columns.map((column) => column.width || "minmax(0, 1fr)").join(" ")} ${renderActions ? "var(--connection-tree-actions-column, 196px)" : ""}`.trim();
|
|
|
|
return (
|
|
<div className={`connection-tree ${className}`.trim()}>
|
|
<div className="connection-tree-header" style={{ gridTemplateColumns }}>
|
|
{columns.map((column) =>
|
|
<div className={`connection-tree-cell ${column.align ? `align-${column.align}` : ""}`.trim()} key={column.id}>
|
|
{column.header}
|
|
</div>
|
|
)}
|
|
{renderActions && <div className="connection-tree-cell align-right">i18n:govoplan-core.actions.c3cd636a</div>}
|
|
</div>
|
|
{flatRows.length === 0 && <div className="connection-tree-empty">{emptyText}</div>}
|
|
{flatRows.map(({ row, depth }) => {
|
|
const className = ["connection-tree-row", depth > 0 ? "is-child" : "is-parent", rowClassName?.(row, depth) || ""].filter(Boolean).join(" ");
|
|
return (
|
|
<div className={className} style={{ gridTemplateColumns }} key={getRowKey(row)}>
|
|
{columns.map((column, index) =>
|
|
<div
|
|
className={`connection-tree-cell ${column.align ? `align-${column.align}` : ""} ${index === 0 ? "connection-tree-primary-cell" : ""}`.trim()}
|
|
key={column.id}>
|
|
{index === 0 ?
|
|
<div className="connection-tree-primary-content" style={{ "--connection-tree-depth": String(depth) } as CSSProperties}>
|
|
{column.render(row, depth)}
|
|
</div> :
|
|
column.render(row, depth)
|
|
}
|
|
</div>
|
|
)}
|
|
{renderActions && <div className="connection-tree-cell align-right connection-tree-actions">{renderActions(row, depth)}</div>}
|
|
</div>);
|
|
|
|
})}
|
|
</div>);
|
|
|
|
}
|
|
|
|
function flattenRows<T>(rows: T[], getChildren?: (row: T) => T[]): FlatRow<T>[] {
|
|
const result: FlatRow<T>[] = [];
|
|
for (const row of rows) {
|
|
result.push({ row, depth: 0 });
|
|
for (const child of getChildren?.(row) ?? []) {
|
|
result.push({ row: child, depth: 1 });
|
|
}
|
|
}
|
|
return result;
|
|
}
|