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

416 lines
10 KiB
Markdown

# 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](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](frontend/src/pages/Settings.jsx))
- Tabbed interface: Appearance, List Display, Behavior
- Real-time preview of setting changes
- Reset to defaults functionality
---
## Settings Schema
```javascript
{
// === 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](frontend/src/styles/theme.css):
**Light Mode** (`:root`):
```css
:root {
--color-text-primary: #212529;
--color-bg-body: #f8f9fa;
--color-bg-surface: #ffffff;
/* ... */
}
```
**Dark Mode** (`[data-theme="dark"]`):
```css
[data-theme="dark"] {
--color-text-primary: #f1f5f9;
--color-bg-body: #0f172a;
--color-bg-surface: #1e293b;
/* ... */
}
```
### Theme Application Logic
```javascript
// 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**:
```javascript
// 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
```javascript
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
```javascript
{settings.showRecentlyBought && (
<RecentlyBoughtSection />
)}
```
### Using Theme Colors
```css
.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**:
```json
{
"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:
```javascript
const DEFAULT_SETTINGS = {
// ...existing settings
myNewSetting: defaultValue,
};
```
2. **Add UI in Settings.jsx**:
```javascript
<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>
```
3. **Use in components**:
```javascript
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
---
## Related Documentation
- [Code Cleanup Guide](code-cleanup-guide.md) - Code organization patterns
- [Component Structure](component-structure.md) - Component architecture
- [Theme Usage Examples](../frontend/src/styles/THEME_USAGE_EXAMPLES.css) - CSS variable usage
---
**Implementation Status**: ✅ Complete
**Phase 1 (Foundation)**: ✅ Complete
**Phase 2 (Dark Mode)**: ✅ Complete
**Phase 3 (List Preferences)**: ✅ Complete
**Phase 4+ (Future)**: ⏳ Planned