diff --git a/packages/frontend/component/src/components/resize-panel/resize-panel.css.ts b/packages/frontend/component/src/components/resize-panel/resize-panel.css.ts index d17b0ac1c2..d8bb76a88f 100644 --- a/packages/frontend/component/src/components/resize-panel/resize-panel.css.ts +++ b/packages/frontend/component/src/components/resize-panel/resize-panel.css.ts @@ -16,26 +16,28 @@ export const root = style({ width: panelWidthVar, minWidth: panelWidthVar, height: '100%', + zIndex: 4, + transform: 'translateX(0)', + maxWidth: '50%', selectors: { - '&[data-is-floating="true"]': { - position: 'absolute', - width: `calc(${panelWidthVar})`, - zIndex: 4, - }, - '&[data-open="true"]': { - maxWidth: '50%', - }, - '&[data-open="false"][data-handle-position="right"]': { - marginLeft: `calc(${panelWidthVar} * -1)`, - }, - '&[data-open="false"][data-handle-position="left"]': { - marginRight: `calc(${panelWidthVar} * -1)`, - }, - '&[data-enable-animation="true"][data-is-floating="false"]': { - transition: `margin-left ${animationTimeout} .05s, margin-right ${animationTimeout} .05s, width ${animationTimeout} .05s,background ${animationTimeout} .05s,scale ${animationTimeout} .05s`, - }, - '&[data-enable-animation="true"][data-is-floating="true"]': { - transition: 'margin-left 0.5s cubic-bezier(0.22,1,0.36,1)', + '&[data-open="false"][data-handle-position="right"],&[data-is-floating="true"][data-handle-position="right"]': + { + marginLeft: `calc(${panelWidthVar} * -1)`, + }, + '&[data-open="false"][data-handle-position="left"],&[data-is-floating="true"][data-handle-position="left"]': + { + marginRight: `calc(${panelWidthVar} * -1)`, + }, + '&[data-open="true"][data-handle-position="right"][data-is-floating="true"]': + { + transform: `translateX(${panelWidthVar})`, + }, + '&[data-open="true"][data-handle-position="left"][data-is-floating="true"]': + { + transform: `translateX(-${panelWidthVar})`, + }, + '&[data-enable-animation="true"]': { + transition: `margin-left ${animationTimeout}, margin-right ${animationTimeout}, transform ${animationTimeout}, background ${animationTimeout}`, }, '&[data-transition-state="exited"]': { // avoid focus on hidden panel diff --git a/packages/frontend/component/src/components/resize-panel/resize-panel.tsx b/packages/frontend/component/src/components/resize-panel/resize-panel.tsx index 5b1f182070..2d9dea2ad2 100644 --- a/packages/frontend/component/src/components/resize-panel/resize-panel.tsx +++ b/packages/frontend/component/src/components/resize-panel/resize-panel.tsx @@ -1,13 +1,6 @@ import { assignInlineVars } from '@vanilla-extract/dynamic'; import clsx from 'clsx'; -import { - forwardRef, - useCallback, - useEffect, - useLayoutEffect, - useRef, - useState, -} from 'react'; +import { forwardRef, useCallback, useLayoutEffect, useRef } from 'react'; import { useTransition } from 'react-transition-state'; import * as styles from './resize-panel.css'; @@ -129,17 +122,6 @@ const ResizeHandle = ({ ); }; -// delay initial animation to avoid flickering -function useEnableAnimation() { - const [enable, setEnable] = useState(false); - useEffect(() => { - window.setTimeout(() => { - setEnable(true); - }, 500); - }, []); - return enable; -} - const animationTimeout = 300; export const ResizePanel = forwardRef( @@ -152,7 +134,7 @@ export const ResizePanel = forwardRef( maxWidth, width, floating, - enableAnimation: _enableAnimation = true, + enableAnimation = true, open, unmountOnExit, onOpen, @@ -165,7 +147,6 @@ export const ResizePanel = forwardRef( }, ref ) { - const enableAnimation = useEnableAnimation() && _enableAnimation; const safeWidth = Math.min(maxWidth, Math.max(minWidth, width)); const [{ status }, toggle] = useTransition({ timeout: animationTimeout, diff --git a/packages/frontend/core/src/components/layouts/workspace-layout.tsx b/packages/frontend/core/src/components/layouts/workspace-layout.tsx index e582e94561..f85d06cd99 100644 --- a/packages/frontend/core/src/components/layouts/workspace-layout.tsx +++ b/packages/frontend/core/src/components/layouts/workspace-layout.tsx @@ -185,7 +185,7 @@ const DesktopLayout = ({ children }: PropsWithChildren) => { - + } diff --git a/packages/frontend/core/src/components/pure/header/index.tsx b/packages/frontend/core/src/components/pure/header/index.tsx index 3a59c56834..5560ce8002 100644 --- a/packages/frontend/core/src/components/pure/header/index.tsx +++ b/packages/frontend/core/src/components/pure/header/index.tsx @@ -18,15 +18,8 @@ interface HeaderPros { export const Header = ({ left, center, right }: HeaderPros) => { const appSidebarService = useService(AppSidebarService).sidebar; const open = useLiveData(appSidebarService.open$); - const appSidebarFloating = useLiveData(appSidebarService.responsiveFloating$); - const hoverFloating = useLiveData(appSidebarService.hoverFloating$); return ( -
+
{left}
diff --git a/packages/frontend/core/src/components/workspace/index.css.ts b/packages/frontend/core/src/components/workspace/index.css.ts index d6cc1bbc0d..d9e7f1d634 100644 --- a/packages/frontend/core/src/components/workspace/index.css.ts +++ b/packages/frontend/core/src/components/workspace/index.css.ts @@ -1,5 +1,5 @@ import { cssVar, lightCssVariables } from '@toeverything/theme'; -import { createVar, globalStyle, keyframes, style } from '@vanilla-extract/css'; +import { createVar, globalStyle, style } from '@vanilla-extract/css'; export const panelWidthVar = createVar('panel-width'); @@ -42,19 +42,7 @@ globalStyle(`html[data-theme="dark"] ${appStyle}`, { }, }); -const anime = keyframes({ - '0%': { - marginLeft: '8px', - }, - '100%': { - marginLeft: '0', - }, -}); - export const mainContainerStyle = style({ - vars: { - [panelWidthVar]: '256px', - }, position: 'relative', zIndex: 0, width: '100%', @@ -76,14 +64,6 @@ export const mainContainerStyle = style({ }, }, }, - '&[data-side-bar-open="true"][data-show-pin-sidebar-animation="true"]': { - marginLeft: panelWidthVar, - transition: 'margin-left 0.5s ease-in-out', - }, - '&[data-client-border="true"][data-side-bar-open="true"][data-side-bar-floating="false"]': - { - animation: `${anime} 0.5s ease-in-out forwards`, - }, '&[data-client-border="true"][data-is-desktop="true"]': { marginTop: 0, }, diff --git a/packages/frontend/core/src/components/workspace/index.tsx b/packages/frontend/core/src/components/workspace/index.tsx index 58fee20021..66842ac667 100644 --- a/packages/frontend/core/src/components/workspace/index.tsx +++ b/packages/frontend/core/src/components/workspace/index.tsx @@ -1,22 +1,15 @@ import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper'; -import { AppSidebarService } from '@affine/core/modules/app-sidebar'; import { DocsService, GlobalContextService, useLiveData, useService, } from '@toeverything/infra'; -import { assignInlineVars } from '@vanilla-extract/dynamic'; import { clsx } from 'clsx'; import type { HTMLAttributes, PropsWithChildren, ReactElement } from 'react'; -import { forwardRef, useMemo } from 'react'; +import { forwardRef } from 'react'; -import { - appStyle, - mainContainerStyle, - panelWidthVar, - toolStyle, -} from './index.css'; +import { appStyle, mainContainerStyle, toolStyle } from './index.css'; export type WorkspaceRootProps = PropsWithChildren<{ className?: string; @@ -54,41 +47,16 @@ export interface MainContainerProps extends HTMLAttributes {} export const MainContainer = forwardRef< HTMLDivElement, PropsWithChildren ->(function MainContainer( - { className, children, style, ...props }, - ref -): ReactElement { - const appSidebarService = useService(AppSidebarService).sidebar; - const appSideBarOpen = useLiveData(appSidebarService.open$); - const appSidebarHoverFloating = useLiveData(appSidebarService.hoverFloating$); - const appSideBarWidth = useLiveData(appSidebarService.width$); - - const showAppSideBarPinAnimation = useLiveData( - appSidebarService.showFloatToPinAnimation$ - ); +>(function MainContainer({ className, children, ...props }, ref): ReactElement { const { appSettings } = useAppSettingHelper(); - const combinedStyle = useMemo(() => { - const dynamicStyle = assignInlineVars({ - [panelWidthVar]: `${appSideBarWidth}px`, - }); - return { - ...style, - ...dynamicStyle, - }; - }, [appSideBarWidth, style]); - return (
diff --git a/packages/frontend/core/src/modules/app-sidebar/entities/app-sidebar.ts b/packages/frontend/core/src/modules/app-sidebar/entities/app-sidebar.ts index 8406bab6d3..1508db7d95 100644 --- a/packages/frontend/core/src/modules/app-sidebar/entities/app-sidebar.ts +++ b/packages/frontend/core/src/modules/app-sidebar/entities/app-sidebar.ts @@ -3,8 +3,6 @@ import { map } from 'rxjs'; import type { AppSidebarState } from '../providers/storage'; -const isMobile = !BUILD_CONFIG.isElectron && window.innerWidth < 768; - enum APP_SIDEBAR_STATE { OPEN = 'open', WIDTH = 'width', @@ -15,11 +13,15 @@ export class AppSidebar extends Entity { super(); } + /** + * whether the sidebar is open, + * even if the sidebar is not open, hovering can show the floating sidebar + */ open$ = LiveData.from( this.appSidebarState .watch(APP_SIDEBAR_STATE.OPEN) - .pipe(map(value => value ?? !isMobile)), - !isMobile + .pipe(map(value => value ?? true)), + true ); width$ = LiveData.from( @@ -28,11 +30,17 @@ export class AppSidebar extends Entity { .pipe(map(value => value ?? 248)), 248 ); - responsiveFloating$ = new LiveData(isMobile); - hoverFloating$ = new LiveData(false); - resizing$ = new LiveData(false); - showFloatToPinAnimation$ = new LiveData(false); + /** + * hovering can show the floating sidebar, without open it + */ + hovering$ = new LiveData(false); + + /** + * small screen mode, will disable hover effect + */ + smallScreenMode$ = new LiveData(false); + resizing$ = new LiveData(false); getCachedAppSidebarOpenState = () => { return this.appSidebarState.get(APP_SIDEBAR_STATE.OPEN); @@ -44,26 +52,15 @@ export class AppSidebar extends Entity { setOpen = (open: boolean) => { this.appSidebarState.set(APP_SIDEBAR_STATE.OPEN, open); - if (!open && this.hoverFloating$.value) { - const timeout = setTimeout(() => { - this.setHoverFloating(false); - }, 500); - return () => { - clearTimeout(timeout); - }; - } return; }; - setResponsiveFloating = (floating: boolean) => { - this.responsiveFloating$.next(floating); + setSmallScreenMode = (smallScreenMode: boolean) => { + this.smallScreenMode$.next(smallScreenMode); }; - setHoverFloating = (hoverFloating: boolean) => { - if (hoverFloating) { - this.showFloatToPinAnimation$.next(false); - } - this.hoverFloating$.next(hoverFloating); + setHovering = (hoverFloating: boolean) => { + this.hovering$.next(hoverFloating); }; setResizing = (resizing: boolean) => { @@ -73,8 +70,4 @@ export class AppSidebar extends Entity { setWidth = (width: number) => { this.appSidebarState.set(APP_SIDEBAR_STATE.WIDTH, width); }; - - setShowFloatToPinAnimation = (show: boolean) => { - this.showFloatToPinAnimation$.next(show); - }; } 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 5e5f21df37..b0f9c4a145 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 @@ -31,9 +31,6 @@ export const hoverNavWrapperStyle = style({ boxShadow: cssVar('--affine-popover-shadow'), borderRadius: '6px', }, - '&[data-is-floating="true"][data-show-pin-animation="true"]': { - marginLeft: '0', - }, '&[data-is-floating="true"][data-is-electron="true"]': { height: '100%', marginTop: '-4px', 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 d8743f6d1a..fe16905f42 100644 --- a/packages/frontend/core/src/modules/app-sidebar/views/index.tsx +++ b/packages/frontend/core/src/modules/app-sidebar/views/index.tsx @@ -12,7 +12,7 @@ import { import clsx from 'clsx'; import { debounce } from 'lodash-es'; import type { PropsWithChildren, ReactElement } from 'react'; -import { useCallback, useContext, useEffect, useMemo, useRef } from 'react'; +import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { AppSidebarService } from '../services/app-sidebar'; import * as styles from './fallback.css'; @@ -45,21 +45,45 @@ export function AppSidebar({ children }: PropsWithChildren) { const open = useLiveData(appSidebarService.open$); const width = useLiveData(appSidebarService.width$); - const responsiveFloating = useLiveData(appSidebarService.responsiveFloating$); - const hoverFloating = useLiveData(appSidebarService.hoverFloating$); + const smallScreenMode = useLiveData(appSidebarService.smallScreenMode$); + const hovering = useLiveData(appSidebarService.hovering$) && open !== true; const resizing = useLiveData(appSidebarService.resizing$); - const showFloatToPinAnimation = useLiveData( - appSidebarService.showFloatToPinAnimation$ - ); - - const timeoutRef = useRef(null); - - const clearExistingTimeout = useCallback(() => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - timeoutRef.current = null; + const [deferredHovering, setDeferredHovering] = useState(false); + useEffect(() => { + if (open) { + // if open, we don't need to show the floating sidebar + setDeferredHovering(false); + return; } - }, []); + if (hovering) { + // if hovering is true, we make a little delay here. + // this allow the sidebar close animation to complete. + const timeout = setTimeout(() => { + setDeferredHovering(hovering); + }, 150); + return () => { + clearTimeout(timeout); + }; + } else { + // if hovering is false, we set the deferred value after 1000ms + const timeout = setTimeout(() => { + setDeferredHovering(hovering); + }, 1000); + return () => { + clearTimeout(timeout); + }; + } + }, [hovering, open]); + + const sidebarState = smallScreenMode + ? open + ? 'floating-with-mask' + : 'close' + : open + ? 'open' + : deferredHovering + ? 'floating' + : 'close'; useEffect(() => { // do not float app sidebar on desktop @@ -71,19 +95,8 @@ export function AppSidebar({ children }: PropsWithChildren) { const isFloatingMaxWidth = window.matchMedia( `(max-width: ${floatingMaxWidth}px)` ).matches; - const isOverflowWidth = window.matchMedia( - `(max-width: ${width / 0.4}px)` - ).matches; - const isFloating = isFloatingMaxWidth || isOverflowWidth; - if ( - open === undefined && - appSidebarService.getCachedAppSidebarOpenState() === undefined - ) { - // give the initial value, - // so that the sidebar can be closed on mobile by default - appSidebarService.setOpen(!isFloating); - } - appSidebarService.setResponsiveFloating(isFloating); + const isFloating = isFloatingMaxWidth; + appSidebarService.setSmallScreenMode(isFloating); } const dOnResize = debounce(onResize, 50); @@ -91,7 +104,7 @@ export function AppSidebar({ children }: PropsWithChildren) { return () => { window.removeEventListener('resize', dOnResize); }; - }, [appSidebarService, open, width]); + }, [appSidebarService]); const hasRightBorder = !BUILD_CONFIG.isElectron && !clientBorder; @@ -121,34 +134,20 @@ export function AppSidebar({ children }: PropsWithChildren) { }, [appSidebarService]); const onMouseEnter = useCallback(() => { - if (!timeoutRef.current) { - return; - } - clearExistingTimeout(); - }, [clearExistingTimeout]); + appSidebarService.setHovering(true); + }, [appSidebarService]); const onMouseLeave = useCallback(() => { - if (!hoverFloating) { - clearExistingTimeout(); - return; - } - clearExistingTimeout(); - timeoutRef.current = setTimeout(() => { - appSidebarService.setOpen(false); - }, 1500); - }, [hoverFloating, clearExistingTimeout, appSidebarService]); - - useEffect(() => { - return () => { - clearExistingTimeout(); - }; - }, [clearExistingTimeout]); + appSidebarService.setHovering(false); + }, [appSidebarService]); return ( <> - {!hoverFloating && ( -
- )} +
); } diff --git a/packages/frontend/core/src/modules/app-sidebar/views/sidebar-header/sidebar-switch.tsx b/packages/frontend/core/src/modules/app-sidebar/views/sidebar-header/sidebar-switch.tsx index 6a1139f8e2..8a99dcd522 100644 --- a/packages/frontend/core/src/modules/app-sidebar/views/sidebar-header/sidebar-switch.tsx +++ b/packages/frontend/core/src/modules/app-sidebar/views/sidebar-header/sidebar-switch.tsx @@ -9,39 +9,26 @@ import * as styles from './sidebar-switch.css'; export const SidebarSwitch = ({ show, - enableOpenHoverSidebar, className, }: { show: boolean; - enableOpenHoverSidebar?: boolean; className?: string; }) => { const appSidebarService = useService(AppSidebarService).sidebar; const open = useLiveData(appSidebarService.open$); - const hoverFloating = useLiveData(appSidebarService.hoverFloating$); const switchRef = useRef(null); const handleMouseEnter = useCallback(() => { - if (!enableOpenHoverSidebar || open) { - return; - } - appSidebarService.setHoverFloating(true); - appSidebarService.setOpen(true); - }, [appSidebarService, enableOpenHoverSidebar, open]); + appSidebarService.setHovering(true); + }, [appSidebarService]); const handleClickSwitch = useCallback(() => { - if (open && hoverFloating) { - appSidebarService.setShowFloatToPinAnimation(true); - const timeout = setTimeout(() => { - appSidebarService.setShowFloatToPinAnimation(false); - appSidebarService.setHoverFloating(false); - }, 500); - return () => { - clearTimeout(timeout); - }; - } - return appSidebarService.toggleSidebar(); - }, [appSidebarService, hoverFloating, open]); + appSidebarService.toggleSidebar(); + }, [appSidebarService]); + + const handleMouseLeave = useCallback(() => { + appSidebarService.setHovering(false); + }, [appSidebarService]); const t = useI18n(); const tooltipContent = open @@ -54,13 +41,13 @@ export const SidebarSwitch = ({ data-show={show} className={styles.sidebarSwitchClip} data-testid={`app-sidebar-arrow-button-${open ? 'collapse' : 'expand'}`} - data-enable-open-hover-sidebar={enableOpenHoverSidebar} onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} > -
+
{sidebarTabs.length > 0 ? ( diff --git a/packages/frontend/core/src/modules/workbench/view/sidebar/sidebar-header.tsx b/packages/frontend/core/src/modules/workbench/view/sidebar/sidebar-header.tsx index 9b5fe55836..a174141453 100644 --- a/packages/frontend/core/src/modules/workbench/view/sidebar/sidebar-header.tsx +++ b/packages/frontend/core/src/modules/workbench/view/sidebar/sidebar-header.tsx @@ -4,7 +4,6 @@ import { RightSidebarIcon } from '@blocksuite/icons/rc'; import * as styles from './sidebar-header.css'; export type HeaderProps = { - floating: boolean; onToggle?: () => void; children?: React.ReactNode; }; @@ -13,20 +12,13 @@ function Container({ children, style, className, - floating, }: { children: React.ReactNode; className?: string; style?: React.CSSProperties; - floating?: boolean; }) { return ( -
+
{children}
); @@ -40,9 +32,9 @@ const ToggleButton = ({ onToggle }: { onToggle?: () => void }) => { ); }; -export const Header = ({ floating, children, onToggle }: HeaderProps) => { +export const Header = ({ children, onToggle }: HeaderProps) => { return ( - + {children} {!BUILD_CONFIG.isElectron && ( <>