@@ -4,6 +4,8 @@ import { ComponentPreview } from "@/components/component-preview";
44import Link from "next/link" ;
55import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock" ;
66import { Tab , Tabs } from "fumadocs-ui/components/tabs" ;
7+ import { m } from "motion/react" ;
8+ import type { ReactNode } from "react" ;
79import { ProgressCircle } from "seed-design/ui/progress-circle" ;
810
911interface 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
2935function 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+
3852function getRelatedLinks ( output : unknown ) : RelatedLink [ ] {
3953 if ( ! output || typeof output !== "object" ) return [ ] ;
4054
@@ -84,14 +98,20 @@ function getToolOutputCode(output: unknown): { code: string; language: string }
8498export 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