From 98bdf258447d8c2ce89d6bd9516ddfdc4cdca1e9 Mon Sep 17 00:00:00 2001 From: CatsJuice Date: Tue, 12 Nov 2024 07:28:10 +0000 Subject: [PATCH] feat(mobile): optimize home header animation (#8707) close AF-1420 ![CleanShot 2024-11-05 at 17.17.29.gif](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/LakojjjzZNf6ogjOVwKE/a74ead2d-30ef-4bb1-8714-fdc77dd93335.gif) --- .../components/workspace-selector/index.tsx | 21 ++++- .../src/mobile/views/home-header/index.tsx | 76 +++++++++++-------- .../mobile/views/home-header/styles.css.ts | 74 +++++------------- 3 files changed, 81 insertions(+), 90 deletions(-) diff --git a/packages/frontend/core/src/mobile/components/workspace-selector/index.tsx b/packages/frontend/core/src/mobile/components/workspace-selector/index.tsx index 23f07a7bed..6b6213c6ea 100644 --- a/packages/frontend/core/src/mobile/components/workspace-selector/index.tsx +++ b/packages/frontend/core/src/mobile/components/workspace-selector/index.tsx @@ -1,12 +1,21 @@ import { MobileMenu } from '@affine/component'; import { track } from '@affine/track'; import { useServiceOptional, WorkspacesService } from '@toeverything/infra'; -import { useCallback, useEffect, useState } from 'react'; +import { + forwardRef, + type HTMLAttributes, + useCallback, + useEffect, + useState, +} from 'react'; import { CurrentWorkspaceCard } from './current-card'; import { SelectorMenu } from './menu'; -export const WorkspaceSelector = () => { +export const WorkspaceSelector = forwardRef< + HTMLDivElement, + HTMLAttributes +>(function WorkspaceSelector({ className }, ref) { const [open, setOpen] = useState(false); const workspaceManager = useServiceOptional(WorkspacesService); @@ -33,7 +42,11 @@ export const WorkspaceSelector = () => { style: { padding: 0 }, }} > - + ); -}; +}); diff --git a/packages/frontend/core/src/mobile/views/home-header/index.tsx b/packages/frontend/core/src/mobile/views/home-header/index.tsx index acd89923df..625423c573 100644 --- a/packages/frontend/core/src/mobile/views/home-header/index.tsx +++ b/packages/frontend/core/src/mobile/views/home-header/index.tsx @@ -9,7 +9,7 @@ import { useI18n } from '@affine/i18n'; import { SettingsIcon } from '@blocksuite/icons/rc'; import { useService } from '@toeverything/infra'; import clsx from 'clsx'; -import { useCallback, useState } from 'react'; +import { useCallback, useRef, useState } from 'react'; import { SearchInput, WorkspaceSelector } from '../../components'; import { searchVTScope } from '../../components/search-input/style.css'; @@ -23,18 +23,12 @@ import * as styles from './styles.css'; * - hide Search */ export const HomeHeader = () => { - const t = useI18n(); - const workbench = useService(WorkbenchService).workbench; const globalDialogService = useService(GlobalDialogService); - const [dense, setDense] = useState(false); - - useGlobalEvent( - 'scroll', - useCallback(() => { - setDense(window.scrollY > 114); - }, []) - ); + const workspaceCardRef = useRef(null); + const floatWorkspaceCardRef = useRef(null); + const t = useI18n(); + const workbench = useService(WorkbenchService).workbench; const navSearch = useCallback(() => { startScopedViewTransition(searchVTScope, () => { @@ -42,33 +36,49 @@ export const HomeHeader = () => { }); }, [workbench]); + const [dense, setDense] = useState(false); + + useGlobalEvent( + 'scroll', + useCallback(() => { + if (!workspaceCardRef.current || !floatWorkspaceCardRef.current) return; + const inFlowTop = workspaceCardRef.current.getBoundingClientRect().top; + const floatTop = + floatWorkspaceCardRef.current.getBoundingClientRect().top; + setDense(inFlowTop <= floatTop); + }, []) + ); + + const openSetting = useCallback(() => { + globalDialogService.open('setting', { + activeTab: 'appearance', + }); + }, [globalDialogService]); + return ( -
- -
-
- -
-
- { - globalDialogService.open('setting', { - activeTab: 'appearance', - }); - }} - size="24" - style={{ padding: 10 }} - icon={} - /> -
+ <> + +
+ } />
-
+
+
- -
+ {/* float */} + + + } + /> -
+ ); }; diff --git a/packages/frontend/core/src/mobile/views/home-header/styles.css.ts b/packages/frontend/core/src/mobile/views/home-header/styles.css.ts index c23743ca4c..92e003ae2c 100644 --- a/packages/frontend/core/src/mobile/views/home-header/styles.css.ts +++ b/packages/frontend/core/src/mobile/views/home-header/styles.css.ts @@ -5,80 +5,48 @@ const headerHeight = createVar('headerHeight'); const wsSelectorHeight = createVar('wsSelectorHeight'); const searchHeight = createVar('searchHeight'); -const searchPadding = [10, 16, 15, 16]; - export const root = style({ vars: { [headerHeight]: '44px', [wsSelectorHeight]: '48px', [searchHeight]: '44px', }, - width: '100vw', + width: '100dvw', }); +export const headerSettingRow = style({ + display: 'flex', + justifyContent: 'end', + height: 44, + paddingRight: 10, +}); +export const wsSelectorAndSearch = style({ + display: 'flex', + flexDirection: 'column', + gap: 15, + padding: '4px 16px 15px 16px', +}); + export const float = style({ - // why not 'sticky'? - // when height change, will affect scroll behavior, causing shaking position: 'fixed', top: 0, width: '100%', background: cssVarV2('layer/background/secondary'), zIndex: 1, -}); -export const space = style({ - height: `calc(${headerHeight} + ${wsSelectorHeight} + ${searchHeight} + ${searchPadding[0] + searchPadding[2]}px + 12px)`, -}); -export const headerAndWsSelector = style({ display: 'flex', + alignItems: 'center', + padding: '4px 10px 4px 16px', gap: 10, - alignItems: 'end', - transition: 'height 0.2s', - height: `calc(${headerHeight} + ${wsSelectorHeight})`, + // visibility control + visibility: 'hidden', selectors: { - [`${root}.dense &`]: { - height: wsSelectorHeight, + '&.dense': { + visibility: 'visible', }, }, }); - -export const wsSelectorWrapper = style({ +export const floatWsSelector = style({ width: 0, flex: 1, - height: wsSelectorHeight, - padding: '0 10px 0 16px', - display: 'flex', - alignItems: 'center', -}); - -export const settingWrapper = style({ - width: '44px', - height: headerHeight, - transition: 'height 0.2s', - - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - alignSelf: 'start', - - selectors: { - [`${root}.dense &`]: { - height: wsSelectorHeight, - }, - }, -}); - -export const searchWrapper = style({ - padding: searchPadding.map(v => `${v}px`).join(' '), - width: '100%', - height: 44 + searchPadding[0] + searchPadding[2], - transition: 'all 0.2s', - overflow: 'hidden', - selectors: { - [`${root}.dense &`]: { - height: 0, - paddingTop: 0, - paddingBottom: 0, - }, - }, });