@ez-cook/nano-peaces 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +665 -0
- package/package.json +54 -0
- package/skills/package.json +10 -0
- package/skills/registry.json +56 -0
- package/skills/shadcn-ui/SKILL.md +99 -0
- package/skills/shadcn-ui/chunks/anti-patterns.md +97 -0
- package/skills/shadcn-ui/chunks/components/button.md +96 -0
- package/skills/shadcn-ui/chunks/components/data-table.md +156 -0
- package/skills/shadcn-ui/chunks/components/dialog.md +123 -0
- package/skills/shadcn-ui/chunks/components/form.md +147 -0
- package/skills/shadcn-ui/chunks/components/sidebar.md +115 -0
- package/skills/shadcn-ui/chunks/config.md +54 -0
- package/skills/shadcn-ui/chunks/installation.md +40 -0
- package/skills/shadcn-ui/chunks/patterns/composition.md +115 -0
- package/skills/shadcn-ui/chunks/patterns/navigation.md +112 -0
- package/skills/shadcn-ui/chunks/theming.md +96 -0
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ez-cook/nano-peaces",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The missing UI skills pack for AI agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"nano-peaces": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"skills",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup && node scripts/copy-skills.js",
|
|
18
|
+
"dev": "tsup --watch",
|
|
19
|
+
"typecheck": "tsc --noEmit"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"commander": "^13.0.0",
|
|
23
|
+
"@clack/prompts": "^0.9.0",
|
|
24
|
+
"fast-glob": "^3.3.0",
|
|
25
|
+
"fs-extra": "^11.2.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"typescript": "^5.7.0",
|
|
29
|
+
"tsup": "^8.4.0",
|
|
30
|
+
"@types/fs-extra": "^11.0.0",
|
|
31
|
+
"@types/node": "^22.0.0"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"ai",
|
|
35
|
+
"agent",
|
|
36
|
+
"ui",
|
|
37
|
+
"skills",
|
|
38
|
+
"shadcn",
|
|
39
|
+
"cli",
|
|
40
|
+
"antigravity",
|
|
41
|
+
"claude",
|
|
42
|
+
"cursor",
|
|
43
|
+
"ez-cook",
|
|
44
|
+
"nano-peaces"
|
|
45
|
+
],
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/ez-cook/nano-peaces"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1.0",
|
|
3
|
+
"skills": [
|
|
4
|
+
{
|
|
5
|
+
"id": "shadcn-ui",
|
|
6
|
+
"description": "Expert shadcn/ui knowledge for AI agents (2026)",
|
|
7
|
+
"signals": {
|
|
8
|
+
"dependencies": [
|
|
9
|
+
"radix-ui",
|
|
10
|
+
"@radix-ui/*",
|
|
11
|
+
"@base-ui-components/*",
|
|
12
|
+
"tailwindcss",
|
|
13
|
+
"class-variance-authority",
|
|
14
|
+
"tailwind-merge",
|
|
15
|
+
"clsx",
|
|
16
|
+
"lucide-react",
|
|
17
|
+
"@tabler/icons-react",
|
|
18
|
+
"tw-animate-css"
|
|
19
|
+
],
|
|
20
|
+
"filePatterns": ["**/components/ui/**", "**/ui/*.tsx", "**/ui/*.jsx", "components.json"],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"shadcn",
|
|
23
|
+
"button variant",
|
|
24
|
+
"dialog",
|
|
25
|
+
"sheet",
|
|
26
|
+
"command palette",
|
|
27
|
+
"combobox",
|
|
28
|
+
"data table",
|
|
29
|
+
"form validation",
|
|
30
|
+
"sidebar",
|
|
31
|
+
"dropdown",
|
|
32
|
+
"field component",
|
|
33
|
+
"sonner toast",
|
|
34
|
+
"asChild",
|
|
35
|
+
"cn() utility"
|
|
36
|
+
],
|
|
37
|
+
"taskIntent": [
|
|
38
|
+
"create component",
|
|
39
|
+
"build form",
|
|
40
|
+
"add UI element",
|
|
41
|
+
"style component",
|
|
42
|
+
"add dialog",
|
|
43
|
+
"create table",
|
|
44
|
+
"setup shadcn",
|
|
45
|
+
"add shadcn component",
|
|
46
|
+
"add sidebar",
|
|
47
|
+
"dark mode",
|
|
48
|
+
"build data table",
|
|
49
|
+
"add toast notification"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"priority": 1,
|
|
53
|
+
"skillPath": "shadcn-ui/SKILL.md"
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: shadcn-ui
|
|
3
|
+
description: 'Expert knowledge for building UI with shadcn/ui (2026). Activated when: project uses radix-ui or @base-ui-components/*, tailwindcss. Covers: component generation, theming, forms (Field+Controller pattern), data tables (TanStack Table), sidebar, accessibility, RTL, MCP, registry.'
|
|
4
|
+
metadata:
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
tags: [ui, react, tailwind, shadcn, components, radix, base-ui, field, registry]
|
|
7
|
+
requires: [react, tailwindcss]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# shadcn/ui Skill
|
|
11
|
+
|
|
12
|
+
## Mental Model
|
|
13
|
+
|
|
14
|
+
shadcn/ui is NOT a library. It's a code distribution platform.
|
|
15
|
+
You COPY components into your project and OWN them.
|
|
16
|
+
Never `npm install shadcn` — use `npx shadcn@latest add [component]`.
|
|
17
|
+
Since Feb 2026: imports use unified `radix-ui` (not `@radix-ui/react-*`).
|
|
18
|
+
Dual primitive support: Radix UI or Base UI — same API, different engine.
|
|
19
|
+
|
|
20
|
+
## Context Router
|
|
21
|
+
|
|
22
|
+
| User Intent | Load Chunk | When |
|
|
23
|
+
| ---------------------------- | ------------------------------- | ---------------------------------------------------- |
|
|
24
|
+
| Create button, card, badge | chunks/components/button.md | Mentions specific component name |
|
|
25
|
+
| Build form with validation | chunks/components/form.md | Mentions form, input, validation, zod, Field |
|
|
26
|
+
| Add data table | chunks/components/data-table.md | Mentions table, sorting, filtering, pagination |
|
|
27
|
+
| Add dialog, sheet, drawer | chunks/components/dialog.md | Mentions modal, popup, overlay, sheet |
|
|
28
|
+
| Build sidebar / navigation | chunks/components/sidebar.md | Mentions sidebar, nav, collapsible menu |
|
|
29
|
+
| Setup/install shadcn | chunks/installation.md | New project or first shadcn mention |
|
|
30
|
+
| Theme, dark mode, colors | chunks/theming.md | Mentions theme, dark, colors, CSS variables, oklch |
|
|
31
|
+
| components.json config | chunks/config.md | Mentions config, aliases, registry, monorepo setup |
|
|
32
|
+
| Breadcrumb, tabs, pagination | chunks/patterns/navigation.md | Mentions breadcrumb, tabs, nav-menu, pagination |
|
|
33
|
+
| cn(), asChild, cva, extend | chunks/patterns/composition.md | Mentions composition, variants, extending components |
|
|
34
|
+
| Something went wrong | chunks/anti-patterns.md | User reports bug or unexpected behavior |
|
|
35
|
+
|
|
36
|
+
## Quick Reference (Top Patterns)
|
|
37
|
+
|
|
38
|
+
### Add a component
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx shadcn@latest add button
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### New project (choose Radix or Base UI)
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx shadcn@latest create
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Button variants & sizes
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<Button variant="default" /> // primary
|
|
54
|
+
<Button variant="destructive" /> // danger
|
|
55
|
+
<Button variant="outline" /> // bordered
|
|
56
|
+
<Button variant="secondary" /> // muted
|
|
57
|
+
<Button variant="ghost" /> // no border
|
|
58
|
+
<Button variant="link" /> // text link
|
|
59
|
+
|
|
60
|
+
// sizes: "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg"
|
|
61
|
+
<Button size="sm">Small</Button>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### cn() utility (ALWAYS use this for conditional classes)
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { cn } from '@/lib/utils'
|
|
68
|
+
;<div className={cn('base-class', condition && 'conditional-class')} />
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Icon spacing (REQUIRED for icons in buttons/menu items)
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
<Button>
|
|
75
|
+
<IconGitBranch data-icon="inline-start" /> New Branch
|
|
76
|
+
</Button>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Imports (2026 — unified package)
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
// ✅ Correct (2026)
|
|
83
|
+
import { Dialog as DialogPrimitive } from 'radix-ui'
|
|
84
|
+
// ❌ Deprecated
|
|
85
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Critical Rules
|
|
89
|
+
|
|
90
|
+
1. NEVER install shadcn as npm dependency — always use CLI `add`
|
|
91
|
+
2. ALWAYS use `cn()` for class merging (not template literals)
|
|
92
|
+
3. ALWAYS check components.json for project config before generating
|
|
93
|
+
4. Components go in the path specified by components.json `aliases.ui`
|
|
94
|
+
5. Use `asChild` prop (Radix Slot) for polymorphic rendering
|
|
95
|
+
6. Import from `radix-ui` unified package (not `@radix-ui/react-*`)
|
|
96
|
+
7. Use `data-icon="inline-start"|"inline-end"` on icons inside buttons
|
|
97
|
+
8. Use `data-invalid` on Field + `aria-invalid` on controls for error states
|
|
98
|
+
9. Tailwind v4: button has `cursor: default` — add CSS override if need pointer
|
|
99
|
+
10. `use client` directive required for interactive components when RSC is enabled
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Anti-Patterns & Common Mistakes — shadcn/ui
|
|
2
|
+
|
|
3
|
+
## Critical Anti-Patterns Table
|
|
4
|
+
|
|
5
|
+
| ❌ Mistake | ✅ Correct | Why |
|
|
6
|
+
| -------------------------------------- | --------------------------------------------- | --------------------------------------------- |
|
|
7
|
+
| `npm install shadcn` | `npx shadcn@latest add button` | shadcn is a CLI tool, not an npm package |
|
|
8
|
+
| `import from "@radix-ui/react-dialog"` | `import { Dialog } from "radix-ui"` | Unified package since Feb 2026 |
|
|
9
|
+
| `<Form><FormField>` pattern | `<Controller><Field>` pattern | Old form API deprecated in 2026 |
|
|
10
|
+
| Template literal class merge | `cn()` utility | tailwind-merge handles class conflicts |
|
|
11
|
+
| Hardcode `@/components/ui` | Read `aliases.ui` from components.json | Path varies per project configuration |
|
|
12
|
+
| Skip `"use client"` | Check `rsc` in components.json | RSC won't render interactive components |
|
|
13
|
+
| Style `default` | Style `new-york` or nova variants | `default` style is deprecated |
|
|
14
|
+
| Manual validation in form | Zod schema + zodResolver | Standard Schema + react-hook-form integration |
|
|
15
|
+
| `cursor: pointer` assumed | Tailwind v4 uses `cursor: default` on buttons | Add CSS override if needed |
|
|
16
|
+
| Skip `data-invalid`/`aria-invalid` | Required for accessible error states | a11y compliance |
|
|
17
|
+
|
|
18
|
+
## Deprecated Patterns (2026)
|
|
19
|
+
|
|
20
|
+
### Old Radix Imports
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
// ❌ Deprecated
|
|
24
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
25
|
+
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
|
|
26
|
+
|
|
27
|
+
// ✅ Correct — unified package
|
|
28
|
+
import { Dialog as DialogPrimitive } from 'radix-ui'
|
|
29
|
+
import { DropdownMenu as DropdownMenuPrimitive } from 'radix-ui'
|
|
30
|
+
|
|
31
|
+
// Migration command:
|
|
32
|
+
// npx shadcn@latest migrate radix
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Old Form Pattern
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
// ❌ Deprecated
|
|
39
|
+
<Form {...form}>
|
|
40
|
+
<FormField
|
|
41
|
+
control={form.control}
|
|
42
|
+
name="username"
|
|
43
|
+
render={({ field }) => (
|
|
44
|
+
<FormItem>
|
|
45
|
+
<FormLabel>Username</FormLabel>
|
|
46
|
+
<FormControl><Input {...field} /></FormControl>
|
|
47
|
+
<FormMessage />
|
|
48
|
+
</FormItem>
|
|
49
|
+
)}
|
|
50
|
+
/>
|
|
51
|
+
</Form>
|
|
52
|
+
|
|
53
|
+
// ✅ Correct (2026)
|
|
54
|
+
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
55
|
+
<Controller
|
|
56
|
+
name="username"
|
|
57
|
+
control={form.control}
|
|
58
|
+
render={({ field, fieldState }) => (
|
|
59
|
+
<Field data-invalid={fieldState.invalid || undefined}>
|
|
60
|
+
<FieldLabel>Username</FieldLabel>
|
|
61
|
+
<Input {...field} aria-invalid={fieldState.invalid || undefined} />
|
|
62
|
+
{fieldState.error && <FieldError errors={[fieldState.error]} />}
|
|
63
|
+
</Field>
|
|
64
|
+
)}
|
|
65
|
+
/>
|
|
66
|
+
</form>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Common Runtime Errors
|
|
70
|
+
|
|
71
|
+
### Missing "use client"
|
|
72
|
+
|
|
73
|
+
**Error:** Component renders as empty or hydration mismatch
|
|
74
|
+
**Fix:** Add `"use client"` at top of file for any component using hooks, event handlers, or browser APIs
|
|
75
|
+
|
|
76
|
+
### Wrong Alias Path
|
|
77
|
+
|
|
78
|
+
**Error:** `Module not found: Can't resolve '@/components/ui/button'`
|
|
79
|
+
**Fix:** Check `components.json` → `aliases.ui` matches your tsconfig paths
|
|
80
|
+
|
|
81
|
+
### Missing DialogTitle
|
|
82
|
+
|
|
83
|
+
**Error:** Console warning about missing accessible name
|
|
84
|
+
**Fix:** Always include `<DialogTitle>` (use `<VisuallyHidden>` if you don't want visible title)
|
|
85
|
+
|
|
86
|
+
### className Conflicts
|
|
87
|
+
|
|
88
|
+
**Error:** Styles not applying or overriding unexpectedly
|
|
89
|
+
**Fix:** Always use `cn()` to merge classes — never concatenate strings
|
|
90
|
+
|
|
91
|
+
## Migration Commands
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npx shadcn@latest migrate radix # @radix-ui/react-* → radix-ui
|
|
95
|
+
npx shadcn@latest migrate rtl # physical → logical CSS classes
|
|
96
|
+
npx shadcn@latest migrate icons # switch icon library
|
|
97
|
+
```
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Button — shadcn/ui
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx shadcn@latest add button
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Variants
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
<Button variant="default" /> // primary — solid background
|
|
13
|
+
<Button variant="destructive" /> // danger — red
|
|
14
|
+
<Button variant="outline" /> // bordered — transparent bg
|
|
15
|
+
<Button variant="secondary" /> // muted — subtle background
|
|
16
|
+
<Button variant="ghost" /> // no border, hover only
|
|
17
|
+
<Button variant="link" /> // looks like a text link
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Sizes
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
<Button size="default" /> // h-9 px-4 py-2
|
|
24
|
+
<Button size="xs" /> // h-7 px-2.5 text-xs
|
|
25
|
+
<Button size="sm" /> // h-8 px-3
|
|
26
|
+
<Button size="lg" /> // h-10 px-6
|
|
27
|
+
<Button size="icon" /> // h-9 w-9 (square)
|
|
28
|
+
<Button size="icon-xs" /> // h-7 w-7
|
|
29
|
+
<Button size="icon-sm" /> // h-8 w-8
|
|
30
|
+
<Button size="icon-lg" /> // h-10 w-10
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Icon Usage (REQUIRED pattern)
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// data-icon adds correct spacing automatically
|
|
37
|
+
<Button>
|
|
38
|
+
<IconPlus data-icon="inline-start" />
|
|
39
|
+
Add Item
|
|
40
|
+
</Button>
|
|
41
|
+
|
|
42
|
+
<Button>
|
|
43
|
+
Settings
|
|
44
|
+
<IconChevronRight data-icon="inline-end" />
|
|
45
|
+
</Button>
|
|
46
|
+
|
|
47
|
+
// Icon-only button
|
|
48
|
+
<Button size="icon" aria-label="Settings">
|
|
49
|
+
<IconSettings />
|
|
50
|
+
</Button>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Button as Link (asChild)
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
<Button asChild>
|
|
57
|
+
<Link href="/dashboard">Go to Dashboard</Link>
|
|
58
|
+
</Button>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Loading State
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
<Button disabled>
|
|
65
|
+
<Spinner data-icon="inline-start" />
|
|
66
|
+
Saving...
|
|
67
|
+
</Button>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Button Group
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
<ButtonGroup>
|
|
74
|
+
<Button variant="outline">Left</Button>
|
|
75
|
+
<Button variant="outline">Center</Button>
|
|
76
|
+
<Button variant="outline">Right</Button>
|
|
77
|
+
</ButtonGroup>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Cursor Override (Tailwind v4)
|
|
81
|
+
|
|
82
|
+
Tailwind v4 sets `cursor: default` on buttons. If you need pointer cursor:
|
|
83
|
+
|
|
84
|
+
```css
|
|
85
|
+
/* globals.css */
|
|
86
|
+
button:not(:disabled) {
|
|
87
|
+
cursor: pointer;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Anti-patterns
|
|
92
|
+
|
|
93
|
+
- ❌ Don't merge classNames with template literals — use `cn()`
|
|
94
|
+
- ❌ Don't wrap Button in `<a>` — use `asChild` with `<Link>`
|
|
95
|
+
- ❌ Don't forget `aria-label` on icon-only buttons
|
|
96
|
+
- ❌ Don't forget `data-icon` on icons — spacing will be wrong without it
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Data Table — shadcn/ui + TanStack Table
|
|
2
|
+
|
|
3
|
+
## Stack
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx shadcn@latest add table
|
|
7
|
+
npm i @tanstack/react-table
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## File Structure
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
app/payments/
|
|
14
|
+
├── columns.tsx ← column definitions (client component)
|
|
15
|
+
├── data-table.tsx ← DataTable component (client component)
|
|
16
|
+
└── page.tsx ← server component, fetch data
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Step 1: Column Definitions
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
'use client'
|
|
23
|
+
|
|
24
|
+
import { ColumnDef } from '@tanstack/react-table'
|
|
25
|
+
|
|
26
|
+
export type Payment = {
|
|
27
|
+
id: string
|
|
28
|
+
amount: number
|
|
29
|
+
status: 'pending' | 'processing' | 'success' | 'failed'
|
|
30
|
+
email: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const columns: ColumnDef<Payment>[] = [
|
|
34
|
+
{
|
|
35
|
+
accessorKey: 'status',
|
|
36
|
+
header: 'Status',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
accessorKey: 'email',
|
|
40
|
+
header: 'Email',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
accessorKey: 'amount',
|
|
44
|
+
header: () => <div className="text-right">Amount</div>,
|
|
45
|
+
cell: ({ row }) => {
|
|
46
|
+
const formatted = new Intl.NumberFormat('en-US', {
|
|
47
|
+
style: 'currency',
|
|
48
|
+
currency: 'USD',
|
|
49
|
+
}).format(row.getValue('amount'))
|
|
50
|
+
return <div className="text-right font-medium">{formatted}</div>
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Step 2: DataTable Component
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
'use client'
|
|
60
|
+
|
|
61
|
+
import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'
|
|
62
|
+
import {
|
|
63
|
+
Table,
|
|
64
|
+
TableBody,
|
|
65
|
+
TableCell,
|
|
66
|
+
TableHead,
|
|
67
|
+
TableHeader,
|
|
68
|
+
TableRow,
|
|
69
|
+
} from '@/components/ui/table'
|
|
70
|
+
|
|
71
|
+
export function DataTable<TData, TValue>({
|
|
72
|
+
columns,
|
|
73
|
+
data,
|
|
74
|
+
}: {
|
|
75
|
+
columns: ColumnDef<TData, TValue>[]
|
|
76
|
+
data: TData[]
|
|
77
|
+
}) {
|
|
78
|
+
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() })
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Table>
|
|
82
|
+
<TableHeader>
|
|
83
|
+
{table.getHeaderGroups().map((headerGroup) => (
|
|
84
|
+
<TableRow key={headerGroup.id}>
|
|
85
|
+
{headerGroup.headers.map((header) => (
|
|
86
|
+
<TableHead key={header.id}>
|
|
87
|
+
{flexRender(header.column.columnDef.header, header.getContext())}
|
|
88
|
+
</TableHead>
|
|
89
|
+
))}
|
|
90
|
+
</TableRow>
|
|
91
|
+
))}
|
|
92
|
+
</TableHeader>
|
|
93
|
+
<TableBody>
|
|
94
|
+
{table.getRowModel().rows.map((row) => (
|
|
95
|
+
<TableRow key={row.id}>
|
|
96
|
+
{row.getVisibleCells().map((cell) => (
|
|
97
|
+
<TableCell key={cell.id}>
|
|
98
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
99
|
+
</TableCell>
|
|
100
|
+
))}
|
|
101
|
+
</TableRow>
|
|
102
|
+
))}
|
|
103
|
+
</TableBody>
|
|
104
|
+
</Table>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Progressive Features
|
|
110
|
+
|
|
111
|
+
Add these incrementally as needed:
|
|
112
|
+
|
|
113
|
+
### Pagination
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { getPaginationRowModel } from "@tanstack/react-table"
|
|
117
|
+
const table = useReactTable({ ..., getPaginationRowModel: getPaginationRowModel() })
|
|
118
|
+
// Controls: table.previousPage(), table.nextPage(), table.getCanPreviousPage()
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Sorting
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
import { getSortedRowModel, SortingState } from "@tanstack/react-table"
|
|
125
|
+
const [sorting, setSorting] = useState<SortingState>([])
|
|
126
|
+
const table = useReactTable({ ..., getSortedRowModel: getSortedRowModel(), onSortingChange: setSorting, state: { sorting } })
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Column Filtering
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import { getFilteredRowModel, ColumnFiltersState } from '@tanstack/react-table'
|
|
133
|
+
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
134
|
+
// Add onColumnFiltersChange: setColumnFilters, state: { columnFilters }
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Row Selection
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
// Add checkbox column:
|
|
141
|
+
{ id: "select", header: ({ table }) => <Checkbox checked={table.getIsAllPageRowsSelected()} onCheckedChange={v => table.toggleAllPageRowsSelected(!!v)} />, cell: ({ row }) => <Checkbox checked={row.getIsSelected()} onCheckedChange={v => row.toggleSelected(!!v)} /> }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Column Visibility
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
|
|
148
|
+
// Add DropdownMenu to toggle columns: table.getAllColumns().filter(c => c.getCanHide())
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Anti-patterns
|
|
152
|
+
|
|
153
|
+
- ❌ Don't put columns + table + page in one file — separate for maintainability
|
|
154
|
+
- ❌ Don't skip `getCoreRowModel()` — required base
|
|
155
|
+
- ❌ Don't use native `<table>` — use shadcn `<Table>` components
|
|
156
|
+
- ❌ Don't load 10k+ rows client-side — use server-side pagination
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Dialog, Sheet, Drawer, Alert Dialog — shadcn/ui
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx shadcn@latest add dialog sheet drawer alert-dialog
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Dialog (Modal)
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
<Dialog>
|
|
13
|
+
<DialogTrigger asChild>
|
|
14
|
+
<Button>Open</Button>
|
|
15
|
+
</DialogTrigger>
|
|
16
|
+
<DialogContent>
|
|
17
|
+
<DialogHeader>
|
|
18
|
+
<DialogTitle>Edit Profile</DialogTitle>
|
|
19
|
+
<DialogDescription>Make changes to your profile here.</DialogDescription>
|
|
20
|
+
</DialogHeader>
|
|
21
|
+
{/* form content */}
|
|
22
|
+
<DialogFooter>
|
|
23
|
+
<Button type="submit">Save</Button>
|
|
24
|
+
</DialogFooter>
|
|
25
|
+
</DialogContent>
|
|
26
|
+
</Dialog>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Sheet (Slide-over Panel)
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
<Sheet>
|
|
33
|
+
<SheetTrigger asChild>
|
|
34
|
+
<Button variant="outline">Open Menu</Button>
|
|
35
|
+
</SheetTrigger>
|
|
36
|
+
<SheetContent side="right">
|
|
37
|
+
{' '}
|
|
38
|
+
{/* "top" | "right" | "bottom" | "left" */}
|
|
39
|
+
<SheetHeader>
|
|
40
|
+
<SheetTitle>Menu</SheetTitle>
|
|
41
|
+
<SheetDescription>Navigation</SheetDescription>
|
|
42
|
+
</SheetHeader>
|
|
43
|
+
{/* content */}
|
|
44
|
+
</SheetContent>
|
|
45
|
+
</Sheet>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Drawer (Mobile Bottom Sheet)
|
|
49
|
+
|
|
50
|
+
Based on `vaul`. Great for mobile-first interactions.
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<Drawer>
|
|
54
|
+
<DrawerTrigger asChild>
|
|
55
|
+
<Button variant="outline">Open</Button>
|
|
56
|
+
</DrawerTrigger>
|
|
57
|
+
<DrawerContent>
|
|
58
|
+
<DrawerHeader>
|
|
59
|
+
<DrawerTitle>Settings</DrawerTitle>
|
|
60
|
+
<DrawerDescription>Adjust your preferences.</DrawerDescription>
|
|
61
|
+
</DrawerHeader>
|
|
62
|
+
{/* content */}
|
|
63
|
+
<DrawerFooter>
|
|
64
|
+
<Button>Submit</Button>
|
|
65
|
+
<DrawerClose asChild>
|
|
66
|
+
<Button variant="outline">Cancel</Button>
|
|
67
|
+
</DrawerClose>
|
|
68
|
+
</DrawerFooter>
|
|
69
|
+
</DrawerContent>
|
|
70
|
+
</Drawer>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Alert Dialog (Non-dismissable)
|
|
74
|
+
|
|
75
|
+
Cannot be closed by clicking outside or pressing Escape. For destructive confirmations.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
<AlertDialog>
|
|
79
|
+
<AlertDialogTrigger asChild>
|
|
80
|
+
<Button variant="destructive">Delete</Button>
|
|
81
|
+
</AlertDialogTrigger>
|
|
82
|
+
<AlertDialogContent>
|
|
83
|
+
<AlertDialogHeader>
|
|
84
|
+
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
|
85
|
+
<AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
|
|
86
|
+
</AlertDialogHeader>
|
|
87
|
+
<AlertDialogFooter>
|
|
88
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
89
|
+
<AlertDialogAction>Delete</AlertDialogAction>
|
|
90
|
+
</AlertDialogFooter>
|
|
91
|
+
</AlertDialogContent>
|
|
92
|
+
</AlertDialog>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Responsive: Dialog on Desktop, Drawer on Mobile
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { useMediaQuery } from '@/hooks/use-media-query'
|
|
99
|
+
|
|
100
|
+
function ResponsiveModal({ children }) {
|
|
101
|
+
const isDesktop = useMediaQuery('(min-width: 768px)')
|
|
102
|
+
|
|
103
|
+
if (isDesktop) {
|
|
104
|
+
return <Dialog>{children}</Dialog>
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return <Drawer>{children}</Drawer>
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Controlled State
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
const [open, setOpen] = useState(false)
|
|
115
|
+
<Dialog open={open} onOpenChange={setOpen}>...</Dialog>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Anti-patterns
|
|
119
|
+
|
|
120
|
+
- ❌ Don't skip `DialogTitle` — required for accessibility (screen readers)
|
|
121
|
+
- ❌ Don't nest Dialog inside Dialog — causes focus trap issues
|
|
122
|
+
- ❌ Don't use Dialog for destructive confirmations — use Alert Dialog
|
|
123
|
+
- ❌ Don't use Sheet on mobile — use Drawer instead
|