added dark mode

This commit is contained in:
2026-02-15 23:11:33 -06:00
parent e97347ff65
commit d8fde5b516
18 changed files with 543 additions and 366 deletions

View File

@@ -0,0 +1,106 @@
# Dark Mode Implementation Plan
## Context
The Synculous client already has a bare `@media (prefers-color-scheme: dark)` in `globals.css` that only flips `body` background/foreground. Every component uses hardcoded Tailwind classes (`bg-white`, `bg-gray-50`, `text-gray-900`, etc.) with no `dark:` variants, so manually switching modes via a button has no effect. The goal is a user-controlled toggle that persists preference, respects the OS default, and applies comprehensive dark styles across all pages.
---
## Steps
### 1. Configure Tailwind v4 for class-based dark mode
**File:** `src/app/globals.css`
Add the Tailwind v4 CSS directive that maps `dark:` utilities to an ancestor `.dark` class:
```css
@variant dark (&:where(.dark, .dark *));
```
Replace the `@media (prefers-color-scheme: dark)` block with a `.dark` selector:
```css
.dark {
--background: #0a0a0a;
--foreground: #ededed;
}
```
### 2. Create ThemeProvider
**New file:** `src/components/theme/ThemeProvider.tsx`
A minimal React context provider:
- State: `isDark: boolean`
- On mount: reads `localStorage.getItem('theme')`, falls back to `window.matchMedia('(prefers-color-scheme: dark)').matches`
- Applies/removes `dark` class on `document.documentElement`
- `toggleDark()` function that flips state and saves to `localStorage`
- Exports `useTheme()` hook
### 3. Anti-flash script + wrap in layout
**File:** `src/app/layout.tsx`
- Add ThemeProvider wrapping `AuthProvider`
- Add inline `<script>` before `<body>` content to set `.dark` class before hydration (prevents white flash)
### 4. Dark mode toggle button in dashboard header
**File:** `src/app/dashboard/layout.tsx`
- Import `useTheme` from the ThemeProvider
- Add a sun/moon icon button in the existing right-side icon row (between Settings and Logout)
- `SunIcon` and `MoonIcon` already exist in `src/components/ui/Icons.tsx` — no new icons needed
### 5. Add `dark:` variants to all components
Color mapping:
| Light class | Dark variant |
|---|---|
| `bg-white` | `dark:bg-gray-800` |
| `bg-gray-50` | `dark:bg-gray-900` |
| `bg-gray-100` | `dark:bg-gray-700` |
| `bg-gray-200` | `dark:bg-gray-600` |
| `text-gray-900` | `dark:text-gray-100` |
| `text-gray-700` | `dark:text-gray-300` |
| `text-gray-600` | `dark:text-gray-400` |
| `text-gray-500` | `dark:text-gray-400` |
| `border-gray-200` | `dark:border-gray-700` |
| `border-gray-100` | `dark:border-gray-800` |
| `border-gray-300` | `dark:border-gray-600` |
| `bg-indigo-50` | `dark:bg-indigo-900/30` |
| `bg-green-50` | `dark:bg-green-900/30` |
| `bg-amber-50` | `dark:bg-amber-900/30` |
| `bg-blue-50` | `dark:bg-blue-900/30` |
| `bg-red-50` | `dark:bg-red-900/30` |
**Files to update (in order):**
1. `src/app/dashboard/layout.tsx` — shell, header, nav
2. `src/app/dashboard/page.tsx` — Today view
3. `src/app/dashboard/routines/page.tsx`
4. `src/app/dashboard/routines/new/page.tsx`
5. `src/app/dashboard/routines/[id]/page.tsx`
6. `src/app/dashboard/routines/[id]/launch/page.tsx`
7. `src/app/dashboard/routines/[id]/run/page.tsx`
8. `src/app/dashboard/templates/page.tsx`
9. `src/app/dashboard/history/page.tsx`
10. `src/app/dashboard/stats/page.tsx`
11. `src/app/dashboard/medications/page.tsx`
12. `src/app/dashboard/medications/new/page.tsx`
13. `src/app/dashboard/settings/page.tsx`
14. `src/app/login/page.tsx`
15. `src/components/session/VisualTimeline.tsx`
16. `src/components/notifications/PushNotificationToggle.tsx`
---
## Files to create
- `src/components/theme/ThemeProvider.tsx` (new)
## Files to modify
- `src/app/globals.css`
- `src/app/layout.tsx`
- All pages listed in step 5
---
## Verification
1. Run `npm run dev` in `synculous-client/`
2. Navigate to `/dashboard` — should default to OS preference
3. Click the sun/moon toggle in the header — UI should switch immediately
4. Refresh the page — theme should persist (no flash)
5. Switch OS theme — manual override takes priority; no override follows OS