costco-grocery-list/docs/archive/settings-dark-mode.md
2026-01-27 00:03:58 -08:00

10 KiB

Settings & Dark Mode Implementation

Status: Phase 1 Complete, Phase 2 Complete
Last Updated: January 2026


Overview

A comprehensive user settings system with persistent preferences, dark mode support, and customizable list display options. Settings are stored per-user in localStorage and automatically applied across the application.


Architecture

Context Hierarchy

<ConfigProvider>          ← Server config (image limits, etc.)
  <AuthProvider>          ← Authentication state
    <SettingsProvider>    ← User preferences (NEW)
      <App />
    </SettingsProvider>
  </AuthProvider>
</ConfigProvider>

Key Components

SettingsContext (frontend/src/context/SettingsContext.jsx)

  • Manages user preferences with localStorage persistence
  • Storage key pattern: user_preferences_${username}
  • Automatically applies theme to document.documentElement
  • Listens for system theme changes in auto mode
  • Provides updateSettings() and resetSettings() methods

Settings Page (frontend/src/pages/Settings.jsx)

  • Tabbed interface: Appearance, List Display, Behavior
  • Real-time preview of setting changes
  • Reset to defaults functionality

Settings Schema

{
  // === Appearance ===
  theme: "light" | "dark" | "auto",  // Theme mode
  compactView: false,                 // Reduced spacing for denser lists
  
  // === List Display ===
  defaultSortMode: "zone",           // Default: "zone" | "az" | "za" | "qty-high" | "qty-low"
  showRecentlyBought: true,          // Toggle recently bought section
  recentlyBoughtCount: 10,           // Initial items shown (5-50)
  recentlyBoughtCollapsed: false,    // Start section collapsed
  
  // === Behavior ===
  confirmBeforeBuy: true,            // Show confirmation modal
  autoReloadInterval: 0,             // Auto-refresh in minutes (0 = disabled)
  hapticFeedback: true,              // Vibration on mobile interactions
  
  // === Advanced ===
  debugMode: false                   // Developer tools (future)
}

Dark Mode Implementation

Theme System

Three modes:

  1. Light: Force light theme
  2. Dark: Force dark theme
  3. Auto: Follow system preferences with live updates

CSS Variable Architecture

All colors use CSS custom properties defined in frontend/src/styles/theme.css:

Light Mode (:root):

:root {
  --color-text-primary: #212529;
  --color-bg-body: #f8f9fa;
  --color-bg-surface: #ffffff;
  /* ... */
}

Dark Mode ([data-theme="dark"]):

[data-theme="dark"] {
  --color-text-primary: #f1f5f9;
  --color-bg-body: #0f172a;
  --color-bg-surface: #1e293b;
  /* ... */
}

Theme Application Logic

// In SettingsContext.jsx
useEffect(() => {
  const applyTheme = () => {
    let theme = settings.theme;
    
    // Auto mode: check system preference
    if (theme === "auto") {
      const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
      theme = prefersDark ? "dark" : "light";
    }
    
    document.documentElement.setAttribute("data-theme", theme);
  };
  
  applyTheme();
  
  // Listen for system theme changes
  if (settings.theme === "auto") {
    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    mediaQuery.addEventListener("change", applyTheme);
    return () => mediaQuery.removeEventListener("change", applyTheme);
  }
}, [settings.theme]);

Integration with Existing Features

GroceryList Integration

Changed:

// Before
const [sortMode, setSortMode] = useState("zone");
const [recentlyBoughtDisplayCount, setRecentlyBoughtDisplayCount] = useState(10);

// After
const { settings } = useContext(SettingsContext);
const [sortMode, setSortMode] = useState(settings.defaultSortMode);
const [recentlyBoughtDisplayCount, setRecentlyBoughtDisplayCount] = useState(settings.recentlyBoughtCount);
const [recentlyBoughtCollapsed, setRecentlyBoughtCollapsed] = useState(settings.recentlyBoughtCollapsed);

Features:

  • Sort mode persists across sessions
  • Recently bought section respects visibility setting
  • Collapse state controlled by user preference
  • Initial display count uses user's preference

File Structure

frontend/src/
├── context/
│   └── SettingsContext.jsx          ← Settings state management
├── pages/
│   └── Settings.jsx                 ← Settings UI
├── styles/
│   ├── theme.css                    ← Dark mode CSS variables
│   └── pages/
│       └── Settings.css             ← Settings page styles
└── App.jsx                          ← Settings route & provider

Usage Examples

Access Settings in Component

import { useContext } from "react";
import { SettingsContext } from "../context/SettingsContext";

