From e29287789b162af93d74615f4d7c51da766d68fc Mon Sep 17 00:00:00 2001 From: Matthew Costabile Date: Thu, 2 Dec 2021 16:32:28 -0500 Subject: [PATCH] adds resolved color scheme to theme context (#1679) * Add `resolvedColorScheme` property to the object returned by `useTheme() * Add `resolvedColorScheme` property to the object returned by `useTheme() * Update .changeset/red-bottles-prove.md Co-authored-by: Cole Bemis --- .changeset/red-bottles-prove.md | 5 ++ src/ThemeProvider.tsx | 27 +++++-- src/__tests__/ThemeProvider.test.tsx | 116 +++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 .changeset/red-bottles-prove.md diff --git a/.changeset/red-bottles-prove.md b/.changeset/red-bottles-prove.md new file mode 100644 index 00000000000..cdced65459f --- /dev/null +++ b/.changeset/red-bottles-prove.md @@ -0,0 +1,5 @@ +--- +'@primer/components': minor +--- + +Add `resolvedColorScheme` property to the object returned by `useTheme()` diff --git a/src/ThemeProvider.tsx b/src/ThemeProvider.tsx index ff59a3ea22a..1c314ff4052 100644 --- a/src/ThemeProvider.tsx +++ b/src/ThemeProvider.tsx @@ -24,6 +24,7 @@ const ThemeContext = React.createContext<{ colorScheme?: string colorMode?: ColorModeWithAuto resolvedColorMode?: ColorMode + resolvedColorScheme?: string dayScheme?: string nightScheme?: string setColorMode: React.Dispatch> @@ -52,7 +53,10 @@ export const ThemeProvider: React.FC = ({children, ...props} const systemColorMode = useSystemColorMode() const resolvedColorMode = resolveColorMode(colorMode, systemColorMode) const colorScheme = chooseColorScheme(resolvedColorMode, dayScheme, nightScheme) - const resolvedTheme = React.useMemo(() => applyColorScheme(theme, colorScheme), [theme, colorScheme]) + const {resolvedTheme, resolvedColorScheme} = React.useMemo( + () => applyColorScheme(theme, colorScheme), + [theme, colorScheme] + ) // Update state if props change React.useEffect(() => { @@ -74,6 +78,7 @@ export const ThemeProvider: React.FC = ({children, ...props} colorScheme, colorMode, resolvedColorMode, + resolvedColorScheme, dayScheme, nightScheme, setColorMode, @@ -156,9 +161,15 @@ function chooseColorScheme(colorMode: ColorMode, dayScheme: string, nightScheme: } } -function applyColorScheme(theme: Theme, colorScheme: string) { +function applyColorScheme( + theme: Theme, + colorScheme: string +): {resolvedTheme: Theme; resolvedColorScheme: string | undefined} { if (!theme.colorSchemes) { - return theme + return { + resolvedTheme: theme, + resolvedColorScheme: undefined + } } if (!theme.colorSchemes[colorScheme]) { @@ -167,10 +178,16 @@ function applyColorScheme(theme: Theme, colorScheme: string) { // Apply the first defined color scheme const defaultColorScheme = Object.keys(theme.colorSchemes)[0] - return deepmerge(theme, theme.colorSchemes[defaultColorScheme]) + return { + resolvedTheme: deepmerge(theme, theme.colorSchemes[defaultColorScheme]), + resolvedColorScheme: defaultColorScheme + } } - return deepmerge(theme, theme.colorSchemes[colorScheme]) + return { + resolvedTheme: deepmerge(theme, theme.colorSchemes[colorScheme]), + resolvedColorScheme: colorScheme + } } export default ThemeProvider diff --git a/src/__tests__/ThemeProvider.test.tsx b/src/__tests__/ThemeProvider.test.tsx index e60d13d70bf..ccce0126f70 100644 --- a/src/__tests__/ThemeProvider.test.tsx +++ b/src/__tests__/ThemeProvider.test.tsx @@ -439,3 +439,119 @@ describe('useColorSchemeVar', () => { expect(screen.getByText('Hello')).toHaveStyleRule('background-color', 'blue') }) }) + +describe('useTheme().resolvedColorScheme', () => { + it('is undefined when not in a theme', () => { + const Component = () => { + const {resolvedColorScheme} = useTheme() + + return {resolvedColorScheme} + } + + render() + + expect(screen.getByTestId('text').textContent).toEqual('') + }) + + it('is undefined when the theme has no colorScheme object', () => { + const Component = () => { + const {resolvedColorScheme} = useTheme() + + return {resolvedColorScheme} + } + + render( + + + + ) + + expect(screen.getByTestId('text').textContent).toEqual('') + }) + + it('is the same as the applied colorScheme, when that colorScheme is in the theme', () => { + const Component = () => { + const {resolvedColorScheme} = useTheme() + + return {resolvedColorScheme} + } + + const schemeToApply = 'dark' + + render( + + + + ) + + expect(exampleTheme.colorSchemes).toHaveProperty(schemeToApply) + expect(screen.getByTestId('text').textContent).toEqual(schemeToApply) + }) + + it('is the value of the fallback colorScheme applied when attempting to apply an invalid colorScheme', () => { + const Component = () => { + const {resolvedColorScheme} = useTheme() + + return {resolvedColorScheme} + } + + const schemeToApply = 'totally-invalid-colorscheme' + render( + + + + ) + + const defaultThemeColorScheme = Object.keys(exampleTheme.colorSchemes)[0] + + expect(defaultThemeColorScheme).not.toEqual(schemeToApply) + expect(exampleTheme.colorSchemes).not.toHaveProperty(schemeToApply) + expect(screen.getByTestId('text').textContent).toEqual('light') + }) + + describe('nested theme', () => { + it('is the same as the applied colorScheme, when that colorScheme is in the theme', () => { + const Component = () => { + const {resolvedColorScheme} = useTheme() + + return {resolvedColorScheme} + } + + const schemeToApply = 'dark' + + render( + + + + + + ) + + expect(exampleTheme.colorSchemes).toHaveProperty(schemeToApply) + expect(screen.getByTestId('text').textContent).toEqual(schemeToApply) + }) + + it('is the value of the fallback colorScheme applied when attempting to apply an invalid colorScheme', () => { + const Component = () => { + const {resolvedColorScheme} = useTheme() + + return {resolvedColorScheme} + } + + const schemeToApply = 'totally-invalid-colorscheme' + render( + + + + + + ) + + const defaultThemeColorScheme = Object.keys(exampleTheme.colorSchemes)[0] + + expect(defaultThemeColorScheme).not.toEqual(schemeToApply) + expect(exampleTheme.colorSchemes).not.toHaveProperty(schemeToApply) + expect(screen.getByTestId('text').textContent).toEqual('light') + }) + }) +})