diff --git a/client/jest.setup.js b/client/jest.setup.js
index ccc7ab57a7..c075aa4eb8 100644
--- a/client/jest.setup.js
+++ b/client/jest.setup.js
@@ -5,3 +5,35 @@ import 'regenerator-runtime/runtime';
// See: https://github.com/testing-library/jest-dom
// eslint-disable-next-line import/no-extraneous-dependencies
import '@testing-library/jest-dom';
+
+// Mock matchMedia
+window.matchMedia = jest.fn().mockImplementation((query) => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: jest.fn(), // Deprecated
+ removeListener: jest.fn(), // Deprecated
+ addEventListener: jest.fn(),
+ removeEventListener: jest.fn(),
+ dispatchEvent: jest.fn()
+}));
+
+// Mock localStorage
+const localStorageMock = (function () {
+ let store = {};
+ return {
+ getItem: jest.fn((key) => store[key] || null),
+ setItem: jest.fn((key, value) => {
+ store[key] = value.toString();
+ }),
+ removeItem: jest.fn((key) => {
+ delete store[key];
+ }),
+ clear: jest.fn(() => {
+ store = {};
+ })
+ };
+})();
+Object.defineProperty(window, 'localStorage', {
+ value: localStorageMock
+});
diff --git a/client/modules/App/components/ThemeProvider.jsx b/client/modules/App/components/ThemeProvider.jsx
index 8bbef6931e..b6d450099b 100644
--- a/client/modules/App/components/ThemeProvider.jsx
+++ b/client/modules/App/components/ThemeProvider.jsx
@@ -1,11 +1,62 @@
-import React from 'react';
+import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
-import { useSelector } from 'react-redux';
+import { useSelector, useDispatch } from 'react-redux';
import { ThemeProvider } from 'styled-components';
-import theme from '../../../theme';
+import theme, { Theme } from '../../../theme';
+import { setTheme } from '../../IDE/actions/preferences';
const Provider = ({ children }) => {
const currentTheme = useSelector((state) => state.preferences.theme);
+ const dispatch = useDispatch();
+
+ // Detect system color scheme preference on initial load
+ useEffect(() => {
+ // Only apply system preference if the user hasn't explicitly set a theme
+ const userHasExplicitlySetTheme =
+ localStorage.getItem('has_set_theme') === 'true';
+ if (!userHasExplicitlySetTheme) {
+ const prefersDarkMode =
+ window.matchMedia &&
+ window.matchMedia('(prefers-color-scheme: dark)').matches;
+
+ if (prefersDarkMode) {
+ dispatch(setTheme(Theme.dark, { isSystemPreference: true }));
+ } else {
+ dispatch(setTheme(Theme.light, { isSystemPreference: true }));
+ }
+ }
+
+ // Listen for changes to system color scheme preference
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+
+ const handleChange = (e) => {
+ if (localStorage.getItem('has_set_theme') !== 'true') {
+ dispatch(
+ setTheme(e.matches ? Theme.dark : Theme.light, {
+ isSystemPreference: true
+ })
+ );
+ }
+ };
+
+ // Add event listener with modern API if available
+ if (mediaQuery.addEventListener) {
+ mediaQuery.addEventListener('change', handleChange);
+ } else {
+ // Fallback for older browsers
+ mediaQuery.addListener(handleChange);
+ }
+
+ // Clean up event listener
+ return () => {
+ if (mediaQuery.removeEventListener) {
+ mediaQuery.removeEventListener('change', handleChange);
+ } else {
+ mediaQuery.removeListener(handleChange);
+ }
+ };
+ }, [dispatch]);
+
return (