diff --git a/src/app/preview/modal/Some.tsx b/src/app/preview/modal/Some.tsx new file mode 100644 index 0000000..fe9cbc7 --- /dev/null +++ b/src/app/preview/modal/Some.tsx @@ -0,0 +1,158 @@ +import { useState } from 'react'; +import React from 'react'; + +import { Button } from '../../../components/ui/Button'; +import Modal from '../../../components/ui/modal/Modal'; + +interface UserData { + id: number; + name: string; + status: '대기' | '승인' | '거절' | '강퇴'; + introduction: string; +} + +const mockUsers: UserData[] = [ + { + id: 1, + name: '김민수', + status: '승인', + introduction: + '안녕하세요! 웹 개발자 김민수입니다. React와 TypeScript를 주로 사용합니다.', + }, + { + id: 2, + name: '이지원', + status: '대기', + introduction: '백엔드 개발자 이지원입니다. Spring과 Node.js를 다룹니다.', + }, + { + id: 3, + name: '박서연', + status: '거절', + introduction: + 'UI/UX 디자이너 박서연입니다. 사용자 경험 개선에 관심이 많습니다.', + }, + { + id: 4, + name: '최준호', + status: '승인', + introduction: + '모바일 앱 개발자 최준호입니다. Flutter와 React Native를 사용합니다.', + }, + { + id: 5, + name: '정다은', + status: '강퇴', + introduction: + '프론트엔드 개발자 정다은입니다. Vue.js와 React를 주로 사용합니다.', + }, + { + id: 6, + name: '강현우', + status: '승인', + introduction: '풀스택 개발자 강현우입니다. MERN 스택을 주로 사용합니다.', + }, + { + id: 7, + name: '손미나', + status: '대기', + introduction: 'DevOps 엔지니어 손미나입니다. AWS와 Docker를 다룹니다.', + }, + { + id: 8, + name: '윤태호', + status: '거절', + introduction: '게임 개발자 윤태호입니다. Unity와 C#을 주로 사용합니다.', + }, + { + id: 9, + name: '임수진', + status: '승인', + introduction: '데이터 엔지니어 임수진입니다. Python과 SQL을 다룹니다.', + }, + { + id: 10, + name: '한도윤', + status: '대기', + introduction: + '보안 엔지니어 한도윤입니다. 네트워크 보안에 관심이 많습니다.', + }, +]; + +const Some = () => { + const [isSecondModalOpen, setIsSecondModalOpen] = useState(false); + const [selectedUser, setSelectedUser] = useState(null); + + const handleSecondModalConfirm = () => { + setIsSecondModalOpen(false); + }; + + const getStatusColor = (status: string) => { + switch (status) { + case '승인': + return 'text-green-600'; + case '대기': + return 'text-yellow-600'; + case '거절': + return 'text-red-600'; + case '강퇴': + return 'text-gray-600'; + default: + return ''; + } + }; + + const handleProfileClick = (user: UserData) => { + setSelectedUser(user); + setIsSecondModalOpen(true); + }; + + return ( +
+
+
이름
+
상태
+
프로필
+
+
+ {mockUsers.map((user) => ( +
+
{user.name}
+
{user.status}
+
+ +
+
+ ))} + + setIsSecondModalOpen(false)} + onConfirm={handleSecondModalConfirm} + confirmText="확인" + cancelText="닫기" + modalClassName="w-96" + > + {selectedUser && ( +
+

+ {selectedUser.name}님의 프로필 +

+

{selectedUser.introduction}

+
+ )} +
+
+ ); +}; + +export default Some; diff --git a/src/app/preview/modal/page.tsx b/src/app/preview/modal/page.tsx new file mode 100644 index 0000000..1495cf2 --- /dev/null +++ b/src/app/preview/modal/page.tsx @@ -0,0 +1,56 @@ +'use client'; + +import Some from '@/app/preview/modal/Some'; +import { Button } from '@/components/ui/Button'; +import Modal from '@/components/ui/modal/Modal'; +import { useState } from 'react'; + +export default function ModalTestPage() { + const [isModalOpen, setIsModalOpen] = useState(false); + const [isModalOpen2, setIsModalOpen2] = useState(false); + const [isModalOpen3, setIsModalOpen3] = useState(false); // 새로운 상태 추가 + + const handleConfirm = () => { + setIsModalOpen(false); + }; + + return ( +
+ + + + + setIsModalOpen(false)} + onConfirm={handleConfirm} + confirmText="삭제" + cancelText="취소" + modalClassName="w-96" + > + + + + setIsModalOpen2(false)} + onConfirm={handleConfirm} + confirmText="삭제" + cancelText="취소" + modalClassName="w-96" + > +