function MyComponent() {
  const { settings, updateSettings } = useContext(SettingsContext);
  
  // Read setting
  const isDark = settings.theme === "dark";
  
  // Update setting
  const toggleTheme = () => {
    updateSettings({ 
      theme: settings.theme === "dark" ? "light" : "dark" 
    });
  };
  
  return <button onClick={toggleTheme}>Toggle Theme</button>;
}

Conditional Rendering Based on Settings

{settings.showRecentlyBought && (
  <RecentlyBoughtSection />
)}

Using Theme Colors

.my-component {
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
  border: 1px solid var(--color-border-light);
}

localStorage Structure

Key: user_preferences_${username}

Example stored value:

{
  "theme": "dark",
  "compactView": false,
  "defaultSortMode": "zone",
  "showRecentlyBought": true,
  "recentlyBoughtCount": 20,
  "recentlyBoughtCollapsed": false,
  "confirmBeforeBuy": true,
  "autoReloadInterval": 0,
  "hapticFeedback": true,
  "debugMode": false
}

Testing Checklist

Settings Page

  • All three tabs accessible
  • Theme toggle works (light/dark/auto)
  • Auto mode follows system preference
  • Settings persist after logout/login
  • Reset button restores defaults

Dark Mode

  • All pages render correctly in dark mode
  • Modals readable in dark mode
  • Forms and inputs visible in dark mode
  • Navigation and buttons styled correctly
  • Images and borders contrast properly

GroceryList Integration

  • Default sort mode applied on load
  • Recently bought visibility respected
  • Collapse state persists during session
  • Display count uses user preference

Future Enhancements (Not Implemented)

Phase 3: Advanced Preferences

  • Compact View: Reduced padding/font sizes for power users
  • Confirm Before Buy: Toggle for confirmation modal
  • Auto-reload: Refresh list every X minutes for shared lists

Phase 4: Account Management

  • Change Password: Security feature (needs backend endpoint)
  • Display Name: Friendly name separate from username

Phase 5: Data Management

  • Export List: Download as CSV/JSON
  • Clear History: Remove recently bought items
  • Import Items: Bulk add from file

Phase 6: Accessibility

  • Font Size: Adjustable text sizing
  • High Contrast Mode: Increased contrast for visibility
  • Reduce Motion: Disable animations

API Endpoints

None required - all settings are client-side only.

Future backend endpoints may include:

  • PATCH /api/users/me - Update user profile (password, display name)
  • GET /api/list/export - Export grocery list data

Browser Compatibility

Theme Detection

  • Chrome/Edge: Full support
  • Firefox: Full support
  • Safari: Full support (iOS 12.2+)
  • Mobile browsers: Full support

localStorage

  • All modern browsers: Supported
  • Fallback: Settings work but don't persist (rare)

Troubleshooting

Settings Don't Persist

Issue: Settings reset after logout
Cause: Settings tied to username
Solution: Working as designed - each user has separate preferences

Dark Mode Not Applied

Issue: Page stays light after selecting dark
Cause: Missing data-theme attribute
Solution: Check SettingsContext is wrapped around App

System Theme Not Detected

Issue: Auto mode doesn't work
Cause: Browser doesn't support prefers-color-scheme
Solution: Fallback to light mode (handled automatically)


Development Notes

Adding New Settings

  1. Update DEFAULT_SETTINGS in SettingsContext.jsx:
const DEFAULT_SETTINGS = {
  // ...existing settings
  myNewSetting: defaultValue,
};
  1. Add UI in Settings.jsx:
<div className="settings-group">
  <label className="settings-label">
    <input
      type="checkbox"
      checked={settings.myNewSetting}
      onChange={() => handleToggle("myNewSetting")}
    />
    <span>My New Setting</span>
  </label>
  <p className="settings-description">Description here</p>
</div>
  1. Use in components:
const { settings } = useContext(SettingsContext);
if (settings.myNewSetting) {
  // Do something
}

Adding Theme Colors

  1. Define in both light (:root) and dark ([data-theme="dark"]) modes
  2. Use descriptive semantic names: --color-purpose-variant
  3. Always provide fallbacks for older code

Performance Considerations

  • Settings load once per user session
  • Theme changes apply instantly (no page reload)
  • localStorage writes are debounced by React state updates
  • No network requests for settings (all client-side)

Accessibility

  • Keyboard navigation works in Settings page
  • Theme buttons have clear active states
  • Range sliders show current values
  • Color contrast meets WCAG AA in both themes
  • ⚠️ Screen reader announcements for theme changes (future enhancement)

Migration Notes

Upgrading from older versions:

  • Old settings are preserved (merged with defaults)
  • Missing settings use default values
  • Invalid values are reset to defaults
  • No migration script needed - handled automatically


Implementation Status: Complete
Phase 1 (Foundation): Complete
Phase 2 (Dark Mode): Complete
Phase 3 (List Preferences): Complete
Phase 4+ (Future): Planned