aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2024-04-19 00:09:27 +0100
committerGitHub <noreply@github.com>2024-04-19 00:09:27 +0100
commite0999f701cd1834c3d940113cd8dd5247c5fe95f (patch)
treec4169a564ecd3f933e711bcc8ef7db20532174ea /apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx
parentdeba31ee010f785a9739fd4df8a64a3056c9593d (diff)
downloadkarakeep-e0999f701cd1834c3d940113cd8dd5247c5fe95f.tar.zst
feature: Nested lists (#110). Fixes #62
* feature: Add support for nested lists * prevent moving the parent to a subtree
Diffstat (limited to 'apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx')
-rw-r--r--apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx111
1 files changed, 111 insertions, 0 deletions
diff --git a/apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx b/apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx
new file mode 100644
index 00000000..da1b7408
--- /dev/null
+++ b/apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx
@@ -0,0 +1,111 @@
+import { useEffect, useState } from "react";
+import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
+import { FullPageSpinner } from "@/components/ui/full-page-spinner";
+
+import {
+ augmentBookmarkListsWithInitialData,
+ useBookmarkLists,
+} from "@hoarder/shared-react/hooks/lists";
+import { ZBookmarkList } from "@hoarder/shared/types/lists";
+import { ZBookmarkListTreeNode } from "@hoarder/shared/utils/listUtils";
+
+type RenderFunc = (params: {
+ item: ZBookmarkListTreeNode;
+ level: number;
+ open: boolean;
+}) => React.ReactNode;
+
+type IsOpenFunc = (list: ZBookmarkListTreeNode) => boolean;
+
+function ListItem({
+ node,
+ render,
+ level,
+ className,
+ isOpenFunc,
+}: {
+ node: ZBookmarkListTreeNode;
+ render: RenderFunc;
+ isOpenFunc: IsOpenFunc;
+ level: number;
+ className?: string;
+}) {
+ // Not the most efficient way to do this, but it works for now
+ const isAnyChildOpen = (
+ node: ZBookmarkListTreeNode,
+ isOpenFunc: IsOpenFunc,
+ ): boolean => {
+ if (isOpenFunc(node)) {
+ return true;
+ }
+ return node.children.some((l) => isAnyChildOpen(l, isOpenFunc));
+ };
+ const [open, setOpen] = useState(false);
+ useEffect(() => {
+ setOpen((curr) => curr || isAnyChildOpen(node, isOpenFunc));
+ }, [node, isOpenFunc]);
+
+ return (
+ <Collapsible open={open} onOpenChange={setOpen} className={className}>
+ {render({
+ item: node,
+ level,
+ open,
+ })}
+ <CollapsibleContent>
+ {node.children.map((l) => (
+ <ListItem
+ isOpenFunc={isOpenFunc}
+ key={l.item.id}
+ node={l}
+ render={render}
+ level={level + 1}
+ className={className}
+ />
+ ))}
+ </CollapsibleContent>
+ </Collapsible>
+ );
+}
+
+export function CollapsibleBookmarkLists({
+ render,
+ initialData,
+ className,
+ isOpenFunc,
+}: {
+ initialData?: ZBookmarkList[];
+ render: RenderFunc;
+ isOpenFunc?: IsOpenFunc;
+ className?: string;
+}) {
+ let { data } = useBookmarkLists(undefined, {
+ initialData: initialData ? { lists: initialData } : undefined,
+ });
+
+ // TODO: This seems to be a bug in react query
+ if (initialData) {
+ data = augmentBookmarkListsWithInitialData(data, initialData);
+ }
+
+ if (!data) {
+ return <FullPageSpinner />;
+ }
+
+ const { root } = data;
+
+ return (
+ <div>
+ {Object.values(root).map((l) => (
+ <ListItem
+ key={l.item.id}
+ node={l}
+ render={render}
+ level={0}
+ className={className}
+ isOpenFunc={isOpenFunc ?? (() => false)}
+ />
+ ))}
+ </div>
+ );
+}