diff options
| author | Mohamed Bassem <me@mbassem.com> | 2024-04-14 00:51:56 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-04-14 00:51:56 +0300 |
| commit | 4f17ea61cbb11a72712a1ea8c98904a1cc513e41 (patch) | |
| tree | 4f1dd775e25feb3495ddb208c5fe4aa03c66fe3a /apps/web/components/dashboard | |
| parent | cf0df0e6d84a76649d8cbf8adcbf83efb6e883ab (diff) | |
| download | karakeep-4f17ea61cbb11a72712a1ea8c98904a1cc513e41.tar.zst | |
feature(web): Allow changing the bookmark grid layout (#98)
Diffstat (limited to 'apps/web/components/dashboard')
10 files changed, 404 insertions, 182 deletions
diff --git a/apps/web/components/dashboard/bookmarks/AssetCard.tsx b/apps/web/components/dashboard/bookmarks/AssetCard.tsx index 8997a7e2..3bda1ee8 100644 --- a/apps/web/components/dashboard/bookmarks/AssetCard.tsx +++ b/apps/web/components/dashboard/bookmarks/AssetCard.tsx @@ -3,12 +3,48 @@ import Image from "next/image"; import { isBookmarkStillTagging } from "@/lib/bookmarkUtils"; import { api } from "@/lib/trpc"; -import { cn } from "@/lib/utils"; -import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; +import type { + ZBookmark, + ZBookmarkTypeAsset, +} from "@hoarder/trpc/types/bookmarks"; -import BookmarkActionBar from "./BookmarkActionBar"; -import TagList from "./TagList"; +import { BookmarkLayoutAdaptingCard } from "./BookmarkLayoutAdaptingCard"; + +function AssetImage({ + bookmark, + className, +}: { + bookmark: ZBookmarkTypeAsset; + className?: string; +}) { + const bookmarkedAsset = bookmark.content; + switch (bookmarkedAsset.assetType) { + case "image": { + return ( + <Image + alt="asset" + src={`/api/assets/${bookmarkedAsset.assetId}`} + fill={true} + className={className} + /> + ); + } + case "pdf": { + return ( + <iframe + title={bookmarkedAsset.assetId} + className={className} + src={`/api/assets/${bookmarkedAsset.assetId}`} + /> + ); + } + default: { + const _exhaustiveCheck: never = bookmarkedAsset.assetType; + return <span />; + } + } +} export default function AssetCard({ bookmark: initialData, @@ -35,49 +71,25 @@ export default function AssetCard({ }, }, ); - const bookmarkedAsset = bookmark.content; - if (bookmarkedAsset.type != "asset") { + + if (bookmark.content.type != "asset") { throw new Error("Unexpected bookmark type"); } + const bookmarkedAsset = { ...bookmark, content: bookmark.content }; + return ( - <div - className={cn( - className, - cn( - "flex h-min max-h-96 flex-col gap-y-1 overflow-hidden rounded-lg shadow-md", - ), - )} - > - {bookmarkedAsset.assetType == "image" && ( - <div className="relative h-56 max-h-56"> - <Image - alt="asset" - src={`/api/assets/${bookmarkedAsset.assetId}`} - fill={true} - className="rounded-t-lg object-cover" - /> + <BookmarkLayoutAdaptingCard + title={bookmarkedAsset.content.fileName} + footer={null} + bookmark={bookmarkedAsset} + className={className} + wrapTags={true} + image={(_layout, className) => ( + <div className="relative size-full flex-1"> + <AssetImage bookmark={bookmarkedAsset} className={className} /> </div> )} - {bookmarkedAsset.assetType == "pdf" && ( - <iframe - title={bookmarkedAsset.assetId} - className="h-56 max-h-56 w-full" - src={`/api/assets/${bookmarkedAsset.assetId}`} - /> - )} - <div className="flex flex-col gap-y-1 overflow-hidden p-2"> - <div className="flex h-full flex-wrap gap-1 overflow-hidden"> - <TagList - bookmark={bookmark} - loading={isBookmarkStillTagging(bookmark)} - /> - </div> - <div className="flex w-full justify-between"> - <div /> - <BookmarkActionBar bookmark={bookmark} /> - </div> - </div> - </div> + /> ); } diff --git a/apps/web/components/dashboard/bookmarks/BookmarkCardSkeleton.tsx b/apps/web/components/dashboard/bookmarks/BookmarkCardSkeleton.tsx deleted file mode 100644 index 026b8d37..00000000 --- a/apps/web/components/dashboard/bookmarks/BookmarkCardSkeleton.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { - ImageCard, - ImageCardBanner, - ImageCardBody, - ImageCardContent, - ImageCardFooter, - ImageCardTitle, -} from "@/components/ui/imageCard"; -import { Skeleton } from "@/components/ui/skeleton"; - -export default function BookmarkCardSkeleton() { - return ( - <ImageCard - className={ - "border-grey-100 border bg-gray-50 duration-300 ease-in hover:border-blue-300 hover:transition-all" - } - > - <ImageCardBanner src="/blur.avif" /> - <ImageCardContent> - <ImageCardTitle></ImageCardTitle> - <ImageCardBody className="space-y-2"> - <Skeleton className="h-4 w-full" /> - <Skeleton className="h-4 w-full" /> - <Skeleton className="h-4 w-full" /> - </ImageCardBody> - <ImageCardFooter></ImageCardFooter> - </ImageCardContent> - </ImageCard> - ); -} diff --git a/apps/web/components/dashboard/bookmarks/BookmarkLayoutAdaptingCard.tsx b/apps/web/components/dashboard/bookmarks/BookmarkLayoutAdaptingCard.tsx new file mode 100644 index 00000000..3d7b93f3 --- /dev/null +++ b/apps/web/components/dashboard/bookmarks/BookmarkLayoutAdaptingCard.tsx @@ -0,0 +1,131 @@ +import type { BookmarksLayoutTypes } from "@/lib/userLocalSettings/types"; +import React from "react"; +import Link from "next/link"; +import { isBookmarkStillTagging } from "@/lib/bookmarkUtils"; +import { + bookmarkLayoutSwitch, + useBookmarkLayout, +} from "@/lib/userLocalSettings/bookmarksLayout"; +import { cn } from "@/lib/utils"; +import dayjs from "dayjs"; + +import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; + +import BookmarkActionBar from "./BookmarkActionBar"; +import TagList from "./TagList"; + +interface Props { + bookmark: ZBookmark; + image: (layout: BookmarksLayoutTypes, className: string) => React.ReactNode; + title?: React.ReactNode; + content?: React.ReactNode; + footer?: React.ReactNode; + className?: string; + fitHeight?: boolean; + wrapTags: boolean; +} + +function BottomRow({ + footer, + bookmark, +}: { + footer?: React.ReactNode; + bookmark: ZBookmark; +}) { + return ( + <div className="justify flex w-full shrink-0 justify-between text-gray-500"> + <div className="flex items-center gap-2 overflow-hidden text-nowrap"> + {footer && <>{footer}•</>} + <Link href={`/dashboard/preview/${bookmark.id}`}> + {dayjs(bookmark.createdAt).format("MMM DD")} + </Link> + </div> + <BookmarkActionBar bookmark={bookmark} /> + </div> + ); +} + +function ListView({ + bookmark, + image, + title, + content, + footer, + className, +}: Props) { + return ( + <div + className={cn( + "flex max-h-96 gap-4 overflow-hidden rounded-lg p-2 shadow-md", + className, + )} + > + <div className="flex size-32 items-center justify-center overflow-hidden"> + {image("list", "object-cover rounded-lg size-32")} + </div> + <div className="flex h-full flex-1 flex-col justify-between gap-2 overflow-hidden"> + <div className="flex flex-col gap-2 overflow-hidden"> + {title && <div className="flex-none shrink-0 text-lg">{title}</div>} + {content && <div className="shrink-1 overflow-hidden">{content}</div>} + <div className="flex shrink-0 flex-wrap gap-1 overflow-hidden"> + <TagList + bookmark={bookmark} + loading={isBookmarkStillTagging(bookmark)} + /> + </div> + </div> + <BottomRow footer={footer} bookmark={bookmark} /> + </div> + </div> + ); +} + +function GridView({ + bookmark, + image, + title, + content, + footer, + className, + wrapTags, + layout, + fitHeight = false, +}: Props & { layout: BookmarksLayoutTypes }) { + const img = image("grid", "h-56 min-h-56 w-full object-cover rounded-t-lg"); + + return ( + <div + className={cn( + "flex flex-col overflow-hidden rounded-lg shadow-md", + className, + fitHeight && layout != "grid" ? "max-h-96" : "h-96", + )} + > + {img && <div className="h-56 w-full shrink-0 overflow-hidden">{img}</div>} + <div className="flex h-full flex-col justify-between gap-2 overflow-hidden p-2"> + <div className="grow-1 flex flex-col gap-2 overflow-hidden"> + {title && <div className="flex-none shrink-0 text-lg">{title}</div>} + {content && <div className="shrink-1 overflow-hidden">{content}</div>} + <div className="flex shrink-0 flex-wrap gap-1 overflow-hidden"> + <TagList + className={wrapTags ? undefined : "h-full"} + bookmark={bookmark} + loading={isBookmarkStillTagging(bookmark)} + /> + </div> + </div> + <BottomRow footer={footer} bookmark={bookmark} /> + </div> + </div> + ); +} + +export function BookmarkLayoutAdaptingCard(props: Props) { + const layout = useBookmarkLayout(); + + return bookmarkLayoutSwitch(layout, { + masonry: <GridView layout={layout} {...props} />, + grid: <GridView layout={layout} {...props} />, + list: <ListView {...props} />, + }); +} diff --git a/apps/web/components/dashboard/bookmarks/BookmarksGrid.tsx b/apps/web/components/dashboard/bookmarks/BookmarksGrid.tsx index bace3435..01f18815 100644 --- a/apps/web/components/dashboard/bookmarks/BookmarksGrid.tsx +++ b/apps/web/components/dashboard/bookmarks/BookmarksGrid.tsx @@ -1,5 +1,9 @@ import { useMemo } from "react"; import { ActionButton } from "@/components/ui/action-button"; +import { + bookmarkLayoutSwitch, + useBookmarkLayout, +} from "@/lib/userLocalSettings/bookmarksLayout"; import tailwindConfig from "@/tailwind.config"; import { Slot } from "@radix-ui/react-slot"; import Masonry from "react-masonry-css"; @@ -36,13 +40,15 @@ function renderBookmark(bookmark: ZBookmark) { let comp; switch (bookmark.content.type) { case "link": - comp = <LinkCard bookmark={bookmark} />; + comp = <LinkCard bookmark={{ ...bookmark, content: bookmark.content }} />; break; case "text": - comp = <TextCard bookmark={bookmark} />; + comp = <TextCard bookmark={{ ...bookmark, content: bookmark.content }} />; break; case "asset": - comp = <AssetCard bookmark={bookmark} />; + comp = ( + <AssetCard bookmark={{ ...bookmark, content: bookmark.content }} /> + ); break; } return <BookmarkCard key={bookmark.id}>{comp}</BookmarkCard>; @@ -61,20 +67,36 @@ export default function BookmarksGrid({ isFetchingNextPage?: boolean; fetchNextPage?: () => void; }) { + const layout = useBookmarkLayout(); const breakpointConfig = useMemo(() => getBreakpointConfig(), []); + if (bookmarks.length == 0 && !showEditorCard) { return <p>No bookmarks</p>; } + + const children = [ + showEditorCard && ( + <BookmarkCard key={"editor"}> + <EditorCard /> + </BookmarkCard> + ), + ...bookmarks.map((b) => renderBookmark(b)), + ]; return ( <> - <Masonry className="flex gap-4" breakpointCols={breakpointConfig}> - {showEditorCard && ( - <BookmarkCard> - <EditorCard /> - </BookmarkCard> - )} - {bookmarks.map((b) => renderBookmark(b))} - </Masonry> + {bookmarkLayoutSwitch(layout, { + masonry: ( + <Masonry className="flex gap-4" breakpointCols={breakpointConfig}> + {children} + </Masonry> + ), + grid: ( + <Masonry className="flex gap-4" breakpointCols={breakpointConfig}> + {children} + </Masonry> + ), + list: <div className="grid grid-cols-1">{children}</div>, + })} {hasNextPage && ( <div className="flex justify-center"> <ActionButton diff --git a/apps/web/components/dashboard/bookmarks/EditorCard.tsx b/apps/web/components/dashboard/bookmarks/EditorCard.tsx index 10ad1f13..f6ea0c9a 100644 --- a/apps/web/components/dashboard/bookmarks/EditorCard.tsx +++ b/apps/web/components/dashboard/bookmarks/EditorCard.tsx @@ -7,6 +7,7 @@ import { Separator } from "@/components/ui/separator"; import { Textarea } from "@/components/ui/textarea"; import { toast } from "@/components/ui/use-toast"; import { useClientConfig } from "@/lib/clientConfig"; +import { useBookmarkLayoutSwitch } from "@/lib/userLocalSettings/bookmarksLayout"; import { cn } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; @@ -78,13 +79,19 @@ export default function EditorCard({ className }: { className?: string }) { variant: "destructive", }); }; + const cardHeight = useBookmarkLayoutSwitch({ + grid: "h-96", + masonry: "h-96", + list: undefined, + }); return ( <Form {...form}> <form className={cn( className, - "flex h-96 flex-col gap-2 rounded-xl bg-card p-4", + "flex flex-col gap-2 rounded-xl bg-card p-4", + cardHeight, )} onSubmit={form.handleSubmit(onSubmit, onError)} > diff --git a/apps/web/components/dashboard/bookmarks/LinkCard.tsx b/apps/web/components/dashboard/bookmarks/LinkCard.tsx index 9796ed4f..5c329424 100644 --- a/apps/web/components/dashboard/bookmarks/LinkCard.tsx +++ b/apps/web/components/dashboard/bookmarks/LinkCard.tsx @@ -2,30 +2,70 @@ import Link from "next/link"; import { - ImageCard, - ImageCardBanner, - ImageCardBody, - ImageCardContent, - ImageCardFooter, - ImageCardTitle, -} from "@/components/ui/imageCard"; -import { isBookmarkStillCrawling, isBookmarkStillLoading, - isBookmarkStillTagging, } from "@/lib/bookmarkUtils"; import { api } from "@/lib/trpc"; -import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; +import type { ZBookmarkTypeLink } from "@hoarder/trpc/types/bookmarks"; + +import { BookmarkLayoutAdaptingCard } from "./BookmarkLayoutAdaptingCard"; + +function LinkTitle({ bookmark }: { bookmark: ZBookmarkTypeLink }) { + const link = bookmark.content; + const parsedUrl = new URL(link.url); + return ( + <Link className="line-clamp-2" href={link.url} target="_blank"> + {link?.title ?? parsedUrl.host} + </Link> + ); +} + +function LinkImage({ + bookmark, + className, +}: { + bookmark: ZBookmarkTypeLink; + className?: string; +}) { + const link = bookmark.content; + + // A dummy white pixel for when there's no image. + // TODO: Better handling for cards with no images + const image = + link.imageUrl ?? + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdj+P///38ACfsD/QVDRcoAAAAASUVORK5CYII="; + return ( + <Link href={link.url} target="_blank"> + {/* eslint-disable-next-line @next/next/no-img-element */} + <img + className={className} + alt="card banner" + src={isBookmarkStillCrawling(bookmark) ? "/blur.avif" : image} + /> + </Link> + ); +} -import BookmarkActionBar from "./BookmarkActionBar"; -import TagList from "./TagList"; +function LinkUrl({ bookmark }: { bookmark: ZBookmarkTypeLink }) { + const link = bookmark.content; + const parsedUrl = new URL(link.url); + return ( + <Link + className="line-clamp-1 hover:text-foreground" + href={link.url} + target="_blank" + > + {parsedUrl.host} + </Link> + ); +} export default function LinkCard({ bookmark: initialData, className, }: { - bookmark: ZBookmark; + bookmark: ZBookmarkTypeLink; className?: string; }) { const { data: bookmark } = api.bookmarks.getBookmark.useQuery( @@ -47,54 +87,23 @@ export default function LinkCard({ }, }, ); - const link = bookmark.content; - if (link.type != "link") { - throw new Error("Unexpected bookmark type"); + + if (bookmark.content.type !== "link") { + throw new Error("Invalid bookmark type"); } - const parsedUrl = new URL(link.url); - // A dummy white pixel for when there's no image. - // TODO: Better handling for cards with no images - const image = - link.imageUrl ?? - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdj+P///38ACfsD/QVDRcoAAAAASUVORK5CYII="; + const bookmarkLink = { ...bookmark, content: bookmark.content }; return ( - <ImageCard className={className}> - <Link href={link.url} target="_blank"> - <ImageCardBanner - src={isBookmarkStillCrawling(bookmark) ? "/blur.avif" : image} - /> - </Link> - <ImageCardContent> - <ImageCardTitle> - <Link className="line-clamp-2" href={link.url} target="_blank"> - {link?.title ?? parsedUrl.host} - </Link> - </ImageCardTitle> - {/* There's a hack here. Every tag has the full hight of the container itself. That why, when we enable flex-wrap, - the overflowed don't show up. */} - <ImageCardBody className="flex h-full flex-wrap space-x-1 overflow-hidden"> - <TagList - bookmark={bookmark} - loading={isBookmarkStillTagging(bookmark)} - /> - </ImageCardBody> - <ImageCardFooter> - <div className="mt-1 flex justify-between text-gray-500"> - <div className="my-auto"> - <Link - className="line-clamp-1 hover:text-foreground" - href={link.url} - target="_blank" - > - {parsedUrl.host} - </Link> - </div> - <BookmarkActionBar bookmark={bookmark} /> - </div> - </ImageCardFooter> - </ImageCardContent> - </ImageCard> + <BookmarkLayoutAdaptingCard + title={<LinkTitle bookmark={bookmarkLink} />} + footer={<LinkUrl bookmark={bookmarkLink} />} + bookmark={bookmarkLink} + wrapTags={false} + image={(_layout, className) => ( + <LinkImage className={className} bookmark={bookmarkLink} /> + )} + className={className} + /> ); } diff --git a/apps/web/components/dashboard/bookmarks/TagList.tsx b/apps/web/components/dashboard/bookmarks/TagList.tsx index e9161961..e0387bd2 100644 --- a/apps/web/components/dashboard/bookmarks/TagList.tsx +++ b/apps/web/components/dashboard/bookmarks/TagList.tsx @@ -8,9 +8,11 @@ import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; export default function TagList({ bookmark, loading, + className, }: { bookmark: ZBookmark; loading?: boolean; + className?: string; }) { if (loading) { return ( @@ -23,8 +25,9 @@ export default function TagList({ return ( <> {bookmark.tags.map((t) => ( - <div key={t.id} className="flex h-full flex-col justify-end"> + <div key={t.id} className={className}> <Link + key={t.id} className={cn( badgeVariants({ variant: "outline" }), "text-nowrap font-normal hover:bg-foreground hover:text-secondary", diff --git a/apps/web/components/dashboard/bookmarks/TextCard.tsx b/apps/web/components/dashboard/bookmarks/TextCard.tsx index 42d09c23..c715c8ab 100644 --- a/apps/web/components/dashboard/bookmarks/TextCard.tsx +++ b/apps/web/components/dashboard/bookmarks/TextCard.tsx @@ -3,14 +3,14 @@ import { useState } from "react"; import { isBookmarkStillTagging } from "@/lib/bookmarkUtils"; import { api } from "@/lib/trpc"; +import { bookmarkLayoutSwitch } from "@/lib/userLocalSettings/bookmarksLayout"; import { cn } from "@/lib/utils"; import Markdown from "react-markdown"; import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; -import BookmarkActionBar from "./BookmarkActionBar"; import { BookmarkedTextViewer } from "./BookmarkedTextViewer"; -import TagList from "./TagList"; +import { BookmarkLayoutAdaptingCard } from "./BookmarkLayoutAdaptingCard"; export default function TextCard({ bookmark: initialData, @@ -50,28 +50,34 @@ export default function TextCard({ open={previewModalOpen} setOpen={setPreviewModalOpen} /> - <div - className={cn( - className, - cn( - "flex h-min max-h-96 flex-col gap-y-1 overflow-hidden rounded-lg p-2 shadow-md", - ), - )} - > - <Markdown className="prose grow overflow-hidden dark:prose-invert"> - {bookmarkedText.text} - </Markdown> - <div className="mt-4 flex flex-none flex-wrap gap-1 overflow-hidden"> - <TagList - bookmark={bookmark} - loading={isBookmarkStillTagging(bookmark)} - /> - </div> - <div className="flex w-full justify-between"> - <div /> - <BookmarkActionBar bookmark={bookmark} /> - </div> - </div> + <BookmarkLayoutAdaptingCard + content={ + <Markdown className="prose dark:prose-invert"> + {bookmarkedText.text} + </Markdown> + } + footer={null} + wrapTags={true} + bookmark={bookmark} + className={className} + fitHeight={true} + image={(layout, className) => + bookmarkLayoutSwitch(layout, { + grid: null, + masonry: null, + list: ( + <div + className={cn( + "flex size-full items-center justify-center bg-accent text-center", + className, + )} + > + Note + </div> + ), + }) + } + /> </> ); } diff --git a/apps/web/components/dashboard/preview/BookmarkPreview.tsx b/apps/web/components/dashboard/preview/BookmarkPreview.tsx index cf8bc2d8..4cd9199d 100644 --- a/apps/web/components/dashboard/preview/BookmarkPreview.tsx +++ b/apps/web/components/dashboard/preview/BookmarkPreview.tsx @@ -128,7 +128,7 @@ export default function BookmarkPreview({ return ( <div className="grid grid-rows-3 gap-2 overflow-hidden bg-background lg:grid-cols-3 lg:grid-rows-none"> - <div className="row-span-2 h-full w-full overflow-hidden p-2 md:col-span-2 lg:row-auto"> + <div className="row-span-2 h-full w-full overflow-auto p-2 md:col-span-2 lg:row-auto"> {isBookmarkStillCrawling(bookmark) ? <ContentLoading /> : content} </div> <div className="lg:col-span1 row-span-1 flex flex-col gap-4 overflow-auto bg-accent p-4 lg:row-auto"> diff --git a/apps/web/components/dashboard/sidebar/SidebarProfileOptions.tsx b/apps/web/components/dashboard/sidebar/SidebarProfileOptions.tsx index 3fe4d52f..c75e292a 100644 --- a/apps/web/components/dashboard/sidebar/SidebarProfileOptions.tsx +++ b/apps/web/components/dashboard/sidebar/SidebarProfileOptions.tsx @@ -6,33 +6,86 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { Slot } from "@radix-ui/react-slot"; -import { LogOut, Moon, MoreHorizontal, Sun } from "lucide-react"; +import { useBookmarkLayout } from "@/lib/userLocalSettings/bookmarksLayout"; +import { updateBookmarksLayout } from "@/lib/userLocalSettings/userLocalSettings"; +import { + Check, + LayoutDashboard, + LayoutGrid, + LayoutList, + LayoutPanelLeft, + LogOut, + Moon, + MoreHorizontal, + Sun, +} from "lucide-react"; import { signOut } from "next-auth/react"; import { useTheme } from "next-themes"; +function BookmarkLayoutSelector() { + const layout = useBookmarkLayout(); + + const checkedComp = <Check className="ml-2 size-4" />; + + return ( + <> + <DropdownMenuItem + className="justify-between" + onClick={async () => await updateBookmarksLayout("masonry")} + > + <div className="flex items-center gap-2"> + <LayoutDashboard className="size-4" /> + <span>Masonry</span> + </div> + {layout == "masonry" && checkedComp} + </DropdownMenuItem> + <DropdownMenuItem + className="justify-between" + onClick={async () => await updateBookmarksLayout("grid")} + > + <div className="flex items-center gap-2"> + <LayoutGrid className="size-4" /> + <span>Grid</span> + </div> + {layout == "grid" && checkedComp} + </DropdownMenuItem> + <DropdownMenuItem + className="justify-between" + onClick={async () => await updateBookmarksLayout("list")} + > + <div className="flex items-center gap-2"> + <LayoutList className="size-4" /> + <span>List</span> + </div> + {layout == "list" && checkedComp} + </DropdownMenuItem> + </> + ); +} + function DarkModeToggle() { const { theme } = useTheme(); - let comp; if (theme == "dark") { - comp = ( - <span> - <Sun className="size-4" /> - <p>Light Mode</p> - </span> + return ( + <> + <Sun className="mr-2 size-4" /> + <span>Light Mode</span> + </> ); } else { - comp = ( - <span> - <Moon className="size-4" /> - <p>Dark Mode</p> - </span> + return ( + <> + <Moon className="mr-2 size-4" /> + <span>Dark Mode</span> + </> ); } - return <Slot className="flex flex-row gap-2">{comp}</Slot>; } export default function SidebarProfileOptions() { @@ -48,6 +101,15 @@ export default function SidebarProfileOptions() { <DropdownMenuItem onClick={toggleTheme}> <DarkModeToggle /> </DropdownMenuItem> + <DropdownMenuSub> + <DropdownMenuSubTrigger> + <LayoutPanelLeft className="mr-2 size-4" /> + <span>Layout</span> + </DropdownMenuSubTrigger> + <DropdownMenuSubContent> + <BookmarkLayoutSelector /> + </DropdownMenuSubContent> + </DropdownMenuSub> <DropdownMenuItem onClick={() => signOut({ |
