@mandujs/mcp 0.38.11 → 0.39.0-beta.1

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.
Files changed (183) hide show
  1. package/README.md +3 -3
  2. package/package.json +3 -5
  3. package/src/activity-adapter.ts +23 -23
  4. package/src/activity-monitor.ts +39 -10
  5. package/src/adapters/index.ts +20 -20
  6. package/src/adapters/monitor-adapter.ts +100 -100
  7. package/src/adapters/tool-adapter.ts +90 -90
  8. package/src/executor/index.ts +22 -22
  9. package/src/executor/tool-executor.ts +148 -148
  10. package/src/hooks/config-watcher.ts +173 -173
  11. package/src/hooks/index.ts +23 -23
  12. package/src/hooks/mcp-hooks.ts +227 -227
  13. package/src/index.ts +5 -5
  14. package/src/logging/index.ts +15 -15
  15. package/src/logging/mcp-transport.ts +134 -134
  16. package/src/new-resources.ts +2 -2
  17. package/src/profiles.ts +25 -54
  18. package/src/prompts.ts +4 -4
  19. package/src/registry/index.ts +13 -13
  20. package/src/registry/mcp-tool-registry.ts +298 -298
  21. package/src/resources/generated-skills/catalog.ts +36 -0
  22. package/src/resources/generated-skills/mandu-agent-workflow/SKILL.md +48 -0
  23. package/src/resources/generated-skills/mandu-contract/SKILL.md +20 -0
  24. package/src/resources/generated-skills/mandu-fs-routes/SKILL.md +19 -0
  25. package/src/resources/generated-skills/mandu-guard/SKILL.md +20 -0
  26. package/src/resources/generated-skills/mandu-hydration/SKILL.md +19 -0
  27. package/src/resources/generated-skills/mandu-testing/SKILL.md +20 -0
  28. package/src/resources/handlers.ts +3 -3
  29. package/src/resources/skills/guides.ts +49 -49
  30. package/src/resources/skills/index.ts +12 -12
  31. package/src/resources/skills/loader.ts +8 -35
  32. package/src/resources/skills/recipes.ts +28 -28
  33. package/src/server.ts +1 -1
  34. package/src/tools/agent.ts +479 -409
  35. package/src/tools/ate-exemplar.ts +92 -92
  36. package/src/tools/ate-flakes.ts +90 -90
  37. package/src/tools/ate-mutate.ts +103 -103
  38. package/src/tools/ate-mutation-report.ts +64 -64
  39. package/src/tools/ate-oracle-pending.ts +49 -49
  40. package/src/tools/ate-oracle-replay.ts +44 -44
  41. package/src/tools/ate-oracle-verdict.ts +70 -70
  42. package/src/tools/ate-prompt.ts +146 -146
  43. package/src/tools/ate-run.ts +1 -1
  44. package/src/tools/ate.ts +38 -38
  45. package/src/tools/brain.ts +6 -6
  46. package/src/tools/composite.ts +19 -61
  47. package/src/tools/contract.ts +11 -9
  48. package/src/tools/deploy-plan.ts +2 -2
  49. package/src/tools/deploy-preview.ts +316 -316
  50. package/src/tools/design.ts +825 -825
  51. package/src/tools/docs.ts +350 -350
  52. package/src/tools/generate.ts +4 -3
  53. package/src/tools/guard.ts +1 -1
  54. package/src/tools/history.ts +1 -1
  55. package/src/tools/hydration.ts +64 -64
  56. package/src/tools/index.ts +0 -118
  57. package/src/tools/kitchen.ts +72 -72
  58. package/src/tools/lint.ts +226 -226
  59. package/src/tools/loop-close.ts +175 -175
  60. package/src/tools/negotiate.ts +263 -263
  61. package/src/tools/resource.ts +1 -1
  62. package/src/tools/run-tests.ts +424 -424
  63. package/src/tools/runtime.ts +1 -1
  64. package/src/tools/seo.ts +1 -1
  65. package/src/tools/slot-validation.ts +19 -19
  66. package/src/tools/spec.ts +201 -201
  67. package/src/tools/transaction.ts +1 -1
  68. package/src/tx-lock.ts +73 -73
  69. package/src/utils/runtime-control.ts +52 -52
  70. package/src/utils/withWarnings.ts +1 -1
  71. package/src/resources/skills/mandu-agent-workflow/SKILL.md +0 -124
  72. package/src/resources/skills/mandu-agent-workflow/metadata.json +0 -7
  73. package/src/resources/skills/mandu-composition/SKILL.md +0 -131
  74. package/src/resources/skills/mandu-composition/metadata.json +0 -13
  75. package/src/resources/skills/mandu-composition/rules/_sections.md +0 -26
  76. package/src/resources/skills/mandu-composition/rules/_template.md +0 -77
  77. package/src/resources/skills/mandu-composition/rules/comp-arch-avoid-boolean-props.md +0 -146
  78. package/src/resources/skills/mandu-composition/rules/comp-arch-compound-components.md +0 -164
  79. package/src/resources/skills/mandu-composition/rules/comp-island-event.md +0 -161
  80. package/src/resources/skills/mandu-composition/rules/comp-island-slot-split.md +0 -167
  81. package/src/resources/skills/mandu-composition/rules/comp-pattern-children.md +0 -149
  82. package/src/resources/skills/mandu-composition/rules/comp-state-context-interface.md +0 -148
  83. package/src/resources/skills/mandu-composition/rules/comp-state-lift-state.md +0 -150
  84. package/src/resources/skills/mandu-deployment/SKILL.md +0 -135
  85. package/src/resources/skills/mandu-deployment/_sections.md +0 -41
  86. package/src/resources/skills/mandu-deployment/_template.md +0 -38
  87. package/src/resources/skills/mandu-deployment/metadata.json +0 -13
  88. package/src/resources/skills/mandu-deployment/rules/db-provider-supabase.md +0 -300
  89. package/src/resources/skills/mandu-deployment/rules/deploy-build-bun.md +0 -109
  90. package/src/resources/skills/mandu-deployment/rules/deploy-build-output.md +0 -115
  91. package/src/resources/skills/mandu-deployment/rules/deploy-cicd-github.md +0 -219
  92. package/src/resources/skills/mandu-deployment/rules/deploy-docker-bun.md +0 -150
  93. package/src/resources/skills/mandu-deployment/rules/deploy-docker-compose.md +0 -223
  94. package/src/resources/skills/mandu-deployment/rules/deploy-platform-fly.md +0 -152
  95. package/src/resources/skills/mandu-deployment/rules/deploy-platform-render.md +0 -179
  96. package/src/resources/skills/mandu-deployment/rules/deploy-platform-vercel.md +0 -140
  97. package/src/resources/skills/mandu-fs-routes/SKILL.md +0 -122
  98. package/src/resources/skills/mandu-fs-routes/metadata.json +0 -12
  99. package/src/resources/skills/mandu-fs-routes/rules/_sections.md +0 -36
  100. package/src/resources/skills/mandu-fs-routes/rules/_template.md +0 -69
  101. package/src/resources/skills/mandu-fs-routes/rules/routes-api-methods.md +0 -65
  102. package/src/resources/skills/mandu-fs-routes/rules/routes-dynamic-param.md +0 -93
  103. package/src/resources/skills/mandu-fs-routes/rules/routes-naming-page.md +0 -55
  104. package/src/resources/skills/mandu-guard/SKILL.md +0 -162
  105. package/src/resources/skills/mandu-guard/metadata.json +0 -12
  106. package/src/resources/skills/mandu-guard/rules/_sections.md +0 -36
  107. package/src/resources/skills/mandu-guard/rules/_template.md +0 -82
  108. package/src/resources/skills/mandu-guard/rules/guard-config-rules.md +0 -100
  109. package/src/resources/skills/mandu-guard/rules/guard-layer-direction.md +0 -76
  110. package/src/resources/skills/mandu-guard/rules/guard-preset-mandu.md +0 -81
  111. package/src/resources/skills/mandu-guard/rules/guard-validate-import.md +0 -80
  112. package/src/resources/skills/mandu-hydration/SKILL.md +0 -139
  113. package/src/resources/skills/mandu-hydration/metadata.json +0 -12
  114. package/src/resources/skills/mandu-hydration/rules/_sections.md +0 -31
  115. package/src/resources/skills/mandu-hydration/rules/_template.md +0 -72
  116. package/src/resources/skills/mandu-hydration/rules/hydration-data-event.md +0 -109
  117. package/src/resources/skills/mandu-hydration/rules/hydration-directive-use-client.md +0 -55
  118. package/src/resources/skills/mandu-hydration/rules/hydration-island-setup.md +0 -160
  119. package/src/resources/skills/mandu-hydration/rules/hydration-priority-visible.md +0 -91
  120. package/src/resources/skills/mandu-performance/SKILL.md +0 -125
  121. package/src/resources/skills/mandu-performance/metadata.json +0 -14
  122. package/src/resources/skills/mandu-performance/rules/_sections.md +0 -31
  123. package/src/resources/skills/mandu-performance/rules/_template.md +0 -64
  124. package/src/resources/skills/mandu-performance/rules/perf-async-defer-await.md +0 -103
  125. package/src/resources/skills/mandu-performance/rules/perf-async-parallel.md +0 -95
  126. package/src/resources/skills/mandu-performance/rules/perf-bun-file.md +0 -124
  127. package/src/resources/skills/mandu-performance/rules/perf-bun-serve.md +0 -125
  128. package/src/resources/skills/mandu-performance/rules/perf-bundle-imports.md +0 -80
  129. package/src/resources/skills/mandu-performance/rules/perf-bundle-island-lazy.md +0 -145
  130. package/src/resources/skills/mandu-performance/rules/perf-cache-react.md +0 -98
  131. package/src/resources/skills/mandu-performance/rules/perf-render-transitions.md +0 -154
  132. package/src/resources/skills/mandu-security/SKILL.md +0 -127
  133. package/src/resources/skills/mandu-security/metadata.json +0 -13
  134. package/src/resources/skills/mandu-security/rules/_sections.md +0 -31
  135. package/src/resources/skills/mandu-security/rules/_template.md +0 -74
  136. package/src/resources/skills/mandu-security/rules/sec-auth-guard.md +0 -127
  137. package/src/resources/skills/mandu-security/rules/sec-env-management.md +0 -133
  138. package/src/resources/skills/mandu-security/rules/sec-input-validate.md +0 -148
  139. package/src/resources/skills/mandu-security/rules/sec-protect-csrf.md +0 -146
  140. package/src/resources/skills/mandu-security/rules/sec-protect-headers.md +0 -138
  141. package/src/resources/skills/mandu-slot/SKILL.md +0 -125
  142. package/src/resources/skills/mandu-slot/metadata.json +0 -12
  143. package/src/resources/skills/mandu-slot/rules/_sections.md +0 -36
  144. package/src/resources/skills/mandu-slot/rules/_template.md +0 -63
  145. package/src/resources/skills/mandu-slot/rules/slot-basic-structure.md +0 -38
  146. package/src/resources/skills/mandu-slot/rules/slot-ctx-response.md +0 -56
  147. package/src/resources/skills/mandu-slot/rules/slot-guard-auth.md +0 -59
  148. package/src/resources/skills/mandu-slot/rules/slot-http-methods.md +0 -64
  149. package/src/resources/skills/mandu-styling/SKILL.md +0 -196
  150. package/src/resources/skills/mandu-styling/_sections.md +0 -43
  151. package/src/resources/skills/mandu-styling/_template.md +0 -32
  152. package/src/resources/skills/mandu-styling/metadata.json +0 -15
  153. package/src/resources/skills/mandu-styling/rules/style-component-compound.md +0 -235
  154. package/src/resources/skills/mandu-styling/rules/style-component-slots.md +0 -255
  155. package/src/resources/skills/mandu-styling/rules/style-component-tokens.md +0 -205
  156. package/src/resources/skills/mandu-styling/rules/style-island-animations.md +0 -272
  157. package/src/resources/skills/mandu-styling/rules/style-island-scoping.md +0 -167
  158. package/src/resources/skills/mandu-styling/rules/style-island-variants.md +0 -221
  159. package/src/resources/skills/mandu-styling/rules/style-perf-critical.md +0 -209
  160. package/src/resources/skills/mandu-styling/rules/style-perf-purge.md +0 -192
  161. package/src/resources/skills/mandu-styling/rules/style-setup-modules.md +0 -162
  162. package/src/resources/skills/mandu-styling/rules/style-setup-panda.md +0 -164
  163. package/src/resources/skills/mandu-styling/rules/style-setup-tailwind.md +0 -170
  164. package/src/resources/skills/mandu-styling/rules/style-tailwind-v4-gotchas.md +0 -179
  165. package/src/resources/skills/mandu-styling/rules/style-theme-darkmode.md +0 -229
  166. package/src/resources/skills/mandu-testing/SKILL.md +0 -132
  167. package/src/resources/skills/mandu-testing/metadata.json +0 -13
  168. package/src/resources/skills/mandu-testing/rules/_sections.md +0 -26
  169. package/src/resources/skills/mandu-testing/rules/_template.md +0 -65
  170. package/src/resources/skills/mandu-testing/rules/test-component-island.md +0 -195
  171. package/src/resources/skills/mandu-testing/rules/test-e2e-playwright.md +0 -196
  172. package/src/resources/skills/mandu-testing/rules/test-mock-fetch.md +0 -219
  173. package/src/resources/skills/mandu-testing/rules/test-slot-unit.md +0 -192
  174. package/src/resources/skills/mandu-ui/SKILL.md +0 -159
  175. package/src/resources/skills/mandu-ui/_sections.md +0 -23
  176. package/src/resources/skills/mandu-ui/_template.md +0 -32
  177. package/src/resources/skills/mandu-ui/metadata.json +0 -13
  178. package/src/resources/skills/mandu-ui/rules/ui-accessibility-aria.md +0 -232
  179. package/src/resources/skills/mandu-ui/rules/ui-accessibility-focus.md +0 -238
  180. package/src/resources/skills/mandu-ui/rules/ui-composition-patterns.md +0 -259
  181. package/src/resources/skills/mandu-ui/rules/ui-island-integration.md +0 -258
  182. package/src/resources/skills/mandu-ui/rules/ui-radix-patterns.md +0 -213
  183. package/src/resources/skills/mandu-ui/rules/ui-shadcn-setup.md +0 -209
