Skip to content

Commit ae8df3b

Browse files
chore(web): improve sponsor banner with hover card feature
1 parent b87c094 commit ae8df3b

File tree

7 files changed

+170
-34
lines changed

7 files changed

+170
-34
lines changed

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@opennextjs/cloudflare": "^1.6.3",
2222
"@orama/orama": "^3.1.11",
2323
"@radix-ui/react-dropdown-menu": "^2.1.15",
24+
"@radix-ui/react-hover-card": "^1.1.14",
2425
"babel-plugin-react-compiler": "^19.1.0-rc.2",
2526
"class-variance-authority": "^0.7.1",
2627
"clsx": "^2.1.1",

apps/web/src/app/docs/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { source } from "@/lib/source";
77
const docsOptions: DocsLayoutProps = {
88
...baseOptions,
99
tree: source.pageTree,
10-
links: [],
10+
// links: [],
1111
sidebar: {
1212
banner: <SpecialSponsorBanner />,
1313
},

apps/web/src/app/global.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@
162162
}
163163

164164
:root {
165+
--color-fd-primary: #8839ef;
166+
--color-fd-primary-foreground: #ffffff;
165167
--background: oklch(1 0 0);
166168
--foreground: oklch(0.44 0.04 279.33);
167169
--card: oklch(1 0 0);
@@ -227,6 +229,8 @@
227229
}
228230

