Skip to content

Commit 782bafb

Browse files
authored
Merge pull request #60 from scape76/toggle-theme
2 parents f666dbf + b21832d commit 782bafb

File tree

12 files changed

+1032
-25
lines changed

12 files changed

+1032
-25
lines changed

package-lock.json

Lines changed: 672 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
"@auth/prisma-adapter": "^1.0.0",
1414
"@next-auth/prisma-adapter": "^1.0.7",
1515
"@prisma/client": "^4.16.2",
16+
"@radix-ui/react-dropdown-menu": "^2.0.5",
1617
"@radix-ui/react-slot": "^1.0.2",
1718
"class-variance-authority": "^0.6.1",
1819
"clsx": "^1.2.1",
1920
"lucide-react": "^0.259.0",
2021
"next": "13.4.9",
2122
"next-auth": "^4.22.1",
23+
"next-themes": "^0.2.1",
2224
"postcss": "8.4.25",
2325
"react": "18.2.0",
2426
"react-dom": "18.2.0",
@@ -28,15 +30,15 @@
2830
"typescript": "5.1.6"
2931
},
3032
"devDependencies": {
31-
"@typescript-eslint/eslint-plugin": "^5.61.0",
32-
"@typescript-eslint/parser": "^5.61.0",
33-
"eslint-plugin-jsx-a11y": "^6.7.1",
34-
"prisma": "^4.16.2",
3533
"@types/node": "20.4.1",
3634
"@types/react": "18.2.14",
3735
"@types/react-dom": "18.2.6",
36+
"@typescript-eslint/eslint-plugin": "^5.61.0",
37+
"@typescript-eslint/parser": "^5.61.0",
38+
"autoprefixer": "10.4.14",
3839
"eslint": "8.44.0",
3940
"eslint-config-next": "13.4.9",
40-
"autoprefixer": "10.4.14"
41+
"eslint-plugin-jsx-a11y": "^6.7.1",
42+
"prisma": "^4.16.2"
4143
}
4244
}

src/app/layout.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import "./globals.css";
33
import type { Metadata } from "next";
44
import { Inter } from "next/font/google";
55

6+
import { Footer } from "@/components/footer";
7+
import { ThemeProvider } from "@/components/theme-provider"
8+
69
const inter = Inter({ subsets: ["latin"] });
710

