Skip to content

Commit f357b60

Browse files
committed
toggle simplification
1 parent e26e947 commit f357b60

File tree

6 files changed

+52
-121
lines changed

6 files changed

+52
-121
lines changed

poliloom-gui/src/app/globals.css

Lines changed: 3 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
@import 'tailwindcss';
22

3+
@custom-variant dark (&:where(.dark, .dark *));
4+
35
@font-face {
46
font-family: 'Mona Sans';
57
src: url('https://assets.opensanctions.org/fonts/Mona-Sans.woff2') format('woff2');
@@ -132,68 +134,7 @@
132134
--font-mono: var(--font-monaspace);
133135
}
134136

135-
/* Dark mode: apply when .dark class is set, or when system prefers dark and no override */
136-
@media (prefers-color-scheme: dark) {
137-
:root:not(.light) {
138-
/* Backgrounds */
139-
--background: #0a0a0a;
140-
--surface: #18181b; /* zinc-900 */
141-
--surface-muted: #1f1f23;
142-
--surface-hover: #27272a; /* zinc-800 */
143-
--surface-active: #3f3f46; /* zinc-700 */
144-
145-
/* Text */
146-
--foreground: #ededed;
147-
--foreground-secondary: #d4d4d8; /* zinc-300 */
148-
--foreground-tertiary: #a1a1aa; /* zinc-400 */
149-
--foreground-muted: #71717a; /* zinc-500 */
150-
--foreground-subtle: #52525b; /* zinc-600 */
151-
152-
/* Borders */
153-
--border-muted: #27272a; /* zinc-800 */
154-
--border: #3f3f46; /* zinc-700 */
155-
--border-strong: #52525b; /* zinc-600 */
156-
157-
/* Primary/Accent (Indigo) */
158-
--accent: #6366f1; /* indigo-500 */
159-
--accent-hover: #818cf8; /* indigo-400 */
160-
--accent-foreground: #a5b4fc; /* indigo-300 */
161-
--accent-foreground-hover: #c7d2fe; /* indigo-200 */
162-
--accent-muted: rgba(99, 102, 241, 0.15);
163-
--accent-muted-hover: rgba(99, 102, 241, 0.25);
164-
--accent-light: #4f46e5; /* indigo-600 */
165-
--accent-border-hover: #6366f1; /* indigo-500 */
166-
--accent-on-muted: #c7d2fe; /* indigo-200 */
167-
--accent-on-solid: #ffffff;
168-
--accent-on-solid-muted: #a5b4fc; /* indigo-300 - dimmed text on solid */
169-
170-
/* Success (Green) */
171-
--success: #22c55e; /* green-500 */
172-
--success-bright: #22c55e; /* green-500 */
173-
--success-muted: rgba(34, 197, 94, 0.15);
174-
--success-muted-hover: rgba(34, 197, 94, 0.25);
175-
--success-foreground: #86efac; /* green-300 */
176-
177-
/* Danger (Red) */
178-
--danger: #ef4444; /* red-500 */
179-
--danger-bright: #f87171; /* red-400 */
180-
--danger-muted: rgba(239, 68, 68, 0.15);
181-
--danger-muted-hover: rgba(239, 68, 68, 0.25);
182-
--danger-foreground: #fca5a5; /* red-300 */
183-
--danger-foreground-hover: #fecaca; /* red-200 */
184-
--danger-subtle: #fca5a5; /* red-300 */
185-
--danger-deep: rgba(127, 29, 29, 0.8);
186-
187-
/* Info (Blue) */
188-
--info: #3b82f6; /* blue-500 */
189-
--info-hover: #60a5fa; /* blue-400 */
190-
--info-muted: rgba(59, 130, 246, 0.15);
191-
--info-muted-hover: rgba(59, 130, 246, 0.25);
192-
--info-foreground: #93c5fd; /* blue-300 */
193-
}
194-
}
195-
196-
/* Manual dark mode override */
137+
/* Dark mode */
197138
:root.dark {
198139
/* Backgrounds */
199140
--background: #0a0a0a;

poliloom-gui/src/app/layout.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,19 @@ export default async function RootLayout({
3333
const themeClass = themeCookie === 'light' || themeCookie === 'dark' ? themeCookie : ''
3434

3535
return (
36-
<html lang="en" className={themeClass}>
36+
<html lang="en" className={themeClass} suppressHydrationWarning>
3737
<head>
38+
<script
39+
dangerouslySetInnerHTML={{
40+
__html: `
41+
if (!document.documentElement.classList.contains('light') && !document.documentElement.classList.contains('dark')) {
42+
document.documentElement.classList.add(
43+
window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
44+
);
45+
}
46+
`,
47+
}}
48+
/>
3849
<Script
3950
src="https://cloud.umami.is/script.js"
4051
data-website-id="0d1eaae6-470d-4087-9908-ec65448c2490"

poliloom-gui/src/components/layout/Header.tsx

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useState, useEffect, useSyncExternalStore } from 'react'
3+
import { useState, useEffect } from 'react'
44
import { useSession, signOut, signIn } from 'next-auth/react'
55
import Image from 'next/image'
66
import Link from 'next/link'
@@ -13,20 +13,11 @@ export function Header() {
1313
const { status } = useSession()
1414
const [menuOpen, setMenuOpen] = useState(false)
1515
const { evaluationCount } = useEvaluationCount()
16-
const { theme, setTheme } = useUserPreferences()
17-
const systemTheme = useSyncExternalStore(
18-
(cb) => {
19-
const mq = window.matchMedia('(prefers-color-scheme: dark)')
20-
mq.addEventListener('change', cb)
21-
return () => mq.removeEventListener('change', cb)
22-
},
23-
() => (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'),
24-
() => 'light',
25-
)
26-
const resolvedTheme = theme ?? systemTheme
16+
const { setTheme } = useUserPreferences()
2717

2818
const toggleTheme = () => {
29-
setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')
19+
const isDark = document.documentElement.classList.contains('dark')
20+
setTheme(isDark ? 'light' : 'dark')
3021
}
3122

3223
useEffect(() => {
@@ -91,28 +82,27 @@ export function Header() {
9182
onClick={toggleTheme}
9283
variant="secondary"
9384
size="small"
94-
title={`Switch to ${resolvedTheme === 'dark' ? 'light' : 'dark'} mode`}
95-
aria-label={`Switch to ${resolvedTheme === 'dark' ? 'light' : 'dark'} mode`}
85+
title="Toggle theme"
86+
aria-label="Toggle theme"
9687
>
97-
{resolvedTheme === 'dark' ? (
98-
<svg
99-
xmlns="http://www.w3.org/2000/svg"
100-
viewBox="0 0 24 24"
101-
fill="currentColor"
102-
className="w-4 h-4"
103-
>
104-
<path d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
105-
</svg>
106-
) : (
107-
<svg
108-
xmlns="http://www.w3.org/2000/svg"
109-
viewBox="0 0 24 24"
110-
fill="currentColor"
111-
className="w-4 h-4"
112-
>
113-
<path d="M12 2.25a.75.75 0 0 1 .75.75v2.25a.75.75 0 0 1-1.5 0V3a.75.75 0 0 1 .75-.75ZM7.5 12a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM18.894 6.166a.75.75 0 0 0-1.06-1.06l-1.591 1.59a.75.75 0 1 0 1.06 1.061l1.591-1.59ZM21.75 12a.75.75 0 0 1-.75.75h-2.25a.75.75 0 0 1 0-1.5H21a.75.75 0 0 1 .75.75ZM17.834 18.894a.75.75 0 0 0 1.06-1.06l-1.59-1.591a.75.75 0 1 0-1.061 1.06l1.591 1.591ZM12 18a.75.75 0 0 1 .75.75V21a.75.75 0 0 1-1.5 0v-2.25A.75.75 0 0 1 12 18ZM7.758 17.303a.75.75 0 0 0-1.061-1.06l-1.591 1.59a.75.75 0 0 0 1.06 1.061l1.591-1.59ZM6 12a.75.75 0 0 1-.75.75H3a.75.75 0 0 1 0-1.5h2.25A.75.75 0 0 1 6 12ZM6.697 7.757a.75.75 0 0 0 1.06-1.06l-1.59-1.591a.75.75 0 0 0-1.061 1.06l1.59 1.591Z" />
114-
</svg>
115-
)}
88+
{/* Moon icon - shown in dark mode */}
89+
<svg
90+
xmlns="http://www.w3.org/2000/svg"
91+
viewBox="0 0 24 24"
92+
fill="currentColor"
93+
className="w-4 h-4 hidden dark:block"
94+
>
95+
<path d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
96+
</svg>
97+
{/* Sun icon - shown in light mode */}
98+
<svg
99+
xmlns="http://www.w3.org/2000/svg"
100+
viewBox="0 0 24 24"
101+
fill="currentColor"
102+
className="w-4 h-4 block dark:hidden"
103+
>
104+
<path d="M12 2.25a.75.75 0 0 1 .75.75v2.25a.75.75 0 0 1-1.5 0V3a.75.75 0 0 1 .75-.75ZM7.5 12a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM18.894 6.166a.75.75 0 0 0-1.06-1.06l-1.591 1.59a.75.75 0 1 0 1.06 1.061l1.591-1.59ZM21.75 12a.75.75 0 0 1-.75.75h-2.25a.75.75 0 0 1 0-1.5H21a.75.75 0 0 1 .75.75ZM17.834 18.894a.75.75 0 0 0 1.06-1.06l-1.59-1.591a.75.75 0 1 0-1.061 1.06l1.591 1.591ZM12 18a.75.75 0 0 1 .75.75V21a.75.75 0 0 1-1.5 0v-2.25A.75.75 0 0 1 12 18ZM7.758 17.303a.75.75 0 0 0-1.061-1.06l-1.591 1.59a.75.75 0 0 0 1.06 1.061l1.591-1.59ZM6 12a.75.75 0 0 1-.75.75H3a.75.75 0 0 1 0-1.5h2.25A.75.75 0 0 1 6 12ZM6.697 7.757a.75.75 0 0 0 1.06-1.06l-1.59-1.591a.75.75 0 0 0-1.061 1.06l1.59 1.591Z" />
105+
</svg>
116106
</Button>
117107
{status === 'authenticated' ? (
118108
<Button

poliloom-gui/src/components/ui/HeaderedBox.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function HeaderedBox({
2626
return (
2727
<Box onHover={onHover}>
2828
{/* Header */}
29-
<div className="px-6 py-4 border-b border-border-muted bg-gradient-to-r from-surface-muted to-surface rounded-t-lg">
29+
<div className="px-6 py-4 border-b border-border-muted bg-gradient-to-r from-surface-muted to-surface dark:bg-gradient-to-l rounded-t-lg">
3030
<div className="flex items-center gap-3">
3131
{icon && <span className="text-2xl">{icon}</span>}
3232
<div className="flex-1">

poliloom-gui/src/contexts/UserPreferencesContext.tsx

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ import {
1616
WikidataEntity,
1717
} from '@/types'
1818

19-
export type Theme = 'light' | 'dark' | undefined
20-
2119
interface UserPreferencesContextType {
2220
filters: PreferenceResponse[]
2321
languages: LanguageResponse[]
@@ -28,7 +26,6 @@ interface UserPreferencesContextType {
2826
updateFilters: (type: PreferenceType, items: WikidataEntity[]) => void
2927
isAdvancedMode: boolean
3028
setAdvancedMode: (enabled: boolean) => void
31-
theme: Theme
3229
setTheme: (theme: 'light' | 'dark') => void
3330
}
3431

@@ -55,20 +52,6 @@ function subscribeToAdvancedMode(callback: () => void): () => void {
5552
return () => window.removeEventListener('storage', callback)
5653
}
5754

58-
// Theme helpers
59-
function getThemeCookie(): Theme {
60-
if (typeof document === 'undefined') return undefined
61-
const match = document.cookie.match(new RegExp(`${THEME_COOKIE_NAME}=([^;]+)`))
62-
const value = match?.[1]
63-
if (value === 'light' || value === 'dark') return value
64-
return undefined
65-
}
66-
67-
function subscribeToTheme(callback: () => void): () => void {
68-
window.addEventListener('storage', callback)
69-
return () => window.removeEventListener('storage', callback)
70-
}
71-
7255
function applyThemeToDocument(theme: 'light' | 'dark') {
7356
if (typeof document === 'undefined') return
7457
const root = document.documentElement
@@ -141,13 +124,21 @@ export function UserPreferencesProvider({ children }: { children: React.ReactNod
141124
forceUpdate((n) => n + 1)
142125
}, [])
143126

144-
// Theme state
145-
const theme = useSyncExternalStore(subscribeToTheme, getThemeCookie, () => undefined)
146-
147127
const setTheme = useCallback((newTheme: 'light' | 'dark') => {
148128
setThemeCookie(newTheme)
149129
applyThemeToDocument(newTheme)
150-
forceUpdate((n) => n + 1)
130+
}, [])
131+
132+
// Follow system preference when no cookie is set
133+
useEffect(() => {
134+
const mq = window.matchMedia('(prefers-color-scheme: dark)')
135+
const handleChange = (e: MediaQueryListEvent) => {
136+
if (!document.cookie.includes(THEME_COOKIE_NAME)) {
137+
applyThemeToDocument(e.matches ? 'dark' : 'light')
138+
}
139+
}
140+
mq.addEventListener('change', handleChange)
141+
return () => mq.removeEventListener('change', handleChange)
151142
}, [])
152143

153144
// Fetch available languages
@@ -259,7 +250,6 @@ export function UserPreferencesProvider({ children }: { children: React.ReactNod
259250
updateFilters,
260251
isAdvancedMode,
261252
setAdvancedMode,
262-
theme,
263253
setTheme,
264254
}
265255

poliloom-gui/src/test/test-utils.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ const MockUserPreferencesProvider = ({ children }: { children: React.ReactNode }
6363
updateFilters: vi.fn(),
6464
isAdvancedMode: false,
6565
setAdvancedMode: vi.fn(),
66-
theme: undefined,
6766
setTheme: vi.fn(),
6867
}}
6968
>

0 commit comments

Comments
 (0)