Skip to content

Commit

Permalink
feat: 공용 모달 임시 작업 (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
changhui-chan authored Dec 29, 2024
1 parent 1a545c8 commit e4fc2bc
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 0 deletions.
60 changes: 60 additions & 0 deletions src/shared/modal/Modal.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
.overlay {
display: flex;
justify-content: center;
align-items: center;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1000;
}

.container {
background-color: variables.$deep-black;
border-radius: 12px;
padding: 12px 12px;
min-width: 295px;
display: flex;
flex-direction: column;
justify-content: center;
}

.content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

@keyframes fadeIn {
from {
transform: translateY(-20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}

@keyframes fadeOut {
from {
transform: translateY(0);
opacity: 1;
}
to {
transform: translateY(-20px);
opacity: 0;
}
}

.visible {
display: block;
animation: fadeIn 0.3s;
}

.hidden {
animation: fadeOut 0.3s;
}
87 changes: 87 additions & 0 deletions src/shared/modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { useEffect, useRef, ReactNode } from 'react';
import styles from './index.module.scss';
import ModalHeader from './ModalHeader';

export interface ModalProps {
headerText?: string;
children?: ReactNode;
customModalContainerStyle?: string;
customModalContentStyle?: string;
onClose?: () => void;
buttonClick?: () => void;
isVisible?: boolean;
customHeader?: ReactNode;
}

const Modal = ({
headerText = '',
children = null,
customModalContainerStyle = '',
customModalContentStyle = '',
onClose = () => {},
buttonClick = () => {},
isVisible = false,
customHeader = null,
}: ModalProps) => {
const modalRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};

if (isVisible) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}

window.addEventListener('keydown', handleKeyDown);

return () => {
window.removeEventListener('keydown', handleKeyDown);
document.body.style.overflow = '';
};
}, [isVisible, onClose]);

const handleOutsideClick = (e: MouseEvent) => {
if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
onClose();
}
};

const handleKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.key === 'Enter') {
buttonClick();
}
};

return (
<div
className={styles.overlay}
onClick={
handleOutsideClick as unknown as React.MouseEventHandler<HTMLDivElement>
}
onKeyUp={handleKeyPress}
role="button"
tabIndex={0}
>
<div
ref={modalRef}
className={`${styles.container} ${isVisible ? styles.visible : styles.hidden} ${customModalContainerStyle}`}
>
{customHeader ||
(headerText && (
<ModalHeader headerText={headerText} onClose={onClose} />
))}
<div className={`${styles.content} ${customModalContentStyle}`}>
{children}
</div>
</div>
</div>
);
};

export default Modal;
18 changes: 18 additions & 0 deletions src/shared/modal/ModalHeader.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.container {
display: flex;
justify-content: space-between;
align-items: center;

p {
color: variables.$white;
line-height: normal;
font: {
size: 18px;
weight: 500;
}
}

img {
display: block;
}
}
21 changes: 21 additions & 0 deletions src/shared/modal/ModalHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Image from 'next/image';
import CloseIcon from '../../../public/icon/ic-close.svg';
import styles from './ModalHeader.module.scss';

interface ModalHeaderProps {
headerText?: string;
onClose?: () => void;
}

const ModalHeader: React.FC<ModalHeaderProps> = ({ headerText, onClose }) => {
return (
<div className={styles.container}>
<p>{headerText}</p>
<button type="button" onClick={onClose}>
<Image src={CloseIcon} alt="close" />
</button>
</div>
);
};

export default ModalHeader;
26 changes: 26 additions & 0 deletions src/shared/modal/useModalStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { create } from 'zustand';

interface ModalContent {
isVisible: boolean;
content: React.ReactNode;
}

interface ModalState {
modals: { [key: string]: ModalContent };
openModal: (id: string, content: React.ReactNode) => void;
closeModal: (id: string) => void;
}

const useModalStore = create<ModalState>((set) => ({
modals: {},
openModal: (id: string, content: React.ReactNode) =>
set((state) => ({
modals: { ...state.modals, [id]: { isVisible: true, content } },
})),
closeModal: (id: string) =>
set((state) => ({
modals: { ...state.modals, [id]: { isVisible: false, content: null } },
})),
}));

export default useModalStore;

0 comments on commit e4fc2bc

Please sign in to comment.