Skip to content

Commit bae2a77

Browse files
committed
feat: implement sponsors
1 parent 123cb19 commit bae2a77

10 files changed

Lines changed: 344 additions & 7 deletions

File tree

apps/blog/src/components/TableOfContents.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react'
2-
import { cn } from '@explainer/ui'
2+
import { cn, SponsorCards, defaultSponsors } from '@explainer/ui'
33
import { useTranslations } from '../i18n/utils'
44

55
export interface TocHeading {
@@ -64,6 +64,7 @@ export function TableOfContents({ headings, locale = 'en' }: TableOfContentsProp
6464
</li>
6565
))}
6666
</ul>
67+
<SponsorCards sponsors={defaultSponsors} title={t('sponsors.title')} />
6768
</nav>
6869
)
6970
}

apps/blog/src/i18n/ui.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ export const ui = {
3333
// Table of contents
3434
'toc.title': 'On this page',
3535

36+
// Sponsors
37+
'sponsors.title': 'Sponsors',
38+
3639
// Share buttons
3740
'share.linkedin': 'Share on LinkedIn',
3841
'share.twitter': 'Share on Twitter',
@@ -71,6 +74,9 @@ export const ui = {
7174
// Table of contents
7275
'toc.title': 'Sur cette page',
7376

77+
// Sponsors
78+
'sponsors.title': 'Sponsors',
79+
7480
// Share buttons
7581
'share.linkedin': 'Partager sur LinkedIn',
7682
'share.twitter': 'Partager sur Twitter',

apps/blog/src/layouts/post.astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ const t = useTranslations(locale)
5656
<div class="flex flex-wrap gap-2 mb-10">
5757
{tags.map((tag) => (
5858
<a
59-
href={`/tags/${tag}`}
60-
class="rounded-full border px-3 py-1 text-xs font-medium text-muted-foreground hover:text-foreground hover:border-foreground transition-colors"
59+
href={`/?tags=${encodeURIComponent(tag)}`}
60+
class="rounded-full border px-3 py-1 text-xs font-medium text-muted-foreground hover:text-primary hover:border-primary transition-colors"
6161
>
6262
{tag}
6363
</a>

apps/docs/src/components/toc.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import { cn, defaultSponsors, SponsorCards } from '@explainer/ui'
12
import * as React from 'react'
2-
import { cn } from '@explainer/ui'
33

44
export interface TocHeading {
55
depth: number
@@ -52,7 +52,7 @@ export function TableOfContents({ headings }: TocProps) {
5252
})
5353

5454
return (
55-
<nav className="w-56 shrink-0 hidden xl:block h-[calc(100vh-var(--header-height,4rem))] overflow-y-auto sticky top-[var(--header-height,4rem)] py-6 pl-4">
55+
<nav className="w-56 shrink-0 hidden xl:block h-[calc(100vh-var(--header-height,4rem))] overflow-y-auto sticky top-(--header-height,4rem) py-6 pl-4">
5656
<p className="text-sm font-medium mb-3">On this page</p>
5757
<ul className="border-l border-border space-y-0.5">
5858
{numbered.map((heading) => (
@@ -72,6 +72,7 @@ export function TableOfContents({ headings }: TocProps) {
7272
</li>
7373
))}
7474
</ul>
75+
<SponsorCards sponsors={defaultSponsors} />
7576
</nav>
7677
)
7778
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
---
2+
title: Sponsors
3+
description: Display sponsor cards below the table of contents in your documentation and blog.
4+
icon: heart
5+
order: 6
6+
---
7+
8+
# Sponsors
9+
10+
Explainer includes a built-in sponsor section that displays below the table of contents in the right sidebar. The configuration is centralized in the `@explainer/ui` package and shared across both the docs and blog apps.
11+
12+
## How it works
13+
14+
Sponsor cards appear automatically below the TOC on any page that has a table of contents. They are only visible on `xl` breakpoints and above, matching the TOC visibility.
15+
16+
Each sponsor displays:
17+
- A **logo** (small, rounded)
18+
- A **name**
19+
- A **tier badge** (gold, silver, or bronze) with distinct visual styling
20+
21+
## Configuration
22+
23+
### Adding sponsors
24+
25+
Edit `packages/ui/src/lib/sponsors.ts` and add entries to the `defaultSponsors` array:
26+
27+
```ts title="packages/ui/src/lib/sponsors.ts"
28+
import type { Sponsor } from '@explainer/ui'
29+
30+
export const defaultSponsors: Sponsor[] = [
31+
{
32+
id: 'acme',
33+
name: 'Acme Corp',
34+
href: 'https://acme.example.com',
35+
logoUrl: 'https://acme.example.com/logo.svg',
36+
tier: 'gold',
37+
},
38+
{
39+
id: 'widgets',
40+
name: 'Widgets Inc',
41+
href: 'https://widgets.example.com',
42+
logoUrl: 'https://widgets.example.com/logo.png',
43+
tier: 'silver',
44+
},
45+
]
46+
```
47+
48+
### Sponsor type
49+
50+
```ts
51+
interface Sponsor {
52+
id: string // Unique identifier
53+
name: string // Display name
54+
href: string // Link URL (opens in new tab)
55+
logoUrl: string // Logo image URL
56+
tier: 'gold' | 'silver' | 'bronze'
57+
}
58+
```
59+
60+
### Tier styling
61+
62+
The default tiers and their colors are defined in `packages/ui/src/lib/sponsors.ts` via two objects: `sponsorTierStyles` (card border & background) and `sponsorBadgeStyles` (badge colors). You can customize them directly:
63+
64+
```ts title="packages/ui/src/lib/sponsors.ts"
65+
export const sponsorTierStyles: Record<Sponsor['tier'], string> = {
66+
gold: 'border-yellow-500/50 bg-yellow-500/5',
67+
silver: 'border-zinc-400/50 bg-zinc-400/5',
68+
bronze: 'border-orange-700/50 bg-orange-700/5',
69+
}
70+
71+
export const sponsorBadgeStyles: Record<Sponsor['tier'], string> = {
72+
gold: 'bg-yellow-500/15 text-yellow-700 dark:text-yellow-400',
73+
silver: 'bg-zinc-400/15 text-zinc-600 dark:text-zinc-400',
74+
bronze: 'bg-orange-700/15 text-orange-800 dark:text-orange-400',
75+
}
76+
```
77+
78+
To add a new tier or change colors, update both objects and the `Sponsor['tier']` union type.
79+
80+
| Tier | Border & background | Badge color |
81+
|------|-------------------|-------------|
82+
| **Gold** | Yellow accent | Yellow |
83+
| **Silver** | Zinc/gray accent | Gray |
84+
| **Bronze** | Orange accent | Orange |
85+
86+
## Customization
87+
88+
### Using the component directly
89+
90+
You can use the `SponsorCards` component anywhere in your layout:
91+
92+
```tsx
93+
import { SponsorCards, defaultSponsors } from '@explainer/ui'
94+
95+
<SponsorCards
96+
sponsors={defaultSponsors}
97+
title="Our Sponsors" // optional, defaults to "Sponsors"
98+
className="mt-8" // optional additional classes
99+
/>
100+
```
101+
102+
### Custom sponsor list
103+
104+
Use the `getSponsors` helper to override individual sponsors:
105+
106+
```ts
107+
import { getSponsors } from '@explainer/ui'
108+
109+
const sponsors = getSponsors({
110+
acme: { name: 'Acme Corp (Premium)' },
111+
})
112+
```
113+
114+
## Visibility
115+
116+
- Sponsors only appear on pages that have a table of contents
117+
- The section is hidden when the `defaultSponsors` array is empty
118+
- On smaller screens (`< xl`), the entire right sidebar (TOC + sponsors) is hidden
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
---
2+
title: Sponsors
3+
description: Afficher des cartes sponsors sous la table des matières dans la documentation et le blog.
4+
icon: heart
5+
order: 6
6+
---
7+
8+
# Sponsors
9+
10+
Explainer inclut une section sponsors intégrée qui s'affiche sous la table des matières dans la colonne droite. La configuration est centralisée dans le package `@explainer/ui` et partagée entre les apps docs et blog.
11+
12+
## Fonctionnement
13+
14+
Les cartes sponsors apparaissent automatiquement sous le TOC sur toute page disposant d'une table des matières. Elles ne sont visibles qu'à partir du breakpoint `xl`, comme le TOC.
15+
16+
Chaque sponsor affiche :
17+
- Un **logo** (petit, arrondi)
18+
- Un **nom**
19+
- Un **badge de tier** (gold, silver ou bronze) avec un style visuel distinct
20+
21+
## Configuration
22+
23+
### Ajouter des sponsors
24+
25+
Éditez `packages/ui/src/lib/sponsors.ts` et ajoutez des entrées au tableau `defaultSponsors` :
26+
27+
```ts title="packages/ui/src/lib/sponsors.ts"
28+
import type { Sponsor } from '@explainer/ui'
29+
30+
export const defaultSponsors: Sponsor[] = [
31+
{
32+
id: 'acme',
33+
name: 'Acme Corp',
34+
href: 'https://acme.example.com',
35+
logoUrl: 'https://acme.example.com/logo.svg',
36+
tier: 'gold',
37+
},
38+
{
39+
id: 'widgets',
40+
name: 'Widgets Inc',
41+
href: 'https://widgets.example.com',
42+
logoUrl: 'https://widgets.example.com/logo.png',
43+
tier: 'silver',
44+
},
45+
]
46+
```
47+
48+
### Type Sponsor
49+
50+
```ts
51+
interface Sponsor {
52+
id: string // Identifiant unique
53+
name: string // Nom affiché
54+
href: string // URL du lien (ouvre un nouvel onglet)
55+
logoUrl: string // URL du logo
56+
tier: 'gold' | 'silver' | 'bronze'
57+
}
58+
```
59+
60+
### Style par tier
61+
62+
Les tiers par défaut et leurs couleurs sont définis dans `packages/ui/src/lib/sponsors.ts` via deux objets : `sponsorTierStyles` (bordure et fond de la carte) et `sponsorBadgeStyles` (couleurs du badge). Vous pouvez les personnaliser directement :
63+
64+
```ts title="packages/ui/src/lib/sponsors.ts"
65+
export const sponsorTierStyles: Record<Sponsor['tier'], string> = {
66+
gold: 'border-yellow-500/50 bg-yellow-500/5',
67+
silver: 'border-zinc-400/50 bg-zinc-400/5',
68+
bronze: 'border-orange-700/50 bg-orange-700/5',
69+
}
70+
71+
export const sponsorBadgeStyles: Record<Sponsor['tier'], string> = {
72+
gold: 'bg-yellow-500/15 text-yellow-700 dark:text-yellow-400',
73+
silver: 'bg-zinc-400/15 text-zinc-600 dark:text-zinc-400',
74+
bronze: 'bg-orange-700/15 text-orange-800 dark:text-orange-400',
75+
}
76+
```
77+
78+
Pour ajouter un nouveau palier ou modifier les couleurs, mettez à jour les deux objets ainsi que le type union `Sponsor['tier']`.
79+
80+
| Tier | Bordure et fond | Couleur du badge |
81+
|------|----------------|-----------------|
82+
| **Gold** | Accent jaune | Jaune |
83+
| **Silver** | Accent zinc/gris | Gris |
84+
| **Bronze** | Accent orange | Orange |
85+
86+
## Personnalisation
87+
88+
### Utiliser le composant directement
89+
90+
Vous pouvez utiliser le composant `SponsorCards` n'importe où dans votre layout :
91+
92+
```tsx
93+
import { SponsorCards, defaultSponsors } from '@explainer/ui'
94+
95+
<SponsorCards
96+
sponsors={defaultSponsors}
97+
title="Nos Sponsors" // optionnel, "Sponsors" par défaut
98+
className="mt-8" // classes CSS additionnelles optionnelles
99+
/>
100+
```
101+
102+
### Liste de sponsors personnalisée
103+
104+
Utilisez le helper `getSponsors` pour surcharger des sponsors individuels :
105+
106+
```ts
107+
import { getSponsors } from '@explainer/ui'
108+
109+
const sponsors = getSponsors({
110+
acme: { name: 'Acme Corp (Premium)' },
111+
})
112+
```
113+
114+
## Visibilité
115+
116+
- Les sponsors n'apparaissent que sur les pages ayant une table des matières
117+
- La section est masquée lorsque le tableau `defaultSponsors` est vide
118+
- Sur les écrans plus petits (`< xl`), toute la colonne droite (TOC + sponsors) est masquée

apps/website/astro.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { defineConfig } from 'astro/config'
21
import react from '@astrojs/react'
3-
import tailwindcss from '@tailwindcss/vite'
42
import { thumbnailIntegration } from '@explainer/thumbnail/integration'
3+
import tailwindcss from '@tailwindcss/vite'
4+
import { defineConfig } from 'astro/config'
55

66
export default defineConfig({
77
site: process.env.PUBLIC_WEBSITE_URL || undefined,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { sponsorBadgeStyles, sponsorTierStyles, type Sponsor } from '../lib/sponsors'
2+
import { cn } from '../lib/utils'
3+
4+
export interface SponsorCardsProps {
5+
sponsors: Sponsor[]
6+
title?: string
7+
className?: string
8+
}
9+
10+
export function SponsorCards({ sponsors, title = 'Sponsors', className }: SponsorCardsProps) {
11+
if (sponsors.length === 0) return null
12+
13+
return (
14+
<div className={cn('border-t border-dashed border-border pt-4 mt-4', className)}>
15+
<p className="text-sm font-medium mb-3">{title}</p>
16+
<div className="space-y-2">
17+
{sponsors.map((sponsor) => (
18+
<a
19+
key={sponsor.id}
20+
href={sponsor.href}
21+
target="_blank"
22+
rel="noopener noreferrer"
23+
className={cn(
24+
'flex items-center gap-2.5 rounded-md border p-2 transition-colors hover:bg-accent/50',
25+
sponsorTierStyles[sponsor.tier],
26+
)}
27+
>
28+
<img
29+
src={sponsor.logoUrl}
30+
alt={sponsor.name}
31+
className="h-6 w-6 shrink-0 rounded"
32+
loading="lazy"
33+
/>
34+
<span className="text-sm font-medium truncate">{sponsor.name}</span>
35+
<span
36+
className={cn(
37+
'ml-auto text-[10px] font-semibold uppercase tracking-wider rounded-full px-1.5 py-0.5 shrink-0',
38+
sponsorBadgeStyles[sponsor.tier],
39+
)}
40+
>
41+
{sponsor.tier}
42+
</span>
43+
</a>
44+
))}
45+
</div>
46+
</div>
47+
)
48+
}

packages/ui/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ export { MobileMenu, type MobileMenuProps } from './components/mobile-menu'
2424
export { LocaleSwitcher, type LocaleSwitcherProps } from './components/locale-switcher'
2525
export { MobileNavLinks } from './components/mobile-nav-links'
2626
export { getAppLinks, defaultAppLinks, type AppLink } from './lib/app-links'
27+
export { SponsorCards, type SponsorCardsProps } from './components/sponsor-cards'
28+
export { defaultSponsors, getSponsors, type Sponsor } from './lib/sponsors'

0 commit comments

Comments
 (0)