229231
.dark {
232+
--color-fd-primary: #cba6f7;
233+
--color-fd-primary-foreground: #11111b;
230234
--background: #11111b;
231235
--foreground: #cdd6f4;
232236
--card: #11111b;

apps/web/src/app/layout.config.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const links: LinkItemType[] = [
2020
{
2121
text: "Docs",
2222
url: "/docs",
23-
active: "nested-url" as const,
23+
active: "nested-url",
2424
},
2525
{
2626
text: "Builder",

apps/web/src/components/special-sponsor-banner.tsx

Lines changed: 117 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
"use client";
2+
import { Github, Globe, Star } from "lucide-react";
23
import Image from "next/image";
34
import { useEffect, useState } from "react";
5+
import {
6+
HoverCard,
7+
HoverCardContent,
8+
HoverCardTrigger,
9+
} from "@/components/ui/hover-card";
410
import {
511
filterCurrentSponsors,
612
filterSpecialSponsors,
13+
formatSponsorUrl,
14+
getSponsorUrl,
715
sortSpecialSponsors,
816
} from "@/lib/sponsor-utils";
917
import type { Sponsor } from "@/lib/types";
@@ -34,12 +42,14 @@ export function SpecialSponsorBanner() {
3442

3543
if (loading) {
3644
return (
37-
<div className="">
38-
<div className="flex items-center gap-3">
39-
<div className="h-[30px] w-[30px] animate-pulse rounded border border-border bg-muted" />
40-
<div className="min-w-0 flex-1">
41-
<div className="h-4 w-20 animate-pulse rounded bg-muted" />
42-
</div>
45+
<div>
46+
<div className="flex items-center gap-2 py-1">
47+
{["s1", "s2", "s3", "s4"].map((key) => (
48+
<div
49+
key={key}
50+
className="h-9 w-9 animate-pulse rounded border border-border bg-muted"
51+
/>
52+
))}
4353
</div>
4454
</div>
4555
);
@@ -50,31 +60,107 @@ export function SpecialSponsorBanner() {
5060
}
5161

5262
return (
53-
<div className="">
54-
<div className="flex flex-col gap-2">
55-
{specialSponsors.map((sponsor) => (
56-
<a
57-
key={sponsor.sponsor.login}
58-
className="flex items-center gap-3"
59-
href={sponsor.sponsor.websiteUrl}
60-
target="_blank"
61-
rel="noopener noreferrer"
62-
>
63-
<Image
64-
src={sponsor.sponsor.customLogoUrl || sponsor.sponsor.avatarUrl}
65-
alt={sponsor.sponsor.name || sponsor.sponsor.login}
66-
width={30}
67-
height={30}
68-
className="rounded border border-border"
69-
unoptimized
70-
/>
71-
<div className="min-w-0 flex-1">
72-
<h4 className="truncate font-medium text-sm">
73-
{sponsor.sponsor.name || sponsor.sponsor.login}
74-
</h4>
75-
</div>
76-
</a>
77-
))}
63+
<div>
64+
<div className="no-scrollbar flex items-center gap-2 overflow-x-auto whitespace-nowrap py-1">
65+
{specialSponsors.map((entry) => {
66+
const displayName = entry.sponsor.name || entry.sponsor.login;
67+
const imgSrc = entry.sponsor.customLogoUrl || entry.sponsor.avatarUrl;
68+
const since = new Date(entry.createdAt).toLocaleDateString(
69+
undefined,
70+
{
71+
year: "numeric",
72+
month: "short",
73+
},
74+
);
75+
const sponsorUrl = getSponsorUrl(entry);
76+
77+
return (
78+
<HoverCard key={entry.sponsor.login}>
79+
<HoverCardTrigger asChild>
80+
<a
81+
href={entry.sponsor.websiteUrl || sponsorUrl}
82+
target="_blank"
83+
rel="noopener noreferrer"
84+
aria-label={displayName}
85+
className="inline-flex"
86+
>
87+
<Image
88+
src={imgSrc}
89+
alt={displayName}
90+
width={36}
91+
height={36}
92+
className="rounded border border-border"
93+
unoptimized
94+
/>
95+
</a>
96+
</HoverCardTrigger>
97+
<HoverCardContent
98+
align="start"
99+
sideOffset={8}
100+
className="bg-fd-background"
101+
>
102+
<div className="space-y-3">
103+
<div className="flex items-center gap-2">
104+
<Star className="h-4 w-4 text-yellow-500/90" />
105+
<div className="ml-auto text-muted-foreground text-xs">
106+
<span>SPECIAL</span>
107+
<span className="px-1"></span>
108+
<span>SINCE {since.toUpperCase()}</span>
109+
</div>
110+
</div>
111+
<div className="flex gap-3">
112+
<Image
113+
src={imgSrc}
114+
alt={displayName}
115+
width={80}
116+
height={80}
117+
className="rounded border border-border"
118+
unoptimized
119+
/>
120+
<div className="grid grid-cols-1 grid-rows-[1fr_auto]">
121+
<div>
122+
<h3 className="truncate font-semibold text-sm">
123+
{displayName}
124+
</h3>
125+
{entry.tierName ? (
126+
<p className="text-primary text-xs">
127+
{entry.tierName}
128+
</p>
129+
) : null}
130+
</div>
131+
<div className="flex flex-col gap-1">
132+
<a
133+
href={`https://github.com/${entry.sponsor.login}`}
134+
target="_blank"
135+
rel="noopener noreferrer"
136+
className="group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary"
137+
>
138+
<Github className="h-4 w-4" />
139+
<span className="truncate">
140+
{entry.sponsor.login}
141+
</span>
142+
</a>
143+
{entry.sponsor.websiteUrl || entry.sponsor.linkUrl ? (
144+
<a
145+
href={sponsorUrl}
146+
target="_blank"
147+
rel="noopener noreferrer"
148+
className="group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary"
149+
>
150+
<Globe className="h-4 w-4" />
151+
<span className="truncate">
152+
{formatSponsorUrl(sponsorUrl)}
153+
</span>
154+
</a>
155+
) : null}
156+
</div>
157+
</div>
158+
</div>
159+
</div>
160+
</HoverCardContent>
161+
</HoverCard>
162+
);
163+
})}
78164
</div>
79165
</div>
80166
);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"use client";
2+
3+
import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
4+
import type * as React from "react";
5+
6+
import { cn } from "@/lib/utils";
7+
8+
function HoverCard({
9+
...props
10+
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
11+
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />;
12+
}
13+
14+
function HoverCardTrigger({
15+
...props
16+
}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
17+
return (
18+
<HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
19+
);
20+
}
21+
22+
function HoverCardContent({
23+
className,
24+
align = "center",
25+
sideOffset = 4,
26+
...props
27+
}: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
28+
return (
29+
<HoverCardPrimitive.Portal data-slot="hover-card-portal">
30+
<HoverCardPrimitive.Content
31+
data-slot="hover-card-content"
32+
align={align}
33+
sideOffset={sideOffset}
34+
className={cn(
35+
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in",
36+
className,
37+
)}
38+
{...props}
39+
/>
40+
</HoverCardPrimitive.Portal>
41+
);
42+
}
43+
44+
export { HoverCard, HoverCardTrigger, HoverCardContent };

bun.lock

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
},
1515
"apps/cli": {
1616
"name": "create-better-t-stack",
17-
"version": "2.29.3",
17+
"version": "2.30.0",
1818
"bin": {
1919
"create-better-t-stack": "dist/index.js",
2020
},
@@ -49,6 +49,7 @@
4949
"@opennextjs/cloudflare": "^1.6.3",
5050
"@orama/orama": "^3.1.11",
5151
"@radix-ui/react-dropdown-menu": "^2.1.15",
52+
"@radix-ui/react-hover-card": "^1.1.14",
5253
"babel-plugin-react-compiler": "^19.1.0-rc.2",
5354
"class-variance-authority": "^0.7.1",
5455
"clsx": "^2.1.1",

0 commit comments

Comments
 (0)