diff --git a/apps/web/src/app/App.tsx b/apps/web/src/app/App.tsx index 8d1795b1fc6cda164313635ba9ea0c1fe7c1556f..0f6d822a6cd6c7a2a2e9817ecb104f32ee8e3649 100644 --- a/apps/web/src/app/App.tsx +++ b/apps/web/src/app/App.tsx @@ -7,6 +7,7 @@ import { useQuerybuilderSettings, useSessionCache, } from '@graphpolaris/shared/lib/data-access'; +import { setCurrentTheme } from '@graphpolaris/shared/lib/data-access/store/configSlice'; import { resetGraphQueryResults, queryingBackend } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice'; import { Query2BackendQuery, QueryMultiGraph } from '@graphpolaris/shared/lib/querybuilder'; import { Navbar } from '../components/navbar/navbar'; @@ -52,6 +53,10 @@ export function App(props: App) { } }, [props]); + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { + dispatch(setCurrentTheme(event.matches)); + }); + const [authCheck, setAuthCheck] = useState(false); const [tab, setTab] = useState<SideNavTab>('Schema'); // const [visFullSize, setVisFullSize] = useState<boolean>(false); diff --git a/libs/shared/lib/components/color-mode/index.tsx b/libs/shared/lib/components/color-mode/index.tsx index 5c8a69580f656a3527e2f1e17aa5539b15c60d1b..d9f9ecb8877ce5eb8507d2b12b6af04d5a1e2adc 100644 --- a/libs/shared/lib/components/color-mode/index.tsx +++ b/libs/shared/lib/components/color-mode/index.tsx @@ -22,12 +22,16 @@ const ColorMode = () => { // Function to toggle the theme const toggleTheme = () => { - const themes = [Theme.light, Theme.dark]; - + const themes = [ + Theme.system, + Theme.light, + Theme.dark, + ]; + const newTheme = themes[(themes.indexOf(config.theme) + 1) % themes.length]; dispatch(setTheme(newTheme)); }; - const iconComponent = config.theme === Theme.dark ? 'icon-[ic--baseline-dark-mode]' : 'icon-[ic--baseline-light-mode]'; + const iconComponent = config.theme === Theme.dark ? 'icon-[ic--baseline-dark-mode]' : config.theme === Theme.light ? 'icon-[ic--baseline-light-mode]' : 'icon-[ic--baseline-auto-mode]'; return ( <TooltipProvider delayDuration={0}> @@ -36,7 +40,7 @@ const ColorMode = () => { <Button variant="ghost" size="sm" iconComponent={iconComponent} onClick={toggleTheme} /> </TooltipTrigger> <TooltipContent> - <p>{`Switch to ${config.theme === Theme.dark ? 'light' : 'dark'}-mode`}</p> + <p>{`Currently on ${config.theme.split('-')[0]} theme`}</p> </TooltipContent> </Tooltip> </TooltipProvider> diff --git a/libs/shared/lib/data-access/store/configSlice.ts b/libs/shared/lib/data-access/store/configSlice.ts index f79c4d3f932773b554edadefebe0b4ee59981f2d..932a281d357088f7669f0616c5c9269477535df6 100644 --- a/libs/shared/lib/data-access/store/configSlice.ts +++ b/libs/shared/lib/data-access/store/configSlice.ts @@ -2,6 +2,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import type { RootState } from './store'; export enum Theme { + system = 'system-mode', light = 'light-mode', dark = 'dark-mode', } @@ -9,6 +10,7 @@ export enum Theme { // Define the initial state using that type export type ConfigStateI = { theme: Theme; + currentTheme: Theme; autoSendQueries: boolean; errors: string[]; warnings: string[]; @@ -16,7 +18,8 @@ export type ConfigStateI = { successes: string[]; }; export const initialState: ConfigStateI = { - theme: localStorage.getItem('theme') as Theme ?? Theme.light, + theme: localStorage.getItem('theme') as Theme ?? Theme.system, + currentTheme: resolveTheme(localStorage.getItem('theme') as Theme ?? Theme.system), autoSendQueries: true, errors: [], warnings: [], @@ -24,6 +27,14 @@ export const initialState: ConfigStateI = { successes: [], }; +function resolveTheme(theme: Theme) { + if (theme == Theme.system) { + const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; + return prefersDarkMode ? Theme.dark : Theme.light; + } + return theme +} + export const configSlice = createSlice({ name: 'config', // `createSlice` will infer the state type from the `initialState` argument @@ -58,11 +69,17 @@ export const configSlice = createSlice({ setTheme: (state, action: PayloadAction<Theme>) => { localStorage.setItem('theme', action.payload); state.theme = action.payload; + state.currentTheme = resolveTheme(action.payload); + }, + setCurrentTheme: (state, action: PayloadAction<boolean>) => { + if (state.theme === Theme.system) { + state.currentTheme = action.payload ? Theme.dark : Theme.light; + } }, }, }); -export const { addError, removeLastError, addWarning, removeLastWarning, addSuccess, removeLastSuccess, addInfo, removeLastInfo, setTheme } = +export const { addError, removeLastError, addWarning, removeLastWarning, addSuccess, removeLastSuccess, addInfo, removeLastInfo, setTheme, setCurrentTheme } = configSlice.actions; // Other code such as selectors can use the imported `RootState` type diff --git a/libs/shared/lib/vis/visualizations/nodelinkvis/components/NLPixi.tsx b/libs/shared/lib/vis/visualizations/nodelinkvis/components/NLPixi.tsx index 8dd8460eb2118fab9f1d53735a2bb0edff15dcfb..b8d262a2ec12d469e304dd320bf4510b6cb08754 100644 --- a/libs/shared/lib/vis/visualizations/nodelinkvis/components/NLPixi.tsx +++ b/libs/shared/lib/vis/visualizations/nodelinkvis/components/NLPixi.tsx @@ -43,7 +43,7 @@ export const NLPixi = forwardRef((props: Props, refExternal) => { useEffect(() => { update(); - }, [globalConfig.theme]); + }, [globalConfig.currentTheme]); const app = useMemo( () => @@ -318,7 +318,7 @@ export const NLPixi = forwardRef((props: Props, refExternal) => { getBackgroundColor() { // Colors corresponding to .bg-light class - return globalConfig.theme === Theme.dark ? 0x121621 : 0xffffff; + return globalConfig.currentTheme === Theme.dark ? 0x121621 : 0xffffff; }, }));