Skip to content

Commit aa76bbe

Browse files
feat: 공통 Chip 컴포넌트 & 스토리북 구현
1 parent d1b00e6 commit aa76bbe

File tree

3 files changed

+150
-0
lines changed

3 files changed

+150
-0
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { Meta, StoryObj } from '@storybook/nextjs'
2+
import { Chip } from './Chip'
3+
import { Flex } from '../Layout'
4+
5+
const meta: Meta<typeof Chip> = {
6+
title: 'Components/Chip',
7+
component: Chip,
8+
tags: ['autodocs'],
9+
}
10+
11+
export default meta
12+
type Story = StoryObj<typeof Chip>
13+
14+
export const Default: Story = {
15+
render: () => (
16+
<Flex className='ui:gap-2'>
17+
<Chip type='SOLO_FRIENDLY' />
18+
<Chip type='GOOD_AMBIENCE' />
19+
<Chip type='VALUE_FOR_MONEY' />
20+
<Chip type='KIND_SERVICE' />
21+
</Flex>
22+
),
23+
}
24+
25+
export const ClickableChips: Story = {
26+
render: () => (
27+
<Flex className='ui:gap-2'>
28+
{(
29+
[
30+
'SOLO_FRIENDLY',
31+
'VALUE_FOR_MONEY',
32+
'GOOD_AMBIENCE',
33+
'KIND_SERVICE',
34+
] as const
35+
).map((type) => (
36+
<Chip
37+
key={type}
38+
type={type}
39+
onToggle={() => {
40+
console.log(`${type} 클릭됨!`)
41+
}}
42+
/>
43+
))}
44+
</Flex>
45+
),
46+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
'use client'
2+
import type { ElementType, JSX, PropsWithChildren } from 'react'
3+
import type { PolymorphicComponentProps } from '../../polymorphics'
4+
import { useState } from 'react'
5+
import { cn } from '../../utils/cn'
6+
import { Icon } from '../Icon'
7+
import { Text } from '../Text'
8+
9+
const CHIP_TAGS = {
10+
SOLO_FRIENDLY: {
11+
label: '혼밥하기 좋은',
12+
icon: 'fingerUp',
13+
},
14+
VALUE_FOR_MONEY: {
15+
label: '가성비 좋은',
16+
icon: 'calculator',
17+
},
18+
GOOD_AMBIENCE: {
19+
label: '분위기 좋은',
20+
icon: 'blingBling',
21+
},
22+
KIND_SERVICE: {
23+
label: '친절해요',
24+
icon: 'waiter',
25+
},
26+
} as const
27+
28+
type ChipTagKey = keyof typeof CHIP_TAGS
29+
30+
export type ChipProps<C extends ElementType> = PolymorphicComponentProps<
31+
C,
32+
{
33+
type: ChipTagKey
34+
onToggle?: () => void
35+
}
36+
>
37+
38+
export type ChipType = <C extends ElementType = 'button'>(
39+
props: PropsWithChildren<ChipProps<C>>,
40+
) => JSX.Element
41+
42+
/**
43+
* Chip 컴포넌트
44+
*
45+
* - 아이콘과 라벨을 가진 토글 가능한 UI 요소입니다.
46+
* - 클릭 시 내부 상태 `isActive`를 토글하며, `onToggle` 콜백을 실행합니다.
47+
* - 다양한 HTML 요소(`as` prop)를 지정하여 렌더링할 수 있습니다.
48+
*
49+
* @template C 렌더링할 HTML 태그 타입 (기본값: 'button')
50+
*
51+
* @param as 렌더링할 HTML 태그 또는 컴포넌트
52+
* @param className 추가 CSS 클래스
53+
* @param type 표시할 Chip 타입
54+
* @param onToggle 클릭 시 실행할 콜백 함수
55+
* @param restProps 나머지 Props
56+
*
57+
* @returns 렌더링된 Chip 요소
58+
*
59+
* @example
60+
* <Chip type="SOLO_FRIENDLY" onToggle={() => console.log('클릭됨')} />
61+
*/
62+
export const Chip: ChipType = ({
63+
as,
64+
className,
65+
type,
66+
onToggle,
67+
...restProps
68+
}) => {
69+
const Component = as || 'button'
70+
const { icon, label } = CHIP_TAGS[type]
71+
const [isActive, setIsActive] = useState(false)
72+
73+
const onClick = () => {
74+
if (onToggle) {
75+
onToggle()
76+
setIsActive((prev) => !prev)
77+
}
78+
}
79+
80+
return (
81+
<Component
82+
className={cn(
83+
'ui:w-fit',
84+
'ui:bg-gray-50',
85+
'ui:rounded-full',
86+
'ui:flex',
87+
'ui:gap-1',
88+
'ui:px-2.5 ui:py-1',
89+
'ui:items-center',
90+
'ui:border-2 ui:border-gray-50',
91+
{ 'ui:border-blue': isActive },
92+
className,
93+
)}
94+
onClick={onClick}
95+
{...restProps}
96+
>
97+
<Icon type={icon} size={14} />
98+
<Text as={'span'} variant={'caption1'} className={'ui:text-gray-300'}>
99+
{label}
100+
</Text>
101+
</Component>
102+
)
103+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { Chip } from './Chip'

0 commit comments

Comments
 (0)