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

Implement dark and light theme toggling with React context #1385

Draft
wants to merge 2 commits into
base: development
Choose a base branch
from
Draft
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
2 changes: 2 additions & 0 deletions src/client/app/components/HeaderButtonsComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { selectHasRolePermissions, selectIsAdmin, selectIsLoggedIn } from '../re
import { UserRole } from '../types/items';
import { useTranslate } from '../redux/componentHooks';
import LanguageSelectorComponent from './LanguageSelectorComponent';
import ThemeSelectorComponent from './ThemeSelectorComponent';
import TooltipMarkerComponent from './TooltipMarkerComponent';
import LoginComponent from './LoginComponent';

Expand Down Expand Up @@ -255,6 +256,7 @@ export default function HeaderButtonsComponent() {
</DropdownToggle>
<DropdownMenu>
<LanguageSelectorComponent />
<ThemeSelectorComponent />
<DropdownItem
style={state.showOptionsStyle}
className='d-none d-lg-block'
Expand Down
49 changes: 49 additions & 0 deletions src/client/app/components/ThemeSelectorComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap';
// import { selectBaseHelpUrl } from '../redux/slices/adminSlice';
import { useContext } from 'react';
import { ThemeContext } from '../../../context/themeContext';

/**
* A component that allows users to select which theme (light or dark mode) the page should be displayed in.
* @returns Theme selector component (used in the header navbar)
*/
export default function ThemeSelectorComponent() {
const {theme, toggleTheme } = useContext(ThemeContext);

// const version = useAppSelector(selectOEDVersion);
// const baseHelpUrl = useAppSelector(selectBaseHelpUrl);
// const helpUrl = baseHelpUrl + version;

return (
<>
<UncontrolledDropdown direction='start'>
<DropdownToggle nav caret>
<FormattedMessage id='theme' />
</DropdownToggle>
<DropdownMenu>
<DropdownItem
onClick={() => toggleTheme('light')}
disabled={theme === 'light'}>
Light Mode
</DropdownItem>
<DropdownItem
onClick={() => toggleTheme('dark')}
disabled={theme === 'dark'}>
Dark Mode
</DropdownItem>
<DropdownItem divider />
{/* <DropdownItem
href={helpUrl + '/language.html'}>
<FormattedMessage id="help" />
</DropdownItem> */}
</DropdownMenu>
</UncontrolledDropdown>
</>
);
}
12 changes: 8 additions & 4 deletions src/client/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './store';
import { ThemeProvider } from '../../context/themeContext';
import RouteComponent from './components/RouteComponent';
import { initApp } from './redux/slices/appStateSlice';
import './styles/index.css';
Expand All @@ -18,8 +19,11 @@ const container = document.getElementById('root') as HTMLElement;
const root = createRoot(container);

root.render(
// Provides the Redux store to all child components
< Provider store={store} stabilityCheck='always' >
< RouteComponent />
</Provider >
<ThemeProvider>
{/* Provides the Redux store to all child components */}
< Provider store={store} stabilityCheck='always' >
< RouteComponent />
</Provider >
</ThemeProvider>

);
1 change: 1 addition & 0 deletions src/client/app/translations/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ const LocaleTranslationData = {
"submit.changes": "Submit changes",
"submit.new.user": "Submit new user",
"the.unit.of.meter": "The unit of meter",
"theme": "Theme",
"this.four.weeks": "These four weeks",
"timezone.no": "No timezone",
"this.week": "This week",
Expand Down
47 changes: 47 additions & 0 deletions src/context/themeContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import * as React from 'react';
import { createContext, useState, useEffect } from 'react';

export type ThemeContextContent = {
theme: string,
toggleTheme: Function
}

//ThemeContext is what's passed around in the application (it holds the set theme and a way to toggle the theme)
export const ThemeContext = createContext<ThemeContextContent>({ theme : 'light', toggleTheme : () => {} });

//ThemeProvider uses React's useState() to get/set the theme values (using local storage) and uses React's useEffect()
//to watch for when the selected theme changes so that it can update the theme on the DOM.
export const ThemeProvider = ({children} : any) => {
const [theme, setTheme] = useState('light');

//Runs when the app first loads to get the user's theme (if it has been set already)
useEffect(() => {
const storedTheme = localStorage.getItem('theme');
if (storedTheme) {
setTheme(storedTheme);
}
}, []);

//Runs each time the theme changes, updates the saved theme in local storage and updates the theme styling for the DOM
useEffect(() => {
localStorage.setItem('theme', theme);
document.documentElement.setAttribute('data-bs-theme', theme);
}, [theme]);

//The function made available to other components; this updates the saved theme depending on what the user has selected
const toggleTheme = (theme: string) => {
setTheme(theme);
}

return (
<div>
<ThemeContext.Provider value={{theme, toggleTheme}}>
{children}
</ThemeContext.Provider>
</div>
)
}
Loading