From c735c879b3cbf7adab112ef0f38946b519be6a2a Mon Sep 17 00:00:00 2001 From: Josh Black Date: Thu, 18 Sep 2025 16:58:05 -0500 Subject: [PATCH] refactor: add styled variant of ThemeProvider to styled-react --- package-lock.json | 4 +- packages/react/package.json | 2 - packages/react/script/build | 3 - .../ThemeProvider.test.tsx | 111 ++------ .../src/{ => ThemeProvider}/ThemeProvider.tsx | 75 +---- packages/react/src/ThemeProvider/index.ts | 0 .../useColorSchemeVar.hookDocs.json | 0 packages/styled-react/package.json | 5 + packages/styled-react/script/build | 3 + .../script/precompile-color-schemes.ts | 2 +- .../ThemeProvider.browser.test.tsx | 259 ++++++++++++++++++ .../ThemeProvider/ThemeProvider.tsx | 106 +++++++ .../src/components/ThemeProvider/index.ts | 1 + packages/styled-react/src/index.tsx | 3 +- .../src/legacy-theme/README.md | 0 .../src/legacy-theme/ts/color-schemes.ts | 0 .../src/legacy-theme/ts/colors/dark.ts | 0 .../legacy-theme/ts/colors/dark_colorblind.ts | 0 .../src/legacy-theme/ts/colors/dark_dimmed.ts | 0 .../ts/colors/dark_high_contrast.ts | 0 .../legacy-theme/ts/colors/dark_tritanopia.ts | 0 .../src/legacy-theme/ts/colors/index.ts | 0 .../src/legacy-theme/ts/colors/light.ts | 0 .../ts/colors/light_colorblind.ts | 0 .../ts/colors/light_high_contrast.ts | 0 .../ts/colors/light_tritanopia.ts | 0 .../src/legacy-theme/ts/index.ts | 0 packages/{react => styled-react}/src/theme.ts | 11 +- .../styled-react/vitest.config.browser.ts | 17 ++ 29 files changed, 443 insertions(+), 159 deletions(-) rename packages/react/src/{__tests__ => ThemeProvider}/ThemeProvider.test.tsx (83%) rename packages/react/src/{ => ThemeProvider}/ThemeProvider.tsx (75%) create mode 100644 packages/react/src/ThemeProvider/index.ts rename packages/react/src/{ => ThemeProvider}/useColorSchemeVar.hookDocs.json (100%) rename packages/{react => styled-react}/script/precompile-color-schemes.ts (93%) create mode 100644 packages/styled-react/src/components/ThemeProvider/ThemeProvider.browser.test.tsx create mode 100644 packages/styled-react/src/components/ThemeProvider/ThemeProvider.tsx create mode 100644 packages/styled-react/src/components/ThemeProvider/index.ts rename packages/{react => styled-react}/src/legacy-theme/README.md (100%) rename packages/{react => styled-react}/src/legacy-theme/ts/color-schemes.ts (100%) rename packages/{react => styled-react}/src/legacy-theme/ts/colors/dark.ts (100%) rename packages/{react => styled-react}/src/legacy-theme/ts/colors/dark_colorblind.ts (100%) rename packages/{react => styled-react}/src/legacy-theme/ts/colors/dark_dimmed.ts (100%) rename packages/{react => styled-react}/src/legacy-theme/ts/colors/dark_high_contrast.ts (100%) rename packages/{react => styled-react}/src/legacy-theme/ts/colors/dark_tritanopia.ts (100%) rename packages/{react => styled-react}/src/legacy-theme/ts/colors/index.ts (100%) rename packages/{react => styled-react}/src/legacy-theme/ts/colors/light.ts (100%) rename packages/{react => styled-react}/src/legacy-theme/ts/colors/light_colorblind.ts (100%) rename packages/{react => styled-react}/src/legacy-theme/ts/colors/light_high_contrast.ts (100%) rename packages/{react => styled-react}/src/legacy-theme/ts/colors/light_tritanopia.ts (100%) rename packages/{react => styled-react}/src/legacy-theme/ts/index.ts (100%) rename packages/{react => styled-react}/src/theme.ts (88%) diff --git a/package-lock.json b/package-lock.json index 7b949b95ae7..6b8f5dea953 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25859,7 +25859,6 @@ "@types/styled-system__theme-get": "^5.0.1", "clsx": "^2.1.1", "color2k": "^2.0.3", - "deepmerge": "^4.3.1", "focus-visible": "^5.2.1", "history": "^5.0.0", "hsluv": "1.0.1", @@ -26414,6 +26413,9 @@ "packages/styled-react": { "name": "@primer/styled-react", "version": "1.0.0-rc.2", + "dependencies": { + "deepmerge": "^4.3.1" + }, "devDependencies": { "@babel/preset-react": "^7.27.1", "@babel/preset-typescript": "^7.27.1", diff --git a/packages/react/package.json b/packages/react/package.json index 0a9949b5949..ddc76827ddd 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -45,7 +45,6 @@ "build:docs:preview": "NODE_OPTIONS=--openssl-legacy-provider script/build-docs preview", "build:components.json": "tsx script/components-json/build.ts", "build:hooks.json": "tsx script/hooks-json/build.ts", - "build:precompile-color-schemes": "tsx script/precompile-color-schemes.ts", "lint:npm": "publint --types", "storybook": "storybook", "type-check": "tsc --noEmit", @@ -87,7 +86,6 @@ "@types/styled-system__theme-get": "^5.0.1", "clsx": "^2.1.1", "color2k": "^2.0.3", - "deepmerge": "^4.3.1", "focus-visible": "^5.2.1", "history": "^5.0.0", "hsluv": "1.0.1", diff --git a/packages/react/script/build b/packages/react/script/build index 4dab4524738..e5b51cc32fd 100755 --- a/packages/react/script/build +++ b/packages/react/script/build @@ -5,9 +5,6 @@ set -e # Clean up npm run clean -# Generate color schemes -npm run build:precompile-color-schemes - # Generate types for CSS Modules npm run type-css-modules diff --git a/packages/react/src/__tests__/ThemeProvider.test.tsx b/packages/react/src/ThemeProvider/ThemeProvider.test.tsx similarity index 83% rename from packages/react/src/__tests__/ThemeProvider.test.tsx rename to packages/react/src/ThemeProvider/ThemeProvider.test.tsx index 0ac937b6880..af30308820b 100644 --- a/packages/react/src/__tests__/ThemeProvider.test.tsx +++ b/packages/react/src/ThemeProvider/ThemeProvider.test.tsx @@ -2,7 +2,8 @@ import {render, screen, waitFor} from '@testing-library/react' import userEvent from '@testing-library/user-event' import {describe, expect, it, vi} from 'vitest' import React from 'react' -import {Text, ThemeProvider, useColorSchemeVar, useTheme} from '..' +import Text from '../Text' +import {ThemeProvider, useColorSchemeVar, useTheme} from '../ThemeProvider' // window.matchMedia() is not implemented by JSDOM so we have to create a mock: // https://vijs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom @@ -43,53 +44,9 @@ const exampleTheme = { }, } -it('respects theme prop', () => { - const theme = { - colors: { - text: '#f00', - }, - space: ['0', '0.25rem'], - } - - render( - - - Hello - - , - ) - - expect(screen.getByText('Hello')).toHaveStyle('color: #f00') - expect(screen.getByText('Hello')).toHaveStyle('margin-bottom: 4px') -}) - -it('has default theme', () => { - render( - - - Hello - - , - ) - - expect(screen.getByText('Hello')).toMatchSnapshot() -}) - -it('inherits theme from parent', () => { - render( - - - Hello - - , - ) - - expect(screen.getByText('Hello')).toHaveStyle('color: rgb(0, 0, 0)') -}) - it('defaults to light color scheme', () => { render( - + Hello , ) @@ -99,7 +56,7 @@ it('defaults to light color scheme', () => { it('defaults to dark color scheme in night mode', () => { render( - + Hello , ) @@ -111,7 +68,7 @@ it('defaults to first color scheme when passed an invalid color scheme name', () const spy = vi.spyOn(console, 'error').mockImplementationOnce(() => {}) render( - + Hello , ) @@ -124,7 +81,7 @@ it('defaults to first color scheme when passed an invalid color scheme name', () it('respects nightScheme prop', () => { render( - + Hello , ) @@ -134,7 +91,7 @@ it('respects nightScheme prop', () => { it('respects nightScheme prop with colorMode="dark"', () => { render( - + Hello , ) @@ -144,7 +101,7 @@ it('respects nightScheme prop with colorMode="dark"', () => { it('respects dayScheme prop', () => { render( - + Hello , ) @@ -154,7 +111,7 @@ it('respects dayScheme prop', () => { it('respects dayScheme prop with colorMode="light"', () => { render( - + Hello , ) @@ -164,7 +121,7 @@ it('respects dayScheme prop with colorMode="light"', () => { it('works in auto mode', () => { render( - + Hello , ) @@ -185,7 +142,7 @@ it('works in auto mode (dark)', () => { })) render( - + Hello , ) @@ -201,7 +158,7 @@ it('updates when colorMode prop changes', async () => { function App() { const [colorMode, setColorMode] = React.useState<'day' | 'night'>('day') return ( - + {colorMode} @@ -311,7 +268,7 @@ it('inherits dayScheme from parent', async () => { function App() { const [dayScheme, setDayScheme] = React.useState('light') return ( - + @@ -337,7 +294,7 @@ it('inherits nightScheme from parent', async () => { function App() { const [nightScheme, setNightScheme] = React.useState('dark') return ( - + @@ -371,7 +328,7 @@ describe('setColorMode', () => { } render( - + Hello , @@ -401,7 +358,7 @@ describe('setDayScheme', () => { } render( - + Hello , @@ -431,7 +388,7 @@ describe('setNightScheme', () => { } render( - + Hello , @@ -474,7 +431,7 @@ describe('useColorSchemeVar', () => { } render( - + , @@ -506,7 +463,7 @@ describe('useColorSchemeVar', () => { } render( - + , @@ -533,22 +490,6 @@ describe('useTheme().resolvedColorScheme', () => { 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() @@ -559,7 +500,7 @@ describe('useTheme().resolvedColorScheme', () => { const schemeToApply = 'dark' render( - + , ) @@ -578,7 +519,7 @@ describe('useTheme().resolvedColorScheme', () => { const schemeToApply = 'totally-invalid-colorscheme' render( - + , ) @@ -604,7 +545,7 @@ describe('useTheme().resolvedColorScheme', () => { const schemeToApply = 'dark' render( - + @@ -626,7 +567,7 @@ describe('useTheme().resolvedColorScheme', () => { const schemeToApply = 'totally-invalid-colorscheme' render( - + diff --git a/packages/react/src/ThemeProvider.tsx b/packages/react/src/ThemeProvider/ThemeProvider.tsx similarity index 75% rename from packages/react/src/ThemeProvider.tsx rename to packages/react/src/ThemeProvider/ThemeProvider.tsx index 416e317a540..cc52d653cbb 100644 --- a/packages/react/src/ThemeProvider.tsx +++ b/packages/react/src/ThemeProvider/ThemeProvider.tsx @@ -1,22 +1,16 @@ import React from 'react' import ReactDOM from 'react-dom' -import {ThemeProvider as SCThemeProvider} from 'styled-components' -import defaultTheme from './theme' -import deepmerge from 'deepmerge' -import {useId} from './hooks' -import {useSyncedState} from './hooks/useSyncedState' +import {useId} from '../hooks/useId' +import {useSyncedState} from '../hooks/useSyncedState' export const defaultColorMode = 'day' const defaultDayScheme = 'light' const defaultNightScheme = 'dark' -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type Theme = {[key: string]: any} type ColorMode = 'day' | 'night' | 'light' | 'dark' export type ColorModeWithAuto = ColorMode | 'auto' export type ThemeProviderProps = { - theme?: Theme colorMode?: ColorModeWithAuto dayScheme?: string nightScheme?: string @@ -24,7 +18,6 @@ export type ThemeProviderProps = { } const ThemeContext = React.createContext<{ - theme?: Theme colorScheme?: string colorMode?: ColorModeWithAuto resolvedColorMode?: ColorMode @@ -53,15 +46,7 @@ const getServerHandoff = (id: string) => { export const ThemeProvider: React.FC> = ({children, ...props}) => { // Get fallback values from parent ThemeProvider (if exists) - const { - theme: fallbackTheme, - colorMode: fallbackColorMode, - dayScheme: fallbackDayScheme, - nightScheme: fallbackNightScheme, - } = useTheme() - - // Initialize state - const theme = props.theme ?? fallbackTheme ?? defaultTheme + const {colorMode: fallbackColorMode, dayScheme: fallbackDayScheme, nightScheme: fallbackNightScheme} = useTheme() const uniqueDataId = useId() const {resolvedServerColorMode} = getServerHandoff(uniqueDataId) @@ -73,10 +58,6 @@ export const ThemeProvider: React.FC const systemColorMode = useSystemColorMode() const resolvedColorMode = resolvedColorModePassthrough.current || resolveColorMode(colorMode, systemColorMode) const colorScheme = chooseColorScheme(resolvedColorMode, dayScheme, nightScheme) - const {resolvedTheme, resolvedColorScheme} = React.useMemo( - () => applyColorScheme(theme, colorScheme), - [theme, colorScheme], - ) // this effect will only run on client React.useEffect( @@ -108,11 +89,10 @@ export const ThemeProvider: React.FC return ( setNightScheme, }} > - - {children} - {props.preventSSRMismatch ? ( -