feat(mobile): disable swipe back gesture when there is no back in header (#8876)

close AF-1663, AF-1756

- new global `ModalConfigContext`
- new logic to judge whether inside modal
- render `✕` for PageHeader back if inside modal
- only enable `NavigationGesture` when there is `<` in PageHeader
This commit is contained in:
CatsJuice
2024-11-25 03:12:21 +00:00
parent 922db5ced4
commit b369ee0cca
23 changed files with 260 additions and 26 deletions

View File

@@ -1,4 +1,5 @@
import { Button, Modal } from '@affine/component';
import { PageHeader } from '@affine/core/mobile/components';
import { useI18n } from '@affine/i18n';
import clsx from 'clsx';
import {
@@ -8,7 +9,6 @@ import {
type ReactNode,
} from 'react';
import { PageHeader } from '../page-header';
import * as styles from './styles.css';
interface ConfigModalProps {

View File

@@ -1,2 +1 @@
export * from './config-modal';
export * from './page-header';

View File

@@ -1,115 +0,0 @@
import { IconButton, SafeArea } from '@affine/component';
import { ArrowLeftSmallIcon } from '@blocksuite/icons/rc';
import clsx from 'clsx';
import {
forwardRef,
type HtmlHTMLAttributes,
type ReactNode,
useCallback,
} from 'react';
import * as styles from './styles.css';
export interface PageHeaderProps
extends Omit<HtmlHTMLAttributes<HTMLHeadElement>, 'prefix'> {
/**
* whether to show back button
*/
back?: boolean;
/**
* Override back button action
*/
backAction?: () => void;
/**
* prefix content, shown after back button(if exists)
*/
prefix?: ReactNode;
/**
* suffix content
*/
suffix?: ReactNode;
/**
* Weather to center the content
* @default true
*/
centerContent?: boolean;
prefixClassName?: string;
prefixStyle?: React.CSSProperties;
suffixClassName?: string;
suffixStyle?: React.CSSProperties;
}
export const PageHeader = forwardRef<HTMLDivElement, PageHeaderProps>(
function PageHeader(
{
back,
backAction,
prefix,
suffix,
children,
className,
centerContent = true,
prefixClassName,
prefixStyle,
suffixClassName,
suffixStyle,
...attrs
},
ref
) {
const handleRouteBack = useCallback(() => {
backAction ? backAction() : history.back();
}, [backAction]);
return (
<>
<SafeArea
top
ref={ref}
className={clsx(styles.root, className)}
data-testid="mobile-page-header"
{...attrs}
>
<header className={styles.inner}>
<section
className={clsx(styles.prefix, prefixClassName)}
style={prefixStyle}
>
{back ? (
<IconButton
size={24}
style={{ padding: 10 }}
onClick={handleRouteBack}
icon={<ArrowLeftSmallIcon />}
data-testid="page-header-back"
/>
) : null}
{prefix}
</section>
<section
className={clsx(styles.content, { center: centerContent })}
>
{children}
</section>
<section
className={clsx(styles.suffix, suffixClassName)}
style={suffixStyle}
>
{suffix}
</section>
</header>
</SafeArea>
{/* Spacer */}
<SafeArea top>
<div className={styles.headerSpacer} />
</SafeArea>
</>
);
}
);

View File

@@ -1,52 +0,0 @@
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css';
export const root = style({
width: '100%',
position: 'fixed',
top: 0,
zIndex: 1,
backgroundColor: cssVarV2('layer/background/secondary'),
});
export const headerSpacer = style({
height: 44,
});
export const inner = style({
height: 44,
padding: '0 6px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
});
export const content = style({
selectors: {
'&.center': {
position: 'absolute',
left: '50%',
transform: 'translateX(-50%)',
width: 'fit-content',
maxWidth: 'calc(100% - 12px - 88px - 16px)',
display: 'flex',
justifyContent: 'center',
pointerEvents: 'none',
},
'&:not(.center)': {
width: 0,
flex: 1,
},
},
});
export const spacer = style({
width: 0,
flex: 1,
});
export const prefix = style({
display: 'flex',
alignItems: 'center',
gap: 0,
});
export const suffix = style({
display: 'flex',
alignItems: 'center',
gap: 6,
});