- ThemeToggle: Single button cycle instead of 3-button bar - UserMenu: Cleaner styling with accent avatar - MobileNavSheet: Consistent Linear tokens - Walkthrough checklists: Desktop two-column layout - RoleModal: Toggle buttons instead of tiny checkboxes - IPMScheduleModal: Toggle buttons instead of checkbox - ScoutingModal: Toggle buttons instead of checkbox
61 lines
2 KiB
TypeScript
61 lines
2 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { Sun, Moon, Monitor } from 'lucide-react';
|
|
|
|
/**
|
|
* ThemeToggle - Minimal icon-only theme switcher
|
|
* Linear-inspired: subtle, clean, unobtrusive
|
|
*/
|
|
|
|
type Theme = 'light' | 'dark' | 'system';
|
|
|
|
export default function ThemeToggle() {
|
|
const [theme, setTheme] = useState<Theme>('system');
|
|
|
|
useEffect(() => {
|
|
const savedTheme = localStorage.getItem('theme') as Theme | null;
|
|
if (savedTheme) {
|
|
setTheme(savedTheme);
|
|
applyTheme(savedTheme);
|
|
} else {
|
|
applyTheme('system');
|
|
}
|
|
}, []);
|
|
|
|
function applyTheme(newTheme: Theme) {
|
|
const root = window.document.documentElement;
|
|
root.classList.remove('light', 'dark');
|
|
|
|
if (newTheme === 'system') {
|
|
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
? 'dark'
|
|
: 'light';
|
|
root.classList.add(systemTheme);
|
|
} else {
|
|
root.classList.add(newTheme);
|
|
}
|
|
}
|
|
|
|
function cycleTheme() {
|
|
const order: Theme[] = ['light', 'dark', 'system'];
|
|
const nextIndex = (order.indexOf(theme) + 1) % order.length;
|
|
const newTheme = order[nextIndex];
|
|
setTheme(newTheme);
|
|
localStorage.setItem('theme', newTheme);
|
|
applyTheme(newTheme);
|
|
}
|
|
|
|
const Icon = theme === 'light' ? Sun : theme === 'dark' ? Moon : Monitor;
|
|
const label = theme === 'light' ? 'Light' : theme === 'dark' ? 'Dark' : 'System';
|
|
|
|
return (
|
|
<button
|
|
onClick={cycleTheme}
|
|
className="flex items-center gap-2 px-3 py-2 w-full rounded-md text-secondary hover:text-primary hover:bg-tertiary transition-colors duration-fast text-sm"
|
|
aria-label={`Theme: ${label}. Click to change.`}
|
|
title={`Theme: ${label}`}
|
|
>
|
|
<Icon size={16} strokeWidth={1.5} />
|
|
<span className="text-xs text-tertiary">{label}</span>
|
|
</button>
|
|
);
|
|
}
|