diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-07-26 12:58:01 +0000 |
|---|---|---|
| committer | Mohamed Bassem <me@mbassem.com> | 2025-07-26 12:58:01 +0000 |
| commit | 154efe17421ca96d433fcc1f820ad460e1675bdc (patch) | |
| tree | 4336090648fe7196818bcc371104d3b603a68c0e /apps/web/components | |
| parent | 8b4fb49cc066eef602d9d089e7b71d183231a8fd (diff) | |
| download | karakeep-154efe17421ca96d433fcc1f820ad460e1675bdc.tar.zst | |
feat: Configurable number of grid columns. Fixes #1713
Diffstat (limited to 'apps/web/components')
| -rw-r--r-- | apps/web/components/dashboard/ChangeLayout.tsx | 64 | ||||
| -rw-r--r-- | apps/web/components/dashboard/GlobalActions.tsx | 4 | ||||
| -rw-r--r-- | apps/web/components/dashboard/ViewOptions.tsx | 115 | ||||
| -rw-r--r-- | apps/web/components/dashboard/bookmarks/BookmarksGrid.tsx | 23 |
4 files changed, 134 insertions, 72 deletions
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<LayoutType, LucideIcon> = { - masonry: LayoutDashboard, - grid: LayoutGrid, - list: LayoutList, - compact: List, -}; - -export default function ChangeLayout() { - const { t } = useTranslation(); - const layout = useBookmarkLayout(); - - return ( - <DropdownMenu> - <DropdownMenuTrigger asChild> - <ButtonWithTooltip - tooltip={t("actions.change_layout")} - delayDuration={100} - variant="ghost" - > - {React.createElement(iconMap[layout], { size: 18 })} - </ButtonWithTooltip> - </DropdownMenuTrigger> - <DropdownMenuContent className="w-fit"> - {(Object.keys(iconMap) as LayoutType[]).map((key) => ( - <DropdownMenuItem - key={key} - className="cursor-pointer justify-between" - onClick={async () => await updateBookmarksLayout(key as LayoutType)} - > - <div className="flex items-center gap-2"> - {React.createElement(iconMap[key as LayoutType], { size: 18 })} - <span>{t(`layouts.${key}`)}</span> - </div> - {layout == key && <Check className="ml-2 size-4" />} - </DropdownMenuItem> - ))} - </DropdownMenuContent> - </DropdownMenu> - ); -} 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 ( <div className="flex min-w-max flex-wrap overflow-hidden"> - <ChangeLayout /> + <ViewOptions /> <SortOrderToggle /> <BulkBookmarksAction /> </div> 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<LayoutType, LucideIcon> = { + masonry: LayoutDashboard, + grid: LayoutGrid, + list: LayoutList, + compact: List, +}; + +const layoutNames: Record<LayoutType, string> = { + 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 ( + <DropdownMenu> + <DropdownMenuTrigger asChild> + <ButtonWithTooltip + tooltip="View Options" + delayDuration={100} + variant="ghost" + > + <Settings size={18} /> + </ButtonWithTooltip> + </DropdownMenuTrigger> + <DropdownMenuContent className="w-56"> + <div className="px-2 py-1.5 text-sm font-semibold">Layout</div> + {(Object.keys(iconMap) as LayoutType[]).map((key) => ( + <DropdownMenuItem + key={key} + className="cursor-pointer justify-between" + onClick={async () => await updateBookmarksLayout(key as LayoutType)} + > + <div className="flex items-center gap-2"> + {React.createElement(iconMap[key as LayoutType], { size: 18 })} + <span>{layoutNames[key]}</span> + </div> + {layout === key && <Check className="ml-2 size-4" />} + </DropdownMenuItem> + ))} + + {showColumnSlider && ( + <> + <DropdownMenuSeparator /> + <div className="px-2 py-3"> + <div className="mb-2 flex items-center justify-between"> + <span className="text-sm font-semibold">Columns</span> + <span className="text-sm text-muted-foreground"> + {tempColumns} + </span> + </div> + <Slider + value={[tempColumns]} + onValueChange={([value]) => setTempColumns(value)} + onValueCommit={([value]) => updateGridColumns(value)} + min={1} + max={6} + step={1} + className="w-full" + /> + <div className="mt-1 flex justify-between text-xs text-muted-foreground"> + <span>1</span> + <span>6</span> + </div> + </div> + </> + )} + </DropdownMenuContent> + </DropdownMenu> + ); +} 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(() => { |
