-
-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathGitHubStarButton.tsx
More file actions
117 lines (102 loc) · 4.46 KB
/
GitHubStarButton.tsx
File metadata and controls
117 lines (102 loc) · 4.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
'use client';
import { Star } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { useEffect, useState } from 'react';
const STORAGE_KEY = 'github-stars';
function getStoredStars(): number | null {
if (typeof sessionStorage === 'undefined') return null;
const stored = sessionStorage.getItem(STORAGE_KEY);
if (!stored) return null;
const parsed = parseInt(stored, 10);
return Number.isNaN(parsed) ? null : parsed;
}
interface GitHubStarButtonProps {
className?: string;
}
export function GitHubStarButton({ className = '' }: GitHubStarButtonProps) {
const t = useTranslations('aria');
const [storedStars] = useState(getStoredStars);
const [displayCount, setDisplayCount] = useState(storedStars ?? 0);
const [finalCount, setFinalCount] = useState<number | null>(storedStars);
const githubUrl = 'https://github.com/DevLoversTeam/devlovers.net';
useEffect(() => {
if (storedStars !== null) return;
const fetchStars = async () => {
try {
const response = await fetch('/api/stats');
if (response.ok) {
const data = await response.json();
const starsStr =
typeof data?.githubStars === 'string'
? data.githubStars
: String(data?.githubStars ?? '0');
let starsNum = 0;
const normalized = starsStr.replace(/,/g, '').toLowerCase();
if (normalized.includes('k+')) {
starsNum = Math.floor(
parseFloat(normalized.replace('k+', '')) * 1000
);
} else {
starsNum = parseInt(normalized, 10);
}
setFinalCount(starsNum);
}
} catch (error) {
console.error('Failed to fetch GitHub stars:', error);
setFinalCount(0);
}
};
fetchStars();
}, []);
useEffect(() => {
if (finalCount === null || storedStars !== null) return;
const duration = 2000;
const steps = 60;
const increment = finalCount / steps;
let current = 0;
const timer = setInterval(() => {
current += increment;
if (current >= finalCount) {
setDisplayCount(finalCount);
clearInterval(timer);
try {
sessionStorage.setItem(STORAGE_KEY, String(finalCount));
} catch {}
} else {
setDisplayCount(Math.floor(current));
}
}, duration / steps);
return () => clearInterval(timer);
}, [finalCount, storedStars]);
const formatStarCount = (count: number): string => {
return count.toLocaleString();
};
return (
<a
href={githubUrl}
target="_blank"
rel="noopener noreferrer"
aria-label={t('starOnGithub', { count: displayCount })}
className={`group hidden h-9 items-center justify-center gap-1.5 rounded-full border border-gray-200 bg-white/50 px-3 text-sm font-medium text-muted-foreground shadow-sm backdrop-blur-sm transition-all duration-200 hover:border-gray-300 hover:bg-white hover:text-foreground hover:shadow min-[375px]:inline-flex dark:border-neutral-800 dark:bg-neutral-900/50 dark:hover:border-neutral-700 dark:hover:bg-neutral-900 ${className}`}
>
<svg
viewBox="0 0 16 16"
className="h-4 w-4 shrink-0 transition-transform duration-300 group-hover:scale-110 group-active:scale-110"
fill="currentColor"
aria-hidden="true"
>
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
</svg>
<span className="flex items-center gap-1">
<span className="min-w-[2ch] tabular-nums">
{formatStarCount(displayCount)}
</span>
<Star
className="h-3.5 w-3.5 shrink-0 text-muted-foreground transition-[transform,color] duration-300 group-hover:rotate-12 group-hover:text-yellow-400 group-hover:drop-shadow-[0_0_6px_rgba(250,204,21,0.5)] group-active:rotate-12 group-active:text-yellow-400 group-active:drop-shadow-[0_0_6px_rgba(250,204,21,0.5)]"
fill="currentColor"
aria-hidden="true"
/>
</span>
</a>
);
}