Skip to content

Commit e30fecc

Browse files
committed
2 parents beda919 + 1f781c4 commit e30fecc

File tree

4 files changed

+197
-357
lines changed

4 files changed

+197
-357
lines changed

src/components/LibraryCard.tsx

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import { Link } from '@tanstack/react-router'
2+
import { Library } from '~/libraries'
3+
import { twMerge } from 'tailwind-merge'
4+
5+
export default function LibraryCard({
6+
library,
7+
index = 0,
8+
isGeneric = false,
9+
}: {
10+
library: Library
11+
index?: number
12+
isGeneric?: boolean
13+
}) {
14+
const isExternal = library.to?.startsWith('http')
15+
const Component = isExternal ? 'a' : Link
16+
const props = isExternal
17+
? { href: library.to, target: '_blank', rel: 'noopener noreferrer' }
18+
: { to: library.to ?? '#' }
19+
20+
const hasTanStackPrefix = library.name.startsWith('TanStack ')
21+
const nameWithoutPrefix = library.name.replace('TanStack ', '')
22+
23+
return (
24+
<Component
25+
key={library.id}
26+
{...props}
27+
className={twMerge(
28+
// General
29+
'p-8 relative group z-0 min-h-[250px] xl:min-h-[220px] shadow-sm hover:shadow-none',
30+
31+
// Transition
32+
'transition-all duration-300 ease-out',
33+
34+
// Border
35+
'rounded-xl border border-gray-200 dark:border-gray-800 hover:border-current/50',
36+
37+
// Shadow / Glow (behind everything)
38+
isGeneric
39+
? 'text-slate-500 dark:text-slate-400 before:bg-slate-500/20 dark:before:bg-slate-400/20'
40+
: 'before:bg-current',
41+
'before:content-[""] before:absolute before:inset-0 before:blur-xl before:opacity-0 hover:before:opacity-20 before:transition-all before:duration-300 before:ease-out',
42+
43+
// Card Background (behind content, front of shadow)
44+
'after:absolute after:inset-0 after:-z-10 after:bg-white dark:after:bg-gray-900 after:backdrop-blur-sm after:rounded-xl',
45+
46+
// Transform
47+
'hover:-translate-y-1',
48+
!isGeneric && library.cardStyles,
49+
)}
50+
style={{
51+
zIndex: index,
52+
willChange: 'transform',
53+
}}
54+
>
55+
{/* Background content that will blur on hover */}
56+
<div className="z-0 relative group-hover:blur-[0.5px] transition-[filter] duration-300 ease-out">
57+
<div className="flex gap-2 justify-between items-center">
58+
<div
59+
className={twMerge(
60+
`flex items-center gap-2 text-[1.2rem] font-extrabold uppercase [letter-spacing:-.04em]`,
61+
)}
62+
style={{
63+
viewTransitionName: `library-name-${library.id}`,
64+
}}
65+
>
66+
{hasTanStackPrefix ? (
67+
<>
68+
<span
69+
className={twMerge(
70+
'rounded-lg leading-none flex items-center',
71+
isGeneric ? 'bg-slate-500 dark:bg-slate-400' : 'bg-current',
72+
)}
73+
>
74+
<span className="text-white dark:text-black text-xs leading-none p-1.5 px-2 uppercase">
75+
TanStack
76+
</span>
77+
</span>
78+
<span
79+
className={
80+
isGeneric
81+
? 'text-slate-500 dark:text-slate-400'
82+
: 'text-current'
83+
}
84+
>
85+
{nameWithoutPrefix}
86+
</span>
87+
</>
88+
) : (
89+
<span
90+
className={
91+
isGeneric
92+
? 'text-slate-500 dark:text-slate-400'
93+
: 'text-current'
94+
}
95+
>
96+
{nameWithoutPrefix}
97+
</span>
98+
)}
99+
</div>
100+
</div>
101+
<div
102+
className={twMerge(
103+
`text-sm italic font-medium mt-3`,
104+
isGeneric ? 'text-slate-500 dark:text-slate-400' : 'text-current',
105+
)}
106+
>
107+
{library.tagline}
108+
</div>
109+
110+
{/* Description preview with ellipsis */}
111+
<div
112+
className={`text-sm mt-3 text-gray-600 dark:text-gray-400 line-clamp-3 leading-relaxed`}
113+
>
114+
{library.description}
115+
</div>
116+
</div>
117+
118+
{/* Foreground content that appears on hover */}
119+
<div
120+
className="absolute inset-0 z-30 bg-white/95 dark:bg-black/95 p-6 rounded-xl
121+
backdrop-blur-sm flex flex-col justify-center opacity-0 group-hover:opacity-100
122+
transition-opacity duration-300 ease-out pointer-events-none group-hover:pointer-events-auto"
123+
>
124+
<div
125+
className={`text-sm text-gray-800 dark:text-gray-200 leading-relaxed`}
126+
>
127+
{library.description}
128+
</div>
129+
<div className="mt-6 text-center">
130+
<span
131+
className="inline-flex items-center gap-2 px-4 py-2 bg-black/5 dark:bg-white/10
132+
rounded-full text-sm font-medium text-gray-900 dark:text-white"
133+
>
134+
Click to learn more
135+
<svg
136+
className="w-4 h-4 transform transition-transform duration-200 group-hover:translate-x-0.5"
137+
fill="none"
138+
viewBox="0 0 24 24"
139+
stroke="currentColor"
140+
>
141+
<path
142+
strokeLinecap="round"
143+
strokeLinejoin="round"
144+
strokeWidth={2}
145+
d="M9 5l7 7-7 7"
146+
/>
147+
</svg>
148+
</span>
149+
</div>
150+
</div>
151+
{/* Badge */}
152+
{library.badge && !isGeneric ? (
153+
<div
154+
className={twMerge(
155+
`absolute -top-2 -right-2 z-40 px-2 py-1 rounded-md`,
156+
['bg-gradient-to-r', library.colorFrom, library.colorTo],
157+
'uppercase text-white font-black italic text-xs',
158+
)}
159+
style={{
160+
animation: 'pulseScale 3s infinite',
161+
animationTimingFunction: 'ease-in-out',
162+
animationDelay: `${index * 0.5}s`,
163+
['--scale-factor' as any]: '1.1',
164+
}}
165+
>
166+
<span>{library.badge}</span>
167+
</div>
168+
) : null}
169+
</Component>
170+
)
171+
}

