From 154efe17421ca96d433fcc1f820ad460e1675bdc Mon Sep 17 00:00:00 2001 From: Mohamed Bassem Date: Sat, 26 Jul 2025 12:58:01 +0000 Subject: feat: Configurable number of grid columns. Fixes #1713 --- apps/web/components/dashboard/ChangeLayout.tsx | 64 ------------ apps/web/components/dashboard/GlobalActions.tsx | 4 +- apps/web/components/dashboard/ViewOptions.tsx | 115 +++++++++++++++++++++ .../dashboard/bookmarks/BookmarksGrid.tsx | 23 +++-- 4 files changed, 134 insertions(+), 72 deletions(-) delete mode 100644 apps/web/components/dashboard/ChangeLayout.tsx create mode 100644 apps/web/components/dashboard/ViewOptions.tsx (limited to 'apps/web/components') diff --git a/apps/web/components/dashboard/ChangeLayout.tsx b/apps/web/components/dashboard/ChangeLayout.tsx deleted file mode 100644 index c7f44a73..00000000 --- a/apps/web/components/dashboard/ChangeLayout.tsx +++ /dev/null @@ -1,64 +0,0 @@ -"use client"; - -import React from "react"; -import { ButtonWithTooltip } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { useTranslation } from "@/lib/i18n/client"; -import { useBookmarkLayout } from "@/lib/userLocalSettings/bookmarksLayout"; -import { updateBookmarksLayout } from "@/lib/userLocalSettings/userLocalSettings"; -import { - Check, - LayoutDashboard, - LayoutGrid, - LayoutList, - List, - LucideIcon, -} from "lucide-react"; - -type LayoutType = "masonry" | "grid" | "list" | "compact"; - -const iconMap: Record = { - masonry: LayoutDashboard, - grid: LayoutGrid, - list: LayoutList, - compact: List, -}; - -export default function ChangeLayout() { - const { t } = useTranslation(); - const layout = useBookmarkLayout(); - - return ( - - - - {React.createElement(iconMap[layout], { size: 18 })} - - - - {(Object.keys(iconMap) as LayoutType[]).map((key) => ( - await updateBookmarksLayout(key as LayoutType)} - > -
- {React.createElement(iconMap[key as LayoutType], { size: 18 })} - {t(`layouts.${key}`)} -
- {layout == key && } -
- ))} -
-
- ); -} diff --git a/apps/web/components/dashboard/GlobalActions.tsx b/apps/web/components/dashboard/GlobalActions.tsx index ecbb70bf..d36b93d9 100644 --- a/apps/web/components/dashboard/GlobalActions.tsx +++ b/apps/web/components/dashboard/GlobalActions.tsx @@ -1,13 +1,13 @@ "use client"; import BulkBookmarksAction from "@/components/dashboard/BulkBookmarksAction"; -import ChangeLayout from "@/components/dashboard/ChangeLayout"; import SortOrderToggle from "@/components/dashboard/SortOrderToggle"; +import ViewOptions from "@/components/dashboard/ViewOptions"; export default function GlobalActions() { return (
- +
diff --git a/apps/web/components/dashboard/ViewOptions.tsx b/apps/web/components/dashboard/ViewOptions.tsx new file mode 100644 index 00000000..6367421f --- /dev/null +++ b/apps/web/components/dashboard/ViewOptions.tsx @@ -0,0 +1,115 @@ +"use client"; + +import React from "react"; +import { ButtonWithTooltip } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Slider } from "@/components/ui/slider"; +import { + useBookmarkLayout, + useGridColumns, +} from "@/lib/userLocalSettings/bookmarksLayout"; +import { + updateBookmarksLayout, + updateGridColumns, +} from "@/lib/userLocalSettings/userLocalSettings"; +import { + Check, + LayoutDashboard, + LayoutGrid, + LayoutList, + List, + LucideIcon, + Settings, +} from "lucide-react"; + +type LayoutType = "masonry" | "grid" | "list" | "compact"; + +const iconMap: Record = { + masonry: LayoutDashboard, + grid: LayoutGrid, + list: LayoutList, + compact: List, +}; + +const layoutNames: Record = { + masonry: "Masonry", + grid: "Grid", + list: "List", + compact: "Compact", +}; + +export default function ViewOptions() { + const layout = useBookmarkLayout(); + const gridColumns = useGridColumns(); + const [tempColumns, setTempColumns] = React.useState(gridColumns); + + const showColumnSlider = layout === "grid" || layout === "masonry"; + + // Update temp value when actual value changes + React.useEffect(() => { + setTempColumns(gridColumns); + }, [gridColumns]); + + return ( + + + + + + + +
Layout
+ {(Object.keys(iconMap) as LayoutType[]).map((key) => ( + await updateBookmarksLayout(key as LayoutType)} + > +
+ {React.createElement(iconMap[key as LayoutType], { size: 18 })} + {layoutNames[key]} +
+ {layout === key && } +
+ ))} + + {showColumnSlider && ( + <> + +
+
+ Columns + + {tempColumns} + +
+ setTempColumns(value)} + onValueCommit={([value]) => updateGridColumns(value)} + min={1} + max={6} + step={1} + className="w-full" + /> +
+ 1 + 6 +
+
+ + )} +
+
+ ); +} diff --git a/apps/web/components/dashboard/bookmarks/BookmarksGrid.tsx b/apps/web/components/dashboard/bookmarks/BookmarksGrid.tsx index 21bc5fed..954a7751 100644 --- a/apps/web/components/dashboard/bookmarks/BookmarksGrid.tsx +++ b/apps/web/components/dashboard/bookmarks/BookmarksGrid.tsx @@ -5,6 +5,7 @@ import useBulkActionsStore from "@/lib/bulkActions"; import { bookmarkLayoutSwitch, useBookmarkLayout, + useGridColumns, } from "@/lib/userLocalSettings/bookmarksLayout"; import tailwindConfig from "@/tailwind.config"; import { Slot } from "@radix-ui/react-slot"; @@ -27,15 +28,21 @@ function StyledBookmarkCard({ children }: { children: React.ReactNode }) { ); } -function getBreakpointConfig() { +function getBreakpointConfig(userColumns: number) { const fullConfig = resolveConfig(tailwindConfig); const breakpointColumnsObj: { [key: number]: number; default: number } = { - default: 3, + default: userColumns, }; - breakpointColumnsObj[parseInt(fullConfig.theme.screens.lg)] = 2; - breakpointColumnsObj[parseInt(fullConfig.theme.screens.md)] = 1; - breakpointColumnsObj[parseInt(fullConfig.theme.screens.sm)] = 1; + + // Responsive behavior: reduce columns on smaller screens + const lgColumns = Math.max(1, Math.min(userColumns, userColumns - 1)); + const mdColumns = Math.max(1, Math.min(userColumns, 2)); + const smColumns = 1; + + breakpointColumnsObj[parseInt(fullConfig.theme.screens.lg)] = lgColumns; + breakpointColumnsObj[parseInt(fullConfig.theme.screens.md)] = mdColumns; + breakpointColumnsObj[parseInt(fullConfig.theme.screens.sm)] = smColumns; return breakpointColumnsObj; } @@ -53,8 +60,12 @@ export default function BookmarksGrid({ fetchNextPage?: () => void; }) { const layout = useBookmarkLayout(); + const gridColumns = useGridColumns(); const bulkActionsStore = useBulkActionsStore(); - const breakpointConfig = useMemo(() => getBreakpointConfig(), []); + const breakpointConfig = useMemo( + () => getBreakpointConfig(gridColumns), + [gridColumns], + ); const { ref: loadMoreRef, inView: loadMoreButtonInView } = useInView(); useEffect(() => { -- cgit v1.2.3-70-g09d2