From 9a311ff9a53f7da355cb9ad8b456c560f3a1f640 Mon Sep 17 00:00:00 2001 From: Juan Date: Thu, 16 Apr 2026 11:43:32 -0500 Subject: [PATCH] design system token v0.6.1 --- components/ui/badge.tsx | 11 ++++-- lib/catalog.ts | 6 ++-- scripts/generate-skill.ts | 2 ++ skill/SKILL.md | 8 +++-- stories/Primitives/Badge.stories.tsx | 53 ++++++++++++++++++++++++++++ 5 files changed, 73 insertions(+), 7 deletions(-) diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx index 3ed8811..9473c3d 100644 --- a/components/ui/badge.tsx +++ b/components/ui/badge.tsx @@ -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 ( ) diff --git a/lib/catalog.ts b/lib/catalog.ts index d7ec43e..36cf990 100644 --- a/lib/catalog.ts +++ b/lib/catalog.ts @@ -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: 'Active', + 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: 'Active\n3 items\nNew feature', }, { name: 'Input', diff --git a/scripts/generate-skill.ts b/scripts/generate-skill.ts index 2155be3..16e8a78 100644 --- a/scripts/generate-skill.ts +++ b/scripts/generate-skill.ts @@ -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 diff --git a/skill/SKILL.md b/skill/SKILL.md index de18bc4..f647c7d 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -254,11 +254,13 @@ All components live in `components/ui/`. Import with `@/components/ui/`. #### 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 Active +3 items +New feature ``` #### Input @@ -642,6 +644,8 @@ pnpm dev`} ## 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 diff --git a/stories/Primitives/Badge.stories.tsx b/stories/Primitives/Badge.stories.tsx index 32290bd..ffe11a8 100644 --- a/stories/Primitives/Badge.stories.tsx +++ b/stories/Primitives/Badge.stories.tsx @@ -28,6 +28,10 @@ const meta = { 'platform', ], }, + size: { + control: 'select', + options: ['sm', 'default', 'lg'], + }, }, } satisfies Meta @@ -168,3 +172,52 @@ export const AllVariants: Story = { ), } + +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: () => ( +
+
+ sm + dense + 12 new + draft +
+
+ default + Active + Published + Pending +
+
+ lg + New feature + Beta + Available +
+
+ ), +}