React ์ฝ๋ ์ด๋์์๋ ๋ชจ๋ฌ ์ปดํฌ๋ํธ๋ฅผ ์ง์ ์ด๊ณ , ๋ซํ ๋์ ๊ฒฐ๊ณผ๋ฅผ Promise๋ก ๋ฐ์ ์ ์๋ ํ์
-์ธ์ดํํ ๋ชจ๋ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
๋๋ค.
@reactleaf/modal์ ์ฝ๋ ์ด๋์์๋ ๋ชจ๋ฌ์ ์ด๊ณ , ์ฌ๋ฌ ๋ชจ๋ฌ์ ์ค์ฒฉํด์ ๋์ธ ์ ์๋ React ๋ชจ๋ฌ ๋งค๋์ ์
๋๋ค. ์ด๋ฏธ ์ด๋ฆฐ ๋ชจ๋ฌ ์์ ํ์ธ, ์
๋ ฅ, ๊ฒฝ๊ณ ๋ชจ๋ฌ์ ๋ค์ ์ด ์ ์๊ณ , ๊ฐ ๋ชจ๋ฌ์ ์ด๋ฆฐ ์์๋๋ก stack์ ์์
๋๋ค.
@reactleaf/modal v2๋ v1์ register ๊ธฐ๋ฐ ๊ตฌ์กฐ๋ฅผ ์ ๊ฑฐํ๊ณ , ๋ชจ๋ฌ ์ปดํฌ๋ํธ๋ฅผ ์ง์ ์ ๋ฌํ๋ ๋ฐฉ์์ผ๋ก ๋์ํฉ๋๋ค.
์ฑ์์ ModalManager ์ธ์คํด์ค๋ฅผ ํ๋ ๋ง๋ค๊ณ , ๊ทธ ์ธ์คํด์ค๋ฅผ ModalProvider์ ์ฐ๊ฒฐํ ๋ค, ํ์ํ ๊ณณ์์ modal.open(Component, props, options)๋ฅผ ํธ์ถํฉ๋๋ค. ๋ชจ๋ฌ ๋ด๋ถ์์๋ useModalInstance()๋ก ํ์ฌ ๋ชจ๋ฌ์ ํ์ ์ํ, ์๊ธฐ ์์ ์ ๋ซ๋ ํจ์, ์๊ธฐ ์์ ์ ๋ค์ ๋ชจ๋ฌ๋ก ๊ต์ฒดํ๋ ํจ์๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
- ๋ฌธ์์ด ์ด๋ฆ์ด๋ register ํ์ผ ์์ด ๋ชจ๋ฌ ์ปดํฌ๋ํธ๋ฅผ ์ง์ ์ฝ๋๋ค. SSR ๊ณผ์ ์์ import๊ฐ ํ์ ๋์ง ์์ ์คํ์ผ์ ์ถ์ถํ ์ ์๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค.
- Browser Back button์ ๋๋ ์ ๋ ๋ชจ๋ฌ์ด ๋ซํ๋ ๋์์ ํ ์ ์๋๋ก, ๊ธฐ๋ฅ์ ์ถ๊ฐํฉ๋๋ค. (ํนํ, Android ๋๋ฐ์ด์ค์์)
register.ts๊ฐ ํ์ํ์ง ์์ต๋๋ค.useModal()๊ณผcreateModalHook()์ด ์ ๊ฑฐ๋์์ต๋๋ค.- ๋ฌธ์์ด ๊ธฐ๋ฐ ๋ชจ๋ฌ ์ด๋ฆ ๋์ ์ปดํฌ๋ํธ๋ฅผ ์ง์ ์ ๋ฌํฉ๋๋ค.
BasicModalProps๊ฐ ์ ๊ฑฐ๋์์ต๋๋ค.modal.open(Component, props?, options?)๋ ๋ชจ๋ฌ์ด ๋ซํ ๋ resolve๋๋Promise๋ฅผ ๋ฐํํฉ๋๋ค.- ๋ชจ๋ฌ ์ปดํฌ๋ํธ๋
useModalInstance()๋กvisible,closeSelf,replaceSelf๋ฅผ ์ฝ์ต๋๋ค.
npm install @reactleaf/modal
# ๋๋
yarn add @reactleaf/modal
# ๋๋
pnpm add @reactleaf/modal๊ธฐ๋ณธ ๋ ์ด์ด์ dim ์คํ์ผ์ ์ฌ์ฉํ๋ ค๋ฉด ์ฑ ์ํธ๋ฆฌ์์ ์คํ์ผ์ํธ๋ฅผ ํ ๋ฒ importํฉ๋๋ค.
import "@reactleaf/modal/style.css";ModalProvider์ modal.open(...) ํธ์ถ๋ถ๋ ๊ฐ์ ModalManager ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค. ๋ณดํต ๋ณ๋ ํ์ผ์์ ํ๋๋ง ๋ง๋ค๊ณ exportํฉ๋๋ค.
// modal.ts
import { ModalManager } from "@reactleaf/modal";
export const modal = new ModalManager();๋ง๋ค์ด์ง Manager๋ฅผ Provider์ ์ฐ๊ฒฐํฉ๋๋ค.
import { ModalProvider } from "@reactleaf/modal";
import { modal } from "./modal";
function App() {
return (
<ModalProvider
manager={modal}
defaultLayerOptions={{ closeDelay: 180, closeOnOutsideClick: true, dim: true }}
rootOptions={{ preventScroll: true }}
>
<YourApp />
</ModalProvider>
);
}ModalProvider๋ children์ ๊ทธ๋๋ก ๋ ๋๋งํ๋ฉด์, ์ด๋ฆฐ ๋ชจ๋ฌ๋ค์ ์ํ ๋ ์ด์ด ์ปจํ
์ด๋๋ฅผ ํจ๊ป ๋ ๋๋งํฉ๋๋ค.
๋ชจ๋ฌ์ ์ผ๋ฐ React ์ปดํฌ๋ํธ์
๋๋ค. visible์ ์ด๋ฆผ/๋ซํ ์ ๋๋ฉ์ด์
์ํ์ ์ฌ์ฉํ ์ ์๊ณ , closeSelf(result)๋ ํ์ฌ ๋ชจ๋ฌ์ ๋ซ์ผ๋ฉด์ modal.open(...)์ Promise๋ฅผ result๋ก resolveํฉ๋๋ค. useModalInstance<Result>()์ ์ ๋ฌํ๋ ์ ๋ค๋ฆญ์ closeSelf(...)๊ฐ ๋ฐ์ ์ ์๋ ๊ฐ์ ํ์
์ ์ ํํ๋ ์ฉ๋์
๋๋ค.
import { ModalComponent, useModalInstance } from "@reactleaf/modal";
export type ConfirmProps = {
message: string;
};
export const Confirm: ModalComponent<ConfirmProps, boolean> = ({ message }) => {
const { visible, closeSelf } = useModalInstance<boolean>();
return (
<div className={visible ? "confirm visible" : "confirm"}>
<p>{message}</p>
<button onClick={() => closeSelf(false)}>์ทจ์</button>
<button onClick={() => closeSelf(true)}>ํ์ธ</button>
</div>
);
};
// ์ด ํ์
๋ชจ๋ฌ์๋ง ์ ์ฉ๋๋ ํน์ ํ ์ต์
์ ์ค์ ํ ์ ์์ต๋๋ค.
Confirm.layerOptions = {
closeOnOutsideClick: false,
};import { modal } from "./modal";
import Confirm from "./modals/Confirm";
async function deleteItem() {
const confirmed = await modal.open(Confirm, {
message: "์ ๋ง ์ญ์ ํ ๊น์?",
});
if (!confirmed) return;
await requestDelete();
}๋ชจ๋ฌ stack์ ๊ด๋ฆฌํ๋ controller ์ธ์คํด์ค๋ฅผ ๋ง๋ญ๋๋ค.
import { ModalManager } from "@reactleaf/modal";
export const modal = new ModalManager();ํ ์ฑ์์ ์ฌ๋ฌ manager๋ฅผ ๋ง๋ค ์๋ ์์ง๋ง, ์ด๋ค ModalProvider์ ์ฐ๊ฒฐ๋ manager์ธ์ง ํธ์ถ๋ถ๊ฐ ๋ช
ํํด์ผ ํฉ๋๋ค.
ํน์ manager์ ๋ชจ๋ฌ stack์ ๋ ๋๋งํฉ๋๋ค.
<ModalProvider
manager={modal}
defaultLayerOptions={{ closeDelay: 180, dim: true }}
rootOptions={{ preventScroll: true }}
>
<App />
</ModalProvider>Props:
manager: ModalManagerdefaultLayerOptions?: Partial<LayerOptions>rootOptions?: Partial<RootOptions>children: React.ReactNode
๋ชจ๋ฌ์ ์ด๊ณ , ๋ซํ ๋ resolve๋๋ Promise๋ฅผ ๋ฐํํฉ๋๋ค.
const result = await modal.open(Alert, {
message: "์ ์ฅ๋์์ต๋๋ค.",
});ํ์ props๊ฐ ์๋ ๋ชจ๋ฌ์ ๋ ๋ฒ์งธ ์ธ์๋ก props๋ฅผ ์ ๋ฌํด์ผ ํฉ๋๋ค. props๊ฐ ์๊ฑฐ๋ ๋ชจ๋ optional์ด๋ฉด ๋ ๋ฒ์งธ ์ธ์๋ฅผ ์๋ตํ ์ ์์ต๋๋ค. props๊ฐ ์๋ ๋ชจ๋ฌ์ options๋ง ์ ๋ฌํ๋ ค๋ฉด ๋ ๋ฒ์งธ ์ธ์์ null์ ๋ฃ์ต๋๋ค.
await modal.open(EmptyModal);
await modal.open(EmptyModal, null, { closeOnOutsideClick: false });๋ชจ๋ฌ ์ปดํฌ๋ํธ๊ฐ ModalComponent<Props, Result>๋ก ํ์
์ง์ ๋์ด ์์ผ๋ฉด modal.open(...)์ props์ ๊ฒฐ๊ณผ ํ์
์ ํจ๊ป ์ถ๋ก ํฉ๋๋ค. ํ์
์ง์ ๋์ง ์์ ์ปดํฌ๋ํธ์์ ๊ฒฐ๊ณผ ํ์
์ ์ง์ ๋ช
์ํ๊ณ ์ถ์ ๋๋ ๋ ๋ฒ์งธ ์ ๋ค๋ฆญ ์ธ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ค์ ๋ฐํ ํ์
์๋ ์ฌ์ฉ์ ์ง์ ๊ฒฐ๊ณผ ํ์
์ ๋ํด ModalAborted | ModalReplaced | undefined๊ฐ ํฌํจ๋ ์ ์์ต๋๋ค.
const name = await modal.open(Prompt, {
title: "์ด๋ฆ์ ์
๋ ฅํ์ธ์.",
});Resolve ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ๋ชจ๋ฌ ๋ด๋ถ์์ ๋ซ๋ ํ๋ก์ฐ
closeSelf(value)->valuecloseSelf()->undefined
- ๋ชจ๋ฌ ์ธ๋ถ์์ ๋ซ์ ๋
- ๋ฐ๊นฅ ์์ญ ํด๋ฆญ /
Escape/ ๋ธ๋ผ์ฐ์ ๋ค๋ก๊ฐ๊ธฐ ->undefined modal.closeWithResult(id, value)->valuemodal.close(id)/modal.closeTop()/modal.closeAll()->undefined
- ๋ฐ๊นฅ ์์ญ ํด๋ฆญ /
- ๋ณ๋์ Abort Controller๋ฅผ ํตํด ๋ซ๋ ๊ฒฝ์ฐ
abortController.abort()->MODAL_ABORTED
replaceSelf(...)๋ก ๊ต์ฒด๋๋ ๊ฒฝ์ฐ- ๊ต์ฒด๋๋ ์ด์ ๋ชจ๋ฌ์
open()Promise ->MODAL_REPLACED
- ๊ต์ฒด๋๋ ์ด์ ๋ชจ๋ฌ์
์ง์ ํ id์ ๋ชจ๋ฌ์ ๋ซ๊ณ , ํด๋น open() Promise๋ฅผ result๋ก resolveํฉ๋๋ค.
modal.closeWithResult(id, { confirmed: true });
modal.closeWithResult(id, { confirmed: true }, { historyBack: true });์ง์ ํ id์ ๋ชจ๋ฌ์ ๋ซ๊ณ , ํด๋น open() Promise๋ฅผ undefined๋ก resolveํฉ๋๋ค.
modal.close(id);๊ฐ์ฅ ์์ ์๋ ๋ชจ๋ฌ์ ๋ซ์ต๋๋ค.
modal.closeTop();
modal.closeTop({ historyBack: true });closeWithResult(id, result), close(id), closeTop() ๋ฑ์ ๋ชจ๋ฌ ๋ซ๊ธฐ ์์ฒญ ํจ์๋
๋ซ์ ์ ์๋ ๋ชจ๋ฌ์ด ์๋ ๊ฒฝ์ฐ ์๋ฌด ์ผ๋ ํ์ง ์์ต๋๋ค.
ํ์ฌ ์ด๋ ค ์๋ ๋ชจ๋ ๋ชจ๋ฌ์ ๋ซ์ต๋๋ค.
modal.closeAll();์ด๋ ค ์๋ ๋ชจ๋ฌ์ด ํ๋๋ผ๋ ์๋์ง ํ์ธํฉ๋๋ค.
if (modal.hasOpenModals()) {
console.log("๋ชจ๋ฌ์ด ์ด๋ ค ์์ต๋๋ค.");
}ํ์ฌ ๋ชจ๋ฌ stack์ ์ฝ๊ธฐ ์ ์ฉ ์ค๋ ์ท์ ๋ฐํํฉ๋๋ค.
const opened = modal.getSnapshot();
if (opened.some((item) => item.Component === Confirm)) {
console.log("Confirm์ด ์ด๋ฏธ ์ด๋ ค ์์ต๋๋ค.");
}์ค๋
์ท ํญ๋ชฉ์๋ id, Component, props, options๊ฐ ํฌํจ๋ฉ๋๋ค.
๋ชจ๋ฌ stack ๋ณํ๋ฅผ ๊ตฌ๋ ํฉ๋๋ค. ์ผ๋ฐ์ ์ธ ์ฌ์ฉ์์๋ ํ์ํ์ง ์์ง๋ง, ๋๋ฒ๊น ํจ๋์ด๋ ์ธ๋ถ ์ํ ์ฐ๋์ฒ๋ผ stack์ ๊ด์ฐฐํด์ผ ํ๋ ๊ฒฝ์ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค.
const unsubscribe = modal.subscribe((stack) => {
console.log("์ด๋ฆฐ ๋ชจ๋ฌ ์:", stack.length);
});
unsubscribe();๋ชจ๋ฌ ์ปดํฌ๋ํธ ๋ด๋ถ์์ ํ์ฌ ๋ชจ๋ฌ ์ธ์คํด์ค์ ์ํ์ ๋ซ๊ธฐ ํจ์๋ฅผ ์ฝ์ต๋๋ค.
const { visible, closeSelf, replaceSelf } = useModalInstance();์ ๊ณต ๊ฐ:
visible: booleancloseSelf(result?): Promise<void>replaceSelf(Component, props?, options?): Promise
useModalInstance()๋ ์ปจํ
์คํธ๋ฅผ ์ฌ์ฉํ๋ฏ๋ก, modal.open(...)์ผ๋ก ์ด๋ฆฐ ๋ชจ๋ฌ ์ปดํฌ๋ํธ ์์์๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค.
ํ์ฌ ๋ชจ๋ฌ ์ธ์คํด์ค์ layer๋ฅผ ์ ์งํ ์ฑ content๋ง ์ ๋ชจ๋ฌ๋ก ๊ต์ฒดํฉ๋๋ค.
import { useModalInstance } from "@reactleaf/modal";
import CodeModal from "./CodeModal";
function EmailModal({ onVerified }: EmailModalProps) {
const { replaceSelf } = useModalInstance();
async function submitEmail(email: string) {
await sendVerificationCode(email);
const verified = await replaceSelf(CodeModal, {
email,
});
if (verified) {
await onVerified();
}
}
}replaceSelf(...)๋ ์ ๋ชจ๋ฌ์ด ๋ซํ ๋ ๊ทธ ๊ฒฐ๊ณผ๋ก resolve๋ฉ๋๋ค. ๊ต์ฒด๋๋ ์ด์ ๋ชจ๋ฌ์ open() Promise๋ MODAL_REPLACED๋ก resolve๋ฉ๋๋ค.
export interface LayerOptions {
className?: string;
closeDelay?: number;
closeOnOutsideClick?: boolean;
dim?: boolean | string;
}
export interface RootOptions {
preventScroll?: boolean;
}
export interface ModalOptions extends LayerOptions {
abortController?: AbortController;
}๋ ์ด์ด ์ต์ ์ ์๋ ์์๋ก ๋ณํฉ๋ฉ๋๋ค. ๋ค์ ๊ฐ์ด ์์ ๊ฐ์ ๋ฎ์ด์๋๋ค.
ModalProvider์defaultLayerOptions- ๋ชจ๋ฌ ์ปดํฌ๋ํธ์
layerOptions modal.open(...)ํธ์ถ ์ ์ ๋ฌํ options
<ModalProvider manager={modal} defaultLayerOptions={{ closeOnOutsideClick: true, dim: true }}>
<App />
</ModalProvider>;
Confirm.layerOptions = {
closeOnOutsideClick: false,
};
await modal.open(Confirm, { message: "์งํํ ๊น์?" }, { className: "confirm-layer" });ModalComponent<Props, Result> ํ์
์ ์ฌ์ฉํ๋ฉด ์ปดํฌ๋ํธ์ layerOptions๋ฅผ ์ ์ธํ๊ณ , modal.open(...)์ด ์ถ๋ก ํ ๊ฒฐ๊ณผ ํ์
์ ํจ๊ป ๋
ธ์ถํ ์ ์์ต๋๋ค.
import { ModalComponent } from "@reactleaf/modal";
type AlertProps = {
message: string;
};
const Alert: ModalComponent<AlertProps> = ({ message }) => {
return <div>{message}</div>;
};
Alert.layerOptions = {
closeOnOutsideClick: false,
};const confirmed = await modal.open(Confirm, {
message: "์ ๋ง ์ญ์ ํ์๊ฒ ์ต๋๊น?",
});
if (confirmed) {
await deleteItem();
}const title = await modal.open(Prompt, {
title: "๋ฌธ์ ์ ๋ชฉ",
placeholder: "์ ๋ชฉ์ ์
๋ ฅํ์ธ์.",
});
if (title) {
await saveTitle(title);
}import { MODAL_ABORTED } from "@reactleaf/modal";
const controller = new AbortController();
const timer = window.setTimeout(() => controller.abort(), 3000);
const result = await modal.open(Alert, { message: "3์ด ํ ์๋์ผ๋ก ๋ซํ๋๋ค." }, { abortController: controller });
window.clearTimeout(timer);
if (result === MODAL_ABORTED) {
console.log("๋ชจ๋ฌ์ด abort๋ก ๋ซํ์ต๋๋ค.");
}manager ์ธ์คํด์ค๋ฅผ importํ ์ ์๋ ๊ณณ์ด๋ผ๋ฉด React ์ปดํฌ๋ํธ ๋ฐ์์๋ ๋ชจ๋ฌ์ ์ด ์ ์์ต๋๋ค.
import { modal } from "./modal";
import Alert from "./modals/Alert";
window.addEventListener("error", () => {
void modal.open(Alert, { message: "์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค." });
});๋ชจ๋ฌ ๋ ์ด์ด๋ ๋ง์ดํธ ์งํ ํ ํ๋ ์ ๋ค์ visible ์ํ๊ฐ ๋ฉ๋๋ค. ๋ ์ด์ด์ dim fade๋ .modal-layer.visible๋ก, ๋ชจ๋ฌ content์ ์ด๋ฆผ/๋ซํ ์ ๋๋ฉ์ด์
์ .modal-layer[data-content-visible="true"]๋ก ๊ตฌํํ ์ ์์ต๋๋ค.
๋ซํ ์ ๋๋ฉ์ด์
์ด ํ์ํ๋ฉด closeDelay๋ฅผ CSS transition ์๊ฐ๊ณผ ๋ง์ถฅ๋๋ค.
<ModalProvider manager={modal} defaultLayerOptions={{ closeDelay: 180 }}>
<App />
</ModalProvider>.modal-layer {
opacity: 0;
transition: opacity 180ms ease;
}
.modal-layer.visible {
opacity: 1;
}
.modal-layer > * {
transform: translateY(8px) scale(0.98);
transition: transform 180ms ease;
}
.modal-layer[data-content-visible="true"] > * {
transform: translateY(0) scale(1);
}๋ชจ๋ฌ ๋ด๋ถ UI์๋ ๊ฐ์ ์ํ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
const { visible } = useModalInstance();Escapeํค๋ฅผ ๋๋ฅด๋ฉด ๊ฐ์ฅ ์์ ๋ชจ๋ฌ์ด ๋ซํ๋๋ค.- ๋ธ๋ผ์ฐ์ ๋ค๋ก๊ฐ๊ธฐ๋ฅผ ๋๋ฅด๋ฉด ๊ฐ์ฅ ์์ ๋ชจ๋ฌ์ด ๋ซํ๋๋ค.
closeOnOutsideClick์false๋ก ์ค์ ํ์ง ์์ผ๋ฉด ์ต์์ ๋ชจ๋ฌ์ ๋ฐ๊นฅ ์์ญ ํด๋ฆญ์ผ๋ก ๋ซํ๋๋ค.- ์ฌ๋ฌ ๋ชจ๋ฌ์ ์ด๋ฆฐ ์์๋๋ก stack์ ์์ ๋๋ค.
rootOptions.preventScroll์ดtrue์ด๋ฉด ๋ชจ๋ฌ์ด ์ด๋ ค ์๋ ๋์ body scroll์ ๋ง์ต๋๋ค.dim์ดtrue์ด๋ฉด ํด๋น ๋ชจ๋ฌ layer์dimclass๋ฅผ ์ถ๊ฐํฉ๋๋ค.dim์ ๋ฌธ์์ด์ ์ ๋ฌํ๋ฉด ํด๋น ๋ฌธ์์ด์ ๋ชจ๋ฌ layer์ custom dim class๋ก ์ถ๊ฐํฉ๋๋ค.
๊ธฐ๋ณธ ์คํ์ผ์ํธ๋ฅผ importํด์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
import "@reactleaf/modal/style.css";์ฃผ์ selector:
.modal-layer.modal-layer.visible.modal-layer[data-content-visible="true"].modal-layer.dim
.modal-layer {
display: flex;
align-items: center;
justify-content: center;
}
.modal-layer.dim {
background: rgba(0, 0, 0, 0.6);
}
.modal-layer[data-content-visible="true"] > * {
transform: translateY(0) scale(1);
}๋ ์ด์ด ์์ฒด์ ์ปค์คํ
class๋ฅผ ์ถ๊ฐํ๋ ค๋ฉด className ์ต์
์ ์ฌ์ฉํฉ๋๋ค.
await modal.open(Confirm, { message: "์งํํ ๊น์?" }, { className: "danger-modal-layer" });๊ธฐ๋ณธ dim class ๋์ ๋ค๋ฅธ class๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด dim์ ๋ฌธ์์ด์ ์ ๋ฌํฉ๋๋ค.
await modal.open(Confirm, { message: "์งํํ ๊น์?" }, { dim: "danger-dim" });replaceSelf(...)๋ฅผ ์ฌ์ฉํ๋ฉด ํ์ฌ ๋ชจ๋ฌ์ layer์ dim์ ์ ์งํ ์ฑ content๋ง ๋ค์ ๋ชจ๋ฌ๋ก ๊ต์ฒดํ ์ ์์ต๋๋ค. ์ด๋ฉ์ผ ์
๋ ฅ ํ ์ธ์ฆ๋ฒํธ ์
๋ ฅ์ผ๋ก ๋์ด๊ฐ๋ ๊ฒ์ฒ๋ผ ํ ํ๋ฆ ์์์ ๋จ๊ณ๊ฐ ๋ฐ๋๋ UI์ ์ ํฉํฉ๋๋ค.
import { type ModalComponent, useModalInstance } from "@reactleaf/modal";
import { modal } from "./modal";
import CodeModal from "./CodeModal";
import EmailModal from "./EmailModal";
type EmailModalProps = {
onVerified: () => Promise<void>;
};
function startEmailVerification() {
void modal.open(EmailModal, {
onVerified: completeSignIn,
});
}
const EmailModal: ModalComponent<EmailModalProps, never> = ({ onVerified }) => {
const { replaceSelf } = useModalInstance();
async function submitEmail(email: string) {
await sendVerificationCode(email);
const verified = await replaceSelf(CodeModal, {
email,
});
if (verified) {
await onVerified();
}
}
};์ด์ ๋ชจ๋ฌ์ open() Promise๋ MODAL_REPLACED๋ก resolve๋๊ณ , replaceSelf()๊ฐ ๋ฐํํ Promise๋ ์ ๋ชจ๋ฌ์ ๊ฒฐ๊ณผ๋ก resolve๋ฉ๋๋ค. replace ์ค์๋ layer๊ฐ ์ ์ง๋๋ฏ๋ก dim์ด ์ฌ๋ผ์ก๋ค๊ฐ ๋ค์ ๋ํ๋์ง ์๊ณ , content๋ง ๋ซํ ๋ค ์ content๊ฐ ์ด๋ฆฝ๋๋ค.
GitHub Pages์์ ์คํ ๊ฐ๋ฅํ ์์ ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค:
https://reactleaf.github.io/modal/
๋ก์ปฌ์์ ์คํํ๋ ค๋ฉด ๋ค์ ๋ช ๋ น์ด๋ฅผ ์ฌ์ฉํ์ธ์.
pnpm install
pnpm dev:docs