Skip to content

Commit c67d508

Browse files
committed
feat(docs-ai): add fade-in motion to tool renderer outputs
1 parent ecfd65d commit c67d508

File tree

1 file changed

+99
-57
lines changed

1 file changed

+99
-57
lines changed

docs/components/ai-panel/tool-result-renderer.tsx

Lines changed: 99 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { ComponentPreview } from "@/components/component-preview";
44
import Link from "next/link";
55
import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock";
66
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
7+
import { m } from "motion/react";
8+
import type { ReactNode } from "react";
79
import { ProgressCircle } from "seed-design/ui/progress-circle";
810

911
interface ToolResultRendererProps {
@@ -25,6 +27,10 @@ const INSTALL_COMMANDS = [
2527
{ manager: "pnpm", commandPrefix: "pnpm dlx" },
2628
{ manager: "bun", commandPrefix: "bunx" },
2729
] as const;
30+
const TOOL_FADE_IN_TRANSITION = {
31+
duration: 0.2,
32+
ease: [0.22, 1, 0.36, 1] as const,
33+
};
2834

2935
function ToolLoading({ label }: { label: string }) {
3036
return (
@@ -35,6 +41,14 @@ function ToolLoading({ label }: { label: string }) {
3541
);
3642
}
3743

44+
function ToolFadeIn({ children }: { children: ReactNode }) {
45+
return (
46+
<m.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={TOOL_FADE_IN_TRANSITION}>
47+
{children}
48+
</m.div>
49+
);
50+
}
51+
3852
function getRelatedLinks(output: unknown): RelatedLink[] {
3953
if (!output || typeof output !== "object") return [];
4054

@@ -84,14 +98,20 @@ function getToolOutputCode(output: unknown): { code: string; language: string }
8498
export function ToolResultRenderer({ toolName, input, state, output }: ToolResultRendererProps) {
8599
// 아직 입력이 완전하지 않으면 로딩 표시
86100
if (state === "input-streaming") {
87-
return <ToolLoading label="처리 중..." />;
101+
return (
102+
<ToolFadeIn>
103+
<ToolLoading label="처리 중..." />
104+
</ToolFadeIn>
105+
);
88106
}
89107

90108
switch (toolName) {
91109
case "showComponentExample": {
92110
if (typeof input.name !== "string") {
93111
return (
94-
<div className="my-1 text-xs text-fd-muted-foreground">잘못된 미리보기 입력입니다.</div>
112+
<ToolFadeIn>
113+
<div className="my-1 text-xs text-fd-muted-foreground">잘못된 미리보기 입력입니다.</div>
114+
</ToolFadeIn>
95115
);
96116
}
97117

@@ -102,70 +122,86 @@ export function ToolResultRenderer({ toolName, input, state, output }: ToolResul
102122
const isCodeLoading = state === "input-available" && !code;
103123

104124
return (
105-
<div className="my-2">
106-
<Tabs items={["미리보기", "코드"]}>
107-
<Tab value="미리보기">
108-
<div className="flex min-h-80">
109-
<ComponentPreview name={input.name} />
110-
</div>
111-
</Tab>
112-
<Tab value="코드">
113-
{code && <DynamicCodeBlock lang={language} code={code} />}
114-
{isCodeLoading && <ToolLoading label="예시 코드를 불러오는 중..." />}
115-
{!code && !isCodeLoading && (
116-
<div className="text-xs text-fd-muted-foreground">예시 코드를 찾지 못했어요.</div>
117-
)}
118-
</Tab>
119-
</Tabs>
120-
</div>
125+
<ToolFadeIn>
126+
<div className="my-2">
127+
<Tabs items={["미리보기", "코드"]}>
128+
<Tab value="미리보기">
129+
<div className="flex min-h-80">
130+
<ComponentPreview name={input.name} />
131+
</div>
132+
</Tab>
133+
<Tab value="코드">
134+
{code && <DynamicCodeBlock lang={language} code={code} />}
135+
{isCodeLoading && <ToolLoading label="예시 코드를 불러오는 중..." />}
136+
{!code && !isCodeLoading && (
137+
<div className="text-xs text-fd-muted-foreground">예시 코드를 찾지 못했어요.</div>
138+
)}
139+
</Tab>
140+
</Tabs>
141+
</div>
142+
</ToolFadeIn>
121143
);
122144
}
123145

124146
case "showInstallation": {
125147
if (typeof input.name !== "string") {
126-
return <div className="my-1 text-xs text-fd-muted-foreground">잘못된 설치 입력입니다.</div>;
148+
return (
149+
<ToolFadeIn>
150+
<div className="my-1 text-xs text-fd-muted-foreground">잘못된 설치 입력입니다.</div>
151+
</ToolFadeIn>
152+
);
127153
}
128154
const componentName = input.name;
129155

130156
return (
131-
<div className="my-2">
132-
<Tabs items={INSTALL_COMMANDS.map(({ manager }) => manager)}>
133-
{INSTALL_COMMANDS.map(({ manager, commandPrefix }) => (
134-
<Tab key={manager} value={manager}>
135-
<DynamicCodeBlock
136-
lang="bash"
137-
code={`${commandPrefix} @seed-design/cli@latest add ${componentName}`}
138-
/>
139-
</Tab>
140-
))}
141-
</Tabs>
142-
</div>
157+
<ToolFadeIn>
158+
<div className="my-2">
159+
<Tabs items={INSTALL_COMMANDS.map(({ manager }) => manager)}>
160+
{INSTALL_COMMANDS.map(({ manager, commandPrefix }) => (
161+
<Tab key={manager} value={manager}>
162+
<DynamicCodeBlock
163+
lang="bash"
164+
code={`${commandPrefix} @seed-design/cli@latest add ${componentName}`}
165+
/>
166+
</Tab>
167+
))}
168+
</Tabs>
169+
</div>
170+
</ToolFadeIn>
143171
);
144172
}
145173

146174
case "showCodeBlock":
147175
if (typeof input.code !== "string") {
148176
return (
149-
<div className="my-1 text-xs text-fd-muted-foreground">
150-
코드 블록 입력이 올바르지 않습니다.
151-
</div>
177+
<ToolFadeIn>
178+
<div className="my-1 text-xs text-fd-muted-foreground">
179+
코드 블록 입력이 올바르지 않습니다.
180+
</div>
181+
</ToolFadeIn>
152182
);
153183
}
154184
return (
155-
<div className="my-2">
156-
{typeof input.title === "string" && (
157-
<div className="text-xs font-medium text-fd-muted-foreground mb-1">{input.title}</div>
158-
)}
159-
<DynamicCodeBlock
160-
lang={typeof input.language === "string" ? input.language : "tsx"}
161-
code={input.code}
162-
/>
163-
</div>
185+
<ToolFadeIn>
186+
<div className="my-2">
187+
{typeof input.title === "string" && (
188+
<div className="text-xs font-medium text-fd-muted-foreground mb-1">{input.title}</div>
189+
)}
190+
<DynamicCodeBlock
191+
lang={typeof input.language === "string" ? input.language : "tsx"}
192+
code={input.code}
193+
/>
194+
</div>
195+
</ToolFadeIn>
164196
);
165197

166198
case "findRelatedLinks": {
167199
if (state === "input-available") {
168-
return <ToolLoading label="관련 링크 찾는 중..." />;
200+
return (
201+
<ToolFadeIn>
202+
<ToolLoading label="관련 링크 찾는 중..." />
203+
</ToolFadeIn>
204+
);
169205
}
170206

171207
const links = getRelatedLinks(output);
@@ -174,25 +210,31 @@ export function ToolResultRenderer({ toolName, input, state, output }: ToolResul
174210
}
175211

176212
return (
177-
<ul className="my-2 list-disc pl-5 text-sm space-y-2">
178-
{links.map((link) => (
179-
<li key={link.url}>
180-
<div className="space-y-0.5">
181-
<Link href={link.href} className="text-fd-primary hover:underline break-all">
182-
{link.title}
183-
</Link>
184-
<div className="text-xs text-fd-muted-foreground break-all">{link.url}</div>
185-
</div>
186-
</li>
187-
))}
188-
</ul>
213+
<ToolFadeIn>
214+
<ul className="my-2 list-disc pl-5 text-sm space-y-2">
215+
{links.map((link) => (
216+
<li key={link.url}>
217+
<div className="space-y-0.5">
218+
<Link href={link.href} className="text-fd-primary hover:underline break-all">
219+
{link.title}
220+
</Link>
221+
<div className="text-xs text-fd-muted-foreground break-all">{link.url}</div>
222+
</div>
223+
</li>
224+
))}
225+
</ul>
226+
</ToolFadeIn>
189227
);
190228
}
191229

192230
default:
193231
// MCP 서버사이드 도구: 실행 중이면 로딩 표시
194232
if (state === "input-available") {
195-
return <ToolLoading label="문서 검색 중..." />;
233+
return (
234+
<ToolFadeIn>
235+
<ToolLoading label="문서 검색 중..." />
236+
</ToolFadeIn>
237+
);
196238
}
197239
return null;
198240
}

0 commit comments

Comments
 (0)