This commit is contained in:
rizrmd 2024-03-13 07:09:51 -07:00
parent af13954707
commit 90563c20ed
2 changed files with 155 additions and 0 deletions

154
comps/list/Tree.tsx Executable file
View File

@ -0,0 +1,154 @@
import { useLocal } from "@/utils/use-local";
import { ElementType, FC, ReactElement, ReactNode, useEffect } from "react";
import { NodeRendererProps, Tree as Arborist, NodeApi } from "react-arborist";
export type TreeItem = {
id: string;
text: string;
id_parent?: string;
sort_idx?: number;
children?: TreeItem[];
};
export const Tree = <T extends TreeItem>({
tree,
renderRow,
className,
onChange,
drag,
}: {
className: string;
tree: T[];
drag?: boolean;
onChange?: (rows: T[]) => Promise<void> | void;
renderRow: (arg: { row: T; style: any; dragHandle: any }) => ReactNode;
}) => {
const local = useLocal({
update: {} as Record<string, T>,
timeout: null as any,
children: generateTreeChildren({
renderRow,
updateNode: (data, arg) => {
if (typeof arg.sort_idx === "number") {
data.sort_idx = arg.sort_idx;
}
local.update[data.id] = { ...data };
clearTimeout(local.timeout);
local.timeout = setTimeout(async () => {
if (onChange) {
await onChange(Object.values(local.update));
local.update = {};
}
}, 300);
},
}),
tree: formatTree(tree),
width: 0,
height: 0,
rob: new ResizeObserver(([el]) => {
local.width = el.contentRect.width;
local.height = el.contentRect.height;
local.render();
}),
});
useEffect(() => {
return () => {
local.rob.disconnect();
};
}, []);
return (
<div
className={cx(
className,
css`
position: relative;
> div {
position: absolute;
left: 0;
bottom: 0;
right: 0;
top: 0;
}
`
)}
ref={(el) => {
if (el) {
local.rob.observe(el);
const bound = el.getBoundingClientRect();
if (local.height === 0) {
local.width = bound.width;
local.height = bound.height;
local.render();
}
}
}}
>
{local.width && local.height && (
<Arborist
initialData={local.tree}
width={local.width}
height={local.height}
disableDrag={drag === false}
children={local.children}
/>
)}
</div>
);
};
const formatTree = <T extends TreeItem>(tree: T[]) => {
const res: T[] = [];
const ftree = tree
.filter((e) => e.id)
.sort((a, b) => (a.sort_idx || 0) - (b.sort_idx || 0))
.map((e) => ({
...e,
id: e.id,
text: e.text,
children: e.children,
id_parent: e.id_parent || null,
sort_idx: e.sort_idx || "0",
})) as TreeItem[];
const scan = (id_parent: null | string, parent: T) => {
for (const s of ftree) {
if (s.id_parent === id_parent) {
if (!parent.children) {
parent.children = [];
}
parent.children.push(s);
scan(s.id, s as T);
}
}
};
scan(null, {
id: "root",
id_parent: "",
sort_idx: 0,
text: "",
children: res as TreeItem[],
} as T);
return res;
};
const generateTreeChildren = <T extends TreeItem>(arg: {
renderRow: (arg: { row: T; style: any; dragHandle: any }) => ReactNode;
updateNode: (data: T, new_data: Partial<T>) => void;
}) => {
const { renderRow, updateNode } = arg;
return (({ node, style, dragHandle }) => {
let sort_idx = node.data.sort_idx;
if (typeof sort_idx === "string") sort_idx = parseInt(sort_idx);
if (!node.isDragging && sort_idx !== node.rowIndex) {
updateNode(node.data, { sort_idx: node.rowIndex } as Partial<T>);
}
return renderRow({ row: node.data, style, dragHandle });
}) as ElementType<NodeRendererProps<T>>;
};

View File

@ -14,3 +14,4 @@ export { Breadcrumb } from "./comps/custom/Breadcrumb";
export { Header } from "./comps/custom/Header"; export { Header } from "./comps/custom/Header";
export { TableList } from "./comps/custom/TableList"; export { TableList } from "./comps/custom/TableList";
export { Carousel } from "./comps/custom/Carousel"; export { Carousel } from "./comps/custom/Carousel";
export { Tree } from "./comps/list/Tree";