Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE][Fix][Docs] #112 : Dropdown 순환 참조 버그 수정 및 스토리북 추가 #181

Merged
merged 6 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
## 🗝 링크 안내

<p>
<a href="https://www.notion.so/127b1b2b649180e88f70d6a4648924a0?pvs=4">팀 노션</a> |
<a href="https://ddara-docs.vercel.app/">팀 위키</a> |
<a href="https://www.notion.so/127b1b2b649180e88f70d6a4648924a0?pvs=4">팀 노션</a> |
<a href="https://www.figma.com/design/r9nl4Jcz9VXIMbrpf50wY6/PickMeUp?node-id=90-1897">기획서</a> |
<a href="https://www.figma.com/design/r9nl4Jcz9VXIMbrpf50wY6/PickMeUp?node-id=87-929">디자인</a>
<!-- <br />
Expand Down
45 changes: 27 additions & 18 deletions frontend/src/component/common/dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,42 @@
import { createContext, ReactNode, useMemo, useState } from 'react';
import { ReactNode } 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';
import { ToggleProvider } from '@/component/common/dropdown/DropdownContext.tsx';

interface IDropdownProps {
/** 드롭다운 컴포넌트 내부에 들어갈 컨텐츠 */
children: ReactNode;
}

export interface IToggleContext {
isOpen: boolean;
toggle: () => void;
}

export const ToggleContext = createContext<IToggleContext>({
isOpen: false,
toggle: () => {},
});
/**
* 드롭다운 컴포넌트입니다.
*
* @param {ReactNode} children - 드롭다운 컴포넌트 내부에 들어갈 컨텐츠
* @return {JSX.Element} 드롭다운 컴포넌트
*
* @remarks
* - 드롭다운 컴포넌트는 드롭다운 메뉴를 제공합니다.
*
* @example
* ```tsx
* <Dropdown>
* <Dropdown.Trigger>
* <MdMenu className="h-6 w-6" />
* <span>메뉴</span>
* </Dropdown.Trigger>
* <Dropdown.Menu>
* <Dropdown.Item>메뉴 항목 1</Dropdown.Item>
* <Dropdown.Item>메뉴 항목 2</Dropdown.Item>
* </Dropdown.Menu>
* </Dropdown>
* ```
*/

// Todo : 드랍다운 외부에서 클릭시 창 닫히게 설정
export const Dropdown = (props: IDropdownProps) => {
const [isOpen, setIsOpen] = useState(false);

const toggle = () => setIsOpen(prevIsOpen => !prevIsOpen);

const toggleContextValue = useMemo(() => ({ isOpen, toggle }), [isOpen]);

return (
<aside className="relative flex w-fit flex-col">
<ToggleContext.Provider value={toggleContextValue}>{props.children}</ToggleContext.Provider>
<ToggleProvider>{props.children}</ToggleProvider>
</aside>
);
};
Expand Down
25 changes: 25 additions & 0 deletions frontend/src/component/common/dropdown/DropdownContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createContext, ReactNode, useMemo, useState } from 'react';

export interface IToggleContext {
isOpen: boolean;
toggle: () => void;
}

interface IToggleProviderProps {
children: ReactNode;
}

export const ToggleContext = createContext<IToggleContext>({
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 (
<ToggleContext.Provider value={toggleContextValue}>{props.children}</ToggleContext.Provider>
);
};
24 changes: 22 additions & 2 deletions frontend/src/component/common/dropdown/DropdownItem.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
import { ReactNode } from 'react';

interface IDropdownItemProps {
/** 드롭다운 아이템 내용 */
children: ReactNode;
/** 버튼 클릭 시 실행할 함수 */
onClick?: React.MouseEventHandler<HTMLButtonElement>;
happyhyep marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* 드롭다운 메뉴의 아이템 컴포넌트입니다.
*
* @param {ReactNode} children - 드롭다운 아이템 내용
* @param {React.MouseEventHandler<HTMLButtonElement>} onClick - 버튼 클릭 시 실행할 함수
* @return {JSX.Element} 드롭다운 아이템 컴포넌트
*
* @remarks
* - 드롭다운 아이템 컴포넌트는 드롭다운 메뉴 내부에서 사용되어야 합니다.
* - 드롭다운 아이템 컴포넌트는 드롭다운 메뉴의 아이템 역할을 수행합니다.
* - 드롭다운 아이템 컴포넌트는 버튼 역할을 수행합니다.
* - 드롭다운 아이템 컴포넌트는 클릭 시 onClick 함수를 실행합니다.
*
* @example
* ```tsx
* <Dropdown.Item onClick={handleOnClick}>아이템</Dropdown.Item>
* ```
*/

export const DropdownItem = (props: IDropdownItemProps) => {
// undefined는 react에서 랜더링 하지 않음
return (
<li className="px-3 py-1.5 text-base">
<li className="list-none px-3 py-1.5 text-base">
<button
type="button"
className="flex w-full items-center justify-between whitespace-nowrap bg-transparent"
Expand Down
23 changes: 22 additions & 1 deletion frontend/src/component/common/dropdown/DropdownMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
import { ReactNode, useContext, useRef, useEffect } from 'react';
import classNames from 'classnames';
import { ToggleContext } from '@/component/common/dropdown/Dropdown.tsx';
import { ToggleContext } from '@/component/common/dropdown/DropdownContext';

interface IDropdownMenuProps {
/** 드롭다운 메뉴가 열려있는지 여부 */
children: ReactNode | ReactNode[];
}

/**
* 드롭다운 메뉴 컴포넌트입니다.
*
* @param {ReactNode} children - 드롭다운 메뉴에 들어갈 컨텐츠
* @return {JSX.Element} 드롭다운 메뉴 컴포넌트
*
* @remarks
* - 드롭다운 메뉴 컴포넌트는 드롭다운 컴포넌트 내부에서 사용되어야 합니다.
* - 드롭다운 메뉴 컴포넌트는 드롭다운 메뉴 역할을 수행합니다.
*
* @example
* ```tsx
* <Dropdown.Menu>
* <Dropdown.Item>메뉴 항목 1</Dropdown.Item>
* <Dropdown.Item>메뉴 항목 2</Dropdown.Item>
* </Dropdown.Menu>
* ```
*/

export const DropdownMenu = (props: IDropdownMenuProps) => {
const { isOpen, toggle } = useContext(ToggleContext);
const ref = useRef<HTMLUListElement | null>(null);
Expand Down Expand Up @@ -55,6 +75,7 @@ export const DropdownMenu = (props: IDropdownMenuProps) => {
'shadow-2xl',
'w-fit',
'bg-white',
'list-none',
)}
>
{props.children}
Expand Down
23 changes: 22 additions & 1 deletion frontend/src/component/common/dropdown/DropdownTrigger.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
import { ReactNode, useContext } from 'react';
import classNames from 'classnames';
import { ToggleContext } from '@/component/common/dropdown/Dropdown.tsx';
import { ToggleContext } from '@/component/common/dropdown/DropdownContext';

interface IDropdownTriggerProps {
/** 버튼 내부에 들어갈 컨텐츠 */
children: ReactNode;
}

/**
* 드롭다운에서 메뉴를 열고 닫는 트리거 컴포넌트입니다.
*
* @param {ReactNode} children - 버튼 내부에 들어갈 컨텐츠
* @return {JSX.Element} 드롭다운 트리거 컴포넌트
*
* @remarks
* - 드롭다운 트리거 컴포넌트를 사용하여 드롭다운 메뉴를 열고 닫을 수 있습니다.
* - 드롭다운 트리거 컴포넌트는 드롭다운 컴포넌트 내부에서 사용되어야 합니다.
* - 드롭다운 트리거 컴포넌트는 버튼 역할을 수행합니다.
*
* @example
* ```tsx
* <Dropdown.Trigger>
* <MdMenu className="h-6 w-6" />
* <span>메뉴</span>
* </Dropdown.Trigger>
* ```
*/

export const DropdownTrigger = (props: IDropdownTriggerProps) => {
const { toggle } = useContext(ToggleContext);

Expand Down
Loading
Loading