diff --git a/commafeed-client/package-lock.json b/commafeed-client/package-lock.json index 1e73a244..9339a0d1 100644 --- a/commafeed-client/package-lock.json +++ b/commafeed-client/package-lock.json @@ -25,6 +25,7 @@ "dayjs": "^1.11.7", "interweave": "^13.1.0", "mousetrap": "^1.6.5", + "re-resizable": "^6.9.9", "react": "^18.2.0", "react-async-hook": "^4.0.0", "react-contexify": "^6.0.0", @@ -10035,6 +10036,15 @@ "node": ">=0.10.0" } }, + "node_modules/re-resizable": { + "version": "6.9.9", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.9.tgz", + "integrity": "sha512-l+MBlKZffv/SicxDySKEEh42hR6m5bAHfNu3Tvxks2c4Ah+ldnWjfnVRwxo/nxF27SsUsxDS0raAzFuJNKABXA==", + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", diff --git a/commafeed-client/package.json b/commafeed-client/package.json index 1f97656f..098bf7b2 100644 --- a/commafeed-client/package.json +++ b/commafeed-client/package.json @@ -31,6 +31,7 @@ "dayjs": "^1.11.7", "interweave": "^13.1.0", "mousetrap": "^1.6.5", + "re-resizable": "^6.9.9", "react": "^18.2.0", "react-async-hook": "^4.0.0", "react-contexify": "^6.0.0", diff --git a/commafeed-client/src/App.tsx b/commafeed-client/src/App.tsx index 0e2d0f34..7f844545 100644 --- a/commafeed-client/src/App.tsx +++ b/commafeed-client/src/App.tsx @@ -66,6 +66,7 @@ function Providers(props: { children: React.ReactNode }) { const ApiDocumentationPage = React.lazy(() => import("pages/app/ApiDocumentationPage")) function AppRoutes() { + const sidebarWidth = useAppSelector(state => state.tree.sidebarWidth) return ( } /> @@ -74,7 +75,7 @@ function AppRoutes() { } /> } /> } /> - } sidebar={} />}> + } sidebar={} sidebarWidth={sidebarWidth} />}> } /> } /> @@ -135,8 +136,11 @@ function FaviconHandler() { const root = useAppSelector(state => state.tree.rootCategory) useEffect(() => { const unreadCount = categoryUnreadCount(root) - if (unreadCount === 0) Tinycon.reset() - else Tinycon.setBubble(unreadCount) + if (unreadCount === 0) { + Tinycon.reset() + } else { + Tinycon.setBubble(unreadCount) + } }, [root]) return null diff --git a/commafeed-client/src/app/constants.ts b/commafeed-client/src/app/constants.ts index 4aecc298..0d56234a 100644 --- a/commafeed-client/src/app/constants.ts +++ b/commafeed-client/src/app/constants.ts @@ -88,7 +88,6 @@ export const Constants = { layout: { mobileBreakpoint: DEFAULT_THEME.breakpoints.md, headerHeight: 60, - sidebarWidth: 350, entryMaxWidth: 650, isTopVisible: (div: HTMLElement) => div.getBoundingClientRect().top >= Constants.layout.headerHeight, isBottomVisible: (div: HTMLElement) => div.getBoundingClientRect().bottom <= window.innerHeight, diff --git a/commafeed-client/src/app/slices/tree.ts b/commafeed-client/src/app/slices/tree.ts index 5b32bfa2..82c22777 100644 --- a/commafeed-client/src/app/slices/tree.ts +++ b/commafeed-client/src/app/slices/tree.ts @@ -9,10 +9,12 @@ import { redirectTo } from "./redirect" interface TreeState { rootCategory?: Category mobileMenuOpen: boolean + sidebarWidth: number } const initialState: TreeState = { mobileMenuOpen: false, + sidebarWidth: 350, } export const reloadTree = createAsyncThunk("tree/reload", () => client.category.getRoot().then(r => r.data)) @@ -27,6 +29,9 @@ export const treeSlice = createSlice({ setMobileMenuOpen: (state, action: PayloadAction) => { state.mobileMenuOpen = action.payload }, + setSidebarWidth: (state, action: PayloadAction) => { + state.sidebarWidth = action.payload + }, }, extraReducers: builder => { builder.addCase(reloadTree.fulfilled, (state, action) => { @@ -54,5 +59,5 @@ export const treeSlice = createSlice({ }, }) -export const { setMobileMenuOpen } = treeSlice.actions +export const { setMobileMenuOpen, setSidebarWidth } = treeSlice.actions export default treeSlice.reducer diff --git a/commafeed-client/src/pages/app/Layout.tsx b/commafeed-client/src/pages/app/Layout.tsx index b25daebe..1ba32e5a 100644 --- a/commafeed-client/src/pages/app/Layout.tsx +++ b/commafeed-client/src/pages/app/Layout.tsx @@ -16,7 +16,7 @@ import { import { useViewportSize } from "@mantine/hooks" import { Constants } from "app/constants" import { redirectToAdd, redirectToRootCategory } from "app/slices/redirect" -import { reloadTree, setMobileMenuOpen } from "app/slices/tree" +import { reloadTree, setMobileMenuOpen, setSidebarWidth } from "app/slices/tree" import { reloadProfile, reloadSettings, reloadTags } from "app/slices/user" import { useAppDispatch, useAppSelector } from "app/store" import { Loader } from "components/Loader" @@ -26,28 +26,34 @@ import { OnMobile } from "components/responsive/OnMobile" import { useAppLoading } from "hooks/useAppLoading" import { useWebSocket } from "hooks/useWebSocket" import { LoadingPage } from "pages/LoadingPage" +import { Resizable } from "re-resizable" import { ReactNode, Suspense, useEffect } from "react" import { TbPlus } from "react-icons/tb" import { Outlet } from "react-router-dom" interface LayoutProps { sidebar: ReactNode + sidebarWidth: number header: ReactNode } const sidebarPadding = DEFAULT_THEME.spacing.xs const sidebarRightBorderWidth = "1px" -const useStyles = createStyles(theme => ({ +const useStyles = createStyles((theme, props: LayoutProps) => ({ + sidebarContentResizeWrapper: { + padding: sidebarPadding, + minHeight: "100vh", + }, sidebarContent: { - maxWidth: `calc(${Constants.layout.sidebarWidth}px - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`, + maxWidth: `calc(${props.sidebarWidth}px - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`, [theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: { maxWidth: `calc(100vw - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`, }, }, mainContentWrapper: { paddingTop: Constants.layout.headerHeight, - paddingLeft: Constants.layout.sidebarWidth, + paddingLeft: props.sidebarWidth, paddingRight: 0, paddingBottom: 0, [theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: { @@ -55,7 +61,7 @@ const useStyles = createStyles(theme => ({ }, }, mainContent: { - maxWidth: `calc(100vw - ${Constants.layout.sidebarWidth}px)`, + maxWidth: `calc(100vw - ${props.sidebarWidth}px)`, padding: theme.spacing.md, [theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: { maxWidth: "100vw", @@ -76,8 +82,8 @@ function LogoAndTitle() { ) } -export default function Layout({ sidebar, header }: LayoutProps) { - const { classes } = useStyles() +export default function Layout(props: LayoutProps) { + const { classes } = useStyles(props) const theme = useMantineTheme() const viewport = useViewportSize() const { loading } = useAppLoading() @@ -85,6 +91,8 @@ export default function Layout({ sidebar, header }: LayoutProps) { const dispatch = useAppDispatch() useWebSocket() + const handleResize = (element: HTMLElement) => dispatch(setSidebarWidth(element.offsetWidth)) + useEffect(() => { dispatch(reloadSettings()) dispatch(reloadProfile()) @@ -122,14 +130,30 @@ export default function Layout({ sidebar, header }: LayoutProps) { navbar={ } header={ @@ -147,19 +171,19 @@ export default function Layout({ sidebar, header }: LayoutProps) { {!mobileMenuOpen && ( {burger} - {header} + {props.header} )} - + {addButton} - {header} + {props.header}