@moontra/moonui-pro 2.17.5 → 2.18.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/dist/index.mjs +1965 -882
- package/package.json +3 -1
- package/src/components/github-stars/github-api.ts +413 -0
- package/src/components/github-stars/hooks.ts +304 -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
|
@@ -1,170 +1,124 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import React, {
|
|
4
|
-
import { motion, AnimatePresence } from "framer-motion"
|
|
3
|
+
import React, { useMemo } from "react"
|
|
5
4
|
import { Card, CardContent } from "../ui/card"
|
|
6
5
|
import { Button } from "../ui/button"
|
|
7
|
-
import { MoonUIBadgePro as Badge } from "../ui/badge"
|
|
8
6
|
import { MoonUISkeletonPro as Skeleton } from "../ui/skeleton"
|
|
9
7
|
import { cn } from "../../lib/utils"
|
|
10
|
-
import {
|
|
8
|
+
import { Lock, Sparkles, RefreshCw, Github, Download, FileJson, FileSpreadsheet } from "lucide-react"
|
|
11
9
|
import { useSubscription } from "../../hooks/use-subscription"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
language: string | null
|
|
24
|
-
topics: string[]
|
|
25
|
-
created_at: string
|
|
26
|
-
updated_at: string
|
|
27
|
-
owner: {
|
|
28
|
-
login: string
|
|
29
|
-
avatar_url: string
|
|
30
|
-
html_url: string
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface GitHubStarsProps {
|
|
35
|
-
username: string
|
|
36
|
-
repositories?: string[]
|
|
37
|
-
showDescription?: boolean
|
|
38
|
-
showTopics?: boolean
|
|
39
|
-
showStats?: boolean
|
|
40
|
-
showOwner?: boolean
|
|
41
|
-
sortBy?: "stars" | "forks" | "updated" | "created"
|
|
42
|
-
layout?: "grid" | "list"
|
|
43
|
-
maxItems?: number
|
|
44
|
-
autoRefresh?: boolean
|
|
45
|
-
refreshInterval?: number
|
|
46
|
-
className?: string
|
|
47
|
-
onRepositoryClick?: (repo: GitHubRepository) => void
|
|
48
|
-
}
|
|
10
|
+
import { useGitHubData, useGitHubNotifications } from "./hooks"
|
|
11
|
+
import { exportData, exportAsCSV } from "./github-api"
|
|
12
|
+
import {
|
|
13
|
+
MinimalVariant,
|
|
14
|
+
CompactVariant,
|
|
15
|
+
CardVariant,
|
|
16
|
+
DetailedVariant,
|
|
17
|
+
} from "./variants"
|
|
18
|
+
import type { GitHubStarsProps } from "./types"
|
|
19
|
+
import { motion, AnimatePresence } from "framer-motion"
|
|
20
|
+
import confetti from "canvas-confetti"
|
|
49
21
|
|
|
50
22
|
const GitHubStarsInternal: React.FC<GitHubStarsProps> = ({
|
|
51
|
-
username,
|
|
23
|
+
username = "",
|
|
24
|
+
repository,
|
|
52
25
|
repositories,
|
|
26
|
+
token,
|
|
27
|
+
variant = "card",
|
|
28
|
+
layout = "grid",
|
|
53
29
|
showDescription = true,
|
|
54
30
|
showTopics = true,
|
|
55
31
|
showStats = true,
|
|
56
32
|
showOwner = true,
|
|
33
|
+
showLanguage = true,
|
|
34
|
+
showActivity = false,
|
|
35
|
+
showTrending = false,
|
|
36
|
+
showMilestones = false,
|
|
37
|
+
showComparison = false,
|
|
38
|
+
showHistory = false,
|
|
57
39
|
sortBy = "stars",
|
|
58
|
-
layout = "grid",
|
|
59
40
|
maxItems = 6,
|
|
60
41
|
autoRefresh = false,
|
|
61
|
-
refreshInterval = 300000,
|
|
42
|
+
refreshInterval = 300000,
|
|
43
|
+
enableNotifications = false,
|
|
44
|
+
enableExport = true,
|
|
45
|
+
enableAnalytics = false,
|
|
46
|
+
cacheEnabled = true,
|
|
47
|
+
cacheDuration = 300000,
|
|
48
|
+
animation = "fade",
|
|
49
|
+
animationDuration = 0.3,
|
|
50
|
+
staggerDelay = 0.05,
|
|
51
|
+
milestones = [10, 50, 100, 500, 1000, 5000, 10000],
|
|
52
|
+
celebrateAt = [100, 1000, 10000],
|
|
53
|
+
onRepositoryClick,
|
|
54
|
+
onStarClick,
|
|
55
|
+
onMilestoneReached,
|
|
56
|
+
onDataUpdate,
|
|
57
|
+
onError,
|
|
62
58
|
className,
|
|
63
|
-
|
|
59
|
+
theme = "auto",
|
|
60
|
+
size = "md",
|
|
61
|
+
customColors,
|
|
64
62
|
}) => {
|
|
65
|
-
const
|
|
66
|
-
const [loading, setLoading] = useState(true)
|
|
67
|
-
const [error, setError] = useState<string | null>(null)
|
|
68
|
-
const [lastUpdated, setLastUpdated] = useState<Date | null>(null)
|
|
69
|
-
|
|
70
|
-
const fetchRepositories = async () => {
|
|
71
|
-
try {
|
|
72
|
-
setLoading(true)
|
|
73
|
-
setError(null)
|
|
74
|
-
|
|
75
|
-
let url = `https://api.github.com/users/${username}/repos?sort=${sortBy}&per_page=100`
|
|
76
|
-
|
|
77
|
-
const response = await fetch(url)
|
|
78
|
-
if (!response.ok) {
|
|
79
|
-
throw new Error(`GitHub API error: ${response.status}`)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
let data: GitHubRepository[] = await response.json()
|
|
83
|
-
|
|
84
|
-
// Filter specific repositories if provided
|
|
85
|
-
if (repositories && repositories.length > 0) {
|
|
86
|
-
data = data.filter(repo => repositories.includes(repo.name))
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Sort repositories
|
|
90
|
-
data.sort((a, b) => {
|
|
91
|
-
switch (sortBy) {
|
|
92
|
-
case "stars":
|
|
93
|
-
return b.stargazers_count - a.stargazers_count
|
|
94
|
-
case "forks":
|
|
95
|
-
return b.forks_count - a.forks_count
|
|
96
|
-
case "updated":
|
|
97
|
-
return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
|
|
98
|
-
case "created":
|
|
99
|
-
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
|
100
|
-
default:
|
|
101
|
-
return 0
|
|
102
|
-
}
|
|
103
|
-
})
|
|
63
|
+
const { notify } = useGitHubNotifications(enableNotifications)
|
|
104
64
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
65
|
+
const {
|
|
66
|
+
repos,
|
|
67
|
+
stats,
|
|
68
|
+
loading,
|
|
69
|
+
error,
|
|
70
|
+
rateLimitInfo,
|
|
71
|
+
lastUpdated,
|
|
72
|
+
refresh,
|
|
73
|
+
} = useGitHubData({
|
|
74
|
+
username,
|
|
75
|
+
repository,
|
|
76
|
+
repositories,
|
|
77
|
+
token,
|
|
78
|
+
autoRefresh,
|
|
79
|
+
refreshInterval,
|
|
80
|
+
sortBy,
|
|
81
|
+
maxItems,
|
|
82
|
+
onError,
|
|
83
|
+
onDataUpdate,
|
|
84
|
+
onMilestoneReached: (milestone) => {
|
|
85
|
+
// Handle milestone reached
|
|
86
|
+
if (celebrateAt?.includes(milestone.count)) {
|
|
87
|
+
// Trigger celebration animation
|
|
88
|
+
confetti({
|
|
89
|
+
particleCount: 100,
|
|
90
|
+
spread: 70,
|
|
91
|
+
origin: { y: 0.6 },
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// Show notification
|
|
95
|
+
notify(`🎉 Milestone Reached!`, {
|
|
96
|
+
body: `${milestone.count} stars achieved!`,
|
|
97
|
+
tag: `milestone-${milestone.count}`,
|
|
98
|
+
})
|
|
108
99
|
}
|
|
100
|
+
|
|
101
|
+
onMilestoneReached?.(milestone)
|
|
102
|
+
},
|
|
103
|
+
milestones,
|
|
104
|
+
})
|
|
109
105
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
} catch (err) {
|
|
113
|
-
setError(err instanceof Error ? err.message : "Failed to fetch repositories")
|
|
114
|
-
} finally {
|
|
115
|
-
setLoading(false)
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
useEffect(() => {
|
|
120
|
-
fetchRepositories()
|
|
121
|
-
}, [username, repositories, sortBy, maxItems])
|
|
122
|
-
|
|
123
|
-
useEffect(() => {
|
|
124
|
-
if (!autoRefresh) return
|
|
125
|
-
|
|
126
|
-
const interval = setInterval(fetchRepositories, refreshInterval)
|
|
127
|
-
return () => clearInterval(interval)
|
|
128
|
-
}, [autoRefresh, refreshInterval])
|
|
129
|
-
|
|
130
|
-
const formatNumber = (num: number): string => {
|
|
131
|
-
if (num >= 1000) {
|
|
132
|
-
return (num / 1000).toFixed(1) + "k"
|
|
133
|
-
}
|
|
134
|
-
return num.toString()
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const formatDate = (dateString: string): string => {
|
|
138
|
-
const date = new Date(dateString)
|
|
139
|
-
return date.toLocaleDateString("en-US", {
|
|
140
|
-
year: "numeric",
|
|
141
|
-
month: "short",
|
|
142
|
-
day: "numeric"
|
|
143
|
-
})
|
|
144
|
-
}
|
|
106
|
+
const handleExport = (format: "json" | "csv") => {
|
|
107
|
+
if (!enableExport) return
|
|
145
108
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
Rust: "#000000",
|
|
156
|
-
Swift: "#fa7343",
|
|
157
|
-
Kotlin: "#7f52ff",
|
|
158
|
-
PHP: "#777bb4",
|
|
159
|
-
Ruby: "#cc342d",
|
|
160
|
-
HTML: "#e34f26",
|
|
161
|
-
CSS: "#1572b6",
|
|
162
|
-
Vue: "#4fc08d",
|
|
163
|
-
React: "#61dafb"
|
|
109
|
+
const timestamp = new Date().toISOString().split("T")[0]
|
|
110
|
+
|
|
111
|
+
if (format === "json") {
|
|
112
|
+
exportData(
|
|
113
|
+
{ repositories: repos, statistics: stats, exportDate: new Date() },
|
|
114
|
+
`github-stars-${username}-${timestamp}.json`
|
|
115
|
+
)
|
|
116
|
+
} else {
|
|
117
|
+
exportAsCSV(repos, `github-stars-${username}-${timestamp}.csv`)
|
|
164
118
|
}
|
|
165
|
-
return colors[language || ""] || "#6b7280"
|
|
166
119
|
}
|
|
167
120
|
|
|
121
|
+
// Loading state
|
|
168
122
|
if (loading) {
|
|
169
123
|
return (
|
|
170
124
|
<Card className={cn("w-full", className)}>
|
|
@@ -202,6 +156,7 @@ const GitHubStarsInternal: React.FC<GitHubStarsProps> = ({
|
|
|
202
156
|
)
|
|
203
157
|
}
|
|
204
158
|
|
|
159
|
+
// Error state
|
|
205
160
|
if (error) {
|
|
206
161
|
return (
|
|
207
162
|
<Card className={cn("w-full", className)}>
|
|
@@ -211,8 +166,13 @@ const GitHubStarsInternal: React.FC<GitHubStarsProps> = ({
|
|
|
211
166
|
<Github className="h-12 w-12 mx-auto mb-2" />
|
|
212
167
|
<h3 className="font-semibold">Failed to load repositories</h3>
|
|
213
168
|
<p className="text-sm text-muted-foreground">{error}</p>
|
|
169
|
+
{rateLimitInfo && rateLimitInfo.remaining === 0 && (
|
|
170
|
+
<p className="text-xs text-muted-foreground mt-2">
|
|
171
|
+
Rate limit will reset at {new Date(rateLimitInfo.reset).toLocaleTimeString()}
|
|
172
|
+
</p>
|
|
173
|
+
)}
|
|
214
174
|
</div>
|
|
215
|
-
<Button onClick={
|
|
175
|
+
<Button onClick={refresh} variant="outline">
|
|
216
176
|
<RefreshCw className="h-4 w-4 mr-2" />
|
|
217
177
|
Try Again
|
|
218
178
|
</Button>
|
|
@@ -222,154 +182,122 @@ const GitHubStarsInternal: React.FC<GitHubStarsProps> = ({
|
|
|
222
182
|
)
|
|
223
183
|
}
|
|
224
184
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
<h3 className="font-semibold text-lg">{username}'s Repositories</h3>
|
|
235
|
-
{lastUpdated && (
|
|
236
|
-
<p className="text-sm text-muted-foreground">
|
|
237
|
-
Last updated: {lastUpdated.toLocaleTimeString()}
|
|
238
|
-
</p>
|
|
239
|
-
)}
|
|
240
|
-
</div>
|
|
241
|
-
</div>
|
|
242
|
-
|
|
243
|
-
<div className="flex items-center gap-2">
|
|
244
|
-
<Badge variant="outline">
|
|
245
|
-
{repos.length} repos
|
|
246
|
-
</Badge>
|
|
247
|
-
{autoRefresh && (
|
|
248
|
-
<Badge variant="secondary">
|
|
249
|
-
Auto-refresh
|
|
250
|
-
</Badge>
|
|
251
|
-
)}
|
|
252
|
-
<Button onClick={fetchRepositories} variant="outline" size="sm">
|
|
253
|
-
<RefreshCw className="h-4 w-4" />
|
|
254
|
-
</Button>
|
|
255
|
-
</div>
|
|
256
|
-
</div>
|
|
257
|
-
|
|
258
|
-
{/* Repository Grid/List */}
|
|
259
|
-
<div className={cn(
|
|
260
|
-
layout === "grid"
|
|
261
|
-
? "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
|
|
262
|
-
: "space-y-4"
|
|
263
|
-
)}>
|
|
264
|
-
<AnimatePresence>
|
|
265
|
-
{repos.map((repo, index) => (
|
|
266
|
-
<motion.div
|
|
267
|
-
key={repo.id}
|
|
268
|
-
initial={{ opacity: 0, y: 20 }}
|
|
269
|
-
animate={{ opacity: 1, y: 0 }}
|
|
270
|
-
exit={{ opacity: 0, y: -20 }}
|
|
271
|
-
transition={{ delay: index * 0.1 }}
|
|
272
|
-
>
|
|
273
|
-
<Card
|
|
274
|
-
className="h-full hover:shadow-md transition-shadow cursor-pointer group"
|
|
275
|
-
onClick={() => onRepositoryClick?.(repo)}
|
|
276
|
-
>
|
|
277
|
-
<CardContent className="p-4">
|
|
278
|
-
<div className="space-y-3">
|
|
279
|
-
{/* Repository Name */}
|
|
280
|
-
<div className="flex items-start justify-between">
|
|
281
|
-
<div className="flex-1 min-w-0">
|
|
282
|
-
<h4 className="font-semibold text-sm group-hover:text-primary transition-colors">
|
|
283
|
-
{repo.name}
|
|
284
|
-
</h4>
|
|
285
|
-
{showOwner && (
|
|
286
|
-
<p className="text-xs text-muted-foreground truncate">
|
|
287
|
-
{repo.owner.login}
|
|
288
|
-
</p>
|
|
289
|
-
)}
|
|
290
|
-
</div>
|
|
291
|
-
<Button
|
|
292
|
-
variant="ghost"
|
|
293
|
-
size="sm"
|
|
294
|
-
className="opacity-0 group-hover:opacity-100 transition-opacity p-1 h-auto"
|
|
295
|
-
onClick={(e) => {
|
|
296
|
-
e.stopPropagation()
|
|
297
|
-
window.open(repo.html_url, '_blank')
|
|
298
|
-
}}
|
|
299
|
-
>
|
|
300
|
-
<ExternalLink className="h-3 w-3" />
|
|
301
|
-
</Button>
|
|
302
|
-
</div>
|
|
303
|
-
|
|
304
|
-
{/* Description */}
|
|
305
|
-
{showDescription && repo.description && (
|
|
306
|
-
<p className="text-sm text-muted-foreground line-clamp-2">
|
|
307
|
-
{repo.description}
|
|
308
|
-
</p>
|
|
309
|
-
)}
|
|
185
|
+
// Render based on variant
|
|
186
|
+
const renderVariant = () => {
|
|
187
|
+
const baseProps = {
|
|
188
|
+
repos,
|
|
189
|
+
stats,
|
|
190
|
+
loading,
|
|
191
|
+
className,
|
|
192
|
+
onRepositoryClick,
|
|
193
|
+
}
|
|
310
194
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
195
|
+
switch (variant) {
|
|
196
|
+
case "minimal":
|
|
197
|
+
return <MinimalVariant {...baseProps} />
|
|
198
|
+
case "compact":
|
|
199
|
+
return <CompactVariant {...baseProps} />
|
|
200
|
+
case "detailed":
|
|
201
|
+
return (
|
|
202
|
+
<DetailedVariant
|
|
203
|
+
{...baseProps}
|
|
204
|
+
onExport={() => {
|
|
205
|
+
// Show export dropdown
|
|
206
|
+
const dropdown = document.createElement("div")
|
|
207
|
+
dropdown.className = "absolute z-50 mt-2 w-48 rounded-md shadow-lg bg-popover border"
|
|
208
|
+
dropdown.innerHTML = `
|
|
209
|
+
<div class="py-1">
|
|
210
|
+
<button class="w-full text-left px-4 py-2 text-sm hover:bg-accent" data-format="json">
|
|
211
|
+
Export as JSON
|
|
212
|
+
</button>
|
|
213
|
+
<button class="w-full text-left px-4 py-2 text-sm hover:bg-accent" data-format="csv">
|
|
214
|
+
Export as CSV
|
|
215
|
+
</button>
|
|
216
|
+
</div>
|
|
217
|
+
`
|
|
218
|
+
|
|
219
|
+
dropdown.addEventListener("click", (e) => {
|
|
220
|
+
const target = e.target as HTMLElement
|
|
221
|
+
const format = target.getAttribute("data-format")
|
|
222
|
+
if (format === "json" || format === "csv") {
|
|
223
|
+
handleExport(format)
|
|
224
|
+
dropdown.remove()
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
document.body.appendChild(dropdown)
|
|
229
|
+
|
|
230
|
+
// Position dropdown
|
|
231
|
+
const rect = (e.target as HTMLElement).getBoundingClientRect()
|
|
232
|
+
dropdown.style.top = `${rect.bottom + window.scrollY}px`
|
|
233
|
+
dropdown.style.left = `${rect.left + window.scrollX}px`
|
|
234
|
+
|
|
235
|
+
// Remove on outside click
|
|
236
|
+
setTimeout(() => {
|
|
237
|
+
document.addEventListener("click", () => dropdown.remove(), { once: true })
|
|
238
|
+
}, 0)
|
|
239
|
+
}}
|
|
240
|
+
/>
|
|
241
|
+
)
|
|
242
|
+
case "card":
|
|
243
|
+
default:
|
|
244
|
+
return <CardVariant {...baseProps} />
|
|
245
|
+
}
|
|
246
|
+
}
|
|
326
247
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
248
|
+
// Apply animation wrapper
|
|
249
|
+
const animationVariants = {
|
|
250
|
+
bounce: {
|
|
251
|
+
initial: { scale: 0.9, opacity: 0 },
|
|
252
|
+
animate: { scale: 1, opacity: 1 },
|
|
253
|
+
transition: { type: "spring", stiffness: 300, damping: 20 },
|
|
254
|
+
},
|
|
255
|
+
pulse: {
|
|
256
|
+
initial: { scale: 0.95, opacity: 0 },
|
|
257
|
+
animate: { scale: 1, opacity: 1 },
|
|
258
|
+
transition: { duration: animationDuration },
|
|
259
|
+
},
|
|
260
|
+
fade: {
|
|
261
|
+
initial: { opacity: 0 },
|
|
262
|
+
animate: { opacity: 1 },
|
|
263
|
+
transition: { duration: animationDuration },
|
|
264
|
+
},
|
|
265
|
+
scale: {
|
|
266
|
+
initial: { scale: 0, opacity: 0 },
|
|
267
|
+
animate: { scale: 1, opacity: 1 },
|
|
268
|
+
transition: { duration: animationDuration },
|
|
269
|
+
},
|
|
270
|
+
slide: {
|
|
271
|
+
initial: { x: -20, opacity: 0 },
|
|
272
|
+
animate: { x: 0, opacity: 1 },
|
|
273
|
+
transition: { duration: animationDuration },
|
|
274
|
+
},
|
|
275
|
+
none: {
|
|
276
|
+
initial: {},
|
|
277
|
+
animate: {},
|
|
278
|
+
transition: {},
|
|
279
|
+
},
|
|
280
|
+
}
|
|
351
281
|
|
|
352
|
-
|
|
353
|
-
<div className="text-xs text-muted-foreground">
|
|
354
|
-
Updated {formatDate(repo.updated_at)}
|
|
355
|
-
</div>
|
|
356
|
-
</div>
|
|
357
|
-
</CardContent>
|
|
358
|
-
</Card>
|
|
359
|
-
</motion.div>
|
|
360
|
-
))}
|
|
361
|
-
</AnimatePresence>
|
|
362
|
-
</div>
|
|
282
|
+
const selectedAnimation = animationVariants[animation]
|
|
363
283
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
284
|
+
return (
|
|
285
|
+
<motion.div
|
|
286
|
+
{...selectedAnimation}
|
|
287
|
+
className={cn("w-full", className)}
|
|
288
|
+
>
|
|
289
|
+
{renderVariant()}
|
|
290
|
+
|
|
291
|
+
{/* Rate limit warning */}
|
|
292
|
+
{rateLimitInfo && rateLimitInfo.remaining < 10 && (
|
|
293
|
+
<div className="mt-4 p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-md text-sm">
|
|
294
|
+
<p className="text-yellow-800 dark:text-yellow-200">
|
|
295
|
+
⚠️ Low API rate limit: {rateLimitInfo.remaining} requests remaining.
|
|
296
|
+
{token ? "" : " Consider adding a GitHub token for higher limits."}
|
|
297
|
+
</p>
|
|
370
298
|
</div>
|
|
371
|
-
|
|
372
|
-
</
|
|
299
|
+
)}
|
|
300
|
+
</motion.div>
|
|
373
301
|
)
|
|
374
302
|
}
|
|
375
303
|
|
|
@@ -377,8 +305,6 @@ export const GitHubStars: React.FC<GitHubStarsProps> = ({ className, ...props })
|
|
|
377
305
|
// Check if we're in docs mode or have pro access
|
|
378
306
|
const { hasProAccess, isLoading } = useSubscription()
|
|
379
307
|
|
|
380
|
-
// In docs mode, always show the component
|
|
381
|
-
|
|
382
308
|
// If not in docs mode and no pro access, show upgrade prompt
|
|
383
309
|
if (!isLoading && !hasProAccess) {
|
|
384
310
|
return (
|
|
@@ -409,4 +335,5 @@ export const GitHubStars: React.FC<GitHubStarsProps> = ({ className, ...props })
|
|
|
409
335
|
return <GitHubStarsInternal className={className} {...props} />
|
|
410
336
|
}
|
|
411
337
|
|
|
412
|
-
export type { GitHubRepository, GitHubStarsProps }
|
|
338
|
+
export type { GitHubRepository, GitHubStarsProps } from "./types"
|
|
339
|
+
export { LANGUAGE_COLORS } from "./github-api"
|