fix(mobile): disable navigation gesture for swipe-dialog (#8993)

This commit is contained in:
CatsJuice
2024-12-12 06:55:15 +00:00
parent 01b6e43c1f
commit 84df2a1d16
6 changed files with 71 additions and 50 deletions
+11 -14
View File
@@ -6,22 +6,19 @@ import { type PropsWithChildren, useCallback } from 'react';
export const ModalConfigProvider = ({ children }: PropsWithChildren) => { export const ModalConfigProvider = ({ children }: PropsWithChildren) => {
const navigationGesture = useService(NavigationGestureService); const navigationGesture = useService(NavigationGestureService);
const onOpenChange = useCallback( const onOpen = useCallback(() => {
(open: boolean) => { const prev = navigationGesture.enabled$.value;
const prev = navigationGesture.enabled$.value; if (prev) {
if (open && !prev) { navigationGesture.setEnabled(false);
navigationGesture.setEnabled(false); return () => {
return () => { navigationGesture.setEnabled(prev);
navigationGesture.setEnabled(prev); };
}; }
} return;
return; }, [navigationGesture]);
},
[navigationGesture]
);
return ( return (
<ModalConfigContext.Provider value={{ onOpenChange }}> <ModalConfigContext.Provider value={{ onOpen }}>
{children} {children}
</ModalConfigContext.Provider> </ModalConfigContext.Provider>
); );
@@ -1,10 +1,12 @@
import { createContext } from 'react'; import { createContext } from 'react';
type OnClose = (() => void) | undefined;
export interface ModalConfig { export interface ModalConfig {
/** /**
* add global callback for modal open/close * add global callback for modal open,
* return a function to handle close/unmount callback
*/ */
onOpenChange?: (open: boolean) => void; onOpen?: () => OnClose;
} }
export const ModalConfigContext = createContext<ModalConfig>({}); export const ModalConfigContext = createContext<ModalConfig>({});
@@ -130,7 +130,7 @@ function createContainer() {
export const ModalInner = forwardRef<HTMLDivElement, ModalProps>( export const ModalInner = forwardRef<HTMLDivElement, ModalProps>(
(props, ref) => { (props, ref) => {
const modalConfig = useContext(ModalConfigContext); const { onOpen: modalConfigOnOpen } = useContext(ModalConfigContext);
const { const {
modal, modal,
portalOptions, portalOptions,
@@ -173,8 +173,9 @@ export const ModalInner = forwardRef<HTMLDivElement, ModalProps>(
); );
useEffect(() => { useEffect(() => {
modalConfig.onOpenChange?.(open ?? false); if (open) return modalConfigOnOpen?.();
}, [modalConfig, open]); return;
}, [modalConfigOnOpen, open]);
useEffect(() => { useEffect(() => {
if (open) { if (open) {
@@ -17,6 +17,7 @@ export interface NavigationBackButtonProps extends IconButtonProps {
* A button to control the back behavior of the mobile app, as well as manage navigation gesture * A button to control the back behavior of the mobile app, as well as manage navigation gesture
*/ */
export const NavigationBackButton = ({ export const NavigationBackButton = ({
icon,
backAction, backAction,
style: propsStyle, style: propsStyle,
...otherProps ...otherProps
@@ -46,7 +47,7 @@ export const NavigationBackButton = ({
size={24} size={24}
style={style} style={style}
onClick={handleRouteBack} onClick={handleRouteBack}
icon={isInsideModal ? <CloseIcon /> : <ArrowLeftSmallIcon />} icon={icon ?? (isInsideModal ? <CloseIcon /> : <ArrowLeftSmallIcon />)}
data-testid="page-header-back" data-testid="page-header-back"
{...otherProps} {...otherProps}
/> />
@@ -1,6 +1,7 @@
import { SafeArea } from '@affine/component'; import { SafeArea } from '@affine/component';
import clsx from 'clsx'; import clsx from 'clsx';
import { forwardRef, type HtmlHTMLAttributes, type ReactNode } from 'react'; import type { HtmlHTMLAttributes, ReactElement, ReactNode } from 'react';
import { forwardRef } from 'react';
import { NavigationBackButton } from '../navigation-back'; import { NavigationBackButton } from '../navigation-back';
import * as styles from './styles.css'; import * as styles from './styles.css';
@@ -11,6 +12,7 @@ export interface PageHeaderProps
* whether to show back button * whether to show back button
*/ */
back?: boolean; back?: boolean;
backIcon?: ReactElement;
/** /**
* Override back button action * Override back button action
*/ */
@@ -51,6 +53,7 @@ export const PageHeader = forwardRef<HTMLDivElement, PageHeaderProps>(
function PageHeader( function PageHeader(
{ {
back, back,
backIcon,
backAction, backAction,
prefix, prefix,
suffix, suffix,
@@ -82,7 +85,9 @@ export const PageHeader = forwardRef<HTMLDivElement, PageHeaderProps>(
className={clsx(styles.prefix, prefixClassName)} className={clsx(styles.prefix, prefixClassName)}
style={prefixStyle} style={prefixStyle}
> >
{back ? <NavigationBackButton backAction={backAction} /> : null} {back ? (
<NavigationBackButton icon={backIcon} backAction={backAction} />
) : null}
{prefix} {prefix}
</section> </section>
@@ -1,5 +1,10 @@
import { Scrollable } from '@affine/component'; import {
InsideModalContext,
ModalConfigContext,
Scrollable,
} from '@affine/component';
import { PageHeader } from '@affine/core/mobile/components'; import { PageHeader } from '@affine/core/mobile/components';
import { ArrowLeftSmallIcon } from '@blocksuite/icons/rc';
import { assignInlineVars } from '@vanilla-extract/dynamic'; import { assignInlineVars } from '@vanilla-extract/dynamic';
import anime from 'animejs'; import anime from 'animejs';
import { import {
@@ -146,6 +151,8 @@ export const SwipeDialog = ({
triggerSize = 10, triggerSize = 10,
onOpenChange, onOpenChange,
}: SwipeDialogProps) => { }: SwipeDialogProps) => {
const insideModal = useContext(InsideModalContext);
const { onOpen: globalOnOpen } = useContext(ModalConfigContext);
const swiperTriggerRef = useRef<HTMLDivElement>(null); const swiperTriggerRef = useRef<HTMLDivElement>(null);
const overlayRef = useRef<HTMLDivElement>(null); const overlayRef = useRef<HTMLDivElement>(null);
const dialogRef = useRef<HTMLDivElement>(null); const dialogRef = useRef<HTMLDivElement>(null);
@@ -202,39 +209,47 @@ export const SwipeDialog = ({
} }
}, [open, prev]); }, [open, prev]);
useEffect(() => {
if (open) return globalOnOpen?.();
return;
}, [globalOnOpen, open]);
if (!open) return null; if (!open) return null;
return ( return (
<SwipeDialogContext.Provider value={{ stack: [...stack, dialogRef] }}> <SwipeDialogContext.Provider value={{ stack: [...stack, dialogRef] }}>
{createPortal( <InsideModalContext.Provider value={insideModal + 1}>
<div className={styles.root}> {createPortal(
<div className={styles.overlay} ref={overlayRef} /> <div className={styles.root}>
<div role="dialog" className={styles.dialog} ref={dialogRef}> <div className={styles.overlay} ref={overlayRef} />
<div className={styles.content}> <div role="dialog" className={styles.dialog} ref={dialogRef}>
<PageHeader <div className={styles.content}>
back <PageHeader
backAction={animateClose} back
className={styles.header} backIcon={<ArrowLeftSmallIcon />}
> backAction={animateClose}
<span className={styles.dialogTitle}>{title}</span> className={styles.header}
</PageHeader> >
<span className={styles.dialogTitle}>{title}</span>
</PageHeader>
<Scrollable.Root className={styles.scrollArea}> <Scrollable.Root className={styles.scrollArea}>
<Scrollable.Viewport>{children}</Scrollable.Viewport> <Scrollable.Viewport>{children}</Scrollable.Viewport>
<Scrollable.Scrollbar orientation="vertical" /> <Scrollable.Scrollbar orientation="vertical" />
</Scrollable.Root> </Scrollable.Root>
</div>
<div
ref={swiperTriggerRef}
className={styles.swipeBackTrigger}
style={assignInlineVars({
[styles.triggerSizeVar]: `${triggerSize}px`,
})}
/>
</div> </div>
<div </div>,
ref={swiperTriggerRef} document.body
className={styles.swipeBackTrigger} )}
style={assignInlineVars({ </InsideModalContext.Provider>
[styles.triggerSizeVar]: `${triggerSize}px`,
})}
/>
</div>
</div>,
document.body
)}
</SwipeDialogContext.Provider> </SwipeDialogContext.Provider>
); );
}; };