|
| 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 | +} |
0 commit comments