diff --git a/src/constants/storage.ts b/src/constants/storage.ts
index 9138ae194f..9d1b44f35f 100644
--- a/src/constants/storage.ts
+++ b/src/constants/storage.ts
@@ -2,6 +2,7 @@ export const CLIENT_STORAGE_PREFIX = "scrumlr/";
export const APP_VERSION_STORAGE_KEY = `${CLIENT_STORAGE_PREFIX}app_version`;
export const COOKIE_CONSENT_STORAGE_KEY = `${CLIENT_STORAGE_PREFIX}cookie_consent`;
export const LOCALE_STORAGE_KEY = `${CLIENT_STORAGE_PREFIX}locale`;
+export const THEME_STORAGE_KEY = `${CLIENT_STORAGE_PREFIX}theme`;
export const CUSTOM_TIMER_STORAGE_KEY = `${CLIENT_STORAGE_PREFIX}custom_timer`;
export const CUSTOM_NUMBER_OF_VOTES_STORAGE_KEY = `${CLIENT_STORAGE_PREFIX}custom_number_of_votes`;
export const CUMULATIVE_VOTING_DEFAULT_STORAGE_KEY = `${CLIENT_STORAGE_PREFIX}cumulative_voting_default`;
diff --git a/src/store/action/view.ts b/src/store/action/view.ts
index 1898969555..fa59851e0e 100644
--- a/src/store/action/view.ts
+++ b/src/store/action/view.ts
@@ -1,7 +1,10 @@
+import {Theme} from "types/view";
+
export const ViewAction = {
InitApplication: "scrumlr.io/initApplication" as const,
SetModerating: "scrumlr.io/setModerating" as const,
SetLanguage: "scrumlr.io/setLanguage" as const,
+ SetTheme: "scrumlr.io/setTheme" as const,
SetServerInfo: "scrumlr.io/setServerInfo" as const,
SetRoute: "scrumlr.io/setRoute" as const,
SetHotkeyState: "scrumlr.io/setHotkeyState" as const,
@@ -25,6 +28,11 @@ export const ViewActionFactory = {
language,
}),
+ setTheme: (theme: Theme) => ({
+ type: ViewAction.SetTheme,
+ theme,
+ }),
+
setServerInfo: (enabledAuthProvider: string[], serverTime: number, feedbackEnabled: boolean) => ({
type: ViewAction.SetServerInfo,
enabledAuthProvider,
@@ -60,6 +68,7 @@ export type ViewReduxAction =
| ReturnType
| ReturnType
| ReturnType
+ | ReturnType
| ReturnType
| ReturnType
| ReturnType
diff --git a/src/store/middleware/view.tsx b/src/store/middleware/view.tsx
index 35cf8f53f0..fa694cc889 100644
--- a/src/store/middleware/view.tsx
+++ b/src/store/middleware/view.tsx
@@ -5,7 +5,7 @@ import {API} from "api";
import i18n from "i18n";
import {Toast} from "utils/Toast";
import {saveToStorage} from "utils/storage";
-import {BOARD_REACTIONS_ENABLE_STORAGE_KEY, HOTKEY_NOTIFICATIONS_ENABLE_STORAGE_KEY} from "constants/storage";
+import {BOARD_REACTIONS_ENABLE_STORAGE_KEY, HOTKEY_NOTIFICATIONS_ENABLE_STORAGE_KEY, THEME_STORAGE_KEY} from "constants/storage";
import store from "../index";
export const passViewMiddleware = (stateAPI: MiddlewareAPI, dispatch: Dispatch, action: ReduxAction) => {
@@ -26,6 +26,12 @@ export const passViewMiddleware = (stateAPI: MiddlewareAPI;
+
export interface View {
readonly moderating: boolean;
@@ -13,6 +20,8 @@ export interface View {
readonly language?: string;
+ readonly theme: Theme;
+
readonly route?: string;
readonly noteFocused: boolean;
diff --git a/src/utils/hooks/useAutoTheme.ts b/src/utils/hooks/useAutoTheme.ts
new file mode 100644
index 0000000000..89f9ef6e7d
--- /dev/null
+++ b/src/utils/hooks/useAutoTheme.ts
@@ -0,0 +1,38 @@
+import {useState, useEffect, useCallback} from "react";
+import {Theme, AutoTheme} from "types/view";
+
+/** this hook return the theme that should be used, regarding the current system preferences. */
+export const useAutoTheme = (theme: Theme): AutoTheme => {
+ const getInitialAutoTheme = useCallback((): AutoTheme => {
+ if (theme === "auto") {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
+ }
+ return theme;
+ }, [theme]);
+
+ const [autoTheme, setAutoTheme] = useState(getInitialAutoTheme());
+
+ // update the theme. if it's set to auto, use the system preference, otherwise use the value.
+ useEffect(() => {
+ const handleColorSchemeChange = (e: MediaQueryListEvent) => {
+ const colorSchemePreference: AutoTheme = e.matches ? "dark" : "light";
+
+ if (theme === "auto") {
+ setAutoTheme(colorSchemePreference);
+ }
+ };
+
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
+ mediaQuery.addEventListener("change", handleColorSchemeChange);
+
+ // update the theme based on the initial value and theme changes
+ setAutoTheme(getInitialAutoTheme());
+
+ return () => {
+ // cleanup
+ mediaQuery.removeEventListener("change", handleColorSchemeChange);
+ };
+ }, [getInitialAutoTheme, theme]);
+
+ return autoTheme;
+};
diff --git a/src/utils/test/getTestApplicationState.ts b/src/utils/test/getTestApplicationState.ts
index 3e9523a8a8..c597fa34d0 100644
--- a/src/utils/test/getTestApplicationState.ts
+++ b/src/utils/test/getTestApplicationState.ts
@@ -172,6 +172,7 @@ export default (overwrite?: Partial): ApplicationState => ({
],
},
view: {
+ theme: "auto",
hotkeyNotificationsEnabled: true,
moderating: false,
serverTimeOffset: 0,