design system token v0.6.1

This commit is contained in:
Juan
2026-04-16 11:43:32 -05:00
parent ae3d219d58
commit 9a311ff9a5
5 changed files with 73 additions and 7 deletions

View File

@@ -5,7 +5,7 @@ import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const badgeVariants = cva(
'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
'inline-flex items-center justify-center rounded-md border font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
{
variants: {
variant: {
@@ -43,9 +43,15 @@ const badgeVariants = cva(
platform:
'border-transparent bg-[#f97316] text-white [a&]:hover:bg-[#f97316]/90',
},
size: {
sm: 'text-xs px-1.5 py-0',
default: 'text-xs px-2 py-0.5',
lg: 'text-sm px-3 py-1 [&>svg]:size-3.5',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
)
@@ -53,6 +59,7 @@ const badgeVariants = cva(
function Badge({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<'span'> &
@@ -62,7 +69,7 @@ function Badge({
return (
<Comp
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
className={cn(badgeVariants({ variant, size }), className)}
{...props}
/>
)

View File

@@ -101,9 +101,9 @@ export const COMPONENT_CATALOG: ComponentSpec[] = [
file: 'components/ui/badge.tsx',
category: 'primitives',
exports: ['Badge', 'badgeVariants'],
description: 'Status indicator / tag. Variants include default, secondary, outline, destructive, success, warning, info, plus channel pills (WhatsApp, Email, Telegram, Zulip).',
props: 'variant?: "default" | "secondary" | "destructive" | "outline" | "success" | "warning" | "info" | ...; asChild?: boolean',
example: '<Badge variant="success">Active</Badge>',
description: 'Status indicator / tag. Variants: default, secondary, muted, outline, destructive, success, warning, info, tag, value, whatsapp, email, telegram, zulip, platform. Sizes: sm (dense data/tables), default (most uses), lg (hero-adjacent, near large type). NEVER override font-size or padding with className — pick a size variant instead. Anything below text-xs (12px) fails accessibility minimums.',
props: 'variant?: "default" | "secondary" | "muted" | "destructive" | "outline" | "success" | "warning" | "info" | "tag" | "value" | "whatsapp" | "email" | "telegram" | "zulip" | "platform"; size?: "sm" | "default" | "lg"; asChild?: boolean',
example: '<Badge variant="success">Active</Badge>\n<Badge variant="secondary" size="sm">3 items</Badge>\n<Badge variant="default" size="lg">New feature</Badge>',
},
{
name: 'Input',

View File

@@ -165,6 +165,8 @@ function buildComponentCatalog(): string {
function buildCompositionRules(): string {
return `## Composition Rules
- **Never override component sizing via \`className\`**: Each component exposes \`size\` / \`variant\` props for a reason. Reach for those first. Overriding font-size, padding, or height with arbitrary Tailwind classes (\`text-sm\`, \`px-3\`, \`py-1\`, etc.) fragments the design system. If no variant fits, add a new \`size\`/\`variant\` to the component — don't one-off patch it at the call site.
- **Minimum font size is \`text-xs\` (12px)**: Anything smaller fails accessibility/readability minimums. If you genuinely need smaller text for a specific reason (e.g., a data-dense legend), add an explicit \`// justification: ...\` comment at the call site. Default answer is: use \`text-xs\`.
- **Card spacing**: \`gap-6\` between cards, \`p-6\` internal padding
- **Section rhythm**: \`py-10\` internal padding per section. Colored sections add \`my-8\` to detach from neighbors
- **Button placement**: Primary action right, secondary left

View File

@@ -254,11 +254,13 @@ All components live in `components/ui/`. Import with `@/components/ui/<name>`.
#### Badge
- **File**: `components/ui/badge.tsx`
- **Exports**: `Badge`, `badgeVariants`
- **Description**: Status indicator / tag. Variants include default, secondary, outline, destructive, success, warning, info, plus channel pills (WhatsApp, Email, Telegram, Zulip).
- **Props**: `variant?: "default" | "secondary" | "destructive" | "outline" | "success" | "warning" | "info" | ...; asChild?: boolean`
- **Description**: Status indicator / tag. Variants: default, secondary, muted, outline, destructive, success, warning, info, tag, value, whatsapp, email, telegram, zulip, platform. Sizes: sm (dense data/tables), default (most uses), lg (hero-adjacent, near large type). NEVER override font-size or padding with className — pick a size variant instead. Anything below text-xs (12px) fails accessibility minimums.
- **Props**: `variant?: "default" | "secondary" | "muted" | "destructive" | "outline" | "success" | "warning" | "info" | "tag" | "value" | "whatsapp" | "email" | "telegram" | "zulip" | "platform"; size?: "sm" | "default" | "lg"; asChild?: boolean`
- **Example**:
```tsx
<Badge variant="success">Active</Badge>
<Badge variant="secondary" size="sm">3 items</Badge>
<Badge variant="default" size="lg">New feature</Badge>
```
#### Input
@@ -642,6 +644,8 @@ pnpm dev`}</Code>
## Composition Rules
- **Never override component sizing via `className`**: Each component exposes `size` / `variant` props for a reason. Reach for those first. Overriding font-size, padding, or height with arbitrary Tailwind classes (`text-sm`, `px-3`, `py-1`, etc.) fragments the design system. If no variant fits, add a new `size`/`variant` to the component — don't one-off patch it at the call site.
- **Minimum font size is `text-xs` (12px)**: Anything smaller fails accessibility/readability minimums. If you genuinely need smaller text for a specific reason (e.g., a data-dense legend), add an explicit `// justification: ...` comment at the call site. Default answer is: use `text-xs`.
- **Card spacing**: `gap-6` between cards, `p-6` internal padding
- **Section rhythm**: `py-10` internal padding per section. Colored sections add `my-8` to detach from neighbors
- **Button placement**: Primary action right, secondary left

View File

@@ -28,6 +28,10 @@ const meta = {
'platform',
],
},
size: {
control: 'select',
options: ['sm', 'default', 'lg'],
},
},
} satisfies Meta<typeof Badge>
@@ -168,3 +172,52 @@ export const AllVariants: Story = {
</div>
),
}
export const SizeSmall: Story = {
args: {
children: 'Small',
variant: 'secondary',
size: 'sm',
},
}
export const SizeDefault: Story = {
args: {
children: 'Default',
variant: 'secondary',
size: 'default',
},
}
export const SizeLarge: Story = {
args: {
children: 'Large',
variant: 'default',
size: 'lg',
},
}
export const AllSizes: Story = {
render: () => (
<div className="flex flex-col gap-6">
<div className="flex items-center gap-3">
<span className="w-20 text-sm text-muted-foreground font-sans">sm</span>
<Badge variant="secondary" size="sm">dense</Badge>
<Badge variant="success" size="sm">12 new</Badge>
<Badge variant="muted" size="sm">draft</Badge>
</div>
<div className="flex items-center gap-3">
<span className="w-20 text-sm text-muted-foreground font-sans">default</span>
<Badge variant="default" size="default">Active</Badge>
<Badge variant="success" size="default">Published</Badge>
<Badge variant="warning" size="default">Pending</Badge>
</div>
<div className="flex items-center gap-3">
<span className="w-20 text-sm text-muted-foreground font-sans">lg</span>
<Badge variant="default" size="lg">New feature</Badge>
<Badge variant="info" size="lg">Beta</Badge>
<Badge variant="success" size="lg">Available</Badge>
</div>
</div>
),
}