80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
import { forwardRef, type ButtonHTMLAttributes, type HTMLAttributes, type ReactNode } from 'react'
|
|
|
|
type ChipTone = 'neutral' | 'accent' | 'cool' | 'success' | 'warning' | 'error' | 'glow' | 'outline'
|
|
type ChipSize = 'xs' | 'sm' | 'md'
|
|
|
|
const toneClass: Record<ChipTone, string> = {
|
|
neutral: 'bg-white/8 text-text-1 border-white/8 hover:bg-white/12',
|
|
accent: 'bg-accent/15 text-accent border-accent/25 hover:bg-accent/20',
|
|
cool: 'bg-cool/12 text-cool border-cool/25',
|
|
success: 'bg-success/12 text-success border-success/25',
|
|
warning: 'bg-warning/12 text-warning border-warning/25',
|
|
error: 'bg-error/12 text-error border-error/25',
|
|
glow:
|
|
'bg-gradient-to-r from-accent/20 to-cool/15 text-text-1 border-accent/30 shadow-[0_0_12px_rgba(245,182,66,0.25)]',
|
|
outline: 'bg-transparent text-text-2 border-border hover:border-border-hover hover:text-text-1',
|
|
}
|
|
|
|
const sizeClass: Record<ChipSize, string> = {
|
|
xs: 'h-5 px-1.5 text-[10px] gap-1 rounded',
|
|
sm: 'h-6 px-2 text-[11px] gap-1.5 rounded-md',
|
|
md: 'h-7 px-2.5 text-[12px] gap-1.5 rounded-md',
|
|
}
|
|
|
|
interface ChipBase {
|
|
tone?: ChipTone
|
|
size?: ChipSize
|
|
icon?: ReactNode
|
|
trailing?: ReactNode
|
|
active?: boolean
|
|
}
|
|
|
|
export type ChipProps = ChipBase & HTMLAttributes<HTMLSpanElement> & { as?: 'span' }
|
|
export type ChipButtonProps = ChipBase & ButtonHTMLAttributes<HTMLButtonElement> & { as: 'button' }
|
|
|
|
export const Chip = forwardRef<HTMLSpanElement | HTMLButtonElement, ChipProps | ChipButtonProps>(
|
|
function Chip(props, ref) {
|
|
const {
|
|
tone = 'neutral',
|
|
size = 'sm',
|
|
icon,
|
|
trailing,
|
|
active,
|
|
className = '',
|
|
children,
|
|
as = 'span',
|
|
...rest
|
|
} = props as ChipBase & { as?: 'span' | 'button' } & Record<string, any>
|
|
|
|
const baseCls =
|
|
`inline-flex items-center font-medium tracking-tight border whitespace-nowrap select-none transition-colors duration-150 ${
|
|
sizeClass[size]
|
|
} ${toneClass[active ? 'accent' : tone]} ${className}`
|
|
|
|
if (as === 'button') {
|
|
return (
|
|
<button
|
|
{...(rest as ButtonHTMLAttributes<HTMLButtonElement>)}
|
|
ref={ref as React.Ref<HTMLButtonElement>}
|
|
className={`${baseCls} cursor-pointer focus-ring`}
|
|
>
|
|
{icon}
|
|
{children}
|
|
{trailing}
|
|
</button>
|
|
)
|
|
}
|
|
return (
|
|
<span
|
|
{...(rest as HTMLAttributes<HTMLSpanElement>)}
|
|
ref={ref as React.Ref<HTMLSpanElement>}
|
|
className={baseCls}
|
|
>
|
|
{icon}
|
|
{children}
|
|
{trailing}
|
|
</span>
|
|
)
|
|
},
|
|
)
|