From 1ab8aabc2c11dca98c9d3ebe7ec53c7f9728f62e Mon Sep 17 00:00:00 2001 From: rtttr1 Date: Fri, 14 Nov 2025 17:12:22 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=EB=9D=BC=EC=9A=B0=ED=8C=85?= =?UTF-8?q?=EC=8B=9C=20=EB=AA=A8=EB=8B=AC=20store=20reset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/components/ModalProvider/ModalProvier.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/shared/components/ModalProvider/ModalProvier.tsx b/src/shared/components/ModalProvider/ModalProvier.tsx index 211cec386..035b9f53a 100644 --- a/src/shared/components/ModalProvider/ModalProvier.tsx +++ b/src/shared/components/ModalProvider/ModalProvier.tsx @@ -1,14 +1,21 @@ import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; import { useModalStore } from '@/common/stores/modal'; const ModalProvider = () => { const { modalStore, resetStore, closeModal } = useModalStore(); + const location = useLocation(); // 언마운트시 모달 리셋 useEffect(() => { return () => resetStore(); }, [resetStore]); + // 라우팅 변경시 모달 리셋 + useEffect(() => { + resetStore(); + }, [location.pathname, resetStore]); + // 모달 오버레이시 배경 스크롤 방지 useEffect(() => { if (modalStore.length > 0) { From 633975216bb8eea45a9faf32cd9b16fb3bbc6b20 Mon Sep 17 00:00:00 2001 From: rtttr1 Date: Fri, 14 Nov 2025 18:53:24 +0900 Subject: [PATCH 02/11] =?UTF-8?q?feat:=20=EB=AA=A8=EB=8B=AC=20=EB=82=B4?= =?UTF-8?q?=EB=B6=80=20=ED=81=B4=EB=A6=AD=EC=8B=9C=20close=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=A0=84=ED=8C=8C=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/components/Modal/Modal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/components/Modal/Modal.tsx b/src/common/components/Modal/Modal.tsx index 413ae2009..cec8e2d70 100644 --- a/src/common/components/Modal/Modal.tsx +++ b/src/common/components/Modal/Modal.tsx @@ -35,7 +35,8 @@ const Modal = ({ return ( -
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} +
e.stopPropagation()} className={containerStyle}>
{content}
{type === 'default' && ( From 86b80bfaa6e6b352f4b1ec489af2c8b1627ef83f Mon Sep 17 00:00:00 2001 From: rtttr1 Date: Fri, 14 Nov 2025 18:53:56 +0900 Subject: [PATCH 03/11] =?UTF-8?q?refactor:=20ModalProvider=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20react=20router=20=EB=82=B4=EB=B6=80=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 5 +---- src/layout/Layout.tsx | 2 ++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index a1214c90d..6b6efafee 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -33,10 +33,7 @@ const App = () => { return ( - - - - + ); diff --git a/src/layout/Layout.tsx b/src/layout/Layout.tsx index 4801d136d..ab663d907 100644 --- a/src/layout/Layout.tsx +++ b/src/layout/Layout.tsx @@ -3,6 +3,7 @@ import { ROUTES_CONFIG } from '@/routes/routesConfig'; import { ApiErrorBoundary } from '@/shared/components/ErrorBoundary/ApiErrorBoundary/ApiErrorBoundary'; import GlobalErrorBoundary from '@/shared/components/ErrorBoundary/GlobalErrorBoundary/GlobalErrorBoundary'; import Header from '@/shared/components/Header/Header'; +import ModalProvider from '@/shared/components/ModalProvider/ModalProvier'; const Layout = () => { const { pathname } = useLocation(); @@ -19,6 +20,7 @@ const Layout = () => { {shouldShowHeader &&
} + From e21aec6395b34c4ddc4f56e3184d8c0a49a92b6c Mon Sep 17 00:00:00 2001 From: rtttr1 Date: Fri, 14 Nov 2025 18:54:16 +0900 Subject: [PATCH 04/11] =?UTF-8?q?feat:=20esc=20=ED=82=A4=EB=A1=9C=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=20=EB=8B=AB=EA=B8=B0=20=EC=A7=80=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/components/Modal/ModalLayout.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/common/components/Modal/ModalLayout.tsx b/src/common/components/Modal/ModalLayout.tsx index 6572b430d..8fffe4c11 100644 --- a/src/common/components/Modal/ModalLayout.tsx +++ b/src/common/components/Modal/ModalLayout.tsx @@ -1,4 +1,4 @@ -import type { PropsWithChildren } from 'react'; +import { useEffect, type PropsWithChildren } from 'react'; import { layoutStyle } from '@/common/components/Modal/modal.css'; interface ModalLayoutProps extends PropsWithChildren { @@ -6,6 +6,17 @@ interface ModalLayoutProps extends PropsWithChildren { } const ModalLayout = ({ onClose, children }: ModalLayoutProps) => { + // esc 키 누르면 모달 닫기 + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + onClose(); + } + }; + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [onClose]); + return (
{ From 681c1b8e900b0656ea3847c78b5daa93b3bf3e79 Mon Sep 17 00:00:00 2001 From: rtttr1 Date: Fri, 14 Nov 2025 20:13:04 +0900 Subject: [PATCH 05/11] =?UTF-8?q?feat:=20slice=EB=A1=9C=20store=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EA=B5=AC=EB=8F=85=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/stores/modal.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/common/stores/modal.ts b/src/common/stores/modal.ts index e78648843..ac40a9c19 100644 --- a/src/common/stores/modal.ts +++ b/src/common/stores/modal.ts @@ -24,3 +24,7 @@ export const useModalStore = create((set) => ({ resetStore: () => set(() => ({ modalStore: [] })), })); + +export const useOpenModal = () => useModalStore((state) => state.openModal); +export const useCloseModal = () => useModalStore((state) => state.closeModal); +export const useResetModalStore = () => useModalStore((state) => state.resetStore); From 639cc503056d3b706817894522cae731ff875345 Mon Sep 17 00:00:00 2001 From: rtttr1 Date: Fri, 14 Nov 2025 20:13:30 +0900 Subject: [PATCH 06/11] =?UTF-8?q?feat:=20=EB=AA=A8=EB=8B=AC=20FocuseTrap?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20=EC=A0=91=EA=B7=BC=EC=84=B1?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/components/FocusTrap/FocusTrap.tsx | 50 +++++++++++++++++++ src/common/components/Modal/Modal.tsx | 8 +-- src/common/components/Modal/ModalLayout.tsx | 5 +- 3 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 src/common/components/FocusTrap/FocusTrap.tsx diff --git a/src/common/components/FocusTrap/FocusTrap.tsx b/src/common/components/FocusTrap/FocusTrap.tsx new file mode 100644 index 000000000..ca05b4543 --- /dev/null +++ b/src/common/components/FocusTrap/FocusTrap.tsx @@ -0,0 +1,50 @@ +import type { ReactNode } from 'react'; +import { useEffect, useRef } from 'react'; + +// 자식 요소 내부의 포커스 가능한 요소들로 포커스 가두는 컴포넌트 +const FocusTrap = ({ children }: { children: ReactNode }) => { + const containerRef = useRef(null); + + useEffect(() => { + const container = containerRef.current; + + if (!container) return; + + const focusableElements = container.querySelectorAll( + 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]' + ); + + if (focusableElements.length === 0) return; + + const firstFocusableElement = focusableElements[0]; + const lastFocusableElement = focusableElements[focusableElements.length - 1]; + + // 랜더링시 첫 요소에 포커스 줘서 포커스 가두기 + focusableElements[0].focus(); + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key !== 'Tab') return; + + if (e.shiftKey) { + // shift + Tab 으로 역방향인 경우 + if (document.activeElement === firstFocusableElement) { + e.preventDefault(); + lastFocusableElement.focus(); + } + } else { + // Tab 으로 정방향인 경우 + if (document.activeElement === lastFocusableElement) { + e.preventDefault(); + firstFocusableElement.focus(); + } + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, []); + + return
{children}
; +}; + +export default FocusTrap; diff --git a/src/common/components/Modal/Modal.tsx b/src/common/components/Modal/Modal.tsx index cec8e2d70..0b7547074 100644 --- a/src/common/components/Modal/Modal.tsx +++ b/src/common/components/Modal/Modal.tsx @@ -30,13 +30,13 @@ const Modal = ({ onClickHandler(); } - onClose(); + // onClose(); }; return ( - {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} -
e.stopPropagation()} className={containerStyle}> + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */} + e.stopPropagation()} className={containerStyle}>
{content}
{type === 'default' && ( @@ -55,7 +55,7 @@ const Modal = ({ )}
-
+
); }; diff --git a/src/common/components/Modal/ModalLayout.tsx b/src/common/components/Modal/ModalLayout.tsx index 8fffe4c11..6f39ea8fd 100644 --- a/src/common/components/Modal/ModalLayout.tsx +++ b/src/common/components/Modal/ModalLayout.tsx @@ -1,4 +1,5 @@ -import { useEffect, type PropsWithChildren } from 'react'; +import { ReactElement, useEffect, type PropsWithChildren } from 'react'; +import FocusTrap from '@/common/components/FocusTrap/FocusTrap'; import { layoutStyle } from '@/common/components/Modal/modal.css'; interface ModalLayoutProps extends PropsWithChildren { @@ -24,7 +25,7 @@ const ModalLayout = ({ onClose, children }: ModalLayoutProps) => { onClose(); }} className={layoutStyle}> - {children} + {children}
); }; From 51796eea2b20e8c9e283729cab4b8fd9aa1823b3 Mon Sep 17 00:00:00 2001 From: rtttr1 Date: Fri, 14 Nov 2025 20:15:18 +0900 Subject: [PATCH 07/11] =?UTF-8?q?feat:=20slice=EB=90=9C=20openModal?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/home/Home.tsx | 29 +++++++++++++++++++ .../components/StudentCard/StudentCard.tsx | 4 +-- .../CancelConfirmPage/CancelConfirmPage.tsx | 4 +-- .../components/NoticeStep/NoticeStep.tsx | 4 +-- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index ee32083df..24904ee66 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -1,8 +1,13 @@ +import { useNavigate } from 'react-router-dom'; import Footer from '@/pages/home/components/Footer/Footer'; import HomeCarousel from '@/pages/home/components/HomeCarousel/HomeCarousel'; import LatestLessons from '@/pages/home/components/LatestLessons/LatestLessons'; import PopularGenre from '@/pages/home/components/PopularGenre/PopularGenre'; import UpcomingLessons from '@/pages/home/components/UpcomingLessons/UpcomingLessons'; +import { ROUTES_CONFIG } from '@/routes/routesConfig'; +import Modal from '@/common/components/Modal/Modal'; +import { useOpenModal } from '@/common/stores/modal'; +import BoxButton from '@/shared/components/BoxButton/BoxButton'; import { FetchErrorBoundary } from '@/shared/components/ErrorBoundary/FetchErrorBoundary/FetchErrorBoundary'; const images = '/images/image_kkukgirl.webp'; @@ -15,8 +20,32 @@ const preload = (imageArray: string) => { const Home = () => { preload(images); + const openModal = useOpenModal(); + + const navigate = useNavigate(); + const handleModalOpen = () => { + openModal(({ close }) => ( + { + navigate(ROUTES_CONFIG.mypage.path); + }} + /> + )); + }; + return (
+ + openModal(({ close }) => ( + + )) + }> + 모달오픈 + diff --git a/src/pages/instructor/classDetail/components/StudentCard/StudentCard.tsx b/src/pages/instructor/classDetail/components/StudentCard/StudentCard.tsx index 4c783578e..43aff651f 100644 --- a/src/pages/instructor/classDetail/components/StudentCard/StudentCard.tsx +++ b/src/pages/instructor/classDetail/components/StudentCard/StudentCard.tsx @@ -7,7 +7,7 @@ import { formatPhoneNumber } from '@/pages/instructor/utils/format'; import { STATUS_KOREAN_MAP } from '@/pages/mypage/components/mypageReservation/constants/statusMap'; import type { ReservationStatus } from '@/pages/mypage/components/mypageReservation/types/reservationStatus'; import Modal from '@/common/components/Modal/Modal'; -import { useModalStore } from '@/common/stores/modal'; +import { useOpenModal } from '@/common/stores/modal'; import ApplyTag from '@/shared/components/ApplyTag/ApplyTag'; import BoxButton from '@/shared/components/BoxButton/BoxButton'; import Head from '@/shared/components/Head/Head'; @@ -35,7 +35,7 @@ interface StudentCardPropTypes { } const StudentCard = ({ studentData, index, lessonId, selectedTab }: StudentCardPropTypes) => { - const { openModal } = useModalStore(); + const openModal = useOpenModal(); const { text: buttonText, variant: buttonVariant } = STATUS_BUTTON_MAP[studentData.reservationStatus]; diff --git a/src/pages/mypage/components/CancelConfirmPage/CancelConfirmPage.tsx b/src/pages/mypage/components/CancelConfirmPage/CancelConfirmPage.tsx index 10a56bcbc..9f2c30c1c 100644 --- a/src/pages/mypage/components/CancelConfirmPage/CancelConfirmPage.tsx +++ b/src/pages/mypage/components/CancelConfirmPage/CancelConfirmPage.tsx @@ -9,7 +9,7 @@ import { CANCEL_CONFIRM_MESSAGE } from '@/pages/mypage/constants/modalMessage'; import ApplicantInfo from '@/pages/reservation/components/ApplicantInfo/ApplicantInfo'; import ClassInfo from '@/pages/reservation/components/ClassInfo/ClassInfo'; import Modal from '@/common/components/Modal/Modal'; -import { useModalStore } from '@/common/stores/modal'; +import { useOpenModal } from '@/common/stores/modal'; import { useGetBankList } from '@/shared/apis/queries'; import BankBottomSheet from '@/shared/components/BankBottomSheet/BankBottomSheet'; import BlurBotton from '@/shared/components/BlurButton/BlurButton'; @@ -38,7 +38,7 @@ const CancelConfirmPage = () => { const { data: myPageData } = useGetMyPage(); const { data: bankList } = useGetBankList(); - const { openModal } = useModalStore(); + const openModal = useOpenModal(); const { mutate: cancelReservation, isPending } = useCancelReservation(); const navigationState = location.state as NavigationState | null; diff --git a/src/pages/mypage/components/Withdraw/components/NoticeStep/NoticeStep.tsx b/src/pages/mypage/components/Withdraw/components/NoticeStep/NoticeStep.tsx index f49e62aad..23e76d73a 100644 --- a/src/pages/mypage/components/Withdraw/components/NoticeStep/NoticeStep.tsx +++ b/src/pages/mypage/components/Withdraw/components/NoticeStep/NoticeStep.tsx @@ -13,7 +13,7 @@ import { } from '@/pages/mypage/components/Withdraw/components/NoticeStep/noticeStep.css'; import { BULLET_LIST } from '@/pages/mypage/components/Withdraw/constants'; import Modal from '@/common/components/Modal/Modal'; -import { useModalStore } from '@/common/stores/modal'; +import { useOpenModal } from '@/common/stores/modal'; import IcCheckcircleGray0524 from '@/shared/assets/svg/IcCheckcircleGray0524'; import IcCheckcircleMain0324 from '@/shared/assets/svg/IcCheckcircleMain0324'; import BlurButton from '@/shared/components/BlurButton/BlurButton'; @@ -28,7 +28,7 @@ interface NoticeStepPropTypes { const NoticeStep = ({ onNext }: NoticeStepPropTypes) => { const [isAgreed, setIsAgreed] = useState(false); - const { openModal } = useModalStore(); + const openModal = useOpenModal(); const titleId = useId(); From 1cb107cc817f4ff63aa0cbf926e3b34779da7e36 Mon Sep 17 00:00:00 2001 From: rtttr1 Date: Thu, 1 Jan 2026 21:06:34 +0900 Subject: [PATCH 08/11] =?UTF-8?q?style:=20dialog=20border:none=20=EC=A0=84?= =?UTF-8?q?=EC=97=AD=20=EC=8A=A4=ED=83=80=EC=9D=BC=EB=A1=9C=20=EC=A7=80?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/styles/reset.css.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/shared/styles/reset.css.ts b/src/shared/styles/reset.css.ts index c67046856..8a0e3ba48 100644 --- a/src/shared/styles/reset.css.ts +++ b/src/shared/styles/reset.css.ts @@ -84,3 +84,7 @@ globalStyle('button', { globalStyle('input, textarea', { outline: 'none', }); + +globalStyle('dialog', { + border: 'none', +}); From 78f89d5c2937a0cdf8f560b7897e990a42580269 Mon Sep 17 00:00:00 2001 From: rtttr1 Date: Thu, 1 Jan 2026 21:07:34 +0900 Subject: [PATCH 09/11] =?UTF-8?q?refactor:=20ModalLayout=20Modal=EA=B3=BC?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC=ED=95=98=EC=97=AC=20=EB=91=90=EA=B0=9C=20?= =?UTF-8?q?=EC=9D=B4=EC=83=81=EC=9D=98=20=EB=AA=A8=EB=8B=AC=20=EB=9D=84?= =?UTF-8?q?=EC=9A=B8=EC=8B=9C=20=EB=B0=B0=EA=B2=BD=EC=83=89=EC=9D=B4=20?= =?UTF-8?q?=EB=8D=94=20=EC=96=B4=EB=91=90=EC=9B=8C=EC=A7=80=EB=8A=94=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/components/Modal/Modal.tsx | 8 ++++---- src/common/components/Modal/ModalLayout.tsx | 19 +++++++++---------- src/common/stores/modal.ts | 4 ++++ .../components/ModalProvider/ModalProvier.tsx | 7 +++++-- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/common/components/Modal/Modal.tsx b/src/common/components/Modal/Modal.tsx index 0b7547074..e5d746204 100644 --- a/src/common/components/Modal/Modal.tsx +++ b/src/common/components/Modal/Modal.tsx @@ -1,5 +1,5 @@ import type { ReactElement } from 'react'; -import ModalLayout from '@/common/components/Modal/ModalLayout'; +import FocusTrap from '@/common/components/FocusTrap/FocusTrap'; import { contentStyle, containerStyle, @@ -34,8 +34,8 @@ const Modal = ({ }; return ( - - {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */} + + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions */} e.stopPropagation()} className={containerStyle}>
{content}
@@ -56,7 +56,7 @@ const Modal = ({ )}
-
+ ); }; diff --git a/src/common/components/Modal/ModalLayout.tsx b/src/common/components/Modal/ModalLayout.tsx index 6f39ea8fd..508487cf5 100644 --- a/src/common/components/Modal/ModalLayout.tsx +++ b/src/common/components/Modal/ModalLayout.tsx @@ -1,31 +1,30 @@ -import { ReactElement, useEffect, type PropsWithChildren } from 'react'; -import FocusTrap from '@/common/components/FocusTrap/FocusTrap'; +import { useEffect, type PropsWithChildren } from 'react'; import { layoutStyle } from '@/common/components/Modal/modal.css'; +import { useModalStore } from '@/common/stores/modal'; -interface ModalLayoutProps extends PropsWithChildren { - onClose: () => void; -} +const ModalLayout = ({ children }: PropsWithChildren) => { + const { closeLastModal } = useModalStore(); -const ModalLayout = ({ onClose, children }: ModalLayoutProps) => { // esc 키 누르면 모달 닫기 useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { - onClose(); + closeLastModal(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); - }, [onClose]); + }, [closeLastModal]); return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
{ e.stopPropagation(); - onClose(); + closeLastModal(); }} className={layoutStyle}> - {children} + {children}
); }; diff --git a/src/common/stores/modal.ts b/src/common/stores/modal.ts index ac40a9c19..353871cb6 100644 --- a/src/common/stores/modal.ts +++ b/src/common/stores/modal.ts @@ -9,6 +9,7 @@ interface ModalStore { openModal: (render: RenderProps) => void; closeModal: (id: string) => void; + closeLastModal: () => void; resetStore: () => void; } @@ -22,9 +23,12 @@ export const useModalStore = create((set) => ({ closeModal: (id) => set((state) => ({ modalStore: state.modalStore.filter((modal) => modal.id !== id) })), + closeLastModal: () => set((state) => ({ modalStore: state.modalStore.slice(0, -1) })), + resetStore: () => set(() => ({ modalStore: [] })), })); export const useOpenModal = () => useModalStore((state) => state.openModal); export const useCloseModal = () => useModalStore((state) => state.closeModal); +export const useCloseLastModal = () => useModalStore((state) => state.closeLastModal); export const useResetModalStore = () => useModalStore((state) => state.resetStore); diff --git a/src/shared/components/ModalProvider/ModalProvier.tsx b/src/shared/components/ModalProvider/ModalProvier.tsx index 035b9f53a..acf636041 100644 --- a/src/shared/components/ModalProvider/ModalProvier.tsx +++ b/src/shared/components/ModalProvider/ModalProvier.tsx @@ -1,5 +1,6 @@ import { useEffect } from 'react'; import { useLocation } from 'react-router-dom'; +import ModalLayout from '@/common/components/Modal/ModalLayout'; import { useModalStore } from '@/common/stores/modal'; const ModalProvider = () => { @@ -25,12 +26,14 @@ const ModalProvider = () => { } }, [modalStore]); + if (modalStore.length === 0) return null; + return ( - <> + {modalStore.map(({ id, render }) => render({ isOpen: modalStore.some((modal) => modal.id === id), close: () => closeModal(id) }) )} - + ); }; From 0841400efa365329aa91b30c6162fdc1650699a9 Mon Sep 17 00:00:00 2001 From: rtttr1 Date: Thu, 1 Jan 2026 21:23:19 +0900 Subject: [PATCH 10/11] =?UTF-8?q?refactor:=20=ED=8F=AC=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=20=EA=B0=80=EB=8A=A5=ED=95=9C=20=EC=B2=AB=EC=9A=94=EC=86=8C=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=A1=9C=20=ED=8F=AC=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=EC=A3=BC=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/components/FocusTrap/FocusTrap.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/components/FocusTrap/FocusTrap.tsx b/src/common/components/FocusTrap/FocusTrap.tsx index ca05b4543..1e082c86d 100644 --- a/src/common/components/FocusTrap/FocusTrap.tsx +++ b/src/common/components/FocusTrap/FocusTrap.tsx @@ -20,7 +20,7 @@ const FocusTrap = ({ children }: { children: ReactNode }) => { const lastFocusableElement = focusableElements[focusableElements.length - 1]; // 랜더링시 첫 요소에 포커스 줘서 포커스 가두기 - focusableElements[0].focus(); + firstFocusableElement.focus(); const handleKeyDown = (e: KeyboardEvent) => { if (e.key !== 'Tab') return; From 8dd6981b22179adf09b3735401b5031c5156e4a9 Mon Sep 17 00:00:00 2001 From: rtttr1 Date: Thu, 1 Jan 2026 22:05:41 +0900 Subject: [PATCH 11/11] =?UTF-8?q?fix:=20=EB=B9=8C=EB=93=9C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 6b6efafee..822c7e64d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,8 +4,6 @@ import { useEffect } from 'react'; import { Toaster } from 'react-hot-toast'; import { RouterProvider } from 'react-router-dom'; import { router } from '@/routes/router.tsx'; -import GlobalErrorBoundary from '@/shared/components/ErrorBoundary/GlobalErrorBoundary/GlobalErrorBoundary'; -import ModalProvider from '@/shared/components/ModalProvider/ModalProvier'; import queryClient from './queryClient'; import './shared/styles/index.css';