From 795f1701bc6291eb12ea4b07dfb0c28691f24e71 Mon Sep 17 00:00:00 2001 From: Seoyoung Date: Fri, 2 Feb 2024 18:54:02 +0900 Subject: [PATCH 01/12] =?UTF-8?q?Feat:=20=EA=B3=B5=EC=9A=A9=20=EB=AA=A8?= =?UTF-8?q?=EB=8B=AC=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Modal/Modal.css.ts | 32 +++++++++++++++++++ src/components/Modal/Modal.tsx | 35 +++++++++++++++++++++ src/components/Modal/ModalButton.css.ts | 41 +++++++++++++++++++++++++ src/components/Modal/ModalButton.tsx | 21 +++++++++++++ src/components/Modal/ModalTitle.css.ts | 8 +++++ src/components/Modal/ModalTitle.tsx | 6 ++++ src/components/ModalPortal.ts | 14 +++++++++ src/hooks/useBooleanOutput.ts | 22 +++++++++++++ src/hooks/useOnClickOutside.ts | 23 ++++++++++++++ 9 files changed, 202 insertions(+) create mode 100644 src/components/Modal/Modal.css.ts create mode 100644 src/components/Modal/Modal.tsx create mode 100644 src/components/Modal/ModalButton.css.ts create mode 100644 src/components/Modal/ModalButton.tsx create mode 100644 src/components/Modal/ModalTitle.css.ts create mode 100644 src/components/Modal/ModalTitle.tsx create mode 100644 src/components/ModalPortal.ts create mode 100644 src/hooks/useBooleanOutput.ts create mode 100644 src/hooks/useOnClickOutside.ts diff --git a/src/components/Modal/Modal.css.ts b/src/components/Modal/Modal.css.ts new file mode 100644 index 00000000..651d130c --- /dev/null +++ b/src/components/Modal/Modal.css.ts @@ -0,0 +1,32 @@ +import { style } from '@vanilla-extract/css'; + +export const background = style({ + width: '100vw', + height: '100vh', + zIndex: 100, + + position: 'fixed', + top: '0px', + left: '0px', + + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + + backgroundColor: 'rgba(25, 25, 27, 0.3)', +}); + +export const container = style({ + width: '326px', + padding: '24px', + + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + gap: '2.4rem', + + backgroundColor: '#fff', + + borderRadius: '8px', +}); diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx new file mode 100644 index 00000000..832c2cfa --- /dev/null +++ b/src/components/Modal/Modal.tsx @@ -0,0 +1,35 @@ +import { FormEvent, MouseEventHandler, ReactNode, useEffect, useRef, useState } from 'react'; +import ModalPortal from '@/components/ModalPortal'; +import * as styles from './Modal.css'; +import ModalTitle from './ModalTitle'; +import ModalButton from './ModalButton'; +import useOnClickOutside from '@/hooks/useOnClickOutside'; +import useBooleanOutput from '@/hooks/useBooleanOutput'; + +interface ModalMainProps { + children?: ReactNode; + handleModalClose: () => void; +} + +function ModalMain({ children, handleModalClose }: ModalMainProps) { + const { ref } = useOnClickOutside(() => { + handleModalClose(); + }); + + return ( + +
+
+ {children} +
+
+
+ ); +} + +const Modal = Object.assign(ModalMain, { + Title: ModalTitle, + Button: ModalButton, +}); + +export default Modal; diff --git a/src/components/Modal/ModalButton.css.ts b/src/components/Modal/ModalButton.css.ts new file mode 100644 index 00000000..48c344cb --- /dev/null +++ b/src/components/Modal/ModalButton.css.ts @@ -0,0 +1,41 @@ +import { style } from '@vanilla-extract/css'; + +export const buttonContainer = style({ + width: '100%', + + display: 'flex', + justifyContent: 'flex-end', + gap: '16px', +}); + +export const baseButton = style({ + padding: '12px 16px', + + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + + flexShrink: '0', + + borderRadius: '12px', + fontSize: '1.4rem', + fontWeight: '500', + lineHeight: '20px', + letterSpacing: '-0.4px', +}); + +export const primaryButton = style([ + baseButton, + { + backgroundColor: '#0047FF', + color: '#fff', + }, +]); + +export const secondaryButton = style([ + baseButton, + { + backgroundColor: '#EBF4FF', + color: '#0047FF', + }, +]); diff --git a/src/components/Modal/ModalButton.tsx b/src/components/Modal/ModalButton.tsx new file mode 100644 index 00000000..d0cf0a0f --- /dev/null +++ b/src/components/Modal/ModalButton.tsx @@ -0,0 +1,21 @@ +import { MouseEventHandler, ReactNode } from 'react'; +import * as styles from './ModalButton.css'; + +interface ModalButtonProps { + children: ReactNode; + onCancel: MouseEventHandler; + onClick: MouseEventHandler; +} + +export default function ModalButton({ children, onCancel, onClick }: ModalButtonProps) { + return ( +
+ + +
+ ); +} diff --git a/src/components/Modal/ModalTitle.css.ts b/src/components/Modal/ModalTitle.css.ts new file mode 100644 index 00000000..c5a57a3b --- /dev/null +++ b/src/components/Modal/ModalTitle.css.ts @@ -0,0 +1,8 @@ +import { style } from '@vanilla-extract/css'; + +export const title = style({ + width: '100%', + fontSize: '1.6rem', + fontWeight: '600', + lineHeight: '150%', +}); diff --git a/src/components/Modal/ModalTitle.tsx b/src/components/Modal/ModalTitle.tsx new file mode 100644 index 00000000..66304b47 --- /dev/null +++ b/src/components/Modal/ModalTitle.tsx @@ -0,0 +1,6 @@ +import { ReactNode } from 'react'; +import * as styles from './ModalTitle.css'; + +export default function ModalTitle({ children }: { children: ReactNode }) { + return
{children}
; +} diff --git a/src/components/ModalPortal.ts b/src/components/ModalPortal.ts new file mode 100644 index 00000000..48937fab --- /dev/null +++ b/src/components/ModalPortal.ts @@ -0,0 +1,14 @@ +import { ReactNode } from 'react'; +import ReactDOM from 'react-dom'; + +interface Props { + children: ReactNode; +} + +const ModalPortal = ({ children }: Props) => { + const el = document.getElementById('modal-root') as HTMLElement; + + return ReactDOM.createPortal(children, el); +}; + +export default ModalPortal; diff --git a/src/hooks/useBooleanOutput.ts b/src/hooks/useBooleanOutput.ts new file mode 100644 index 00000000..b1da56c9 --- /dev/null +++ b/src/hooks/useBooleanOutput.ts @@ -0,0 +1,22 @@ +import { useCallback, useEffect, useState } from 'react'; + +interface useBooleanOutput { + isOn: boolean; + toggle: () => void; + handleSetOn: () => void; + handleSetOff: () => void; +} + +export default function useBooleanOutput(defaultValue?: boolean): useBooleanOutput { + const [isOn, setIsOn] = useState(!!defaultValue); + + const toggle = useCallback(() => setIsOn((prev) => !prev), []); + const handleSetOn = useCallback(() => setIsOn(true), []); + const handleSetOff = useCallback(() => setIsOn(false), []); + + useEffect(() => { + setIsOn(false); + }, []); + + return { isOn, toggle, handleSetOn, handleSetOff }; +} diff --git a/src/hooks/useOnClickOutside.ts b/src/hooks/useOnClickOutside.ts new file mode 100644 index 00000000..615cd647 --- /dev/null +++ b/src/hooks/useOnClickOutside.ts @@ -0,0 +1,23 @@ +import { EventHandler, useEffect, useRef, useState } from 'react'; + +const useOnClickOutside = (handler: () => void) => { + const ref = useRef(null); + + useEffect(() => { + const handleClickOutside = (e: Event) => { + if (ref.current !== null && !ref.current.contains(e.target as Node)) { + handler(); + } + }; + document.addEventListener('mousedown', handleClickOutside); + document.addEventListener('touchstart', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + document.removeEventListener('touchstart', handleClickOutside); + }; + }, [ref]); + + return { ref }; +}; + +export default useOnClickOutside; From be2908eba16287b12fdf9e23fcdd2a3620fa5293 Mon Sep 17 00:00:00 2001 From: seoyoung-min Date: Fri, 2 Feb 2024 19:25:26 +0900 Subject: [PATCH 02/12] =?UTF-8?q?Refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useOnClickOutside.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useOnClickOutside.ts b/src/hooks/useOnClickOutside.ts index 615cd647..410835c4 100644 --- a/src/hooks/useOnClickOutside.ts +++ b/src/hooks/useOnClickOutside.ts @@ -1,4 +1,4 @@ -import { EventHandler, useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; const useOnClickOutside = (handler: () => void) => { const ref = useRef(null); From 46962d666f0fc71fa9aedb3be2653ec399fe54fd Mon Sep 17 00:00:00 2001 From: seoyoung-min Date: Fri, 2 Feb 2024 21:15:34 +0900 Subject: [PATCH 03/12] =?UTF-8?q?Refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Modal/Modal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx index 832c2cfa..058343d0 100644 --- a/src/components/Modal/Modal.tsx +++ b/src/components/Modal/Modal.tsx @@ -4,7 +4,6 @@ import * as styles from './Modal.css'; import ModalTitle from './ModalTitle'; import ModalButton from './ModalButton'; import useOnClickOutside from '@/hooks/useOnClickOutside'; -import useBooleanOutput from '@/hooks/useBooleanOutput'; interface ModalMainProps { children?: ReactNode; From 238a2838a1b8551e309238dea353c1b902be9a1b Mon Sep 17 00:00:00 2001 From: seoyoung-min Date: Fri, 2 Feb 2024 22:35:51 +0900 Subject: [PATCH 04/12] =?UTF-8?q?Feat:=20layout=EC=97=90=20Portal=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/layout.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 19153c10..007ea386 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -8,6 +8,7 @@ export default function TempLayout({ children }: { children: ReactNode }) { ListyWave +