416 lines
10 KiB
Markdown
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
|