'use client'; import { use, useSyncExternalStore, useTransition } from 'react'; import type { DialogProps, ModalOverlayProps } from 'react-aria-components'; import { Button as AriaButton, Dialog, Heading, Modal, ModalOverlay, OverlayTriggerStateContext, } from 'react-aria-components'; import clsx from 'clsx'; import { twMerge } from 'tailwind-merge'; import { Button } from '../button/Button'; interface ModalRootProps extends DialogProps, Pick { className?: string; } const ModalRoot = (props: ModalRootProps) => { const { isOpen, onOpenChange, isKeyboardDismissDisabled, isDismissable, className, ...dialogProps } = props; return ( {props.children} ); }; interface ModalHeaderProps { hasCloseButton?: boolean; children: React.ReactNode; } const ModalHeader = (props: ModalHeaderProps) => { const ctx = use(OverlayTriggerStateContext); const { hasCloseButton = true } = props; return (
{props.children} {hasCloseButton && ( )}
); }; interface ModalBodyProps extends React.PropsWithChildren { className?: string; } const ModalBody = (props: ModalBodyProps) => { return
{props.children}
; }; interface ModalFooterProps extends React.PropsWithChildren {} const ModalFooter = (props: ModalFooterProps) => { return
{props.children}
; }; let _modals: ( | { typeof: 'confirm'; title?: string; content: string; confirm?: boolean; cancelText?: string; onCancel?: () => void; confirmText?: string; onConfirm: (close: () => void) => void; } | { typeof: 'alert'; title?: string; content: string; cancelText?: string; onCancel?: () => void; } )[] = []; let listeners: (() => void)[] = []; function subscribe(listener: () => void) { listeners = [...listeners, listener]; return () => { listeners = listeners.filter((l) => l !== listener); }; } function getSnapshot() { return _modals; } function emitChange() { for (const listener of listeners) { listener(); } } type AlertProps = { title?: string; content?: string; cancelText?: string; onCancel?: () => void; }; const alert = (props: AlertProps) => { _modals = [ ..._modals, { typeof: 'alert', content: props.content || '', cancelText: props.cancelText || '확인', onCancel: props.onCancel, }, ]; emitChange(); }; const confirm = (props: { title?: string; content: string; cancelText?: string; onCancel?: () => void; confirmText?: string; onConfirm: (close: () => void) => void; }) => { _modals = [ ..._modals, { typeof: 'confirm', title: props.title, content: props.content, cancelText: props.cancelText, onCancel: props.onCancel, confirmText: props.confirmText, onConfirm: props.onConfirm, }, ]; emitChange(); }; function getServerSnapshot() { return _modals; } const ModalRegion = () => { const modals = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); const [isPending, startTransition] = useTransition(); return ( <> {modals.map((modal, idx) => ( { if (!isOpen) { _modals = _modals.filter((f) => f !== modal); emitChange(); } }} > {({ close }) => ( <> {modal.title && {modal.title}}

{modal.content}

{modal.typeof === 'confirm' && ( )} )}
))} ); }; export { alert, confirm, ModalBody, ModalFooter, ModalHeader, ModalRegion, ModalRoot }; export type { ModalBodyProps, ModalFooterProps, ModalHeaderProps, ModalRootProps };