From fa50743393462b4941873fc1c235650d72af1fa9 Mon Sep 17 00:00:00 2001 From: CatsJuice Date: Fri, 4 Apr 2025 05:51:19 +0000 Subject: [PATCH] feat(core): hide all sidebars when resizing to a small screen (#11105) --- .../hooks/use-responsive-siedebar.ts | 74 +++++++++++++++++++ .../workspace/layouts/workspace-layout.tsx | 2 + .../modules/app-sidebar/views/index.css.ts | 1 - .../src/modules/app-sidebar/views/index.tsx | 44 +---------- 4 files changed, 77 insertions(+), 44 deletions(-) create mode 100644 packages/frontend/core/src/components/hooks/use-responsive-siedebar.ts diff --git a/packages/frontend/core/src/components/hooks/use-responsive-siedebar.ts b/packages/frontend/core/src/components/hooks/use-responsive-siedebar.ts new file mode 100644 index 0000000000..2858c06ca2 --- /dev/null +++ b/packages/frontend/core/src/components/hooks/use-responsive-siedebar.ts @@ -0,0 +1,74 @@ +import { observeResize } from '@affine/component'; +import { AppSidebarService } from '@affine/core/modules/app-sidebar'; +import { WorkbenchService } from '@affine/core/modules/workbench'; +import { useService } from '@toeverything/infra'; +import { useCallback, useEffect, useRef } from 'react'; + +let OBSERVED = false; + +export const useResponsiveSidebar = ( + hideThreshold = 540, + floatThreshold = 768 +) => { + const previousWidthRef = useRef(null); + + const appSidebarService = useService(AppSidebarService); + const workbenchService = useService(WorkbenchService); + + const handleHideSidebar = useCallback(() => { + appSidebarService.sidebar.setOpen(false); + workbenchService.workbench.setSidebarOpen(false); + }, [appSidebarService, workbenchService]); + + const handleFloatSidebar = useCallback( + (float: boolean) => { + appSidebarService.sidebar.setSmallScreenMode(float); + }, + [appSidebarService.sidebar] + ); + + useEffect(() => { + if (OBSERVED) { + console.warn( + `"${useResponsiveSidebar.name}" already observed, do not call it multiple times` + ); + return; + } + + OBSERVED = true; + const unobserve = observeResize(document.body, entry => { + if (BUILD_CONFIG.isMobileEdition) { + return; + } + + const width = entry.contentRect.width; + const previousWidth = previousWidthRef.current; + + if (previousWidth === null) { + previousWidthRef.current = width; + return; + } + + // should hide sidebar + if (width <= hideThreshold && previousWidth > hideThreshold) { + handleHideSidebar(); + } + + if (!BUILD_CONFIG.isElectron) { + if (width >= floatThreshold && previousWidth < floatThreshold) { + handleFloatSidebar(false); + } + if (width <= floatThreshold && previousWidth > floatThreshold) { + handleFloatSidebar(true); + } + } + + previousWidthRef.current = width; + }); + + return () => { + OBSERVED = false; + unobserve(); + }; + }, [floatThreshold, handleFloatSidebar, handleHideSidebar, hideThreshold]); +}; diff --git a/packages/frontend/core/src/desktop/pages/workspace/layouts/workspace-layout.tsx b/packages/frontend/core/src/desktop/pages/workspace/layouts/workspace-layout.tsx index 05b9c8d597..05044da98e 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/layouts/workspace-layout.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/layouts/workspace-layout.tsx @@ -5,6 +5,7 @@ import { CloudQuotaModal, LocalQuotaModal, } from '@affine/core/components/affine/quota-reached-modal'; +import { useResponsiveSidebar } from '@affine/core/components/hooks/use-responsive-siedebar'; import { SWRConfigProvider } from '@affine/core/components/providers/swr-config-provider'; import { WorkspaceSideEffects } from '@affine/core/components/providers/workspace-side-effects'; import { AIIsland } from '@affine/core/desktop/components/ai-island'; @@ -59,6 +60,7 @@ const WorkspaceLayoutUIContainer = ({ children }: PropsWithChildren) => { return get(workbench.basename$) + get(workbench.location$).pathname; }) ); + useResponsiveSidebar(); return ( {children} diff --git a/packages/frontend/core/src/modules/app-sidebar/views/index.css.ts b/packages/frontend/core/src/modules/app-sidebar/views/index.css.ts index c9b2b97927..dc33e55268 100644 --- a/packages/frontend/core/src/modules/app-sidebar/views/index.css.ts +++ b/packages/frontend/core/src/modules/app-sidebar/views/index.css.ts @@ -1,7 +1,6 @@ import { cssVar } from '@toeverything/theme'; import { cssVarV2 } from '@toeverything/theme/v2'; import { style } from '@vanilla-extract/css'; -export const floatingMaxWidth = 768; export const navWrapperStyle = style({ '@media': { print: { diff --git a/packages/frontend/core/src/modules/app-sidebar/views/index.tsx b/packages/frontend/core/src/modules/app-sidebar/views/index.tsx index 12d6d78c5a..98e78887c3 100644 --- a/packages/frontend/core/src/modules/app-sidebar/views/index.tsx +++ b/packages/frontend/core/src/modules/app-sidebar/views/index.tsx @@ -11,9 +11,8 @@ import { useServiceOptional, } from '@toeverything/infra'; import clsx from 'clsx'; -import { debounce } from 'lodash-es'; import type { PropsWithChildren, ReactElement } from 'react'; -import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { useCallback, useContext, useEffect, useMemo } from 'react'; import { WorkbenchService } from '../../workbench'; import { allowedSplitViewEntityTypes } from '../../workbench/view/split-view/types'; @@ -21,7 +20,6 @@ import { WorkspaceService } from '../../workspace'; import { AppSidebarService } from '../services/app-sidebar'; import * as styles from './fallback.css'; import { - floatingMaxWidth, hoverNavWrapperStyle, navBodyStyle, navHeaderStyle, @@ -54,21 +52,6 @@ export function AppSidebar({ children }: PropsWithChildren) { const smallScreenMode = useLiveData(appSidebarService.smallScreenMode$); const hovering = useLiveData(appSidebarService.hovering$) && open !== true; const resizing = useLiveData(appSidebarService.resizing$); - const [initialized, setInitialized] = useState(false); - - useEffect(() => { - if (BUILD_CONFIG.isElectron) { - setInitialized(true); - return; - } - const shouldFloating = window.matchMedia( - `(max-width: ${floatingMaxWidth}px)` - ).matches; - - appSidebarService.setSmallScreenMode(shouldFloating); - setInitialized(true); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); const sidebarState = smallScreenMode ? open @@ -80,27 +63,6 @@ export function AppSidebar({ children }: PropsWithChildren) { ? 'floating' : 'close'; - useEffect(() => { - // do not float app sidebar on desktop - if (BUILD_CONFIG.isElectron) { - return; - } - - function onResize() { - const isFloatingMaxWidth = window.matchMedia( - `(max-width: ${floatingMaxWidth}px)` - ).matches; - const isFloating = isFloatingMaxWidth; - appSidebarService.setSmallScreenMode(isFloating); - } - - const dOnResize = debounce(onResize, 50); - window.addEventListener('resize', dOnResize); - return () => { - window.removeEventListener('resize', dOnResize); - }; - }, [appSidebarService]); - const hasRightBorder = !BUILD_CONFIG.isElectron && !clientBorder; const handleOpenChange = useCallback( @@ -176,10 +138,6 @@ export function AppSidebar({ children }: PropsWithChildren) { }); }, [workbenchService.views$.value]); - if (!initialized) { - return null; - } - return ( <>