Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added accesibility for default system theme to p5.js-web-editor #3371

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Added accesibility for default system theme to p5.js-web-editor
  • Loading branch information
takshittt committed Mar 6, 2025
commit f25dfcf00ed17235e185607a2a4595c1334d67c4
32 changes: 32 additions & 0 deletions client/jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
57 changes: 54 additions & 3 deletions client/modules/App/components/ThemeProvider.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<ThemeProvider theme={{ ...theme[currentTheme] }}>{children}</ThemeProvider>
);
Expand Down
9 changes: 8 additions & 1 deletion client/modules/IDE/actions/preferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export function setGridOutput(value) {
};
}

export function setTheme(value) {
export function setTheme(value, { isSystemPreference = false } = {}) {
// return {
// type: ActionTypes.SET_THEME,
// value
Expand All @@ -187,6 +187,13 @@ export function setTheme(value) {
type: ActionTypes.SET_THEME,
value
});

// If this is a user-initiated theme change (not from system preference),
// mark that the user has explicitly set a theme
if (!isSystemPreference) {
localStorage.setItem('has_set_theme', 'true');
}

const state = getState();
if (state.user.authenticated) {
const formParams = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,21 @@ describe('<Preferences />', () => {
};

describe('testing theme switching', () => {
beforeEach(() => {
// Mock localStorage for theme tests
Object.defineProperty(window, 'localStorage', {
value: {
getItem: jest.fn().mockImplementation((key) => {
if (key === 'has_set_theme') return 'true';
return null;
}),
setItem: jest.fn(),
removeItem: jest.fn()
},
writable: true
});
});

describe('dark mode', () => {
it('switch to light', () => {
subject({ theme: 'dark' });
Expand Down
38 changes: 35 additions & 3 deletions client/modules/IDE/components/Preferences/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,29 @@ export default function Preferences() {
<div className="preference">
<h4 className="preference__title">{t('Preferences.Theme')}</h4>
<div className="preference__options">
<input
type="radio"
onChange={() => {
localStorage.removeItem('has_set_theme');
const prefersDarkMode =
window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches;
dispatch(
setTheme(prefersDarkMode ? 'dark' : 'light', {
isSystemPreference: true
})
);
}}
aria-label={t('Preferences.SystemThemeARIA')}
name="system theme"
id="system-theme-on"
className="preference__radio-button"
value="system"
checked={localStorage.getItem('has_set_theme') !== 'true'}
/>
<label htmlFor="system-theme-on" className="preference__option">
{t('Preferences.SystemTheme')}
</label>
<input
type="radio"
onChange={() => dispatch(setTheme('light'))}
Expand All @@ -110,7 +133,10 @@ export default function Preferences() {
id="light-theme-on"
className="preference__radio-button"
value="light"
checked={theme === 'light'}
checked={
theme === 'light' &&
localStorage.getItem('has_set_theme') === 'true'
}
/>
<label htmlFor="light-theme-on" className="preference__option">
{t('Preferences.LightTheme')}
Expand All @@ -123,7 +149,10 @@ export default function Preferences() {
id="dark-theme-on"
className="preference__radio-button"
value="dark"
checked={theme === 'dark'}
checked={
theme === 'dark' &&
localStorage.getItem('has_set_theme') === 'true'
}
/>
<label htmlFor="dark-theme-on" className="preference__option">
{t('Preferences.DarkTheme')}
Expand All @@ -136,7 +165,10 @@ export default function Preferences() {
id="high-contrast-theme-on"
className="preference__radio-button"
value="contrast"
checked={theme === 'contrast'}
checked={
theme === 'contrast' &&
localStorage.getItem('has_set_theme') === 'true'
}
/>
<label
htmlFor="high-contrast-theme-on"
Expand Down
4 changes: 3 additions & 1 deletion translations/locales/en-US/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@
"GeneralSettings": "General settings",
"Accessibility": "Accessibility",
"Theme": "Theme",
"SystemTheme": "System Preference",
"SystemThemeARIA": "use system theme preference",
"LightTheme": "Light",
"LightThemeARIA": "light theme on",
"DarkTheme": "Dark",
Expand Down Expand Up @@ -362,7 +364,7 @@
"CreateTokenSubmit": "Create",
"NoTokens": "You have no existing tokens.",
"NewTokenTitle": "Your new access token",
"NewTokenInfo": "Make sure to copy your new personal access token now.\n You wont be able to see it again!",
"NewTokenInfo": "Make sure to copy your new personal access token now.\n You won't be able to see it again!",
"ExistingTokensTitle": "Existing tokens"
},
"APIKeyList": {
Expand Down