@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moontra/moonui-pro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.1",
|
|
4
4
|
"description": "Premium React components for MoonUI - Advanced UI library with 50+ pro components including performance, interactive, and gesture components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
"@radix-ui/react-toast": "^1.2.14",
|
|
93
93
|
"@radix-ui/react-tooltip": "^1.2.7",
|
|
94
94
|
"@tanstack/react-table": "^8.20.5",
|
|
95
|
+
"canvas-confetti": "^1.9.3",
|
|
95
96
|
"class-variance-authority": "^0.7.0",
|
|
96
97
|
"clsx": "^2.1.1",
|
|
97
98
|
"date-fns": "^3.6.0",
|
|
@@ -114,6 +115,7 @@
|
|
|
114
115
|
"@testing-library/jest-dom": "^6.6.3",
|
|
115
116
|
"@testing-library/react": "^16.3.0",
|
|
116
117
|
"@testing-library/user-event": "^14.6.1",
|
|
118
|
+
"@types/canvas-confetti": "^1.9.0",
|
|
117
119
|
"@types/jest": "^30.0.0",
|
|
118
120
|
"@types/node": "^18.16.0",
|
|
119
121
|
"@types/react": "^19.0.0",
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { GitHubRepository, GitHubStats, GitHubActivity, LanguageStats, RateLimitInfo, StarHistory } from "./types"
|
|
2
|
+
|
|
3
|
+
// Cache management
|
|
4
|
+
const cache = new Map<string, { data: any; timestamp: number; expiresAt: number }>()
|
|
5
|
+
|
|
6
|
+
// Language colors
|
|
7
|
+
export const LANGUAGE_COLORS: Record<string, string> = {
|
|
8
|
+
JavaScript: "#f7df1e",
|
|
9
|
+
TypeScript: "#3178c6",
|
|
10
|
+
Python: "#3776ab",
|
|
11
|
+
Java: "#ed8b00",
|
|
12
|
+
"C++": "#00599c",
|
|
13
|
+
"C#": "#239120",
|
|
14
|
+
Go: "#00add8",
|
|
15
|
+
Rust: "#000000",
|
|
16
|
+
Swift: "#fa7343",
|
|
17
|
+
Kotlin: "#7f52ff",
|
|
18
|
+
PHP: "#777bb4",
|
|
19
|
+
Ruby: "#cc342d",
|
|
20
|
+
HTML: "#e34f26",
|
|
21
|
+
CSS: "#1572b6",
|
|
22
|
+
Vue: "#4fc08d",
|
|
23
|
+
React: "#61dafb",
|
|
24
|
+
Shell: "#89e051",
|
|
25
|
+
Dart: "#0175c2",
|
|
26
|
+
Elixir: "#6e4a7e",
|
|
27
|
+
Scala: "#c22d40",
|
|
28
|
+
R: "#198ce7",
|
|
29
|
+
Julia: "#9558b2",
|
|
30
|
+
Lua: "#000080",
|
|
31
|
+
Perl: "#39457e",
|
|
32
|
+
Haskell: "#5e5086",
|
|
33
|
+
Clojure: "#db5855",
|
|
34
|
+
Erlang: "#b83998",
|
|
35
|
+
Objective_C: "#438eff",
|
|
36
|
+
// Add more as needed
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// API base URL
|
|
40
|
+
const API_BASE = "https://api.github.com"
|
|
41
|
+
|
|
42
|
+
// Helper to make authenticated requests
|
|
43
|
+
async function githubFetch(url: string, token?: string): Promise<Response> {
|
|
44
|
+
const headers: HeadersInit = {
|
|
45
|
+
Accept: "application/vnd.github.v3+json",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (token) {
|
|
49
|
+
headers.Authorization = `token ${token}`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const response = await fetch(url, { headers })
|
|
53
|
+
|
|
54
|
+
if (response.status === 403 && response.headers.get("X-RateLimit-Remaining") === "0") {
|
|
55
|
+
const resetTime = parseInt(response.headers.get("X-RateLimit-Reset") || "0") * 1000
|
|
56
|
+
const resetDate = new Date(resetTime)
|
|
57
|
+
throw new Error(`GitHub API rate limit exceeded. Resets at ${resetDate.toLocaleTimeString()}`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return response
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Get rate limit info
|
|
68
|
+
export async function getRateLimitInfo(token?: string): Promise<RateLimitInfo> {
|
|
69
|
+
const cacheKey = `rate-limit-${token || "public"}`
|
|
70
|
+
const cached = cache.get(cacheKey)
|
|
71
|
+
|
|
72
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
73
|
+
return cached.data
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const response = await githubFetch(`${API_BASE}/rate_limit`, token)
|
|
77
|
+
const data = await response.json()
|
|
78
|
+
|
|
79
|
+
const rateLimitInfo: RateLimitInfo = {
|
|
80
|
+
limit: data.rate.limit,
|
|
81
|
+
remaining: data.rate.remaining,
|
|
82
|
+
reset: data.rate.reset * 1000,
|
|
83
|
+
used: data.rate.used,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
cache.set(cacheKey, {
|
|
87
|
+
data: rateLimitInfo,
|
|
88
|
+
timestamp: Date.now(),
|
|
89
|
+
expiresAt: Date.now() + 60000, // Cache for 1 minute
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
return rateLimitInfo
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Fetch user repositories
|
|
96
|
+
export async function fetchUserRepositories(
|
|
97
|
+
username: string,
|
|
98
|
+
token?: string,
|
|
99
|
+
options?: {
|
|
100
|
+
sort?: string
|
|
101
|
+
per_page?: number
|
|
102
|
+
page?: number
|
|
103
|
+
}
|
|
104
|
+
): Promise<GitHubRepository[]> {
|
|
105
|
+
const params = new URLSearchParams({
|
|
106
|
+
sort: options?.sort || "updated",
|
|
107
|
+
per_page: String(options?.per_page || 100),
|
|
108
|
+
page: String(options?.page || 1),
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const cacheKey = `repos-${username}-${params.toString()}`
|
|
112
|
+
const cached = cache.get(cacheKey)
|
|
113
|
+
|
|
114
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
115
|
+
return cached.data
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const response = await githubFetch(`${API_BASE}/users/${username}/repos?${params}`, token)
|
|
119
|
+
const repos = await response.json()
|
|
120
|
+
|
|
121
|
+
cache.set(cacheKey, {
|
|
122
|
+
data: repos,
|
|
123
|
+
timestamp: Date.now(),
|
|
124
|
+
expiresAt: Date.now() + 300000, // Cache for 5 minutes
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
return repos
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Fetch single repository
|
|
131
|
+
export async function fetchRepository(
|
|
132
|
+
owner: string,
|
|
133
|
+
repo: string,
|
|
134
|
+
token?: string
|
|
135
|
+
): Promise<GitHubRepository> {
|
|
136
|
+
const cacheKey = `repo-${owner}-${repo}`
|
|
137
|
+
const cached = cache.get(cacheKey)
|
|
138
|
+
|
|
139
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
140
|
+
return cached.data
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const response = await githubFetch(`${API_BASE}/repos/${owner}/${repo}`, token)
|
|
144
|
+
const repository = await response.json()
|
|
145
|
+
|
|
146
|
+
cache.set(cacheKey, {
|
|
147
|
+
data: repository,
|
|
148
|
+
timestamp: Date.now(),
|
|
149
|
+
expiresAt: Date.now() + 300000, // Cache for 5 minutes
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
return repository
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Fetch repository contributors count
|
|
156
|
+
export async function fetchContributorsCount(
|
|
157
|
+
owner: string,
|
|
158
|
+
repo: string,
|
|
159
|
+
token?: string
|
|
160
|
+
): Promise<number> {
|
|
161
|
+
const cacheKey = `contributors-${owner}-${repo}`
|
|
162
|
+
const cached = cache.get(cacheKey)
|
|
163
|
+
|
|
164
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
165
|
+
return cached.data
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const response = await githubFetch(
|
|
170
|
+
`${API_BASE}/repos/${owner}/${repo}/contributors?per_page=1&anon=true`,
|
|
171
|
+
token
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
// Get total count from Link header
|
|
175
|
+
const linkHeader = response.headers.get("Link")
|
|
176
|
+
if (linkHeader) {
|
|
177
|
+
const match = linkHeader.match(/page=(\d+)>; rel="last"/)
|
|
178
|
+
if (match) {
|
|
179
|
+
const count = parseInt(match[1])
|
|
180
|
+
cache.set(cacheKey, {
|
|
181
|
+
data: count,
|
|
182
|
+
timestamp: Date.now(),
|
|
183
|
+
expiresAt: Date.now() + 3600000, // Cache for 1 hour
|
|
184
|
+
})
|
|
185
|
+
return count
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// If no pagination, count the results
|
|
190
|
+
const contributors = await response.json()
|
|
191
|
+
const count = contributors.length
|
|
192
|
+
|
|
193
|
+
cache.set(cacheKey, {
|
|
194
|
+
data: count,
|
|
195
|
+
timestamp: Date.now(),
|
|
196
|
+
expiresAt: Date.now() + 3600000, // Cache for 1 hour
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
return count
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.error("Failed to fetch contributors:", error)
|
|
202
|
+
return 0
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Fetch repository star history (limited without token)
|
|
207
|
+
export async function fetchStarHistory(
|
|
208
|
+
owner: string,
|
|
209
|
+
repo: string,
|
|
210
|
+
token?: string
|
|
211
|
+
): Promise<StarHistory[]> {
|
|
212
|
+
const cacheKey = `star-history-${owner}-${repo}`
|
|
213
|
+
const cached = cache.get(cacheKey)
|
|
214
|
+
|
|
215
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
216
|
+
return cached.data
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
// This is a simplified version - for full star history you'd need
|
|
221
|
+
// to use the stargazers API with Accept: application/vnd.github.v3.star+json
|
|
222
|
+
// header and paginate through all results
|
|
223
|
+
const response = await githubFetch(`${API_BASE}/repos/${owner}/${repo}`, token)
|
|
224
|
+
const repoData = await response.json()
|
|
225
|
+
|
|
226
|
+
// For now, return current count as single data point
|
|
227
|
+
const history: StarHistory[] = [{
|
|
228
|
+
date: new Date().toISOString(),
|
|
229
|
+
count: repoData.stargazers_count,
|
|
230
|
+
repository: repoData.full_name,
|
|
231
|
+
}]
|
|
232
|
+
|
|
233
|
+
cache.set(cacheKey, {
|
|
234
|
+
data: history,
|
|
235
|
+
timestamp: Date.now(),
|
|
236
|
+
expiresAt: Date.now() + 3600000, // Cache for 1 hour
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
return history
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.error("Failed to fetch star history:", error)
|
|
242
|
+
return []
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Calculate repository statistics
|
|
247
|
+
export function calculateStats(repositories: GitHubRepository[]): GitHubStats {
|
|
248
|
+
const totalStars = repositories.reduce((sum, repo) => sum + repo.stargazers_count, 0)
|
|
249
|
+
const totalForks = repositories.reduce((sum, repo) => sum + repo.forks_count, 0)
|
|
250
|
+
const totalWatchers = repositories.reduce((sum, repo) => sum + repo.watchers_count, 0)
|
|
251
|
+
const totalIssues = repositories.reduce((sum, repo) => sum + repo.open_issues_count, 0)
|
|
252
|
+
|
|
253
|
+
const avgStarsPerRepo = repositories.length > 0 ? totalStars / repositories.length : 0
|
|
254
|
+
|
|
255
|
+
const mostStarredRepo = repositories.reduce((max, repo) =>
|
|
256
|
+
repo.stargazers_count > (max?.stargazers_count || 0) ? repo : max
|
|
257
|
+
, null as GitHubRepository | null)
|
|
258
|
+
|
|
259
|
+
// Calculate language statistics
|
|
260
|
+
const languageMap = new Map<string, number>()
|
|
261
|
+
repositories.forEach(repo => {
|
|
262
|
+
if (repo.language) {
|
|
263
|
+
languageMap.set(repo.language, (languageMap.get(repo.language) || 0) + 1)
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
const totalRepos = repositories.length
|
|
268
|
+
const languages: LanguageStats[] = Array.from(languageMap.entries())
|
|
269
|
+
.map(([language, count]) => ({
|
|
270
|
+
language,
|
|
271
|
+
count,
|
|
272
|
+
percentage: (count / totalRepos) * 100,
|
|
273
|
+
color: LANGUAGE_COLORS[language] || "#6b7280",
|
|
274
|
+
}))
|
|
275
|
+
.sort((a, b) => b.count - a.count)
|
|
276
|
+
|
|
277
|
+
// Generate recent activity (mock for now)
|
|
278
|
+
const recentActivity: GitHubActivity[] = repositories
|
|
279
|
+
.slice(0, 5)
|
|
280
|
+
.map(repo => ({
|
|
281
|
+
type: "star" as const,
|
|
282
|
+
repository: repo.full_name,
|
|
283
|
+
timestamp: repo.updated_at,
|
|
284
|
+
description: `Repository updated`,
|
|
285
|
+
}))
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
totalStars,
|
|
289
|
+
totalForks,
|
|
290
|
+
totalWatchers,
|
|
291
|
+
totalIssues,
|
|
292
|
+
avgStarsPerRepo,
|
|
293
|
+
mostStarredRepo,
|
|
294
|
+
recentActivity,
|
|
295
|
+
languages,
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Format numbers for display
|
|
300
|
+
export function formatNumber(num: number): string {
|
|
301
|
+
if (num >= 1000000) {
|
|
302
|
+
return (num / 1000000).toFixed(1) + "M"
|
|
303
|
+
}
|
|
304
|
+
if (num >= 1000) {
|
|
305
|
+
return (num / 1000).toFixed(1) + "k"
|
|
306
|
+
}
|
|
307
|
+
return num.toString()
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Format date for display
|
|
311
|
+
export function formatDate(dateString: string): string {
|
|
312
|
+
const date = new Date(dateString)
|
|
313
|
+
const now = new Date()
|
|
314
|
+
const diffMs = now.getTime() - date.getTime()
|
|
315
|
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
|
|
316
|
+
|
|
317
|
+
if (diffDays === 0) {
|
|
318
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60))
|
|
319
|
+
if (diffHours === 0) {
|
|
320
|
+
const diffMinutes = Math.floor(diffMs / (1000 * 60))
|
|
321
|
+
return `${diffMinutes} minutes ago`
|
|
322
|
+
}
|
|
323
|
+
return `${diffHours} hours ago`
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (diffDays === 1) return "yesterday"
|
|
327
|
+
if (diffDays < 7) return `${diffDays} days ago`
|
|
328
|
+
if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`
|
|
329
|
+
if (diffDays < 365) return `${Math.floor(diffDays / 30)} months ago`
|
|
330
|
+
|
|
331
|
+
return date.toLocaleDateString("en-US", {
|
|
332
|
+
year: "numeric",
|
|
333
|
+
month: "short",
|
|
334
|
+
day: "numeric",
|
|
335
|
+
})
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Clear cache
|
|
339
|
+
export function clearCache(pattern?: string): void {
|
|
340
|
+
if (pattern) {
|
|
341
|
+
for (const key of cache.keys()) {
|
|
342
|
+
if (key.includes(pattern)) {
|
|
343
|
+
cache.delete(key)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
} else {
|
|
347
|
+
cache.clear()
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Export data as JSON
|
|
352
|
+
export function exportData(data: any, filename: string): void {
|
|
353
|
+
const jsonString = JSON.stringify(data, null, 2)
|
|
354
|
+
const blob = new Blob([jsonString], { type: "application/json" })
|
|
355
|
+
const url = URL.createObjectURL(blob)
|
|
356
|
+
|
|
357
|
+
const link = document.createElement("a")
|
|
358
|
+
link.href = url
|
|
359
|
+
link.download = filename
|
|
360
|
+
document.body.appendChild(link)
|
|
361
|
+
link.click()
|
|
362
|
+
document.body.removeChild(link)
|
|
363
|
+
|
|
364
|
+
URL.revokeObjectURL(url)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Export data as CSV
|
|
368
|
+
export function exportAsCSV(repositories: GitHubRepository[], filename: string): void {
|
|
369
|
+
const headers = [
|
|
370
|
+
"Name",
|
|
371
|
+
"Owner",
|
|
372
|
+
"Stars",
|
|
373
|
+
"Forks",
|
|
374
|
+
"Watchers",
|
|
375
|
+
"Issues",
|
|
376
|
+
"Language",
|
|
377
|
+
"Description",
|
|
378
|
+
"URL",
|
|
379
|
+
"Created",
|
|
380
|
+
"Updated",
|
|
381
|
+
]
|
|
382
|
+
|
|
383
|
+
const rows = repositories.map(repo => [
|
|
384
|
+
repo.name,
|
|
385
|
+
repo.owner.login,
|
|
386
|
+
repo.stargazers_count,
|
|
387
|
+
repo.forks_count,
|
|
388
|
+
repo.watchers_count,
|
|
389
|
+
repo.open_issues_count,
|
|
390
|
+
repo.language || "",
|
|
391
|
+
repo.description || "",
|
|
392
|
+
repo.html_url,
|
|
393
|
+
new Date(repo.created_at).toLocaleDateString(),
|
|
394
|
+
new Date(repo.updated_at).toLocaleDateString(),
|
|
395
|
+
])
|
|
396
|
+
|
|
397
|
+
const csvContent = [
|
|
398
|
+
headers.join(","),
|
|
399
|
+
...rows.map(row => row.map(cell => `"${cell}"`).join(",")),
|
|
400
|
+
].join("\n")
|
|
401
|
+
|
|
402
|
+
const blob = new Blob([csvContent], { type: "text/csv" })
|
|
403
|
+
const url = URL.createObjectURL(blob)
|
|
404
|
+
|
|
405
|
+
const link = document.createElement("a")
|
|
406
|
+
link.href = url
|
|
407
|
+
link.download = filename
|
|
408
|
+
document.body.appendChild(link)
|
|
409
|
+
link.click()
|
|
410
|
+
document.body.removeChild(link)
|
|
411
|
+
|
|
412
|
+
URL.revokeObjectURL(url)
|
|
413
|
+
}
|