feat: unmount dialog from dom on closed

This commit is contained in:
p-sw 2024-08-07 18:58:53 +09:00
parent 8728df1f60
commit 4b6724ed90
2 changed files with 82 additions and 36 deletions

View File

@ -1,12 +1,20 @@
import { Slot, type VariantProps, useDocument, vcn } from "@pswui-lib";
import React, { type ReactNode, useId, useState } from "react";
import {
Slot,
type VariantProps,
useAnimatedMount,
useDocument,
vcn,
} from "@pswui-lib";
import React, { type ReactNode, useId, useRef, useState } from "react";
import ReactDOM from "react-dom";
import {
DialogContext,
type IDialogContext,
InnerDialogContext,
initialDialogContext,
useDialogContext,
useInnerDialogContext,
} from "./Context";
/**
@ -81,22 +89,35 @@ interface DialogOverlay
const DialogOverlay = React.forwardRef<HTMLDivElement, DialogOverlay>(
(props, ref) => {
const [{ opened, ids }, setContext] = useDialogContext();
const [variantProps, otherPropsCompressed] = resolveDialogOverlayVariant({
...props,
opened,
});
const [variantProps, otherPropsCompressed] =
resolveDialogOverlayVariant(props);
const { children, closeOnClick, onClick, ...otherPropsExtracted } =
otherPropsCompressed;
const internalRef = useRef<HTMLDivElement | null>(null);
const { isMounted, isRendered } = useAnimatedMount(opened, internalRef);
const document = useDocument();
if (!document) return null;
return ReactDOM.createPortal(
return isMounted
? ReactDOM.createPortal(
<div
{...otherPropsExtracted}
id={ids.dialog}
ref={ref}
className={dialogOverlayVariant(variantProps)}
ref={(el) => {
internalRef.current = el;
if (typeof ref === "function") {
ref(el);
} else if (ref) {
ref.current = el;
}
}}
className={dialogOverlayVariant({
...variantProps,
opened: isRendered,
})}
onClick={(e) => {
if (closeOnClick) {
setContext((p) => ({ ...p, opened: false }));
@ -104,17 +125,20 @@ const DialogOverlay = React.forwardRef<HTMLDivElement, DialogOverlay>(
onClick?.(e);
}}
>
{/* Layer for overflow positioning */}
<div
className={
"w-screen max-w-full min-h-screen flex flex-col justify-center items-center"
}
>
{/* Layer for overflow positioning */}
<InnerDialogContext.Provider value={{ isMounted, isRendered }}>
{children}
</InnerDialogContext.Provider>
</div>
</div>,
document.body,
);
)
: null;
},
);
DialogOverlay.displayName = "DialogOverlay";
@ -144,11 +168,10 @@ interface DialogContentProps
const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps>(
(props, ref) => {
const [{ opened, ids }] = useDialogContext();
const [variantProps, otherPropsCompressed] = resolveDialogContentVariant({
...props,
opened,
});
const [{ ids }] = useDialogContext();
const [variantProps, otherPropsCompressed] =
resolveDialogContentVariant(props);
const { isRendered } = useInnerDialogContext();
const { children, onClick, ...otherPropsExtracted } = otherPropsCompressed;
return (
<div
@ -157,7 +180,10 @@ const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps>(
role="dialog"
aria-labelledby={ids.title}
aria-describedby={ids.description}
className={dialogContentVariant(variantProps)}
className={dialogContentVariant({
...variantProps,
opened: isRendered,
})}
onClick={(e) => {
e.stopPropagation();
onClick?.(e);

View File

@ -38,3 +38,23 @@ export const DialogContext = createContext<
]);
export const useDialogContext = () => useContext(DialogContext);
/**
* ===================
* InnerDialogContext
* ===================
*/
export interface IInnerDialogContext {
isMounted: boolean;
isRendered: boolean;
}
export const initialInnerDialogContext: IInnerDialogContext = {
isMounted: false,
isRendered: false,
};
export const InnerDialogContext = createContext<IInnerDialogContext>(
initialInnerDialogContext,
);
export const useInnerDialogContext = () => useContext(InnerDialogContext);