mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-20 07:47:19 +08:00
fix(core): menu not scrollable when opening in modal (#8179)
fix AF-1360 When menu (with modal = false) is rendered in Modal, the [RemoveScroll utility wrapped by Modal](660060a765/packages/react/dialog/src/Dialog.tsx (L203)) will prevent menu from scrolling. The reason why menu is scrollable within a dialog is because it is also wrapped a RemoveScroll [when modal is on. ](660060a765/packages/react/menu/src/Menu.tsx (L305)) In this fix, added a `useWithinModal` utility hook so that menu will automatically assign noportal mode for menu when it is rendered inside of a modal.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import type { MenuProps } from '../menu.types';
|
import type { MenuProps } from '../menu.types';
|
||||||
import * as styles from '../styles.css';
|
import * as styles from '../styles.css';
|
||||||
@@ -9,6 +10,7 @@ import * as desktopStyles from './styles.css';
|
|||||||
export const DesktopMenu = ({
|
export const DesktopMenu = ({
|
||||||
children,
|
children,
|
||||||
items,
|
items,
|
||||||
|
noPortal,
|
||||||
portalOptions,
|
portalOptions,
|
||||||
rootOptions: {
|
rootOptions: {
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
@@ -33,6 +35,7 @@ export const DesktopMenu = ({
|
|||||||
side,
|
side,
|
||||||
sideOffset: (sideOffset ?? 0) + 5,
|
sideOffset: (sideOffset ?? 0) + 5,
|
||||||
});
|
});
|
||||||
|
const ContentWrapper = noPortal ? React.Fragment : DropdownMenu.Portal;
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Root
|
<DropdownMenu.Root
|
||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
@@ -51,7 +54,7 @@ export const DesktopMenu = ({
|
|||||||
{children}
|
{children}
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
|
|
||||||
<DropdownMenu.Portal {...portalOptions}>
|
<ContentWrapper {...portalOptions}>
|
||||||
<DropdownMenu.Content
|
<DropdownMenu.Content
|
||||||
className={clsx(
|
className={clsx(
|
||||||
styles.menuContent,
|
styles.menuContent,
|
||||||
@@ -68,7 +71,7 @@ export const DesktopMenu = ({
|
|||||||
>
|
>
|
||||||
{items}
|
{items}
|
||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
</DropdownMenu.Portal>
|
</ContentWrapper>
|
||||||
</DropdownMenu.Root>
|
</DropdownMenu.Root>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import * as Dialog from '@radix-ui/react-dialog';
|
|||||||
import * as VisuallyHidden from '@radix-ui/react-visually-hidden';
|
import * as VisuallyHidden from '@radix-ui/react-visually-hidden';
|
||||||
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type { CSSProperties, MouseEvent } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { forwardRef, useCallback, useEffect, useState } from 'react';
|
import { forwardRef, useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { startScopedViewTransition } from '../../utils';
|
import { startScopedViewTransition } from '../../utils';
|
||||||
@@ -142,7 +142,6 @@ export const ModalInner = forwardRef<HTMLDivElement, ModalProps>(
|
|||||||
overlayOptions: {
|
overlayOptions: {
|
||||||
className: overlayClassName,
|
className: overlayClassName,
|
||||||
style: overlayStyle,
|
style: overlayStyle,
|
||||||
onClick: onOverlayClick,
|
|
||||||
...otherOverlayOptions
|
...otherOverlayOptions
|
||||||
} = {},
|
} = {},
|
||||||
closeButtonOptions,
|
closeButtonOptions,
|
||||||
@@ -191,19 +190,6 @@ export const ModalInner = forwardRef<HTMLDivElement, ModalProps>(
|
|||||||
[onEscapeKeyDown, persistent]
|
[onEscapeKeyDown, persistent]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOverlayClick = useCallback(
|
|
||||||
(e: MouseEvent<HTMLDivElement>) => {
|
|
||||||
onOverlayClick?.(e);
|
|
||||||
if (persistent) {
|
|
||||||
e.preventDefault();
|
|
||||||
} else {
|
|
||||||
e.stopPropagation();
|
|
||||||
onOpenChange?.(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[onOpenChange, onOverlayClick, persistent]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!container) {
|
if (!container) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -226,74 +212,74 @@ export const ModalInner = forwardRef<HTMLDivElement, ModalProps>(
|
|||||||
style={{
|
style={{
|
||||||
...overlayStyle,
|
...overlayStyle,
|
||||||
}}
|
}}
|
||||||
onClick={handleOverlayClick}
|
|
||||||
{...otherOverlayOptions}
|
{...otherOverlayOptions}
|
||||||
/>
|
|
||||||
<div
|
|
||||||
data-full-screen={fullScreen}
|
|
||||||
data-modal={modal}
|
|
||||||
className={clsx(
|
|
||||||
`anim-${animation}`,
|
|
||||||
styles.modalContentWrapper,
|
|
||||||
contentWrapperClassName
|
|
||||||
)}
|
|
||||||
style={contentWrapperStyle}
|
|
||||||
>
|
>
|
||||||
<Dialog.Content
|
<div
|
||||||
onPointerDownOutside={handlePointerDownOutSide}
|
data-full-screen={fullScreen}
|
||||||
onEscapeKeyDown={handleEscapeKeyDown}
|
data-modal={modal}
|
||||||
className={clsx(styles.modalContent, contentClassName)}
|
className={clsx(
|
||||||
style={{
|
`anim-${animation}`,
|
||||||
...assignInlineVars({
|
styles.modalContentWrapper,
|
||||||
[styles.widthVar]: getVar(
|
contentWrapperClassName
|
||||||
width,
|
)}
|
||||||
fullScreen ? '100dvw' : '50dvw'
|
style={contentWrapperStyle}
|
||||||
),
|
|
||||||
[styles.heightVar]: getVar(
|
|
||||||
height,
|
|
||||||
fullScreen ? '100dvh' : 'unset'
|
|
||||||
),
|
|
||||||
[styles.minHeightVar]: getVar(minHeight, '26px'),
|
|
||||||
}),
|
|
||||||
...contentStyle,
|
|
||||||
}}
|
|
||||||
{...(description ? {} : { 'aria-describedby': undefined })}
|
|
||||||
{...otherContentOptions}
|
|
||||||
ref={ref}
|
|
||||||
>
|
>
|
||||||
{withoutCloseButton ? null : (
|
<Dialog.Content
|
||||||
<Dialog.Close asChild>
|
onPointerDownOutside={handlePointerDownOutSide}
|
||||||
<IconButton
|
onEscapeKeyDown={handleEscapeKeyDown}
|
||||||
size="20"
|
className={clsx(styles.modalContent, contentClassName)}
|
||||||
className={clsx(styles.closeButton, closeButtonClassName)}
|
style={{
|
||||||
aria-label="Close"
|
...assignInlineVars({
|
||||||
data-testid="modal-close-button"
|
[styles.widthVar]: getVar(
|
||||||
{...otherCloseButtonProps}
|
width,
|
||||||
>
|
fullScreen ? '100dvw' : '50dvw'
|
||||||
<CloseIcon />
|
),
|
||||||
</IconButton>
|
[styles.heightVar]: getVar(
|
||||||
</Dialog.Close>
|
height,
|
||||||
)}
|
fullScreen ? '100dvh' : 'unset'
|
||||||
{title ? (
|
),
|
||||||
<Dialog.Title className={styles.modalHeader}>
|
[styles.minHeightVar]: getVar(minHeight, '26px'),
|
||||||
{title}
|
}),
|
||||||
</Dialog.Title>
|
...contentStyle,
|
||||||
) : (
|
}}
|
||||||
// Refer: https://www.radix-ui.com/primitives/docs/components/dialog#title
|
{...(description ? {} : { 'aria-describedby': undefined })}
|
||||||
// If you want to hide the title, wrap it inside our Visually Hidden utility like this <VisuallyHidden asChild>.
|
{...otherContentOptions}
|
||||||
<VisuallyHidden.Root asChild>
|
ref={ref}
|
||||||
<Dialog.Title></Dialog.Title>
|
>
|
||||||
</VisuallyHidden.Root>
|
{withoutCloseButton ? null : (
|
||||||
)}
|
<Dialog.Close asChild>
|
||||||
{description ? (
|
<IconButton
|
||||||
<Dialog.Description className={styles.modalDescription}>
|
size="20"
|
||||||
{description}
|
className={clsx(styles.closeButton, closeButtonClassName)}
|
||||||
</Dialog.Description>
|
aria-label="Close"
|
||||||
) : null}
|
data-testid="modal-close-button"
|
||||||
|
{...otherCloseButtonProps}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Dialog.Close>
|
||||||
|
)}
|
||||||
|
{title ? (
|
||||||
|
<Dialog.Title className={styles.modalHeader}>
|
||||||
|
{title}
|
||||||
|
</Dialog.Title>
|
||||||
|
) : (
|
||||||
|
// Refer: https://www.radix-ui.com/primitives/docs/components/dialog#title
|
||||||
|
// If you want to hide the title, wrap it inside our Visually Hidden utility like this <VisuallyHidden asChild>.
|
||||||
|
<VisuallyHidden.Root asChild>
|
||||||
|
<Dialog.Title></Dialog.Title>
|
||||||
|
</VisuallyHidden.Root>
|
||||||
|
)}
|
||||||
|
{description ? (
|
||||||
|
<Dialog.Description className={styles.modalDescription}>
|
||||||
|
{description}
|
||||||
|
</Dialog.Description>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
</div>
|
</div>
|
||||||
|
</Dialog.Overlay>
|
||||||
</Dialog.Portal>
|
</Dialog.Portal>
|
||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user