22import type { GetStaticPaths } from ' astro' ;
33import BaseLayout from ' ../../layouts/BaseLayout.astro' ;
44import ProjectCard from ' ../../components/ProjectCard.astro' ;
5- import Pagination from ' ../../components/Pagination.astro' ;
65import { getProjectsWithStats , getProjectSlug } from ' ../../lib/projects' ;
76
8- export const getStaticPaths = (async ({ paginate } ) => {
7+ export const getStaticPaths = (async () => {
98 const allProjects = await getProjectsWithStats ();
109 // Sort by GitHub stars (highest first), using live stats when available
1110 const sortedProjects = allProjects .sort ((a , b ) => {
@@ -14,10 +13,32 @@ export const getStaticPaths = (async ({ paginate }) => {
1413 return bStars - aStars ;
1514 });
1615
17- return paginate ( sortedProjects , { pageSize: 9 }) ;
16+ return [{ params: { page: undefined }, props: { projects: sortedProjects } }] ;
1817}) satisfies GetStaticPaths ;
1918
20- const { page } = Astro .props ;
19+ const { projects } = Astro .props ;
20+
21+ // Category labels for filter buttons
22+ const categoryLabels: Record <string , string > = {
23+ ' vs-extension' : ' VS Extensions' ,
24+ ' vscode-extension' : ' VS Code Extensions' ,
25+ ' github-action' : ' GitHub Actions' ,
26+ ' cli-tool' : ' CLI Tools' ,
27+ ' nuget-package' : ' NuGet Packages' ,
28+ ' desktop-app' : ' Desktop Apps' ,
29+ ' documentation' : ' Documentation' ,
30+ };
31+
32+ // Get unique categories from projects, sorted by count (descending)
33+ const categoryCounts = projects .reduce ((acc , project ) => {
34+ const cat = project .data .category ;
35+ acc [cat ] = (acc [cat ] || 0 ) + 1 ;
36+ return acc ;
37+ }, {} as Record <string , number >);
38+
39+ const categories = Object .entries (categoryCounts )
40+ .sort ((a , b ) => b [1 ] - a [1 ])
41+ .map (([cat ]) => cat );
2142---
2243
2344<BaseLayout title =" Open Source Projects" description =" Open source projects by Calvin Allen" image =" /images/projects-og.png" >
@@ -28,8 +49,28 @@ const { page } = Astro.props;
2849 A collection of open source projects I've created and maintain, sorted by GitHub stars.
2950 </p >
3051
31- <div class =" grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" >
32- { page .data .map (project => {
52+ <!-- Filter Buttons -->
53+ <div class =" flex flex-wrap gap-2 mb-8" id =" filter-buttons" >
54+ <button
55+ type =" button"
56+ class =" filter-btn px-4 py-2 rounded-full text-sm font-medium transition-colors bg-primary text-white"
57+ data-category =" all"
58+ >
59+ All ({ projects .length } )
60+ </button >
61+ { categories .map (cat => (
62+ <button
63+ type = " button"
64+ class = " filter-btn px-4 py-2 rounded-full text-sm font-medium transition-colors bg-background-2 text-text-muted hover:bg-background-3"
65+ data-category = { cat }
66+ >
67+ { categoryLabels [cat ] || cat } ({ categoryCounts [cat ]} )
68+ </button >
69+ ))}
70+ </div >
71+
72+ <div class =" grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" id =" projects-grid" >
73+ { projects .map (project => {
3374 const stars = project .githubStats ?.stars ?? project .data .stars ;
3475 const downloads = project .marketplaceStats ?.downloads ?? project .marketplaceStats ?.installs ?? project .data .downloads ;
3576 return (
@@ -42,18 +83,48 @@ const { page } = Astro.props;
4283 image = { project .resolvedImage }
4384 stars = { stars }
4485 downloads = { downloads }
86+ category = { project .data .category }
4587 />
4688 );
4789 })}
4890 </div >
49-
50- { page .lastPage > 1 && (
51- <Pagination
52- currentPage = { page .currentPage }
53- totalPages = { page .lastPage }
54- baseUrl = " /projects/"
55- />
56- )}
5791 </div >
5892 </section >
5993</BaseLayout >
94+
95+ <script >
96+ function initProjectFilters() {
97+ const filterButtons = document.querySelectorAll('.filter-btn');
98+ const projectCards = document.querySelectorAll('.project-card');
99+
100+ filterButtons.forEach(button => {
101+ button.addEventListener('click', () => {
102+ const category = (button as HTMLElement).dataset.category;
103+
104+ // Update active button styling
105+ filterButtons.forEach(btn => {
106+ btn.classList.remove('bg-primary', 'text-white');
107+ btn.classList.add('bg-background-2', 'text-text-muted', 'hover:bg-background-3');
108+ });
109+ button.classList.remove('bg-background-2', 'text-text-muted', 'hover:bg-background-3');
110+ button.classList.add('bg-primary', 'text-white');
111+
112+ // Filter project cards
113+ projectCards.forEach(card => {
114+ const cardCategory = (card as HTMLElement).dataset.category;
115+ if (category === 'all' || cardCategory === category) {
116+ (card as HTMLElement).style.display = '';
117+ } else {
118+ (card as HTMLElement).style.display = 'none';
119+ }
120+ });
121+ });
122+ });
123+ }
124+
125+ // Run on page load
126+ document.addEventListener('DOMContentLoaded', initProjectFilters);
127+
128+ // Also run on Astro page transitions (View Transitions API)
129+ document.addEventListener('astro:page-load', initProjectFilters);
130+ </script >
0 commit comments