mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-31 17:19:56 +08: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) => {
|
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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user