Add dark mode
This commit is contained in:
parent
5e210e14bd
commit
7235df0a97
14 changed files with 304 additions and 139 deletions
|
|
@ -23,6 +23,8 @@ import type {
|
|||
HexagonPropertiesResponse,
|
||||
} from './types';
|
||||
|
||||
type Theme = 'light' | 'dark';
|
||||
|
||||
const DEBOUNCE_MS = 150;
|
||||
const URL_DEBOUNCE_MS = 300;
|
||||
|
||||
|
|
@ -180,9 +182,13 @@ type Page = 'home' | 'dashboard' | 'data-sources';
|
|||
function Header({
|
||||
activePage,
|
||||
onPageChange,
|
||||
theme,
|
||||
onToggleTheme,
|
||||
}: {
|
||||
activePage: Page;
|
||||
onPageChange: (page: Page) => void;
|
||||
theme: Theme;
|
||||
onToggleTheme: () => void;
|
||||
}) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
|
|
@ -240,7 +246,23 @@ function Header({
|
|||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
{activePage === 'dashboard' && (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={onToggleTheme}
|
||||
className="flex items-center justify-center w-8 h-8 rounded bg-navy-800 hover:bg-navy-700 transition-colors"
|
||||
title={`Theme: ${theme}`}
|
||||
>
|
||||
{theme === 'light' ? (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
{activePage === 'dashboard' && (
|
||||
<button
|
||||
onClick={handleShare}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded bg-navy-800 hover:bg-navy-700 transition-colors text-sm"
|
||||
|
|
@ -271,7 +293,8 @@ function Header({
|
|||
</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
|
@ -335,6 +358,28 @@ export default function App() {
|
|||
const [rightPaneTab, setRightPaneTab] = useState<'pois' | 'properties'>(urlState.tab || 'pois');
|
||||
const [activePage, setActivePage] = useState<Page>('home');
|
||||
|
||||
// Theme state — defaults to system preference on first visit
|
||||
const [theme, setTheme] = useState<Theme>(() => {
|
||||
const stored = localStorage.getItem('theme');
|
||||
if (stored === 'light' || stored === 'dark') return stored;
|
||||
return 'light';
|
||||
});
|
||||
|
||||
// Sync dark class on <html> and persist to localStorage
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
if (theme === 'dark') {
|
||||
root.classList.add('dark');
|
||||
} else {
|
||||
root.classList.remove('dark');
|
||||
}
|
||||
localStorage.setItem('theme', theme);
|
||||
}, [theme]);
|
||||
|
||||
const toggleTheme = useCallback(() => {
|
||||
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
|
||||
}, []);
|
||||
|
||||
// Derive enabled features from filter keys
|
||||
const enabledFeatures = useMemo(() => new Set(Object.keys(filters)), [filters]);
|
||||
|
||||
|
|
@ -704,9 +749,9 @@ export default function App() {
|
|||
|
||||
return (
|
||||
<div className="h-screen flex flex-col">
|
||||
<Header activePage={activePage} onPageChange={setActivePage} />
|
||||
<Header activePage={activePage} onPageChange={setActivePage} theme={theme} onToggleTheme={toggleTheme} />
|
||||
{activePage === 'home' ? (
|
||||
<HomePage onOpenDashboard={() => setActivePage('dashboard')} />
|
||||
<HomePage onOpenDashboard={() => setActivePage('dashboard')} theme={theme} />
|
||||
) : activePage === 'data-sources' ? (
|
||||
<DataSourcesPage />
|
||||
) : (
|
||||
|
|
@ -742,22 +787,23 @@ export default function App() {
|
|||
selectedHexagonId={selectedHexagon?.h3 || null}
|
||||
onHexagonClick={handleHexagonClick}
|
||||
initialViewState={initialViewState}
|
||||
theme={theme}
|
||||
/>
|
||||
{loading && (
|
||||
<div className="absolute top-4 right-4 bg-white px-3 py-1 rounded shadow">
|
||||
<div className="absolute top-4 right-4 bg-white dark:bg-warm-800 dark:text-warm-200 px-3 py-1 rounded shadow">
|
||||
Loading...
|
||||
</div>
|
||||
)}
|
||||
<DataSources onNavigate={() => setActivePage('data-sources')} />
|
||||
</div>
|
||||
<div className="w-72 bg-white shadow-lg z-10 flex flex-col">
|
||||
<div className="w-72 bg-white dark:bg-warm-900 shadow-lg z-10 flex flex-col">
|
||||
{/* Tab headers */}
|
||||
<div className="flex border-b border-warm-200">
|
||||
<div className="flex border-b border-warm-200 dark:border-warm-700">
|
||||
<button
|
||||
className={`flex-1 p-3 ${
|
||||
rightPaneTab === 'pois'
|
||||
? 'border-b-2 border-teal-500 font-semibold'
|
||||
: 'text-warm-600'
|
||||
? 'border-b-2 border-teal-500 font-semibold dark:text-warm-100'
|
||||
: 'text-warm-600 dark:text-warm-400'
|
||||
}`}
|
||||
onClick={() => setRightPaneTab('pois')}
|
||||
>
|
||||
|
|
@ -766,8 +812,8 @@ export default function App() {
|
|||
<button
|
||||
className={`flex-1 p-3 ${
|
||||
rightPaneTab === 'properties'
|
||||
? 'border-b-2 border-teal-500 font-semibold'
|
||||
: 'text-warm-600'
|
||||
? 'border-b-2 border-teal-500 font-semibold dark:text-warm-100'
|
||||
: 'text-warm-600 dark:text-warm-400'
|
||||
}`}
|
||||
onClick={() => setRightPaneTab('properties')}
|
||||
>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue