From 8294235402d65d8bc813b88bb9d73eb5cf7fed22 Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Sat, 16 Nov 2024 21:40:00 +0900 Subject: [PATCH 01/18] =?UTF-8?q?[FE][Feat]=20#59=20:=20Modal=20=EC=97=B4?= =?UTF-8?q?=EA=B3=A0=20=EB=8B=AB=EA=B8=B0=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/useModal.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 frontend/src/hooks/useModal.ts diff --git a/frontend/src/hooks/useModal.ts b/frontend/src/hooks/useModal.ts new file mode 100644 index 00000000..4488860b --- /dev/null +++ b/frontend/src/hooks/useModal.ts @@ -0,0 +1,10 @@ +import { useState } from 'react'; + +export const useModal = () => { + const [isOpen, setIsOpen] = useState(false); + + const onOpen = () => setIsOpen(true); + const onClose = () => setIsOpen(false); + + return { isOpen, onOpen, onClose }; +}; From 92525560dd86f58b6141f7384aa956a1c76607cb Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Sat, 16 Nov 2024 21:40:32 +0900 Subject: [PATCH 02/18] =?UTF-8?q?[FE][Feat]=20#59=20:=20Modal=20header=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/component/common/modal/ModalHeader.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 frontend/src/component/common/modal/ModalHeader.tsx diff --git a/frontend/src/component/common/modal/ModalHeader.tsx b/frontend/src/component/common/modal/ModalHeader.tsx new file mode 100644 index 00000000..83bf4129 --- /dev/null +++ b/frontend/src/component/common/modal/ModalHeader.tsx @@ -0,0 +1,18 @@ +import { MdClear } from 'react-icons/md'; +import { Button } from '../button/Button'; + +interface IModalHeaderProps { + /** 모달 헤더의 제목 텍스트입니다. */ + content: string; + /** 모달을 닫는 함수입니다. */ + onClose: () => void; +} + +export const ModalHeader = (props: IModalHeaderProps) => ( +
+

{props.content}

+ +
+); From de79934ea6387cad1987affcdb8254236a1e2947 Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Sat, 16 Nov 2024 21:41:29 +0900 Subject: [PATCH 03/18] =?UTF-8?q?[FE][Feat]=20#59=20:=20Modal=20Input=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 항목명과 input을 가지는 컴포넌트입니다 --- .../src/component/common/modal/ModalInput.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 frontend/src/component/common/modal/ModalInput.tsx diff --git a/frontend/src/component/common/modal/ModalInput.tsx b/frontend/src/component/common/modal/ModalInput.tsx new file mode 100644 index 00000000..53d3fc08 --- /dev/null +++ b/frontend/src/component/common/modal/ModalInput.tsx @@ -0,0 +1,25 @@ +interface IModalInputProps { + /** 입력 필드의 제목입니다. */ + title: string; + /** 입력 필드의 이름 속성입니다. */ + name: string; + /** 입력 필드의 placeholder 텍스트입니다. */ + placeholder: string; + /** 입력 필드의 현재 값입니다. */ + value: string; + /** 입력 필드 값이 변경될 때 호출되는 함수입니다. */ + onChange: (e: React.ChangeEvent) => void; +} + +export const ModalInput = (props: IModalInputProps) => ( +
+

{props.title}

