Skip to content

Commit 097309a

Browse files
KevinVandyBalastrongTkDodoAhmedBasetSeanCassiere
authored
feat: contributors pages for repos (#423)
* feat: contributors pages for repos * prettier * Update src/components/MaintainerCard.tsx Co-authored-by: Leonardo Montini <[email protected]> * Update src/components/ContributorsWall.tsx Co-authored-by: Leonardo Montini <[email protected]> * Update src/routes/_libraries/route.tsx Co-authored-by: Leonardo Montini <[email protected]> * Update src/libraries/maintainers.ts Co-authored-by: Dominik Dorfmeister <[email protected]> * Update src/libraries/maintainers.ts Co-authored-by: Dominik Dorfmeister <[email protected]> * Update src/libraries/maintainers.ts Co-authored-by: Ahmed Abdelbaset <[email protected]> * Update src/libraries/maintainers.ts Co-authored-by: Ahmed Abdelbaset <[email protected]> * update contributors * update card design * update social links * add core contributors to home page * add details for Manuel * add details for Sean * add details for TypeChris * fix the bluesky links for Dominik * Improve maintainer card pill designs * add more maintainers and fix card pill alignments * fix tanstack pill links when they don't exist --------- Co-authored-by: Leonardo Montini <[email protected]> Co-authored-by: Dominik Dorfmeister <[email protected]> Co-authored-by: Ahmed Abdelbaset <[email protected]> Co-authored-by: SeanCassiere <[email protected]>
1 parent b2b4dd8 commit 097309a

File tree

9 files changed

+1010
-98
lines changed

9 files changed

+1010
-98
lines changed

src/components/ContributorsWall.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { type Library } from '~/libraries'
2+
3+
export function ContributorsWall({ library }: { library: Library }) {
4+
return (
5+
<div className="flex flex-col items-center my-4">
6+
<a
7+
href={`https://github.com/${library.repo}/graphs/contributors`}
8+
target="_blank"
9+
rel="noopener noreferrer"
10+
>
11+
<img
12+
alt="GitHub Contributors"
13+
src={`https://contrib.rocks/image?repo=${library.repo}`}
14+
loading="lazy"
15+
/>
16+
</a>
17+
<div className="text-xs text-gray-500 mt-2">
18+
Powered by{' '}
19+
<a
20+
href="https://contrib.rocks"
21+
target="_blank"
22+
rel="noopener noreferrer"
23+
className="underline"
24+
>
25+
contrib.rocks
26+
</a>
27+
</div>
28+
<div className="mt-4">
29+
<a
30+
href={`https://github.com/${library.repo}/graphs/contributors`}
31+
target="_blank"
32+
rel="noopener noreferrer"
33+
className="text-sm text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 mb-2"
34+
>
35+
View all contributors on GitHub
36+
</a>
37+
</div>
38+
</div>
39+
)
40+
}

src/components/DocsLayout.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@ const useMenuConfig = ({
197197
},
198198
]
199199
: []),
200+
{
201+
label: 'Contributors',
202+
to: '/$libraryId/$version/docs/contributors',
203+
},
200204
{
201205
label: (
202206
<div className="flex items-center gap-2">

src/components/MaintainerCard.tsx

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
import { Library, Framework, frameworkOptions } from '~/libraries'
2+
import {
3+
getRoleInLibrary,
4+
Maintainer,
5+
getPersonsMaintainerOf,
6+
} from '~/libraries/maintainers'
7+
import { useState } from 'react'
8+
9+
function RoleBadge({
10+
maintainer,
11+
libraryId,
12+
}: {
13+
maintainer: Maintainer
14+
libraryId?: Library['id']
15+
}) {
16+
const role = libraryId ? getRoleInLibrary(maintainer, libraryId) : ''
17+
const isCreator = role.toLowerCase().includes('creator')
18+
const isMaintainer = role.toLowerCase().includes('maintainer')
19+
const isCoreMaintainer = isMaintainer && maintainer.isCoreMaintainer
20+
21+
if (isCreator) {
22+
return (
23+
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gradient-to-r from-purple-500 to-pink-500 text-white shadow-lg">
24+
{role}
25+
</span>
26+
)
27+
}
28+
29+
if (isMaintainer) {
30+
return (
31+
<span
32+
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
33+
isCoreMaintainer
34+
? 'bg-gradient-to-r from-blue-400 to-blue-700 text-white shadow border border-blue-300'
35+
: 'bg-blue-500 text-white'
36+
}`}
37+
>
38+
{role}
39+
</span>
40+
)
41+
}
42+
43+
return <span className="text-gray-500 dark:text-gray-400">{role}</span>
44+
}
45+
46+
function FrameworkChip({ framework }: { framework: Framework }) {
47+
const frameworkOption = frameworkOptions.find((f) => f.value === framework)
48+
const bgColor = frameworkOption?.color || 'bg-gray-500'
49+
return (
50+
<span
51+
className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${bgColor} bg-opacity-80 text-white gap-1`}
52+
aria-label={`Framework: ${frameworkOption?.label || framework}`}
53+
>
54+
<svg
55+
aria-hidden="true"
56+
className="w-3 h-3"
57+
fill="currentColor"
58+
viewBox="0 0 16 16"
59+
>
60+
<circle cx="8" cy="8" r="8" />
61+
</svg>
62+
{frameworkOption?.label || framework}
63+
</span>
64+
)
65+
}
66+
67+
function SpecialtyChip({ specialty }: { specialty: string }) {
68+
return (
69+
<span
70+
className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-gray-200 text-gray-800 gap-1 dark:bg-gray-700 dark:text-gray-200"
71+
aria-label={`Specialty: ${specialty}`}
72+
>
73+
<svg
74+
aria-hidden="true"
75+
className="w-3 h-3"
76+
fill="currentColor"
77+
viewBox="0 0 16 16"
78+
>
79+
<rect x="2" y="2" width="12" height="12" rx="1" />
80+
</svg>
81+
{specialty}
82+
</span>
83+
)
84+
}
85+
86+
function LibraryBadge({ library }: { library: Library }) {
87+
if (library.to) {
88+
return (
89+
<a
90+
href={`${library.to}/latest/docs/contributors`}
91+
className={`inline-flex items-center px-2.5 py-1 rounded-full text-xs font-semibold text-green-900 dark:text-green-200 ${
92+
library.bgStyle ?? 'bg-gray-500'
93+
} bg-opacity-40 dark:bg-opacity-30 capitalize hover:underline focus:outline-none focus:ring-2 focus:ring-blue-400 transition-colors`}
94+
aria-label={`View contributors for ${library.name}`}
95+
tabIndex={0}
96+
onClick={(e) => e.stopPropagation()}
97+
title={`View all contributors for ${library.name}`}
98+
>
99+
{library.name.replace('TanStack', '🌴')}
100+
</a>
101+
)
102+
}
103+
return (
104+
<span
105+
className={`inline-flex items-center px-2.5 py-1 rounded-full text-xs font-semibold text-green-900 dark:text-green-200 ${
106+
library.bgStyle ?? 'bg-gray-500'
107+
} bg-opacity-40 dark:bg-opacity-30 capitalize`}
108+
aria-label={`View contributors for ${library.name}`}
109+
title={`View all contributors for ${library.name}`}
110+
>
111+
{library.name.replace('TanStack', '🌴')}
112+
</span>
113+
)
114+
}
115+
116+
interface MaintainerCardProps {
117+
maintainer: Maintainer
118+
libraryId?: Library['id']
119+
}
120+
121+
export function MaintainerCard({ maintainer, libraryId }: MaintainerCardProps) {
122+
const libraries = getPersonsMaintainerOf(maintainer)
123+
const [showAllLibraries, setShowAllLibraries] = useState(false)
124+
125+
return (
126+
<div
127+
className="group bg-white dark:bg-gray-800 rounded-lg overflow-hidden shadow-lg"
128+
aria-label={`Maintainer card for ${maintainer.name}`}
129+
>
130+
<a
131+
href={`https://github.com/${maintainer.github}`}
132+
target="_blank"
133+
rel="noopener noreferrer"
134+
aria-label={`View ${maintainer.name}'s GitHub profile`}
135+
className="relative h-64 overflow-hidden block"
136+
tabIndex={0}
137+
>
138+
<img
139+
alt={`Avatar of ${maintainer.name}`}
140+
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
141+
src={maintainer.avatar}
142+
loading="lazy"
143+
decoding="async"
144+
style={{
145+
aspectRatio: '1/1',
146+
objectFit: 'cover',
147+
}}
148+
/>
149+
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-black/30 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
150+
<div className="absolute inset-0 p-4 flex flex-col justify-end opacity-0 group-hover:opacity-100 transition-opacity duration-300">
151+
<div className="space-y-2">
152+
{maintainer.frameworkExpertise &&
153+
maintainer.frameworkExpertise.length > 0 && (
154+
<div className="flex flex-wrap gap-2 mb-2">
155+
{maintainer.frameworkExpertise.map((framework) => (
156+
<FrameworkChip key={framework} framework={framework} />
157+
))}
158+
</div>
159+
)}
160+
{maintainer.specialties && maintainer.specialties.length > 0 && (
161+
<div className="flex flex-wrap gap-2">
162+
{maintainer.specialties.map((specialty) => (
163+
<SpecialtyChip key={specialty} specialty={specialty} />
164+
))}
165+
</div>
166+
)}
167+
</div>
168+
</div>
169+
</a>
170+
<div className="p-3 space-y-2">
171+
<div className="flex items-center justify-between">
172+
<span
173+
className="text-base font-bold"
174+
id={`maintainer-name-${maintainer.github}`}
175+
>
176+
{maintainer.name}
177+
</span>
178+
<div className="flex items-center gap-2">
179+
{libraryId && (
180+
<RoleBadge maintainer={maintainer} libraryId={libraryId} />
181+
)}
182+
</div>
183+
</div>
184+
{!libraryId && libraries.length > 0 && (
185+
<div className="flex flex-wrap gap-1.5 pt-1">
186+
{libraries
187+
.slice(0, showAllLibraries ? undefined : 2)
188+
.map((library) => (
189+
<LibraryBadge key={library.id} library={library} />
190+
))}
191+
{!showAllLibraries && libraries.length > 2 && (
192+
<button
193+
onClick={(e) => {
194+
e.preventDefault()
195+
e.stopPropagation()
196+
setShowAllLibraries(true)
197+
}}
198+
className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-400"
199+
aria-label={`Show ${libraries.length - 2} more libraries`}
200+
tabIndex={0}
201+
type="button"
202+
>
203+
+{libraries.length - 2} more
204+
</button>
205+
)}
206+
</div>
207+
)}
208+
<div className="flex items-center space-x-4 text-gray-400 dark:text-gray-500 [&>*]:grayscale pt-1">
209+
<a
210+
href={`https://github.com/${maintainer.github}`}
211+
className="hover:text-gray-700 dark:hover:text-gray-200 transition-colors p-2 -m-2 hover:grayscale-0"
212+
target="_blank"
213+
rel="noopener noreferrer"
214+
aria-label="GitHub profile"
215+
onClick={(e) => e.stopPropagation()}
216+
>
217+
<svg
218+
viewBox="0 0 16 16"
219+
className="w-5 h-5"
220+
fill="currentColor"
221+
aria-hidden="true"
222+
>
223+
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
224+
</svg>
225+
</a>
226+
{maintainer.social?.twitter && (
227+
<a
228+
href={maintainer.social.twitter}
229+
className="hover:text-gray-700 dark:hover:text-gray-200 transition-colors p-2 -m-2 hover:grayscale-0"
230+
target="_blank"
231+
rel="noopener noreferrer"
232+
aria-label="Twitter profile"
233+
onClick={(e) => e.stopPropagation()}
234+
>
235+
<svg
236+
viewBox="0 0 24 24"
237+
className="w-5 h-5"
238+
fill="currentColor"
239+
aria-hidden="true"
240+
>
241+
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
242+
</svg>
243+
</a>
244+
)}
245+
{maintainer.social?.bluesky && (
246+
<a
247+
href={maintainer.social.bluesky}
248+
className="hover:text-gray-700 dark:hover:text-gray-200 transition-colors p-2 -m-2 hover:grayscale-0"
249+
target="_blank"
250+
rel="noopener noreferrer"
251+
aria-label="Bluesky profile"
252+
onClick={(e) => e.stopPropagation()}
253+
>
254+
<span className="text-lg" aria-hidden="true">
255+
🦋
256+
</span>
257+
</a>
258+
)}
259+
{maintainer.social?.website && (
260+
<a
261+
href={maintainer.social.website}
262+
className="hover:text-gray-700 dark:hover:text-gray-200 transition-colors p-2 -m-2 hover:grayscale-0"
263+
target="_blank"
264+
rel="noopener noreferrer"
265+
aria-label="Personal website"
266+
onClick={(e) => e.stopPropagation()}
267+
>
268+
<svg
269+
viewBox="0 0 24 24"
270+
className="w-5 h-5"
271+
fill="currentColor"
272+
aria-hidden="true"
273+
>
274+
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z" />
275+
</svg>
276+
</a>
277+
)}
278+
</div>
279+
</div>
280+
</div>
281+
)
282+
}

src/libraries/index.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ export type Library = {
5757
| 'db'
5858
| 'config'
5959
| 'react-charts'
60+
| 'create-tsrouter-app'
6061
name: string
6162
cardStyles: string
62-
to: string
63+
to?: string
6364
tagline: string
6465
description: string
6566
ogImage?: string
@@ -89,6 +90,7 @@ export type Library = {
8990
}[]
9091
docsRoot?: string
9192
embedEditor?: 'codesandbox' | 'stackblitz'
93+
visible?: boolean
9294
}
9395

9496
export type LibraryMenuItem = {
@@ -109,6 +111,16 @@ export const libraries = [
109111
rangerProject,
110112
dbProject,
111113
configProject,
114+
{
115+
id: 'react-charts',
116+
name: 'React Charts',
117+
repo: 'tanstack/react-charts',
118+
} as Library,
119+
{
120+
id: 'create-tsrouter-app',
121+
name: 'Create TS Router App',
122+
repo: 'tanstack/create-tsrouter-app',
123+
} as Library,
112124
] satisfies Library[]
113125

114126
export const librariesByGroup = {
@@ -125,7 +137,6 @@ export const librariesByGroup = {
125137
}
126138

127139
export const librariesGroupNamesMap = {
128-
app: 'Application Building',
129140
state: 'Data and State Management',
130141
headlessUI: 'Headless UI',
131142
other: 'Other',

0 commit comments

Comments
 (0)