diff --git a/comps/list/Tree.tsx b/comps/list/Tree.tsx new file mode 100755 index 0000000..eadc5ea --- /dev/null +++ b/comps/list/Tree.tsx @@ -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 = ({ + tree, + renderRow, + className, + onChange, + drag, +}: { + className: string; + tree: T[]; + drag?: boolean; + onChange?: (rows: T[]) => Promise | void; + renderRow: (arg: { row: T; style: any; dragHandle: any }) => ReactNode; +}) => { + const local = useLocal({ + update: {} as Record, + 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 { + 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 && ( + + )} +
+ ); +}; + +const formatTree = (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 = (arg: { + renderRow: (arg: { row: T; style: any; dragHandle: any }) => ReactNode; + updateNode: (data: T, new_data: Partial) => 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); + } + + return renderRow({ row: node.data, style, dragHandle }); + }) as ElementType>; +}; diff --git a/exports.tsx b/exports.tsx index 7963f16..45a2fbe 100755 --- a/exports.tsx +++ b/exports.tsx @@ -14,3 +14,4 @@ export { Breadcrumb } from "./comps/custom/Breadcrumb"; export { Header } from "./comps/custom/Header"; export { TableList } from "./comps/custom/TableList"; export { Carousel } from "./comps/custom/Carousel"; +export { Tree } from "./comps/list/Tree";