1
0

github-stats.tsx 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. 'use client';
  2. import { useEffect, useState } from 'react';
  3. import { GitFork, Star, Tag } from 'lucide-react';
  4. import { fetchGitHubStats, formatCount, type GitHubStats } from '@/lib/github-stats';
  5. /**
  6. * Stars / forks / latest-release row. Renders the build-time numbers
  7. * immediately (no layout shift, works without JS), then swaps in live ones
  8. * from the GitHub API after hydration.
  9. */
  10. export function GitHubStatsRow({
  11. initial,
  12. labels,
  13. }: {
  14. initial: GitHubStats;
  15. labels: { stars: string; forks: string; latest: string };
  16. }) {
  17. const [stats, setStats] = useState(initial);
  18. useEffect(() => {
  19. let cancelled = false;
  20. // Plain fetch, no custom headers — keeps the request preflight-free.
  21. void fetchGitHubStats().then((live) => {
  22. if (cancelled || !live) return;
  23. setStats((prev) => ({ ...live, latestVersion: live.latestVersion || prev.latestVersion }));
  24. });
  25. return () => {
  26. cancelled = true;
  27. };
  28. }, []);
  29. return (
  30. <dl className="mt-8 flex flex-wrap items-center justify-center gap-x-8 gap-y-3 text-sm">
  31. <Stat icon={<Star className="size-4" aria-hidden />} label={labels.stars}>
  32. {formatCount(stats.stars)}
  33. </Stat>
  34. <Stat icon={<GitFork className="size-4" aria-hidden />} label={labels.forks}>
  35. {formatCount(stats.forks)}
  36. </Stat>
  37. <Stat icon={<Tag className="size-4" aria-hidden />} label={labels.latest}>
  38. {stats.latestVersion}
  39. </Stat>
  40. </dl>
  41. );
  42. }
  43. function Stat({
  44. icon,
  45. label,
  46. children,
  47. }: {
  48. icon: React.ReactNode;
  49. label: string;
  50. children: React.ReactNode;
  51. }) {
  52. return (
  53. <div className="inline-flex items-center gap-2">
  54. <span className="text-brand">{icon}</span>
  55. <dt className="sr-only">{label}</dt>
  56. <dd>
  57. <span className="font-semibold">{children}</span>{' '}
  58. <span className="text-fd-muted-foreground">{label}</span>
  59. </dd>
  60. </div>
  61. );
  62. }