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

feat(system): Dropdown 컴포넌트 구현 #26

Merged
merged 2 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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: 2 additions & 0 deletions src/app/(sidebar)/(my-info)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use client';

import { Icon } from '@/system/components';
import { InfoCardList } from './components/InfoCardList';

Expand Down
14 changes: 14 additions & 0 deletions src/system/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { CheckboxItem } from './compounds/CheckboxItem';
import { Content } from './compounds/Content';
import { Root } from './compounds/Root';
import { Separator } from './compounds/Separator';
import { Trigger } from './compounds/Trigger';
import { TriggerArrow } from './compounds/TriggerArrow';

export const Dropdown = Object.assign(Root, {
Trigger,
TriggerArrow,
Content,
Separator,
CheckboxItem,
});
qkrdmstlr3 marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions src/system/components/Dropdown/anatomy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type DropdownAnatomy = 'trigger-arrow' | 'content' | 'separator' | 'checkbox-item';
28 changes: 28 additions & 0 deletions src/system/components/Dropdown/compounds/CheckboxItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use client';

import { If } from '@/system/utils/If';
import { Icon } from '../..';
import { ComponentProps, forwardRef } from 'react';
import { mergeProps } from '@/utils/mergeProps';
import { color } from '@/system/token/color';
import { useDropdownContext } from '../context';

interface Props extends ComponentProps<'button'> {
checked?: boolean;
}

