diff options
Diffstat (limited to 'apps/web/components/dashboard')
3 files changed, 80 insertions, 42 deletions
diff --git a/apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx b/apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx index 77a67f5f..522bb1d6 100644 --- a/apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx +++ b/apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx @@ -1,6 +1,8 @@ import { useEffect, useState } from "react"; import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible"; import { FullPageSpinner } from "@/components/ui/full-page-spinner"; +import { api } from "@/lib/trpc"; +import { keepPreviousData } from "@tanstack/react-query"; import { augmentBookmarkListsWithInitialData, @@ -13,6 +15,7 @@ type RenderFunc = (params: { item: ZBookmarkListTreeNode; level: number; open: boolean; + numBookmarks?: number; }) => React.ReactNode; type IsOpenFunc = (list: ZBookmarkListTreeNode) => boolean; @@ -44,6 +47,9 @@ function ListItem({ useEffect(() => { setOpen((curr) => curr || isAnyChildOpen(node, isOpenFunc)); }, [node, isOpenFunc]); + const { data: listStats } = api.lists.stats.useQuery(undefined, { + placeholderData: keepPreviousData, + }); return ( <Collapsible open={open} onOpenChange={setOpen} className={className}> @@ -51,6 +57,7 @@ function ListItem({ item: node, level, open, + numBookmarks: listStats?.stats.get(node.item.id), })} <CollapsibleContent> {node.children diff --git a/apps/web/components/dashboard/lists/ListOptions.tsx b/apps/web/components/dashboard/lists/ListOptions.tsx index a7217954..0e24d6a2 100644 --- a/apps/web/components/dashboard/lists/ListOptions.tsx +++ b/apps/web/components/dashboard/lists/ListOptions.tsx @@ -1,5 +1,3 @@ -"use client"; - import { useState } from "react"; import { DropdownMenu, @@ -17,8 +15,12 @@ import DeleteListConfirmationDialog from "./DeleteListConfirmationDialog"; export function ListOptions({ list, + isOpen, + onOpenChange, children, }: { + isOpen?: boolean; + onOpenChange?: (open: boolean) => void; list: ZBookmarkList; children?: React.ReactNode; }) { @@ -29,7 +31,7 @@ export function ListOptions({ const [editModalOpen, setEditModalOpen] = useState(false); return ( - <DropdownMenu> + <DropdownMenu open={isOpen} onOpenChange={onOpenChange}> <EditListModal open={newNestedListModalOpen} setOpen={setNewNestedListModalOpen} diff --git a/apps/web/components/dashboard/sidebar/AllLists.tsx b/apps/web/components/dashboard/sidebar/AllLists.tsx index 7341e118..5b4b12bc 100644 --- a/apps/web/components/dashboard/sidebar/AllLists.tsx +++ b/apps/web/components/dashboard/sidebar/AllLists.tsx @@ -1,12 +1,14 @@ "use client"; -import { useCallback } from "react"; +import { useCallback, useState } from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import SidebarItem from "@/components/shared/sidebar/SidebarItem"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { CollapsibleTriggerTriangle } from "@/components/ui/collapsible"; import { useTranslation } from "@/lib/i18n/client"; +import { cn } from "@/lib/utils"; import { MoreHorizontal, Plus } from "lucide-react"; import type { ZBookmarkList } from "@hoarder/shared/types/lists"; @@ -27,6 +29,9 @@ export default function AllLists({ (node: ZBookmarkListTreeNode) => pathName.includes(node.item.id), [pathName], ); + + const [selectedListId, setSelectedListId] = useState<string | null>(null); + return ( <ul className="max-h-full gap-y-2 overflow-auto text-sm font-medium"> <li className="flex justify-between pb-2 font-bold"> @@ -49,45 +54,69 @@ export default function AllLists({ path={`/dashboard/favourites`} linkClassName="py-0.5" /> + <CollapsibleBookmarkLists + initialData={initialData.lists} + isOpenFunc={isNodeOpen} + render={({ item: node, level, open, numBookmarks }) => ( + <SidebarItem + collapseButton={ + node.children.length > 0 && ( + <CollapsibleTriggerTriangle + className="absolute left-0 top-1/2 size-2 -translate-y-1/2" + open={open} + /> + ) + } + logo={ + <span className="flex"> + <span className="text-lg"> {node.item.icon}</span> + </span> + } + name={node.item.name} + path={`/dashboard/lists/${node.item.id}`} + right={ + <ListOptions + onOpenChange={(open) => { + if (open) { + setSelectedListId(node.item.id); + } else { + setSelectedListId(null); + } + }} + list={node.item} + > + <Button size="none" variant="ghost"> + <div className="relative"> + <MoreHorizontal + className={cn( + "absolute inset-0 m-auto size-4 opacity-0 transition-opacity duration-100 group-hover:opacity-100", + selectedListId == node.item.id + ? "opacity-100" + : "opacity-0", + )} + /> - { - <CollapsibleBookmarkLists - initialData={initialData.lists} - isOpenFunc={isNodeOpen} - render={({ item: node, level, open }) => ( - <SidebarItem - collapseButton={ - node.children.length > 0 && ( - <CollapsibleTriggerTriangle - className="absolute left-0 top-1/2 size-2 -translate-y-1/2" - open={open} - /> - ) - } - logo={ - <span className="flex"> - <span className="text-lg"> {node.item.icon}</span> - </span> - } - name={node.item.name} - path={`/dashboard/lists/${node.item.id}`} - right={ - <ListOptions list={node.item}> - <Button - size="none" - variant="ghost" - className="invisible group-hover:visible" - > - <MoreHorizontal className="size-4" /> - </Button> - </ListOptions> - } - linkClassName="group py-0.5" - style={{ marginLeft: `${level * 1}rem` }} - /> - )} - /> - } + <Badge + variant="outline" + className={cn( + "opacity-100 transition-opacity duration-100 group-hover:opacity-0", + selectedListId == node.item.id || + numBookmarks === undefined + ? "opacity-0" + : "opacity-100", + )} + > + {numBookmarks} + </Badge> + </div> + </Button> + </ListOptions> + } + linkClassName="group py-0.5" + style={{ marginLeft: `${level * 1}rem` }} + /> + )} + /> </ul> ); } |