+ +
+); From 6949fbe1622ddd7552cee1283df36e5be20f9e89 Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Sat, 16 Nov 2024 21:42:13 +0900 Subject: [PATCH 04/18] =?UTF-8?q?[FE][Feat]=20#59=20:=20Modal=20Footer=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 모달에서 버튼이 있는 부분인 Footer입니다 --- .../component/common/modal/ModalFooter.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 frontend/src/component/common/modal/ModalFooter.tsx diff --git a/frontend/src/component/common/modal/ModalFooter.tsx b/frontend/src/component/common/modal/ModalFooter.tsx new file mode 100644 index 00000000..8612db9a --- /dev/null +++ b/frontend/src/component/common/modal/ModalFooter.tsx @@ -0,0 +1,29 @@ +interface IModalFooterProps { + /** 메인 버튼의 텍스트입니다. */ + text: string; + /** 메인 버튼 클릭 시 호출되는 함수입니다. */ + onClick?: () => void; + /** 보조 버튼의 텍스트입니다 (선택 사항) */ + text2?: string; + /** 보조 버튼 클릭 시 호출되는 함수입니다 (선택 사항) */ + onClick2?: () => void; +} + +export const ModalFooter = (props: IModalFooterProps) => ( +
+ + {props.text2 ? ( + + ) : ( + '' + )} +
+); From 8f661c3d8cd14e4f6a0fd3a4e37f858df03950b0 Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Sat, 16 Nov 2024 21:45:35 +0900 Subject: [PATCH 05/18] =?UTF-8?q?[FE][Feat]=20#59=20:=20Modal=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 합성 컴포넌트 방식을 사용하여 Modal을 구현하였습니다 --- frontend/src/component/common/modal/Modal.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 frontend/src/component/common/modal/Modal.tsx diff --git a/frontend/src/component/common/modal/Modal.tsx b/frontend/src/component/common/modal/Modal.tsx new file mode 100644 index 00000000..f0c3be5f --- /dev/null +++ b/frontend/src/component/common/modal/Modal.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { ModalHeader } from '@/component/common/modal/ModalHeader'; +import { ModalInput } from '@/component/common/modal/ModalInput'; +import { ModalFooter } from '@/component/common/modal/ModalFooter'; + +interface IModalProps { + /** 모달이 열려 있는지 여부를 나타냅니다. */ + isOpen: boolean; + /** 모달 내에서 렌더링할 자식 요소들입니다. */ + children: React.ReactNode; +} + +export const Modal = (props: IModalProps) => { + if (!props.isOpen) return null; + + return ( +
+
+ {props.children} +
+
+ ); +}; + +Modal.Header = ModalHeader; +Modal.Footer = ModalFooter; +Modal.Input = ModalInput; From 33c586dd8c228aa8b269abb177339b2e88156af8 Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Sat, 16 Nov 2024 21:49:02 +0900 Subject: [PATCH 06/18] =?UTF-8?q?[FE][Feat]=20#59=20:=20Modal=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A5=BC=20=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8/=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=AA=A8=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/component/authmodal/AuthModal.tsx | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 frontend/src/component/authmodal/AuthModal.tsx diff --git a/frontend/src/component/authmodal/AuthModal.tsx b/frontend/src/component/authmodal/AuthModal.tsx new file mode 100644 index 00000000..a13689a8 --- /dev/null +++ b/frontend/src/component/authmodal/AuthModal.tsx @@ -0,0 +1,139 @@ +import React, { useState } from 'react'; +import { Modal } from '@/component/common/modal/Modal'; + +interface IAuthModalProps { + /** 모달이 열려 있는지 여부를 나타냅니다. */ + isOpen: boolean; + /** 모달을 닫는 함수입니다. */ + onClose: () => void; + /** 모달의 타입을 결정하는 값으로, 'login' 또는 'register'를 가집니다. */ + type: 'login' | 'register'; +} + +export const AuthModal = (props: IAuthModalProps) => { + const [loginData, setLoginData] = useState({ + id: '', + pw: '', + }); + + const [registerData, setRegisterData] = useState({ + id: '', + email: '', + name: '', + pw: '', + confirmPw: '', + }); + + const [modalType, setModalType] = useState<'login' | 'register'>(props.type); + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + + if (modalType === 'login') { + setLoginData(prevState => ({ + ...prevState, + [name]: value, + })); + } else { + setRegisterData(prevState => ({ + ...prevState, + [name]: value, + })); + } + }; + + const handleLoginClick = () => { + console.log('로그인 데이터:', loginData); + }; + + const handleSignUpClick = () => { + if (registerData.pw !== registerData.confirmPw) { + alert('비밀번호가 일치하지 않습니다.'); + return; + } + console.log('회원가입 데이터:', registerData); + }; + + const switchToRegister = () => { + setModalType('register'); + }; + + const switchToLogin = () => { + setModalType('login'); + }; + + return ( + + {modalType === 'login' ? ( + <> + + + + + + ) : ( + <> + + + + + + + + + )} + + ); +}; From 5d150b9e654e6d6e487beeedcbea623154584eba Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Sun, 17 Nov 2024 00:32:11 +0900 Subject: [PATCH 07/18] [FE][Docs] #59 : Modal Header Storybook --- .../common/modal/ModalHeader.stories.tsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 frontend/src/stories/common/modal/ModalHeader.stories.tsx diff --git a/frontend/src/stories/common/modal/ModalHeader.stories.tsx b/frontend/src/stories/common/modal/ModalHeader.stories.tsx new file mode 100644 index 00000000..ca09941b --- /dev/null +++ b/frontend/src/stories/common/modal/ModalHeader.stories.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Meta, Story } from '@storybook/react'; +import { ModalHeader } from '@/component/common/modal/ModalHeader'; +import { fn } from '@storybook/test'; + +export default { + title: 'Components/common/modal/ModalHeader', + component: ModalHeader, + tags: ['autodocs'], + parameters: { + layout: 'centered', + }, +} as Meta; + +const Template: Story = args => ; + +export const DefaultHeader = Template.bind({}); +DefaultHeader.args = { + content: 'Login', + onClose: fn(), +}; From 1b045828c85e4cac42230570ebbaabd3b983466a Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Sun, 17 Nov 2024 00:32:39 +0900 Subject: [PATCH 08/18] [FE][Docs] #59 : Modal Footer Storybook --- .../common/modal/ModalFooter.stories.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 frontend/src/stories/common/modal/ModalFooter.stories.tsx diff --git a/frontend/src/stories/common/modal/ModalFooter.stories.tsx b/frontend/src/stories/common/modal/ModalFooter.stories.tsx new file mode 100644 index 00000000..767fca48 --- /dev/null +++ b/frontend/src/stories/common/modal/ModalFooter.stories.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Meta, Story } from '@storybook/react'; +import { ModalFooter } from '@/component/common/modal/ModalFooter'; +import { fn } from '@storybook/test'; + +export default { + title: 'Components/common/modal/ModalFooter', + component: ModalFooter, + parameters: { layout: 'centered' }, + tags: ['autodocs'], +} as Meta; + +const Template: Story = args => ; + +export const OneButton = Template.bind({}); +OneButton.args = { + text: '회원가입', + onClick: fn(), +}; + +export const TwoButton = Template.bind({}); +TwoButton.args = { + text: '로그인', + onClick: fn(), + text2: '회원가입', + onClick2: fn(), +}; From 27bef3fbcd60c097334a0adda8dccb7deaae4118 Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Sun, 17 Nov 2024 00:33:34 +0900 Subject: [PATCH 09/18] [FE][Docs] #59 : Modal Input Storybook --- .../common/modal/ModalInput.stories.tsx | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 frontend/src/stories/common/modal/ModalInput.stories.tsx diff --git a/frontend/src/stories/common/modal/ModalInput.stories.tsx b/frontend/src/stories/common/modal/ModalInput.stories.tsx new file mode 100644 index 00000000..8a4968f7 --- /dev/null +++ b/frontend/src/stories/common/modal/ModalInput.stories.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Meta, Story } from '@storybook/react'; +import { ModalInput } from '@/component/common/modal/ModalInput'; +import { fn } from '@storybook/test'; + +export default { + title: 'Components/common/modal/ModalInput', + component: ModalInput, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +} as Meta; + +const Template: Story = args => ; + +export const DefaultInput = Template.bind({}); +DefaultInput.args = { + title: 'ID', + name: 'id', + placeholder: '사용할 ID를 입력해주세요.', + value: '', + onChange: fn(), +}; + +export const NoTitle = Template.bind({}); +NoTitle.args = { + title: '', + name: 'confirmPw', + placeholder: '비밀번호를 한 번 더 입력해주세요.', + value: '', + onChange: fn(), +}; From 35bbc556da4eb71cc73cc46b7ace52ef64efbe3d Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Sun, 17 Nov 2024 00:34:19 +0900 Subject: [PATCH 10/18] [FE][Docs] #59 : Modal Storybook --- .../stories/common/modal/Modal.stories.tsx | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 frontend/src/stories/common/modal/Modal.stories.tsx diff --git a/frontend/src/stories/common/modal/Modal.stories.tsx b/frontend/src/stories/common/modal/Modal.stories.tsx new file mode 100644 index 00000000..4fb7b29b --- /dev/null +++ b/frontend/src/stories/common/modal/Modal.stories.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { Meta, Story } from '@storybook/react'; +import { Modal } from '@/component/common/modal/Modal'; +import { useModal } from '@/hooks/useModal'; +import { fn } from '@storybook/test'; + +export default { + title: 'Components/common/modal/Modal', + component: Modal, + tags: ['autodocs'], +} as Meta; + +const LoginModalTemplate: Story = () => { + const { isOpen, onOpen, onClose } = useModal(); + + return ( +
+ + + + + + + + +
+ ); +}; + +const RegisterModalTemplate: Story = () => { + const { isOpen, onOpen, onClose } = useModal(); + + return ( +
+ + + + + + + + + + + +
+ ); +}; + +export const LoginModal = LoginModalTemplate.bind({}); +LoginModal.parameters = { + docs: { + description: { + story: '로그인 모달 컴포넌트를 렌더링합니다.', + }, + }, + backgrounds: { + default: 'gray', + values: [{ name: 'gray', value: '#f3f4f6' }], + }, +}; + +export const RegisterModal = RegisterModalTemplate.bind({}); +RegisterModal.parameters = { + docs: { + description: { + story: '회원가입 모달 컴포넌트를 렌더링합니다.', + }, + }, + backgrounds: { + default: 'gray', + values: [{ name: 'gray', value: '#f3f4f6' }], + }, +}; From 6bd55e6e15ca7f645bc04e7171466d1b64a0433e Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Sun, 17 Nov 2024 00:35:51 +0900 Subject: [PATCH 11/18] [FE][Docs] #59 : AuthModal Storybook --- frontend/src/stories/AuthModal.stories.tsx | 59 ++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 frontend/src/stories/AuthModal.stories.tsx diff --git a/frontend/src/stories/AuthModal.stories.tsx b/frontend/src/stories/AuthModal.stories.tsx new file mode 100644 index 00000000..544e774c --- /dev/null +++ b/frontend/src/stories/AuthModal.stories.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { Meta, Story } from '@storybook/react'; +import { useModal } from '@/hooks/useModal'; +import { AuthModal } from '@/component/authmodal/AuthModal'; + +export default { + title: 'Components/AuthModal', + component: AuthModal, + parameters: { + layout: 'fullscreen', + }, + tags: ['autodocs'], +} as Meta; + +const LoginModalTemplate: Story = args => { + const { onClose } = useModal(); + + return ; +}; + +const RegisterModalTemplate: Story = args => { + const { onClose } = useModal(); + + return ; +}; + +export const LoginModal = LoginModalTemplate.bind({}); +LoginModal.args = { + isOpen: true, + type: 'login', +}; +LoginModal.parameters = { + docs: { + description: { + story: '로그인 모달 컴포넌트를 렌더링합니다.', + }, + }, + backgrounds: { + default: 'gray', + values: [{ name: 'gray', value: '#f3f4f6' }], + }, +}; + +export const RegisterModal = RegisterModalTemplate.bind({}); +RegisterModal.args = { + isOpen: true, + type: 'register', +}; +RegisterModal.parameters = { + docs: { + description: { + story: '회원가입 모달 컴포넌트를 렌더링합니다.', + }, + }, + backgrounds: { + default: 'gray', + values: [{ name: 'gray', value: '#f3f4f6' }], + }, +}; From 4acfa6c4b05e0acd841ffa054fedf86c855216c3 Mon Sep 17 00:00:00 2001 From: effozen Date: Sun, 17 Nov 2024 04:10:36 +0900 Subject: [PATCH 12/18] =?UTF-8?q?[FE][Refactor]=20:=20Dropdown=20=ED=86=A0?= =?UTF-8?q?=EA=B8=80=20=EC=A0=84=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EB=84=98=EA=B2=A8=EC=A3=BC=EA=B2=8C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/component/common/dropdown/Dropdown.tsx | 10 ++++++---- .../src/component/common/dropdown/DropdownMenu.tsx | 4 ++-- .../src/component/common/dropdown/DropdownTrigger.tsx | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/frontend/src/component/common/dropdown/Dropdown.tsx b/frontend/src/component/common/dropdown/Dropdown.tsx index ec3933fd..8479c06a 100644 --- a/frontend/src/component/common/dropdown/Dropdown.tsx +++ b/frontend/src/component/common/dropdown/Dropdown.tsx @@ -1,4 +1,4 @@ -import React, { createContext, ReactNode, useMemo, useState } from 'react'; +import { createContext, ReactNode, useMemo, useState } from 'react'; import { DropdownTrigger } from '@/component/common/dropdown/DropdownTrigger.tsx'; import { DropdownItem } from '@/component/common/dropdown/DropdownItem.tsx'; import { DropdownMenu } from '@/component/common/dropdown/DropdownMenu.tsx'; @@ -9,19 +9,21 @@ interface IDropdownProps { export interface IToggleContext { isOpen: boolean; - setIsOpen: React.Dispatch>; + toggle: () => void; } export const ToggleContext = createContext({ isOpen: false, - setIsOpen: () => {}, + toggle: () => {}, }); // Todo : 드랍다운 외부에서 클릭시 창 닫히게 설정 export const Dropdown = (props: IDropdownProps) => { const [isOpen, setIsOpen] = useState(false); - const toggleContextValue = useMemo(() => ({ isOpen, setIsOpen }), [isOpen, setIsOpen]); + const toggle = () => setIsOpen(prevIsOpen => !prevIsOpen); + + const toggleContextValue = useMemo(() => ({ isOpen, toggle }), [isOpen]); return (