From aca257d1e97f1f23d203b7f0c1d05339fd1d7d86 Mon Sep 17 00:00:00 2001 From: effozen Date: Tue, 12 Nov 2024 14:57:54 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[FE][Feat]=20#108=20:=20dropdown=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/dropdown/DropdownButton.tsx | 5 +- .../src/stories/DropdownButton.stories.tsx | 46 ------------------- 2 files changed, 3 insertions(+), 48 deletions(-) delete mode 100644 frontend/src/stories/DropdownButton.stories.tsx diff --git a/frontend/src/component/common/dropdown/DropdownButton.tsx b/frontend/src/component/common/dropdown/DropdownButton.tsx index 6e5043d9..6da9bb56 100644 --- a/frontend/src/component/common/dropdown/DropdownButton.tsx +++ b/frontend/src/component/common/dropdown/DropdownButton.tsx @@ -4,12 +4,13 @@ import classNames from 'classnames'; interface IDropdownButtonProps extends React.ButtonHTMLAttributes { children: ReactNode; className?: string; + onclick?: React.MouseEventHandler; } export const DropdownButton = (props: IDropdownButtonProps) => { return ( diff --git a/frontend/src/stories/DropdownButton.stories.tsx b/frontend/src/stories/DropdownButton.stories.tsx deleted file mode 100644 index 46745bee..00000000 --- a/frontend/src/stories/DropdownButton.stories.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import { fn } from '@storybook/test'; - -import { DropdownButton } from '@/component/common/dropdown/DropdownButton.tsx'; - -import { MdDensityMedium } from 'react-icons/md'; - -// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export -const meta = { - title: 'Dropdown/Button', - component: DropdownButton, - parameters: { - // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout - layout: 'centered', - }, - // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs - tags: ['autodocs'], - // More on argTypes: https://storybook.js.org/docs/api/argtypes - argTypes: { - children: { - control: 'object', - description: '자식 컴포넌트로 항상 리액트 노드를 넘겨준다.', - table: { - type: { summary: 'ReactNode' }, - }, - required: true, // 설명 목적으로 required 여부는 table 필드로 작성함 - }, - className: { - control: 'text', - description: '테일 윈드 기반의 클래스 이름을 넘겨준다.', - }, - }, - // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args - args: { onClick: fn() }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args -export const Default: Story = { - args: { - children: , - className: '', - }, -}; From 230576a35233c1526ff3878b0997f6446f1a73e5 Mon Sep 17 00:00:00 2001 From: effozen Date: Wed, 13 Nov 2024 09:41:47 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[FE][Feat]=20#108=20:=20=EB=93=9C=EB=9E=8D?= =?UTF-8?q?=EB=8B=A4=EC=9A=B4=20=EB=B2=84=ED=8A=BC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 세부적인 디테일 수정사항이 남아있음. - TODO에 배치 --- .../component/common/dropdown/Dropdown.tsx | 35 +++++++++++++++++ .../common/dropdown/DropdownButton.tsx | 28 ------------- .../common/dropdown/DropdownItem.tsx | 17 ++++++++ .../common/dropdown/DropdownMenu.tsx | 39 +++++++++++++++++++ .../common/dropdown/DropdownTrigger.tsx | 32 +++++++++++++++ .../src/component/header/HeaderDropdown.tsx | 37 ++++++++++++++++++ frontend/tailwind.config.js | 10 +++++ 7 files changed, 170 insertions(+), 28 deletions(-) create mode 100644 frontend/src/component/common/dropdown/Dropdown.tsx delete mode 100644 frontend/src/component/common/dropdown/DropdownButton.tsx create mode 100644 frontend/src/component/common/dropdown/DropdownItem.tsx create mode 100644 frontend/src/component/common/dropdown/DropdownMenu.tsx create mode 100644 frontend/src/component/common/dropdown/DropdownTrigger.tsx create mode 100644 frontend/src/component/header/HeaderDropdown.tsx diff --git a/frontend/src/component/common/dropdown/Dropdown.tsx b/frontend/src/component/common/dropdown/Dropdown.tsx new file mode 100644 index 00000000..ec3933fd --- /dev/null +++ b/frontend/src/component/common/dropdown/Dropdown.tsx @@ -0,0 +1,35 @@ +import React, { createContext, ReactNode, useMemo, useState } from 'react'; +import { DropdownTrigger } from '@/component/common/dropdown/DropdownTrigger.tsx'; +import { DropdownItem } from '@/component/common/dropdown/DropdownItem.tsx'; +import { DropdownMenu } from '@/component/common/dropdown/DropdownMenu.tsx'; + +interface IDropdownProps { + children: ReactNode; +} + +export interface IToggleContext { + isOpen: boolean; + setIsOpen: React.Dispatch>; +} + +export const ToggleContext = createContext({ + isOpen: false, + setIsOpen: () => {}, +}); + +// Todo : 드랍다운 외부에서 클릭시 창 닫히게 설정 +export const Dropdown = (props: IDropdownProps) => { + const [isOpen, setIsOpen] = useState(false); + + const toggleContextValue = useMemo(() => ({ isOpen, setIsOpen }), [isOpen, setIsOpen]); + + return ( + + ); +}; + +Dropdown.Trigger = DropdownTrigger; +Dropdown.Item = DropdownItem; +Dropdown.Menu = DropdownMenu; diff --git a/frontend/src/component/common/dropdown/DropdownButton.tsx b/frontend/src/component/common/dropdown/DropdownButton.tsx deleted file mode 100644 index 6da9bb56..00000000 --- a/frontend/src/component/common/dropdown/DropdownButton.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { ReactNode } from 'react'; -import classNames from 'classnames'; - -interface IDropdownButtonProps extends React.ButtonHTMLAttributes { - children: ReactNode; - className?: string; - onclick?: React.MouseEventHandler; -} - -export const DropdownButton = (props: IDropdownButtonProps) => { - return ( - - ); -}; diff --git a/frontend/src/component/common/dropdown/DropdownItem.tsx b/frontend/src/component/common/dropdown/DropdownItem.tsx new file mode 100644 index 00000000..7252a3b8 --- /dev/null +++ b/frontend/src/component/common/dropdown/DropdownItem.tsx @@ -0,0 +1,17 @@ +import { ReactNode } from 'react'; + +interface IDropdownItemProps { + children: ReactNode; + onClick?: React.MouseEventHandler; +} + +export const DropdownItem = (props: IDropdownItemProps) => { + // undefined는 react에서 랜더링 하지 않음 + return ( +
  • + +
  • + ); +}; diff --git a/frontend/src/component/common/dropdown/DropdownMenu.tsx b/frontend/src/component/common/dropdown/DropdownMenu.tsx new file mode 100644 index 00000000..9ffafd62 --- /dev/null +++ b/frontend/src/component/common/dropdown/DropdownMenu.tsx @@ -0,0 +1,39 @@ +import { ReactNode, useContext } from 'react'; +import classNames from 'classnames'; +import { ToggleContext } from '@/component/common/dropdown/Dropdown.tsx'; + +interface IDropdownMenuProps { + children: ReactNode | ReactNode[]; +} + +export const DropdownMenu = (props: IDropdownMenuProps) => { + const { isOpen } = useContext(ToggleContext); + + return ( + isOpen && ( +
      + {props.children} +
    + ) + ); +}; diff --git a/frontend/src/component/common/dropdown/DropdownTrigger.tsx b/frontend/src/component/common/dropdown/DropdownTrigger.tsx new file mode 100644 index 00000000..011d9612 --- /dev/null +++ b/frontend/src/component/common/dropdown/DropdownTrigger.tsx @@ -0,0 +1,32 @@ +import { ReactNode, useContext } from 'react'; +import classNames from 'classnames'; +import { ToggleContext } from '@/component/common/dropdown/Dropdown.tsx'; + +interface IDropdownTriggerProps { + children: ReactNode; +} + +export const DropdownTrigger = (props: IDropdownTriggerProps) => { + const { setIsOpen } = useContext(ToggleContext); + + const handleOnClick = () => { + setIsOpen(prevIsOpen => !prevIsOpen); + }; + + return ( + + ); +}; diff --git a/frontend/src/component/header/HeaderDropdown.tsx b/frontend/src/component/header/HeaderDropdown.tsx new file mode 100644 index 00000000..a71bf20e --- /dev/null +++ b/frontend/src/component/header/HeaderDropdown.tsx @@ -0,0 +1,37 @@ +import { Dropdown } from '@/component/common/dropdown/Dropdown.tsx'; +import { MdMenu, MdLocationOn } from 'react-icons/md'; +import { DropdownItem } from '@/component/common/dropdown/DropdownItem.tsx'; +import classNames from 'classnames'; + +// interface IDropdownContainerProps {} + +// TDDO : props로 전달되면 해석되도록 로직 변경 (props:props: IDropdownContainerProps) +export const HeaderDropdown = () => { + return ( +
    + + + + + + {[ + '사용자 1 보기', + '사용자 2 보기', + '사용자 3 보기', + '사용자 4 보기', + '사용자 5 보기', + ].map((e, i) => ( + + {/* 위치 자동으로 채워지게 하는 방법 찾기 */} +
    + {e} + + {/* 아이콘 색 변경 로직 찾기 + 아이콘 높이 맞추기 */} +
    +
    + ))} +
    +
    +
    + ); +}; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 95613736..1ee4edd1 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -47,6 +47,16 @@ module.exports = { float: '0 4px 20px rgba(0, 0, 0, 0.13)', basic: 'inset 0 0 3px rgba(0, 0, 0, 0.11)', }, + keyframes: { + smoothAppear: { + '0%': { opacity: '0', transform: 'translateY(-5%)' }, + '100%': { opacity: '1', transform: 'translateY(0)' }, + }, + }, + animation: { + smoothAppear: 'smoothAppear 0.5s ease-out', // 0.5s는 원하는 지속 시간 + smoothDisappear: 'smoothAppear 0.5s reverse ease-out', // 0.5s는 원하는 지속 시간 + }, }, // 필요한 커스터마이징을 여기서 설정 가능 }, plugins: [], From 79ee60ede69947bca3df7822a1bb1bdb158b907f Mon Sep 17 00:00:00 2001 From: effozen Date: Wed, 13 Nov 2024 09:42:59 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[FE][Feat]=20:=20frontend=20=EB=B8=8C?= =?UTF-8?q?=EB=A0=8C=EC=B9=98=EC=99=80=20=EB=A8=B8=EC=A7=80=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - App.tsx 삭제 --- frontend/src/App.tsx | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 frontend/src/App.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx deleted file mode 100644 index 1682e151..00000000 --- a/frontend/src/App.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { Map } from '@/component/maps/Map.tsx'; - -export const App = () => { - return ; -}; From 0b7f0804d084b3dff385488b92a7de7ce1dc310a Mon Sep 17 00:00:00 2001 From: effozen Date: Wed, 13 Nov 2024 17:35:43 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[FE][Feat]=20:=20Dropdown=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Todos: - unmount 이미지 구현 - 아이콘 색상 들어가는 요소 수정 필요 - 링크 붙이기 --- .../common/dropdown/DropdownItem.tsx | 8 +++-- .../common/dropdown/DropdownMenu.tsx | 32 ++++++++++++++++--- .../common/dropdown/DropdownTrigger.tsx | 1 + .../src/component/header/HeaderDropdown.tsx | 19 +++++------ frontend/src/main.tsx | 2 +- 5 files changed, 46 insertions(+), 16 deletions(-) diff --git a/frontend/src/component/common/dropdown/DropdownItem.tsx b/frontend/src/component/common/dropdown/DropdownItem.tsx index 7252a3b8..472e372c 100644 --- a/frontend/src/component/common/dropdown/DropdownItem.tsx +++ b/frontend/src/component/common/dropdown/DropdownItem.tsx @@ -8,8 +8,12 @@ interface IDropdownItemProps { export const DropdownItem = (props: IDropdownItemProps) => { // undefined는 react에서 랜더링 하지 않음 return ( -
  • -
  • diff --git a/frontend/src/component/common/dropdown/DropdownMenu.tsx b/frontend/src/component/common/dropdown/DropdownMenu.tsx index 9ffafd62..e80c67ac 100644 --- a/frontend/src/component/common/dropdown/DropdownMenu.tsx +++ b/frontend/src/component/common/dropdown/DropdownMenu.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useContext } from 'react'; +import { ReactNode, useContext, useRef, useEffect } from 'react'; import classNames from 'classnames'; import { ToggleContext } from '@/component/common/dropdown/Dropdown.tsx'; @@ -7,11 +7,37 @@ interface IDropdownMenuProps { } export const DropdownMenu = (props: IDropdownMenuProps) => { - const { isOpen } = useContext(ToggleContext); + const { isOpen, setIsOpen } = useContext(ToggleContext); + const ref = useRef(null); + + const handleOutSideClick = (event: MouseEvent) => { + const { target } = event; + + if (!(target instanceof HTMLElement)) { + return; + } + + if ( + ref.current && + target && + !ref.current.contains(target) && + target.dataset.component !== 'DropdownTrigger' + ) { + setIsOpen(false); + } + }; + + useEffect(() => { + document.addEventListener('click', handleOutSideClick); + return () => { + document.removeEventListener('click', handleOutSideClick); + }; + }, []); return ( isOpen && (
      { 'top-8', 'z-10', 'flex', - 'translate-x-0', 'flex-col', 'justify-center', 'gap-2.5', 'rounded-xl', 'p-2.5', 'shadow-2xl', - 'animate-smoothAppear', 'w-fit', )} > diff --git a/frontend/src/component/common/dropdown/DropdownTrigger.tsx b/frontend/src/component/common/dropdown/DropdownTrigger.tsx index 011d9612..75e2d2cc 100644 --- a/frontend/src/component/common/dropdown/DropdownTrigger.tsx +++ b/frontend/src/component/common/dropdown/DropdownTrigger.tsx @@ -24,6 +24,7 @@ export const DropdownTrigger = (props: IDropdownTriggerProps) => { 'w-fit', 'h-fit', )} + data-component="DropdownTrigger" onClick={handleOnClick} > {props.children} diff --git a/frontend/src/component/header/HeaderDropdown.tsx b/frontend/src/component/header/HeaderDropdown.tsx index a71bf20e..d2dfd760 100644 --- a/frontend/src/component/header/HeaderDropdown.tsx +++ b/frontend/src/component/header/HeaderDropdown.tsx @@ -20,16 +20,17 @@ export const HeaderDropdown = () => { '사용자 3 보기', '사용자 4 보기', '사용자 5 보기', - ].map((e, i) => ( - - {/* 위치 자동으로 채워지게 하는 방법 찾기 */} -
      + ].map((e, i) => { + return ( + {e} - - {/* 아이콘 색 변경 로직 찾기 + 아이콘 높이 맞추기 */} -
      -
      - ))} + + {/* 아이콘 색 변경 로직 찾기, 현재는 아이콘색이 반영이 안됨 수정할 사 */} + + ); + })} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 3177f1a3..b1e5cba1 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -2,7 +2,7 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; -import { App } from './App'; +import { App } from './App2'; // 우선은 폰트 다 포함시켰는데, 나중에 사용할 것들만 따로 뺴자. import '@fontsource/pretendard/100.css'; import '@fontsource/pretendard/200.css';