design system token v0.6.1
This commit is contained in:
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from 'class-variance-authority'
|
|||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const badgeVariants = cva(
|
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: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
@@ -43,9 +43,15 @@ const badgeVariants = cva(
|
|||||||
platform:
|
platform:
|
||||||
'border-transparent bg-[#f97316] text-white [a&]:hover:bg-[#f97316]/90',
|
'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: {
|
defaultVariants: {
|
||||||
variant: 'default',
|
variant: 'default',
|
||||||
|
size: 'default',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -53,6 +59,7 @@ const badgeVariants = cva(
|
|||||||
function Badge({
|
function Badge({
|
||||||
className,
|
className,
|
||||||
variant,
|
variant,
|
||||||
|
size,
|
||||||
asChild = false,
|
asChild = false,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<'span'> &
|
}: React.ComponentProps<'span'> &
|
||||||
@@ -62,7 +69,7 @@ function Badge({
|
|||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
data-slot="badge"
|
data-slot="badge"
|
||||||
className={cn(badgeVariants({ variant }), className)}
|
className={cn(badgeVariants({ variant, size }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -101,9 +101,9 @@ export const COMPONENT_CATALOG: ComponentSpec[] = [
|
|||||||
file: 'components/ui/badge.tsx',
|
file: 'components/ui/badge.tsx',
|
||||||
category: 'primitives',
|
category: 'primitives',
|
||||||
exports: ['Badge', 'badgeVariants'],
|
exports: ['Badge', 'badgeVariants'],
|
||||||
description: 'Status indicator / tag. Variants include default, secondary, outline, destructive, success, warning, info, plus channel pills (WhatsApp, Email, Telegram, Zulip).',
|
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" | "destructive" | "outline" | "success" | "warning" | "info" | ...; asChild?: boolean',
|
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>',
|
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',
|
name: 'Input',
|
||||||
|
|||||||
@@ -165,6 +165,8 @@ function buildComponentCatalog(): string {
|
|||||||
function buildCompositionRules(): string {
|
function buildCompositionRules(): string {
|
||||||
return `## Composition Rules
|
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
|
- **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
|
- **Section rhythm**: \`py-10\` internal padding per section. Colored sections add \`my-8\` to detach from neighbors
|
||||||
- **Button placement**: Primary action right, secondary left
|
- **Button placement**: Primary action right, secondary left
|
||||||
|
|||||||
@@ -254,11 +254,13 @@ All components live in `components/ui/`. Import with `@/components/ui/<name>`.
|
|||||||
#### Badge
|
#### Badge
|
||||||
- **File**: `components/ui/badge.tsx`
|
- **File**: `components/ui/badge.tsx`
|
||||||
- **Exports**: `Badge`, `badgeVariants`
|
- **Exports**: `Badge`, `badgeVariants`
|
||||||
- **Description**: Status indicator / tag. Variants include default, secondary, outline, destructive, success, warning, info, plus channel pills (WhatsApp, Email, Telegram, Zulip).
|
- **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" | "destructive" | "outline" | "success" | "warning" | "info" | ...; asChild?: boolean`
|
- **Props**: `variant?: "default" | "secondary" | "muted" | "destructive" | "outline" | "success" | "warning" | "info" | "tag" | "value" | "whatsapp" | "email" | "telegram" | "zulip" | "platform"; size?: "sm" | "default" | "lg"; asChild?: boolean`
|
||||||
- **Example**:
|
- **Example**:
|
||||||
```tsx
|
```tsx
|
||||||
<Badge variant="success">Active</Badge>
|
<Badge variant="success">Active</Badge>
|
||||||
|
<Badge variant="secondary" size="sm">3 items</Badge>
|
||||||
|
<Badge variant="default" size="lg">New feature</Badge>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Input
|
#### Input
|
||||||
@@ -642,6 +644,8 @@ pnpm dev`}</Code>
|
|||||||
|
|
||||||
## Composition Rules
|
## 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
|
- **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
|
- **Section rhythm**: `py-10` internal padding per section. Colored sections add `my-8` to detach from neighbors
|
||||||
- **Button placement**: Primary action right, secondary left
|
- **Button placement**: Primary action right, secondary left
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ const meta = {
|
|||||||
'platform',
|
'platform',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['sm', 'default', 'lg'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} satisfies Meta<typeof Badge>
|
} satisfies Meta<typeof Badge>
|
||||||
|
|
||||||
@@ -168,3 +172,52 @@ export const AllVariants: Story = {
|
|||||||
</div>
|
</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>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user