fix(component): modal overlap issue (#7691)

This commit is contained in:
EYHN 2024-08-01 08:03:21 +00:00
parent 33fc00f8c7
commit bb767a6cdc
No known key found for this signature in database
GPG Key ID: 46C9E26A75AB276C

View File

@ -10,7 +10,7 @@ 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 } from 'react'; import type { CSSProperties } from 'react';
import { forwardRef, useCallback } from 'react'; import { forwardRef, useCallback, useEffect, useState } from 'react';
import type { IconButtonProps } from '../button'; import type { IconButtonProps } from '../button';
import { IconButton } from '../button'; import { IconButton } from '../button';
@ -90,129 +90,163 @@ class ModalTransitionContainer extends HTMLElement {
} }
} }
let container: ModalTransitionContainer | null = null; let defined = false;
function prepareContainer() { function createContainer() {
if (!container) { if (!defined) {
customElements.define( customElements.define(
'modal-transition-container', 'modal-transition-container',
ModalTransitionContainer ModalTransitionContainer
); );
container = new ModalTransitionContainer(); defined = true;
document.body.append(container);
} }
const container = new ModalTransitionContainer();
document.body.append(container);
return container; return container;
} }
export const Modal = forwardRef<HTMLDivElement, ModalProps>((props, ref) => { export const ModalInner = forwardRef<HTMLDivElement, ModalProps>(
const { (props, ref) => {
modal, const {
portalOptions, modal,
open, portalOptions,
onOpenChange, open,
width, onOpenChange,
height, width,
minHeight = 194, height,
title, minHeight = 194,
description, title,
withoutCloseButton = false, description,
persistent, withoutCloseButton = false,
contentOptions: { persistent,
style: contentStyle, contentOptions: {
className: contentClassName, style: contentStyle,
onPointerDownOutside, className: contentClassName,
onEscapeKeyDown, onPointerDownOutside,
...otherContentOptions onEscapeKeyDown,
} = {}, ...otherContentOptions
overlayOptions: { } = {},
className: overlayClassName, overlayOptions: {
style: overlayStyle, className: overlayClassName,
...otherOverlayOptions style: overlayStyle,
} = {}, ...otherOverlayOptions
closeButtonOptions = {}, } = {},
children, closeButtonOptions = {},
...otherProps children,
} = props; ...otherProps
} = props;
return ( const [container, setContainer] = useState<ModalTransitionContainer | null>(
<Dialog.Root null
modal={modal} );
open={open}
onOpenChange={onOpenChange} useEffect(() => {
{...otherProps} const container = createContainer();
> setContainer(container);
<Dialog.Portal container={prepareContainer()} {...portalOptions}> return () => {
<Dialog.Overlay setTimeout(() => {
className={clsx(styles.modalOverlay, overlayClassName)} container.remove();
style={{ }, 1000) as unknown as number;
...overlayStyle, };
}} }, []);
{...otherOverlayOptions}
/> const handlePointerDownOutSide = useCallback(
<div data-modal={modal} className={clsx(styles.modalContentWrapper)}> (e: PointerDownOutsideEvent) => {
<Dialog.Content onPointerDownOutside?.(e);
onPointerDownOutside={useCallback( persistent && e.preventDefault();
(e: PointerDownOutsideEvent) => { },
onPointerDownOutside?.(e); [onPointerDownOutside, persistent]
persistent && e.preventDefault(); );
},
[onPointerDownOutside, persistent] const handleEscapeKeyDown = useCallback(
)} (e: KeyboardEvent) => {
onEscapeKeyDown={useCallback( onEscapeKeyDown?.(e);
(e: KeyboardEvent) => { persistent && e.preventDefault();
onEscapeKeyDown?.(e); },
persistent && e.preventDefault(); [onEscapeKeyDown, persistent]
}, );
[onEscapeKeyDown, persistent]
)} if (!container) {
className={clsx(styles.modalContent, contentClassName)} return;
}
return (
<Dialog.Root
modal={modal}
open={open}
onOpenChange={onOpenChange}
{...otherProps}
>
<Dialog.Portal container={container} {...portalOptions}>
<Dialog.Overlay
className={clsx(styles.modalOverlay, overlayClassName)}
style={{ style={{
...assignInlineVars({ ...overlayStyle,
[styles.widthVar]: getVar(width, '50vw'),
[styles.heightVar]: getVar(height, 'unset'),
[styles.minHeightVar]: getVar(minHeight, '26px'),
}),
...contentStyle,
}} }}
{...(description ? {} : { 'aria-describedby': undefined })} {...otherOverlayOptions}
{...otherContentOptions} />
ref={ref} <div data-modal={modal} className={clsx(styles.modalContentWrapper)}>
> <Dialog.Content
{withoutCloseButton ? null : ( onPointerDownOutside={handlePointerDownOutSide}
<Dialog.Close asChild> onEscapeKeyDown={handleEscapeKeyDown}
<IconButton className={clsx(styles.modalContent, contentClassName)}
className={styles.closeButton} style={{
aria-label="Close" ...assignInlineVars({
type="plain" [styles.widthVar]: getVar(width, '50vw'),
data-testid="modal-close-button" [styles.heightVar]: getVar(height, 'unset'),
{...closeButtonOptions} [styles.minHeightVar]: getVar(minHeight, '26px'),
> }),
<CloseIcon /> ...contentStyle,
</IconButton> }}
</Dialog.Close> {...(description ? {} : { 'aria-describedby': undefined })}
)} {...otherContentOptions}
{title ? ( ref={ref}
<Dialog.Title className={styles.modalHeader}> >
{title} {withoutCloseButton ? null : (
</Dialog.Title> <Dialog.Close asChild>
) : ( <IconButton
// Refer: https://www.radix-ui.com/primitives/docs/components/dialog#title className={styles.closeButton}
// If you want to hide the title, wrap it inside our Visually Hidden utility like this <VisuallyHidden asChild>. aria-label="Close"
<VisuallyHidden.Root asChild> type="plain"
<Dialog.Title></Dialog.Title> data-testid="modal-close-button"
</VisuallyHidden.Root> {...closeButtonOptions}
)} >
{description ? ( <CloseIcon />
<Dialog.Description className={styles.modalDescription}> </IconButton>
{description} </Dialog.Close>
</Dialog.Description> )}
) : null} {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.Portal> </Dialog.Portal>
</Dialog.Root> </Dialog.Root>
); );
}
);
ModalInner.displayName = 'ModalInner';
export const Modal = forwardRef<HTMLDivElement, ModalProps>((props, ref) => {
if (!props.open) {
return;
}
return <ModalInner {...props} ref={ref} />;
}); });
Modal.displayName = 'Modal'; Modal.displayName = 'Modal';