Skip to content

Commit

Permalink
Improve dark mode: refactor state management, add auto-detection.
Browse files Browse the repository at this point in the history
  • Loading branch information
mdevils committed Jun 3, 2023
1 parent 68d6892 commit f1fe7aa
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 35 deletions.
4 changes: 2 additions & 2 deletions src/components/header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ const HeaderSubLogo = styled.span`
opacity: 0.9;
`;

export function Header({toggleTheme}: {toggleTheme: () => void}) {
export function Header() {
return (
<HeaderWrapper>
<HeaderLogo>
<TsLogo />
TypeScript
<HeaderSubLogo>exercises</HeaderSubLogo>
</HeaderLogo>
<ThemeToggle toggleTheme={toggleTheme} />
<ThemeToggle />
</HeaderWrapper>
);
}
25 changes: 10 additions & 15 deletions src/components/theme-toggle/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import styled from '@emotion/styled';
import React from 'react';
import React, {useCallback} from 'react';
import {useAppTheme} from 'containers/app-theme-provider';
import {MoonLogo} from './icons/MoonLogo';
import {SunLogo} from './icons/SunLogo';

Expand All @@ -10,13 +11,11 @@ interface ToggleProps {
const Toggle = styled.div<ToggleProps>`
width: 50px;
max-height: 30px;
display: flex;
padding: 5px;
background-color: #1a202c;
display: block;
border-radius: 1000px;
cursor: pointer;
box-shadow: 0px 5px 20px -10px #000000;
box-shadow: 0 5px 20px -10px #000000;
transition: background-color 0.2s ease-in;
background-color: #ddd;
Expand All @@ -25,26 +24,22 @@ const Toggle = styled.div<ToggleProps>`
align-items: center;
width: 20px;
height: 20px;
background-color: white;
border-radius: 1000px;
transition: margin-left 0.2s ease-in, background-color 0.2s ease-in;
margin-left: ${(props) => (props.isDark ? '30px' : '0')};
background-color: #294e80;
}
`;

export function ThemeToggle({toggleTheme}: {toggleTheme: () => void}) {
const [darkMode, setDarkMode] = React.useState(localStorage.getItem('theme') === 'dark');

const click = () => {
toggleTheme();
setDarkMode((current) => !current);
localStorage.setItem('theme', darkMode ? 'light' : 'dark');
};
export function ThemeToggle() {
const {theme, setTheme} = useAppTheme();
const toggleTheme = useCallback(() => {
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
}, [setTheme]);

return (
<Toggle isDark={darkMode} id='toggle' onClick={() => click()}>
<div className='toggle-inner'>{darkMode ? <SunLogo /> : <MoonLogo />} </div>
<Toggle isDark={theme === 'dark'} id='toggle' onClick={toggleTheme}>
<div className='toggle-inner'>{theme === 'dark' ? <SunLogo /> : <MoonLogo />} </div>
</Toggle>
);
}
50 changes: 50 additions & 0 deletions src/containers/app-theme-provider/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {ThemeProvider} from '@emotion/react';
import React, {createContext, ReactNode, useCallback, useContext, useMemo, useState} from 'react';
import {darkTheme, lightTheme} from 'styles/themes';

export type AppTheme = 'light' | 'dark';

interface AppThemeContextValue {
theme: AppTheme;
setTheme(theme: AppTheme | ((prev: AppTheme) => AppTheme)): void;
}

const AppThemeContext = createContext<AppThemeContextValue>({
theme: 'light',
setTheme: () => null
});

export function AppThemeProvider({children}: {children: ReactNode}) {
const [theme, setTheme] = useState<AppTheme>(() => {
if (localStorage.getItem('theme')) {
return localStorage.getItem('theme') === 'light' ? 'light' : 'dark';
}
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
});
const setThemeAndUpdateLocalStorage = useCallback(
(value: AppTheme | ((prev: AppTheme) => AppTheme)) => {
setTheme((prev) => {
const newValue = typeof value === 'function' ? value(prev) : value;
localStorage.setItem('theme', newValue);
return newValue;
});
},
[setTheme]
);
const value = useMemo(
() => ({theme, setTheme: setThemeAndUpdateLocalStorage}),
[theme, setThemeAndUpdateLocalStorage]
);
return (
<AppThemeContext.Provider value={value}>
<ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>{children}</ThemeProvider>
</AppThemeContext.Provider>
);
}

export function useAppTheme(): AppThemeContextValue {
return useContext(AppThemeContext);
}
21 changes: 6 additions & 15 deletions src/containers/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {Global, css} from '@emotion/core';
import {ThemeProvider} from '@emotion/react';
import React, {useState} from 'react';
import {darkTheme, lightTheme} from 'styles/themes';
import React from 'react';
import {load} from 'components/loading-container';
import {useAppTheme} from 'containers/app-theme-provider';
import {Exercise} from 'containers/exercise';
import {PageLayout} from 'containers/page-layout';
import {urlParams} from 'observables/url-params';
Expand Down Expand Up @@ -39,24 +38,16 @@ const globalStylesDark = css`
`;

export function App() {
const [theme, setTheme] = useState(localStorage.getItem('theme') || 'light');

const toggleTheme = () => {
if (theme === 'light') {
setTheme('dark');
} else {
setTheme('light');
}
};
const {theme} = useAppTheme();

return (
<ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>
<>
<Global styles={theme === 'light' ? globalStylesLight : globalStylesDark} />
<PageLayout toggleTheme={toggleTheme}>
<PageLayout>
{load(urlParams.observable$, (params) => (
<Exercise key={params.exercise} exerciseNumber={Number(params.exercise)} />
))}
</PageLayout>
</ThemeProvider>
</>
);
}
4 changes: 2 additions & 2 deletions src/containers/page-layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ const PageLayoutMain = styled.main`
position: relative;
`;

export function PageLayout({children, toggleTheme}: {children: React.ReactNode; toggleTheme: () => void}) {
export function PageLayout({children}: {children: React.ReactNode}) {
return (
<PageLayoutWrapper>
<Header toggleTheme={toggleTheme} />
<Header />
<Navigation />
<PageLayoutMain>{children}</PageLayoutMain>
<Footer />
Expand Down
5 changes: 4 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom';
import {App} from 'containers/app';
import {AppThemeProvider} from './containers/app-theme-provider';

ReactDOM.render(
<React.StrictMode>
<App />
<AppThemeProvider>
<App />
</AppThemeProvider>
</React.StrictMode>,
document.getElementById('root')
);

0 comments on commit f1fe7aa

Please sign in to comment.