feat: add ThemeToggle component with next-themes

This commit is contained in:
2026-08-11 08:58:58 +02:00
parent 630f036f14
commit 3994d32d35
3 changed files with 62 additions and 0 deletions
+11
View File
@@ -15,6 +15,7 @@
"mariadb": "^3.5.3", "mariadb": "^3.5.3",
"next": "^16.3.0", "next": "^16.3.0",
"next-intl": "^4.13.6", "next-intl": "^4.13.6",
"next-themes": "^0.4.6",
"prisma": "^7.9.1", "prisma": "^7.9.1",
"react": "^19.2.8", "react": "^19.2.8",
"react-dom": "^19.2.8" "react-dom": "^19.2.8"
@@ -7096,6 +7097,16 @@
"integrity": "sha512-M2L8jtPEAXj0CPmXbiW66THdr3OnDqA9IsU1hqv3CdxtVow3Bl9eXPdT9Opeji7L4AFrUZ016dJOs+CoTw66OA==", "integrity": "sha512-M2L8jtPEAXj0CPmXbiW66THdr3OnDqA9IsU1hqv3CdxtVow3Bl9eXPdT9Opeji7L4AFrUZ016dJOs+CoTw66OA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/next-themes": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
"integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/node-addon-api": { "node_modules/node-addon-api": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+1
View File
@@ -39,6 +39,7 @@
"mariadb": "^3.5.3", "mariadb": "^3.5.3",
"next": "^16.3.0", "next": "^16.3.0",
"next-intl": "^4.13.6", "next-intl": "^4.13.6",
"next-themes": "^0.4.6",
"prisma": "^7.9.1", "prisma": "^7.9.1",
"react": "^19.2.8", "react": "^19.2.8",
"react-dom": "^19.2.8" "react-dom": "^19.2.8"
+50
View File
@@ -0,0 +1,50 @@
'use client'
import { useTheme } from 'next-themes'
import { useEffect, useState } from 'react'
function SunIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<circle cx="12" cy="12" r="5"/>
<line x1="12" y1="1" x2="12" y2="3"/>
<line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/>
<line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</svg>
)
}
function MoonIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
)
}
export function ThemeToggle() {
const { theme, setTheme } = useTheme()
const [mounted, setMounted] = useState(false)
useEffect(() => { setMounted(true) }, [])
if (!mounted) {
// Prevents hydration mismatch -- renders a same-size placeholder
return <div className="w-9 h-9" />
}
return (
<button
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
aria-label="Basculer le theme"
className="rounded-lg p-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-800 transition-colors"
>
{theme === 'dark' ? <SunIcon /> : <MoonIcon />}
</button>
)
}