docs: extend all 18 AGENTS.md files with comprehensive context, code examples, and recipes
This commit is contained in:
@@ -1,59 +1,345 @@
|
||||
# @repo/ui — Atomic Design Component Library
|
||||
# @repo/ui -- Atomic Design Component Library
|
||||
|
||||
shadcn/ui + Tailwind CSS v4 + Atomic Design. Components organized by level with co-located stories.
|
||||
## Purpose
|
||||
|
||||
## Atomic Classification Guide
|
||||
Shared UI component library built with shadcn/ui patterns, Tailwind CSS v4, and organized by Atomic Design levels. All components have co-located Storybook stories. This package is consumed by all frontend apps (`apps/web-next`, `apps/web-tanstack`).
|
||||
|
||||
| Level | Definition | Examples |
|
||||
|---|---|---|
|
||||
| Atom | Single element, can't break down further | Button, Input, Label, Badge, Separator |
|
||||
| Molecule | 2-3 atoms, single responsibility | FormField, SearchBar, Tooltip, Select |
|
||||
| Organism | Complex section, self-contained | DataTable, Dialog, Header, Sidebar, Card |
|
||||
| Template | Page layout, content-agnostic | DashboardLayout, AuthLayout |
|
||||
| Page | Template + real data | **LIVES IN apps/, NOT HERE** |
|
||||
## Atomic Design Classification Guide
|
||||
|
||||
## Import Rules
|
||||
| Level | Definition | Examples | Import Rules |
|
||||
|---|---|---|---|
|
||||
| Atom | Single HTML element, cannot be broken down further | Button, Input, Label, Badge, Separator, Icon | Can import: `lib/`, `styles/`. NEVER import: molecules, organisms, templates |
|
||||
| Molecule | 2-3 atoms composed together, single responsibility | FormField, SearchBar, Tooltip, Select, Dropdown | Can import: `atoms/`, `lib/`. NEVER import: organisms, templates |
|
||||
| Organism | Complex self-contained section with multiple molecules/atoms | DataTable, Dialog, Header, Sidebar, Card, Navbar | Can import: `atoms/`, `molecules/`, `lib/`. NEVER import: templates |
|
||||
| Template | Page-level layout shell, content-agnostic (uses children/slots) | DashboardLayout, AuthLayout, MarketingLayout | Can import: `atoms/`, `molecules/`, `organisms/`, `lib/` |
|
||||
| Page | Template filled with real data | **LIVES IN `apps/`, NOT IN THIS PACKAGE** | N/A |
|
||||
|
||||
## Import Rules Table
|
||||
|
||||
| Level | Can import from | NEVER import from |
|
||||
|---|---|---|
|
||||
| Atoms | lib/, hooks/, styles/ | molecules/, organisms/, templates/ |
|
||||
| Molecules | atoms/, lib/, hooks/ | organisms/, templates/ |
|
||||
| Organisms | atoms/, molecules/, lib/, hooks/ | templates/ |
|
||||
| Templates | atoms/, molecules/, organisms/, lib/, hooks/ | (top level) |
|
||||
| Atoms | `lib/`, `hooks/`, `styles/` | `molecules/`, `organisms/`, `templates/` |
|
||||
| Molecules | `atoms/`, `lib/`, `hooks/` | `organisms/`, `templates/` |
|
||||
| Organisms | `atoms/`, `molecules/`, `lib/`, `hooks/` | `templates/` |
|
||||
| Templates | `atoms/`, `molecules/`, `organisms/`, `lib/`, `hooks/` | (nothing above -- top level) |
|
||||
|
||||
## Component Rules
|
||||
## Component Rules Per Level
|
||||
|
||||
- **Atoms:** No margins/positioning, no state, no business logic
|
||||
- **Molecules:** Single responsibility, minimal controlled state
|
||||
- **Organisms:** Can have internal state and sub-components
|
||||
- **Templates:** Use children/slots, NEVER hard-code content
|
||||
- **All:** Co-locate `.stories.tsx` next to component
|
||||
- **Atoms:** No margins or positioning (consumer controls layout). No internal state. No business logic. Accept `className` prop for composition. Use `forwardRef` for DOM elements.
|
||||
- **Molecules:** Single responsibility. Minimal controlled state (e.g., open/closed). Compose atoms only. Accept `className` for outer container.
|
||||
- **Organisms:** Can have internal state and sub-components. Can fetch context. Self-contained sections of a page.
|
||||
- **Templates:** Use `children` or named slots (`sidebar`, `header`, etc.) for content injection. NEVER hard-code content or data.
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
packages/ui/
|
||||
src/
|
||||
lib/
|
||||
utils.ts # cn() utility (clsx + twMerge)
|
||||
styles/
|
||||
globals.css # Tailwind v4 @theme tokens, @import "tailwindcss"
|
||||
atoms/
|
||||
button/
|
||||
button.tsx # Button component (5 variants, 3 sizes)
|
||||
button.stories.tsx # Storybook stories
|
||||
index.ts # Barrel export
|
||||
input/
|
||||
input.tsx # Input component
|
||||
input.stories.tsx
|
||||
index.ts
|
||||
label/
|
||||
label.tsx # Label component
|
||||
index.ts
|
||||
index.ts # Barrel: re-exports all atoms
|
||||
molecules/
|
||||
form-field/
|
||||
form-field.tsx # FormField = Label + Input + error/description
|
||||
form-field.stories.tsx
|
||||
index.ts
|
||||
index.ts # Barrel: re-exports all molecules
|
||||
organisms/
|
||||
index.ts # Barrel (empty -- no organisms yet)
|
||||
templates/
|
||||
index.ts # Barrel (empty -- no templates yet)
|
||||
index.ts # Package entry: re-exports cn + all levels
|
||||
package.json
|
||||
AGENTS.md
|
||||
```
|
||||
|
||||
## Existing Components
|
||||
|
||||
### Button (Atom)
|
||||
|
||||
- **Variants:** `default`, `secondary`, `destructive`, `outline`, `ghost`
|
||||
- **Sizes:** `sm` (h-9), `default` (h-10), `lg` (h-11)
|
||||
- **Props:** Extends `ButtonHTMLAttributes<HTMLButtonElement>` plus `variant` and `size`
|
||||
- **Uses:** `forwardRef`, `cn()` for class merging
|
||||
|
||||
### Input (Atom)
|
||||
|
||||
- **Props:** Extends `InputHTMLAttributes<HTMLInputElement>`
|
||||
- **Uses:** `forwardRef`, `cn()` for class merging
|
||||
- Full styling: border, focus ring, disabled state, file input support
|
||||
|
||||
### Label (Atom)
|
||||
|
||||
- **Props:** Extends `LabelHTMLAttributes<HTMLLabelElement>`
|
||||
- **Uses:** `forwardRef`, `cn()` for class merging
|
||||
- Handles `peer-disabled` state
|
||||
|
||||
### FormField (Molecule)
|
||||
|
||||
- **Props:** Extends `InputProps` plus `label` (string), `error?` (string), `description?` (string)
|
||||
- **Composes:** Label + Input
|
||||
- Auto-generates `id` from label text if not provided
|
||||
|
||||
## cn() Utility
|
||||
|
||||
The `cn()` function merges Tailwind classes safely using `clsx` + `tailwind-merge`:
|
||||
|
||||
```typescript
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
```
|
||||
|
||||
Usage in components:
|
||||
|
||||
```tsx
|
||||
<button
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center rounded-md", // base classes
|
||||
variantStyles[variant], // variant classes
|
||||
sizeStyles[size], // size classes
|
||||
className // consumer override
|
||||
)}
|
||||
/>
|
||||
```
|
||||
|
||||
`cn()` ensures that consumer-provided classes properly override component defaults (e.g., `cn("bg-red-500", "bg-blue-500")` yields `"bg-blue-500"`).
|
||||
|
||||
## Tailwind v4 CSS-First Configuration
|
||||
|
||||
This project uses **Tailwind CSS v4**, which replaces `tailwind.config.ts` with CSS-first configuration. There is no `tailwind.config.ts` file. All design tokens are defined in `src/styles/globals.css` using the `@theme` directive:
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--color-background: hsl(0 0% 100%);
|
||||
--color-foreground: hsl(240 10% 3.9%);
|
||||
--color-primary: hsl(240 5.9% 10%);
|
||||
--color-primary-foreground: hsl(0 0% 98%);
|
||||
--color-secondary: hsl(240 4.8% 95.9%);
|
||||
--color-destructive: hsl(0 84.2% 60.2%);
|
||||
--color-border: hsl(240 5.9% 90%);
|
||||
--color-input: hsl(240 5.9% 90%);
|
||||
--color-ring: hsl(240 5.9% 10%);
|
||||
--radius-sm: 0.25rem;
|
||||
--radius-md: 0.375rem;
|
||||
--radius-lg: 0.5rem;
|
||||
}
|
||||
```
|
||||
|
||||
To add new tokens, add them inside the `@theme { }` block in `globals.css`. Classes like `bg-primary`, `text-destructive`, `rounded-lg` reference these tokens automatically.
|
||||
|
||||
## shadcn/ui Workflow
|
||||
|
||||
1. `pnpm dlx shadcn@latest add [component]` — lands in atoms/ by default
|
||||
2. Check classification guide above
|
||||
3. If not atom → move to correct directory
|
||||
4. Create `.stories.tsx` with title: `"{Level}/{ComponentName}"`
|
||||
5. Update level's `index.ts` barrel
|
||||
When adding a new shadcn/ui component:
|
||||
|
||||
## Story Template
|
||||
1. **Install:** `pnpm dlx shadcn@latest add [component]` -- it lands in `atoms/` by default
|
||||
2. **Classify:** Check the Atomic Design classification table above
|
||||
3. **Relocate:** If the component is not an atom, move it to the correct level directory
|
||||
4. **Story:** Create a `.stories.tsx` file next to the component
|
||||
5. **Export:** Add to the level's `index.ts` barrel file
|
||||
|
||||
## Storybook MCP Integration
|
||||
|
||||
Before creating any new UI component, query the Storybook MCP to check for existing components:
|
||||
|
||||
- **`list-all-documentation`** -- discover all existing components and their stories
|
||||
- **`get-documentation`** -- understand existing component props, variants, and usage
|
||||
- **`run-story-tests`** -- validate your new story renders correctly after creation
|
||||
|
||||
Storybook MCP is available at `http://localhost:6006/mcp` when Storybook is running.
|
||||
|
||||
## Complete Story File Template
|
||||
|
||||
```tsx
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { MyComponent } from "./my-component.js";
|
||||
import { MyComponent } from "./my-component";
|
||||
|
||||
const meta = {
|
||||
title: "{Level}/{ComponentName}",
|
||||
title: "{Level}/{ComponentName}", // e.g., "Atoms/Button", "Molecules/FormField"
|
||||
component: MyComponent,
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
// Define controls for interactive props
|
||||
variant: {
|
||||
control: "select",
|
||||
options: ["default", "secondary"],
|
||||
},
|
||||
size: {
|
||||
control: "select",
|
||||
options: ["sm", "default", "lg"],
|
||||
},
|
||||
disabled: { control: "boolean" },
|
||||
},
|
||||
} satisfies Meta<typeof MyComponent>;
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: "Default",
|
||||
},
|
||||
};
|
||||
|
||||
export const AnotherVariant: Story = {
|
||||
args: {
|
||||
children: "Another Variant",
|
||||
variant: "secondary",
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Storybook MCP
|
||||
## Recipe: Adding a New Component (8 Steps)
|
||||
|
||||
Before creating UI components, query Storybook MCP:
|
||||
- `list-all-documentation` — check for existing components
|
||||
- `get-documentation` — understand props/variants
|
||||
- After creating: `run-story-tests` to validate
|
||||
This example adds a `Badge` atom component.
|
||||
|
||||
### Step 1: Check Storybook MCP for existing components
|
||||
|
||||
Query `list-all-documentation` to confirm no Badge component exists.
|
||||
|
||||
### Step 2: Classify the component
|
||||
|
||||
Badge is a single HTML element displaying a short label -- it is an **Atom**.
|
||||
|
||||
### Step 3: Create the component directory
|
||||
|
||||
```
|
||||
src/atoms/badge/
|
||||
badge.tsx
|
||||
badge.stories.tsx
|
||||
index.ts
|
||||
```
|
||||
|
||||
### Step 4: Write the component
|
||||
|
||||
`src/atoms/badge/badge.tsx`:
|
||||
|
||||
```tsx
|
||||
import { type HTMLAttributes } from "react";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
||||
variant?: "default" | "secondary" | "destructive" | "outline";
|
||||
}
|
||||
|
||||
const variantStyles: Record<NonNullable<BadgeProps["variant"]>, string> = {
|
||||
default: "bg-primary text-primary-foreground",
|
||||
secondary: "bg-secondary text-secondary-foreground",
|
||||
destructive: "bg-destructive text-destructive-foreground",
|
||||
outline: "border border-input bg-background text-foreground",
|
||||
};
|
||||
|
||||
export function Badge({
|
||||
className,
|
||||
variant = "default",
|
||||
...props
|
||||
}: BadgeProps) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors",
|
||||
variantStyles[variant],
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Create the barrel export
|
||||
|
||||
`src/atoms/badge/index.ts`:
|
||||
|
||||
```typescript
|
||||
export { Badge, type BadgeProps } from "./badge";
|
||||
```
|
||||
|
||||
### Step 6: Write the story
|
||||
|
||||
`src/atoms/badge/badge.stories.tsx`:
|
||||
|
||||
```tsx
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { Badge } from "./badge";
|
||||
|
||||
const meta = {
|
||||
title: "Atoms/Badge",
|
||||
component: Badge,
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: "select",
|
||||
options: ["default", "secondary", "destructive", "outline"],
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Badge>;
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: { children: "Badge" },
|
||||
};
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: { children: "Secondary", variant: "secondary" },
|
||||
};
|
||||
|
||||
export const Destructive: Story = {
|
||||
args: { children: "Error", variant: "destructive" },
|
||||
};
|
||||
|
||||
export const Outline: Story = {
|
||||
args: { children: "Outline", variant: "outline" },
|
||||
};
|
||||
```
|
||||
|
||||
### Step 7: Add to atoms barrel
|
||||
|
||||
Edit `src/atoms/index.ts`:
|
||||
|
||||
```typescript
|
||||
export { Button, type ButtonProps } from "./button/index";
|
||||
export { Input, type InputProps } from "./input/index";
|
||||
export { Label, type LabelProps } from "./label/index";
|
||||
export { Badge, type BadgeProps } from "./badge/index"; // <-- add
|
||||
```
|
||||
|
||||
### Step 8: Validate
|
||||
|
||||
Run `run-story-tests` via Storybook MCP to confirm the stories render correctly.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Dependency | Purpose |
|
||||
|---|---|
|
||||
| `react` | JSX runtime |
|
||||
| `clsx` | Conditional class string builder |
|
||||
| `tailwind-merge` | Intelligent Tailwind class merging (deduplication) |
|
||||
| `tailwindcss` (devDep) | Tailwind CSS v4 engine |
|
||||
|
||||
## Cross-References
|
||||
|
||||
- **Storybook app:** `apps/storybook/` -- see `apps/storybook/AGENTS.md`
|
||||
- **Consumed by Next.js:** `apps/web-next/` -- see `apps/web-next/AGENTS.md`
|
||||
- **Consumed by TanStack:** `apps/web-tanstack/` -- see `apps/web-tanstack/AGENTS.md`
|
||||
|
||||
Reference in New Issue
Block a user