811
export const metadata: Metadata = {
@@ -18,8 +21,11 @@ export default function RootLayout({
1821
return (
1922
<html lang="en">
2023
<body className={inter.className}>
21-
<Header />
22-
{children}
24+
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
25+
<Header />
26+
{children}
27+
<Footer />
28+
</ThemeProvider>
2329
</body>
2430
</html>
2531
);

src/components/footer.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import * as React from "react";
2+
3+
import { cn } from "@/lib/utils";
4+
import { ModeToggle } from "@/components/mode-toggle";
5+
import Link from "next/link";
6+
import Image from "next/image";
7+
8+
export function Footer({ className }: React.HTMLAttributes<HTMLElement>) {
9+
return (
10+
<footer className={cn(className)}>
11+
<div className="container flex flex-col items-center justify-between gap-4 py-10 md:h-24 md:flex-row md:py-0">
12+
<div className="flex flex-col items-center gap-4 px-8 md:flex-row md:gap-2 md:px-0">
13+
<Link href="/">
14+
<Image
15+
unoptimized
16+
src="/static/logo.png"
17+
width={36}
18+
height={36}
19+
alt="Code Racer Logo"
20+
/>
21+
</Link>
22+
<p className="text-center text-sm leading-loose md:text-left">
23+
Built by{" "}
24+
<a
25+
href="https://twitter.com/webdevcody"
26+
target="_blank"
27+
rel="noreferrer"
28+
className="font-medium underline underline-offset-4"
29+
>
30+
Cody
31+
</a>{" "}
32+
& his community. The source code is available on{" "}
33+
<a
34+
href="https://github.com/webdevcody/code-racer"
35+
target="_blank"
36+
rel="noreferrer"
37+
className="font-medium underline underline-offset-4"
38+
>
39+
GitHub
40+
</a>
41+
.
42+
</p>
43+
</div>
44+
<ModeToggle />
45+
</div>
46+
</footer>
47+
);
48+
}

src/components/header.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const Header = () => {
1717

1818
return (
1919
<div>
20-
<nav className="bg-white w-full border-b md:border-0 md:static">
20+
<nav className="w-full border-b md:border-0 md:static">
2121
<div className="items-center px-4 max-w-screen-xl mx-auto md:flex md:px-8">
2222
<div className="flex items-center justify-between py-3 md:py-5 md:block">
2323
<Link href="/">
@@ -30,10 +30,7 @@ const Header = () => {
3030
/>
3131
</Link>
3232
<div className="md:hidden">
33-
<button
34-
className="text-gray-700 outline-none p-2 rounded-md focus:border-gray-400 focus:border"
35-
onClick={() => setState(!state)}
36-
>
33+
<Button onClick={() => setState(!state)}>
3734
{state ? (
3835
<svg
3936
xmlns="http://www.w3.org/2000/svg"
@@ -63,7 +60,7 @@ const Header = () => {
6360
/>
6461
</svg>
6562
)}
66-
</button>
63+
</Button>
6764
</div>
6865
</div>
6966
<div

src/components/icons.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Laptop, Moon, SunMedium, type Icon as LucideIcon } from "lucide-react";
2+
3+
export type Icon = LucideIcon;
4+
5+
export const Icons = {
6+
sun: SunMedium,
7+
moon: Moon,
8+
laptop: Laptop,
9+
};

src/components/mode-toggle.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
import { useTheme } from "next-themes";
5+
6+
import { Button } from "@/components/ui/button";
7+
import {
8+
DropdownMenu,
9+
DropdownMenuContent,
10+
DropdownMenuItem,
11+
DropdownMenuTrigger,
12+
} from "@/components/ui/dropdown-menu";
13+
import { Icons } from "@/components/icons";
14+
15+
export function ModeToggle() {
16+
const { setTheme } = useTheme();
17+
18+
return (
19+
<DropdownMenu>
20+
<DropdownMenuTrigger asChild>
21+
<Button variant="ghost" size="sm" className="h-8 w-8 px-0">
22+
<Icons.sun className="rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
23+
<Icons.moon className="absolute rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
24+
<span className="sr-only">Toggle theme</span>
25+
</Button>
26+
</DropdownMenuTrigger>
27+
<DropdownMenuContent align="end">
28+
<DropdownMenuItem onClick={() => setTheme("light")}>
29+
<Icons.sun className="mr-2 h-4 w-4" />
30+
<span>Light</span>
31+
</DropdownMenuItem>
32+
<DropdownMenuItem onClick={() => setTheme("dark")}>
33+
<Icons.moon className="mr-2 h-4 w-4" />
34+
<span>Dark</span>
35+
</DropdownMenuItem>
36+
<DropdownMenuItem onClick={() => setTheme("system")}>
37+
<Icons.laptop className="mr-2 h-4 w-4" />
38+
<span>System</span>
39+
</DropdownMenuItem>
40+
</DropdownMenuContent>
41+
</DropdownMenu>
42+
);
43+
}

src/components/theme-provider.tsx

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

src/components/typing/displayedCode.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ interface displayCodeProps {
77

88
export default function DisplayedCode({ code, errors }: displayCodeProps) {
99
return (
10-
<p className="text-gray-600 mb-4">
10+
<p className="text-foreground-accent mb-4">
1111
{code.split("").map((char, index) => (
1212
<span
1313
key={index}
1414
className={`${
15-
errors.includes(index) ? "text-red-500" : "opacity-100"
15+
errors.includes(index) ? "text-destructive" : "opacity-100"
1616
}`}
1717
>
1818
{char}

src/components/typing/typingCode.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import { useState, useEffect } from "react";
44

55
import DisplayedCode from "./displayedCode";
6+
import { Input } from "@/components/ui/input";
7+
import { Button } from "@/components/ui/button";
68

79
const code = `printf("hello world")`;
810

@@ -54,15 +56,15 @@ export default function TypingCode() {
5456
};
5557

5658
return (
57-
<div className="w-3/4 p-8 bg-gray-100 rounded-md">
59+
<div className="w-3/4 p-8 bg-accent rounded-md">
5860
<h1 className="text-2xl font-bold mb-4">Type this code:</h1>
5961
<DisplayedCode code={code} errors={errors} />
60-
<input
62+
<Input
6163
type="text"
6264
value={input}
6365
onChange={handleInputChange}
6466
disabled={endTime !== null}
65-
className="w-full p-2 border border-gray-300 rounded mb-4"
67+
className="w-full p-2 border border-border rounded mb-4"
6668
/>
6769
{endTime && (
6870
<div>
@@ -73,12 +75,7 @@ export default function TypingCode() {
7375
<p className="mb-4">Errors: {errors.length}</p>
7476
</div>
7577
)}
76-
<button
77-
onClick={handleRestart}
78-
className="bg-black text-white py-2 px-4 rounded"
79-
>
80-
Restart
81-
</button>
78+
<Button onClick={handleRestart}>Restart</Button>
8279
</div>
8380
);
8481
}

0 commit comments

Comments
 (0)