22
33import clsx from 'clsx'
44import { Github , Download } from 'lucide-react'
5- import { Fragment } from 'react'
65import useSWR from 'swr'
76import type { BrandsMap } from '~/components/ui/brand'
87import { Brand } from '~/components/ui/brand'
@@ -15,21 +14,85 @@ import type { PROJECTS } from '~/data/projects'
1514import type { GithubRepository , NpmPackage } from '~/types/data'
1615import { fetcher } from '~/utils/misc'
1716
17+ function NpmStats ( { npmPackageName, downloads } : { npmPackageName : string ; downloads : number } ) {
18+ return (
19+ < div className = "space-y-1.5" >
20+ < div className = "text-xs text-gray-600 dark:text-gray-400" >
21+ < span className = "hidden sm:inline" > Monthly downloads</ span >
22+ < span className = "sm:hidden" > Downloads</ span >
23+ </ div >
24+ < div className = "flex items-center gap-2" >
25+ < Link
26+ href = { `https://www.npmjs.com/package/${ npmPackageName } ` }
27+ className = "flex items-center gap-1.5"
28+ >
29+ < GrowingUnderline data-umami-event = "project-npm-link" >
30+ < div className = "flex items-center space-x-1.5" >
31+ < Download size = { 16 } strokeWidth = { 1.5 } />
32+ < span className = "font-medium" > { downloads } </ span >
33+ </ div >
34+ </ GrowingUnderline >
35+ </ Link >
36+ </ div >
37+ </ div >
38+ )
39+ }
40+
41+ function GithubStats ( { repository } : { repository : GithubRepository } ) {
42+ return (
43+ < div className = "space-y-1.5" >
44+ < div className = "text-xs text-gray-600 dark:text-gray-400" >
45+ < span className = "hidden sm:inline" > Github stars</ span >
46+ < span className = "sm:hidden" > Stars</ span >
47+ </ div >
48+ < div className = "flex items-center gap-2" >
49+ < Link href = { repository . url } className = "flex items-center gap-1.5" >
50+ < GrowingUnderline data-umami-event = "project-github-link" >
51+ < div className = "flex items-center space-x-1.5" >
52+ < Github size = { 16 } strokeWidth = { 1.5 } />
53+ < span className = "font-medium" > { repository . stargazerCount } </ span >
54+ </ div >
55+ </ GrowingUnderline >
56+ </ Link >
57+ </ div >
58+ </ div >
59+ )
60+ }
61+
62+ function Stack ( { builtWith } : { builtWith : string [ ] } ) {
63+ return (
64+ < div className = "space-y-1.5" >
65+ < div className = "text-xs text-gray-600 dark:text-gray-400" > Stack</ div >
66+ < div className = "flex h-6 flex-wrap items-center gap-1.5" >
67+ { builtWith ?. map ( ( tool ) => {
68+ return (
69+ < Brand
70+ key = { tool }
71+ name = { tool as keyof typeof BrandsMap }
72+ iconClassName = { clsx ( tool === 'Pygame' ? 'h-4' : 'h-4 w-4' ) }
73+ />
74+ )
75+ } ) }
76+ </ div >
77+ </ div >
78+ )
79+ }
80+
1881export function ProjectCard ( { project } : { project : ( typeof PROJECTS ) [ 0 ] } ) {
19- const { title, description, imgSrc, url, repo, npm , builtWith, links } = project
82+ const { title, description, imgSrc, url, repo, npmPackageName , builtWith, links } = project
2083
2184 const { data : repository } = useSWR < GithubRepository > (
22- repo && typeof repo === 'string' ? `/api/github?repo=${ repo } ` : null ,
85+ repo ? `/api/github?repo=${ repo } ` : null ,
2386 fetcher
2487 )
2588
2689 const { data : npmPackage } = useSWR < NpmPackage > (
27- npm && typeof npm === 'string' ? `/api/npm?package=${ npm } ` : null ,
90+ npmPackageName ? `/api/npm?package=${ npmPackageName } ` : null ,
2891 fetcher
2992 )
30-
3193 const href = url || repository ?. url
32- const lang = repository ?. languages ?. [ 0 ]
94+
95+ const propertyCount = ( npmPackage ? 1 : 0 ) + ( repository ? 1 : 0 ) + ( builtWith . length > 0 ? 1 : 0 )
3396
3497 return (
3598 < GradientBorder
@@ -51,101 +114,21 @@ export function ProjectCard({ project }: { project: (typeof PROJECTS)[0] }) {
51114 </ h2 >
52115 </ div >
53116 </ div >
54- < p className = "mb-16 line-clamp-3 grow text-lg" > { repository ?. description || description } </ p >
55- < div className = { clsx ( 'mt-auto flex gap-6 sm:gap-9 md:grid md:gap-0' , `grid-cols-3` ) } >
56- { npmPackage
57- ? // NPM Downloads
58- ( npmPackage || ( npm && typeof npm === 'object' ) ) && (
59- < div className = "space-y-1.5" >
60- < div className = "text-xs text-gray-600 dark:text-gray-400" >
61- < span className = "hidden sm:inline" > Monthly downloads</ span >
62- < span className = "sm:hidden" > Downloads</ span >
63- </ div >
64- < div className = "flex items-center gap-2" >
65- < div className = "flex items-center space-x-1.5" >
66- < Download size = { 16 } strokeWidth = { 1.5 } />
67- < span className = "font-medium" >
68- { npmPackage ?. downloads ||
69- ( npm && typeof npm === 'object' ? npm . downloads : 0 ) }
70- </ span >
71- </ div >
72- </ div >
73- </ div >
74- )
75- : repository
76- ? // GitHub Stars
77- ( repository || ( repo && typeof repo === 'object' ) ) && (
78- < div className = "space-y-1.5" >
79- < div className = "text-xs text-gray-600 dark:text-gray-400" >
80- < span className = "hidden sm:inline" > Github stars</ span >
81- < span className = "sm:hidden" > Stars</ span >
82- </ div >
83- < div className = "flex items-center gap-2" >
84- < div className = "flex items-center space-x-1.5" >
85- < Github size = { 16 } strokeWidth = { 1.5 } />
86- < span className = "font-medium" >
87- { repository ?. stargazerCount ||
88- ( repo && typeof repo === 'object' ? repo . stargazerCount : 0 ) }
89- </ span >
90- </ div >
91- </ div >
92- </ div >
93- )
94- : null }
95-
96- { /* Links (only show if no repo and no npm) */ }
97- { ! repository &&
98- ! npmPackage &&
99- ! ( repo && typeof repo === 'object' ) &&
100- ! ( npm && typeof npm === 'object' ) &&
101- links && (
102- < div className = "space-y-1.5" >
103- < div className = "text-xs text-gray-600 dark:text-gray-400" > Links</ div >
104- < div className = "flex flex-col items-start gap-0.5 sm:flex-row sm:items-center sm:gap-1.5" >
105- { links ?. map ( ( { title, url } , idx ) => (
106- < Fragment key = { url } >
107- < Link href = { url } className = "flex items-center gap-1.5" >
108- < GrowingUnderline className = "font-medium" data-umami-event = "project-link" >
109- { title }
110- </ GrowingUnderline >
111- </ Link >
112- { idx !== links . length - 1 && (
113- < span className = "hidden text-gray-400 dark:text-gray-500 md:inline" > |</ span >
114- ) }
115- </ Fragment >
116- ) ) }
117- </ div >
118- </ div >
119- ) }
120-
121- { /* Stack */ }
122- { builtWith && builtWith . length > 0 && (
123- < div className = "space-y-1.5" >
124- < div className = "text-xs text-gray-600 dark:text-gray-400" > Stack</ div >
125- < div className = "flex h-6 flex-wrap items-center gap-1.5" >
126- { builtWith ?. map ( ( tool ) => {
127- return (
128- < Brand
129- key = { tool }
130- name = { tool as keyof typeof BrandsMap }
131- iconClassName = { clsx ( tool === 'Pygame' ? 'h-4' : 'h-4 w-4' ) }
132- />
133- )
134- } ) }
135- </ div >
136- </ div >
137- ) }
138-
139- { /* Language */ }
140- { lang && (
141- < div className = "space-y-1.5" >
142- < div className = "text-xs text-gray-600 dark:text-gray-400" > Language</ div >
143- < div className = "flex items-center gap-1.5" >
144- < Brand name = { lang . name as keyof typeof BrandsMap } as = "icon" className = "h-4 w-4" />
145- < span className = "font-medium" > { lang . name } </ span >
146- </ div >
147- </ div >
117+ < p className = "mb-16 line-clamp-3 grow text-lg" > { description } </ p >
118+ < div
119+ className = { clsx ( 'mt-auto flex gap-6 sm:gap-9 md:grid md:gap-0' , {
120+ 'md:grid-cols-1' : propertyCount === 1 ,
121+ 'md:grid-cols-2' : propertyCount === 2 ,
122+ 'md:grid-cols-3' : propertyCount === 3 ,
123+ 'md:grid-cols-4' : propertyCount === 4 ,
124+ } ) }
125+ >
126+ { /* {projectProperty} */ }
127+ { npmPackageName && (
128+ < NpmStats npmPackageName = { npmPackageName } downloads = { npmPackage ?. downloads || 0 } />
148129 ) }
130+ { repository && < GithubStats repository = { repository } /> }
131+ { builtWith . length > 0 && < Stack builtWith = { builtWith } /> }
149132 </ div >
150133 </ GradientBorder >
151134 )
0 commit comments