aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx
blob: da1b7408d9c2148e808aa8b14d9c87edde1a43e7 (plain) (blame)
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
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>
  );
}