diff --git a/frontend/src/component/common/dropdown/Dropdown.tsx b/frontend/src/component/common/dropdown/Dropdown.tsx index 0d8e1acf..d9c194c3 100644 --- a/frontend/src/component/common/dropdown/Dropdown.tsx +++ b/frontend/src/component/common/dropdown/Dropdown.tsx @@ -1,8 +1,9 @@ -import { ReactNode } from 'react'; +import { ReactNode, useMemo } from 'react'; +import { v4 as uuidv4 } from 'uuid'; import { DropdownTrigger } from '@/component/common/dropdown/DropdownTrigger.tsx'; import { DropdownItem } from '@/component/common/dropdown/DropdownItem.tsx'; import { DropdownMenu } from '@/component/common/dropdown/DropdownMenu.tsx'; -import { ToggleProvider } from '@/component/common/dropdown/DropdownContext.tsx'; +import { DropdownInstanceContext } from '@/context/DropdownInstanceContext'; interface IDropdownProps { /** 드롭다운 컴포넌트 내부에 들어갈 컨텐츠 */ @@ -34,10 +35,12 @@ interface IDropdownProps { */ export const Dropdown = (props: IDropdownProps) => { + const id = useMemo(() => uuidv4(), []); // 각 Dropdown 인스턴스에 고유 ID 생성 + return ( - + + + ); }; diff --git a/frontend/src/component/common/dropdown/DropdownContext.tsx b/frontend/src/component/common/dropdown/DropdownContext.tsx deleted file mode 100644 index 61b865ac..00000000 --- a/frontend/src/component/common/dropdown/DropdownContext.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { createContext, ReactNode, useMemo, useState } from 'react'; - -export interface IToggleContext { - isOpen: boolean; - toggle: () => void; -} - -interface IToggleProviderProps { - children: ReactNode; -} - -export const ToggleContext = createContext({ - isOpen: false, - toggle: () => {}, -}); - -export const ToggleProvider = (props: IToggleProviderProps) => { - const [isOpen, setIsOpen] = useState(false); - const toggle = () => setIsOpen(prevIsOpen => !prevIsOpen); - const toggleContextValue = useMemo(() => ({ isOpen, toggle }), [isOpen]); - - return ( - {props.children} - ); -}; diff --git a/frontend/src/component/common/dropdown/DropdownMenu.tsx b/frontend/src/component/common/dropdown/DropdownMenu.tsx index c49eab34..189cd792 100644 --- a/frontend/src/component/common/dropdown/DropdownMenu.tsx +++ b/frontend/src/component/common/dropdown/DropdownMenu.tsx @@ -1,6 +1,7 @@ import { ReactNode, useContext, useRef, useEffect } from 'react'; import classNames from 'classnames'; -import { ToggleContext } from '@/component/common/dropdown/DropdownContext'; +import { ToggleContext } from '@/context/DropdownContext.tsx'; +import { DropdownInstanceContext } from '@/context/DropdownInstanceContext'; interface IDropdownMenuProps { /** 드롭다운 메뉴가 열려있는지 여부 */ @@ -27,7 +28,9 @@ interface IDropdownMenuProps { */ export const DropdownMenu = (props: IDropdownMenuProps) => { - const { isOpen, toggle } = useContext(ToggleContext); + const { openDropdownId, setOpenDropdownId } = useContext(ToggleContext); + const dropdownId = useContext(DropdownInstanceContext); + const isOpen = openDropdownId === dropdownId; const ref = useRef(null); const handleOutSideClick = (event: MouseEvent) => { @@ -43,23 +46,26 @@ export const DropdownMenu = (props: IDropdownMenuProps) => { !ref.current.contains(target) && target.dataset.component !== 'DropdownTrigger' ) { - toggle(); + setOpenDropdownId(null); // 외부 클릭 시 드롭다운 닫기 } }; useEffect(() => { - document.addEventListener('click', handleOutSideClick); + if (isOpen) { + document.addEventListener('click', handleOutSideClick); + } else { + document.removeEventListener('click', handleOutSideClick); + } return () => { document.removeEventListener('click', handleOutSideClick); }; - }, []); + }, [isOpen]); return ( isOpen && ( { - const { toggle } = useContext(ToggleContext); + const { openDropdownId, setOpenDropdownId } = useContext(ToggleContext); + const dropdownId = useContext(DropdownInstanceContext); + const isOpen = openDropdownId === dropdownId; const handleOnClick = () => { - toggle(); + if (isOpen) { + setOpenDropdownId(null); // 이미 열려 있으면 닫기 + } else { + setOpenDropdownId(dropdownId); // 이 드롭다운 열기 + } }; return ( diff --git a/frontend/src/component/content/Content.tsx b/frontend/src/component/content/Content.tsx index b2426028..9eb9d5f3 100644 --- a/frontend/src/component/content/Content.tsx +++ b/frontend/src/component/content/Content.tsx @@ -82,7 +82,6 @@ export const Content = (props: IContentProps) => { className="relative flex w-full flex-row items-center justify-between px-4 py-5" onClick={goToHostViewPage} > - {/* */} {props.title} @@ -102,7 +101,7 @@ export const Content = (props: IContentProps) => { { - e.stopPropagation(); + e.stopPropagation(); // 부모의 onClick 이벤트 방지 }} > diff --git a/frontend/src/context/DropdownContext.tsx b/frontend/src/context/DropdownContext.tsx new file mode 100644 index 00000000..7d131dd2 --- /dev/null +++ b/frontend/src/context/DropdownContext.tsx @@ -0,0 +1,24 @@ +// DropdownContext.tsx +import { createContext, ReactNode, useState, useMemo } from 'react'; + +export interface IToggleContext { + openDropdownId: string | null; + setOpenDropdownId: (id: string | null) => void; +} + +interface IToggleProviderProps { + children: ReactNode; +} + +export const ToggleContext = createContext({ + openDropdownId: null, + setOpenDropdownId: () => {}, +}); + +export const ToggleProvider = ({ children }: IToggleProviderProps) => { + const [openDropdownId, setOpenDropdownId] = useState(null); + + const value = useMemo(() => ({ openDropdownId, setOpenDropdownId }), [openDropdownId]); + + return {children}; +}; diff --git a/frontend/src/context/DropdownInstanceContext.tsx b/frontend/src/context/DropdownInstanceContext.tsx new file mode 100644 index 00000000..c27b732f --- /dev/null +++ b/frontend/src/context/DropdownInstanceContext.tsx @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export const DropdownInstanceContext = createContext(null); diff --git a/frontend/src/pages/Main.tsx b/frontend/src/pages/Main.tsx index 245dce0f..bebbc120 100644 --- a/frontend/src/pages/Main.tsx +++ b/frontend/src/pages/Main.tsx @@ -1,3 +1,4 @@ +// Main.tsx import { Fragment, useContext, useEffect, useState } from 'react'; import { MdLogout } from 'react-icons/md'; import { FooterContext } from '@/component/layout/footer/LayoutFooterProvider'; @@ -14,6 +15,7 @@ import { getUserLocation } from '@/hooks/getUserLocation.ts'; import { MapCanvasForView } from '@/component/canvasWithMap/canvasWithMapForView/MapCanvasForView.tsx'; import { LoadingSpinner } from '@/component/common/loadingSpinner/LoadingSpinner.tsx'; import { UserContext } from '@/context/UserContext'; +import { ToggleProvider } from '@/context/DropdownContext.tsx'; export const Main = () => { const { @@ -116,73 +118,75 @@ export const Main = () => { const isUserLoggedIn = loadLocalData(AppConfig.KEYS.LOGIN_TOKEN) !== null; return ( - - - {isUserLoggedIn && ( - - - 로그아웃 - - )} - + + + + {isUserLoggedIn && ( + + + 로그아웃 + + )} + - - {/* eslint-disable-next-line no-nested-ternary */} - {lat && lng ? ( - otherLocations ? ( - + + {/* eslint-disable-next-line no-nested-ternary */} + {lat && lng ? ( + otherLocations ? ( + + ) : ( + + ) ) : ( - - ) - ) : ( - - - {error ? `Error: ${error}` : 'Loading map data...'} - - )} - + + + {error ? `Error: ${error}` : 'Loading map data...'} + + )} + - {isUserLoggedIn ? ( - - {channels.map(item => ( - - - - - ))} - - - ) : ( - - - - 로그인을 진행하여 - 더 많은 기능을 - 사용해보세요 + {isUserLoggedIn ? ( + + {channels.map(item => ( + + + + + ))} + + + ) : ( + + + + 로그인을 진행하여 + 더 많은 기능을 + 사용해보세요 + - - - )} + + )} - {/* 로그인 모달 */} - - + {/* 로그인 모달 */} + + + ); };
로그인을 진행하여
더 많은 기능을
사용해보세요