@@ -1,258 +0,0 @@
1
- ---
2
- title: UI Library Island Integration
3
- impact: MEDIUM
4
- impactDescription: Proper integration of UI components with Island architecture
5
- tags: ui, island, integration, client
6
- ---
7
-
8
- ## UI Library Island Integration
9
-
10
- **Impact: MEDIUM (Proper integration of UI components with Island architecture)**
11
-
12
- UI 라이브러리 컴포넌트를 Mandu Island 아키텍처에 올바르게 통합하세요.
13
-
14
- ## "use client" 경계
15
-
16
- ```tsx
17
- // components/ui/button.tsx
18
- // shadcn/ui 컴포넌트는 이미 클라이언트 컴포넌트
19
- "use client";
20
-
21
- import { cn } from "@/lib/utils";
22
- // ...
23
- ```
24
-
25
- ```tsx
26
- // app/dashboard/page.tsx (서버 컴포넌트)
27
- import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
28
- import { DashboardActionsIsland } from "./client";
29
-
30
- export default function DashboardPage() {
31
- return (
32
- <div>
33
- {/* Card는 서버에서 렌더링 (정적 마크업) */}
34
- <Card>
35
- <CardHeader>
36
- <CardTitle>Dashboard</CardTitle>
37
- </CardHeader>
38
- <CardContent>
39
- {/* 인터랙티브 부분만 Island */}
40
- <DashboardActionsIsland />
41
- </CardContent>
42
- </Card>
43
- </div>
44
- );
45
- }
46
- ```
47
-
48
- ## 서버 데이터 → Island 전달
49
-
50
- ```tsx
51
- // app/users/page.tsx
52
- import { UserTableIsland } from "./client";
53
- import { getUsers } from "@/lib/db";
54
-
55
- export default async function UsersPage() {
56
- const users = await getUsers();
57
-
58
- return (
59
- <div>
60
- <h1>Users</h1>
61
- {/* 초기 데이터를 props로 전달 */}
62
- <UserTableIsland initialUsers={users} />
63
- </div>
64
- );
65
- }
66
- ```
67
-
68
- ```tsx
69
- // app/users/client.tsx
70
- "use client";
71
-
72
- import { useState } from "react";
73
- import {
74
- Table,
75
- TableBody,
76
- TableCell,
77
- TableHead,
78
- TableHeader,
79
- TableRow,
80
- } from "@/components/ui/table";
81
- import { Button } from "@/components/ui/button";
82
-
83
- export function UserTableIsland({ initialUsers }) {
84
- const [users, setUsers] = useState(initialUsers);
85
-
86
- const handleDelete = async (id: string) => {
87
- await fetch(`/api/users/${id}`, { method: "DELETE" });
88
- setUsers(users.filter(u => u.id !== id));
89
- };
90
-
91
- return (
92
- <Table>
93
- <TableHeader>
94
- <TableRow>
95
- <TableHead>Name</TableHead>
96
- <TableHead>Email</TableHead>
97
- <TableHead>Actions</TableHead>
98
- </TableRow>
99
- </TableHeader>
100
- <TableBody>
101
- {users.map(user => (
102
- <TableRow key={user.id}>
103
- <TableCell>{user.name}</TableCell>
104
- <TableCell>{user.email}</TableCell>
105
- <TableCell>
106
- <Button
107
- variant="destructive"
108
- size="sm"
109
- onClick={() => handleDelete(user.id)}
110
- >
111
- Delete
112
- </Button>
113
- </TableCell>
114
- </TableRow>
115
- ))}
116
- </TableBody>
117
- </Table>
118
- );
119
- }
120
- ```
121
-
122
- ## Dialog/Modal Island 패턴
123
-
124
- ```tsx
125
- // app/users/client.tsx
126
- "use client";
127
-
128
- import { useState } from "react";
129
- import { Button } from "@/components/ui/button";
130
- import {
131
- Dialog,
132
- DialogContent,
133
- DialogHeader,
134
- DialogTitle,
135
- DialogTrigger,
136
- } from "@/components/ui/dialog";
137
-
138
- export function CreateUserIsland() {
139
- const [open, setOpen] = useState(false);
140
-
141
- const handleSubmit = async (data: FormData) => {
142
- await fetch("/api/users", {
143
- method: "POST",
144
- body: data,
145
- });
146
- setOpen(false);
147
- };
148
-
149
- return (
150
- <Dialog open={open} onOpenChange={setOpen}>
151
- <DialogTrigger asChild>
152
- <Button>Add User</Button>
153
- </DialogTrigger>
154
- <DialogContent>
155
- <DialogHeader>
156
- <DialogTitle>Create New User</DialogTitle>
157
- </DialogHeader>
158
- <form action={handleSubmit}>
159
- {/* Form fields */}
160
- </form>
161
- </DialogContent>
162
- </Dialog>
163
- );
164
- }
165
- ```
166
-
167
- ## Toast Island
168
-
169
- ```tsx
170
- // app/layout.tsx
171
- import { Toaster } from "@/components/ui/toaster";
172
-
173
- export default function RootLayout({ children }) {
174
- return (
175
- <html>
176
- <body>
177
- {children}
178
- {/* Toast는 전역 Island */}
179
- <Toaster />
180
- </body>
181
- </html>
182
- );
183
- }
184
- ```
185
-
186
- ```tsx
187
- // app/form/client.tsx
188
- "use client";
189
-
190
- import { useToast } from "@/components/ui/use-toast";
191
-
192
- export function FormIsland() {
193
- const { toast } = useToast();
194
-
195
- const handleSubmit = async () => {
196
- try {
197
- await submitForm();
198
- toast({
199
- title: "Success",
200
- description: "Form submitted successfully",
201
- });
202
- } catch {
203
- toast({
204
- title: "Error",
205
- description: "Something went wrong",
206
- variant: "destructive",
207
- });
208
- }
209
- };
210
-
211
- // ...
212
- }
213
- ```
214
-
215
- ## Island 간 UI 상태 공유
216
-
217
- ```tsx
218
- // app/sidebar/client.tsx
219
- "use client";
220
-
221
- import { useIslandEvent } from "@mandujs/core/client";
222
-
223
- export function SidebarIsland() {
224
- const [isOpen, setIsOpen] = useState(true);
225
-
226
- useIslandEvent("toggle-sidebar", () => {
227
- setIsOpen(prev => !prev);
228
- });
229
-
230
- return (
231
- <aside className={cn(isOpen ? "w-64" : "w-0", "transition-all")}>
232
- {/* Sidebar content */}
233
- </aside>
234
- );
235
- }
236
- ```
237
-
238
- ```tsx
239
- // app/header/client.tsx
240
- "use client";
241
-
242
- import { useIslandEvent } from "@mandujs/core/client";
243
- import { Button } from "@/components/ui/button";
244
-
245
- export function HeaderIsland() {
246
- const { emit } = useIslandEvent("toggle-sidebar");
247
-
248
- return (
249
- <header>
250
- <Button variant="ghost" size="icon" onClick={() => emit({})}>
251
- <MenuIcon />
252
- </Button>
253
- </header>
254
- );
255
- }
256
- ```
257
-
258
- Reference: [React Server Components](https://react.dev/reference/rsc/server-components)
@@ -1,213 +0,0 @@
1
- ---
2
- title: Radix UI Patterns
3
- impact: HIGH
4
- impactDescription: Accessible headless primitives for custom components
5
- tags: ui, radix, headless, primitives
6
- ---
7
-
8
- ## Radix UI Patterns
9
-
10
- **Impact: HIGH (Accessible headless primitives for custom components)**
11
-
12
- Radix UI를 직접 사용하여 커스텀 컴포넌트를 만들 때의 패턴입니다.
13
-
14
- **설치:**
15
-
16
- ```bash
17
- # 개별 패키지
18
- bun add @radix-ui/react-dialog
19
- bun add @radix-ui/react-dropdown-menu
20
- bun add @radix-ui/react-popover
21
- bun add @radix-ui/react-tabs
22
- bun add @radix-ui/react-tooltip
23
- ```
24
-
25
- ## Dialog 커스텀 구현
26
-
27
- ```tsx
28
- // components/ui/custom-dialog.tsx
29
- "use client";
30
-
31
- import * as DialogPrimitive from "@radix-ui/react-dialog";
32
- import { X } from "lucide-react";
33
- import { cn } from "@/lib/utils";
34
-
35
- const Dialog = DialogPrimitive.Root;
36
- const DialogTrigger = DialogPrimitive.Trigger;
37
- const DialogPortal = DialogPrimitive.Portal;
38
- const DialogClose = DialogPrimitive.Close;
39
-
40
- const DialogOverlay = ({ className, ...props }) => (
41
- <DialogPrimitive.Overlay
42
- className={cn(
43
- "fixed inset-0 z-50 bg-black/80",
44
- "data-[state=open]:animate-in data-[state=closed]:animate-out",
45
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
46
- className
47
- )}
48
- {...props}
49
- />
50
- );
51
-
52
- const DialogContent = ({ className, children, ...props }) => (
53
- <DialogPortal>
54
- <DialogOverlay />
55
- <DialogPrimitive.Content
56
- className={cn(
57
- "fixed left-1/2 top-1/2 z-50 w-full max-w-lg -translate-x-1/2 -translate-y-1/2",
58
- "rounded-lg border bg-background p-6 shadow-lg",
59
- "data-[state=open]:animate-in data-[state=closed]:animate-out",
60
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
61
- "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
62
- className
63
- )}
64
- {...props}
65
- >
66
- {children}
67
- <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100">
68
- <X className="h-4 w-4" />
69
- <span className="sr-only">Close</span>
70
- </DialogPrimitive.Close>
71
- </DialogPrimitive.Content>
72
- </DialogPortal>
73
- );
74
-
75
- export { Dialog, DialogTrigger, DialogContent, DialogClose };
76
- ```
77
-
78
- ## Dropdown Menu 패턴
79
-
80
- ```tsx
81
- // components/ui/custom-dropdown.tsx
82
- "use client";
83
-
84
- import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
85
- import { cn } from "@/lib/utils";
86
-
87
- const DropdownMenu = DropdownMenuPrimitive.Root;
88
- const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
89
-
90
- const DropdownMenuContent = ({ className, sideOffset = 4, ...props }) => (
91
- <DropdownMenuPrimitive.Portal>
92
- <DropdownMenuPrimitive.Content
93
- sideOffset={sideOffset}
94
- className={cn(
95
- "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 shadow-md",
96
- "data-[state=open]:animate-in data-[state=closed]:animate-out",
97
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
98
- "data-[side=bottom]:slide-in-from-top-2",
99
- "data-[side=top]:slide-in-from-bottom-2",
100
- className
101
- )}
102
- {...props}
103
- />
104
- </DropdownMenuPrimitive.Portal>
105
- );
106
-
107
- const DropdownMenuItem = ({ className, ...props }) => (
108
- <DropdownMenuPrimitive.Item
109
- className={cn(
110
- "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none",
111
- "focus:bg-accent focus:text-accent-foreground",
112
- "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
113
- className
114
- )}
115
- {...props}
116
- />
117
- );
118
-
119
- export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem };
120
- ```
121
-
122
- ## Island에서 사용
123
-
124
- ```tsx
125
- // app/user-menu/client.tsx
126
- "use client";
127
-
128
- import {
129
- DropdownMenu,
130
- DropdownMenuTrigger,
131
- DropdownMenuContent,
132
- DropdownMenuItem,
133
- } from "@/components/ui/custom-dropdown";
134
-
135
- export function UserMenuIsland({ user }: { user: { name: string; avatar: string } }) {
136
- return (
137
- <DropdownMenu>
138
- <DropdownMenuTrigger asChild>
139
- <button className="flex items-center gap-2 rounded-full p-1 hover:bg-accent">
140
- <img src={user.avatar} alt="" className="h-8 w-8 rounded-full" />
141
- <span className="sr-only">User menu</span>
142
- </button>
143
- </DropdownMenuTrigger>
144
-
145
- <DropdownMenuContent align="end">
146
- <DropdownMenuItem onSelect={() => navigate("/profile")}>
147
- Profile
148
- </DropdownMenuItem>
149
- <DropdownMenuItem onSelect={() => navigate("/settings")}>
150
- Settings
151
- </DropdownMenuItem>
152
- <DropdownMenuItem
153
- onSelect={() => logout()}
154
- className="text-destructive"
155
- >
156
- Logout
157
- </DropdownMenuItem>
158
- </DropdownMenuContent>
159
- </DropdownMenu>
160
- );
161
- }
162
- ```
163
-
164
- ## Tooltip 패턴
165
-
166
- ```tsx
167
- // components/ui/tooltip.tsx
168
- "use client";
169
-
170
- import * as TooltipPrimitive from "@radix-ui/react-tooltip";
171
- import { cn } from "@/lib/utils";
172
-
173
- const TooltipProvider = TooltipPrimitive.Provider;
174
- const Tooltip = TooltipPrimitive.Root;
175
- const TooltipTrigger = TooltipPrimitive.Trigger;
176
-
177
- const TooltipContent = ({ className, sideOffset = 4, ...props }) => (
178
- <TooltipPrimitive.Content
179
- sideOffset={sideOffset}
180
- className={cn(
181
- "z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm shadow-md",
182
- "animate-in fade-in-0 zoom-in-95",
183
- className
184
- )}
185
- {...props}
186
- />
187
- );
188
-
189
- // 사용
190
- <TooltipProvider>
191
- <Tooltip>
192
- <TooltipTrigger asChild>
193
- <button>Hover me</button>
194
- </TooltipTrigger>
195
- <TooltipContent>
196
- <p>Tooltip text</p>
197
- </TooltipContent>
198
- </Tooltip>
199
- </TooltipProvider>
200
- ```
201
-
202
- ## data-state 스타일링
203
-
204
- ```css
205
- /* Radix는 상태를 data-* 속성으로 노출 */
206
- [data-state="open"] { /* 열림 상태 */ }
207
- [data-state="closed"] { /* 닫힘 상태 */ }
208
- [data-state="active"] { /* 활성 상태 */ }
209
- [data-disabled] { /* 비활성화 */ }
210
- [data-highlighted] { /* 키보드 포커스 */ }
211
- ```
212
-
213
- Reference: [Radix UI Documentation](https://www.radix-ui.com/primitives)
@@ -1,209 +0,0 @@
1
- ---
2
- title: shadcn/ui Setup
3
- impact: HIGH
4
- impactDescription: Production-ready UI components with full customization
5
- tags: ui, shadcn, setup, components
6
- ---
7
-
8
- ## shadcn/ui Setup
9
-
10
- **Impact: HIGH (Production-ready UI components with full customization)**
11
-
12
- shadcn/ui를 Mandu 프로젝트에 설정하세요. 컴포넌트를 직접 소유하고 커스터마이징할 수 있습니다.
13
-
14
- **초기화:**
15
-
16
- ```bash
17
- bunx shadcn-ui@latest init
18
- ```
19
-
20
- **선택 옵션:**
21
- ```
22
- ✔ TypeScript: yes
23
- ✔ Style: Default (또는 New York)
24
- ✔ Base color: Slate
25
- ✔ Global CSS: app/globals.css
26
- ✔ CSS variables: yes
27
- ✔ tailwind.config: tailwind.config.ts
28
- ✔ Components alias: @/components
29
- ✔ Utils alias: @/lib/utils
30
- ```
31
-
32
- ## 컴포넌트 추가
33
-
34
- ```bash
35
- # 자주 사용하는 컴포넌트
36
- bunx shadcn-ui@latest add button
37
- bunx shadcn-ui@latest add input
38
- bunx shadcn-ui@latest add card
39
- bunx shadcn-ui@latest add dialog
40
- bunx shadcn-ui@latest add dropdown-menu
41
- bunx shadcn-ui@latest add form
42
- bunx shadcn-ui@latest add toast
43
-
44
- # 한 번에 여러 개
45
- bunx shadcn-ui@latest add button input card dialog
46
- ```
47
-
48
- ## 생성된 구조
49
-
50
- ```
51
- components/
52
- └── ui/
53
- ├── button.tsx # 직접 수정 가능
54
- ├── input.tsx
55
- ├── card.tsx
56
- └── ...
57
-
58
- lib/
59
- └── utils.ts # cn() 함수
60
- ```
61
-
62
- ## 컴포넌트 커스터마이징
63
-
64
- ```tsx
65
- // components/ui/button.tsx
66
- import { cva, type VariantProps } from "class-variance-authority";
67
-
68
- const buttonVariants = cva(
69
- "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
70
- {
71
- variants: {
72
- variant: {
73
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
74
- destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
75
- outline: "border border-input bg-background hover:bg-accent",
76
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
77
- ghost: "hover:bg-accent hover:text-accent-foreground",
78
- link: "text-primary underline-offset-4 hover:underline",
79
- // 커스텀 variant 추가
80
- mandu: "bg-blue-500 text-white hover:bg-blue-600",
81
- },
82
- size: {
83
- default: "h-10 px-4 py-2",
84
- sm: "h-9 rounded-md px-3",
85
- lg: "h-11 rounded-md px-8",
86
- icon: "h-10 w-10",
87
- // 커스텀 size 추가
88
- xl: "h-14 rounded-lg px-10 text-lg",
89
- },
90
- },
91
- defaultVariants: {
92
- variant: "default",
93
- size: "default",
94
- },
95
- }
96
- );
97
-
98
- // ... 컴포넌트 코드
99
- ```
100
-
101
- ## Island에서 사용
102
-
103
- ```tsx
104
- // app/actions/client.tsx
105
- "use client";
106
-
107
- import { Button } from "@/components/ui/button";
108
- import { useState } from "react";
109
-
110
- export function ActionButtonsIsland() {
111
- const [loading, setLoading] = useState(false);
112
-
113
- const handleAction = async () => {
114
- setLoading(true);
115
- try {
116
- await doSomething();
117
- } finally {
118
- setLoading(false);
119
- }
120
- };
121
-
122
- return (
123
- <div className="flex gap-2">
124
- <Button onClick={handleAction} disabled={loading}>
125
- {loading ? "Processing..." : "Submit"}
126
- </Button>
127
- <Button variant="outline">Cancel</Button>
128
- </div>
129
- );
130
- }
131
- ```
132
-
133
- ## Form 통합
134
-
135
- ```bash
136
- bunx shadcn-ui@latest add form
137
- bun add react-hook-form @hookform/resolvers zod
138
- ```
139
-
140
- ```tsx
141
- // app/contact/client.tsx
142
- "use client";
143
-
144
- import { useForm } from "react-hook-form";
145
- import { zodResolver } from "@hookform/resolvers/zod";
146
- import { z } from "zod";
147
- import { Button } from "@/components/ui/button";
148
- import {
149
- Form,
150
- FormControl,
151
- FormField,
152
- FormItem,
153
- FormLabel,
154
- FormMessage,
155
- } from "@/components/ui/form";
156
- import { Input } from "@/components/ui/input";
157
-
158
- const formSchema = z.object({
159
- email: z.string().email("Invalid email"),
160
- message: z.string().min(10, "Minimum 10 characters"),
161
- });
162
-
163
- export function ContactFormIsland() {
164
- const form = useForm<z.infer<typeof formSchema>>({
165
- resolver: zodResolver(formSchema),
166
- defaultValues: { email: "", message: "" },
167
- });
168
-
169
- const onSubmit = async (values: z.infer<typeof formSchema>) => {
170
- await fetch("/api/contact", {
171
- method: "POST",
172
- body: JSON.stringify(values),
173
- });
174
- };
175
-
176
- return (
177
- <Form {...form}>
178
- <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
179
- <FormField
180
- control={form.control}
181
- name="email"
182
- render={({ field }) => (
183
- <FormItem>
184
- <FormLabel>Email</FormLabel>
185
- <FormControl>
186
- <Input placeholder="you@example.com" {...field} />
187
- </FormControl>
188
- <FormMessage />
189
- </FormItem>
190
- )}
191
- />
192
- <Button type="submit">Send</Button>
193
- </form>
194
- </Form>
195
- );
196
- }
197
- ```
198
-
199
- ## 업데이트
200
-
201
- ```bash
202
- # 컴포넌트 업데이트 (덮어쓰기 주의!)
203
- bunx shadcn-ui@latest add button --overwrite
204
-
205
- # diff 확인 후 수동 병합 권장
206
- bunx shadcn-ui@latest diff button
207
- ```
208
-
209
- Reference: [shadcn/ui Documentation](https://ui.shadcn.com/docs)