Skip to content

Commit 9c968f2

Browse files
committed
add npm stats to docs
1 parent 393377e commit 9c968f2

4 files changed

Lines changed: 642 additions & 9 deletions

File tree

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
'use client';
2+
3+
import { useState, useEffect } from 'react';
4+
import {
5+
BarChart,
6+
Bar,
7+
PieChart,
8+
Pie,
9+
Cell,
10+
XAxis,
11+
YAxis,
12+
CartesianGrid,
13+
Tooltip,
14+
ResponsiveContainer,
15+
} from 'recharts';
16+
import dayjs from 'dayjs';
17+
import { Shimmer } from '@shimmer-from-structure/react';
18+
19+
interface DownloadData {
20+
downloads: number;
21+
day: string;
22+
}
23+
24+
interface PackageStats {
25+
package: string;
26+
total: number;
27+
downloads: DownloadData[];
28+
}
29+
30+
const PACKAGES = [
31+
'@shimmer-from-structure/react',
32+
'@shimmer-from-structure/vue',
33+
'@shimmer-from-structure/svelte',
34+
'@shimmer-from-structure/angular',
35+
'@shimmer-from-structure/solid',
36+
'@shimmer-from-structure/core',
37+
'shimmer-from-structure',
38+
];
39+
40+
// Template data for shimmer skeleton
41+
const statsTemplate: PackageStats[] = PACKAGES.map((pkg) => ({
42+
package: pkg,
43+
total: 900000,
44+
downloads: [],
45+
}));
46+
47+
async function fetchPackageDownloads(packageName: string): Promise<PackageStats> {
48+
const startDate = '2026-01-19';
49+
const endDate = dayjs().format('YYYY-MM-DD');
50+
const url = `https://api.npmjs.org/downloads/range/${startDate}:${endDate}/${packageName}`;
51+
52+
const response = await fetch(url);
53+
const data = await response.json();
54+
55+
const total =
56+
data.downloads?.reduce((sum: number, day: DownloadData) => sum + day.downloads, 0) || 0;
57+
58+
return {
59+
package: packageName,
60+
total,
61+
downloads: data.downloads || [],
62+
};
63+
}
64+
65+
// Separate component that receives stats as props
66+
const StatsContent = ({ stats }: { stats: PackageStats[] }) => {
67+
const totalDownloads = stats.reduce((sum, pkg) => sum + pkg.total, 0);
68+
69+
// Prepare chart data - bar chart with total downloads per package
70+
const chartData = stats.map((pkg) => ({
71+
name: pkg.package
72+
.replace('@shimmer-from-structure/', '')
73+
.replace('shimmer-from-structure', 'main'),
74+
downloads: pkg.total,
75+
}));
76+
77+
// Prepare pie chart data - exclude core package
78+
const pieData = stats
79+
.filter((pkg) => !pkg.package.includes('/core'))
80+
.map((pkg) => ({
81+
name: pkg.package
82+
.replace('@shimmer-from-structure/', '')
83+
.replace('shimmer-from-structure', 'main'),
84+
value: pkg.total,
85+
}));
86+
87+
const COLORS = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#14B8A6'];
88+
89+
return (
90+
<div className="w-full">
91+
<div className="text-center mb-8">
92+
<h3 className="text-2xl font-bold mb-2 w-fit mx-auto">NPM Downloads</h3>
93+
<p className="text-4xl font-bold text-gray-900 dark:text-white w-fit mx-auto">
94+
{totalDownloads.toLocaleString()}
95+
</p>
96+
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1 w-fit mx-auto">
97+
Total downloads since January 2026
98+
</p>
99+
</div>
100+
101+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
102+
{stats.map((pkg) => (
103+
<div
104+
key={pkg.package}
105+
className="p-4 border border-gray-200 dark:border-gray-800 rounded-lg bg-white dark:bg-gray-900"
106+
>
107+
<div className="text-xs text-gray-600 dark:text-gray-400 mb-1">
108+
{pkg.package
109+
.replace('@shimmer-from-structure/', '')
110+
.replace('shimmer-from-structure', 'main')}
111+
</div>
112+
<div className="text-xl font-bold">{pkg.total.toLocaleString()}</div>
113+
</div>
114+
))}
115+
</div>
116+
117+
<div className="grid md:grid-cols-2 gap-6">
118+
<div className="w-full h-[400px] bg-white dark:bg-gray-900 p-4 border border-gray-200 dark:border-gray-800 rounded-lg">
119+
<h4 className="text-center font-semibold mb-4 w-fit mx-auto">
120+
Total Downloads by Package
121+
</h4>
122+
<ResponsiveContainer width="100%" height="100%">
123+
<BarChart data={chartData}>
124+
<CartesianGrid strokeDasharray="3 3" stroke="#374151" />
125+
<XAxis
126+
dataKey="name"
127+
stroke="#9CA3AF"
128+
tick={{ fill: '#9CA3AF', fontSize: 12 }}
129+
angle={-45}
130+
textAnchor="end"
131+
height={80}
132+
/>
133+
<YAxis stroke="#9CA3AF" tick={{ fill: '#9CA3AF' }} />
134+
<Tooltip
135+
contentStyle={{
136+
backgroundColor: '#1F2937',
137+
border: '1px solid #374151',
138+
borderRadius: '8px',
139+
color: '#fff',
140+
}}
141+
formatter={(value) => (typeof value === 'number' ? value.toLocaleString() : value)}
142+
/>
143+
<Bar dataKey="downloads" fill="#3B82F6" radius={[8, 8, 0, 0]} />
144+
</BarChart>
145+
</ResponsiveContainer>
146+
</div>
147+
148+
<div className="w-full h-[400px] bg-white dark:bg-gray-900 p-4 border border-gray-200 dark:border-gray-800 rounded-lg">
149+
<h4 className="text-center font-semibold mb-4 w-fit mx-auto">
150+
Framework Distribution (excluding core)
151+
</h4>
152+
<ResponsiveContainer width="100%" height="100%">
153+
<PieChart>
154+
<Pie
155+
data={pieData}
156+
cx="50%"
157+
cy="50%"
158+
labelLine={false}
159+
label={({ name, percent }) => `${name} ${((percent ?? 0) * 100).toFixed(0)}%`}
160+
outerRadius={120}
161+
fill="#8884d8"
162+
dataKey="value"
163+
>
164+
{pieData.map((entry, index) => (
165+
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
166+
))}
167+
</Pie>
168+
<Tooltip
169+
contentStyle={{
170+
backgroundColor: '#1F2937',
171+
border: '1px solid #374151',
172+
borderRadius: '8px',
173+
color: '#fff',
174+
}}
175+
formatter={(value) => (typeof value === 'number' ? value.toLocaleString() : value)}
176+
/>
177+
</PieChart>
178+
</ResponsiveContainer>
179+
</div>
180+
</div>
181+
</div>
182+
);
183+
};
184+
185+
export function NpmDownloadStats({ showShimmerDemo }: { showShimmerDemo?: boolean }) {
186+
const [stats, setStats] = useState<PackageStats[]>([]);
187+
const [loading, setLoading] = useState(true);
188+
const [error, setError] = useState<string | null>(null);
189+
190+
useEffect(() => {
191+
async function loadStats() {
192+
try {
193+
setLoading(true);
194+
const results = await Promise.all(PACKAGES.map((pkg) => fetchPackageDownloads(pkg)));
195+
setStats(results);
196+
} catch (err) {
197+
setError(err instanceof Error ? err.message : 'Failed to load download stats');
198+
} finally {
199+
setLoading(false);
200+
}
201+
}
202+
203+
loadStats();
204+
}, []);
205+
206+
if (error) {
207+
return <div className="w-full p-8 text-center text-red-600 dark:text-red-400">{error}</div>;
208+
}
209+
210+
return (
211+
<Shimmer loading={loading || showShimmerDemo || false} templateProps={{ stats: statsTemplate }}>
212+
<StatsContent stats={stats.length > 0 ? stats : statsTemplate} />
213+
</Shimmer>
214+
);
215+
}

