Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/icon-192.png" />
<title>Polaroid — print-at-home photo frames</title>
<script>
// Apply the saved (or system) theme before paint to avoid a flash.
try {
var t = localStorage.getItem('theme')
if (t === 'dark' || (!t && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark')
} catch (e) {}
</script>
</head>
<body>
<div id="root"></div>
Expand Down
32 changes: 32 additions & 0 deletions apps/web/src/components/theme-toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Moon, Sun } from 'lucide-react'

import { Button } from '@/components/ui/button'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip'
import { useThemeStore } from '@/stores/theme-store'

export function ThemeToggle() {
const theme = useThemeStore((state) => state.theme)
const toggle = useThemeStore((state) => state.toggle)
const dark = theme === 'dark'

return (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
className="size-8"
aria-label={dark ? 'Switch to light mode' : 'Switch to dark mode'}
onClick={toggle}
>
{dark ? <Sun className="size-4" /> : <Moon className="size-4" />}
</Button>
</TooltipTrigger>
<TooltipContent>{dark ? 'Light mode' : 'Dark mode'}</TooltipContent>
</Tooltip>
)
}
6 changes: 5 additions & 1 deletion apps/web/src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { A4Preview } from '@/components/a4-preview'
import { OptionsPanel } from '@/components/options-panel'
import { PhotoSidebar } from '@/components/photo-sidebar'
import { ProjectControls } from '@/components/project-controls'
import { ThemeToggle } from '@/components/theme-toggle'

export const Route = createFileRoute('/')({
component: Home,
Expand All @@ -19,7 +20,10 @@ function Home() {
Edit each frame on the page — everything stays on your device.
</p>
</div>
<ProjectControls />
<div className="flex items-start gap-2">
<ThemeToggle />
<ProjectControls />
</div>
</header>

<div className="grid gap-6 lg:grid-cols-[240px_minmax(0,1fr)_300px] lg:items-start">
Expand Down
30 changes: 30 additions & 0 deletions apps/web/src/stores/theme-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { create } from 'zustand'

export type Theme = 'light' | 'dark'

// The initial class is set by an inline script in index.html (before paint),
// so we just read it back here to seed the store.
function currentTheme(): Theme {
if (typeof document === 'undefined') return 'light'
return document.documentElement.classList.contains('dark') ? 'dark' : 'light'
}

interface ThemeState {
theme: Theme
setTheme: (theme: Theme) => void
toggle: () => void
}

export const useThemeStore = create<ThemeState>((set, get) => ({
theme: currentTheme(),
setTheme: (theme) => {
document.documentElement.classList.toggle('dark', theme === 'dark')
try {
localStorage.setItem('theme', theme)
} catch {
// Private mode / storage disabled — the choice just won't persist.
}
set({ theme })
},
toggle: () => get().setTheme(get().theme === 'dark' ? 'light' : 'dark'),
}))
2 changes: 2 additions & 0 deletions apps/web/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@custom-variant dark (&:is(.dark *));

:root {
color-scheme: light;
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
Expand All @@ -24,6 +25,7 @@
}

.dark {
color-scheme: dark;
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
Expand Down
Loading