Skip to content

Commit 0b697fd

Browse files
ericyangpanclaude
andcommitted
refactor: extract ThemeSwitcher into dedicated component
Extract theme switching logic from Footer into a reusable ThemeSwitcher component with improved styling and icon support. - Create ThemeSwitcher.tsx with lucide-react icons - Add footer-control-button and footer-link CSS classes - Update Footer to use new ThemeSwitcher component - Convert Footer to async server component - Use getTranslations instead of useTranslations - Simplify footer link rendering with shared CSS classes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 4269d50 commit 0b697fd

File tree

3 files changed

+82
-34
lines changed

3 files changed

+82
-34
lines changed

src/app/[locale]/globals.css

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,40 @@ code.hljs:not(pre code.hljs) {
218218
[data-theme="dark"] .hljs-selector-tag {
219219
color: #ff7b72;
220220
}
221+
222+
/* Footer control button styles */
223+
.footer-control-button {
224+
display: flex;
225+
align-items: center;
226+
gap: var(--spacing-xs);
227+
padding: var(--spacing-xs) var(--spacing-sm);
228+
font-size: 0.75rem;
229+
border: 1px solid var(--color-border);
230+
background-color: var(--color-bg);
231+
color: var(--color-text);
232+
transition: all 0.2s;
233+
cursor: pointer;
234+
}
235+
236+
.footer-control-button:hover {
237+
border-color: var(--color-border-strong);
238+
background-color: var(--color-hover);
239+
}
240+
241+
.footer-control-icon {
242+
width: 1rem;
243+
height: 1rem;
244+
flex-shrink: 0;
245+
}
246+
247+
/* Footer link styles */
248+
.footer-link {
249+
text-decoration: none;
250+
color: var(--color-text-secondary);
251+
font-size: 0.875rem;
252+
transition: color 0.2s;
253+
}
254+
255+
.footer-link:hover {
256+
color: var(--color-text);
257+
}

src/components/Footer.tsx

Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
'use client'
2-
3-
import { useTranslations } from 'next-intl'
1+
import { getTranslations } from 'next-intl/server'
42
import { Link } from '@/i18n/navigation'
53
import LanguageSwitcher from './controls/LanguageSwitcher'
6-
import { useTheme } from './ThemeProvider'
4+
import ThemeSwitcher from './controls/ThemeSwitcher'
75

86
// Footer link list component to reduce code duplication
97
interface FooterLinkListProps {
@@ -19,19 +17,11 @@ function FooterLinkList({ title, links }: FooterLinkListProps) {
1917
{links.map(item => (
2018
<li key={item.href}>
2119
{item.isExternal ? (
22-
<a
23-
href={item.href}
24-
target="_blank"
25-
rel="noopener noreferrer"
26-
className="text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text)] transition-colors font-light"
27-
>
20+
<a href={item.href} target="_blank" rel="noopener noreferrer" className="footer-link">
2821
{item.label}
2922
</a>
3023
) : (
31-
<Link
32-
href={item.href}
33-
className="text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text)] transition-colors font-light"
34-
>
24+
<Link href={item.href} className="footer-link">
3525
{item.label}
3626
</Link>
3727
)}
@@ -42,12 +32,11 @@ function FooterLinkList({ title, links }: FooterLinkListProps) {
4232
)
4333
}
4434

45-
function Footer() {
46-
const { theme, toggleTheme } = useTheme()
47-
const tFooter = useTranslations('footer')
48-
const tCommunity = useTranslations('community')
49-
const tStacks = useTranslations('stacks')
50-
const tNav = useTranslations('header')
35+
export default async function Footer() {
36+
const tFooter = await getTranslations('footer')
37+
const tCommunity = await getTranslations('community')
38+
const tStacks = await getTranslations('stacks')
39+
const tHeader = await getTranslations('header')
5140

5241
// Define link arrays (static hrefs, only labels depend on translations)
5342
const resourceLinks = [
@@ -60,9 +49,9 @@ function Footer() {
6049
]
6150

6251
const documentationLinks = [
63-
{ href: '/docs', label: tNav('docs') },
64-
{ href: '/articles', label: tNav('articles') },
65-
{ href: '/curated-collections', label: tNav('curatedCollections') },
52+
{ href: '/docs', label: tFooter('docs') },
53+
{ href: '/articles', label: tFooter('articles') },
54+
{ href: '/curated-collections', label: tFooter('curatedCollections') },
6655
{ href: '/#faq', label: tFooter('faq') },
6756
]
6857

@@ -89,20 +78,13 @@ function Footer() {
8978
<div className="max-w-8xl mx-auto px-[var(--spacing-md)]">
9079
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-9 gap-[var(--spacing-lg)] mb-[var(--spacing-lg)]">
9180
<div className="flex flex-col gap-[var(--spacing-sm)] lg:col-span-3">
92-
<span className="text-sm font-semibold tracking-tight">{tFooter('aicodingstack')}</span>
81+
<span className="text-sm font-semibold tracking-tight">{tHeader('aiCodingStack')}</span>
9382
<p className="text-sm pb-[var(--spacing-sm)] leading-[1.8] text-[var(--color-text-secondary)] font-light">
9483
{tFooter('tagline')}
9584
<span className="block mt-[var(--spacing-sm)]">{tFooter('openSource')}</span>
9685
</p>
9786
<div className="flex gap-[var(--spacing-xs)]">
98-
<button
99-
type="button"
100-
onClick={toggleTheme}
101-
className="inline-block w-auto px-[var(--spacing-sm)] py-[var(--spacing-xs)] border border-[var(--color-border)] hover:bg-[var(--color-hover)] transition-colors text-xs font-light tracking-tight text-left"
102-
aria-label={tFooter('toggleTheme')}
103-
>
104-
{theme === 'light' ? `◐ ${tFooter('darkMode')}` : `◑ ${tFooter('lightMode')}`}
105-
</button>
87+
<ThemeSwitcher />
10688
<LanguageSwitcher />
10789
</div>
10890
</div>
@@ -123,5 +105,3 @@ function Footer() {
123105
</footer>
124106
)
125107
}
126-
127-
export default Footer
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use client'
2+
3+
import { Moon, Sun } from 'lucide-react'
4+
import { useTranslations } from 'next-intl'
5+
import { useTheme } from '../ThemeProvider'
6+
7+
/**
8+
* ThemeSwitcher component for toggling between light and dark themes
9+
* Displays a button with theme icon that allows users to switch themes
10+
*/
11+
export default function ThemeSwitcher() {
12+
const { theme, toggleTheme } = useTheme()
13+
const t = useTranslations('footer')
14+
15+
return (
16+
<button
17+
type="button"
18+
onClick={toggleTheme}
19+
className="footer-control-button"
20+
title={t('toggleTheme')}
21+
aria-label={t('toggleTheme')}
22+
>
23+
{theme === 'light' ? (
24+
<Moon className="footer-control-icon" />
25+
) : (
26+
<Sun className="footer-control-icon" />
27+
)}
28+
{theme === 'light' ? t('darkMode') : t('lightMode')}
29+
</button>
30+
)
31+
}

0 commit comments

Comments
 (0)