@mandujs/mcp 0.12.2 → 0.13.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/README.md +367 -367
- package/package.json +2 -2
- package/src/activity-monitor.ts +847 -847
- package/src/adapters/index.ts +20 -20
- package/src/adapters/monitor-adapter.ts +100 -100
- package/src/adapters/tool-adapter.ts +88 -88
- package/src/executor/error-handler.ts +250 -250
- package/src/executor/index.ts +22 -22
- package/src/executor/tool-executor.ts +148 -148
- package/src/hooks/config-watcher.ts +174 -174
- package/src/hooks/index.ts +23 -23
- package/src/hooks/mcp-hooks.ts +227 -227
- package/src/index.ts +106 -106
- package/src/logging/index.ts +15 -15
- package/src/logging/mcp-transport.ts +134 -134
- package/src/registry/index.ts +13 -13
- package/src/registry/mcp-tool-registry.ts +298 -298
- package/src/resources/skills/guides.ts +1136 -1136
- package/src/resources/skills/index.ts +12 -12
- package/src/resources/skills/loader.ts +218 -218
- package/src/resources/skills/mandu-composition/SKILL.md +91 -91
- package/src/resources/skills/mandu-composition/metadata.json +13 -13
- package/src/resources/skills/mandu-composition/rules/_sections.md +26 -26
- package/src/resources/skills/mandu-composition/rules/_template.md +77 -77
- package/src/resources/skills/mandu-composition/rules/comp-arch-avoid-boolean-props.md +146 -146
- package/src/resources/skills/mandu-composition/rules/comp-arch-compound-components.md +164 -164
- package/src/resources/skills/mandu-composition/rules/comp-island-event.md +161 -161
- package/src/resources/skills/mandu-composition/rules/comp-island-slot-split.md +167 -167
- package/src/resources/skills/mandu-composition/rules/comp-pattern-children.md +149 -149
- package/src/resources/skills/mandu-composition/rules/comp-state-context-interface.md +148 -148
- package/src/resources/skills/mandu-composition/rules/comp-state-lift-state.md +150 -150
- package/src/resources/skills/mandu-deployment/SKILL.md +92 -92
- package/src/resources/skills/mandu-deployment/_sections.md +41 -41
- package/src/resources/skills/mandu-deployment/_template.md +38 -38
- package/src/resources/skills/mandu-deployment/metadata.json +13 -13
- package/src/resources/skills/mandu-deployment/rules/deploy-build-bun.md +109 -109
- package/src/resources/skills/mandu-deployment/rules/deploy-build-output.md +115 -115
- package/src/resources/skills/mandu-deployment/rules/deploy-cicd-github.md +219 -219
- package/src/resources/skills/mandu-deployment/rules/deploy-docker-bun.md +150 -150
- package/src/resources/skills/mandu-deployment/rules/deploy-docker-compose.md +223 -223
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-fly.md +152 -152
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-render.md +179 -179
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-supabase.md +323 -323
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-vercel.md +140 -140
- package/src/resources/skills/mandu-fs-routes/SKILL.md +82 -82
- package/src/resources/skills/mandu-fs-routes/metadata.json +12 -12
- package/src/resources/skills/mandu-fs-routes/rules/_sections.md +36 -36
- package/src/resources/skills/mandu-fs-routes/rules/_template.md +69 -69
- package/src/resources/skills/mandu-fs-routes/rules/routes-api-methods.md +65 -65
- package/src/resources/skills/mandu-fs-routes/rules/routes-dynamic-param.md +93 -93
- package/src/resources/skills/mandu-fs-routes/rules/routes-naming-page.md +55 -55
- package/src/resources/skills/mandu-guard/SKILL.md +129 -129
- package/src/resources/skills/mandu-guard/metadata.json +12 -12
- package/src/resources/skills/mandu-guard/rules/_sections.md +36 -36
- package/src/resources/skills/mandu-guard/rules/_template.md +82 -82
- package/src/resources/skills/mandu-guard/rules/guard-config-rules.md +100 -100
- package/src/resources/skills/mandu-guard/rules/guard-layer-direction.md +76 -76
- package/src/resources/skills/mandu-guard/rules/guard-preset-mandu.md +81 -81
- package/src/resources/skills/mandu-guard/rules/guard-validate-import.md +80 -80
- package/src/resources/skills/mandu-hydration/SKILL.md +91 -91
- package/src/resources/skills/mandu-hydration/metadata.json +12 -12
- package/src/resources/skills/mandu-hydration/rules/_sections.md +31 -31
- package/src/resources/skills/mandu-hydration/rules/_template.md +72 -72
- package/src/resources/skills/mandu-hydration/rules/hydration-data-event.md +109 -109
- package/src/resources/skills/mandu-hydration/rules/hydration-directive-use-client.md +55 -55
- package/src/resources/skills/mandu-hydration/rules/hydration-island-setup.md +113 -113
- package/src/resources/skills/mandu-hydration/rules/hydration-priority-visible.md +68 -68
- package/src/resources/skills/mandu-performance/SKILL.md +85 -85
- package/src/resources/skills/mandu-performance/metadata.json +14 -14
- package/src/resources/skills/mandu-performance/rules/_sections.md +31 -31
- package/src/resources/skills/mandu-performance/rules/_template.md +64 -64
- package/src/resources/skills/mandu-performance/rules/perf-async-defer-await.md +103 -103
- package/src/resources/skills/mandu-performance/rules/perf-async-parallel.md +95 -95
- package/src/resources/skills/mandu-performance/rules/perf-bun-file.md +124 -124
- package/src/resources/skills/mandu-performance/rules/perf-bun-serve.md +125 -125
- package/src/resources/skills/mandu-performance/rules/perf-bundle-imports.md +80 -80
- package/src/resources/skills/mandu-performance/rules/perf-bundle-island-lazy.md +145 -145
- package/src/resources/skills/mandu-performance/rules/perf-cache-react.md +98 -98
- package/src/resources/skills/mandu-performance/rules/perf-render-transitions.md +154 -154
- package/src/resources/skills/mandu-security/SKILL.md +87 -87
- package/src/resources/skills/mandu-security/metadata.json +13 -13
- package/src/resources/skills/mandu-security/rules/_sections.md +31 -31
- package/src/resources/skills/mandu-security/rules/_template.md +74 -74
- package/src/resources/skills/mandu-security/rules/sec-auth-guard.md +127 -127
- package/src/resources/skills/mandu-security/rules/sec-env-management.md +133 -133
- package/src/resources/skills/mandu-security/rules/sec-input-validate.md +148 -148
- package/src/resources/skills/mandu-security/rules/sec-protect-csrf.md +146 -146
- package/src/resources/skills/mandu-security/rules/sec-protect-headers.md +138 -138
- package/src/resources/skills/mandu-slot/SKILL.md +85 -85
- package/src/resources/skills/mandu-slot/metadata.json +12 -12
- package/src/resources/skills/mandu-slot/rules/_sections.md +36 -36
- package/src/resources/skills/mandu-slot/rules/_template.md +63 -63
- package/src/resources/skills/mandu-slot/rules/slot-basic-structure.md +38 -38
- package/src/resources/skills/mandu-slot/rules/slot-ctx-response.md +56 -56
- package/src/resources/skills/mandu-slot/rules/slot-guard-auth.md +59 -59
- package/src/resources/skills/mandu-slot/rules/slot-http-methods.md +64 -64
- package/src/resources/skills/mandu-styling/SKILL.md +154 -154
- package/src/resources/skills/mandu-styling/_sections.md +43 -43
- package/src/resources/skills/mandu-styling/_template.md +32 -32
- package/src/resources/skills/mandu-styling/metadata.json +15 -15
- package/src/resources/skills/mandu-styling/rules/style-component-compound.md +235 -235
- package/src/resources/skills/mandu-styling/rules/style-component-slots.md +255 -255
- package/src/resources/skills/mandu-styling/rules/style-component-tokens.md +205 -205
- package/src/resources/skills/mandu-styling/rules/style-island-animations.md +272 -272
- package/src/resources/skills/mandu-styling/rules/style-island-scoping.md +167 -167
- package/src/resources/skills/mandu-styling/rules/style-island-variants.md +221 -221
- package/src/resources/skills/mandu-styling/rules/style-perf-critical.md +209 -209
- package/src/resources/skills/mandu-styling/rules/style-perf-purge.md +192 -192
- package/src/resources/skills/mandu-styling/rules/style-setup-modules.md +162 -162
- package/src/resources/skills/mandu-styling/rules/style-setup-panda.md +164 -164
- package/src/resources/skills/mandu-styling/rules/style-setup-tailwind.md +170 -170
- package/src/resources/skills/mandu-styling/rules/style-tailwind-v4-gotchas.md +179 -179
- package/src/resources/skills/mandu-styling/rules/style-theme-darkmode.md +229 -229
- package/src/resources/skills/mandu-testing/SKILL.md +99 -99
- package/src/resources/skills/mandu-testing/metadata.json +13 -13
- package/src/resources/skills/mandu-testing/rules/_sections.md +26 -26
- package/src/resources/skills/mandu-testing/rules/_template.md +65 -65
- package/src/resources/skills/mandu-testing/rules/test-component-island.md +195 -195
- package/src/resources/skills/mandu-testing/rules/test-e2e-playwright.md +196 -196
- package/src/resources/skills/mandu-testing/rules/test-mock-fetch.md +219 -219
- package/src/resources/skills/mandu-testing/rules/test-slot-unit.md +192 -192
- package/src/resources/skills/mandu-ui/SKILL.md +117 -117
- package/src/resources/skills/mandu-ui/_sections.md +23 -23
- package/src/resources/skills/mandu-ui/_template.md +32 -32
- package/src/resources/skills/mandu-ui/metadata.json +13 -13
- package/src/resources/skills/mandu-ui/rules/ui-accessibility-aria.md +232 -232
- package/src/resources/skills/mandu-ui/rules/ui-accessibility-focus.md +238 -238
- package/src/resources/skills/mandu-ui/rules/ui-composition-patterns.md +259 -259
- package/src/resources/skills/mandu-ui/rules/ui-island-integration.md +258 -258
- package/src/resources/skills/mandu-ui/rules/ui-radix-patterns.md +213 -213
- package/src/resources/skills/mandu-ui/rules/ui-shadcn-setup.md +209 -209
- package/src/resources/skills/recipes.ts +932 -932
- package/src/tools/generate.ts +7 -4
- package/src/tools/guard.ts +17 -4
- package/src/tools/hydration.ts +10 -10
- package/src/tools/project.ts +334 -334
- package/src/tools/runtime.ts +497 -497
- package/src/tools/seo.ts +417 -417
- package/src/tools/spec.ts +80 -159
- package/src/utils/project.ts +22 -12
- package/src/utils/withWarnings.ts +83 -83
|
@@ -1,213 +1,213 @@
|
|
|
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
|
+
---
|
|
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)
|