안녕하세용용

+
+ + setIsModalOpen3(false)} + closeOnly + cancelText="닫기" + modalClassName="w-96" + > +

닫기 버튼만 있는 모달입니다!

+
+
+ ); +} diff --git a/src/components/ui/modal/Modal.tsx b/src/components/ui/modal/Modal.tsx new file mode 100644 index 0000000..190d515 --- /dev/null +++ b/src/components/ui/modal/Modal.tsx @@ -0,0 +1,125 @@ +import { Button } from '@/components/ui/Button'; +import React from 'react'; + +interface AlertModalProps { + isOpen: boolean; + onClose: () => void; + onConfirm?: () => void; + confirmText?: string; + cancelText?: string; + children: React.ReactNode; + modalClassName?: string; + contentClassName?: string; + buttonClassName?: string; + closeOnly?: boolean; +} + +/** + * Modal 컴포넌트는 사용자 인터페이스에 모달 다이얼로그를 표시하는 재사용 가능한 컴포넌트입니다. + * + * @component + * @example + * ```tsx + * // 기본 모달 (확인/취소 버튼) + * setIsOpen(false)} + * onConfirm={() => handleConfirm()} + * confirmText="확인" + * cancelText="취소" + * modalClassName="w-96" + * > + *

모달 내용

+ *
+ * + * // 닫기 버튼만 있는 모달 + * setIsOpen(false)} + * closeOnly + * cancelText="닫기" + * modalClassName="w-96" + * > + *

모달 내용

+ *
+ * ``` + * + * @param props - 모달 컴포넌트 프로퍼티 + * @param props.isOpen - 모달의 표시 여부를 제어 + * @param props.onClose - 모달이 닫힐 때 호출되는 콜백 함수 + * @param props.onConfirm - 확인 버튼 클릭 시 호출되는 콜백 함수 (closeOnly가 false일 때만 필요) + * @param props.closeOnly - true일 경우 닫기 버튼만 표시 (기본값: false) + * @param props.confirmText - 확인 버튼의 텍스트 (기본값: '확인', closeOnly가 false일 때만 사용) + * @param props.cancelText - 취소/닫기 버튼의 텍스트 (기본값: closeOnly가 true일 때 '닫기', false일 때 '취소') + * @param props.children - 모달 내부에 표시될 컨텐츠 + * @param props.modalClassName - 모달 컨테이너에 적용할 추가 클래스명 + * @param props.contentClassName - 모달 컨텐츠 영역에 적용할 추가 클래스명 + * @param props.buttonClassName - 버튼 영역에 적용할 추가 클래스명 + * + * @returns React 컴포넌트 + */ + +const Modal: React.FC = ({ + isOpen, + onClose, + onConfirm, + confirmText = '확인', + cancelText = '취소', + children, + modalClassName = '', + contentClassName = '', + buttonClassName = '', + closeOnly = false, +}) => { + const handleBackdropClick = (e: React.MouseEvent): void => { + if (e.target === e.currentTarget) { + onClose(); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent): void => { + if (e.key === 'Escape') { + onClose(); + } + }; + + if (!isOpen) return null; + + return ( +
+
+
{children}
+ +
+ {closeOnly ? ( + + ) : ( + <> + + + + )} +
+
+
+ ); +}; + +export default Modal; diff --git a/tailwind.config.ts b/tailwind.config.ts index ceacef9..00cd51a 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -15,7 +15,7 @@ export default { lg: '745px', }, colors: { - main: '#3853EA', // 이전 #525FEE + main: '#3853EA', default: '#C2C9FF', solid: '#E5e7fa', disable: '#30333e', @@ -32,7 +32,7 @@ export default { Cgray700: '#B4BBCE', Cgray800: '#D8DEE8', BG: '#0F0F0F', - BG_2: '1B1B1D', + BG_2: '#1B1B1D', background: 'hsl(var(--background))', foreground: 'hsl(var(--foreground))', card: {