Skip to content

Commit 484e1a7

Browse files
committed
fix: changed theme component to provide better support for SSR
The Theme provider uses window and causes issues if SRR. This should now fail safe. And separate options are available to use the themes server side. fix #249
1 parent 5eb3f06 commit 484e1a7

File tree

3 files changed

+68
-14
lines changed

3 files changed

+68
-14
lines changed

src/components/ThemeProvider/ThemeController.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export const ThemeController: React.FC<ThemeControllerProps> = ({
7171
)
7272

7373
const setMode = (mode: ThemeChoice) => {
74-
window.localStorage.setItem('themeChoice', mode)
74+
window?.localStorage.setItem('themeChoice', mode)
7575
setThemeValues(mode)
7676
}
7777

@@ -97,13 +97,15 @@ export const ThemeController: React.FC<ThemeControllerProps> = ({
9797

9898
// paints the app before it renders elements
9999
useLayoutEffect(() => {
100-
const localTheme = window.localStorage.getItem('themeChoice') as ThemeChoice
100+
const localTheme = window?.localStorage.getItem(
101+
'themeChoice'
102+
) as ThemeChoice
101103

102104
if (localTheme) {
103105
setThemeValues(localTheme)
104106
} else if (
105-
window.matchMedia &&
106-
window.matchMedia('(prefers-color-scheme: dark)').matches
107+
window?.matchMedia &&
108+
window?.matchMedia('(prefers-color-scheme: dark)').matches
107109
) {
108110
setThemeValues('dark')
109111
} else {

src/components/ThemeSwitch/ThemeSwitch.stories.tsx

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import React from 'react'
22
import { Story, Meta } from '@storybook/react'
3-
import { ThemeSwitch } from '.'
3+
import { ControlledThemeSwitch, ThemeSwitch } from '.'
44
import { AppBar, AppBarActions, AppBarHeading } from '../AppBar'
55
import { Container } from '../Container'
66
import { Heading } from '../Heading'
77
import { Paper } from '../Paper'
8-
import { ThemeProvider } from '../ThemeProvider'
8+
import { ThemeProvider, useThemeController } from '../ThemeProvider'
99
import { lightTheme } from '../../stitches.config'
1010

1111
export default {
@@ -14,7 +14,7 @@ export default {
1414
} as Meta
1515

1616
export const Default: Story = (args) => (
17-
<ThemeProvider light={lightTheme}>
17+
<ThemeProvider local>
1818
<AppBar>
1919
<AppBarHeading>Example</AppBarHeading>
2020
<AppBarActions>
@@ -49,3 +49,29 @@ Default.parameters = {
4949
},
5050
},
5151
}
52+
53+
/**
54+
* A controllable version of the theme switch is provided which only has the UI.
55+
*
56+
* This an be used in situations where you need a different mechanism for theme switching such as Server Side Rendering.
57+
*
58+
* Here we just reuse the `useThemeController` hook as an example.
59+
*/
60+
export const SSR: Story = () => {
61+
const [choice, toggle] = useThemeController()
62+
return (
63+
<ThemeProvider local choice={choice}>
64+
<AppBar>
65+
<AppBarHeading>Example</AppBarHeading>
66+
<AppBarActions>
67+
<ControlledThemeSwitch choice={choice} toggle={toggle} />
68+
</AppBarActions>
69+
</AppBar>
70+
<Paper>
71+
<Container css={{ height: '200px' }}>
72+
<Heading variant="h4">Theme switch</Heading>
73+
</Container>
74+
</Paper>
75+
</ThemeProvider>
76+
)
77+
}

src/components/ThemeSwitch/ThemeSwitch.tsx

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { ComponentProps, ElementRef, forwardRef } from 'react'
22
import { IconButton } from '../IconButton'
33
import { DarkMode, LightMode } from '../Icons'
44
import { useThemeController } from '../ThemeProvider'
5+
import { ThemeChoice } from '../ThemeProvider/ThemeController'
56

67
type ThemeSwitchProps = ComponentProps<typeof IconButton> & {
78
/**
@@ -16,27 +17,31 @@ type ThemeSwitchProps = ComponentProps<typeof IconButton> & {
1617
darkColor?: string
1718
}
1819

20+
type ControlledThemeSwitchProps = ThemeSwitchProps & {
21+
choice: ThemeChoice
22+
toggle: () => void
23+
}
24+
1925
const THEME_SWITCH_CLASS_NAME = 'c-theme-switch'
26+
2027
/**
21-
* The theme switch component can be used to switch from light to dark mode explicitly.
22-
*
23-
* Use the underlying hook `useThemeController` to create your own theme switch.
28+
* This theme switch component is UI only and can be used to make a theme switch in a SSR setting.
2429
*/
25-
export const ThemeSwitch = forwardRef<
30+
export const ControlledThemeSwitch = forwardRef<
2631
ElementRef<typeof IconButton>,
27-
ThemeSwitchProps
32+
ControlledThemeSwitchProps
2833
>(
2934
(
3035
{
3136
lightColor = '$brandContrast',
3237
darkColor = '$brandContrast',
3338
className,
39+
choice,
40+
toggle,
3441
...props
3542
},
3643
forwardedRef
3744
) => {
38-
const [choice, toggle] = useThemeController()
39-
4045
const isLight = choice === 'light'
4146
const title = isLight ? 'Use dark theme' : 'Use light theme'
4247
const icon = isLight ? <LightMode /> : <DarkMode />
@@ -57,4 +62,25 @@ export const ThemeSwitch = forwardRef<
5762
)
5863
}
5964
)
65+
ControlledThemeSwitch.toString = () => `.${THEME_SWITCH_CLASS_NAME}`
66+
67+
/**
68+
* The theme switch component can be used to switch from light to dark mode explicitly.
69+
*
70+
* Use the underlying hook `useThemeController` to create your own theme switch.
71+
*/
72+
export const ThemeSwitch = forwardRef<
73+
ElementRef<typeof ControlledThemeSwitch>,
74+
ThemeSwitchProps
75+
>((props, forwardedRef) => {
76+
const [choice, toggle] = useThemeController()
77+
return (
78+
<ControlledThemeSwitch
79+
{...props}
80+
ref={forwardedRef}
81+
choice={choice}
82+
toggle={toggle}
83+
/>
84+
)
85+
})
6086
ThemeSwitch.toString = () => `.${THEME_SWITCH_CLASS_NAME}`

0 commit comments

Comments
 (0)