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