Skip to content

Commit

Permalink
[FE][Feat] #372 : 드랍다운 개별적으로 동작하도록 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
effozen committed Dec 3, 2024
1 parent 9b604eb commit 36321f2
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 104 deletions.
13 changes: 8 additions & 5 deletions frontend/src/component/common/dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -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 {
/** 드롭다운 컴포넌트 내부에 들어갈 컨텐츠 */
Expand Down Expand Up @@ -34,10 +35,12 @@ interface IDropdownProps {
*/

export const Dropdown = (props: IDropdownProps) => {
const id = useMemo(() => uuidv4(), []); // 각 Dropdown 인스턴스에 고유 ID 생성

return (
<aside className="relative flex w-fit flex-col">
<ToggleProvider>{props.children}</ToggleProvider>
</aside>
<DropdownInstanceContext.Provider value={id}>
<aside className="relative flex w-fit flex-col">{props.children}</aside>
</DropdownInstanceContext.Provider>
);
};

Expand Down
25 changes: 0 additions & 25 deletions frontend/src/component/common/dropdown/DropdownContext.tsx

This file was deleted.

18 changes: 12 additions & 6 deletions frontend/src/component/common/dropdown/DropdownMenu.tsx
Original file line number Diff line number Diff line change
@@ -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 {
/** 드롭다운 메뉴가 열려있는지 여부 */
Expand All @@ -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<HTMLUListElement | null>(null);

const handleOutSideClick = (event: MouseEvent) => {
Expand All @@ -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 && (
<ul
ref={ref}
className={classNames(
// 추후 애니메이션 조건부 적용을 위해서 classNames 사용
'align-center',
'animate-smoothAppear',
'absolute',
Expand Down
13 changes: 10 additions & 3 deletions frontend/src/component/common/dropdown/DropdownTrigger.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ReactNode, useContext } 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 IDropdownTriggerProps {
/** 버튼 내부에 들어갈 컨텐츠 */
Expand Down Expand Up @@ -28,10 +29,16 @@ interface IDropdownTriggerProps {
*/

export const DropdownTrigger = (props: IDropdownTriggerProps) => {
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 (
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/component/content/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
>
{/* <div className="relative flex w-full flex-row justify-between px-4 py-5"> */}
<div>
<header className="border-gray-200 pb-1 text-start text-base font-normal">
{props.title}
Expand All @@ -102,7 +101,7 @@ export const Content = (props: IContentProps) => {
<div
className="relative"
onClick={e => {
e.stopPropagation();
e.stopPropagation(); // 부모의 onClick 이벤트 방지
}}
>
<Dropdown>
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/context/DropdownContext.tsx
Original file line number Diff line number Diff line change
@@ -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<IToggleContext>({
openDropdownId: null,
setOpenDropdownId: () => {},
});

export const ToggleProvider = ({ children }: IToggleProviderProps) => {
const [openDropdownId, setOpenDropdownId] = useState<string | null>(null);

const value = useMemo(() => ({ openDropdownId, setOpenDropdownId }), [openDropdownId]);

return <ToggleContext.Provider value={value}>{children}</ToggleContext.Provider>;
};
3 changes: 3 additions & 0 deletions frontend/src/context/DropdownInstanceContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createContext } from 'react';

export const DropdownInstanceContext = createContext<string | null>(null);
130 changes: 67 additions & 63 deletions frontend/src/pages/Main.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 {
Expand Down Expand Up @@ -116,73 +118,75 @@ export const Main = () => {
const isUserLoggedIn = loadLocalData(AppConfig.KEYS.LOGIN_TOKEN) !== null;

return (
<div className="flex flex-col overflow-hidden">
<header className="absolute left-0 right-0 top-0 z-10 flex p-4">
{isUserLoggedIn && (
<button
type="button"
onClick={handleLogout}
className="flex flex-col items-center gap-2 text-gray-700"
>
<MdLogout size={24} className="text-blueGray-800" />
<span className="text-xs">로그아웃</span>
</button>
)}
</header>
<ToggleProvider>
<div className="flex flex-col overflow-hidden">
<header className="absolute left-0 right-0 top-0 z-10 flex p-4">
{isUserLoggedIn && (
<button
type="button"
onClick={handleLogout}
className="flex flex-col items-center gap-2 text-gray-700"
>
<MdLogout size={24} className="text-blueGray-800" />
<span className="text-xs">로그아웃</span>
</button>
)}
</header>

<main className="absolute h-full w-screen flex-grow overflow-hidden">
{/* eslint-disable-next-line no-nested-ternary */}
{lat && lng ? (
otherLocations ? (
<MapCanvasForView
width="100%"
height="100%"
lat={lat}
lng={lng}
alpha={alpha}
otherLocations={otherLocations}
/>
<main className="absolute h-full w-screen flex-grow overflow-hidden">
{/* eslint-disable-next-line no-nested-ternary */}
{lat && lng ? (
otherLocations ? (
<MapCanvasForView
width="100%"
height="100%"
lat={lat}
lng={lng}
alpha={alpha}
otherLocations={otherLocations}
/>
) : (
<LoadingSpinner />
)
) : (
<LoadingSpinner />
)
) : (
<section className="flex h-full flex-col items-center justify-center gap-2 text-xl text-gray-700">
<LoadingSpinner />
{error ? `Error: ${error}` : 'Loading map data...'}
</section>
)}
</main>
<section className="flex h-full flex-col items-center justify-center gap-2 text-xl text-gray-700">
<LoadingSpinner />
{error ? `Error: ${error}` : 'Loading map data...'}
</section>
)}
</main>

{isUserLoggedIn ? (
<BottomSheet minHeight={MIN_HEIGHT} maxHeight={MAX_HEIGHT} backgroundColor="#FFFFFF">
{channels.map(item => (
<Fragment key={item.id}>
<Content
channelId={item.id}
title={item.name}
link={`/channel/${item.id}/host`}
person={item.guest_count}
time={item.generated_at}
/>
<hr className="my-2" />
</Fragment>
))}
<div className="h-20" />
</BottomSheet>
) : (
<BottomSheet minHeight={MIN_HEIGHT} maxHeight={MAX_HEIGHT} backgroundColor="#F1F1F1F2">
<div className="h-full w-full cursor-pointer" onClick={handleLoginRequest}>
<div className="absolute left-1/2 top-[20%] flex -translate-x-1/2 transform cursor-pointer flex-col p-6 text-center">
<p className="text-grayscale-175 mb-5 text-lg font-normal">로그인을 진행하여</p>
<p className="text-grayscale-175 mb-5 text-lg font-normal">더 많은 기능을</p>
<p className="text-grayscale-175 text-lg font-normal">사용해보세요</p>
{isUserLoggedIn ? (
<BottomSheet minHeight={MIN_HEIGHT} maxHeight={MAX_HEIGHT} backgroundColor="#FFFFFF">
{channels.map(item => (
<Fragment key={item.id}>
<Content
channelId={item.id}
title={item.name}
link={`/channel/${item.id}/host`}
person={item.guest_count}
time={item.generated_at}
/>
<hr className="my-2" />
</Fragment>
))}
<div className="h-20" />
</BottomSheet>
) : (
<BottomSheet minHeight={MIN_HEIGHT} maxHeight={MAX_HEIGHT} backgroundColor="#F1F1F1F2">
<div className="h-full w-full cursor-pointer" onClick={handleLoginRequest}>
<div className="absolute left-1/2 top-[20%] flex -translate-x-1/2 transform cursor-pointer flex-col p-6 text-center">
<p className="text-grayscale-175 mb-5 text-lg font-normal">로그인을 진행하여</p>
<p className="text-grayscale-175 mb-5 text-lg font-normal">더 많은 기능을</p>
<p className="text-grayscale-175 text-lg font-normal">사용해보세요</p>
</div>
</div>
</div>
</BottomSheet>
)}
</BottomSheet>
)}

{/* 로그인 모달 */}
<AuthModal isOpen={showLoginModal} onClose={handleCloseLoginModal} type="login" />
</div>
{/* 로그인 모달 */}
<AuthModal isOpen={showLoginModal} onClose={handleCloseLoginModal} type="login" />
</div>
</ToggleProvider>
);
};

0 comments on commit 36321f2

Please sign in to comment.