diff --git a/src/system/components/Dropdown/Dropdown.tsx b/src/system/components/Dropdown/Dropdown.tsx new file mode 100644 index 00000000..43a47c7b --- /dev/null +++ b/src/system/components/Dropdown/Dropdown.tsx @@ -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, +}); diff --git a/src/system/components/Dropdown/anatomy.ts b/src/system/components/Dropdown/anatomy.ts new file mode 100644 index 00000000..81416eae --- /dev/null +++ b/src/system/components/Dropdown/anatomy.ts @@ -0,0 +1 @@ +export type DropdownAnatomy = 'trigger-arrow' | 'content' | 'separator' | 'checkbox-item'; diff --git a/src/system/components/Dropdown/compounds/CheckboxItem.tsx b/src/system/components/Dropdown/compounds/CheckboxItem.tsx new file mode 100644 index 00000000..a9e4a3f1 --- /dev/null +++ b/src/system/components/Dropdown/compounds/CheckboxItem.tsx @@ -0,0 +1,29 @@ +import { If } from '@/system/utils/If'; +import { Icon } from '../..'; +import { ComponentProps, MouseEvent, forwardRef } from 'react'; +import { cn } from '@/utils'; +import { useDropdownStyles } from '../styles/useDropdownStyles'; +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(function CheckboxItem( + { checked, disabled, children, ...restProps }, + ref, +) { + const { styles } = useDropdownContext(); + const { logics } = useDropdownContext(); + + return ( + + ); +}); diff --git a/src/system/components/Dropdown/compounds/Content.tsx b/src/system/components/Dropdown/compounds/Content.tsx new file mode 100644 index 00000000..981b5e99 --- /dev/null +++ b/src/system/components/Dropdown/compounds/Content.tsx @@ -0,0 +1,12 @@ +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; + +export function Content(props: ContentProps) { + const { styles } = useDropdownContext(); + + return ; +} diff --git a/src/system/components/Dropdown/compounds/Root.tsx b/src/system/components/Dropdown/compounds/Root.tsx new file mode 100644 index 00000000..3c2006f7 --- /dev/null +++ b/src/system/components/Dropdown/compounds/Root.tsx @@ -0,0 +1,24 @@ +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) { + const [open, setOpen] = useState(defaultOpen); + const dropdownStyles = useDropdownStyles(); + const dropdownLogics = useDropdownLogics({ onOpenChange: setOpen }); + + return ( + + + {children} + + + ); +} diff --git a/src/system/components/Dropdown/compounds/Separator.tsx b/src/system/components/Dropdown/compounds/Separator.tsx new file mode 100644 index 00000000..049fef6d --- /dev/null +++ b/src/system/components/Dropdown/compounds/Separator.tsx @@ -0,0 +1,7 @@ +import { useDropdownContext } from '../context'; + +export function Separator() { + const { styles } = useDropdownContext(); + + return
; +} diff --git a/src/system/components/Dropdown/compounds/Trigger.tsx b/src/system/components/Dropdown/compounds/Trigger.tsx new file mode 100644 index 00000000..e03fd891 --- /dev/null +++ b/src/system/components/Dropdown/compounds/Trigger.tsx @@ -0,0 +1,8 @@ +import * as RadixDropdownMenu from '@radix-ui/react-dropdown-menu'; +import { ComponentProps } from 'react'; + +export type TriggerProps = ComponentProps; + +export function Trigger({ children }: TriggerProps) { + return {children}; +} diff --git a/src/system/components/Dropdown/compounds/TriggerArrow.tsx b/src/system/components/Dropdown/compounds/TriggerArrow.tsx new file mode 100644 index 00000000..27b98125 --- /dev/null +++ b/src/system/components/Dropdown/compounds/TriggerArrow.tsx @@ -0,0 +1,20 @@ +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 ( + + + + ); +} diff --git a/src/system/components/Dropdown/context.tsx b/src/system/components/Dropdown/context.tsx new file mode 100644 index 00000000..cb59acfd --- /dev/null +++ b/src/system/components/Dropdown/context.tsx @@ -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({ + name: 'Dropdown', +}); diff --git a/src/system/components/Dropdown/logics/useDropdownLogics.ts b/src/system/components/Dropdown/logics/useDropdownLogics.ts new file mode 100644 index 00000000..fbdee987 --- /dev/null +++ b/src/system/components/Dropdown/logics/useDropdownLogics.ts @@ -0,0 +1,18 @@ +import { DropdownAnatomy } from '../anatomy'; + +export type DropdownLogics = Record; + +interface Props { + onOpenChange: (open: boolean) => void; +} + +export function useDropdownLogics({ onOpenChange }: Props): DropdownLogics { + return { + content: {}, + separator: {}, + 'trigger-arrow': {}, + 'checkbox-item': { + onClick: () => onOpenChange(false), + }, + }; +} diff --git a/src/system/components/Dropdown/styles/useDropdownStyles.ts b/src/system/components/Dropdown/styles/useDropdownStyles.ts new file mode 100644 index 00000000..b687e82c --- /dev/null +++ b/src/system/components/Dropdown/styles/useDropdownStyles.ts @@ -0,0 +1,24 @@ +import { DropdownAnatomy } from '../anatomy'; + +interface DropdownStyle { + className?: string; +} + +export type DropdownStyles = Record; + +export function useDropdownStyles(): Record { + 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', + }, + }; +} diff --git a/src/system/components/index.ts b/src/system/components/index.ts index 41b6007d..ed86e633 100644 --- a/src/system/components/index.ts +++ b/src/system/components/index.ts @@ -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';