Implement settings page and feature and also add dark mode
This commit is contained in:
parent
0c16d22c1e
commit
5ce4177446
415
docs/settings-dark-mode.md
Normal file
415
docs/settings-dark-mode.md
Normal file
@ -0,0 +1,415 @@
|
|||||||
|
# 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
|
||||||
@ -2,11 +2,13 @@ import { BrowserRouter, Route, Routes } from "react-router-dom";
|
|||||||
import { ROLES } from "./constants/roles";
|
import { ROLES } from "./constants/roles";
|
||||||
import { AuthProvider } from "./context/AuthContext.jsx";
|
import { AuthProvider } from "./context/AuthContext.jsx";
|
||||||
import { ConfigProvider } from "./context/ConfigContext.jsx";
|
import { ConfigProvider } from "./context/ConfigContext.jsx";
|
||||||
|
import { SettingsProvider } from "./context/SettingsContext.jsx";
|
||||||
|
|
||||||
import AdminPanel from "./pages/AdminPanel.jsx";
|
import AdminPanel from "./pages/AdminPanel.jsx";
|
||||||
import GroceryList from "./pages/GroceryList.jsx";
|
import GroceryList from "./pages/GroceryList.jsx";
|
||||||
import Login from "./pages/Login.jsx";
|
import Login from "./pages/Login.jsx";
|
||||||
import Register from "./pages/Register.jsx";
|
import Register from "./pages/Register.jsx";
|
||||||
|
import Settings from "./pages/Settings.jsx";
|
||||||
|
|
||||||
import AppLayout from "./components/layout/AppLayout.jsx";
|
import AppLayout from "./components/layout/AppLayout.jsx";
|
||||||
import PrivateRoute from "./utils/PrivateRoute.jsx";
|
import PrivateRoute from "./utils/PrivateRoute.jsx";
|
||||||
@ -18,35 +20,38 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<ConfigProvider>
|
<ConfigProvider>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<BrowserRouter>
|
<SettingsProvider>
|
||||||
<Routes>
|
<BrowserRouter>
|
||||||
|
<Routes>
|
||||||
|
|
||||||
{/* Public route */}
|
{/* Public route */}
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Route path="/register" element={<Register />} />
|
<Route path="/register" element={<Register />} />
|
||||||
|
|
||||||
{/* Private routes with layout */}
|
|
||||||
<Route
|
|
||||||
element={
|
|
||||||
<PrivateRoute>
|
|
||||||
<AppLayout />
|
|
||||||
</PrivateRoute>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Route path="/" element={<GroceryList />} />
|
|
||||||
|
|
||||||
|
{/* Private routes with layout */}
|
||||||
<Route
|
<Route
|
||||||
path="/admin"
|
|
||||||
element={
|
element={
|
||||||
<RoleGuard allowed={[ROLES.ADMIN]}>
|
<PrivateRoute>
|
||||||
<AdminPanel />
|
<AppLayout />
|
||||||
</RoleGuard>
|
</PrivateRoute>
|
||||||
}
|
}
|
||||||
/>
|
>
|
||||||
</Route>
|
<Route path="/" element={<GroceryList />} />
|
||||||
|
<Route path="/settings" element={<Settings />} />
|
||||||
|
|
||||||
</Routes>
|
<Route
|
||||||
</BrowserRouter>
|
path="/admin"
|
||||||
|
element={
|
||||||
|
<RoleGuard allowed={[ROLES.ADMIN]}>
|
||||||
|
<AdminPanel />
|
||||||
|
</RoleGuard>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
|
</SettingsProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import "../../styles/components/SuggestionList.css";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
suggestions: string[];
|
suggestions: string[];
|
||||||
@ -8,27 +9,12 @@ export default function SuggestionList({ suggestions, onSelect }: Props) {
|
|||||||
if (!suggestions.length) return null;
|
if (!suggestions.length) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul
|
<ul className="suggestion-list">
|
||||||
className="suggestion-list"
|
|
||||||
style={{
|
|
||||||
background: "#fff",
|
|
||||||
border: "1px solid #ccc",
|
|
||||||
maxHeight: "150px",
|
|
||||||
overflowY: "auto",
|
|
||||||
listStyle: "none",
|
|
||||||
padding: 0,
|
|
||||||
margin: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{suggestions.map((s) => (
|
{suggestions.map((s) => (
|
||||||
<li
|
<li
|
||||||
key={s}
|
key={s}
|
||||||
onClick={() => onSelect(s)}
|
onClick={() => onSelect(s)}
|
||||||
style={{
|
className="suggestion-item"
|
||||||
padding: "0.5em",
|
|
||||||
cursor: "pointer",
|
|
||||||
borderBottom: "1px solid #eee",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{s}
|
{s}
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -11,6 +11,7 @@ export default function Navbar() {
|
|||||||
<nav className="navbar">
|
<nav className="navbar">
|
||||||
<div className="navbar-links">
|
<div className="navbar-links">
|
||||||
<Link to="/">Home</Link>
|
<Link to="/">Home</Link>
|
||||||
|
<Link to="/settings">Settings</Link>
|
||||||
|
|
||||||
{role === "admin" && <Link to="/admin">Admin</Link>}
|
{role === "admin" && <Link to="/admin">Admin</Link>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
122
frontend/src/context/SettingsContext.jsx
Normal file
122
frontend/src/context/SettingsContext.jsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { createContext, useContext, useEffect, useState } from "react";
|
||||||
|
import { AuthContext } from "./AuthContext";
|
||||||
|
|
||||||
|
|
||||||
|
const DEFAULT_SETTINGS = {
|
||||||
|
// Appearance
|
||||||
|
theme: "auto", // "light" | "dark" | "auto"
|
||||||
|
compactView: false,
|
||||||
|
|
||||||
|
// List Display
|
||||||
|
defaultSortMode: "zone",
|
||||||
|
showRecentlyBought: true,
|
||||||
|
recentlyBoughtCount: 10,
|
||||||
|
recentlyBoughtCollapsed: false,
|
||||||
|
|
||||||
|
// Behavior
|
||||||
|
confirmBeforeBuy: true,
|
||||||
|
autoReloadInterval: 0, // 0 = disabled, else minutes
|
||||||
|
hapticFeedback: true,
|
||||||
|
|
||||||
|
// Advanced
|
||||||
|
debugMode: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const SettingsContext = createContext({
|
||||||
|
settings: DEFAULT_SETTINGS,
|
||||||
|
updateSettings: () => { },
|
||||||
|
resetSettings: () => { },
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export const SettingsProvider = ({ children }) => {
|
||||||
|
const { username } = useContext(AuthContext);
|
||||||
|
const [settings, setSettings] = useState(DEFAULT_SETTINGS);
|
||||||
|
|
||||||
|
|
||||||
|
// Load settings from localStorage when user changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!username) {
|
||||||
|
setSettings(DEFAULT_SETTINGS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const storageKey = `user_preferences_${username}`;
|
||||||
|
const savedSettings = localStorage.getItem(storageKey);
|
||||||
|
|
||||||
|
if (savedSettings) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(savedSettings);
|
||||||
|
setSettings({ ...DEFAULT_SETTINGS, ...parsed });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to parse settings:", error);
|
||||||
|
setSettings(DEFAULT_SETTINGS);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setSettings(DEFAULT_SETTINGS);
|
||||||
|
}
|
||||||
|
}, [username]);
|
||||||
|
|
||||||
|
|
||||||
|
// Apply theme to document
|
||||||
|
useEffect(() => {
|
||||||
|
const applyTheme = () => {
|
||||||
|
let theme = settings.theme;
|
||||||
|
|
||||||
|
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 in auto mode
|
||||||
|
if (settings.theme === "auto") {
|
||||||
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
const handler = () => applyTheme();
|
||||||
|
mediaQuery.addEventListener("change", handler);
|
||||||
|
return () => mediaQuery.removeEventListener("change", handler);
|
||||||
|
}
|
||||||
|
}, [settings.theme]);
|
||||||
|
|
||||||
|
|
||||||
|
// Save settings to localStorage
|
||||||
|
const updateSettings = (newSettings) => {
|
||||||
|
if (!username) return;
|
||||||
|
|
||||||
|
const updated = { ...settings, ...newSettings };
|
||||||
|
setSettings(updated);
|
||||||
|
|
||||||
|
const storageKey = `user_preferences_${username}`;
|
||||||
|
localStorage.setItem(storageKey, JSON.stringify(updated));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Reset to defaults
|
||||||
|
const resetSettings = () => {
|
||||||
|
if (!username) return;
|
||||||
|
|
||||||
|
setSettings(DEFAULT_SETTINGS);
|
||||||
|
|
||||||
|
const storageKey = `user_preferences_${username}`;
|
||||||
|
localStorage.setItem(storageKey, JSON.stringify(DEFAULT_SETTINGS));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const value = {
|
||||||
|
settings,
|
||||||
|
updateSettings,
|
||||||
|
resetSettings,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</SettingsContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -84,9 +84,14 @@ body {
|
|||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
background: var(--color-bg-body);
|
background: var(--color-bg-body);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: var(--spacing-md);
|
padding: 0;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@ -94,11 +99,6 @@ body {
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
padding: var(--container-padding);
|
padding: var(--container-padding);
|
||||||
}
|
}
|
||||||
background: white;
|
|
||||||
padding: 1em;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { getAllUsers, updateRole } from "../api/users";
|
import { getAllUsers, updateRole } from "../api/users";
|
||||||
import UserRoleCard from "../components/common/UserRoleCard";
|
import UserRoleCard from "../components/common/UserRoleCard";
|
||||||
import "../styles/UserRoleCard.css";
|
import "../styles/UserRoleCard.css";
|
||||||
|
import "../styles/pages/AdminPanel.css";
|
||||||
|
|
||||||
export default function AdminPanel() {
|
export default function AdminPanel() {
|
||||||
const [users, setUsers] = useState([]);
|
const [users, setUsers] = useState([]);
|
||||||
@ -22,16 +23,18 @@ export default function AdminPanel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: "2rem" }}>
|
<div className="admin-panel-page">
|
||||||
<h1>Admin Panel</h1>
|
<div className="admin-panel-container">
|
||||||
<div style={{ marginTop: "2rem" }}>
|
<h1 className="admin-panel-title">Admin Panel</h1>
|
||||||
{users.map((user) => (
|
<div className="admin-panel-users">
|
||||||
<UserRoleCard
|
{users.map((user) => (
|
||||||
key={user.id}
|
<UserRoleCard
|
||||||
user={user}
|
key={user.id}
|
||||||
onRoleChange={changeRole}
|
user={user}
|
||||||
/>
|
onRoleChange={changeRole}
|
||||||
))}
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -20,18 +20,20 @@ import SimilarItemModal from "../components/modals/SimilarItemModal";
|
|||||||
import { ZONE_FLOW } from "../constants/classifications";
|
import { ZONE_FLOW } from "../constants/classifications";
|
||||||
import { ROLES } from "../constants/roles";
|
import { ROLES } from "../constants/roles";
|
||||||
import { AuthContext } from "../context/AuthContext";
|
import { AuthContext } from "../context/AuthContext";
|
||||||
|
import { SettingsContext } from "../context/SettingsContext";
|
||||||
import "../styles/pages/GroceryList.css";
|
import "../styles/pages/GroceryList.css";
|
||||||
import { findSimilarItems } from "../utils/stringSimilarity";
|
import { findSimilarItems } from "../utils/stringSimilarity";
|
||||||
|
|
||||||
|
|
||||||
export default function GroceryList() {
|
export default function GroceryList() {
|
||||||
const { role } = useContext(AuthContext);
|
const { role } = useContext(AuthContext);
|
||||||
|
const { settings } = useContext(SettingsContext);
|
||||||
|
|
||||||
// === State === //
|
// === State === //
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState([]);
|
||||||
const [recentlyBoughtItems, setRecentlyBoughtItems] = useState([]);
|
const [recentlyBoughtItems, setRecentlyBoughtItems] = useState([]);
|
||||||
const [recentlyBoughtDisplayCount, setRecentlyBoughtDisplayCount] = useState(10);
|
const [recentlyBoughtDisplayCount, setRecentlyBoughtDisplayCount] = useState(settings.recentlyBoughtCount);
|
||||||
const [sortMode, setSortMode] = useState("zone");
|
const [sortMode, setSortMode] = useState(settings.defaultSortMode);
|
||||||
const [suggestions, setSuggestions] = useState([]);
|
const [suggestions, setSuggestions] = useState([]);
|
||||||
const [showAddForm, setShowAddForm] = useState(true);
|
const [showAddForm, setShowAddForm] = useState(true);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@ -42,6 +44,7 @@ export default function GroceryList() {
|
|||||||
const [similarItemSuggestion, setSimilarItemSuggestion] = useState(null);
|
const [similarItemSuggestion, setSimilarItemSuggestion] = useState(null);
|
||||||
const [showEditModal, setShowEditModal] = useState(false);
|
const [showEditModal, setShowEditModal] = useState(false);
|
||||||
const [editingItem, setEditingItem] = useState(null);
|
const [editingItem, setEditingItem] = useState(null);
|
||||||
|
const [recentlyBoughtCollapsed, setRecentlyBoughtCollapsed] = useState(settings.recentlyBoughtCollapsed);
|
||||||
|
|
||||||
|
|
||||||
// === Data Loading ===
|
// === Data Loading ===
|
||||||
@ -459,34 +462,47 @@ export default function GroceryList() {
|
|||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{recentlyBoughtItems.length > 0 && (
|
{recentlyBoughtItems.length > 0 && settings.showRecentlyBought && (
|
||||||
<>
|
<>
|
||||||
<h2 className="glist-section-title">Recently Bought (24HR)</h2>
|
<div className="glist-section-header">
|
||||||
<ul className="glist-ul">
|
<h2 className="glist-section-title">Recently Bought (24HR)</h2>
|
||||||
{recentlyBoughtItems.slice(0, recentlyBoughtDisplayCount).map((item) => (
|
<button
|
||||||
<GroceryListItem
|
className="glist-collapse-btn"
|
||||||
key={item.id}
|
onClick={() => setRecentlyBoughtCollapsed(!recentlyBoughtCollapsed)}
|
||||||
item={item}
|
>
|
||||||
allItems={recentlyBoughtItems}
|
{recentlyBoughtCollapsed ? "▼ Show" : "▲ Hide"}
|
||||||
onClick={null}
|
</button>
|
||||||
onImageAdded={
|
</div>
|
||||||
[ROLES.ADMIN, ROLES.EDITOR].includes(role) ? handleImageAdded : null
|
|
||||||
}
|
{!recentlyBoughtCollapsed && (
|
||||||
onLongPress={
|
<>
|
||||||
[ROLES.ADMIN, ROLES.EDITOR].includes(role) ? handleLongPress : null
|
<ul className="glist-ul">
|
||||||
}
|
{recentlyBoughtItems.slice(0, recentlyBoughtDisplayCount).map((item) => (
|
||||||
/>
|
<GroceryListItem
|
||||||
))}
|
key={item.id}
|
||||||
</ul>
|
item={item}
|
||||||
{recentlyBoughtDisplayCount < recentlyBoughtItems.length && (
|
allItems={recentlyBoughtItems}
|
||||||
<div style={{ textAlign: 'center', marginTop: '1rem' }}>
|
onClick={null}
|
||||||
<button
|
onImageAdded={
|
||||||
className="glist-show-more-btn"
|
[ROLES.ADMIN, ROLES.EDITOR].includes(role) ? handleImageAdded : null
|
||||||
onClick={() => setRecentlyBoughtDisplayCount(prev => prev + 10)}
|
}
|
||||||
>
|
onLongPress={
|
||||||
Show More ({recentlyBoughtItems.length - recentlyBoughtDisplayCount} remaining)
|
[ROLES.ADMIN, ROLES.EDITOR].includes(role) ? handleLongPress : null
|
||||||
</button>
|
}
|
||||||
</div>
|
/>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{recentlyBoughtDisplayCount < recentlyBoughtItems.length && (
|
||||||
|
<div style={{ textAlign: 'center', marginTop: '1rem' }}>
|
||||||
|
<button
|
||||||
|
className="glist-show-more-btn"
|
||||||
|
onClick={() => setRecentlyBoughtDisplayCount(prev => prev + 10)}
|
||||||
|
>
|
||||||
|
Show More ({recentlyBoughtItems.length - recentlyBoughtDisplayCount} remaining)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
250
frontend/src/pages/Settings.jsx
Normal file
250
frontend/src/pages/Settings.jsx
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
import { useContext, useState } from "react";
|
||||||
|
import { SettingsContext } from "../context/SettingsContext";
|
||||||
|
import "../styles/pages/Settings.css";
|
||||||
|
|
||||||
|
|
||||||
|
export default function Settings() {
|
||||||
|
const { settings, updateSettings, resetSettings } = useContext(SettingsContext);
|
||||||
|
const [activeTab, setActiveTab] = useState("appearance");
|
||||||
|
|
||||||
|
|
||||||
|
const handleThemeChange = (theme) => {
|
||||||
|
updateSettings({ theme });
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleToggle = (key) => {
|
||||||
|
updateSettings({ [key]: !settings[key] });
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleNumberChange = (key, value) => {
|
||||||
|
updateSettings({ [key]: parseInt(value, 10) });
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleSelectChange = (key, value) => {
|
||||||
|
updateSettings({ [key]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
if (window.confirm("Reset all settings to defaults?")) {
|
||||||
|
resetSettings();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="settings-page">
|
||||||
|
<div className="settings-container">
|
||||||
|
<h1 className="settings-title">Settings</h1>
|
||||||
|
|
||||||
|
<div className="settings-tabs">
|
||||||
|
<button
|
||||||
|
className={`settings-tab ${activeTab === "appearance" ? "active" : ""}`}
|
||||||
|
onClick={() => setActiveTab("appearance")}
|
||||||
|
>
|
||||||
|
Appearance
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`settings-tab ${activeTab === "list" ? "active" : ""}`}
|
||||||
|
onClick={() => setActiveTab("list")}
|
||||||
|
>
|
||||||
|
List Display
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`settings-tab ${activeTab === "behavior" ? "active" : ""}`}
|
||||||
|
onClick={() => setActiveTab("behavior")}
|
||||||
|
>
|
||||||
|
Behavior
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="settings-content">
|
||||||
|
{/* Appearance Tab */}
|
||||||
|
{activeTab === "appearance" && (
|
||||||
|
<div className="settings-section">
|
||||||
|
<h2 className="settings-section-title">Appearance</h2>
|
||||||
|
|
||||||
|
<div className="settings-group">
|
||||||
|
<label className="settings-label">Theme</label>
|
||||||
|
<div className="settings-theme-options">
|
||||||
|
<button
|
||||||
|
className={`settings-theme-btn ${settings.theme === "light" ? "active" : ""}`}
|
||||||
|
onClick={() => handleThemeChange("light")}
|
||||||
|
>
|
||||||
|
☀️ Light
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`settings-theme-btn ${settings.theme === "dark" ? "active" : ""}`}
|
||||||
|
onClick={() => handleThemeChange("dark")}
|
||||||
|
>
|
||||||
|
🌙 Dark
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`settings-theme-btn ${settings.theme === "auto" ? "active" : ""}`}
|
||||||
|
onClick={() => handleThemeChange("auto")}
|
||||||
|
>
|
||||||
|
🔄 Auto
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p className="settings-description">
|
||||||
|
Auto mode follows your system preferences
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="settings-group">
|
||||||
|
<label className="settings-label">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={settings.compactView}
|
||||||
|
onChange={() => handleToggle("compactView")}
|
||||||
|
/>
|
||||||
|
<span>Compact View</span>
|
||||||
|
</label>
|
||||||
|
<p className="settings-description">
|
||||||
|
Show more items on screen with reduced spacing
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* List Display Tab */}
|
||||||
|
{activeTab === "list" && (
|
||||||
|
<div className="settings-section">
|
||||||
|
<h2 className="settings-section-title">List Display</h2>
|
||||||
|
|
||||||
|
<div className="settings-group">
|
||||||
|
<label className="settings-label">Default Sort Mode</label>
|
||||||
|
<select
|
||||||
|
value={settings.defaultSortMode}
|
||||||
|
onChange={(e) => handleSelectChange("defaultSortMode", e.target.value)}
|
||||||
|
className="settings-select"
|
||||||
|
>
|
||||||
|
<option value="zone">By Zone</option>
|
||||||
|
<option value="az">A → Z</option>
|
||||||
|
<option value="za">Z → A</option>
|
||||||
|
<option value="qty-high">Quantity: High → Low</option>
|
||||||
|
<option value="qty-low">Quantity: Low → High</option>
|
||||||
|
</select>
|
||||||
|
<p className="settings-description">
|
||||||
|
Your preferred sorting method when opening the list
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="settings-group">
|
||||||
|
<label className="settings-label">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={settings.showRecentlyBought}
|
||||||
|
onChange={() => handleToggle("showRecentlyBought")}
|
||||||
|
/>
|
||||||
|
<span>Show Recently Bought Section</span>
|
||||||
|
</label>
|
||||||
|
<p className="settings-description">
|
||||||
|
Display items bought in the last 24 hours
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{settings.showRecentlyBought && (
|
||||||
|
<>
|
||||||
|
<div className="settings-group">
|
||||||
|
<label className="settings-label">
|
||||||
|
Recently Bought Item Count: {settings.recentlyBoughtCount}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="5"
|
||||||
|
max="50"
|
||||||
|
step="5"
|
||||||
|
value={settings.recentlyBoughtCount}
|
||||||
|
onChange={(e) => handleNumberChange("recentlyBoughtCount", e.target.value)}
|
||||||
|
className="settings-range"
|
||||||
|
/>
|
||||||
|
<p className="settings-description">
|
||||||
|
Number of items to show initially (5-50)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="settings-group">
|
||||||
|
<label className="settings-label">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={settings.recentlyBoughtCollapsed}
|
||||||
|
onChange={() => handleToggle("recentlyBoughtCollapsed")}
|
||||||
|
/>
|
||||||
|
<span>Collapse Recently Bought by Default</span>
|
||||||
|
</label>
|
||||||
|
<p className="settings-description">
|
||||||
|
Start with the section collapsed
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Behavior Tab */}
|
||||||
|
{activeTab === "behavior" && (
|
||||||
|
<div className="settings-section">
|
||||||
|
<h2 className="settings-section-title">Behavior</h2>
|
||||||
|
|
||||||
|
<div className="settings-group">
|
||||||
|
<label className="settings-label">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={settings.confirmBeforeBuy}
|
||||||
|
onChange={() => handleToggle("confirmBeforeBuy")}
|
||||||
|
/>
|
||||||
|
<span>Confirm Before Buying</span>
|
||||||
|
</label>
|
||||||
|
<p className="settings-description">
|
||||||
|
Show confirmation modal when marking items as bought
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="settings-group">
|
||||||
|
<label className="settings-label">
|
||||||
|
Auto-reload Interval (minutes): {settings.autoReloadInterval || "Disabled"}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="30"
|
||||||
|
step="5"
|
||||||
|
value={settings.autoReloadInterval}
|
||||||
|
onChange={(e) => handleNumberChange("autoReloadInterval", e.target.value)}
|
||||||
|
className="settings-range"
|
||||||
|
/>
|
||||||
|
<p className="settings-description">
|
||||||
|
Automatically refresh the list every X minutes (0 = disabled)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="settings-group">
|
||||||
|
<label className="settings-label">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={settings.hapticFeedback}
|
||||||
|
onChange={() => handleToggle("hapticFeedback")}
|
||||||
|
/>
|
||||||
|
<span>Haptic Feedback (Mobile)</span>
|
||||||
|
</label>
|
||||||
|
<p className="settings-description">
|
||||||
|
Vibrate on long-press and other interactions
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="settings-actions">
|
||||||
|
<button onClick={handleReset} className="settings-btn-reset">
|
||||||
|
Reset to Defaults
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -4,40 +4,40 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: rgba(0, 0, 0, 0.6);
|
background: var(--modal-backdrop-bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
z-index: 1000;
|
z-index: var(--z-modal);
|
||||||
animation: fadeIn 0.2s ease-out;
|
animation: fadeIn 0.2s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-image-modal {
|
.add-image-modal {
|
||||||
background: white;
|
background: var(--modal-bg);
|
||||||
padding: 2em;
|
padding: var(--spacing-xl);
|
||||||
border-radius: 12px;
|
border-radius: var(--border-radius-xl);
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
box-shadow: var(--shadow-xl);
|
||||||
animation: slideUp 0.3s ease-out;
|
animation: slideUp 0.3s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-image-modal h2 {
|
.add-image-modal h2 {
|
||||||
margin: 0 0 0.5em 0;
|
margin: 0 0 var(--spacing-sm) 0;
|
||||||
font-size: 1.5em;
|
font-size: var(--font-size-2xl);
|
||||||
color: #333;
|
color: var(--color-text-primary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-image-subtitle {
|
.add-image-subtitle {
|
||||||
margin: 0 0 1.5em 0;
|
margin: 0 0 var(--spacing-xl) 0;
|
||||||
color: #666;
|
color: var(--color-text-secondary);
|
||||||
font-size: 0.95em;
|
font-size: 0.95em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-image-subtitle strong {
|
.add-image-subtitle strong {
|
||||||
color: #007bff;
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-image-options {
|
.add-image-options {
|
||||||
@ -48,32 +48,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.add-image-option-btn {
|
.add-image-option-btn {
|
||||||
padding: 1.2em;
|
padding: var(--spacing-lg);
|
||||||
border: 2px solid #ddd;
|
border: var(--border-width-medium) solid var(--color-border-light);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
background: white;
|
background: var(--color-bg-surface);
|
||||||
font-size: 1.1em;
|
font-size: var(--font-size-lg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: var(--transition-base);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0.5em;
|
gap: var(--spacing-sm);
|
||||||
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-image-option-btn:hover {
|
.add-image-option-btn:hover {
|
||||||
border-color: #007bff;
|
border-color: var(--color-primary);
|
||||||
background: #f8f9fa;
|
background: var(--color-bg-hover);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 2px 8px rgba(0, 123, 255, 0.2);
|
box-shadow: var(--shadow-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-image-option-btn.camera {
|
.add-image-option-btn.camera {
|
||||||
color: #007bff;
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-image-option-btn.gallery {
|
.add-image-option-btn.gallery {
|
||||||
color: #28a745;
|
color: var(--color-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-image-preview-container {
|
.add-image-preview-container {
|
||||||
@ -86,9 +87,10 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 250px;
|
width: 250px;
|
||||||
height: 250px;
|
height: 250px;
|
||||||
border: 2px solid #ddd;
|
border: var(--border-width-medium) solid var(--color-border-light);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background: var(--color-gray-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-image-preview img {
|
.add-image-preview img {
|
||||||
|
|||||||
@ -4,21 +4,21 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: rgba(0, 0, 0, 0.6);
|
background: var(--modal-backdrop-bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
z-index: 1000;
|
z-index: var(--z-modal);
|
||||||
animation: fadeIn 0.2s ease-out;
|
animation: fadeIn 0.2s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-buy-modal {
|
.confirm-buy-modal {
|
||||||
background: white;
|
background: var(--modal-bg);
|
||||||
padding: 1em;
|
padding: var(--spacing-md);
|
||||||
border-radius: 12px;
|
border-radius: var(--border-radius-xl);
|
||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
box-shadow: var(--shadow-xl);
|
||||||
animation: slideUp 0.3s ease-out;
|
animation: slideUp 0.3s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
.confirm-buy-zone {
|
.confirm-buy-zone {
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
color: #666;
|
color: var(--color-text-secondary);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 0.2em;
|
margin-bottom: 0.2em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@ -39,7 +39,7 @@
|
|||||||
.confirm-buy-item-name {
|
.confirm-buy-item-name {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
color: #007bff;
|
color: var(--color-primary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,14 +54,14 @@
|
|||||||
.confirm-buy-nav-btn {
|
.confirm-buy-nav-btn {
|
||||||
width: 35px;
|
width: 35px;
|
||||||
height: 35px;
|
height: 35px;
|
||||||
border: 2px solid #007bff;
|
border: var(--border-width-medium) solid var(--color-primary);
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
background: white;
|
background: var(--color-bg-surface);
|
||||||
color: #007bff;
|
color: var(--color-primary);
|
||||||
font-size: 1.8em;
|
font-size: 1.8em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: var(--transition-base);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -71,13 +71,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.confirm-buy-nav-btn:hover:not(:disabled) {
|
.confirm-buy-nav-btn:hover:not(:disabled) {
|
||||||
background: #007bff;
|
background: var(--color-primary);
|
||||||
color: white;
|
color: var(--color-text-inverse);
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-buy-nav-btn:disabled {
|
.confirm-buy-nav-btn:disabled {
|
||||||
border-color: #ccc;
|
border-color: var(--color-border-medium);
|
||||||
color: #ccc;
|
color: var(--color-text-disabled);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,10 +87,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: 2px solid #ddd;
|
border: var(--border-width-medium) solid var(--color-border-light);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #f8f9fa;
|
background: var(--color-gray-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-buy-image {
|
.confirm-buy-image {
|
||||||
@ -101,7 +101,7 @@
|
|||||||
|
|
||||||
.confirm-buy-image-placeholder {
|
.confirm-buy-image-placeholder {
|
||||||
font-size: 4em;
|
font-size: 4em;
|
||||||
color: #ccc;
|
color: var(--color-border-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-buy-quantity-section {
|
.confirm-buy-quantity-section {
|
||||||
@ -118,14 +118,14 @@
|
|||||||
.confirm-buy-counter-btn {
|
.confirm-buy-counter-btn {
|
||||||
width: 45px;
|
width: 45px;
|
||||||
height: 45px;
|
height: 45px;
|
||||||
border: 2px solid #007bff;
|
border: var(--border-width-medium) solid var(--color-primary);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
background: white;
|
background: var(--color-bg-surface);
|
||||||
color: #007bff;
|
color: var(--color-primary);
|
||||||
font-size: 1.6em;
|
font-size: 1.6em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: var(--transition-base);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -134,31 +134,31 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.confirm-buy-counter-btn:hover:not(:disabled) {
|
.confirm-buy-counter-btn:hover:not(:disabled) {
|
||||||
background: #007bff;
|
background: var(--color-primary);
|
||||||
color: white;
|
color: var(--color-text-inverse);
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-buy-counter-btn:disabled {
|
.confirm-buy-counter-btn:disabled {
|
||||||
border-color: #ccc;
|
border-color: var(--color-border-medium);
|
||||||
color: #ccc;
|
color: var(--color-text-disabled);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-buy-counter-display {
|
.confirm-buy-counter-display {
|
||||||
width: 70px;
|
width: 70px;
|
||||||
height: 45px;
|
height: 45px;
|
||||||
border: 2px solid #ddd;
|
border: var(--border-width-medium) solid var(--color-border-light);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 1.4em;
|
font-size: 1.4em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #333;
|
color: var(--color-text-primary);
|
||||||
background: #f8f9fa;
|
background: var(--color-gray-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-buy-counter-display:focus {
|
.confirm-buy-counter-display:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #007bff;
|
border-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-buy-actions {
|
.confirm-buy-actions {
|
||||||
@ -172,30 +172,30 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 0.75em 0.5em;
|
padding: 0.75em 0.5em;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
font-size: 0.95em;
|
font-size: 0.95em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: var(--transition-base);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-buy-cancel {
|
.confirm-buy-cancel {
|
||||||
background: #f0f0f0;
|
background: var(--color-gray-200);
|
||||||
color: #333;
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-buy-cancel:hover {
|
.confirm-buy-cancel:hover {
|
||||||
background: #e0e0e0;
|
background: var(--color-gray-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-buy-confirm {
|
.confirm-buy-confirm {
|
||||||
background: #28a745;
|
background: var(--color-success);
|
||||||
color: white;
|
color: var(--color-text-inverse);
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-buy-confirm:hover {
|
.confirm-buy-confirm:hover {
|
||||||
background: #218838;
|
background: var(--color-success-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
|
|||||||
@ -4,46 +4,46 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: rgba(0, 0, 0, 0.6);
|
background: var(--modal-backdrop-bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
z-index: 1000;
|
z-index: var(--z-modal);
|
||||||
animation: fadeIn 0.2s ease-out;
|
animation: fadeIn 0.2s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.similar-item-modal {
|
.similar-item-modal {
|
||||||
background: white;
|
background: var(--modal-bg);
|
||||||
padding: 2em;
|
padding: var(--spacing-xl);
|
||||||
border-radius: 12px;
|
border-radius: var(--border-radius-xl);
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
box-shadow: var(--shadow-xl);
|
||||||
animation: slideUp 0.3s ease-out;
|
animation: slideUp 0.3s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.similar-item-modal h2 {
|
.similar-item-modal h2 {
|
||||||
margin: 0 0 1em 0;
|
margin: 0 0 var(--spacing-md) 0;
|
||||||
font-size: 1.5em;
|
font-size: var(--font-size-2xl);
|
||||||
color: #333;
|
color: var(--color-text-primary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.similar-item-question {
|
.similar-item-question {
|
||||||
margin: 0 0 0.5em 0;
|
margin: 0 0 var(--spacing-sm) 0;
|
||||||
font-size: 1.1em;
|
font-size: var(--font-size-lg);
|
||||||
color: #333;
|
color: var(--color-text-primary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.similar-item-question strong {
|
.similar-item-question strong {
|
||||||
color: #007bff;
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.similar-item-clarification {
|
.similar-item-clarification {
|
||||||
margin: 0 0 2em 0;
|
margin: 0 0 var(--spacing-xl) 0;
|
||||||
font-size: 0.9em;
|
font-size: var(--font-size-sm);
|
||||||
color: #666;
|
color: var(--color-text-secondary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
@ -58,40 +58,40 @@
|
|||||||
.similar-item-no,
|
.similar-item-no,
|
||||||
.similar-item-yes {
|
.similar-item-yes {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 0.8em;
|
padding: var(--button-padding-y) var(--button-padding-x);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: var(--button-border-radius);
|
||||||
font-size: 1em;
|
font-size: var(--font-size-base);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: var(--transition-base);
|
||||||
font-weight: 500;
|
font-weight: var(--button-font-weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
.similar-item-cancel {
|
.similar-item-cancel {
|
||||||
background: #f0f0f0;
|
background: var(--color-gray-200);
|
||||||
color: #333;
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.similar-item-cancel:hover {
|
.similar-item-cancel:hover {
|
||||||
background: #e0e0e0;
|
background: var(--color-gray-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.similar-item-no {
|
.similar-item-no {
|
||||||
background: #6c757d;
|
background: var(--color-secondary);
|
||||||
color: white;
|
color: var(--color-text-inverse);
|
||||||
}
|
}
|
||||||
|
|
||||||
.similar-item-no:hover {
|
.similar-item-no:hover {
|
||||||
background: #5a6268;
|
background: var(--color-secondary-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.similar-item-yes {
|
.similar-item-yes {
|
||||||
background: #28a745;
|
background: var(--color-success);
|
||||||
color: white;
|
color: var(--color-text-inverse);
|
||||||
}
|
}
|
||||||
|
|
||||||
.similar-item-yes:hover {
|
.similar-item-yes:hover {
|
||||||
background: #218838;
|
background: var(--color-success-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
|
|||||||
@ -2,39 +2,52 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 1rem;
|
padding: var(--spacing-md);
|
||||||
margin: 0.5rem 0;
|
margin: var(--spacing-sm) 0;
|
||||||
background: #f5f5f5;
|
background: var(--color-bg-surface);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
border: 1px solid #ddd;
|
border: var(--border-width-thin) solid var(--color-border-light);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
transition: var(--transition-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-card:hover {
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-info {
|
.user-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.25rem;
|
gap: var(--spacing-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-username {
|
.user-username {
|
||||||
color: #666;
|
color: var(--color-text-secondary);
|
||||||
font-size: 0.9rem;
|
font-size: var(--font-size-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info h3 {
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.role-select {
|
.role-select {
|
||||||
padding: 0.5rem;
|
padding: var(--spacing-sm);
|
||||||
border-radius: 4px;
|
border-radius: var(--border-radius-sm);
|
||||||
border: 1px solid #ccc;
|
border: var(--border-width-thin) solid var(--input-border-color);
|
||||||
background: white;
|
background: var(--color-bg-surface);
|
||||||
|
color: var(--color-text-primary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.9rem;
|
font-size: var(--font-size-sm);
|
||||||
|
transition: var(--transition-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.role-select:hover {
|
.role-select:hover {
|
||||||
border-color: #007bff;
|
border-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.role-select:focus {
|
.role-select:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #007bff;
|
border-color: var(--input-focus-border-color);
|
||||||
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
|
box-shadow: var(--input-focus-shadow);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,12 @@
|
|||||||
font-family: var(--font-family-base);
|
font-family: var(--font-family-base);
|
||||||
transition: var(--transition-base);
|
transition: var(--transition-base);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
background: var(--color-bg-surface);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-item-form-input::placeholder {
|
||||||
|
color: var(--color-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-item-form-input:focus {
|
.add-item-form-input:focus {
|
||||||
@ -107,6 +113,8 @@
|
|||||||
font-family: var(--font-family-base);
|
font-family: var(--font-family-base);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transition: var(--transition-base);
|
transition: var(--transition-base);
|
||||||
|
background: var(--color-bg-surface);
|
||||||
|
color: var(--color-text-primary);
|
||||||
-moz-appearance: textfield; /* Remove spinner in Firefox */
|
-moz-appearance: textfield; /* Remove spinner in Firefox */
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +141,8 @@
|
|||||||
border-radius: var(--button-border-radius);
|
border-radius: var(--button-border-radius);
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
font-weight: var(--button-font-weight);
|
font-weight: var(--button-font-weight);
|
||||||
cursor: pointer;
|
flex: 1;
|
||||||
|
min-width: 120px
|
||||||
transition: var(--transition-base);
|
transition: var(--transition-base);
|
||||||
margin-top: var(--spacing-sm);
|
margin-top: var(--spacing-sm);
|
||||||
}
|
}
|
||||||
@ -150,12 +159,13 @@
|
|||||||
|
|
||||||
.add-item-form-submit.disabled,
|
.add-item-form-submit.disabled,
|
||||||
.add-item-form-submit:disabled {
|
.add-item-form-submit:disabled {
|
||||||
background: var(--color-bg-disabled);
|
background: var(--color-gray-400);
|
||||||
color: var(--color-text-disabled);
|
color: var(--color-gray-600);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: 0.6;
|
opacity: 1;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
transform: none;
|
transform: none;
|
||||||
|
border: var(--border-width-thin) solid var(--color-gray-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
|
|||||||
@ -4,43 +4,43 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: rgba(0, 0, 0, 0.6);
|
background: var(--modal-backdrop-bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 1000;
|
z-index: var(--z-modal);
|
||||||
padding: 1em;
|
padding: var(--spacing-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-item-details-modal {
|
.add-item-details-modal {
|
||||||
background: white;
|
background: var(--modal-bg);
|
||||||
border-radius: 12px;
|
border-radius: var(--border-radius-xl);
|
||||||
padding: 1.5em;
|
padding: var(--spacing-xl);
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
box-shadow: var(--shadow-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-item-details-title {
|
.add-item-details-title {
|
||||||
font-size: 1.4em;
|
font-size: var(--font-size-xl);
|
||||||
margin: 0 0 0.3em 0;
|
margin: 0 0 var(--spacing-xs) 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #333;
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-item-details-subtitle {
|
.add-item-details-subtitle {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #666;
|
color: var(--color-text-secondary);
|
||||||
margin: 0 0 1.5em 0;
|
margin: 0 0 var(--spacing-xl) 0;
|
||||||
font-size: 0.9em;
|
font-size: var(--font-size-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-item-details-section {
|
.add-item-details-section {
|
||||||
margin-bottom: 1.5em;
|
margin-bottom: var(--spacing-xl);
|
||||||
padding-bottom: 1.5em;
|
padding-bottom: var(--spacing-xl);
|
||||||
border-bottom: 1px solid #e0e0e0;
|
border-bottom: var(--border-width-thin) solid var(--color-border-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-item-details-section:last-of-type {
|
.add-item-details-section:last-of-type {
|
||||||
@ -48,9 +48,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.add-item-details-section-title {
|
.add-item-details-section-title {
|
||||||
font-size: 1.1em;
|
font-size: var(--font-size-lg);
|
||||||
margin: 0 0 1em 0;
|
margin: 0 0 var(--spacing-md) 0;
|
||||||
color: #555;
|
color: var(--color-text-secondary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,27 +68,27 @@
|
|||||||
.add-item-details-image-btn {
|
.add-item-details-image-btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 140px;
|
min-width: 140px;
|
||||||
padding: 0.8em;
|
padding: var(--button-padding-y) var(--button-padding-x);
|
||||||
font-size: 0.95em;
|
font-size: 0.95em;
|
||||||
border: 2px solid #007bff;
|
border: var(--border-width-medium) solid var(--color-primary);
|
||||||
background: white;
|
background: var(--color-bg-surface);
|
||||||
color: #007bff;
|
color: var(--color-primary);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 600;
|
font-weight: var(--button-font-weight);
|
||||||
transition: all 0.2s;
|
transition: var(--transition-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-item-details-image-btn:hover {
|
.add-item-details-image-btn:hover {
|
||||||
background: #007bff;
|
background: var(--color-primary);
|
||||||
color: white;
|
color: var(--color-text-inverse);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-item-details-image-preview {
|
.add-item-details-image-preview {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 2px solid #e0e0e0;
|
border: var(--border-width-medium) solid var(--color-border-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-item-details-image-preview img {
|
.add-item-details-image-preview img {
|
||||||
|
|||||||
@ -1,44 +1,45 @@
|
|||||||
/* Classification Section */
|
/* Classification Section */
|
||||||
.classification-section {
|
.classification-section {
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.classification-title {
|
.classification-title {
|
||||||
font-size: 1em;
|
font-size: var(--font-size-base);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 0.8rem;
|
margin-bottom: var(--spacing-md);
|
||||||
color: #333;
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.classification-field {
|
.classification-field {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: var(--spacing-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.classification-field label {
|
.classification-field label {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 0.9em;
|
font-size: var(--font-size-sm);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 0.4rem;
|
margin-bottom: var(--spacing-xs);
|
||||||
color: #555;
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.classification-select {
|
.classification-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.6rem;
|
padding: var(--input-padding-y) var(--input-padding-x);
|
||||||
font-size: 1em;
|
font-size: var(--font-size-base);
|
||||||
border: 1px solid #ccc;
|
border: var(--border-width-thin) solid var(--input-border-color);
|
||||||
border-radius: 4px;
|
border-radius: var(--input-border-radius);
|
||||||
background: white;
|
background: var(--color-bg-surface);
|
||||||
|
color: var(--color-text-primary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color 0.2s;
|
transition: var(--transition-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.classification-select:focus {
|
.classification-select:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #007bff;
|
border-color: var(--input-focus-border-color);
|
||||||
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.1);
|
box-shadow: var(--input-focus-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.classification-select:hover {
|
.classification-select:hover {
|
||||||
border-color: #999;
|
border-color: var(--color-border-dark);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,36 +4,36 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: rgba(0, 0, 0, 0.6);
|
background: var(--modal-backdrop-bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 1000;
|
z-index: var(--z-modal);
|
||||||
padding: 1em;
|
padding: var(--spacing-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-modal-content {
|
.edit-modal-content {
|
||||||
background: white;
|
background: var(--modal-bg);
|
||||||
border-radius: 12px;
|
border-radius: var(--border-radius-xl);
|
||||||
padding: 1.5em;
|
padding: var(--spacing-xl);
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
box-shadow: var(--shadow-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-modal-title {
|
.edit-modal-title {
|
||||||
font-size: 1.5em;
|
font-size: var(--font-size-2xl);
|
||||||
margin: 0 0 1em 0;
|
margin: 0 0 var(--spacing-md) 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #333;
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-modal-subtitle {
|
.edit-modal-subtitle {
|
||||||
font-size: 1.1em;
|
font-size: var(--font-size-lg);
|
||||||
margin: 0.5em 0 0.8em 0;
|
margin: var(--spacing-sm) 0 var(--spacing-md) 0;
|
||||||
color: #555;
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-modal-field {
|
.edit-modal-field {
|
||||||
@ -42,33 +42,36 @@
|
|||||||
|
|
||||||
.edit-modal-field label {
|
.edit-modal-field label {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 0.3em;
|
margin-bottom: var(--spacing-xs);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: var(--color-text-primary);
|
||||||
font-size: 0.95em;
|
font-size: 0.95em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-modal-input,
|
.edit-modal-input,
|
||||||
.edit-modal-select {
|
.edit-modal-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.6em;
|
padding: var(--input-padding-y) var(--input-padding-x);
|
||||||
font-size: 1em;
|
font-size: var(--font-size-base);
|
||||||
border: 1px solid #ccc;
|
border: var(--border-width-thin) solid var(--input-border-color);
|
||||||
border-radius: 6px;
|
border-radius: var(--input-border-radius);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
transition: border-color 0.2s;
|
transition: var(--transition-base);
|
||||||
|
background: var(--color-bg-surface);
|
||||||
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-modal-input:focus,
|
.edit-modal-input:focus,
|
||||||
.edit-modal-select:focus {
|
.edit-modal-select:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #007bff;
|
border-color: var(--input-focus-border-color);
|
||||||
|
box-shadow: var(--input-focus-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-modal-divider {
|
.edit-modal-divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: #e0e0e0;
|
background: var(--color-border-light);
|
||||||
margin: 1.5em 0;
|
margin: var(--spacing-xl) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-modal-actions {
|
.edit-modal-actions {
|
||||||
@ -79,13 +82,13 @@
|
|||||||
|
|
||||||
.edit-modal-btn {
|
.edit-modal-btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 0.7em;
|
padding: var(--button-padding-y) var(--button-padding-x);
|
||||||
font-size: 1em;
|
font-size: var(--font-size-base);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: var(--button-border-radius);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 600;
|
font-weight: var(--button-font-weight);
|
||||||
transition: all 0.2s;
|
transition: var(--transition-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-modal-btn:disabled {
|
.edit-modal-btn:disabled {
|
||||||
@ -94,39 +97,39 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.edit-modal-btn-cancel {
|
.edit-modal-btn-cancel {
|
||||||
background: #6c757d;
|
background: var(--color-secondary);
|
||||||
color: white;
|
color: var(--color-text-inverse);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-modal-btn-cancel:hover:not(:disabled) {
|
.edit-modal-btn-cancel:hover:not(:disabled) {
|
||||||
background: #5a6268;
|
background: var(--color-secondary-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-modal-btn-save {
|
.edit-modal-btn-save {
|
||||||
background: #007bff;
|
background: var(--color-primary);
|
||||||
color: white;
|
color: var(--color-text-inverse);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-modal-btn-save:hover:not(:disabled) {
|
.edit-modal-btn-save:hover:not(:disabled) {
|
||||||
background: #0056b3;
|
background: var(--color-primary-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-modal-btn-image {
|
.edit-modal-btn-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.7em;
|
padding: var(--button-padding-y) var(--button-padding-x);
|
||||||
font-size: 1em;
|
font-size: var(--font-size-base);
|
||||||
border: 2px solid #28a745;
|
border: var(--border-width-medium) solid var(--color-success);
|
||||||
border-radius: 6px;
|
border-radius: var(--button-border-radius);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 600;
|
font-weight: var(--button-font-weight);
|
||||||
transition: all 0.2s;
|
transition: var(--transition-base);
|
||||||
background: white;
|
background: var(--color-bg-surface);
|
||||||
color: #28a745;
|
color: var(--color-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-modal-btn-image:hover:not(:disabled) {
|
.edit-modal-btn-image:hover:not(:disabled) {
|
||||||
background: #28a745;
|
background: var(--color-success);
|
||||||
color: white;
|
color: var(--color-text-inverse);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-modal-btn-image:disabled {
|
.edit-modal-btn-image:disabled {
|
||||||
|
|||||||
42
frontend/src/styles/components/SuggestionList.css
Normal file
42
frontend/src/styles/components/SuggestionList.css
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/* Suggestion List Component */
|
||||||
|
.suggestion-list {
|
||||||
|
background: var(--color-bg-surface);
|
||||||
|
border: 2px solid var(--color-border-medium);
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
list-style: none;
|
||||||
|
padding: var(--spacing-xs);
|
||||||
|
margin: 0;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||||||
|
position: relative;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-item {
|
||||||
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
background: var(--color-bg-hover);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
border: 1px solid var(--color-border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-item:hover {
|
||||||
|
background: var(--color-primary-light);
|
||||||
|
color: var(--color-primary);
|
||||||
|
font-weight: 500;
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-item:active {
|
||||||
|
background: var(--color-primary);
|
||||||
|
color: var(--color-text-inverse);
|
||||||
|
}
|
||||||
41
frontend/src/styles/pages/AdminPanel.css
Normal file
41
frontend/src/styles/pages/AdminPanel.css
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/* Admin Panel Page */
|
||||||
|
.admin-panel-page {
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-panel-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: var(--color-bg-surface);
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-panel-title {
|
||||||
|
font-size: var(--font-size-3xl);
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin: 0 0 var(--spacing-xl) 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-panel-users {
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.admin-panel-page {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-panel-container {
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-panel-title {
|
||||||
|
font-size: var(--font-size-2xl);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -31,6 +31,40 @@
|
|||||||
padding-top: var(--spacing-md);
|
padding-top: var(--spacing-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.glist-section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
border-top: var(--border-width-medium) solid var(--color-border-light);
|
||||||
|
padding-top: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glist-section-header .glist-section-title {
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glist-collapse-btn {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
padding: var(--spacing-xs) var(--spacing-md);
|
||||||
|
cursor: pointer;
|
||||||
|
border: var(--border-width-thin) solid var(--color-border-medium);
|
||||||
|
background: var(--color-bg-surface);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
border-radius: var(--button-border-radius);
|
||||||
|
transition: var(--transition-base);
|
||||||
|
font-weight: var(--button-font-weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glist-collapse-btn:hover {
|
||||||
|
background: var(--color-bg-hover);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
/* Classification Groups */
|
/* Classification Groups */
|
||||||
.glist-classification-group {
|
.glist-classification-group {
|
||||||
margin-bottom: var(--spacing-xl);
|
margin-bottom: var(--spacing-xl);
|
||||||
@ -94,49 +128,52 @@
|
|||||||
|
|
||||||
/* Suggestion dropdown */
|
/* Suggestion dropdown */
|
||||||
.glist-suggest-box {
|
.glist-suggest-box {
|
||||||
background: #fff;
|
background: var(--color-bg-surface);
|
||||||
border: 1px solid #ccc;
|
border: var(--border-width-thin) solid var(--color-border-medium);
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 999;
|
z-index: var(--z-dropdown);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
box-shadow: 0 0 10px rgba(0,0,0,0.08);
|
box-shadow: var(--shadow-card);
|
||||||
padding: 1em;
|
padding: var(--spacing-md);
|
||||||
width: calc(100% - 8em);
|
width: calc(100% - 8em);
|
||||||
max-width: 440px;
|
max-width: 440px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.glist-suggest-item {
|
.glist-suggest-item {
|
||||||
padding: 0.5em;
|
padding: var(--spacing-sm);
|
||||||
padding-inline: 2em;
|
padding-inline: var(--spacing-xl);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
transition: var(--transition-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.glist-suggest-item:hover {
|
.glist-suggest-item:hover {
|
||||||
background: #eee;
|
background: var(--color-bg-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Grocery list items */
|
/* Grocery list items */
|
||||||
.glist-ul {
|
.glist-ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-top: 1em;
|
margin-top: var(--spacing-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.glist-li {
|
.glist-li {
|
||||||
background: #fff;
|
background: var(--color-bg-surface);
|
||||||
border: 1px solid #e0e0e0;
|
border: var(--border-width-thin) solid var(--color-border-light);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
margin-bottom: 0.8em;
|
margin-bottom: var(--spacing-sm);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: box-shadow 0.2s, transform 0.2s;
|
transition: box-shadow var(--transition-base), transform var(--transition-base);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.glist-li:hover {
|
.glist-li:hover {
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
box-shadow: var(--shadow-md);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,21 +188,21 @@
|
|||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
min-width: 50px;
|
min-width: 50px;
|
||||||
background: #f5f5f5;
|
background: var(--color-gray-100);
|
||||||
border: 2px solid #e0e0e0;
|
border: var(--border-width-medium) solid var(--color-border-light);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
color: #ccc;
|
color: var(--color-border-medium);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.glist-item-image.has-image {
|
.glist-item-image.has-image {
|
||||||
border-color: #007bff;
|
border-color: var(--color-primary);
|
||||||
background: #fff;
|
background: var(--color-bg-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
.glist-item-image img {
|
.glist-item-image img {
|
||||||
@ -176,7 +213,7 @@
|
|||||||
|
|
||||||
.glist-item-image.has-image:hover {
|
.glist-item-image.has-image:hover {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
box-shadow: 0 0 8px rgba(0, 123, 255, 0.3);
|
box-shadow: 0 0 8px var(--color-primary-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.glist-item-content {
|
.glist-item-content {
|
||||||
@ -197,37 +234,40 @@
|
|||||||
.glist-item-name {
|
.glist-item-name {
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
color: #333;
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.glist-item-quantity {
|
.glist-item-quantity {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background: rgba(0, 123, 255, 0.9);
|
background: var(--color-primary);
|
||||||
color: white;
|
color: var(--color-text-inverse);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 0.3em;
|
font-size: 0.3em;
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
border-radius: 0 6px 0 4px;
|
border-radius: 0 var(--border-radius-md) 0 var(--border-radius-sm);
|
||||||
min-width: 20%;
|
min-width: 20%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
box-shadow: var(--shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.glist-item-users {
|
.glist-item-users {
|
||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
color: #888;
|
color: var(--color-text-secondary);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sorting dropdown */
|
/* Sorting dropdown */
|
||||||
.glist-sort {
|
.glist-sort {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0.3em 0;
|
margin: var(--spacing-xs) 0;
|
||||||
padding: 0.5em;
|
padding: var(--spacing-sm);
|
||||||
font-size: 1em;
|
font-size: var(--font-size-base);
|
||||||
border-radius: 4px;
|
border-radius: var(--border-radius-sm);
|
||||||
|
border: var(--border-width-thin) solid var(--color-border-medium);
|
||||||
|
background: var(--color-bg-surface);
|
||||||
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Image upload */
|
/* Image upload */
|
||||||
@ -237,18 +277,19 @@
|
|||||||
|
|
||||||
.glist-image-label {
|
.glist-image-label {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 0.6em;
|
padding: var(--spacing-sm);
|
||||||
background: #f0f0f0;
|
background: var(--color-gray-100);
|
||||||
border: 2px dashed #ccc;
|
border: var(--border-width-medium) dashed var(--color-border-medium);
|
||||||
border-radius: 4px;
|
border-radius: var(--border-radius-sm);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: var(--transition-base);
|
||||||
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.glist-image-label:hover {
|
.glist-image-label:hover {
|
||||||
background: #e8e8e8;
|
background: var(--color-bg-hover);
|
||||||
border-color: #007bff;
|
border-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.glist-image-preview {
|
.glist-image-preview {
|
||||||
@ -260,8 +301,8 @@
|
|||||||
.glist-image-preview img {
|
.glist-image-preview img {
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
border: 2px solid #ddd;
|
border: var(--border-width-medium) solid var(--color-border-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.glist-remove-image {
|
.glist-remove-image {
|
||||||
@ -270,10 +311,10 @@
|
|||||||
right: -8px;
|
right: -8px;
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
background: #ff4444;
|
background: var(--color-danger);
|
||||||
color: white;
|
color: var(--color-text-inverse);
|
||||||
border: 2px solid white;
|
border: var(--border-width-medium) solid var(--color-bg-surface);
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -283,7 +324,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.glist-remove-image:hover {
|
.glist-remove-image:hover {
|
||||||
background: #cc0000;
|
background: var(--color-danger-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Floating Action Button (FAB) */
|
/* Floating Action Button (FAB) */
|
||||||
@ -291,10 +332,10 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
background: #28a745;
|
background: var(--color-success);
|
||||||
color: white;
|
color: var(--color-text-inverse);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
width: 62px;
|
width: 62px;
|
||||||
height: 62px;
|
height: 62px;
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
@ -302,12 +343,14 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-shadow: 0 3px 10px rgba(0,0,0,0.2);
|
box-shadow: var(--shadow-lg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: var(--transition-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.glist-fab:hover {
|
.glist-fab:hover {
|
||||||
background: #218838;
|
background: var(--color-success-hover);
|
||||||
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile tweaks */
|
/* Mobile tweaks */
|
||||||
|
|||||||
297
frontend/src/styles/pages/Settings.css
Normal file
297
frontend/src/styles/pages/Settings.css
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
/* Settings Page Styles */
|
||||||
|
|
||||||
|
.settings-page {
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-container {
|
||||||
|
background: var(--color-bg-surface);
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-title {
|
||||||
|
font-size: var(--font-size-2xl);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin: 0 0 var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* === Tabs === */
|
||||||
|
.settings-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
border-bottom: 2px solid var(--color-border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-tab {
|
||||||
|
padding: var(--spacing-md) var(--spacing-lg);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
margin-bottom: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-tab:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
background: var(--color-bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-tab.active {
|
||||||
|
color: var(--color-primary);
|
||||||
|
border-bottom-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* === Content === */
|
||||||
|
.settings-content {
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-section {
|
||||||
|
animation: fadeIn 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-section-title {
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin: 0 0 var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-group {
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
padding-bottom: var(--spacing-xl);
|
||||||
|
border-bottom: 1px solid var(--color-border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-group:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-label input[type="checkbox"] {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-description {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin: var(--spacing-sm) 0 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* === Theme Buttons === */
|
||||||
|
.settings-theme-options {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
margin-top: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-theme-btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
border: 2px solid var(--color-border-light);
|
||||||
|
background: var(--color-bg-surface);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-theme-btn:hover {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
background: var(--color-primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-theme-btn.active {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
background: var(--color-primary);
|
||||||
|
color: var(--color-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* === Select & Range === */
|
||||||
|
.settings-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
|
border: 1px solid var(--color-border-medium);
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
background: var(--color-bg-surface);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-range {
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: var(--color-gray-300);
|
||||||
|
outline: none;
|
||||||
|
margin-top: var(--spacing-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-range::-webkit-slider-thumb {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--color-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-range::-webkit-slider-thumb:hover {
|
||||||
|
background: var(--color-primary-hover);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-range::-moz-range-thumb {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--color-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-range::-moz-range-thumb:hover {
|
||||||
|
background: var(--color-primary-hover);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* === Actions === */
|
||||||
|
.settings-actions {
|
||||||
|
margin-top: var(--spacing-2xl);
|
||||||
|
padding-top: var(--spacing-xl);
|
||||||
|
border-top: 2px solid var(--color-border-light);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-btn-reset {
|
||||||
|
padding: var(--spacing-md) var(--spacing-xl);
|
||||||
|
border: 2px solid var(--color-danger);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--color-danger);
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.settings-btn-reset:hover {
|
||||||
|
background: var(--color-danger);
|
||||||
|
color: var(--color-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* === Responsive === */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.settings-page {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-container {
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-tabs {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-tab {
|
||||||
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-theme-options {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.settings-title {
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-container {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -189,23 +189,94 @@
|
|||||||
--modal-max-width: 500px;
|
--modal-max-width: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
DARK MODE
|
||||||
|
============================================ */
|
||||||
|
[data-theme="dark"] {
|
||||||
|
/* Primary Colors */
|
||||||
|
--color-primary: #4da3ff;
|
||||||
|
--color-primary-hover: #66b3ff;
|
||||||
|
--color-primary-light: #1a3a52;
|
||||||
|
--color-primary-dark: #3d8fdb;
|
||||||
|
|
||||||
|
/* Semantic Colors */
|
||||||
|
--color-success: #4ade80;
|
||||||
|
--color-success-hover: #5fe88d;
|
||||||
|
--color-success-light: #1a3a28;
|
||||||
|
|
||||||
|
--color-danger: #f87171;
|
||||||
|
--color-danger-hover: #fa8585;
|
||||||
|
--color-danger-light: #4a2020;
|
||||||
|
|
||||||
|
--color-warning: #fbbf24;
|
||||||
|
--color-warning-hover: #fcd34d;
|
||||||
|
--color-warning-light: #3a2f0f;
|
||||||
|
|
||||||
|
--color-info: #38bdf8;
|
||||||
|
--color-info-hover: #5dc9fc;
|
||||||
|
--color-info-light: #1a2f3a;
|
||||||
|
|
||||||
|
/* Text Colors */
|
||||||
|
--color-text-primary: #f1f5f9;
|
||||||
|
--color-text-secondary: #94a3b8;
|
||||||
|
--color-text-muted: #64748b;
|
||||||
|
--color-text-inverse: #1e293b;
|
||||||
|
--color-text-disabled: #475569;
|
||||||
|
|
||||||
|
/* Background Colors */
|
||||||
|
--color-bg-body: #0f172a;
|
||||||
|
--color-bg-surface: #1e293b;
|
||||||
|
--color-bg-hover: #334155;
|
||||||
|
--color-bg-disabled: #1e293b;
|
||||||
|
|
||||||
|
/* Border Colors */
|
||||||
|
--color-border-light: #334155;
|
||||||
|
--color-border-medium: #475569;
|
||||||
|
--color-border-dark: #64748b;
|
||||||
|
--color-border-disabled: #334155;
|
||||||
|
|
||||||
|
/* Neutral Colors - Dark adjusted */
|
||||||
|
--color-gray-50: #1e293b;
|
||||||
|
--color-gray-100: #1e293b;
|
||||||
|
--color-gray-200: #334155;
|
||||||
|
--color-gray-300: #475569;
|
||||||
|
--color-gray-400: #64748b;
|
||||||
|
--color-gray-500: #94a3b8;
|
||||||
|
--color-gray-600: #cbd5e1;
|
||||||
|
--color-gray-700: #e2e8f0;
|
||||||
|
--color-gray-800: #f1f5f9;
|
||||||
|
--color-gray-900: #f8fafc;
|
||||||
|
|
||||||
|
/* Shadows - Lighter for dark mode */
|
||||||
|
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
|
||||||
|
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);
|
||||||
|
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
|
||||||
|
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.6);
|
||||||
|
--shadow-card: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||||
|
|
||||||
|
/* Modals */
|
||||||
|
--modal-backdrop-bg: rgba(0, 0, 0, 0.8);
|
||||||
|
--modal-bg: var(--color-bg-surface);
|
||||||
|
|
||||||
|
/* Inputs */
|
||||||
|
--input-border-color: var(--color-border-medium);
|
||||||
|
--input-focus-shadow: 0 0 0 2px rgba(77, 163, 255, 0.3);
|
||||||
|
|
||||||
|
/* Cards */
|
||||||
|
--card-bg: var(--color-bg-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
DARK MODE SUPPORT (Future Implementation)
|
DARK MODE SUPPORT (Future Implementation)
|
||||||
============================================ */
|
============================================ */
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
/* Uncomment to enable dark mode
|
/* Auto mode will use data-theme attribute set by JS */
|
||||||
:root {
|
|
||||||
--color-text-primary: #f8f9fa;
|
|
||||||
--color-text-secondary: #adb5bd;
|
|
||||||
--color-bg-body: #212529;
|
|
||||||
--color-bg-surface: #343a40;
|
|
||||||
--color-border-light: #495057;
|
|
||||||
--color-border-medium: #6c757d;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Manual dark mode class override */
|
|
||||||
|
/* Manual dark mode class override (deprecated - use data-theme) */
|
||||||
.dark-mode {
|
.dark-mode {
|
||||||
--color-text-primary: #f8f9fa;
|
--color-text-primary: #f8f9fa;
|
||||||
--color-text-secondary: #adb5bd;
|
--color-text-secondary: #adb5bd;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user