diff --git a/apps/client/src/components/layouts/global/app-shell.module.css b/apps/client/src/components/layouts/global/app-shell.module.css index b6e88d9..ed36961 100644 --- a/apps/client/src/components/layouts/global/app-shell.module.css +++ b/apps/client/src/components/layouts/global/app-shell.module.css @@ -14,3 +14,18 @@ } } +.resizeHandle { + width: 3px; + cursor: col-resize; + position: absolute; + right: 0; + top: 0; + bottom: 0; + + &:hover, &:active { + width: 5px; + background: light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-5)) + } +} + + diff --git a/apps/client/src/components/layouts/global/global-app-shell.tsx b/apps/client/src/components/layouts/global/global-app-shell.tsx index 663ed44..ae26c98 100644 --- a/apps/client/src/components/layouts/global/global-app-shell.tsx +++ b/apps/client/src/components/layouts/global/global-app-shell.tsx @@ -1,12 +1,12 @@ import { AppShell, Container } from "@mantine/core"; -import React from "react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; import { useLocation } from "react-router-dom"; import SettingsSidebar from "@/components/settings/settings-sidebar.tsx"; import { useAtom } from "jotai"; import { asideStateAtom, desktopSidebarAtom, - mobileSidebarAtom, + mobileSidebarAtom, sidebarWidthAtom, } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts"; import { SpaceSidebar } from "@/features/space/components/sidebar/space-sidebar.tsx"; import { AppHeader } from "@/components/layouts/global/app-header.tsx"; @@ -21,6 +21,45 @@ export default function GlobalAppShell({ const [mobileOpened] = useAtom(mobileSidebarAtom); const [desktopOpened] = useAtom(desktopSidebarAtom); const [{ isAsideOpen }] = useAtom(asideStateAtom); + const [sidebarWidth, setSidebarWidth] = useAtom(sidebarWidthAtom); + const [isResizing, setIsResizing] = useState(false); + const sidebarRef = useRef(null); + + const startResizing = React.useCallback((mouseDownEvent) => { + setIsResizing(true); + }, []); + + const stopResizing = React.useCallback(() => { + setIsResizing(false); + }, []); + + const resize = React.useCallback( + (mouseMoveEvent) => { + if (isResizing) { + const newWidth = mouseMoveEvent.clientX - sidebarRef.current.getBoundingClientRect().left; + if (newWidth < 220) { + setSidebarWidth(220); + return; + } + if (newWidth > 600) { + setSidebarWidth(600); + return; + } + setSidebarWidth(newWidth); + } + }, + [isResizing] + ); + + useEffect(() => { + //https://codesandbox.io/p/sandbox/kz9de + window.addEventListener("mousemove", resize); + window.addEventListener("mouseup", stopResizing); + return () => { + window.removeEventListener("mousemove", resize); + window.removeEventListener("mouseup", stopResizing); + }; + }, [resize, stopResizing]); const location = useLocation(); const isSettingsRoute = location.pathname.startsWith("/settings"); @@ -33,7 +72,7 @@ export default function GlobalAppShell({ header={{ height: 45 }} navbar={ !isHomeRoute && { - width: 300, + width: isSpaceRoute ? sidebarWidth : 300, breakpoint: "sm", collapsed: { mobile: !mobileOpened, @@ -54,7 +93,10 @@ export default function GlobalAppShell({ {!isHomeRoute && ( - + e.preventDefault()} + > +
{isSpaceRoute && } {isSettingsRoute && } diff --git a/apps/client/src/components/layouts/global/hooks/atoms/sidebar-atom.ts b/apps/client/src/components/layouts/global/hooks/atoms/sidebar-atom.ts index fed366e..f71fc6f 100644 --- a/apps/client/src/components/layouts/global/hooks/atoms/sidebar-atom.ts +++ b/apps/client/src/components/layouts/global/hooks/atoms/sidebar-atom.ts @@ -19,3 +19,5 @@ export const asideStateAtom = atom({ tab: "", isAsideOpen: false, }); + +export const sidebarWidthAtom = atomWithWebStorage('sidebarWidth', 300); diff --git a/apps/client/src/lib/jotai-helper.ts b/apps/client/src/lib/jotai-helper.ts index 06d2525..252a3bf 100644 --- a/apps/client/src/lib/jotai-helper.ts +++ b/apps/client/src/lib/jotai-helper.ts @@ -2,9 +2,9 @@ import { atom } from "jotai"; export function atomWithWebStorage(key: string, initialValue: Value, storage = localStorage) { const storedValue = localStorage.getItem(key); - const isString = typeof initialValue === "string"; + const isStringOrInt = typeof initialValue === "string" || typeof initialValue === "number"; - const storageValue = storedValue ? isString ? storedValue : storedValue === "true" : undefined; + const storageValue = storedValue ? isStringOrInt ? storedValue : storedValue === "true" : undefined; const baseAtom = atom(storageValue ?? initialValue); return atom(