Skip to content

Commit e30ab53

Browse files
CopilotBoshen
andauthored
Add NPM Downloads page displaying weekly download statistics in compact centered list format (#62)
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: Boshen <[email protected]>
1 parent b9167c6 commit e30ab53

File tree

5 files changed

+201
-1
lines changed

5 files changed

+201
-1
lines changed

apps/dashboard/src/App.css

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,68 @@
323323
border-bottom: 2px solid #e2e8f0;
324324
}
325325

326+
/* NPM Downloads List */
327+
.npm-downloads-list-container {
328+
margin: 0 auto 2rem auto;
329+
border-radius: 0.5rem;
330+
border: 1px solid #e2e8f0;
331+
background: white;
332+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
333+
max-width: 500px;
334+
width: fit-content;
335+
}
336+
337+
.npm-downloads-list {
338+
list-style: none;
339+
padding: 0;
340+
margin: 0;
341+
}
342+
343+
.npm-download-item {
344+
display: flex;
345+
align-items: center;
346+
justify-content: space-between;
347+
padding: 0.5rem 1rem;
348+
border-bottom: 1px solid #e2e8f0;
349+
transition: all 0.2s ease;
350+
cursor: pointer;
351+
gap: 0.75rem;
352+
min-width: fit-content;
353+
}
354+
355+
.npm-download-item:hover {
356+
background: #f8fafc;
357+
}
358+
359+
.npm-download-item:focus {
360+
outline: 2px solid #3b82f6;
361+
outline-offset: -2px;
362+
background: #f8fafc;
363+
}
364+
365+
.npm-download-item:last-child {
366+
border-bottom: none;
367+
}
368+
369+
.npm-download-item .package-name {
370+
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
371+
font-size: 0.875rem;
372+
font-weight: 500;
373+
color: #374151;
374+
background: #f1f5f9;
375+
padding: 0.25rem 0.5rem;
376+
border-radius: 0.25rem;
377+
border: 1px solid #cbd5e1;
378+
min-width: fit-content;
379+
white-space: nowrap;
380+
}
381+
382+
.npm-download-item .download-badge {
383+
height: auto;
384+
max-height: 20px;
385+
flex-shrink: 0;
386+
}
387+
326388
/* Responsive Design */
327389
@media (max-width: 768px) {
328390
.page-nav {
@@ -347,6 +409,27 @@
347409
grid-template-columns: 1fr;
348410
}
349411

412+
.npm-downloads-list-container {
413+
margin-bottom: 1rem;
414+
}
415+
416+
.npm-download-item {
417+
padding: 0.75rem;
418+
flex-direction: column;
419+
align-items: flex-start;
420+
gap: 0.5rem;
421+
}
422+
423+
.npm-download-item .package-name {
424+
font-size: 0.875rem;
425+
padding: 0.375rem 0.5rem;
426+
}
427+
428+
.npm-download-item .download-badge {
429+
max-width: 100%;
430+
height: auto;
431+
}
432+
350433
.library-charts-grid {
351434
padding: 0 1rem;
352435
gap: 1rem;

apps/dashboard/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom';
22
import './App.css';
33
import Layout from './components/Layout';
44
import MinificationBenchmarksPage from './pages/MinificationBenchmarksPage';
5+
import NpmDownloadsPage from './pages/NpmDownloadsPage';
56
import RolldownStatsPage from './pages/RolldownStatsPage';
67

78
function App() {
@@ -11,6 +12,7 @@ function App() {
1112
<Route path='/' element={<Layout />}>
1213
<Route index element={<RolldownStatsPage />} />
1314
<Route path='minification' element={<MinificationBenchmarksPage />} />
15+
<Route path='npm-downloads' element={<NpmDownloadsPage />} />
1416
</Route>
1517
</Routes>
1618
</BrowserRouter>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
interface NpmDownloadsProps {}
2+
3+
// List of npm packages to display download counts for
4+
const packages = [
5+
'vite',
6+
'rolldown-vite',
7+
'rolldown',
8+
'tsdown',
9+
'oxlint',
10+
'oxc-parser',
11+
'oxc-transform',
12+
'oxc-minify',
13+
'oxc-resolver'
14+
];
15+
16+
function NpmDownloads({}: NpmDownloadsProps) {
17+
const handleCardClick = (packageName: string) => {
18+
const npmUrl = `https://www.npmjs.com/package/${packageName}`;
19+
window.open(npmUrl, '_blank', 'noopener,noreferrer');
20+
};
21+
22+
return (
23+
<>
24+
<main className='dashboard-main'>
25+
<div className='chart-container'>
26+
<h2>NPM Weekly Downloads</h2>
27+
28+
<div className='npm-downloads-list-container'>
29+
<ul className='npm-downloads-list'>
30+
{packages.map((packageName) => (
31+
<li
32+
key={packageName}
33+
className='npm-download-item'
34+
onClick={() => handleCardClick(packageName)}
35+
role="button"
36+
tabIndex={0}
37+
onKeyDown={(e) => {
38+
if (e.key === 'Enter' || e.key === ' ') {
39+
e.preventDefault();
40+
handleCardClick(packageName);
41+
}
42+
}}
43+
>
44+
<span className='package-name'>{packageName}</span>
45+
<img
46+
className='download-badge'
47+
src={`https://img.shields.io/npm/dw/${packageName}?label=npm`}
48+
alt={`Weekly downloads for ${packageName}`}
49+
loading="lazy"
50+
/>
51+
</li>
52+
))}
53+
</ul>
54+
</div>
55+
</div>
56+
57+
<div className='stats-grid'>
58+
<div className='stat-card'>
59+
<h3>Total Packages</h3>
60+
<p className='stat-value'>{packages.length}</p>
61+
<span className='stat-change positive'>NPM Packages</span>
62+
</div>
63+
<div className='stat-card'>
64+
<h3>Registry</h3>
65+
<p className='stat-value'>NPM</p>
66+
<span className='stat-change positive'>Public Registry</span>
67+
</div>
68+
<div className='stat-card'>
69+
<h3>Update Frequency</h3>
70+
<p className='stat-value'>Weekly</p>
71+
<span className='stat-change positive'>Auto Updated</span>
72+
</div>
73+
<div className='stat-card'>
74+
<h3>Data Source</h3>
75+
<p className='stat-value'>Shields.io</p>
76+
<span className='stat-change positive'>Live Data</span>
77+
</div>
78+
</div>
79+
</main>
80+
</>
81+
);
82+
}
83+
84+
export default NpmDownloads;

apps/dashboard/src/components/Layout.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BarChart3, Package, Zap } from 'lucide-react';
1+
import { BarChart3, Download, Package, Zap } from 'lucide-react';
22
import { Link, Outlet, useLocation } from 'react-router-dom';
33

44
function Layout() {
@@ -30,6 +30,13 @@ function Layout() {
3030
<Zap size={20} />
3131
Minification Benchmarks
3232
</Link>
33+
<Link
34+
to='/npm-downloads'
35+
className={`page-button ${location.pathname === '/npm-downloads' ? 'active' : ''}`}
36+
>
37+
<Download size={20} />
38+
NPM Downloads
39+
</Link>
3340
</nav>
3441

3542
{/* Render the current route's component */}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { BarChart3 } from 'lucide-react';
2+
import NpmDownloads from '../NpmDownloads';
3+
4+
function NpmDownloadsPage() {
5+
return (
6+
<div className='dashboard'>
7+
<header className='dashboard-header'>
8+
<div className='header-content'>
9+
<div className='logo'>
10+
<BarChart3 size={28} />
11+
<h1>NPM Downloads</h1>
12+
</div>
13+
<p className='header-subtitle'>
14+
Weekly download statistics for key packages
15+
</p>
16+
</div>
17+
</header>
18+
19+
<NpmDownloads />
20+
</div>
21+
);
22+
}
23+
24+
export default NpmDownloadsPage;

0 commit comments

Comments
 (0)