Skip to content

Commit

Permalink
Added root and scope props to ThemeProvider which allow ThemeProvider…
Browse files Browse the repository at this point in the history
… to attach theme selector to a specific root element and persist it under a specific scope name
  • Loading branch information
corbanbrook committed Nov 6, 2023
1 parent e2e8c14 commit 2aa0b81
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 9 deletions.
73 changes: 73 additions & 0 deletions src/components/ThemeProvider/ThemeProvider.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { Meta } from '@storybook/react'

import { Button } from '~/components/Button'

import { Box } from '../Box'
import { Card } from '../Card'
import { Collapsible } from '../Collapsible'
import { Text } from '../Text'

import { ThemeProvider, useTheme } from './ThemeProvider'

export default {
Expand All @@ -17,3 +22,71 @@ export const Default = () => {

return <Button label="Toggle theme" onClick={toggleTheme} />
}

export const Nested = () => {
return (
<Card>
<Text variant="normal" color="text100" fontWeight="bold">
Root Application
</Text>

<div id="app1">
<ThemeProvider root="#app1" scope="application1" theme="light">
<Card background="backgroundPrimary" marginTop="4">
<Collapsible label="Nested Application 1">
<Text variant="normal" color="text100">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</Text>

<Box marginTop="4">
<SetThemeButton />
</Box>

<div id="app2">
<ThemeProvider root="#app2" scope="application2" theme="dark">
<Card background="backgroundPrimary" marginTop="4">
<Collapsible label="Nested Application 2">
<Text variant="normal" color="text100">
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea
commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint occaecat cupidatat
non proident, sunt in culpa qui officia deserunt mollit
anim id est laborum.
</Text>

<Box marginTop="4">
<SetThemeButton />
</Box>
</Collapsible>
</Card>
</ThemeProvider>
</div>
</Collapsible>
</Card>
</ThemeProvider>
</div>
</Card>
)
}

const SetThemeButton = () => {
const { theme, setTheme } = useTheme()

const themeLabel = theme === 'light' ? 'Dark' : 'Light'

const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light')
}

return <Button label={`Set ${themeLabel} Mode`} onClick={toggleTheme} />
}
26 changes: 17 additions & 9 deletions src/components/ThemeProvider/ThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,22 @@ const DEFAULT_THEME = 'dark'
const THEME_ATTR = 'data-theme'
const STORAGE_KEY = '@sequence.theme'

const getStorageKey = (scope?: string) =>
scope ? `${STORAGE_KEY}.${scope}` : STORAGE_KEY

interface ThemeContextValue {
theme: Theme
setTheme: (mode: Theme) => void
}

interface ThemeProviderProps {
theme?: Theme
root?: string
scope?: string
}

const getTheme = (): Theme => {
const persistedTheme = localStorage.getItem(STORAGE_KEY) as Theme
const getTheme = (scope?: string): Theme => {
const persistedTheme = localStorage.getItem(getStorageKey(scope)) as Theme

if (THEMES.includes(persistedTheme)) {
return persistedTheme
Expand All @@ -43,7 +48,9 @@ const getTheme = (): Theme => {
const ThemeContext = createContext<ThemeContextValue | null>(null)

export const ThemeProvider = (props: PropsWithChildren<ThemeProviderProps>) => {
const [theme, setTheme] = useState<Theme>(props.theme || getTheme())
const [theme, setTheme] = useState<Theme>(
props.theme || getTheme(props.scope)
)

useEffect(() => {
// Add is-apple class
Expand All @@ -60,12 +67,13 @@ export const ThemeProvider = (props: PropsWithChildren<ThemeProviderProps>) => {

// Set the data-theme attribtute on the document root element
useEffect(() => {
const root = document.querySelector(':root')
const rootEl = document.querySelector(props.root || ':root')

if (root) {
root.setAttribute(THEME_ATTR, theme)
if (rootEl) {
console.log('Found', props.root || ':root')
rootEl.setAttribute(THEME_ATTR, theme)
}
}, [theme])
}, [theme, props.root])

// Create the context value
const value: ThemeContextValue = useMemo(() => {
Expand All @@ -74,14 +82,14 @@ export const ThemeProvider = (props: PropsWithChildren<ThemeProviderProps>) => {
setTheme: (mode: Theme) => {
if (THEMES.includes(mode)) {
// Save to local storage
localStorage.setItem(STORAGE_KEY, mode)
localStorage.setItem(getStorageKey(props.scope), mode)

// Set the theme state which will cause a re-render
setTheme(mode)
}
},
}
}, [theme])
}, [theme, props.scope])

return (
<ThemeContext.Provider value={value}>
Expand Down

0 comments on commit 2aa0b81

Please sign in to comment.