From 5fce2aad45ebafe906a8945fa1e7c9b08d43f769 Mon Sep 17 00:00:00 2001 From: "Mateus V. Farias" Date: Wed, 23 Mar 2022 19:28:42 -0400 Subject: [PATCH] feat: implement listener to detect dark mode in the browser --- @types/styled.d.ts | 4 +-- components/Layout/Header/index.tsx | 11 +++----- components/Logo/index.tsx | 6 ++--- components/ToggleTheme/index.tsx | 14 +++++------ contexts/index.tsx | 2 +- contexts/styles.tsx | 10 ++++---- contexts/styles/StylesContext.ts | 33 ------------------------ contexts/styles/StylesProvider.tsx | 27 -------------------- contexts/styles/types.ts | 7 ------ contexts/theme/ThemeContext.ts | 33 ++++++++++++++++++++++++ contexts/theme/ThemeProvider.tsx | 40 ++++++++++++++++++++++++++++++ contexts/theme/types.ts | 11 ++++++++ styles/theme/colors.ts | 11 ++++---- styles/theme/index.ts | 2 +- 14 files changed, 111 insertions(+), 100 deletions(-) delete mode 100644 contexts/styles/StylesContext.ts delete mode 100644 contexts/styles/StylesProvider.tsx delete mode 100644 contexts/styles/types.ts create mode 100644 contexts/theme/ThemeContext.ts create mode 100644 contexts/theme/ThemeProvider.tsx create mode 100644 contexts/theme/types.ts diff --git a/@types/styled.d.ts b/@types/styled.d.ts index cfaa652..29d1a6c 100644 --- a/@types/styled.d.ts +++ b/@types/styled.d.ts @@ -1,9 +1,9 @@ /* eslint @typescript-eslint/no-empty-interface: "off" */ import 'styled-components'; -import { dark, light } from 'styles/theme/colors'; +import { light as defaultTheme } from 'styles/theme/colors'; -export type Theme = typeof light & typeof dark; +export type Theme = typeof defaultTheme; declare module 'styled-components' { export interface DefaultTheme extends Theme {} diff --git a/components/Layout/Header/index.tsx b/components/Layout/Header/index.tsx index 356197d..e0f61eb 100644 --- a/components/Layout/Header/index.tsx +++ b/components/Layout/Header/index.tsx @@ -1,20 +1,17 @@ import { LocaleSwitcher } from 'components/LocaleSwitcher'; import { ToggleTheme } from 'components/ToggleTheme'; -import { - useStylesDispatch, - useStylesState, -} from 'contexts/styles/StylesContext'; +import { useThemeDispatch, useThemeState } from 'contexts/theme/ThemeContext'; import * as S from 'styles/components/Layout/Header'; export function Header() { - const { theme } = useStylesState(); - const { switchTheme } = useStylesDispatch(); + const { mode } = useThemeState(); + const { onSelectMode } = useThemeDispatch(); return ( - switchTheme(theme)} /> + onSelectMode(mode)} /> ); } diff --git a/components/Logo/index.tsx b/components/Logo/index.tsx index 5596ead..607cdca 100644 --- a/components/Logo/index.tsx +++ b/components/Logo/index.tsx @@ -1,13 +1,13 @@ import Image from 'next/image'; -import { useStylesState } from 'contexts/styles/StylesContext'; +import { useThemeState } from 'contexts/theme/ThemeContext'; import { useI18nState } from 'contexts/i18n/I18Context'; export function Logo() { - const { theme } = useStylesState(); + const { mode } = useThemeState(); const { t } = useI18nState(); - return theme === 'light' ? ( + return mode === 'light' ? ( {t.heading.meme_generator_description} ); } diff --git a/contexts/index.tsx b/contexts/index.tsx index 98b7786..bf35a8d 100644 --- a/contexts/index.tsx +++ b/contexts/index.tsx @@ -12,7 +12,7 @@ const DynamicStylesProvider = dynamic( ); const DynamicStylesContainer = dynamic( - () => import('./styles/StylesProvider').then(mod => mod.StylesContainer), + () => import('./theme/ThemeProvider').then(mod => mod.ThemeContainer), { ssr: false, }, diff --git a/contexts/styles.tsx b/contexts/styles.tsx index d8ad5b0..0c648b2 100644 --- a/contexts/styles.tsx +++ b/contexts/styles.tsx @@ -1,16 +1,16 @@ import { PropsWithChildren } from 'react'; import { ThemeProvider } from 'styled-components'; -import { colors } from 'styles/theme'; import GlobalStyle from 'styles/global'; - -import { useStylesState } from './styles/StylesContext'; +import { theme } from 'styles/theme'; +import { useThemeState } from './theme/ThemeContext'; export function StylesProvider({ children }: PropsWithChildren) { - const { theme } = useStylesState(); + const { mode } = useThemeState(); + const costumTheme = theme[mode]; return ( - + {children} diff --git a/contexts/styles/StylesContext.ts b/contexts/styles/StylesContext.ts deleted file mode 100644 index 3fb160b..0000000 --- a/contexts/styles/StylesContext.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { useContext, createContext } from 'react'; -import { StylesDispatchContextData, StylesStateContextData } from './types'; - -export const StylesStateContext = createContext< - StylesStateContextData | undefined ->(undefined); - -export const StylesDispatchContext = createContext< - StylesDispatchContextData | undefined ->(undefined); - -export const StylesStateProvider = StylesStateContext.Provider; -export const StylesDispatchProvider = StylesDispatchContext.Provider; - -export function useStylesState() { - const context = useContext(StylesStateContext); - - if (!context) { - throw new Error('useStylesState must be used within a StylesProvider'); - } - - return context; -} - -export function useStylesDispatch() { - const context = useContext(StylesDispatchContext); - - if (!context) { - throw new Error('useStylesDispatch must be used within StylesProvider'); - } - - return context; -} diff --git a/contexts/styles/StylesProvider.tsx b/contexts/styles/StylesProvider.tsx deleted file mode 100644 index 49bdbac..0000000 --- a/contexts/styles/StylesProvider.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { PropsWithChildren, useCallback, useMemo } from 'react'; - -import { THEME_STORAGE_KEY } from 'constants/localStorage'; -import { usePersistedState } from 'hooks/usePersistedState'; - -import { StylesDispatchProvider, StylesStateProvider } from './StylesContext'; - -export function StylesContainer({ children }: PropsWithChildren) { - const [theme, setTheme] = usePersistedState(THEME_STORAGE_KEY, 'light'); - - const switchTheme = useCallback( - theme => setTheme(theme === 'light' ? 'dark' : 'light'), - [], - ); - - const stylesState = useMemo(() => ({ theme }), [theme]); - - const stylesDispatch = useMemo(() => ({ switchTheme }), [switchTheme]); - - return ( - - - {children} - - - ); -} diff --git a/contexts/styles/types.ts b/contexts/styles/types.ts deleted file mode 100644 index 2be959e..0000000 --- a/contexts/styles/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type StylesStateContextData = { - theme: string; -}; - -export type StylesDispatchContextData = { - switchTheme: (theme: string) => void; -}; diff --git a/contexts/theme/ThemeContext.ts b/contexts/theme/ThemeContext.ts new file mode 100644 index 0000000..aded7ff --- /dev/null +++ b/contexts/theme/ThemeContext.ts @@ -0,0 +1,33 @@ +import { useContext, createContext } from 'react'; +import { ThemeDispatchContextData, ThemeStateContextData } from './types'; + +export const ThemeStateContext = createContext< + ThemeStateContextData | undefined +>(undefined); + +export const ThemeDispatchContext = createContext< + ThemeDispatchContextData | undefined +>(undefined); + +export const ThemeStateProvider = ThemeStateContext.Provider; +export const ThemeDispatchProvider = ThemeDispatchContext.Provider; + +export function useThemeState() { + const context = useContext(ThemeStateContext); + + if (!context) { + throw new Error('useThemeState must be used within a ThemeProvider'); + } + + return context; +} + +export function useThemeDispatch() { + const context = useContext(ThemeDispatchContext); + + if (!context) { + throw new Error('useThemeDispatch must be used within ThemeProvider'); + } + + return context; +} diff --git a/contexts/theme/ThemeProvider.tsx b/contexts/theme/ThemeProvider.tsx new file mode 100644 index 0000000..c915bc2 --- /dev/null +++ b/contexts/theme/ThemeProvider.tsx @@ -0,0 +1,40 @@ +import { PropsWithChildren, useCallback, useEffect, useMemo } from 'react'; + +import { THEME_STORAGE_KEY } from 'constants/localStorage'; +import { usePersistedState } from 'hooks/usePersistedState'; +import { ThemeDispatchProvider, ThemeStateProvider } from './ThemeContext'; +import { Theme } from './types'; + +export function ThemeContainer({ children }: PropsWithChildren) { + const [mode, setMode] = usePersistedState(THEME_STORAGE_KEY, 'light'); + + const onSelectMode = useCallback((mode: Theme) => { + setMode(mode === 'light' ? 'dark' : 'light'); + }, []); + + useEffect(() => { + window + .matchMedia('(prefers-color-scheme: light)') + .addEventListener('change', e => + onSelectMode(e.matches ? 'dark' : 'light'), + ); + + onSelectMode( + window.matchMedia('(prefers-color-scheme: light)').matches + ? 'dark' + : 'light', + ); + }, []); + + const themeState = useMemo(() => ({ mode }), [mode]); + + const themeDispatch = useMemo(() => ({ onSelectMode }), [onSelectMode]); + + return ( + + + {children} + + + ); +} diff --git a/contexts/theme/types.ts b/contexts/theme/types.ts new file mode 100644 index 0000000..c2df2df --- /dev/null +++ b/contexts/theme/types.ts @@ -0,0 +1,11 @@ +import { theme } from 'styles/theme'; + +export type Theme = keyof typeof theme; + +export type ThemeStateContextData = { + mode: Theme; +}; + +export type ThemeDispatchContextData = { + onSelectMode: (mode: Theme) => void; +}; diff --git a/styles/theme/colors.ts b/styles/theme/colors.ts index d7e8179..35f8fe4 100644 --- a/styles/theme/colors.ts +++ b/styles/theme/colors.ts @@ -9,6 +9,9 @@ export const light = { background: '#f5f6fA', }, text: '#ffffff', + orange: '#ff9000', + danger: '#c53030', + disabled: '#666360', loading: '#2e384d', button: { background: '#4395D8', @@ -16,7 +19,8 @@ export const light = { }, }; -export const dark = { +export const dark: typeof light = { + ...light, background: '#171717', modal: '#444444', alto: '#dbdbdb', @@ -26,10 +30,5 @@ export const dark = { borderColor: '#5a5a5a', background: '#404040', }, - text: '#ffffff', loading: '#ffffff', - button: { - background: '#4395D8', - color: '#ffffff', - }, }; diff --git a/styles/theme/index.ts b/styles/theme/index.ts index c434d26..1126b00 100644 --- a/styles/theme/index.ts +++ b/styles/theme/index.ts @@ -1,6 +1,6 @@ import { light, dark } from './colors'; -export const colors = { +export const theme = { light, dark, };