@moontra/moonui-pro 2.17.5 → 2.18.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.
- package/dist/index.mjs +2003 -882
- package/package.json +3 -1
- package/src/components/github-stars/github-api.ts +413 -0
- package/src/components/github-stars/hooks.ts +362 -0
- package/src/components/github-stars/index.tsx +215 -288
- package/src/components/github-stars/types.ts +146 -0
- package/src/components/github-stars/variants.tsx +380 -0
- package/src/components/lazy-component/index.tsx +567 -85
- package/src/components/virtual-list/index.tsx +6 -105
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
export interface GitHubRepository {
|
|
2
|
+
id: number
|
|
3
|
+
name: string
|
|
4
|
+
full_name: string
|
|
5
|
+
description: string | null
|
|
6
|
+
html_url: string
|
|
7
|
+
homepage: string | null
|
|
8
|
+
stargazers_count: number
|
|
9
|
+
watchers_count: number
|
|
10
|
+
forks_count: number
|
|
11
|
+
language: string | null
|
|
12
|
+
topics: string[]
|
|
13
|
+
created_at: string
|
|
14
|
+
updated_at: string
|
|
15
|
+
pushed_at: string
|
|
16
|
+
size: number
|
|
17
|
+
open_issues_count: number
|
|
18
|
+
license: {
|
|
19
|
+
key: string
|
|
20
|
+
name: string
|
|
21
|
+
spdx_id: string
|
|
22
|
+
url: string
|
|
23
|
+
} | null
|
|
24
|
+
owner: {
|
|
25
|
+
login: string
|
|
26
|
+
avatar_url: string
|
|
27
|
+
html_url: string
|
|
28
|
+
type: string
|
|
29
|
+
}
|
|
30
|
+
// Extended properties
|
|
31
|
+
contributors_count?: number
|
|
32
|
+
commits_count?: number
|
|
33
|
+
releases_count?: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface GitHubStats {
|
|
37
|
+
totalStars: number
|
|
38
|
+
totalForks: number
|
|
39
|
+
totalWatchers: number
|
|
40
|
+
totalIssues: number
|
|
41
|
+
avgStarsPerRepo: number
|
|
42
|
+
mostStarredRepo: GitHubRepository | null
|
|
43
|
+
recentActivity: GitHubActivity[]
|
|
44
|
+
languages: LanguageStats[]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface GitHubActivity {
|
|
48
|
+
type: "star" | "fork" | "issue" | "pr" | "release"
|
|
49
|
+
repository: string
|
|
50
|
+
timestamp: string
|
|
51
|
+
actor?: string
|
|
52
|
+
description?: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface LanguageStats {
|
|
56
|
+
language: string
|
|
57
|
+
count: number
|
|
58
|
+
percentage: number
|
|
59
|
+
color: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface StarHistory {
|
|
63
|
+
date: string
|
|
64
|
+
count: number
|
|
65
|
+
repository?: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface Milestone {
|
|
69
|
+
count: number
|
|
70
|
+
reached: boolean
|
|
71
|
+
date?: string
|
|
72
|
+
celebration?: boolean
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type DisplayVariant = "compact" | "full" | "minimal" | "detailed" | "card"
|
|
76
|
+
export type AnimationOption = "bounce" | "pulse" | "fade" | "scale" | "slide" | "none"
|
|
77
|
+
export type SortOption = "stars" | "forks" | "updated" | "created" | "name" | "issues"
|
|
78
|
+
export type LayoutOption = "grid" | "list" | "masonry" | "carousel"
|
|
79
|
+
|
|
80
|
+
export interface GitHubStarsProps {
|
|
81
|
+
// Basic props
|
|
82
|
+
username?: string
|
|
83
|
+
repository?: string // For single repo mode
|
|
84
|
+
repositories?: string[] // For multiple repos
|
|
85
|
+
token?: string // GitHub token for higher rate limits
|
|
86
|
+
|
|
87
|
+
// Display options
|
|
88
|
+
variant?: DisplayVariant
|
|
89
|
+
layout?: LayoutOption
|
|
90
|
+
showDescription?: boolean
|
|
91
|
+
showTopics?: boolean
|
|
92
|
+
showStats?: boolean
|
|
93
|
+
showOwner?: boolean
|
|
94
|
+
showLanguage?: boolean
|
|
95
|
+
showActivity?: boolean
|
|
96
|
+
showTrending?: boolean
|
|
97
|
+
showMilestones?: boolean
|
|
98
|
+
showComparison?: boolean
|
|
99
|
+
showHistory?: boolean
|
|
100
|
+
|
|
101
|
+
// Behavior options
|
|
102
|
+
sortBy?: SortOption
|
|
103
|
+
maxItems?: number
|
|
104
|
+
autoRefresh?: boolean
|
|
105
|
+
refreshInterval?: number
|
|
106
|
+
enableNotifications?: boolean
|
|
107
|
+
enableExport?: boolean
|
|
108
|
+
enableAnalytics?: boolean
|
|
109
|
+
cacheEnabled?: boolean
|
|
110
|
+
cacheDuration?: number
|
|
111
|
+
|
|
112
|
+
// Animation options
|
|
113
|
+
animation?: AnimationOption
|
|
114
|
+
animationDuration?: number
|
|
115
|
+
staggerDelay?: number
|
|
116
|
+
|
|
117
|
+
// Milestone configuration
|
|
118
|
+
milestones?: number[]
|
|
119
|
+
celebrateAt?: number[]
|
|
120
|
+
|
|
121
|
+
// Callbacks
|
|
122
|
+
onRepositoryClick?: (repo: GitHubRepository) => void
|
|
123
|
+
onStarClick?: (repo: GitHubRepository) => void
|
|
124
|
+
onMilestoneReached?: (milestone: Milestone) => void
|
|
125
|
+
onDataUpdate?: (stats: GitHubStats) => void
|
|
126
|
+
onError?: (error: Error) => void
|
|
127
|
+
|
|
128
|
+
// Styling
|
|
129
|
+
className?: string
|
|
130
|
+
theme?: "light" | "dark" | "auto"
|
|
131
|
+
size?: "sm" | "md" | "lg" | "xl"
|
|
132
|
+
customColors?: Record<string, string>
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface CacheEntry {
|
|
136
|
+
data: any
|
|
137
|
+
timestamp: number
|
|
138
|
+
expiresAt: number
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface RateLimitInfo {
|
|
142
|
+
limit: number
|
|
143
|
+
remaining: number
|
|
144
|
+
reset: number
|
|
145
|
+
used: number
|
|
146
|
+
}
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { motion } from "framer-motion"
|
|
3
|
+
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"
|
|
4
|
+
import { Badge } from "../ui/badge"
|
|
5
|
+
import { Button } from "../ui/button"
|
|
6
|
+
import { Progress } from "../ui/progress"
|
|
7
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"
|
|
8
|
+
import {
|
|
9
|
+
Star,
|
|
10
|
+
GitFork,
|
|
11
|
+
Eye,
|
|
12
|
+
Users,
|
|
13
|
+
ExternalLink,
|
|
14
|
+
Github,
|
|
15
|
+
TrendingUp,
|
|
16
|
+
BarChart3,
|
|
17
|
+
Activity,
|
|
18
|
+
Code2,
|
|
19
|
+
Calendar,
|
|
20
|
+
Package,
|
|
21
|
+
Download,
|
|
22
|
+
} from "lucide-react"
|
|
23
|
+
import { GitHubRepository, GitHubStats, LanguageStats } from "./types"
|
|
24
|
+
import { formatNumber, formatDate, LANGUAGE_COLORS } from "./github-api"
|
|
25
|
+
import { cn } from "../../lib/utils"
|
|
26
|
+
|
|
27
|
+
interface BaseVariantProps {
|
|
28
|
+
repos: GitHubRepository[]
|
|
29
|
+
stats: GitHubStats | null
|
|
30
|
+
loading?: boolean
|
|
31
|
+
className?: string
|
|
32
|
+
onRepositoryClick?: (repo: GitHubRepository) => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Minimal variant - Single line with key stats
|
|
36
|
+
export const MinimalVariant: React.FC<BaseVariantProps> = ({ repos, stats, className }) => {
|
|
37
|
+
if (!stats) return null
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className={cn("flex items-center gap-4 text-sm", className)}>
|
|
41
|
+
<div className="flex items-center gap-2">
|
|
42
|
+
<Github className="h-4 w-4" />
|
|
43
|
+
<span className="font-medium">{repos.length} repositories</span>
|
|
44
|
+
</div>
|
|
45
|
+
<div className="flex items-center gap-2">
|
|
46
|
+
<Star className="h-4 w-4 text-yellow-500" />
|
|
47
|
+
<span>{formatNumber(stats.totalStars)} stars</span>
|
|
48
|
+
</div>
|
|
49
|
+
<div className="flex items-center gap-2">
|
|
50
|
+
<GitFork className="h-4 w-4 text-blue-500" />
|
|
51
|
+
<span>{formatNumber(stats.totalForks)} forks</span>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Compact variant - Small card with essential info
|
|
58
|
+
export const CompactVariant: React.FC<BaseVariantProps> = ({
|
|
59
|
+
repos,
|
|
60
|
+
stats,
|
|
61
|
+
className,
|
|
62
|
+
onRepositoryClick,
|
|
63
|
+
}) => {
|
|
64
|
+
if (!stats) return null
|
|
65
|
+
|
|
66
|
+
const topRepos = repos.slice(0, 3)
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Card className={cn("w-full max-w-sm", className)}>
|
|
70
|
+
<CardContent className="p-4">
|
|
71
|
+
<div className="space-y-4">
|
|
72
|
+
<div className="flex items-center justify-between">
|
|
73
|
+
<div className="flex items-center gap-2">
|
|
74
|
+
<Github className="h-5 w-5" />
|
|
75
|
+
<span className="font-semibold">{repos.length} Repos</span>
|
|
76
|
+
</div>
|
|
77
|
+
<div className="flex items-center gap-3 text-sm">
|
|
78
|
+
<div className="flex items-center gap-1">
|
|
79
|
+
<Star className="h-4 w-4 text-yellow-500" />
|
|
80
|
+
<span>{formatNumber(stats.totalStars)}</span>
|
|
81
|
+
</div>
|
|
82
|
+
<div className="flex items-center gap-1">
|
|
83
|
+
<GitFork className="h-4 w-4 text-blue-500" />
|
|
84
|
+
<span>{formatNumber(stats.totalForks)}</span>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div className="space-y-2">
|
|
90
|
+
{topRepos.map((repo) => (
|
|
91
|
+
<div
|
|
92
|
+
key={repo.id}
|
|
93
|
+
className="flex items-center justify-between p-2 rounded-md hover:bg-accent cursor-pointer transition-colors"
|
|
94
|
+
onClick={() => onRepositoryClick?.(repo)}
|
|
95
|
+
>
|
|
96
|
+
<div className="flex-1 min-w-0">
|
|
97
|
+
<p className="text-sm font-medium truncate">{repo.name}</p>
|
|
98
|
+
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
99
|
+
{repo.language && (
|
|
100
|
+
<div className="flex items-center gap-1">
|
|
101
|
+
<div
|
|
102
|
+
className="w-2 h-2 rounded-full"
|
|
103
|
+
style={{ backgroundColor: LANGUAGE_COLORS[repo.language] || "#6b7280" }}
|
|
104
|
+
/>
|
|
105
|
+
<span>{repo.language}</span>
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
<div className="flex items-center gap-1">
|
|
109
|
+
<Star className="h-3 w-3" />
|
|
110
|
+
<span>{formatNumber(repo.stargazers_count)}</span>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
<ExternalLink className="h-3 w-3 text-muted-foreground" />
|
|
115
|
+
</div>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</CardContent>
|
|
120
|
+
</Card>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Card variant - Medium-sized cards in a grid
|
|
125
|
+
export const CardVariant: React.FC<BaseVariantProps> = ({
|
|
126
|
+
repos,
|
|
127
|
+
className,
|
|
128
|
+
onRepositoryClick,
|
|
129
|
+
}) => {
|
|
130
|
+
return (
|
|
131
|
+
<div className={cn("grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", className)}>
|
|
132
|
+
{repos.map((repo, index) => (
|
|
133
|
+
<motion.div
|
|
134
|
+
key={repo.id}
|
|
135
|
+
initial={{ opacity: 0, y: 20 }}
|
|
136
|
+
animate={{ opacity: 1, y: 0 }}
|
|
137
|
+
transition={{ delay: index * 0.05 }}
|
|
138
|
+
>
|
|
139
|
+
<Card
|
|
140
|
+
className="h-full hover:shadow-lg transition-shadow cursor-pointer"
|
|
141
|
+
onClick={() => onRepositoryClick?.(repo)}
|
|
142
|
+
>
|
|
143
|
+
<CardContent className="p-6">
|
|
144
|
+
<div className="space-y-4">
|
|
145
|
+
<div>
|
|
146
|
+
<h3 className="font-semibold text-lg mb-1">{repo.name}</h3>
|
|
147
|
+
<p className="text-sm text-muted-foreground line-clamp-2">
|
|
148
|
+
{repo.description || "No description available"}
|
|
149
|
+
</p>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
{repo.topics.length > 0 && (
|
|
153
|
+
<div className="flex flex-wrap gap-1">
|
|
154
|
+
{repo.topics.slice(0, 3).map((topic) => (
|
|
155
|
+
<Badge key={topic} variant="secondary" className="text-xs">
|
|
156
|
+
{topic}
|
|
157
|
+
</Badge>
|
|
158
|
+
))}
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
<div className="flex items-center justify-between text-sm">
|
|
163
|
+
<div className="flex items-center gap-3">
|
|
164
|
+
{repo.language && (
|
|
165
|
+
<div className="flex items-center gap-1">
|
|
166
|
+
<div
|
|
167
|
+
className="w-3 h-3 rounded-full"
|
|
168
|
+
style={{ backgroundColor: LANGUAGE_COLORS[repo.language] || "#6b7280" }}
|
|
169
|
+
/>
|
|
170
|
+
<span>{repo.language}</span>
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
<div className="flex items-center gap-3">
|
|
175
|
+
<div className="flex items-center gap-1">
|
|
176
|
+
<Star className="h-4 w-4 text-yellow-500" />
|
|
177
|
+
<span>{formatNumber(repo.stargazers_count)}</span>
|
|
178
|
+
</div>
|
|
179
|
+
<div className="flex items-center gap-1">
|
|
180
|
+
<GitFork className="h-4 w-4 text-blue-500" />
|
|
181
|
+
<span>{formatNumber(repo.forks_count)}</span>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
</CardContent>
|
|
187
|
+
</Card>
|
|
188
|
+
</motion.div>
|
|
189
|
+
))}
|
|
190
|
+
</div>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Detailed variant - Full information with tabs
|
|
195
|
+
export const DetailedVariant: React.FC<BaseVariantProps & { onExport?: () => void }> = ({
|
|
196
|
+
repos,
|
|
197
|
+
stats,
|
|
198
|
+
className,
|
|
199
|
+
onRepositoryClick,
|
|
200
|
+
onExport,
|
|
201
|
+
}) => {
|
|
202
|
+
if (!stats) return null
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<Card className={cn("w-full", className)}>
|
|
206
|
+
<CardHeader>
|
|
207
|
+
<div className="flex items-center justify-between">
|
|
208
|
+
<CardTitle className="flex items-center gap-2">
|
|
209
|
+
<Github className="h-6 w-6" />
|
|
210
|
+
GitHub Repository Analytics
|
|
211
|
+
</CardTitle>
|
|
212
|
+
{onExport && (
|
|
213
|
+
<Button onClick={onExport} variant="outline" size="sm">
|
|
214
|
+
<Download className="h-4 w-4 mr-2" />
|
|
215
|
+
Export
|
|
216
|
+
</Button>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
</CardHeader>
|
|
220
|
+
<CardContent>
|
|
221
|
+
<Tabs defaultValue="overview" className="w-full">
|
|
222
|
+
<TabsList className="grid w-full grid-cols-4">
|
|
223
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
224
|
+
<TabsTrigger value="repositories">Repositories</TabsTrigger>
|
|
225
|
+
<TabsTrigger value="languages">Languages</TabsTrigger>
|
|
226
|
+
<TabsTrigger value="activity">Activity</TabsTrigger>
|
|
227
|
+
</TabsList>
|
|
228
|
+
|
|
229
|
+
<TabsContent value="overview" className="space-y-4">
|
|
230
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
231
|
+
<StatCard
|
|
232
|
+
icon={<Package className="h-4 w-4" />}
|
|
233
|
+
label="Total Repos"
|
|
234
|
+
value={repos.length}
|
|
235
|
+
/>
|
|
236
|
+
<StatCard
|
|
237
|
+
icon={<Star className="h-4 w-4 text-yellow-500" />}
|
|
238
|
+
label="Total Stars"
|
|
239
|
+
value={formatNumber(stats.totalStars)}
|
|
240
|
+
/>
|
|
241
|
+
<StatCard
|
|
242
|
+
icon={<GitFork className="h-4 w-4 text-blue-500" />}
|
|
243
|
+
label="Total Forks"
|
|
244
|
+
value={formatNumber(stats.totalForks)}
|
|
245
|
+
/>
|
|
246
|
+
<StatCard
|
|
247
|
+
icon={<Eye className="h-4 w-4 text-green-500" />}
|
|
248
|
+
label="Total Watchers"
|
|
249
|
+
value={formatNumber(stats.totalWatchers)}
|
|
250
|
+
/>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
{stats.mostStarredRepo && (
|
|
254
|
+
<Card>
|
|
255
|
+
<CardContent className="p-4">
|
|
256
|
+
<div className="flex items-center justify-between">
|
|
257
|
+
<div>
|
|
258
|
+
<p className="text-sm text-muted-foreground mb-1">Most Starred Repository</p>
|
|
259
|
+
<h4 className="font-semibold">{stats.mostStarredRepo.name}</h4>
|
|
260
|
+
<p className="text-sm text-muted-foreground">
|
|
261
|
+
{formatNumber(stats.mostStarredRepo.stargazers_count)} stars
|
|
262
|
+
</p>
|
|
263
|
+
</div>
|
|
264
|
+
<TrendingUp className="h-8 w-8 text-muted-foreground" />
|
|
265
|
+
</div>
|
|
266
|
+
</CardContent>
|
|
267
|
+
</Card>
|
|
268
|
+
)}
|
|
269
|
+
</TabsContent>
|
|
270
|
+
|
|
271
|
+
<TabsContent value="repositories" className="space-y-4">
|
|
272
|
+
<div className="space-y-2">
|
|
273
|
+
{repos.map((repo) => (
|
|
274
|
+
<Card
|
|
275
|
+
key={repo.id}
|
|
276
|
+
className="hover:shadow-md transition-shadow cursor-pointer"
|
|
277
|
+
onClick={() => onRepositoryClick?.(repo)}
|
|
278
|
+
>
|
|
279
|
+
<CardContent className="p-4">
|
|
280
|
+
<div className="flex items-start justify-between">
|
|
281
|
+
<div className="flex-1">
|
|
282
|
+
<h4 className="font-semibold mb-1">{repo.name}</h4>
|
|
283
|
+
<p className="text-sm text-muted-foreground mb-2">
|
|
284
|
+
{repo.description || "No description"}
|
|
285
|
+
</p>
|
|
286
|
+
<div className="flex items-center gap-4 text-sm">
|
|
287
|
+
{repo.language && (
|
|
288
|
+
<div className="flex items-center gap-1">
|
|
289
|
+
<div
|
|
290
|
+
className="w-3 h-3 rounded-full"
|
|
291
|
+
style={{
|
|
292
|
+
backgroundColor: LANGUAGE_COLORS[repo.language] || "#6b7280",
|
|
293
|
+
}}
|
|
294
|
+
/>
|
|
295
|
+
<span>{repo.language}</span>
|
|
296
|
+
</div>
|
|
297
|
+
)}
|
|
298
|
+
<div className="flex items-center gap-1">
|
|
299
|
+
<Star className="h-3 w-3" />
|
|
300
|
+
<span>{formatNumber(repo.stargazers_count)}</span>
|
|
301
|
+
</div>
|
|
302
|
+
<div className="flex items-center gap-1">
|
|
303
|
+
<GitFork className="h-3 w-3" />
|
|
304
|
+
<span>{formatNumber(repo.forks_count)}</span>
|
|
305
|
+
</div>
|
|
306
|
+
<div className="flex items-center gap-1">
|
|
307
|
+
<Calendar className="h-3 w-3" />
|
|
308
|
+
<span>{formatDate(repo.updated_at)}</span>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
<ExternalLink className="h-4 w-4 text-muted-foreground" />
|
|
313
|
+
</div>
|
|
314
|
+
</CardContent>
|
|
315
|
+
</Card>
|
|
316
|
+
))}
|
|
317
|
+
</div>
|
|
318
|
+
</TabsContent>
|
|
319
|
+
|
|
320
|
+
<TabsContent value="languages" className="space-y-4">
|
|
321
|
+
<div className="space-y-3">
|
|
322
|
+
{stats.languages.map((lang) => (
|
|
323
|
+
<div key={lang.language} className="space-y-2">
|
|
324
|
+
<div className="flex items-center justify-between text-sm">
|
|
325
|
+
<div className="flex items-center gap-2">
|
|
326
|
+
<div
|
|
327
|
+
className="w-3 h-3 rounded-full"
|
|
328
|
+
style={{ backgroundColor: lang.color }}
|
|
329
|
+
/>
|
|
330
|
+
<span className="font-medium">{lang.language}</span>
|
|
331
|
+
</div>
|
|
332
|
+
<span className="text-muted-foreground">
|
|
333
|
+
{lang.count} repos ({lang.percentage.toFixed(1)}%)
|
|
334
|
+
</span>
|
|
335
|
+
</div>
|
|
336
|
+
<Progress value={lang.percentage} className="h-2" />
|
|
337
|
+
</div>
|
|
338
|
+
))}
|
|
339
|
+
</div>
|
|
340
|
+
</TabsContent>
|
|
341
|
+
|
|
342
|
+
<TabsContent value="activity" className="space-y-4">
|
|
343
|
+
<div className="space-y-2">
|
|
344
|
+
{stats.recentActivity.map((activity, index) => (
|
|
345
|
+
<div key={index} className="flex items-center gap-3 p-3 rounded-lg bg-accent/50">
|
|
346
|
+
<Activity className="h-4 w-4 text-muted-foreground" />
|
|
347
|
+
<div className="flex-1">
|
|
348
|
+
<p className="text-sm">{activity.description}</p>
|
|
349
|
+
<p className="text-xs text-muted-foreground">
|
|
350
|
+
{activity.repository} • {formatDate(activity.timestamp)}
|
|
351
|
+
</p>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
))}
|
|
355
|
+
</div>
|
|
356
|
+
</TabsContent>
|
|
357
|
+
</Tabs>
|
|
358
|
+
</CardContent>
|
|
359
|
+
</Card>
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Helper component for stat cards
|
|
364
|
+
const StatCard: React.FC<{
|
|
365
|
+
icon: React.ReactNode
|
|
366
|
+
label: string
|
|
367
|
+
value: string | number
|
|
368
|
+
}> = ({ icon, label, value }) => (
|
|
369
|
+
<Card>
|
|
370
|
+
<CardContent className="p-4">
|
|
371
|
+
<div className="flex items-center justify-between">
|
|
372
|
+
<div>
|
|
373
|
+
<p className="text-sm text-muted-foreground">{label}</p>
|
|
374
|
+
<p className="text-2xl font-bold">{value}</p>
|
|
375
|
+
</div>
|
|
376
|
+
{icon}
|
|
377
|
+
</div>
|
|
378
|
+
</CardContent>
|
|
379
|
+
</Card>
|
|
380
|
+
)
|