diff options
Diffstat (limited to 'src/utils/providers/prism-theme-provider')
3 files changed, 137 insertions, 0 deletions
diff --git a/src/utils/providers/prism-theme-provider/index.ts b/src/utils/providers/prism-theme-provider/index.ts new file mode 100644 index 0000000..fec2444 --- /dev/null +++ b/src/utils/providers/prism-theme-provider/index.ts @@ -0,0 +1 @@ +export * from './prism-theme-provider'; diff --git a/src/utils/providers/prism-theme-provider/prism-theme-provider.test.tsx b/src/utils/providers/prism-theme-provider/prism-theme-provider.test.tsx new file mode 100644 index 0000000..d0c2630 --- /dev/null +++ b/src/utils/providers/prism-theme-provider/prism-theme-provider.test.tsx @@ -0,0 +1,50 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '@testing-library/react'; +import { type FC, useContext } from 'react'; +import { PrismThemeContext, PrismThemeProvider } from './prism-theme-provider'; + +const bodyPrefix = 'Current Prism theme is:'; + +const ComponentTest: FC = () => { + const { theme } = useContext(PrismThemeContext); + + return ( + <div> + {bodyPrefix} {theme} + </div> + ); +}; + +describe('PrismThemeProvider', () => { + it('uses the default value when the provider is not used', () => { + const defaultValue = 'system'; + + render(<ComponentTest />); + + expect(rtlScreen.getByText(new RegExp(bodyPrefix))).toHaveTextContent( + `${bodyPrefix} ${defaultValue}` + ); + }); + + it('provides the given value to its children and set a matching attribute', () => { + const attribute = 'ratione'; + const theme = 'dark'; + + const { baseElement } = render( + <PrismThemeProvider + attribute={attribute} + defaultTheme={theme} + storageKey="qui" + > + <ComponentTest /> + </PrismThemeProvider> + ); + + expect(rtlScreen.getByText(new RegExp(bodyPrefix))).toHaveTextContent( + `${bodyPrefix} ${theme}` + ); + expect(baseElement.parentElement?.getAttribute(`data-${attribute}`)).toBe( + theme + ); + }); +}); diff --git a/src/utils/providers/prism-theme-provider/prism-theme-provider.tsx b/src/utils/providers/prism-theme-provider/prism-theme-provider.tsx new file mode 100644 index 0000000..0f0a4a1 --- /dev/null +++ b/src/utils/providers/prism-theme-provider/prism-theme-provider.tsx @@ -0,0 +1,86 @@ +import { + type Dispatch, + type FC, + type ReactNode, + type SetStateAction, + createContext, + useEffect, + useMemo, +} from 'react'; +import type { Theme } from '../../../types'; +import { + getDataAttributeFrom, + getThemeFromSystem, + themeValidator, +} from '../../helpers'; +import { useLocalStorage, useSystemColorScheme } from '../../hooks'; + +export type PrismThemeContextProps = { + attribute: string; + resolvedTheme: Exclude<Theme, 'system'>; + setTheme: Dispatch<SetStateAction<Theme>>; + theme: Theme; +}; + +export const PrismThemeContext = createContext<PrismThemeContextProps>({ + attribute: 'data-prism-theme', + resolvedTheme: getThemeFromSystem(), + setTheme: (value) => value, + theme: 'system', +}); + +export type PrismThemeProviderProps = { + /** + * The attribute name to append to document root. + */ + attribute: string; + /** + * The provider children. + */ + children?: ReactNode; + /** + * The default theme. + * + * @default 'system' + */ + defaultTheme?: Theme; + /** + * The key to use in local storage. + */ + storageKey: string; +}; + +export const PrismThemeProvider: FC<PrismThemeProviderProps> = ({ + attribute, + children, + defaultTheme = 'system', + storageKey, +}) => { + const [theme, setTheme] = useLocalStorage( + storageKey, + defaultTheme, + themeValidator + ); + const resolvedTheme = useSystemColorScheme(); + const dataAttribute = getDataAttributeFrom(attribute); + + useEffect(() => { + if (typeof window !== 'undefined') { + document.documentElement.setAttribute(dataAttribute, `${theme}`); + } + + return () => { + document.documentElement.removeAttribute(dataAttribute); + }; + }, [dataAttribute, theme]); + + const value = useMemo(() => { + return { attribute: dataAttribute, resolvedTheme, setTheme, theme }; + }, [dataAttribute, resolvedTheme, setTheme, theme]); + + return ( + <PrismThemeContext.Provider value={value}> + {children} + </PrismThemeContext.Provider> + ); +}; |
