diff --git a/src/core/components/Avatar/index.tsx b/src/core/components/Avatar/index.tsx index d00beb5..0f2626d 100644 --- a/src/core/components/Avatar/index.tsx +++ b/src/core/components/Avatar/index.tsx @@ -23,7 +23,7 @@ import { AvatarProps } from '@/core/components/Avatar/types'; import { getFirstLetter } from '@/utilities/letter'; import Icon from '@/core/components/Icon'; import getAvatarColorTheme from '@/core/components/Avatar/utils/getAvatarColorTheme'; -import Popover from '@/core/components/Popover'; +import Popover from '@/core/components/Popover/PopoverBase'; const Avatar = ({ src, diff --git a/src/core/components/Avatar/types/index.ts b/src/core/components/Avatar/types/index.ts index 5b45bed..403aa54 100644 --- a/src/core/components/Avatar/types/index.ts +++ b/src/core/components/Avatar/types/index.ts @@ -3,7 +3,7 @@ import { HTMLAttributes } from 'react'; import { AVATAR_SIZE_VARIANTS } from '@/core/components/Avatar/constants'; import { RoundedType } from '@/core/components/Button/ButtonBase/types'; import { ColorThemeType } from '@/types'; -import { PopoverProps } from '@/core/components/Popover/types'; +import { PopoverProps } from '@/core/components/Popover/PopoverBase/types'; export type AvatarSizeVariants = (typeof AVATAR_SIZE_VARIANTS)[keyof typeof AVATAR_SIZE_VARIANTS]; diff --git a/src/core/components/Button/ButtonBase/constants/index.ts b/src/core/components/Button/ButtonBase/constants/index.ts index 8b441da..f2fa0b1 100644 --- a/src/core/components/Button/ButtonBase/constants/index.ts +++ b/src/core/components/Button/ButtonBase/constants/index.ts @@ -23,6 +23,8 @@ export const GAP = { GAP_12: 'gap-12', GAP_10: 'gap-10', GAP_8: 'gap-8', + GAP_6: 'gap-6', + GAP_4: 'gap-4', } as const; export const BUTTON_SIZE: Record = { @@ -48,4 +50,6 @@ export const BUTTON_GAP: Record = { [GAP['GAP_12']]: 'gap-x-3', [GAP['GAP_10']]: 'gap-x-2.5', [GAP['GAP_8']]: 'gap-x-2', + [GAP['GAP_6']]: 'gap-x-1.5', + [GAP['GAP_4']]: 'gap-x-1', }; diff --git a/src/core/components/Menu/Menu.stories.tsx b/src/core/components/Menu/Menu.stories.tsx new file mode 100644 index 0000000..c11ef7a --- /dev/null +++ b/src/core/components/Menu/Menu.stories.tsx @@ -0,0 +1,36 @@ +import { Meta } from '@storybook/react'; + +import IconButton from '@/core/components/Button/IconButton'; +import Icon from '@/core/components/Icon'; +import Menu from '@/core/components/Menu'; + +const meta = { + title: 'core/Menu', + component: Menu, +} satisfies Meta; + +export default meta; + +export const Default = () => { + return ( + } + size={'h-40'} + /> + } + items={[ + , + } + />, + ]} + popoverOptions={{ className: 'w-20' }} + /> + ); +}; diff --git a/src/core/components/Menu/MenuItem.tsx b/src/core/components/Menu/MenuItem.tsx new file mode 100644 index 0000000..7f3e487 --- /dev/null +++ b/src/core/components/Menu/MenuItem.tsx @@ -0,0 +1,60 @@ +import { ComponentPropsWithoutRef, ElementType, MouseEvent } from 'react'; +import clsx from 'clsx'; + +import { LIGHT_COLOR_THEME } from '@/constants/theme'; +import { MENU_ITEM_THEME } from './constants'; +import { BUTTON_GAP, GAP } from '@/core/components/Button/ButtonBase/constants'; +import { MenuItemProps } from './types'; + +const MenuItem = ({ + element: Element, + content, + leftIcon, + rightIcon, + className, + disabled, + onClick, + gap = GAP['GAP_6'], + colorTheme = LIGHT_COLOR_THEME['SECONDARY'], + ...props +}: MenuItemProps & ComponentPropsWithoutRef) => { + const Component: ElementType = Element || 'button'; + const defaultProps = + Component === 'button' ? { disabled, type: 'button' } : {}; + + const handleClick = (e: MouseEvent) => { + e.preventDefault(); + + if (!disabled) { + onClick?.(e); + } + }; + + return ( +
  • + + {leftIcon && leftIcon} + {content} + {rightIcon && rightIcon} + +
  • + ); +}; + +export default MenuItem; diff --git a/src/core/components/Menu/constants/index.ts b/src/core/components/Menu/constants/index.ts new file mode 100644 index 0000000..fe0757d --- /dev/null +++ b/src/core/components/Menu/constants/index.ts @@ -0,0 +1,7 @@ +import { LIGHT_COLOR_THEME } from '@/constants/theme'; +import { MenuItemColorTheme } from '@/core/components/Menu/types'; + +export const MENU_ITEM_THEME: Record = { + [LIGHT_COLOR_THEME['ERROR']]: 'text-rose-600 hover:bg-[#FFEDEF]', + [LIGHT_COLOR_THEME['SECONDARY']]: 'hover:bg-gray-00 hover:text-primary-03', +} as const; diff --git a/src/core/components/Menu/index.tsx b/src/core/components/Menu/index.tsx new file mode 100644 index 0000000..21aa0e0 --- /dev/null +++ b/src/core/components/Menu/index.tsx @@ -0,0 +1,27 @@ +import clsx from 'clsx'; + +import { MenuProps, MenuReturnType } from '@/core/components/Menu/types'; +import Popover from '@/core/components/Popover/PopoverBase'; +import MenuPopoverItem from '@/core/components/Menu/MenuItem'; + +const Menu = ({ items, popoverOptions = {}, ...props }: MenuProps) => { + const { className, ...rest } = popoverOptions; + + return ( + {items}} + popoverOptions={{ + backgroundColor: 'white', + hasShadow: true, + className: clsx('p-1.5', className), + ...rest, + }} + {...props} + /> + ); +}; + +export default Menu as unknown as MenuReturnType; + +Menu.displayName = 'Menu'; +Menu.Item = MenuPopoverItem; diff --git a/src/core/components/Menu/types/index.ts b/src/core/components/Menu/types/index.ts new file mode 100644 index 0000000..79c3aa2 --- /dev/null +++ b/src/core/components/Menu/types/index.ts @@ -0,0 +1,31 @@ +import { ElementType, ReactElement, ReactNode } from 'react'; + +import { ButtonProps } from '@/core/components/Button/Button/types'; +import { LightColorThemeType } from '@/types'; +import { PopoverProps } from '@/core/components/Popover/PopoverBase/types'; +import MenuPopoverItem from '@/core/components/Menu/MenuItem'; + +export interface MenuProps extends Omit { + items: ReactNode[]; +} + +export type MenuItemColorTheme = Extract< + LightColorThemeType, + 'error' | 'secondary' +>; + +export interface MenuItemProps + extends Pick< + ButtonProps, + 'content' | 'leftIcon' | 'rightIcon' | 'disabled' | 'gap' + > { + element?: T; + colorTheme?: MenuItemColorTheme; +} + +type Menu = (props: MenuProps) => ReactElement; + +export type MenuReturnType = Menu & { + displayName: string; + Item: typeof MenuPopoverItem; +}; diff --git a/src/core/components/Popover/InfoPopover/InfoPopover.stories.tsx b/src/core/components/Popover/InfoPopover/InfoPopover.stories.tsx new file mode 100644 index 0000000..1545895 --- /dev/null +++ b/src/core/components/Popover/InfoPopover/InfoPopover.stories.tsx @@ -0,0 +1,53 @@ +import { Meta } from '@storybook/react'; + +import IconButton from '@/core/components/Button/IconButton'; +import Icon from '@/core/components/Icon'; +import InfoPopover from '@/core/components/Popover/InfoPopover/index'; + +const meta = { + title: 'core/InfoPopover', + component: InfoPopover, + argTypes: { + heading: { + control: 'text', + description: 'Information Popover Heading', + }, + items: { + control: 'object', + description: 'Information Popover Items', + }, + }, +} satisfies Meta; + +export default meta; + +export const Default = () => { + const data = [ + { + title: '제목1', + description: '설명', + }, + { + title: '제목2', + description: '설명', + }, + ]; + + return ( + + } + size={'h-40'} + /> + } + items={data} + popoverOptions={{ className: 'w-32' }} + /> + ); +}; diff --git a/src/core/components/Popover/InfoPopover/index.tsx b/src/core/components/Popover/InfoPopover/index.tsx new file mode 100644 index 0000000..26d37e5 --- /dev/null +++ b/src/core/components/Popover/InfoPopover/index.tsx @@ -0,0 +1,67 @@ +import { InfoPopoverProps } from '@/core/components/Popover/InfoPopover/types'; +import Typography from '@/core/components/Typography'; +import Icon from '@/core/components/Icon'; +import Popover from '@/core/components/Popover/PopoverBase'; + +const InfoPopover = ({ + trigger, + heading, + items, + popoverOptions = {}, + ...props +}: InfoPopoverProps) => { + const ITEMS = () => { + return items.map(({ title, description }) => ( +
  • +
    + + + {title} + + } + className={'flex gap-1'} + /> +
    + +
  • + )); + }; + + return ( + + +
      {ITEMS()}
    + + } + popoverOptions={{ + backgroundColor: 'white', + hasShadow: true, + ...popoverOptions, + }} + {...props} + /> + ); +}; + +export default InfoPopover; diff --git a/src/core/components/Popover/InfoPopover/types/index.ts b/src/core/components/Popover/InfoPopover/types/index.ts new file mode 100644 index 0000000..b3cf195 --- /dev/null +++ b/src/core/components/Popover/InfoPopover/types/index.ts @@ -0,0 +1,6 @@ +import { PopoverProps } from '@/core/components/Popover/PopoverBase/types'; + +export interface InfoPopoverProps extends Omit { + heading: string; + items: { title: string; description: string }[]; +} diff --git a/src/core/components/Popover/Popover.stories.tsx b/src/core/components/Popover/PopoverBase/Popover.stories.tsx similarity index 96% rename from src/core/components/Popover/Popover.stories.tsx rename to src/core/components/Popover/PopoverBase/Popover.stories.tsx index 22b076b..de7b91e 100644 --- a/src/core/components/Popover/Popover.stories.tsx +++ b/src/core/components/Popover/PopoverBase/Popover.stories.tsx @@ -1,7 +1,7 @@ import { Meta } from '@storybook/react'; import { useRef } from 'react'; -import { PopoverProps } from '@/core/components/Popover/types'; +import { PopoverProps } from '@/core/components/Popover/PopoverBase/types'; import Popover from './index'; import Button from '@/core/components/Button/Button'; import Section from '@/core/components/Section'; diff --git a/src/core/components/Popover/hooks/effects/useRootScrollLockEffect.ts b/src/core/components/Popover/PopoverBase/hooks/effects/useRootScrollLockEffect.ts similarity index 88% rename from src/core/components/Popover/hooks/effects/useRootScrollLockEffect.ts rename to src/core/components/Popover/PopoverBase/hooks/effects/useRootScrollLockEffect.ts index e0684c7..d4db9e4 100644 --- a/src/core/components/Popover/hooks/effects/useRootScrollLockEffect.ts +++ b/src/core/components/Popover/PopoverBase/hooks/effects/useRootScrollLockEffect.ts @@ -1,6 +1,6 @@ import { useEffect } from 'react'; -import { PopoverProps } from '@/core/components/Popover/types'; +import { PopoverProps } from '@/core/components/Popover/PopoverBase/types'; export const useRootScrollLockEffect = ({ isOpen, diff --git a/src/core/components/Popover/hooks/effects/useUpdatePopoverPositionEffect.ts b/src/core/components/Popover/PopoverBase/hooks/effects/useUpdatePopoverPositionEffect.ts similarity index 86% rename from src/core/components/Popover/hooks/effects/useUpdatePopoverPositionEffect.ts rename to src/core/components/Popover/PopoverBase/hooks/effects/useUpdatePopoverPositionEffect.ts index 3f27ad2..fa53780 100644 --- a/src/core/components/Popover/hooks/effects/useUpdatePopoverPositionEffect.ts +++ b/src/core/components/Popover/PopoverBase/hooks/effects/useUpdatePopoverPositionEffect.ts @@ -1,7 +1,7 @@ import { useEffect } from 'react'; -import { getPopoverPosition } from '@/core/components/Popover/utils/getPopoverPosition'; -import { UseUpdatePopoverPositionEffectProps } from '@/core/components/Popover/types/PopoverPosition'; +import { getPopoverPosition } from '@/core/components/Popover/PopoverBase/utils/getPopoverPosition'; +import { UseUpdatePopoverPositionEffectProps } from '@/core/components/Popover/PopoverBase/types/PopoverPosition'; export const useUpdatePopoverPositionEffect = ({ isOpen, diff --git a/src/core/components/Popover/hooks/usePopoverPosition.ts b/src/core/components/Popover/PopoverBase/hooks/usePopoverPosition.ts similarity index 83% rename from src/core/components/Popover/hooks/usePopoverPosition.ts rename to src/core/components/Popover/PopoverBase/hooks/usePopoverPosition.ts index e679d9a..49b4c5c 100644 --- a/src/core/components/Popover/hooks/usePopoverPosition.ts +++ b/src/core/components/Popover/PopoverBase/hooks/usePopoverPosition.ts @@ -1,7 +1,7 @@ import { CSSProperties, useRef, useState } from 'react'; -import { useUpdatePopoverPositionEffect } from '@/core/components/Popover/hooks/effects/useUpdatePopoverPositionEffect'; -import { UseUpdatePopoverPositionProps } from '@/core/components/Popover/types/PopoverPosition'; +import { useUpdatePopoverPositionEffect } from '@/core/components/Popover/PopoverBase/hooks/effects/useUpdatePopoverPositionEffect'; +import { UseUpdatePopoverPositionProps } from '@/core/components/Popover/PopoverBase/types/PopoverPosition'; export const usePopoverPosition = ({ gap, diff --git a/src/core/components/Popover/index.tsx b/src/core/components/Popover/PopoverBase/index.tsx similarity index 87% rename from src/core/components/Popover/index.tsx rename to src/core/components/Popover/PopoverBase/index.tsx index 3f5722f..1b27de0 100644 --- a/src/core/components/Popover/index.tsx +++ b/src/core/components/Popover/PopoverBase/index.tsx @@ -1,8 +1,8 @@ import { cloneElement, MouseEvent, useState } from 'react'; -import { PopoverProps } from '@/core/components/Popover/types'; -import { useRootScrollLockEffect } from '@/core/components/Popover/hooks/effects/useRootScrollLockEffect'; -import { usePopoverPosition } from '@/core/components/Popover/hooks/usePopoverPosition'; +import { PopoverProps } from '@/core/components/Popover/PopoverBase/types'; +import { useRootScrollLockEffect } from '@/core/components/Popover/PopoverBase/hooks/effects/useRootScrollLockEffect'; +import { usePopoverPosition } from '@/core/components/Popover/PopoverBase/hooks/usePopoverPosition'; import useClickOutside from '@/hooks/useClickOutSide'; import Section from '@/core/components/Section'; diff --git a/src/core/components/Popover/types/PopoverPosition.ts b/src/core/components/Popover/PopoverBase/types/PopoverPosition.ts similarity index 88% rename from src/core/components/Popover/types/PopoverPosition.ts rename to src/core/components/Popover/PopoverBase/types/PopoverPosition.ts index 75ee43d..2ba59cc 100644 --- a/src/core/components/Popover/types/PopoverPosition.ts +++ b/src/core/components/Popover/PopoverBase/types/PopoverPosition.ts @@ -1,6 +1,6 @@ import { CSSProperties, Dispatch, RefObject, SetStateAction } from 'react'; -import { PopoverProps } from '@/core/components/Popover/types'; +import { PopoverProps } from '@/core/components/Popover/PopoverBase/types'; export interface UseUpdatePopoverPositionProps extends Pick { diff --git a/src/core/components/Popover/types/index.ts b/src/core/components/Popover/PopoverBase/types/index.ts similarity index 79% rename from src/core/components/Popover/types/index.ts rename to src/core/components/Popover/PopoverBase/types/index.ts index 4afe9c4..55c5913 100644 --- a/src/core/components/Popover/types/index.ts +++ b/src/core/components/Popover/PopoverBase/types/index.ts @@ -1,7 +1,7 @@ import { HTMLAttributes, ReactElement, RefObject } from 'react'; import { SectionProps } from '@/core/components/Section/types'; -import { UseUpdatePopoverPositionProps } from '@/core/components/Popover/types/PopoverPosition'; +import { UseUpdatePopoverPositionProps } from '@/core/components/Popover/PopoverBase/types/PopoverPosition'; import { ModalBaseProps } from '@/core/components/Modal/ModalBase/types'; export interface PopoverProps @@ -12,7 +12,13 @@ export interface PopoverProps useHover?: boolean; popoverOptions?: Pick< SectionProps<'div'>, - 'color' | 'colorTheme' | 'backgroundColor' | 'borderColor' | 'className' + | 'color' + | 'colorTheme' + | 'backgroundColor' + | 'borderColor' + | 'className' + | 'hasBorder' + | 'hasShadow' > & Pick; rootClassName?: HTMLAttributes['className']; diff --git a/src/core/components/Popover/utils/getPopoverPosition.ts b/src/core/components/Popover/PopoverBase/utils/getPopoverPosition.ts similarity index 96% rename from src/core/components/Popover/utils/getPopoverPosition.ts rename to src/core/components/Popover/PopoverBase/utils/getPopoverPosition.ts index 7f936fd..4871147 100644 --- a/src/core/components/Popover/utils/getPopoverPosition.ts +++ b/src/core/components/Popover/PopoverBase/utils/getPopoverPosition.ts @@ -1,6 +1,6 @@ import { CSSProperties } from 'react'; -import { GetPopoverPositionProps } from '@/core/components/Popover/types/PopoverPosition'; +import { GetPopoverPositionProps } from '@/core/components/Popover/PopoverBase/types/PopoverPosition'; export const getPopoverPosition = ({ trigger, diff --git a/src/index.ts b/src/index.ts index 52f8cdb..a95835d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -57,10 +57,12 @@ export { default as Chips } from '@/core/components/Chips'; export { default as Icon } from '@/core/components/Icon'; export { default as Avatar } from '@/core/components/Avatar'; export { default as AvatarGroup } from '@/core/components/AvatarGroup'; -export { default as Popover } from '@/core/components/Popover'; +export { default as Popover } from '@/core/components/Popover/PopoverBase'; +export { default as Menu } from '@/core/components/Menu'; +export { default as InfoPopover } from '@/core/components/Popover/InfoPopover'; export { useInput } from '@/core/components/Input/hooks/useInput'; -export * from '@/core/components/Popover/utils/getPopoverPosition'; +export * from '@/core/components/Popover/PopoverBase/utils/getPopoverPosition'; export * from '@/utilities/day'; export * from '@/utilities/ref'; export * from '@/utilities/letter'; diff --git a/src/types/index.ts b/src/types/index.ts index 17bd74e..4194846 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,4 +1,8 @@ -import { COLOR_THEME, COLOR_THEME_VARIANT } from '@/constants/theme'; +import { + COLOR_THEME, + COLOR_THEME_VARIANT, + LIGHT_COLOR_THEME, +} from '@/constants/theme'; export type ThemeColors = | 'white' @@ -50,5 +54,8 @@ export type ThemeTypography = export type ColorThemeType = (typeof COLOR_THEME)[keyof typeof COLOR_THEME]; +export type LightColorThemeType = + (typeof LIGHT_COLOR_THEME)[keyof typeof LIGHT_COLOR_THEME]; + export type ColorThemeVariant = (typeof COLOR_THEME_VARIANT)[keyof typeof COLOR_THEME_VARIANT];