Skip to content

Commit

Permalink
feat(mobile): optimize home header animation (#8707)
Browse files Browse the repository at this point in the history
  • Loading branch information
CatsJuice committed Nov 12, 2024
1 parent fa82842 commit 98bdf25
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 86 deletions.
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>
>(function WorkspaceSelector({ className }, ref) {
const [open, setOpen] = useState(false);
const workspaceManager = useServiceOptional(WorkspacesService);

Expand All @@ -33,7 +42,11 @@ export const WorkspaceSelector = () => {
style: { padding: 0 },
}}
>
<CurrentWorkspaceCard onClick={openMenu} />
<CurrentWorkspaceCard
ref={ref}
onClick={openMenu}
className={className}
/>
</MobileMenu>
);
};
});
68 changes: 39 additions & 29 deletions packages/frontend/core/src/mobile/views/home-header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -23,52 +23,62 @@ import * as styles from './styles.css';
* - hide Search
*/
export const HomeHeader = () => {
const globalDialogService = useService(GlobalDialogService);

const workspaceCardRef = useRef<HTMLDivElement>(null);
const floatWorkspaceCardRef = useRef<HTMLDivElement>(null);
const t = useI18n();
const workbench = useService(WorkbenchService).workbench;
const globalDialogService = useService(GlobalDialogService);

const navSearch = useCallback(() => {
startScopedViewTransition(searchVTScope, () => {
workbench.open('/search');
});
}, [workbench]);

const [dense, setDense] = useState(false);

useGlobalEvent(
'scroll',
useCallback(() => {
setDense(window.scrollY > 114);
if (!workspaceCardRef.current || !floatWorkspaceCardRef.current) return;
const inFlowTop = workspaceCardRef.current.getBoundingClientRect().top;
const floatTop =
floatWorkspaceCardRef.current.getBoundingClientRect().top;
setDense(inFlowTop <= floatTop);
}, [])
);

const navSearch = useCallback(() => {
startScopedViewTransition(searchVTScope, () => {
workbench.open('/search');
const openSetting = useCallback(() => {
globalDialogService.open('setting', {
activeTab: 'appearance',
});
}, [workbench]);
}, [globalDialogService]);

return (
<div className={clsx(styles.root, { dense })}>
<SafeArea top className={styles.float}>
<div className={styles.headerAndWsSelector}>
<div className={styles.wsSelectorWrapper}>
<WorkspaceSelector />
</div>
<div className={styles.settingWrapper}>
<IconButton
onClick={() => {
globalDialogService.open('setting', {
activeTab: 'appearance',
});
}}
size="24"
style={{ padding: 10 }}
icon={<SettingsIcon />}
/>
</div>
<>
<SafeArea top className={styles.root}>
<div className={styles.headerSettingRow}>
<IconButton onClick={openSetting} size={28} icon={<SettingsIcon />} />
</div>
<div className={styles.searchWrapper}>
<div className={styles.wsSelectorAndSearch}>
<WorkspaceSelector ref={workspaceCardRef} />
<SearchInput placeholder={t['Quick search']()} onClick={navSearch} />
</div>
</SafeArea>
<SafeArea top>
<div className={styles.space} />
{/* float */}
<SafeArea top className={clsx(styles.root, styles.float, { dense })}>
<WorkspaceSelector
className={styles.floatWsSelector}
ref={floatWorkspaceCardRef}
/>
<IconButton
style={{ transition: 'none' }}
onClick={openSetting}
size={28}
icon={<SettingsIcon />}
/>
</SafeArea>
</div>
</>
);
};
74 changes: 21 additions & 53 deletions packages/frontend/core/src/mobile/views/home-header/styles.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
});

0 comments on commit 98bdf25

Please sign in to comment.