Skip to content
Draft
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
package.json
package-lock.json
public
next-env.d.ts
2 changes: 1 addition & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts"
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
19 changes: 13 additions & 6 deletions src/components/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,27 @@ export const Checkbox: React.FC<CheckboxProps> = ({
className={classNames([
"checkbox",
"appearance-none",
"h-4",
"w-4",
"h-5",
"w-5",
"shrink-0",
"border",
"border-2",
"border-gray-300",
"dark:border-gray-600",
"rounded-md",
"bg-white",
"checked:bg-purple-700",
"checked:border-purple-700",
"dark:bg-gray-700",
"checked:bg-orange-500",
"dark:checked:bg-orange-600",
"checked:border-orange-500",
"dark:checked:border-orange-600",
"transition",
"duration-200",
"cursor-pointer",
"focus:outline-none",
"focus-visible:border-black",
"focus:ring-2",
"focus:ring-orange-500",
"focus:ring-offset-2",
"dark:focus:ring-offset-gray-800",
className,
])}
/>
Expand Down
19 changes: 13 additions & 6 deletions src/components/header.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import React from "react"
import Link from "next/link"
import ContentContainer from "./content-container"
import ThemeToggle from "./theme-toggle"

interface HeaderProps {
title: string
}

const Header: React.FC<HeaderProps> = ({ title }) => (
<header className="bg-purple-700">
<header className="bg-blue-600 dark:bg-blue-900 shadow-md">
<ContentContainer>
<h1 className="py-4">
<Link href="/" className="text-white text-xl font-bold no-underline">
{title}
</Link>
</h1>
<div className="py-4 flex items-center justify-between">
<h1>
<Link
href="/"
className="text-white text-xl font-bold no-underline hover:opacity-90 transition-opacity"
>
{title}
</Link>
</h1>
<ThemeToggle />
</div>
</ContentContainer>
</header>
)
Expand Down
46 changes: 46 additions & 0 deletions src/components/theme-toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from "react"
import { useTheme } from "../hooks/useTheme"

const ThemeToggle: React.FC = () => {
const { theme, toggleTheme } = useTheme()

return (
<button
onClick={toggleTheme}
className="p-2 rounded-lg bg-white dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors border border-gray-300 dark:border-gray-600 cursor-pointer"
aria-label="Toggle theme"
>
{theme === "light" ? (
<svg
className="w-5 h-5 text-gray-800 dark:text-gray-200"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
/>
</svg>
) : (
<svg
className="w-5 h-5 text-yellow-400 dark:text-yellow-300"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
/>
</svg>
)}
</button>
)
}

export default ThemeToggle
40 changes: 40 additions & 0 deletions src/hooks/useTheme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useEffect, useRef, useState } from "react"

function getInitialTheme(): "light" | "dark" {
if (typeof window === "undefined") return "light"

const savedTheme = localStorage.getItem("theme") as "light" | "dark" | null
if (savedTheme) return savedTheme

return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light"
}

export function useTheme() {
const isInitialMount = useRef(true)
const [theme, setTheme] = useState<"light" | "dark">("light")

// Initialize theme on mount - this is intentional and safe
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false
const initialTheme = getInitialTheme()
// eslint-disable-next-line react-hooks/set-state-in-effect
setTheme(initialTheme)
document.documentElement.classList.remove("light", "dark")
document.documentElement.classList.add(initialTheme)
}
}, [])

const toggleTheme = () => {
const newTheme = theme === "light" ? "dark" : "light"
setTheme(newTheme)
localStorage.setItem("theme", newTheme)
// Immediately update DOM for instant feedback
document.documentElement.classList.remove("light", "dark")
document.documentElement.classList.add(newTheme)
}

return { theme, toggleTheme }
}
8 changes: 4 additions & 4 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ const sourceSans3 = Source_Sans_3({

const App: React.FC<AppProps> = ({ Component, pageProps }) => {
return (
<main className={sourceSans3.className}>
<div className={sourceSans3.className}>
<SEO />
<Header title="Nathan's Cookbook" />
<ContentContainer>
<div className="my-8">
<main className="my-6">
<Component {...pageProps} />
</div>
</main>
</ContentContainer>
</main>
</div>
)
}

Expand Down
28 changes: 28 additions & 0 deletions src/pages/_document.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Html, Head, Main, NextScript } from "next/document"

export default function Document() {
return (
<Html>
<Head />
<body>
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
try {
var theme = localStorage.getItem('theme');
if (!theme) {
theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
document.documentElement.classList.add(theme);
} catch (e) {}
})();
`,
}}
/>
<Main />
<NextScript />
</body>
</Html>
)
}
17 changes: 12 additions & 5 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,19 @@ const Index: React.FC<IndexProps> = ({ recipes }) => {
<React.Fragment>
{recipes.map((recipe) => (
<article className="mb-4" key={recipe.slug}>
<Link href={`/recipes/${recipe.slug}`}>
<h2 className="text-2xl hover:text-pink-800 focus:text-pink-800">
<h2 className="text-2xl">
<Link
href={`/recipes/${recipe.slug}`}
className="hover:text-orange-600 dark:hover:text-orange-400 transition-colors"
>
{recipe.title}
</h2>
</Link>
{recipe.description && <p>{recipe.description}</p>}
</Link>
</h2>
{recipe.description && (
<p className="text-gray-600 dark:text-gray-400">
{recipe.description}
</p>
)}
</article>
))}
</React.Fragment>
Expand Down
Loading