Skip to content

Commit

Permalink
feat(system): Dropdown 컴포넌트 구현 (#26)
Browse files Browse the repository at this point in the history
* fix

* review
  • Loading branch information
qkrdmstlr3 authored Aug 21, 2024
1 parent 844a7b2 commit 79e6cd2
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 0 deletions.
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 { CheckedItem } from './compounds/CheckedItem';
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,
CheckedItem,
});
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/CheckedItem.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 CheckedItem = forwardRef<HTMLButtonElement, Props>(function CheckedItem(
{ 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>
);
});
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
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>
);
}
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),
},
};
}
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

0 comments on commit 79e6cd2

Please sign in to comment.