mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-12 06:43:24 +03:00
fix(component): modal overlap issue (#7691)
This commit is contained in:
parent
33fc00f8c7
commit
bb767a6cdc
@ -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';
|
||||||
|
Loading…
Reference in New Issue
Block a user