/** * @title Badge * @description 새 댓글 수나 변경량처럼 숫자로 된 상태 정보를 작게 보여줘요. */import * as React from 'react';import { cva } from 'class-variance-authority';import { cn } from '@/lib/utils';// Atlassian @atlaskit/badge — badge-new 스펙/** 레거시 appearance */export type ThemeAppearance = 'added' | 'default' | 'important' | 'primary' | 'primaryInverted' | 'removed';/** 새 appearance */export type NewAppearance = | 'success' | 'danger' | 'neutral' | 'information' | 'inverse' | 'warning' | 'discovery' | 'informationBold' | 'successBold' | 'dangerBold' | 'warningBold' | 'discoveryBold';// 레거시 이름 → 새 이름 (appearance-mapping)const appearanceMapping: Record<ThemeAppearance | NewAppearance, NewAppearance> = { added: 'success', removed: 'danger', default: 'neutral', primary: 'information', primaryInverted: 'inverse', important: 'danger', warning: 'warning', discovery: 'discovery', danger: 'danger', success: 'success', information: 'information', inverse: 'inverse', neutral: 'neutral', informationBold: 'informationBold', successBold: 'successBold', dangerBold: 'dangerBold', warningBold: 'warningBold', discoveryBold: 'discoveryBold',};const badgeVariants = cva( // root: radius xsmall, 좌우 4px, 최소 너비 24px, 글자는 body.small 'box-border inline-flex h-min min-w-ds-300 shrink-0 justify-center rounded-ds-xsmall px-ds-050 py-0 text-center align-middle ds-font-body-small', { variants: { appearance: { success: 'bg-ds-background-success-subtler text-ds-text-success-bolder', danger: 'bg-ds-background-danger-subtler text-ds-text-danger-bolder', warning: 'bg-ds-background-warning-subtler text-ds-text-warning-bolder', information: 'bg-ds-background-information-subtler text-ds-text-information-bolder', discovery: 'bg-ds-background-discovery-subtler text-ds-text-discovery-bolder', neutral: 'bg-ds-background-accent-gray-subtler text-ds-text', inverse: 'bg-ds-surface text-ds-text', informationBold: 'bg-ds-background-information-subtle text-ds-text-information-bolder', successBold: 'bg-ds-background-success-subtle text-ds-text-success-bolder', dangerBold: 'bg-ds-background-danger-subtle text-ds-text-danger-bolder', // 원본도 다크 테마에서만 warning.subtle 로 바꿔 대비를 확보해요 (useThemeObserver) warningBold: 'bg-ds-background-warning-bold text-ds-text-warning-bolder dark:bg-ds-background-warning-subtle', discoveryBold: 'bg-ds-background-discovery-subtle text-ds-text-discovery-bolder', }, }, defaultVariants: { appearance: 'neutral' }, },);/** * 숫자를 max 로 자르고 음수를 그대로 표시해요 (format-value-with-negative-support). * max 를 넘으면 `${max}+`, Infinity 는 ∞. */function formatBadgeValue(value: number, max: number): string { if (max === Infinity && value === Infinity) return '∞'; if (max < value) return `${max}+`; if (value === Infinity) return '∞'; return value.toString();}export interface BadgeProps extends Omit<React.ComponentProps<'span'>, 'children' | 'style'> { /** 시각 스타일. 레거시 이름(added, default, important, primary, primaryInverted, removed)도 받아요. */ appearance?: ThemeAppearance | NewAppearance; /** 표시할 값. 숫자를 넘기면 max 기준으로 자동 포맷돼요. */ children?: number | React.ReactNode; /** 최대 표시 값. 기본 99. false 면 값을 그대로 보여줘요. */ max?: number | false; /** backgroundColor, color 만 지원해요. */ style?: Pick<React.CSSProperties, 'backgroundColor' | 'color'>; testId?: string;}function Badge({ appearance = 'default', children = 0, max = 99, style, testId, className, ...props }: BadgeProps) { const resolved = appearanceMapping[appearance] ?? 'neutral'; const value = typeof children === 'number' && typeof max === 'number' ? formatBadgeValue(children, max) : children; return ( <span data-slot="badge" data-appearance={resolved} data-testid={testId} style={{ background: style?.backgroundColor, color: style?.color }} className={cn(badgeVariants({ appearance: resolved }), className)} {...props} > {value} </span> );}export { Badge, badgeVariants, formatBadgeValue };