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:
pengx17
2024-09-10 06:09:00 +00:00
parent 9d343bdaa6
commit fb76fdfca3
2 changed files with 70 additions and 81 deletions

View File

@@ -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>
); );
}; };

View File

@@ -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>
); );