Skip to content
This repository was archived by the owner on Jul 31, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/components/Badge/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,44 @@ export const AllVariants: Story = {
</div>
),
}

export const AllSizes: Story = {
render: () => (
<div className="flex items-center gap-4">
<Badge size="sm">
<Badge.Text>Small</Badge.Text>
</Badge>
<Badge size="md">
<Badge.Text>Medium</Badge.Text>
</Badge>
<Badge size="lg">
<Badge.Text>Large</Badge.Text>
</Badge>
</div>
),
}

export const AllSizesWithIcon: Story = {
render: () => (
<div className="flex items-center gap-4">
<Badge size="sm">
<Badge.LeftIcon>
<GitPullRequest />
</Badge.LeftIcon>
<Badge.Text>Small</Badge.Text>
</Badge>
<Badge size="md">
<Badge.LeftIcon>
<GitPullRequest />
</Badge.LeftIcon>
<Badge.Text>Medium</Badge.Text>
</Badge>
<Badge size="lg">
<Badge.LeftIcon>
<GitPullRequest />
</Badge.LeftIcon>
<Badge.Text>Large</Badge.Text>
</Badge>
</div>
),
}
35 changes: 35 additions & 0 deletions src/components/Badge/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,39 @@ describe('Badge', () => {
render(<Badge>Default</Badge>)
expect(screen.getByText('Default')).toBeInTheDocument()
})

describe('size prop', () => {
it('defaults to md size classes', () => {
const { container } = render(<Badge>Default</Badge>)
const badge = container.firstChild as HTMLElement
expect(badge).toHaveClass('h-5')
expect(badge).toHaveClass('text-xs')
})

it('applies sm size classes', () => {
const { container } = render(<Badge size="sm">Small</Badge>)
const badge = container.firstChild as HTMLElement
expect(badge).toHaveClass('h-4')
expect(badge).toHaveClass('text-2xs')
})

it('applies lg size classes', () => {
const { container } = render(<Badge size="lg">Large</Badge>)
const badge = container.firstChild as HTMLElement
expect(badge).toHaveClass('h-6')
expect(badge).toHaveClass('text-base')
})

it('sm does not have md size classes', () => {
const { container } = render(<Badge size="sm">Small</Badge>)
const badge = container.firstChild as HTMLElement
expect(badge).not.toHaveClass('h-5')
})

it('lg does not have md size classes', () => {
const { container } = render(<Badge size="lg">Large</Badge>)
const badge = container.firstChild as HTMLElement
expect(badge).not.toHaveClass('h-5')
})
})
})
20 changes: 16 additions & 4 deletions src/components/Badge/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Slot } from '@radix-ui/react-slot'
import { cva } from 'class-variance-authority'

import { cn } from '@/lib/utils'
import { BadgeVariant } from '@/types'
import { BadgeVariant, BadgeSize } from '@/types'

const BadgeLeftIcon = React.forwardRef<
HTMLSpanElement,
Expand Down Expand Up @@ -41,12 +41,16 @@ const BadgeText = React.forwardRef<
HTMLSpanElement,
React.HTMLAttributes<HTMLSpanElement>
>(({ className, ...props }, ref) => (
<span ref={ref} className={cn('flex-1', className)} {...props} />
<span
ref={ref}
className={cn('text-trim-cap flex-1', className)}
{...props}
/>
))
BadgeText.displayName = 'BadgeText'

const badgeVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap select-none font-mono uppercase tracking-[0.03em] rounded-xs border transition-colors h-5 px-1 py-1 text-[12px] leading-[12px] gap-1 [&_svg]:size-3',
'inline-flex items-center justify-center whitespace-nowrap select-none font-mono uppercase tracking-[0.03em] rounded-xs border transition-colors',
{
variants: {
variant: {
Expand All @@ -56,6 +60,11 @@ const badgeVariants = cva(
success: 'text-default-success border-success-softest',
warning: 'text-default-warning border-warning-softest',
},
size: {
sm: 'h-4 px-0.75 py-0.75 text-2xs leading-none gap-0.75 [&_svg]:size-2.25',
md: 'h-5 px-1 py-1 text-xs leading-none gap-1 [&_svg]:size-3',
lg: 'h-6 px-1 py-1 text-base leading-none gap-1 [&_svg]:size-4',
},
background: {
true: '',
false: 'bg-transparent',
Expand Down Expand Up @@ -91,6 +100,7 @@ const badgeVariants = cva(
defaultVariants: {
variant: 'neutral',
background: true,
size: 'md',
},
}
)
Expand All @@ -100,6 +110,7 @@ type Attributes = Omit<React.HTMLAttributes<HTMLSpanElement>, 'style'>
export interface BadgeProps extends Attributes {
asChild?: boolean
variant?: BadgeVariant
size?: BadgeSize
background?: boolean
className?: string
'aria-label'?: string
Expand All @@ -109,6 +120,7 @@ const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
(
{
variant = 'neutral',
size = 'md',
background = true,
asChild = false,
className,
Expand Down Expand Up @@ -200,7 +212,7 @@ const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(

return (
<Comp
className={cn(badgeVariants({ variant, background }), className)}
className={cn(badgeVariants({ variant, size, background }), className)}
ref={ref}
{...props}
>
Expand Down
6 changes: 4 additions & 2 deletions src/global.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Global CSS - Main Entry Point
*
*
* This file orchestrates the design system by:
* 1. Importing Tailwind and plugins
* 2. Defining custom variants
Expand Down Expand Up @@ -63,5 +63,7 @@
--font-mono: var(--font-diatype-mono);
--font-display: var(--font-tobias);
--font-ascii: var(--font-speakeasy), var(--font-diatype-mono), monospace;
}

/* Extended text scale */
--text-2xs: 0.5625rem; /* 9px */
}
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export const badgeVariants = [
] as const
export type BadgeVariant = (typeof badgeVariants)[number]

// Badge sizes
export const badgeSizes = ['sm', 'md', 'lg'] as const
export type BadgeSize = (typeof badgeSizes)[number]

// Generic
export type Orientation = 'horizontal' | 'vertical'

Expand Down
Loading