export const CheckboxItem = forwardRef<HTMLButtonElement, Props>(function CheckboxItem(
{ checked, disabled, children, ...restProps },
ref,
) {
const { styles, logics } = useDropdownContext();

return (
<button ref={ref} disabled={disabled} {...mergeProps(styles['checkbox-item'], logics['checkbox-item'], restProps)}>
{children}
<If condition={checked}>
<Icon name="check" color={color.neutral30} size={20} />
</If>
</button>
);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropdown에 Checkbox가 필요한 이유에 대해 고민한 시간이 존재했습니다.
이후 영상을 보고 어떤 컴포넌트인지 파악했어요
CheckedItem이라는 이름은 어떨까요??

코멘트 수준으로 남겨드립니다 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋습니다!

14 changes: 14 additions & 0 deletions src/system/components/Dropdown/compounds/Content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use client';

import * as RadixDropdownMenu from '@radix-ui/react-dropdown-menu';
import { ComponentProps } from 'react';
import { mergeProps } from '@/utils/mergeProps';
import { useDropdownContext } from '../context';

type ContentProps = ComponentProps<typeof RadixDropdownMenu.Content>;

export function Content(props: ContentProps) {
const { styles } = useDropdownContext();

return <RadixDropdownMenu.Content {...mergeProps(props, styles.content)} />;
}
26 changes: 26 additions & 0 deletions src/system/components/Dropdown/compounds/Root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use client';

import * as RadixDropdownMenu from '@radix-ui/react-dropdown-menu';
import { PropsWithChildren, useState } from 'react';
import { DropdownProvider } from '../context';
import { useDropdownStyles } from '../styles/useDropdownStyles';
import { useDropdownLogics } from '../logics/useDropdownLogics';

interface RootProps {
defaultOpen?: boolean;
}

// 추후 Controlled방식도 지원
export function Root({ defaultOpen = false, children }: PropsWithChildren<RootProps>) {
const [open, setOpen] = useState(defaultOpen);
const dropdownStyles = useDropdownStyles();
const dropdownLogics = useDropdownLogics({ onOpenChange: setOpen });

return (
<RadixDropdownMenu.Root open={open} onOpenChange={setOpen}>
<DropdownProvider open={open} styles={dropdownStyles} logics={dropdownLogics} onOpenChange={setOpen}>
{children}
</DropdownProvider>
</RadixDropdownMenu.Root>
);
}
9 changes: 9 additions & 0 deletions src/system/components/Dropdown/compounds/Separator.tsx
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 나눠진 리스트에 divider 쓸 때 필요한거죠??
image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네!

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use client';

import { useDropdownContext } from '../context';

export function Separator() {
const { styles } = useDropdownContext();

return <div {...styles.separator} />;
}
10 changes: 10 additions & 0 deletions src/system/components/Dropdown/compounds/Trigger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use client';

import * as RadixDropdownMenu from '@radix-ui/react-dropdown-menu';
import { ComponentProps } from 'react';

export type TriggerProps = ComponentProps<typeof RadixDropdownMenu.Trigger>;

export function Trigger({ children }: TriggerProps) {
return <RadixDropdownMenu.Trigger>{children}</RadixDropdownMenu.Trigger>;
}
22 changes: 22 additions & 0 deletions src/system/components/Dropdown/compounds/TriggerArrow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client';

import { motion } from 'framer-motion';
import { Icon } from '../..';
import { useDropdownContext } from '../context';
import { color } from '@/system/token/color';

export function TriggerArrow() {
const { open } = useDropdownContext();

return (
<motion.div
variants={{
closed: { rotate: '0deg' },
opened: { rotate: '-180deg' },
}}
transition={{ duration: 0.1 }}
animate={open ? 'opened' : 'closed'}>
<Icon name="downChevron" color={color.neutral30} />
</motion.div>
);
}
qkrdmstlr3 marked this conversation as resolved.
Show resolved Hide resolved
14 changes: 14 additions & 0 deletions src/system/components/Dropdown/context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { generateContext } from '@/lib';
import { DropdownStyles } from './styles/useDropdownStyles';
import { DropdownLogics } from './logics/useDropdownLogics';

interface DropdownContext {
open: boolean;
styles: DropdownStyles;
logics: DropdownLogics;
onOpenChange: (open: boolean) => void;
}

export const [DropdownProvider, useDropdownContext] = generateContext<DropdownContext>({
name: 'Dropdown',
});
18 changes: 18 additions & 0 deletions src/system/components/Dropdown/logics/useDropdownLogics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { DropdownAnatomy } from '../anatomy';

export type DropdownLogics = Record<DropdownAnatomy, any>;

interface Props {
onOpenChange: (open: boolean) => void;
}

export function useDropdownLogics({ onOpenChange }: Props): DropdownLogics {
return {
content: {},
separator: {},
'trigger-arrow': {},
'checkbox-item': {
onClick: () => onOpenChange(false),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chain을 사용해서 onClick을 체이닝하는 것이 좋다고 생각합니다 !
임의로 추가되는 onClick에 대해서도 대응하면 좋을 것 같아요 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컴포넌트 단계에서 mergeProps로 합쳐주고 있어요!

},
};
}
24 changes: 24 additions & 0 deletions src/system/components/Dropdown/styles/useDropdownStyles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { DropdownAnatomy } from '../anatomy';

interface DropdownStyle {
className?: string;
}

export type DropdownStyles = Record<DropdownAnatomy, DropdownStyle>;

export function useDropdownStyles(): Record<DropdownAnatomy, DropdownStyle> {
return {
content: {
className:
'flex flex-col py-[8px] shadow-[0px_2px_8px_0px_rgba(0,0,0,0.12),0px_0px_1px_0px_rgba(0,0,0,0.08)] min-w-[170px] bg-white rounded-[12px] border-[1px] hover:border-neutral-20',
},
separator: {
className: 'h-[8px] bg-nuetral-3 w-full',
},
'trigger-arrow': {},
'checkbox-item': {
className:
'flex justify-between items-center mx-[8px] my-[4px] px-[8px] py-[4px] text-label1 font-medium text-neutral-80 rounded-[6px] hover:bg-neutral-3 disabled:text-neutral-30 disabled:bg-white',
},
};
}
1 change: 1 addition & 0 deletions src/system/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { Button } from './Button/Button';
export type { ButtonProps } from './Button/Button';
export { Dropdown } from './Dropdown/Dropdown';
export { Icon } from './Icon/Icon';
export type { IconProps } from './Icon/Icon';
export { Text } from './Text/Text';
Expand Down
Loading