docs/app/page.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Link from 'next/link';
66
import { Shimmer } from '@shimmer-from-structure/react';
77
import { Header } from './components/Header';
88
import { Footer } from './components/Footer';
9+
import { NpmDownloadStats } from './components/NpmDownloadStats';
910

1011
export default function Home() {
1112
const [loading, setLoading] = useState(true);
@@ -32,8 +33,8 @@ export default function Home() {
3233
const backgroundColor = isDark ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)';
3334

3435
return (
35-
<Shimmer loading={loading} shimmerColor={shimmerColor} backgroundColor={backgroundColor}>
36-
<div className="min-h-screen flex flex-col">
36+
<div className="min-h-screen flex flex-col">
37+
<Shimmer loading={loading} shimmerColor={shimmerColor} backgroundColor={backgroundColor}>
3738
<Header />
3839

3940
{/* Demo Button */}
@@ -161,8 +162,17 @@ export default function Home() {
161162
</div>
162163
</div>
163164
</section>
165+
</Shimmer>
164166

165-
{/* Quick Example */}
167+
{/* NPM Download Stats */}
168+
<section className="px-6 py-20">
169+
<div className="max-w-6xl mx-auto">
170+
<NpmDownloadStats showShimmerDemo={loading} />
171+
</div>
172+
</section>
173+
174+
{/* Quick Example */}
175+
<Shimmer loading={loading} shimmerColor={shimmerColor} backgroundColor={backgroundColor}>
166176
<section className="px-6 py-20 bg-gray-50 dark:bg-gray-900/50">
167177
<div className="max-w-4xl mx-auto">
168178
<h2 className="text-3xl font-bold text-center mb-12 w-fit mx-auto">Quick Example</h2>
@@ -192,7 +202,7 @@ function UserCard() {
192202
</section>
193203

194204
<Footer />
195-
</div>
196-
</Shimmer>
205+
</Shimmer>
206+
</div>
197207
);
198208
}

0 commit comments

Comments
 (0)