feat: add Badge component to shared/ui

This commit is contained in:
Ilia Mashkov
2026-04-18 15:53:01 +03:00
parent db2d7b78dd
commit bb9c91b4f8
3 changed files with 91 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
export { Badge } from './ui/Badge'
+52
View File
@@ -0,0 +1,52 @@
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { Badge } from './Badge'
describe('Badge', () => {
describe('rendering', () => {
it('renders children', () => {
render(<Badge>React</Badge>)
expect(screen.getByText('React')).toBeInTheDocument()
})
it('renders as inline span', () => {
render(<Badge>Tag</Badge>)
expect(screen.getByText('Tag').tagName).toBe('SPAN')
})
})
describe('variants', () => {
it('applies default variant classes', () => {
render(<Badge variant="default">Tag</Badge>)
const el = screen.getByText('Tag')
expect(el).toHaveClass('bg-carbon-black', 'text-ochre-clay')
})
it('applies primary variant classes', () => {
render(<Badge variant="primary">Tag</Badge>)
expect(screen.getByText('Tag')).toHaveClass('bg-burnt-oxide')
})
it('applies secondary variant classes', () => {
render(<Badge variant="secondary">Tag</Badge>)
expect(screen.getByText('Tag')).toHaveClass('bg-slate-indigo')
})
it('applies outline variant classes', () => {
render(<Badge variant="outline">Tag</Badge>)
expect(screen.getByText('Tag')).toHaveClass('bg-transparent')
})
it('defaults to default variant when unspecified', () => {
render(<Badge>Tag</Badge>)
expect(screen.getByText('Tag')).toHaveClass('bg-carbon-black')
})
})
describe('className passthrough', () => {
it('merges custom className', () => {
render(<Badge className="mt-4">Tag</Badge>)
expect(screen.getByText('Tag')).toHaveClass('mt-4')
})
})
})
+38
View File
@@ -0,0 +1,38 @@
import type { ReactNode } from 'react'
import { cn } from '$shared/lib'
type BadgeVariant = 'default' | 'primary' | 'secondary' | 'outline'
interface Props {
/**
* Badge content
*/
children: ReactNode
/**
* Visual variant
* @default 'default'
*/
variant?: BadgeVariant
/**
* Additional CSS classes
*/
className?: string
}
const VARIANTS: Record<BadgeVariant, string> = {
default: 'brutal-border bg-carbon-black text-ochre-clay',
primary: 'brutal-border bg-burnt-oxide text-ochre-clay',
secondary: 'brutal-border bg-slate-indigo text-ochre-clay',
outline: 'brutal-border bg-transparent text-carbon-black',
}
/**
* Small label for categorization or status.
*/
export function Badge({ children, variant = 'default', className }: Props) {
return (
<span className={cn('inline-block px-3 py-1 text-xs uppercase tracking-wider', VARIANTS[variant], className)}>
{children}
</span>
)
}