From 36321f2d91a1742b7ba88745078e9021852e7f0e Mon Sep 17 00:00:00 2001 From: effozen Date: Tue, 3 Dec 2024 17:27:50 +0900 Subject: [PATCH] =?UTF-8?q?[FE][Feat]=20#372=20:=20=EB=93=9C=EB=9E=8D?= =?UTF-8?q?=EB=8B=A4=EC=9A=B4=20=EA=B0=9C=EB=B3=84=EC=A0=81=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=8F=99=EC=9E=91=ED=95=98=EB=8F=84=EB=A1=9D=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 --- .../component/common/dropdown/Dropdown.tsx | 13 +- .../common/dropdown/DropdownContext.tsx | 25 ---- .../common/dropdown/DropdownMenu.tsx | 18 ++- .../common/dropdown/DropdownTrigger.tsx | 13 +- frontend/src/component/content/Content.tsx | 3 +- frontend/src/context/DropdownContext.tsx | 24 ++++ .../src/context/DropdownInstanceContext.tsx | 3 + frontend/src/pages/Main.tsx | 130 +++++++++--------- 8 files changed, 125 insertions(+), 104 deletions(-) delete mode 100644 frontend/src/component/common/dropdown/DropdownContext.tsx create mode 100644 frontend/src/context/DropdownContext.tsx create mode 100644 frontend/src/context/DropdownInstanceContext.tsx 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 => ( + + +
    +
    + ))} +
    + + ) : ( + +
    +
    +

    로그인을 진행하여

    +

    더 많은 기능을

    +

    사용해보세요

    +
    -
    -
    - )} + + )} - {/* 로그인 모달 */} - -
    + {/* 로그인 모달 */} + +
    + ); };