feat(mobile): show doc title on title bar when scroll down (#8976)
This commit is contained in:
@@ -31,6 +31,7 @@ export interface PageHeaderProps
|
|||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
centerContent?: boolean;
|
centerContent?: boolean;
|
||||||
|
contentClassName?: string;
|
||||||
|
|
||||||
prefixClassName?: string;
|
prefixClassName?: string;
|
||||||
prefixStyle?: React.CSSProperties;
|
prefixStyle?: React.CSSProperties;
|
||||||
@@ -56,6 +57,7 @@ export const PageHeader = forwardRef<HTMLDivElement, PageHeaderProps>(
|
|||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
centerContent = true,
|
centerContent = true,
|
||||||
|
contentClassName,
|
||||||
prefixClassName,
|
prefixClassName,
|
||||||
prefixStyle,
|
prefixStyle,
|
||||||
suffixClassName,
|
suffixClassName,
|
||||||
@@ -85,7 +87,9 @@ export const PageHeader = forwardRef<HTMLDivElement, PageHeaderProps>(
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
className={clsx(styles.content, { center: centerContent })}
|
className={clsx(styles.content, contentClassName, {
|
||||||
|
center: centerContent,
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import { style } from '@vanilla-extract/css';
|
import { createVar, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
export const root = style({
|
export const root = style({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -18,14 +18,22 @@ export const inner = style({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
});
|
});
|
||||||
|
const contentMaxWidth = createVar('contentMaxWidth');
|
||||||
export const content = style({
|
export const content = style({
|
||||||
|
vars: {
|
||||||
|
[contentMaxWidth]: 'unset',
|
||||||
|
},
|
||||||
|
maxWidth: contentMaxWidth,
|
||||||
selectors: {
|
selectors: {
|
||||||
'&.center': {
|
'&.center': {
|
||||||
|
vars: {
|
||||||
|
// gap(6 * 2) + button(44 * 2) + padding(8 * 2)
|
||||||
|
[contentMaxWidth]: 'calc(100% - 12px - 88px - 16px)',
|
||||||
|
},
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translateX(-50%)',
|
transform: 'translateX(-50%)',
|
||||||
width: 'fit-content',
|
width: 'fit-content',
|
||||||
maxWidth: 'calc(100% - 12px - 88px - 16px)',
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { bodyEmphasized } from '@toeverything/theme/typography';
|
||||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import { globalStyle, style } from '@vanilla-extract/css';
|
import { globalStyle, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
@@ -16,6 +17,26 @@ export const header = style({
|
|||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const headerContent = style({
|
||||||
|
maxWidth: `calc(100% - 200px)`,
|
||||||
|
});
|
||||||
|
export const headerTitle = style([
|
||||||
|
bodyEmphasized,
|
||||||
|
{
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
|
||||||
|
opacity: 0,
|
||||||
|
transition: 'opacity 0.23s ease',
|
||||||
|
selectors: {
|
||||||
|
'&[data-show="true"]': {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
export const mainContainer = style({
|
export const mainContainer = style({
|
||||||
containerType: 'inline-size',
|
containerType: 'inline-size',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-he
|
|||||||
import { PageDetailEditor } from '@affine/core/components/page-detail-editor';
|
import { PageDetailEditor } from '@affine/core/components/page-detail-editor';
|
||||||
import { DetailPageWrapper } from '@affine/core/desktop/pages/workspace/detail-page/detail-page-wrapper';
|
import { DetailPageWrapper } from '@affine/core/desktop/pages/workspace/detail-page/detail-page-wrapper';
|
||||||
import { PageHeader } from '@affine/core/mobile/components';
|
import { PageHeader } from '@affine/core/mobile/components';
|
||||||
|
import { useGlobalEvent } from '@affine/core/mobile/hooks/use-global-events';
|
||||||
|
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
|
||||||
import { EditorService } from '@affine/core/modules/editor';
|
import { EditorService } from '@affine/core/modules/editor';
|
||||||
import { JournalService } from '@affine/core/modules/journal';
|
import { JournalService } from '@affine/core/modules/journal';
|
||||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||||
import { ViewService } from '@affine/core/modules/workbench/services/view';
|
import { ViewService } from '@affine/core/modules/workbench/services/view';
|
||||||
import { i18nTime } from '@affine/i18n';
|
import { i18nTime, useI18n } from '@affine/i18n';
|
||||||
import {
|
import {
|
||||||
BookmarkBlockService,
|
BookmarkBlockService,
|
||||||
customImageProxyMiddleware,
|
customImageProxyMiddleware,
|
||||||
@@ -36,11 +38,10 @@ import {
|
|||||||
useServices,
|
useServices,
|
||||||
WorkspaceService,
|
WorkspaceService,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import { bodyEmphasized } from '@toeverything/theme/typography';
|
|
||||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { AppTabs } from '../../../components';
|
import { AppTabs } from '../../../components';
|
||||||
@@ -219,6 +220,8 @@ const skeletonWithBack = getSkeleton(true);
|
|||||||
const notFound = getNotFound(false);
|
const notFound = getNotFound(false);
|
||||||
const notFoundWithBack = getNotFound(true);
|
const notFoundWithBack = getNotFound(true);
|
||||||
|
|
||||||
|
const checkShowTitle = () => window.scrollY >= 158;
|
||||||
|
|
||||||
const MobileDetailPage = ({
|
const MobileDetailPage = ({
|
||||||
pageId,
|
pageId,
|
||||||
date,
|
date,
|
||||||
@@ -226,8 +229,15 @@ const MobileDetailPage = ({
|
|||||||
pageId: string;
|
pageId: string;
|
||||||
date?: string;
|
date?: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const docDisplayMetaService = useService(DocDisplayMetaService);
|
||||||
const journalService = useService(JournalService);
|
const journalService = useService(JournalService);
|
||||||
|
|
||||||
|
const [showTitle, setShowTitle] = useState(checkShowTitle);
|
||||||
const { openJournal } = useJournalRouteHelper();
|
const { openJournal } = useJournalRouteHelper();
|
||||||
|
const titleInfo = useLiveData(docDisplayMetaService.title$(pageId));
|
||||||
|
const title =
|
||||||
|
typeof titleInfo === 'string' ? titleInfo : t[titleInfo.i18nKey]();
|
||||||
|
|
||||||
const allJournalDates = useLiveData(journalService.allJournalDates$);
|
const allJournalDates = useLiveData(journalService.allJournalDates$);
|
||||||
|
|
||||||
@@ -237,6 +247,12 @@ const MobileDetailPage = ({
|
|||||||
},
|
},
|
||||||
[openJournal]
|
[openJournal]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useGlobalEvent(
|
||||||
|
'scroll',
|
||||||
|
useCallback(() => setShowTitle(checkShowTitle()), [])
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
<DetailPageWrapper
|
<DetailPageWrapper
|
||||||
@@ -247,6 +263,7 @@ const MobileDetailPage = ({
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
back={!date}
|
back={!date}
|
||||||
className={styles.header}
|
className={styles.header}
|
||||||
|
contentClassName={styles.headerContent}
|
||||||
suffix={
|
suffix={
|
||||||
<>
|
<>
|
||||||
<PageHeaderShareButton />
|
<PageHeaderShareButton />
|
||||||
@@ -265,11 +282,11 @@ const MobileDetailPage = ({
|
|||||||
}
|
}
|
||||||
bottomSpacer={94}
|
bottomSpacer={94}
|
||||||
>
|
>
|
||||||
{date ? (
|
<span data-show={!!date || showTitle} className={styles.headerTitle}>
|
||||||
<span className={bodyEmphasized}>
|
{date
|
||||||
{i18nTime(dayjs(date), { absolute: { accuracy: 'month' } })}
|
? i18nTime(dayjs(date), { absolute: { accuracy: 'month' } })
|
||||||
</span>
|
: title}
|
||||||
) : null}
|
</span>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<DetailPageImpl />
|
<DetailPageImpl />
|
||||||
<AppTabs background={cssVarV2('layer/background/primary')} />
|
<AppTabs background={cssVarV2('layer/background/primary')} />
|
||||||
|
|||||||
Reference in New Issue
Block a user