src/routes/_libraries/index.tsx

Lines changed: 6 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Footer } from '~/components/Footer'
44
import { LazySponsorSection } from '~/components/LazySponsorSection'
55
import discordImage from '~/images/discord-logo-white.svg'
66
import { useMutation } from '~/hooks/useMutation'
7-
import { librariesByGroup, librariesGroupNamesMap } from '~/libraries'
7+
import { librariesByGroup, librariesGroupNamesMap, Library } from '~/libraries'
88
import bytesImage from '~/images/bytes.svg'
99
import { PartnersGrid } from '~/components/PartnersGrid'
1010
import OpenSourceStats from '~/components/OpenSourceStats'
@@ -26,6 +26,7 @@ import { GamHeader } from '~/components/Gam'
2626
import { TrustedByMarquee } from '~/components/TrustedByMarquee'
2727
import { Layers, Zap, Shield, Code2 } from 'lucide-react'
2828
import { Card } from '~/components/Card'
29+
import LibraryCard from '~/components/LibraryCard'
2930

3031
export const textColors = [
3132
`text-rose-500`,
@@ -234,124 +235,11 @@ function Index() {
234235
>
235236
{groupLibraries.map((library, i: number) => {
236237
return (
237-
<Card
238-
as={Link}
238+
<LibraryCard
239239
key={library.name}
240-
to={library.to ?? '#'}
241-
params
242-
className={twMerge(
243-
`rounded-xl p-8 transition-all duration-300 ease-out`,
244-
'hover:shadow-md hover:shadow-current/5 hover:border-current/30 hover:-translate-y-1',
245-
'relative group',
246-
'min-h-[250px] xl:min-h-[220px]',
247-
library.cardStyles,
248-
)}
249-
style={{
250-
zIndex: i,
251-
willChange: 'transform',
252-
}}
253-
>
254-
{/* Background content that will blur on hover */}
255-
<div className="z-0 relative group-hover:blur-[0.5px] transition-[filter] duration-300 ease-out">
256-
<div className="flex gap-2 justify-between items-center">
257-
<MatchRoute
258-
pending
259-
to={library.to}
260-
children={() => {
261-
return (
262-
<div
263-
className={twMerge(
264-
`flex items-center gap-2 text-[1.2rem] font-extrabold uppercase [letter-spacing:-.04em]`,
265-
)}
266-
style={{
267-
viewTransitionName: `library-name-${library.id}`,
268-
}}
269-
>
270-
<span className="bg-current rounded-lg leading-none flex items-center">
271-
<span className="text-white dark:text-black text-xs leading-none p-1.5 px-2 uppercase">
272-
TanStack
273-
</span>
274-
</span>
275-
<span className="text-current">
276-
{library.name.replace('TanStack ', '')}
277-
</span>
278-
</div>
279-
)
280-
}}
281-
/>
282-
</div>
283-
<div
284-
className={`text-sm italic font-medium mt-3 text-current`}
285-
>
286-
{library.tagline}
287-
</div>
288-
289-
{/* Description preview with ellipsis */}
290-
<div
291-
className={`text-sm mt-3 text-gray-600 dark:text-gray-400 line-clamp-3 leading-relaxed`}
292-
>
293-
{library.description}
294-
</div>
295-
</div>
296-
297-
{/* Foreground content that appears on hover */}
298-
<div
299-
className="absolute inset-0 z-30 bg-white/95 dark:bg-black/95 p-6 rounded-xl
300-
backdrop-blur-sm flex flex-col justify-center opacity-0 group-hover:opacity-100
301-
transition-opacity duration-300 ease-out pointer-events-none group-hover:pointer-events-auto"
302-
>
303-
<div
304-
className={`text-sm text-gray-800 dark:text-gray-200 leading-relaxed`}
305-
>
306-
{library.description}
307-
</div>
308-
<div className="mt-6 text-center">
309-
<span
310-
className="inline-flex items-center gap-2 px-4 py-2 bg-black/5 dark:bg-white/10
311-
rounded-full text-sm font-medium text-gray-900 dark:text-white"
312-
>
313-
Click to learn more
314-
<svg
315-
className="w-4 h-4 transform transition-transform duration-200 group-hover:translate-x-0.5"
316-
fill="none"
317-
viewBox="0 0 24 24"
318-
stroke="currentColor"
319-
>
320-
<path
321-
strokeLinecap="round"
322-
strokeLinejoin="round"
323-
strokeWidth={2}
324-
d="M9 5l7 7-7 7"
325-
/>
326-
</svg>
327-
</span>
328-
</div>
329-
</div>
330-
{/* Badge */}
331-
{library.badge ? (
332-
<>
333-
<div
334-
className={twMerge(
335-
`absolute -top-2 -right-2 z-40 px-2 py-1 rounded-md`,
336-
[
337-
'bg-gradient-to-r',
338-
library.colorFrom,
339-
library.colorTo,
340-
],
341-
'uppercase text-white font-black italic text-xs',
342-
)}
343-
style={{
344-
animation: 'pulseScale 3s infinite',
345-
animationTimingFunction: 'ease-in-out',
346-
animationDelay: `${i * 0.5}s`,
347-
['--scale-factor' as any]: '1.1',
348-
}}
349-
>
350-
<span>{library.badge}</span>
351-
</div>
352-
</>
353-
) : null}
354-
</Card>
240+
index={i}
241+
library={library as Library}
242+
/>
355243
)
356244
})}
357245
</div>

0 commit comments

Comments
 (0)