mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
fix(mobile): disable navigation gesture for swipe-dialog (#8993)
This commit is contained in:
@@ -6,22 +6,19 @@ import { type PropsWithChildren, useCallback } from 'react';
|
||||
export const ModalConfigProvider = ({ children }: PropsWithChildren) => {
|
||||
const navigationGesture = useService(NavigationGestureService);
|
||||
|
||||
const onOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
const prev = navigationGesture.enabled$.value;
|
||||
if (open && !prev) {
|
||||
navigationGesture.setEnabled(false);
|
||||
return () => {
|
||||
navigationGesture.setEnabled(prev);
|
||||
};
|
||||
}
|
||||
return;
|
||||
},
|
||||
[navigationGesture]
|
||||
);
|
||||
const onOpen = useCallback(() => {
|
||||
const prev = navigationGesture.enabled$.value;
|
||||
if (prev) {
|
||||
navigationGesture.setEnabled(false);
|
||||
return () => {
|
||||
navigationGesture.setEnabled(prev);
|
||||
};
|
||||
}
|
||||
return;
|
||||
}, [navigationGesture]);
|
||||
|
||||
return (
|
||||
<ModalConfigContext.Provider value={{ onOpenChange }}>
|
||||
<ModalConfigContext.Provider value={{ onOpen }}>
|
||||
{children}
|
||||
</ModalConfigContext.Provider>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
type OnClose = (() => void) | undefined;
|
||||
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>({});
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ function createContainer() {
|
||||
|
||||
export const ModalInner = forwardRef<HTMLDivElement, ModalProps>(
|
||||
(props, ref) => {
|
||||
const modalConfig = useContext(ModalConfigContext);
|
||||
const { onOpen: modalConfigOnOpen } = useContext(ModalConfigContext);
|
||||
const {
|
||||
modal,
|
||||
portalOptions,
|
||||
@@ -173,8 +173,9 @@ export const ModalInner = forwardRef<HTMLDivElement, ModalProps>(
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
modalConfig.onOpenChange?.(open ?? false);
|
||||
}, [modalConfig, open]);
|
||||
if (open) return modalConfigOnOpen?.();
|
||||
return;
|
||||
}, [modalConfigOnOpen, open]);
|
||||
|
||||
useEffect(() => {
|
||||
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
|
||||
*/
|
||||
export const NavigationBackButton = ({
|
||||
icon,
|
||||
backAction,
|
||||
style: propsStyle,
|
||||
...otherProps
|
||||
@@ -46,7 +47,7 @@ export const NavigationBackButton = ({
|
||||
size={24}
|
||||
style={style}
|
||||
onClick={handleRouteBack}
|
||||
icon={isInsideModal ? <CloseIcon /> : <ArrowLeftSmallIcon />}
|
||||
icon={icon ?? (isInsideModal ? <CloseIcon /> : <ArrowLeftSmallIcon />)}
|
||||
data-testid="page-header-back"
|
||||
{...otherProps}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { SafeArea } from '@affine/component';
|
||||
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 * as styles from './styles.css';
|
||||
@@ -11,6 +12,7 @@ export interface PageHeaderProps
|
||||
* whether to show back button
|
||||
*/
|
||||
back?: boolean;
|
||||
backIcon?: ReactElement;
|
||||
/**
|
||||
* Override back button action
|
||||
*/
|
||||
@@ -51,6 +53,7 @@ export const PageHeader = forwardRef<HTMLDivElement, PageHeaderProps>(
|
||||
function PageHeader(
|
||||
{
|
||||
back,
|
||||
backIcon,
|
||||
backAction,
|
||||
prefix,
|
||||
suffix,
|
||||
@@ -82,7 +85,9 @@ export const PageHeader = forwardRef<HTMLDivElement, PageHeaderProps>(
|
||||
className={clsx(styles.prefix, prefixClassName)}
|
||||
style={prefixStyle}
|
||||
>
|
||||
{back ? <NavigationBackButton backAction={backAction} /> : null}
|
||||
{back ? (
|
||||
<NavigationBackButton icon={backIcon} backAction={backAction} />
|
||||
) : null}
|
||||
{prefix}
|
||||
</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 { ArrowLeftSmallIcon } from '@blocksuite/icons/rc';
|
||||
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||
import anime from 'animejs';
|
||||
import {
|
||||
@@ -146,6 +151,8 @@ export const SwipeDialog = ({
|
||||
triggerSize = 10,
|
||||
onOpenChange,
|
||||
}: SwipeDialogProps) => {
|
||||
const insideModal = useContext(InsideModalContext);
|
||||
const { onOpen: globalOnOpen } = useContext(ModalConfigContext);
|
||||
const swiperTriggerRef = useRef<HTMLDivElement>(null);
|
||||
const overlayRef = useRef<HTMLDivElement>(null);
|
||||
const dialogRef = useRef<HTMLDivElement>(null);
|
||||
@@ -202,39 +209,47 @@ export const SwipeDialog = ({
|
||||
}
|
||||
}, [open, prev]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) return globalOnOpen?.();
|
||||
return;
|
||||
}, [globalOnOpen, open]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<SwipeDialogContext.Provider value={{ stack: [...stack, dialogRef] }}>
|
||||
{createPortal(
|
||||
<div className={styles.root}>
|
||||
<div className={styles.overlay} ref={overlayRef} />
|
||||
<div role="dialog" className={styles.dialog} ref={dialogRef}>
|
||||
<div className={styles.content}>
|
||||
<PageHeader
|
||||
back
|
||||
backAction={animateClose}
|
||||
className={styles.header}
|
||||
>
|
||||
<span className={styles.dialogTitle}>{title}</span>
|
||||
</PageHeader>
|
||||
<InsideModalContext.Provider value={insideModal + 1}>
|
||||
{createPortal(
|
||||
<div className={styles.root}>
|
||||
<div className={styles.overlay} ref={overlayRef} />
|
||||
<div role="dialog" className={styles.dialog} ref={dialogRef}>
|
||||
<div className={styles.content}>
|
||||
<PageHeader
|
||||
back
|
||||
backIcon={<ArrowLeftSmallIcon />}
|
||||
backAction={animateClose}
|
||||
className={styles.header}
|
||||
>
|
||||
<span className={styles.dialogTitle}>{title}</span>
|
||||
</PageHeader>
|
||||
|
||||
<Scrollable.Root className={styles.scrollArea}>
|
||||
<Scrollable.Viewport>{children}</Scrollable.Viewport>
|
||||
<Scrollable.Scrollbar orientation="vertical" />
|
||||
</Scrollable.Root>
|
||||
<Scrollable.Root className={styles.scrollArea}>
|
||||
<Scrollable.Viewport>{children}</Scrollable.Viewport>
|
||||
<Scrollable.Scrollbar orientation="vertical" />
|
||||
</Scrollable.Root>
|
||||
</div>
|
||||
<div
|
||||
ref={swiperTriggerRef}
|
||||
className={styles.swipeBackTrigger}
|
||||
style={assignInlineVars({
|
||||
[styles.triggerSizeVar]: `${triggerSize}px`,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
ref={swiperTriggerRef}
|
||||
className={styles.swipeBackTrigger}
|
||||
style={assignInlineVars({
|
||||
[styles.triggerSizeVar]: `${triggerSize}px`,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
</InsideModalContext.Provider>
|
||||
</SwipeDialogContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user