UNPKG

@divetocode/modal

Version:

A lightweight modal dialog system for React/Next.js

1 lines 12.9 kB
{"version":3,"sources":["../src/AlertModal.tsx","../src/_Conponents/ConfirmButton.tsx","../src/ConfirmModal.tsx"],"sourcesContent":["// src/AlertModal.tsx\r\n\"use client\";\r\n\r\nimport { useEffect, useState, CSSProperties } from \"react\";\r\nimport ConfirmButton from \"./_Conponents/ConfirmButton\";\r\n\r\ntype AlertModalProps = {\r\n isOpen: boolean;\r\n message: string;\r\n onClose: () => void;\r\n\r\n confirmText?: string;\r\n alarmText?: string;\r\n};\r\n\r\nexport default function AlertModal({\r\n isOpen,\r\n message,\r\n onClose,\r\n confirmText,\r\n alarmText,\r\n}: AlertModalProps) {\r\n const [show, setShow] = useState(false);\r\n\r\n // Control mount/unmount for transition\r\n useEffect(() => {\r\n let timer: ReturnType<typeof setTimeout> | undefined;\r\n if (isOpen) {\r\n setShow(true);\r\n } else {\r\n timer = setTimeout(() => setShow(false), 300); // remove after transition\r\n }\r\n return () => timer && clearTimeout(timer);\r\n }, [isOpen]);\r\n\r\n if (!isOpen && !show) return null;\r\n\r\n return (\r\n <div\r\n style={{\r\n position: \"fixed\",\r\n inset: 0,\r\n background: \"rgba(0,0,0,0.4)\",\r\n backdropFilter: \"blur(4px)\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n zIndex: 50,\r\n opacity: isOpen ? 1 : 0,\r\n transition: \"opacity 300ms ease\",\r\n }}\r\n onClick={onClose}\r\n >\r\n <div\r\n onClick={(e) => e.stopPropagation()}\r\n style={{\r\n background: \"#fff\",\r\n borderRadius: \"12px\",\r\n boxShadow:\r\n \"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)\",\r\n width: \"100%\",\r\n maxWidth: \"28rem\",\r\n padding: \"2rem\",\r\n textAlign: \"center\",\r\n transform: isOpen ? \"translateY(0)\" : \"translateY(2.5rem)\",\r\n opacity: isOpen ? 1 : 0,\r\n transition: \"transform 300ms ease, opacity 300ms ease\",\r\n }}\r\n >\r\n <h2\r\n style={{\r\n fontSize: \"1.25rem\",\r\n lineHeight: \"1.75rem\",\r\n fontWeight: 700,\r\n color: \"#1f2937\",\r\n margin: 0,\r\n }}\r\n >\r\n ❗ {alarmText || \"Alarm\"}\r\n </h2>\r\n\r\n <p\r\n style={{\r\n fontSize: \"1rem\",\r\n lineHeight: \"1.5rem\",\r\n color: \"#374151\",\r\n marginTop: \"1.5rem\",\r\n marginBottom: 0,\r\n }}\r\n >\r\n {message}\r\n </p>\r\n\r\n <div style={{ marginTop: \"1.5rem\" }}>\r\n <ConfirmButton onClick={onClose}>\r\n {confirmText || \"Confirm\"}\r\n </ConfirmButton>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n}","\"use client\";\r\n\r\nimport { ButtonHTMLAttributes, ReactNode } from \"react\";\r\n\r\ntype PrimaryButtonProps = {\r\n children: ReactNode;\r\n className?: string;\r\n} & ButtonHTMLAttributes<HTMLButtonElement>;\r\n\r\nexport default function ConfirmButton({\r\n children,\r\n className = \"\",\r\n ...props\r\n}: PrimaryButtonProps) {\r\n const baseStyles =\r\n \"w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed\";\r\n\r\n return (\r\n <button\r\n {...props}\r\n className={`${baseStyles} ${className}`}\r\n >\r\n {children}\r\n </button>\r\n );\r\n}\r\n","// src/ConfirmModal.tsx\r\n\"use client\";\r\n\r\nimport { useEffect, useId, useRef, useState, CSSProperties } from \"react\";\r\n\r\ntype ConfirmModalProps = {\r\n isOpen: boolean;\r\n message: string;\r\n onYes: () => void;\r\n onNo: () => void;\r\n\r\n title?: string;\r\n yesText?: string;\r\n noText?: string;\r\n closeOnBackdrop?: boolean;\r\n};\r\n\r\nexport default function ConfirmModal({\r\n isOpen,\r\n message,\r\n onYes,\r\n onNo,\r\n title = \"Confirm\",\r\n yesText = \"Yes\",\r\n noText = \"No\",\r\n closeOnBackdrop = true,\r\n}: ConfirmModalProps) {\r\n const [show, setShow] = useState(false);\r\n const titleId = useId();\r\n const descId = useId();\r\n const yesBtnRef = useRef<HTMLButtonElement | null>(null);\r\n\r\n // Mount/unmount transition\r\n useEffect(() => {\r\n let timer: ReturnType<typeof setTimeout> | undefined;\r\n if (isOpen) {\r\n setShow(true);\r\n } else {\r\n timer = setTimeout(() => setShow(false), 300);\r\n }\r\n return () => timer && clearTimeout(timer);\r\n }, [isOpen]);\r\n\r\n // Focus \"Yes\" button on open\r\n useEffect(() => {\r\n if (isOpen) {\r\n const t = setTimeout(() => yesBtnRef.current?.focus(), 50);\r\n return () => clearTimeout(t);\r\n }\r\n }, [isOpen]);\r\n\r\n // ESC key closes modal (triggers onNo)\r\n useEffect(() => {\r\n if (!isOpen) return;\r\n const onKey = (e: KeyboardEvent) => {\r\n if (e.key === \"Escape\") {\r\n e.preventDefault();\r\n onNo();\r\n }\r\n };\r\n window.addEventListener(\"keydown\", onKey);\r\n return () => window.removeEventListener(\"keydown\", onKey);\r\n }, [isOpen, onNo]);\r\n\r\n if (!isOpen && !show) return null;\r\n\r\n const handleBackdropClick = () => {\r\n if (closeOnBackdrop) onNo();\r\n };\r\n\r\n // === 스타일 객체 ===\r\n const overlayStyle: CSSProperties = {\r\n position: \"fixed\",\r\n inset: 0,\r\n background: \"rgba(0, 0, 0, 0.4)\",\r\n backdropFilter: \"blur(4px)\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n zIndex: 50,\r\n opacity: isOpen ? 1 : 0,\r\n transition: \"opacity 300ms ease\",\r\n };\r\n\r\n const contentStyle: CSSProperties = {\r\n background: \"#ffffff\",\r\n borderRadius: \"12px\",\r\n boxShadow:\r\n \"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)\",\r\n width: \"100%\",\r\n maxWidth: \"28rem\",\r\n padding: \"2rem\",\r\n textAlign: \"center\",\r\n transform: isOpen ? \"translateY(0)\" : \"translateY(2.5rem)\",\r\n opacity: isOpen ? 1 : 0,\r\n transition: \"transform 300ms ease, opacity 300ms ease\",\r\n };\r\n\r\n const titleStyle: CSSProperties = {\r\n fontSize: \"1.25rem\",\r\n lineHeight: \"1.75rem\",\r\n fontWeight: 700,\r\n color: \"#1f2937\",\r\n margin: 0,\r\n };\r\n\r\n const messageStyle: CSSProperties = {\r\n fontSize: \"1rem\",\r\n lineHeight: \"1.5rem\",\r\n color: \"#374151\",\r\n marginTop: \"1.5rem\",\r\n marginBottom: 0,\r\n };\r\n\r\n const actionsStyle: CSSProperties = {\r\n marginTop: \"1rem\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n gap: \"0.75rem\",\r\n };\r\n\r\n const baseBtnStyle: CSSProperties = {\r\n display: \"inline-flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n borderRadius: \"0.5rem\",\r\n padding: \"0.5rem 1rem\",\r\n fontSize: \"1rem\",\r\n fontWeight: 500,\r\n cursor: \"pointer\",\r\n transition: \"background 150ms ease, border-color 150ms ease, color 150ms ease\",\r\n outline: \"none\",\r\n };\r\n\r\n const yesBtnStyle: CSSProperties = {\r\n ...baseBtnStyle,\r\n color: \"#fff\",\r\n background: \"#2563eb\",\r\n border: \"none\",\r\n };\r\n\r\n const noBtnStyle: CSSProperties = {\r\n ...baseBtnStyle,\r\n color: \"#374151\",\r\n background: \"#fff\",\r\n border: \"1px solid #d1d5db\",\r\n };\r\n\r\n return (\r\n <div\r\n style={overlayStyle}\r\n onClick={handleBackdropClick}\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n aria-labelledby={titleId}\r\n aria-describedby={descId}\r\n >\r\n <div onClick={(e) => e.stopPropagation()} style={contentStyle}>\r\n <h2 id={titleId} style={titleStyle}>\r\n {title}\r\n </h2>\r\n\r\n <p id={descId} style={messageStyle}>\r\n {message}\r\n </p>\r\n\r\n <div style={actionsStyle}>\r\n <button\r\n ref={yesBtnRef}\r\n type=\"button\"\r\n onClick={onYes}\r\n style={yesBtnStyle}\r\n >\r\n {yesText}\r\n </button>\r\n <button type=\"button\" onClick={onNo} style={noBtnStyle}>\r\n {noText}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n}"],"mappings":"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","names":["jsx","useEffect","useState","jsx","jsxs"]}