diff --git a/public/icons/heart.svg b/public/icons/heart.svg index c7811e95..6d882f8b 100644 --- a/public/icons/heart.svg +++ b/public/icons/heart.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/stories/Base/Badge.stories.tsx b/src/stories/Base/Badge.stories.tsx new file mode 100644 index 00000000..a7859dda --- /dev/null +++ b/src/stories/Base/Badge.stories.tsx @@ -0,0 +1,56 @@ +import Badge from '@/ui/Base/Badge'; +import { IconHeart } from '@public/icons'; +import { Meta, StoryObj } from '@storybook/react'; + +const meta: Meta = { + title: 'Base/Badge', + component: Badge, + tags: ['autodocs'], +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + size: 'small', + colorScheme: 'main-light', + fontWeight: 'bold', + }, + render: args => 프론트엔드 개발자, +}; + +export const BookshelfLike: Story = { + args: { + size: 'small', + colorScheme: 'red', + fontWeight: 'bold', + }, + render: args => ( + +
+ +
+ 99 + + ), +}; + +export const GroupProgress: Story = { + args: { + size: 'large', + colorScheme: 'main', + fontWeight: 'bold', + }, + render: args => 진행중, +}; + +export const GroupDisclosure: Story = { + args: { + size: 'medium', + colorScheme: 'grey', + fontWeight: 'normal', + }, + render: args => 공개, +}; diff --git a/src/ui/Base/Badge.tsx b/src/ui/Base/Badge.tsx new file mode 100644 index 00000000..3330bc4f --- /dev/null +++ b/src/ui/Base/Badge.tsx @@ -0,0 +1,93 @@ +import { PropsWithChildren, useMemo } from 'react'; + +type Size = 'small' | 'medium' | 'large'; +type ColorScheme = 'main' | 'main-light' | 'grey' | 'red'; +type FontWeight = 'thin' | 'normal' | 'bold'; + +type BadgeProps = PropsWithChildren<{ + size?: Size; + colorScheme?: ColorScheme; + fontWeight?: FontWeight; + isFilled?: boolean; +}>; + +const getSizeClasses = (size: Size) => { + switch (size) { + case 'small': { + return 'h-[1.8rem] text-2xs'; + } + case 'medium': { + return 'h-[1.9rem] text-xs'; + } + case 'large': { + return 'h-[2.1rem] text-xs'; + } + } +}; + +const getSchemeClasses = (colorScheme: ColorScheme, isFilled: boolean) => { + switch (colorScheme) { + case 'main': { + return isFilled + ? 'border-main-900 bg-main-900 text-white' + : 'border-main-900 text-main-900'; + } + case 'main-light': { + return isFilled + ? 'border-main-600 bg-main-600 text-white' + : 'border-main-600 text-main-600'; + } + case 'grey': { + return isFilled + ? 'border-black-100 bg-black-100 text-black-500' + : 'border-black-500 text-black-500'; + } + case 'red': { + return isFilled + ? 'border-warning-800 bg-warning-800 text-white' + : 'border-warning-800 text-warning-800'; + } + } +}; + +const getFontWeightClasses = (fontWeight: FontWeight) => { + switch (fontWeight) { + case 'thin': { + return 'font-thin'; + } + case 'normal': { + return 'font-normal'; + } + case 'bold': { + return 'font-bold'; + } + } +}; + +const Badge = ({ + size = 'medium', + colorScheme = 'main', + fontWeight = 'normal', + isFilled = true, + children, + ...props +}: BadgeProps) => { + const computedClasses = useMemo(() => { + const sizeClass = getSizeClasses(size); + const schemeClass = getSchemeClasses(colorScheme, isFilled); + const fontWeightClass = getFontWeightClasses(fontWeight); + + return [sizeClass, schemeClass, fontWeightClass].join(' '); + }, [size, colorScheme, isFilled, fontWeight]); + + return ( +
+ {children} +
+ ); +}; + +export default Badge;