Skip to content

Commit a988fd0

Browse files
committed
Add themes
1 parent aee48a5 commit a988fd0

File tree

11 files changed

+121
-53
lines changed

11 files changed

+121
-53
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"lucide-react": "^0.509.0",
4444
"next": "^15.3.2",
4545
"next-intl": "^4.1.0",
46+
"next-themes": "^0.4.6",
4647
"pg": "^8.13.0",
4748
"pino": "^9.5.0",
4849
"pino-pretty": "^13.0.0",

pnpm-lock.yaml

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/layout.tsx

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ import type { Metadata } from 'next';
44
import { NextIntlClientProvider } from 'next-intl';
55
import { getLocale } from 'next-intl/server';
66

7+
import { ThemeProvider } from '@/components/ThemeProvider';
8+
79
export const metadata: Metadata = {
810
icons: [
9-
{
10-
rel: 'apple-touch-icon',
11-
url: '/apple-touch-icon.png',
12-
},
11+
{ rel: 'apple-touch-icon', url: '/apple-touch-icon.png' },
1312
{
1413
rel: 'icon',
1514
type: 'image/png',
@@ -22,14 +21,15 @@ export const metadata: Metadata = {
2221
sizes: '16x16',
2322
url: '/favicon-16x16.png',
2423
},
25-
{
26-
rel: 'icon',
27-
url: '/favicon.ico',
28-
},
24+
{ rel: 'icon', url: '/favicon.ico' },
2925
],
3026
};
3127

32-
export default async function RootLayout(props: { children: React.ReactNode }) {
28+
export default async function RootLayout({
29+
children,
30+
}: {
31+
children: React.ReactNode;
32+
}) {
3333
const locale = await getLocale();
3434

3535
return (
@@ -38,7 +38,16 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
3838
className="bg-background text-foreground antialiased"
3939
suppressHydrationWarning
4040
>
41-
<NextIntlClientProvider>{props.children}</NextIntlClientProvider>
41+
<NextIntlClientProvider>
42+
<ThemeProvider
43+
attribute="class"
44+
defaultTheme="system"
45+
enableSystem
46+
disableTransitionOnChange
47+
>
48+
{children}
49+
</ThemeProvider>
50+
</NextIntlClientProvider>
4251
</body>
4352
</html>
4453
);

src/components/LocaleSwitcher.tsx

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ import { useLocale } from 'next-intl';
66

77
import { Locales, isLocale } from '@/types/locale';
88
import { setLocale } from '@/actions/locale';
9-
import { Button } from '@/components/ui/button';
109
import {
10+
Button,
1111
DropdownMenu,
1212
DropdownMenuContent,
1313
DropdownMenuRadioGroup,
1414
DropdownMenuRadioItem,
1515
DropdownMenuTrigger,
16-
} from '@/components/ui/dropdown-menu';
16+
} from '@/components/ui';
17+
import { Globe } from 'lucide-react';
1718

1819
export const LocaleSwitcher = () => {
1920
const router = useRouter();
@@ -32,24 +33,8 @@ export const LocaleSwitcher = () => {
3233
return (
3334
<DropdownMenu>
3435
<DropdownMenuTrigger asChild>
35-
<Button
36-
className="p-2 focus-visible:ring-offset-0"
37-
variant="ghost"
38-
size="icon"
39-
aria-label="lang-switcher"
40-
>
41-
<svg
42-
xmlns="http://www.w3.org/2000/svg"
43-
className="size-6 stroke-current stroke-2"
44-
fill="none"
45-
strokeLinecap="round"
46-
strokeLinejoin="round"
47-
viewBox="0 0 24 24"
48-
>
49-
<path stroke="none" d="M0 0h24v24H0z" />
50-
<path d="M3 12a9 9 0 1 0 18 0 9 9 0 0 0-18 0M3.6 9h16.8M3.6 15h16.8" />
51-
<path d="M11.5 3a17 17 0 0 0 0 18M12.5 3a17 17 0 0 1 0 18" />
52-
</svg>
36+
<Button variant="ghost" size="icon">
37+
<Globe />
5338
</Button>
5439
</DropdownMenuTrigger>
5540
<DropdownMenuContent>

src/components/ThemeProvider.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use client';
2+
3+
import * as React from 'react';
4+
import { ThemeProvider as NextThemesProvider } from 'next-themes';
5+
6+
export function ThemeProvider({
7+
children,
8+
...props
9+
}: React.ComponentProps<typeof NextThemesProvider>) {
10+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
11+
}

src/components/ThemeSwitcher.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use client';
2+
3+
import { Moon, Sun } from 'lucide-react';
4+
import { useTheme } from 'next-themes';
5+
6+
import {
7+
Button,
8+
DropdownMenu,
9+
DropdownMenuContent,
10+
DropdownMenuItem,
11+
DropdownMenuTrigger,
12+
} from '@/components/ui';
13+
14+
export function ThemeSwitcher() {
15+
const { setTheme } = useTheme();
16+
17+
return (
18+
<DropdownMenu>
19+
<DropdownMenuTrigger asChild>
20+
<Button variant="ghost" size="icon">
21+
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
22+
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
23+
</Button>
24+
</DropdownMenuTrigger>
25+
<DropdownMenuContent align="end">
26+
<DropdownMenuItem onClick={() => setTheme('light')}>
27+
Light
28+
</DropdownMenuItem>
29+
<DropdownMenuItem onClick={() => setTheme('dark')}>
30+
Dark
31+
</DropdownMenuItem>
32+
<DropdownMenuItem onClick={() => setTheme('system')}>
33+
System
34+
</DropdownMenuItem>
35+
</DropdownMenuContent>
36+
</DropdownMenu>
37+
);
38+
}

src/components/ToggleMenuButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type ForwardedRef, forwardRef } from 'react';
22

3-
import { Button } from '@/components/ui/button';
3+
import { Button } from '@/components/ui';
44

55
type ToggleMenuButtonProps = {
66
onClick?: () => void;

src/components/dashboard/DashboardHeader.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import {
1111
DropdownMenuContent,
1212
DropdownMenuItem,
1313
DropdownMenuTrigger,
14-
} from '@/components/ui/dropdown-menu';
15-
import { Separator } from '@/components/ui/separator';
14+
Separator,
15+
} from '@/components/ui';
1616
import { Logo } from '@/components/templates/Logo';
1717

1818
type DashboardHeaderProps = {

src/components/landing/CenteredMenu.tsx

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@ import { ToggleMenuButton } from '@/components/ToggleMenuButton';
66
import { useMenu } from '@/hooks/useMenu';
77
import { cn } from '@/lib/utils';
88

9-
export const CenteredMenu = (props: {
9+
type CenteredMenuProps = {
1010
logo: React.ReactNode;
11-
children: React.ReactNode;
1211
rightMenu: React.ReactNode;
13-
}) => {
12+
children?: React.ReactNode;
13+
};
14+
15+
export const CenteredMenu = ({
16+
logo,
17+
rightMenu,
18+
children,
19+
}: CenteredMenuProps) => {
1420
const { showMenu, handleToggleMenu } = useMenu();
1521

1622
const navClass = cn('max-lg:w-full max-lg:bg-secondary max-lg:p-5', {
@@ -19,26 +25,25 @@ export const CenteredMenu = (props: {
1925

2026
return (
2127
<div className="flex flex-wrap items-center justify-between">
22-
<Link href="/">{props.logo}</Link>
23-
28+
<Link href="/">{logo}</Link>
2429
<div className="lg:hidden [&_button:hover]:opacity-100 [&_button]:opacity-60">
2530
<ToggleMenuButton onClick={handleToggleMenu} />
2631
</div>
27-
28-
<nav className={cn('rounded-t max-lg:mt-2', navClass)}>
29-
<ul className="flex gap-x-6 gap-y-1 text-lg font-medium max-lg:flex-col [&_a:hover]:opacity-100 [&_a]:opacity-60 max-lg:[&_a]:inline-block max-lg:[&_a]:w-full">
30-
{props.children}
31-
</ul>
32-
</nav>
33-
32+
{children && (
33+
<nav className={cn('rounded-t max-lg:mt-2', navClass)}>
34+
<ul className="flex gap-x-6 gap-y-1 text-lg font-medium max-lg:flex-col [&_a:hover]:opacity-100 [&_a]:opacity-60 max-lg:[&_a]:inline-block max-lg:[&_a]:w-full">
35+
{children}
36+
</ul>
37+
</nav>
38+
)}
3439
<div
3540
className={cn(
3641
'rounded-b max-lg:border-t max-lg:border-border',
3742
navClass,
3843
)}
3944
>
4045
<ul className="flex flex-row items-center gap-x-1.5 text-lg font-medium [&_li[data-fade]:hover]:opacity-100 [&_li[data-fade]]:opacity-60">
41-
{props.rightMenu}
46+
{rightMenu}
4247
</ul>
4348
</div>
4449
</div>

src/components/templates/Navbar.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import Link from 'next/link';
22
import { useTranslations } from 'next-intl';
33

4+
import { Button } from '@/components/ui';
45
import { LocaleSwitcher } from '@/components/LocaleSwitcher';
5-
import { buttonVariants } from '@/components/ui/button';
6+
import { ThemeSwitcher } from '@/components/ThemeSwitcher';
67
import { CenteredMenu } from '@/components/landing/CenteredMenu';
78
import { Section } from '@/components/landing/Section';
89

@@ -20,19 +21,20 @@ export const Navbar = () => {
2021
<li data-fade>
2122
<LocaleSwitcher />
2223
</li>
24+
<li data-fade>
25+
<ThemeSwitcher />
26+
</li>
2327
<li className="ml-1 mr-2.5" data-fade>
2428
<Link href="/sign-in">{t('sign_in')}</Link>
2529
</li>
2630
<li>
27-
<Link className={buttonVariants()} href="/sign-up">
28-
{t('sign_up')}
29-
</Link>
31+
<Button asChild>
32+
<Link href="/sign-up">{t('sign_up')}</Link>
33+
</Button>
3034
</li>
3135
</>
3236
}
33-
>
34-
<div />
35-
</CenteredMenu>
37+
/>
3638
</Section>
3739
);
3840
};

0 commit comments

Comments
 (0)