From 1560475ddc506978407782c7eee6b92b57c49318 Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Mon, 30 Oct 2023 18:46:08 +0900 Subject: [PATCH] =?UTF-8?q?[Style]=20Modal=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=9C=EC=9E=91=20(#52)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore : zustand 설치 및 store 폴더 생성 * feat : 모달창 전역 상태 추가 * style : 모달창 컴포넌트 제작 * fix: package.json 쉼표 오류 * fix: package-lock.json 쉼표 오류 * fix : 모달창 type 받아 분기 처리 * fix : type 에러 수정 * chore : 주석 제거 * fix: isDarkMode 옵셔널로 수정 --------- Co-authored-by: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- package-lock.json | 46 ++++++- package.json | 6 +- src/assets/icons/Exclamation.svg | 5 + src/assets/icons/Warning.svg | 3 + .../Buttons/NormalButton/NormalButton.tsx | 4 +- .../NormalButton/NormalButtonStyles.ts | 2 +- src/components/common/Modal/index.tsx | 124 ++++++++++++++++++ src/components/layouts/Layout.tsx | 2 + src/hooks/useModal.tsx | 22 ++++ src/store/ModalStore.tsx | 28 ++++ tsconfig.json | 3 +- 11 files changed, 235 insertions(+), 10 deletions(-) create mode 100644 src/assets/icons/Exclamation.svg create mode 100644 src/assets/icons/Warning.svg create mode 100644 src/components/common/Modal/index.tsx create mode 100644 src/hooks/useModal.tsx create mode 100644 src/store/ModalStore.tsx diff --git a/package-lock.json b/package-lock.json index f4daf9e9..60875e21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,9 @@ "react-icons": "^4.11.0", "react-router-dom": "^6.16.0", "react-spinners": "^0.13.8", - "react-toastify": "^9.1.3" + "react-toastify": "^9.1.3", + "zustand": "^4.4.4", + "zustand-persist": "^0.4.0" }, "devDependencies": { "@rushstack/eslint-config": "^3.4.1", @@ -2518,13 +2520,13 @@ "version": "15.7.9", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz", "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==", - "dev": true + "devOptional": true }, "node_modules/@types/react": { "version": "18.2.30", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.30.tgz", "integrity": "sha512-OfqdJnDsSo4UNw0bqAjFCuBpLYQM7wvZidz0hVxHRjrEkzRlvZL1pJVyOSY55HMiKvRNEo9DUBRuEl7FNlJ/Vg==", - "dev": true, + "devOptional": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -2544,7 +2546,7 @@ "version": "0.16.5", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz", "integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==", - "dev": true + "devOptional": true }, "node_modules/@types/semver": { "version": "7.5.4", @@ -9208,6 +9210,42 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.4.tgz", + "integrity": "sha512-5UTUIAiHMNf5+mFp7/AnzJXS7+XxktULFN0+D1sCiZWyX7ZG+AQpqs2qpYrynRij4QvoDdCD+U+bmg/cG3Ucxw==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/zustand-persist": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/zustand-persist/-/zustand-persist-0.4.0.tgz", + "integrity": "sha512-u6bBIc4yZRpSKBKuTNhoqvoIb09gGHk2NkiPg4K7MPIWTYZg70PlpBn48QEDnKZwfNurnf58TaW5BuMGIMf5hw==", + "peerDependencies": { + "react": ">=16.8.0", + "zustand": ">=3.6.3" + } } } } diff --git a/package.json b/package.json index 243bac8f..5c48947f 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,10 @@ "react-hook-form": "^7.47.0", "react-icons": "^4.11.0", "react-router-dom": "^6.16.0", - "react-spinners": "^0.13.8", - "react-toastify": "^9.1.3" + "react-toastify": "^9.1.3", + "zustand": "^4.4.4", + "zustand-persist": "^0.4.0", + "react-spinners": "^0.13.8" }, "devDependencies": { "@rushstack/eslint-config": "^3.4.1", diff --git a/src/assets/icons/Exclamation.svg b/src/assets/icons/Exclamation.svg new file mode 100644 index 00000000..c6348371 --- /dev/null +++ b/src/assets/icons/Exclamation.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/Warning.svg b/src/assets/icons/Warning.svg new file mode 100644 index 00000000..8a1b6177 --- /dev/null +++ b/src/assets/icons/Warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/common/Buttons/NormalButton/NormalButton.tsx b/src/components/common/Buttons/NormalButton/NormalButton.tsx index 060bbd38..db596958 100644 --- a/src/components/common/Buttons/NormalButton/NormalButton.tsx +++ b/src/components/common/Buttons/NormalButton/NormalButton.tsx @@ -7,9 +7,9 @@ import { NormalButtonStyles, NormalButtonType } from './NormalButtonStyles' const NormalButton = styled.button<{ normalButtonType: NormalButtonType - isDarkMode: boolean + isDarkMode?: boolean }>` - ${({ normalButtonType, isDarkMode }) => { + ${({ normalButtonType, isDarkMode = false }) => { const processedTypeKey = isDarkMode ? `${normalButtonType}-dark` : normalButtonType const processedType = ( NormalButtonStyles[processedTypeKey as NormalButtonType] ? processedTypeKey : normalButtonType diff --git a/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts b/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts index fb028b1d..2f97c3b7 100644 --- a/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts +++ b/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts @@ -144,7 +144,7 @@ export const NormalButtonStyles: Record = { width: 85, height: 40, fontColor: palette.GRAY400, - backgroundColor: palette.GRAY100, + backgroundColor: palette.GRAY200, font: 'Body_14', fontWeight: 600, letterSpacing: -2, diff --git a/src/components/common/Modal/index.tsx b/src/components/common/Modal/index.tsx new file mode 100644 index 00000000..75b43e9a --- /dev/null +++ b/src/components/common/Modal/index.tsx @@ -0,0 +1,124 @@ +import styled from '@emotion/styled' + +import ExclamationIcon from '@/assets/icons/Exclamation.svg' +import WarningIcon from '@/assets/icons/Warning.svg' +import NormalButton from '@/components/common/Buttons/NormalButton/NormalButton' +import useModalStore from '@/store/ModalStore' +import { palette } from '@/styles/palette' +import { typo } from '@/styles/typo' + +const Modal = () => { + const { modalState, setModalState, okFunc, mainText, subText, type } = useModalStore() + const OkAndClose = () => { + okFunc() + closeModal() + } + const closeModal = () => { + setModalState(false) + } + return ( + <> + {modalState ? ( + + + {type == 'confirm' ? ( + + ) : ( + + )} + + {mainText} + {subText} + {type === 'confirm' ? ( + + + {'확인'} + + + {'취소'} + + + ) : ( + + + {'예, 나가겠습니다.'} + + + {'아니오, 돌아가겠습니다.'} + + + )} + + + ) : ( + '' + )} + + ) +} + +const StyleModalWrapper = styled.div` + z-index: 999; + display: flex; + position: absolute; + justify-content: center; + align-items: center; + width: 100%; + background-color: rgba(0, 0, 0, 0.4); + border-radius: 10px; + top: 0; + left: 0; + right: 0; + bottom: 0; +` +const StyleModal = styled.div<{ type: string }>` + width: 344px; + height: ${({ type }) => (type == 'warn' ? '195.6px' : '246px')}; + z-index: 1; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: white; + border-radius: 10px; + box-shadow: 3px 3px 3px ${palette.GRAY400}; + text-align: center; +` + +const StyleButtonWrapper = styled.span` + justify-content: center; + margin: 10px; + display: flex; +` +const StyleMainText = styled.div<{ subTrue: boolean }>` + color: ${palette.BLACK}; + text-align: center; + font-size: ${typo.Body_20()}; + margin-top: ${({ subTrue }) => (subTrue ? '' : '10px')}; + margin-bottom: ${({ subTrue }) => (subTrue ? '20px' : '30px')}; +` +const StyleSubText = styled.span<{ type: string }>` + color: ${palette.GRAY500}; + text-align: center; + font-size: ${typo.Body_14()}; +` +const StyleIcon = styled.img` + margin: 22px; +` +export default Modal diff --git a/src/components/layouts/Layout.tsx b/src/components/layouts/Layout.tsx index 8fe3fb31..e6e41eb6 100644 --- a/src/components/layouts/Layout.tsx +++ b/src/components/layouts/Layout.tsx @@ -4,11 +4,13 @@ import styled from '@emotion/styled' import { Outlet } from 'react-router-dom' import { ToastContainer } from 'react-toastify' +import Modal from '@/components/common/Modal' import { theme } from '@/styles/theme' const Layout = () => { return ( + diff --git a/src/hooks/useModal.tsx b/src/hooks/useModal.tsx new file mode 100644 index 00000000..de18310e --- /dev/null +++ b/src/hooks/useModal.tsx @@ -0,0 +1,22 @@ +import Modal from '@/components/common/Modal' +import useModalStore from '@/store/ModalStore' + +type ModalConfirmPropsType = { + type: 'warn' | 'confirm' + okFunc: () => void + mainText: string + subText?: string +} +export const useModal = () => { + const { setModalState, setOkFunc, setMainText, setSubText, setType } = useModalStore() + + const openModal = ({ mainText, subText, okFunc, type }: ModalConfirmPropsType) => { + setModalState(true) + setType(type) + setMainText(mainText) + setSubText(subText) + setOkFunc(okFunc) + } + + return { openModal, Modal } +} diff --git a/src/store/ModalStore.tsx b/src/store/ModalStore.tsx new file mode 100644 index 00000000..c87e8751 --- /dev/null +++ b/src/store/ModalStore.tsx @@ -0,0 +1,28 @@ +import { create } from 'zustand' + +type ModalState = { + modalState: boolean + type: 'confirm' | 'warn' + okFunc: () => void + mainText: string + subText?: string | undefined + setType: (type: 'confirm' | 'warn') => void + setSubText: (text: string | undefined) => void + setModalState: (state: boolean) => void + setMainText: (text: string) => void + setOkFunc: (func: () => void) => void +} + +const useModalStore = create((set) => ({ + modalState: false, + okFunc: () => {}, + mainText: '', + subText: '', + type: 'confirm', + setType: (type) => set({ type: type }), + setSubText: (text) => set({ subText: text }), + setModalState: (state) => set({ modalState: state }), + setMainText: (text) => set({ mainText: text }), + setOkFunc: (func) => set({ okFunc: func }), +})) +export default useModalStore diff --git a/tsconfig.json b/tsconfig.json index 51f7e0f0..027d4a68 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,7 +30,8 @@ "@/hooks/*": ["src/hooks/*"], "@/assets/*": ["src/assets/*"], "@/styles/*": ["src/styles/*"], - "@/mocks/*": ["src/mocks/*"] + "@/mocks/*": ["src/mocks/*"], + "@/store/*": ["src/store/*"] } }, "include": ["src"],