Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions src/Header.messages.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ const messages = defineMessages({
defaultMessage: 'Schools & Partners',
description: 'Link to the schools and partners landing page',
},
'header.user.theme': {
id: 'header.user.theme',
defaultMessage: 'Toggle Theme',
description: 'Toggle between light and dark theme',
},
'header.user.menu.dashboard': {
id: 'header.user.menu.dashboard',
defaultMessage: 'Dashboard',
Expand Down
19 changes: 16 additions & 3 deletions src/ThemeToggleButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { getConfig } from '@edx/frontend-platform';
import Cookies from 'universal-cookie';
import { Icon } from '@openedx/paragon';
import { WbSunny, Nightlight } from '@openedx/paragon/icons';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './Header.messages';

const themeCookie = 'indigo-toggle-dark';
const themeCookieExpiry = 90; // days

const ThemeToggleButton = () => {
const ThemeToggleButton = ({ intl }) => {
const cookies = new Cookies();
const isThemeToggleEnabled = getConfig().INDIGO_ENABLE_DARK_TOGGLE;

Expand Down Expand Up @@ -75,6 +77,12 @@ const ThemeToggleButton = () => {
}
};

const hanldeKeyUp = (event) => {
if (event.key === 'Enter') {
onToggleTheme();
}
};

if (!isThemeToggleEnabled) {
return <div />;
}
Expand All @@ -84,7 +92,7 @@ const ThemeToggleButton = () => {
<div className="light-theme-icon"><Icon src={WbSunny} /></div>
<div className="toggle-switch">
<label htmlFor="theme-toggle-checkbox" className="switch">
<input id="theme-toggle-checkbox" defaultChecked={cookies.get(themeCookie) === 'dark'} onChange={onToggleTheme} type="checkbox" />
<input id="theme-toggle-checkbox" defaultChecked={cookies.get(themeCookie) === 'dark'} onChange={onToggleTheme} onKeyUp={hanldeKeyUp} type="checkbox" title={intl.formatMessage(messages['header.user.theme'])} />
<span className="slider round" />
</label>
</div>
Expand All @@ -93,4 +101,9 @@ const ThemeToggleButton = () => {
);
};

export default ThemeToggleButton;
ThemeToggleButton.propTypes = {
// i18n
intl: intlShape.isRequired,
};

export default injectIntl(ThemeToggleButton);
52 changes: 52 additions & 0 deletions src/ThemeToggleButton.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { render, fireEvent } from '@testing-library/react';
import { getConfig } from '@edx/frontend-platform';
import ThemeToggleButton from './ThemeToggleButton';

jest.mock('@edx/frontend-platform', () => ({
getConfig: jest.fn(),
}));

const mockCookiesGet = jest.fn();
const mockCookiesSet = jest.fn();
jest.mock('universal-cookie', () => jest.fn().mockImplementation(() => ({
get: mockCookiesGet, // Simulate initial light mode
set: mockCookiesSet,
})));

describe('ThemeToggleButton', () => {
beforeEach(() => {
getConfig.mockReturnValue({
LMS_BASE_URL: 'https://fake.url',
INDIGO_ENABLE_DARK_TOGGLE: true,
});

// Reset body class
document.body.classList.remove('indigo-dark-theme');
});

it('calls onToggleTheme when Enter key is pressed', () => {
mockCookiesGet.mockReturnValue('light');

const { container } = render(
<IntlProvider locale="en" messages={{}}>
<ThemeToggleButton />
</IntlProvider>,
);
const checkbox = container.querySelector('#theme-toggle-checkbox');
checkbox.focus();
fireEvent.keyUp(checkbox, { key: 'Enter' });

expect(mockCookiesSet).toHaveBeenCalledWith(
'indigo-toggle-dark',
'dark',
expect.objectContaining({
domain: 'fake.url',
path: '/',
expires: expect.any(Date),
}),
);
});
});