Skip to content

Commit 0db3b80

Browse files
authored
add: dark mode (#5)
1 parent be41866 commit 0db3b80

File tree

10 files changed

+125
-28
lines changed

10 files changed

+125
-28
lines changed

app/globals.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
body {
5858
@apply bg-background text-foreground;
5959
font-feature-settings: "rlig" 1, "calt" 1;
60+
color-scheme: light dark;
6061
}
6162

6263
html {

app/layout.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import "./globals.css";
55
import { Footer } from "@/components/footer";
66
import { Header } from "@/components/header";
77
import { SkipLink } from "@/components/layout/SkipLink";
8+
import { ThemeProvider } from "@/components/layout/ThemeProvider";
89

910
const inter = Inter({
1011
subsets: ["latin"],
@@ -35,12 +36,14 @@ export default function RootLayout({
3536
children: React.ReactNode;
3637
}>) {
3738
return (
38-
<html lang="en" className={inter.variable}>
39+
<html lang="en" className={inter.variable} suppressHydrationWarning>
3940
<body className="flex min-h-screen flex-col antialiased">
40-
<SkipLink />
41-
<Header />
42-
<main id="main-content" className="flex-1">{children}</main>
43-
<Footer />
41+
<ThemeProvider>
42+
<SkipLink />
43+
<Header />
44+
<main id="main-content" className="flex-1">{children}</main>
45+
<Footer />
46+
</ThemeProvider>
4447
</body>
4548
</html>
4649
);

app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export default function Home() {
161161
</CardHeader>
162162
<CardContent className="space-y-4">
163163
{pubWithImage.image && (
164-
<div className="overflow-hidden rounded-lg border bg-muted/30">
164+
<div className="overflow-hidden rounded-lg border bg-white shadow-sm dark:bg-white/90">
165165
<img
166166
src={pubWithImage.image}
167167
alt={pub.title}

app/research/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function MediaMosaic({ images, title }: { images: string[]; title: string }) {
5151

5252
if (images.length === 1) {
5353
return (
54-
<figure className="overflow-hidden rounded-xl ring-1 ring-border/50">
54+
<figure className="overflow-hidden rounded-xl bg-white ring-1 ring-border/50 dark:bg-white/95">
5555
<img
5656
src={images[0]}
5757
alt={title}
@@ -76,7 +76,7 @@ function MediaMosaic({ images, title }: { images: string[]; title: string }) {
7676
{images.map((img, idx) => (
7777
<figure
7878
key={idx}
79-
className="group relative aspect-[4/3] overflow-hidden rounded-xl ring-1 ring-border/50 transition-all duration-200 hover:scale-[1.01] hover:shadow-lg"
79+
className="group relative aspect-[4/3] overflow-hidden rounded-xl bg-white ring-1 ring-border/50 transition-all duration-200 hover:scale-[1.01] hover:shadow-lg dark:bg-white/95"
8080
>
8181
<img
8282
src={img}

components/animations/AnimatedLogo.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function AnimatedLogo({ className = "" }: { className?: string }) {
2323
<img
2424
src="/clean_logo.svg"
2525
alt="Mathis Group Logo"
26-
className="h-auto w-full"
26+
className="h-auto w-full transition-shadow duration-500 dark:brightness-110 dark:drop-shadow-[0_20px_60px_rgba(255,255,255,0.18)]"
2727
/>
2828
</div>
2929
</div>

components/footer.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,15 @@ export function Footer() {
6363
alt="EPFL"
6464
width={150}
6565
height={64}
66-
className="h-10 w-auto"
66+
className="h-10 w-auto dark:hidden"
67+
priority={false}
68+
/>
69+
<Image
70+
src="/images/footer/epfl_logo_r.png"
71+
alt="EPFL"
72+
width={150}
73+
height={64}
74+
className="hidden h-10 w-auto dark:inline"
6775
priority={false}
6876
/>
6977
</a>

components/funding/FunderLogo.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ export function FunderLogo({
3333
<div
3434
className={cn(
3535
"relative flex items-center justify-center overflow-hidden rounded-2xl border border-border/50 bg-gradient-to-br from-muted/30 to-muted/60 shadow-sm transition-all duration-300",
36+
"dark:border-white/10 dark:from-white/5 dark:to-white/[0.07]",
3637
"group-hover:border-border group-hover:shadow-soft group-hover:from-muted/50 group-hover:to-muted/80",
38+
"dark:group-hover:border-white/20 dark:group-hover:from-white/10 dark:group-hover:to-white/[0.12]",
3739
sizeClasses[size],
3840
containerPadding[size],
3941
className
@@ -42,8 +44,7 @@ export function FunderLogo({
4244
<img
4345
src={logo}
4446
alt={`${name} logo`}
45-
className="size-full object-contain opacity-60 grayscale transition-all duration-500 ease-out group-hover:scale-105 group-hover:opacity-100 group-hover:grayscale-0"
46-
style={{ mixBlendMode: "multiply" }}
47+
className="size-full object-contain opacity-70 mix-blend-multiply grayscale transition-all duration-500 ease-out group-hover:scale-105 group-hover:opacity-100 group-hover:grayscale-0 dark:opacity-90 dark:mix-blend-normal dark:grayscale-0"
4748
/>
4849
</div>
4950
)

components/header.tsx

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { usePathname } from "next/navigation"
66
import * as React from "react"
77
import { createPortal } from "react-dom"
88

9+
import { ThemeToggle } from "@/components/layout/ThemeToggle"
910
import { Button } from "@/components/ui/button"
1011
import { useHeaderOffset } from "@/lib/hooks/useHeaderOffset"
1112
import { cn } from "@/lib/utils"
@@ -68,7 +69,8 @@ export function Header() {
6869
<span className="text-xl font-bold tracking-tight">Mathis Group</span>
6970
</Link>
7071
</div>
71-
<div className="flex lg:hidden">
72+
<div className="flex items-center gap-2 lg:hidden">
73+
<ThemeToggle />
7274
<Button
7375
variant="ghost"
7476
size="icon"
@@ -84,21 +86,24 @@ export function Header() {
8486
)}
8587
</Button>
8688
</div>
87-
<div className="hidden lg:flex lg:gap-x-8">
88-
{navigation.map((item) => (
89-
<Link
90-
key={item.name}
91-
href={item.href}
92-
className={cn(
93-
"text-sm font-medium transition-colors hover:text-primary",
94-
pathname === item.href
95-
? "text-foreground"
96-
: "text-muted-foreground"
97-
)}
98-
>
99-
{item.name}
100-
</Link>
101-
))}
89+
<div className="hidden items-center gap-6 lg:flex">
90+
<div className="flex items-center gap-8">
91+
{navigation.map((item) => (
92+
<Link
93+
key={item.name}
94+
href={item.href}
95+
className={cn(
96+
"text-sm font-medium transition-colors hover:text-primary",
97+
pathname === item.href
98+
? "text-foreground"
99+
: "text-muted-foreground"
100+
)}
101+
>
102+
{item.name}
103+
</Link>
104+
))}
105+
</div>
106+
<ThemeToggle />
102107
</div>
103108
</nav>
104109

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"use client"
2+
3+
import { ThemeProvider as NextThemesProvider } from "next-themes"
4+
import type { ThemeProviderProps } from "next-themes/dist/types"
5+
6+
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
7+
return (
8+
<NextThemesProvider
9+
attribute="class"
10+
defaultTheme="system"
11+
enableSystem
12+
disableTransitionOnChange
13+
{...props}
14+
>
15+
{children}
16+
</NextThemesProvider>
17+
)
18+
}

components/layout/ThemeToggle.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"use client"
2+
3+
import { Moon, Sun } from "lucide-react"
4+
import { useTheme } from "next-themes"
5+
import { useEffect, useState, type ComponentProps } from "react"
6+
7+
import { Button } from "@/components/ui/button"
8+
import { cn } from "@/lib/utils"
9+
10+
type ThemeToggleProps = {
11+
className?: string
12+
size?: ComponentProps<typeof Button>["size"]
13+
variant?: ComponentProps<typeof Button>["variant"]
14+
}
15+
16+
export function ThemeToggle({
17+
className,
18+
size = "icon",
19+
variant = "ghost",
20+
}: ThemeToggleProps) {
21+
const { theme, setTheme, resolvedTheme } = useTheme()
22+
const [mounted, setMounted] = useState(false)
23+
24+
useEffect(() => {
25+
setMounted(true)
26+
}, [])
27+
28+
const currentTheme = theme === "system" ? resolvedTheme : theme
29+
const isDark = currentTheme === "dark"
30+
31+
const handleToggle = () => {
32+
setTheme(isDark ? "light" : "dark")
33+
}
34+
35+
return (
36+
<Button
37+
type="button"
38+
variant={variant}
39+
size={size}
40+
onClick={handleToggle}
41+
aria-label="Toggle theme"
42+
className={cn("relative", className)}
43+
>
44+
<Sun
45+
className={cn(
46+
"size-5 transition-all duration-300",
47+
mounted && isDark ? "rotate-90 scale-0 opacity-0" : "rotate-0 scale-100 opacity-100"
48+
)}
49+
aria-hidden="true"
50+
/>
51+
<Moon
52+
className={cn(
53+
"absolute inset-0 m-auto size-5 transition-all duration-300",
54+
mounted && isDark ? "rotate-0 scale-100 opacity-100" : "rotate-90 scale-0 opacity-0"
55+
)}
56+
aria-hidden="true"
57+
/>
58+
<span className="sr-only">Toggle theme</span>
59+
</Button>
60+
)
61+
}

0 commit comments

Comments
 (0)