Skip to content

Commit 84eccfe

Browse files
committed
feat(skeleton): add loading skeletons for all tabs
- Update Skeleton.tsx with semantic theme tokens (light mode support) - Add SearchResultSkeleton, SearchResultsSkeleton - Add DependencyGraphSkeleton with fake graph visualization - Add StyleInsightsSkeleton with metric cards - Update DependencyGraph to use skeleton instead of spinner - Update StyleInsights to use skeleton instead of spinner - Update SearchPanel to show results skeleton while loading
1 parent 3b5e3a5 commit 84eccfe

4 files changed

Lines changed: 195 additions & 36 deletions

File tree

frontend/src/components/DependencyGraph.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import dagre from 'dagre'
1212
import { Lightbulb } from 'lucide-react'
1313
import 'reactflow/dist/style.css'
1414
import { useDependencyGraph } from '../hooks/useCachedQuery'
15+
import { DependencyGraphSkeleton } from './ui/Skeleton'
1516

1617
interface DependencyGraphProps {
1718
repoId: string
@@ -174,12 +175,7 @@ export function DependencyGraph({ repoId, apiUrl, apiKey }: DependencyGraphProps
174175
}
175176

176177
if (loading) {
177-
return (
178-
<div className="p-12 text-center">
179-
<div className="w-16 h-16 border-4 border-primary/20 border-t-primary rounded-full animate-spin mx-auto mb-4" />
180-
<p className="text-muted-foreground">Building dependency graph...</p>
181-
</div>
182-
)
178+
return <DependencyGraphSkeleton />
183179
}
184180

185181
return (

frontend/src/components/SearchPanel.tsx

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useState } from 'react';
22
import { toast } from 'sonner';
33
import { Zap, Search } from 'lucide-react';
44
import { SearchBox, ResultCard } from './search';
5+
import { SearchResultsSkeleton } from './ui/Skeleton';
56
import type { SearchResult } from '../types';
67

78
interface SearchPanelProps {
@@ -70,20 +71,27 @@ export function SearchPanel({ repoId, apiUrl, apiKey, repoUrl, defaultBranch }:
7071
)}
7172
</div>
7273

74+
{/* Loading Skeleton */}
75+
{loading && (
76+
<SearchResultsSkeleton count={3} />
77+
)}
78+
7379
{/* Results */}
74-
<div className="space-y-3">
75-
{results.map((result, idx) => (
76-
<ResultCard
77-
key={`${result.file_path}-${result.line_start}-${idx}`}
78-
result={result}
79-
rank={idx + 1}
80-
isExpanded={idx === 0}
81-
aiSummary={idx === 0 ? aiSummary || undefined : undefined}
82-
repoUrl={repoUrl}
83-
defaultBranch={defaultBranch}
84-
/>
85-
))}
86-
</div>
80+
{!loading && (
81+
<div className="space-y-3">
82+
{results.map((result, idx) => (
83+
<ResultCard
84+
key={`${result.file_path}-${result.line_start}-${idx}`}
85+
result={result}
86+
rank={idx + 1}
87+
isExpanded={idx === 0}
88+
aiSummary={idx === 0 ? aiSummary || undefined : undefined}
89+
repoUrl={repoUrl}
90+
defaultBranch={defaultBranch}
91+
/>
92+
))}
93+
</div>
94+
)}
8795

8896
{/* Empty State */}
8997
{results.length === 0 && hasSearched && !loading && (

frontend/src/components/StyleInsights.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useStyleAnalysis } from '../hooks/useCachedQuery'
2+
import { StyleInsightsSkeleton } from './ui/Skeleton'
23

34
interface StyleInsightsProps {
45
repoId: string
@@ -10,12 +11,7 @@ export function StyleInsights({ repoId, apiUrl, apiKey }: StyleInsightsProps) {
1011
const { data, isLoading: loading } = useStyleAnalysis({ repoId, apiKey })
1112

1213
if (loading) {
13-
return (
14-
<div className="p-12 text-center">
15-
<div className="w-16 h-16 border-4 border-primary/20 border-t-primary rounded-full animate-spin mx-auto mb-4" />
16-
<p className="text-muted-foreground">Analyzing code style patterns...</p>
17-
</div>
18-
)
14+
return <StyleInsightsSkeleton />
1915
}
2016

2117
if (!data) return null

frontend/src/components/ui/Skeleton.tsx

Lines changed: 170 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function Skeleton({ className }: SkeletonProps) {
88
return (
99
<div
1010
className={cn(
11-
'animate-pulse rounded-lg bg-white/5',
11+
'animate-pulse rounded-lg bg-muted',
1212
className
1313
)}
1414
/>
@@ -18,7 +18,7 @@ export function Skeleton({ className }: SkeletonProps) {
1818
// Preset skeleton components
1919
export function RepoCardSkeleton() {
2020
return (
21-
<div className="bg-[#111113] border border-white/5 rounded-2xl p-5 space-y-4">
21+
<div className="bg-card border border-border rounded-2xl p-5 space-y-4">
2222
<div className="flex items-center gap-3">
2323
<Skeleton className="w-10 h-10 rounded-xl" />
2424
<div className="flex-1 space-y-2">
@@ -49,7 +49,7 @@ export function RepoGridSkeleton({ count = 3 }: { count?: number }) {
4949

5050
export function StatCardSkeleton() {
5151
return (
52-
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5 space-y-2">
52+
<div className="bg-card border border-border rounded-xl p-5 space-y-2">
5353
<Skeleton className="h-3 w-20" />
5454
<Skeleton className="h-8 w-16" />
5555
</div>
@@ -58,16 +58,175 @@ export function StatCardSkeleton() {
5858

5959
export function SearchResultSkeleton() {
6060
return (
61-
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5 space-y-4">
62-
<div className="flex items-start justify-between">
63-
<div className="space-y-2">
64-
<Skeleton className="h-4 w-32" />
61+
<div className="bg-card border border-border rounded-xl overflow-hidden">
62+
{/* Header */}
63+
<div className="p-4 flex items-start justify-between">
64+
<div className="space-y-2 flex-1">
65+
<div className="flex items-center gap-2">
66+
<Skeleton className="h-5 w-24 rounded" />
67+
<Skeleton className="h-4 w-16 rounded" />
68+
</div>
6569
<Skeleton className="h-3 w-48" />
6670
</div>
67-
<Skeleton className="h-6 w-12" />
71+
<div className="flex items-center gap-3">
72+
<Skeleton className="h-1.5 w-16 rounded-full" />
73+
<Skeleton className="h-5 w-10" />
74+
</div>
75+
</div>
76+
{/* Code block */}
77+
<div className="border-t border-border p-4 bg-muted/30">
78+
<div className="space-y-2">
79+
<Skeleton className="h-4 w-full" />
80+
<Skeleton className="h-4 w-5/6" />
81+
<Skeleton className="h-4 w-4/5" />
82+
<Skeleton className="h-4 w-full" />
83+
<Skeleton className="h-4 w-3/4" />
84+
</div>
85+
</div>
86+
{/* Footer */}
87+
<div className="px-4 py-3 bg-muted/50 flex justify-between">
88+
<Skeleton className="h-3 w-24" />
89+
<div className="flex gap-2">
90+
<Skeleton className="h-7 w-16 rounded" />
91+
<Skeleton className="h-7 w-16 rounded" />
92+
</div>
93+
</div>
94+
</div>
95+
)
96+
}
97+
98+
export function SearchResultsSkeleton({ count = 3 }: { count?: number }) {
99+
return (
100+
<div className="space-y-3">
101+
{Array.from({ length: count }).map((_, i) => (
102+
<SearchResultSkeleton key={i} />
103+
))}
104+
</div>
105+
)
106+
}
107+
108+
export function DependencyGraphSkeleton() {
109+
return (
110+
<div className="p-6 space-y-6">
111+
{/* Metrics Grid */}
112+
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
113+
{Array.from({ length: 4 }).map((_, i) => (
114+
<div key={i} className="bg-card border border-border rounded-xl p-5">
115+
<Skeleton className="h-3 w-20 mb-2" />
116+
<Skeleton className="h-8 w-16" />
117+
</div>
118+
))}
119+
</div>
120+
121+
{/* Graph Area */}
122+
<div className="bg-card border border-border rounded-xl p-6">
123+
<div className="flex items-center justify-between mb-4">
124+
<Skeleton className="h-5 w-32" />
125+
<div className="flex gap-2">
126+
<Skeleton className="h-8 w-24 rounded" />
127+
<Skeleton className="h-8 w-20 rounded" />
128+
</div>
129+
</div>
130+
{/* Fake graph visualization */}
131+
<div className="h-[400px] relative overflow-hidden rounded-lg bg-muted/30">
132+
<div className="absolute inset-0 flex items-center justify-center">
133+
<div className="relative">
134+
{/* Center node */}
135+
<Skeleton className="w-12 h-12 rounded-full" />
136+
{/* Surrounding nodes */}
137+
{[0, 60, 120, 180, 240, 300].map((angle, i) => (
138+
<Skeleton
139+
key={i}
140+
className="w-8 h-8 rounded-full absolute"
141+
style={{
142+
left: `${Math.cos(angle * Math.PI / 180) * 80 + 10}px`,
143+
top: `${Math.sin(angle * Math.PI / 180) * 80 + 10}px`,
144+
}}
145+
/>
146+
))}
147+
</div>
148+
</div>
149+
<div className="absolute bottom-4 left-4 right-4">
150+
<Skeleton className="h-3 w-48" />
151+
</div>
152+
</div>
153+
</div>
154+
155+
{/* Critical Files */}
156+
<div className="bg-card border border-border rounded-xl p-5">
157+
<Skeleton className="h-5 w-28 mb-4" />
158+
<div className="space-y-2">
159+
{Array.from({ length: 4 }).map((_, i) => (
160+
<div key={i} className="flex items-center justify-between p-3 bg-muted/50 rounded-lg">
161+
<Skeleton className="h-4 w-48" />
162+
<Skeleton className="h-4 w-16" />
163+
</div>
164+
))}
165+
</div>
166+
</div>
167+
</div>
168+
)
169+
}
170+
171+
export function StyleInsightsSkeleton() {
172+
return (
173+
<div className="p-6 space-y-6">
174+
{/* Summary Cards */}
175+
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
176+
{Array.from({ length: 4 }).map((_, i) => (
177+
<div key={i} className="bg-card border border-border rounded-xl p-5">
178+
<Skeleton className="h-3 w-24 mb-2" />
179+
<Skeleton className="h-8 w-20" />
180+
</div>
181+
))}
182+
</div>
183+
184+
{/* Two Column Layout */}
185+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
186+
{/* Naming Conventions */}
187+
<div className="bg-card border border-border rounded-xl p-5">
188+
<Skeleton className="h-5 w-36 mb-4" />
189+
<div className="space-y-3">
190+
{Array.from({ length: 3 }).map((_, i) => (
191+
<div key={i} className="space-y-2">
192+
<div className="flex justify-between">
193+
<Skeleton className="h-4 w-24" />
194+
<Skeleton className="h-4 w-12" />
195+
</div>
196+
<Skeleton className="h-2 w-full rounded-full" />
197+
</div>
198+
))}
199+
</div>
200+
</div>
201+
202+
{/* Common Patterns */}
203+
<div className="bg-card border border-border rounded-xl p-5">
204+
<Skeleton className="h-5 w-32 mb-4" />
205+
<div className="space-y-2">
206+
{Array.from({ length: 4 }).map((_, i) => (
207+
<div key={i} className="flex items-center justify-between p-3 bg-muted/50 rounded-lg">
208+
<Skeleton className="h-4 w-32" />
209+
<Skeleton className="h-5 w-12 rounded-full" />
210+
</div>
211+
))}
212+
</div>
213+
</div>
214+
</div>
215+
216+
{/* Language Distribution */}
217+
<div className="bg-card border border-border rounded-xl p-5">
218+
<Skeleton className="h-5 w-40 mb-4" />
219+
<div className="flex gap-2 mb-4">
220+
{Array.from({ length: 5 }).map((_, i) => (
221+
<Skeleton
222+
key={i}
223+
className="h-6 rounded-full"
224+
style={{ width: `${20 - i * 3}%` }}
225+
/>
226+
))}
227+
</div>
228+
<Skeleton className="h-3 w-full rounded-full" />
68229
</div>
69-
<Skeleton className="h-32 w-full rounded-lg" />
70-
<Skeleton className="h-3 w-40" />
71230
</div>
72231
)
73232
}
@@ -76,7 +235,7 @@ export function TableSkeleton({ rows = 5 }: { rows?: number }) {
76235
return (
77236
<div className="space-y-2">
78237
{Array.from({ length: rows }).map((_, i) => (
79-
<div key={i} className="flex items-center gap-4 p-3 bg-white/5 rounded-lg">
238+
<div key={i} className="flex items-center gap-4 p-3 bg-muted/50 rounded-lg">
80239
<Skeleton className="h-4 flex-1" />
81240
<Skeleton className="h-4 w-16" />
82241
</div>

0 commit comments

Comments
 (0)