aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/components/dashboard
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/components/dashboard')
-rw-r--r--apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx7
-rw-r--r--apps/web/components/dashboard/lists/ListOptions.tsx8
-rw-r--r--apps/web/components/dashboard/sidebar/AllLists.tsx107
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>
);
}