---
name: ui-component
description: Use when creating new UI components under apps/web/src/components/
---
# UI Component Creation Skill
## Checklist (MUST complete all)
- [ ] Create component file
- [ ] Create `*.stories.tsx` for Storybook
- [ ] Use semantic colors for monetary values (see `AGENTS.md`)
- [ ] Verify Storybook: `pnpm --filter @mf-dashboard/web test:storybook`
## Component Types
### 1. Pure UI Components (`components/charts/` or `components/ui/`)
Data-agnostic, reusable components. Receive all data via props.
```typescript
// components/charts/my-chart.tsx
"use client";
interface MyChartProps {
data: Array<{ label: string; value: number }>;
// ... props only, no data fetching
}
export function MyChart({ data }: MyChartProps) {
return (/* pure rendering */);
}
```
### 2. Data-Fetching Components (`components/info/`)
| File | Purpose |
| ----------------- | --------------------------------------------------------- |
| `foo.tsx` | Server Component - fetches data via `lib/queries` |
| `foo.client.tsx` | Client Component - handles interactivity (ONLY if needed) |
| `foo.stories.tsx` | Storybook - uses mock data |
**IMPORTANT:** Only create `.client.tsx` when interactivity is required (useState, useEffect, event handlers, etc.). If the component only displays data, keep it as a Server Component.
#### Server Component Only (no interactivity)
```typescript
// components/info/my-info.tsx
import { getMyData } from "../../lib/queries";
export function MyInfo() {
const data = getMyData();
return (
My Info
{/* render data */}
);
}
```
#### With Client Component (interactivity required)
```typescript
// components/info/my-info.tsx
import { getMyData } from "../../lib/queries";
import { MyInfoClient } from "./my-info.client";
export function MyInfo() {
const data = getMyData();
return ;
}
```
```typescript
// components/info/my-info.client.tsx
"use client";
interface MyInfoClientProps {
data: MyData;
}
export function MyInfoClient({ data }: MyInfoClientProps) {
const [state, setState] = useState(/* ... */);
return (/* interactive UI */);
}
```
## File Locations
- Pure charts: `apps/web/src/components/charts/`
- UI primitives: `apps/web/src/components/ui/`
- Data-fetching: `apps/web/src/components/info/`
- Queries: `apps/web/src/lib/queries.ts`
## Storybook Template
```typescript
// components/info/my-info.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { MyInfoClient } from "./my-info.client";
const meta = {
component: MyInfoClient,
} satisfies Meta;
export default meta;
type Story = StoryObj;
export const Default: Story = {
args: {
data: {
// mock data here
},
},
};
```
## Semantic Colors (MUST use)
| Purpose | Class |
| ---------------- | ----------------------- |
| Income amount | `text-income` |
| Expense amount | `text-expense` |
| Positive balance | `text-balance-positive` |
| Negative balance | `text-balance-negative` |