Skip to content

Commit ce88bdf

Browse files
chore: forward @primer/react theming from @primer/styled-react under FF (#7800)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 8f8844e commit ce88bdf

6 files changed

Lines changed: 187 additions & 2 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/react": patch
3+
---
4+
5+
chore: add `primer_react_styled_react_use_primer_theme_providers` feature flag to DefaultFeatureFlags
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/styled-react": patch
3+
---
4+
5+
chore: forward @primer/react theming from @primer/styled-react under feature flag

packages/react/src/FeatureFlags/DefaultFeatureFlags.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ export const DefaultFeatureFlags = FeatureFlagScope.create({
44
primer_react_css_anchor_positioning: false,
55
primer_react_select_panel_fullscreen_on_narrow: false,
66
primer_react_select_panel_order_selected_at_top: false,
7+
primer_react_styled_react_use_primer_theme_providers: false,
78
})
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import {render, screen} from '@testing-library/react'
2+
import {describe, expect, it, vi} from 'vitest'
3+
import React from 'react'
4+
import {ThemeProvider, useTheme, BaseStyles} from '../'
5+
import {FeatureFlags} from '@primer/react/experimental'
6+
7+
// window.matchMedia() is not implemented by JSDOM so we have to create a mock:
8+
Object.defineProperty(window, 'matchMedia', {
9+
writable: true,
10+
value: vi.fn().mockImplementation(query => ({
11+
matches: false,
12+
media: query,
13+
onchange: null,
14+
addListener: vi.fn(),
15+
removeListener: vi.fn(),
16+
addEventListener: vi.fn(),
17+
removeEventListener: vi.fn(),
18+
dispatchEvent: vi.fn(),
19+
})),
20+
})
21+
22+
describe('FeatureFlaggedTheming', () => {
23+
describe('when primer_react_styled_react_use_primer_theme_providers is disabled', () => {
24+
it('ThemeProvider does not render a wrapper div with data-color-mode', () => {
25+
render(
26+
<FeatureFlags flags={{primer_react_styled_react_use_primer_theme_providers: false}}>
27+
<ThemeProvider>
28+
<div data-testid="child">Hello</div>
29+
</ThemeProvider>
30+
</FeatureFlags>,
31+
)
32+
33+
// The styled ThemeProvider uses styled-components SCThemeProvider which
34+
// does not inject a wrapper div. The child should not have a parent with data-color-mode.
35+
const child = screen.getByTestId('child')
36+
expect(child.parentElement).not.toHaveAttribute('data-color-mode')
37+
})
38+
39+
it('useTheme returns styled theme context values', () => {
40+
function ThemeConsumer() {
41+
const theme = useTheme()
42+
return <div data-testid="theme-consumer">{theme.colorMode ?? 'day'}</div>
43+
}
44+
45+
render(
46+
<FeatureFlags flags={{primer_react_styled_react_use_primer_theme_providers: false}}>
47+
<ThemeProvider colorMode="night">
48+
<ThemeConsumer />
49+
</ThemeProvider>
50+
</FeatureFlags>,
51+
)
52+
53+
expect(screen.getByTestId('theme-consumer')).toHaveTextContent('night')
54+
})
55+
56+
it('BaseStyles renders with data-color-mode and without data-component', () => {
57+
render(
58+
<FeatureFlags flags={{primer_react_styled_react_use_primer_theme_providers: false}}>
59+
<ThemeProvider>
60+
<BaseStyles data-testid="base-styles">
61+
<div>Hello</div>
62+
</BaseStyles>
63+
</ThemeProvider>
64+
</FeatureFlags>,
65+
)
66+
67+
const baseStyles = screen.getByTestId('base-styles')
68+
expect(baseStyles).toHaveAttribute('data-color-mode')
69+
expect(baseStyles).toHaveAttribute('data-light-theme')
70+
expect(baseStyles).toHaveAttribute('data-dark-theme')
71+
expect(baseStyles).not.toHaveAttribute('data-component')
72+
})
73+
})
74+
75+
describe('when primer_react_styled_react_use_primer_theme_providers is enabled', () => {
76+
it('ThemeProvider renders a wrapper div with data-color-mode', () => {
77+
render(
78+
<FeatureFlags flags={{primer_react_styled_react_use_primer_theme_providers: true}}>
79+
<ThemeProvider>
80+
<div data-testid="child">Hello</div>
81+
</ThemeProvider>
82+
</FeatureFlags>,
83+
)
84+
85+
// The @primer/react ThemeProvider renders a <div> with data-color-mode
86+
const child = screen.getByTestId('child')
87+
expect(child.parentElement).toHaveAttribute('data-color-mode')
88+
expect(child.parentElement).toHaveAttribute('data-light-theme')
89+
expect(child.parentElement).toHaveAttribute('data-dark-theme')
90+
})
91+
92+
it('useTheme returns primer theme context values', () => {
93+
function ThemeConsumer() {
94+
const theme = useTheme()
95+
return <div data-testid="theme-consumer">{theme.colorMode ?? 'day'}</div>
96+
}
97+
98+
render(
99+
<FeatureFlags flags={{primer_react_styled_react_use_primer_theme_providers: true}}>
100+
<ThemeProvider colorMode="night">
101+
<ThemeConsumer />
102+
</ThemeProvider>
103+
</FeatureFlags>,
104+
)
105+
106+
expect(screen.getByTestId('theme-consumer')).toHaveTextContent('night')
107+
})
108+
109+
it('BaseStyles renders with data-component and without data-color-mode', () => {
110+
render(
111+
<FeatureFlags flags={{primer_react_styled_react_use_primer_theme_providers: true}}>
112+
<ThemeProvider>
113+
<BaseStyles data-testid="base-styles">
114+
<div>Hello</div>
115+
</BaseStyles>
116+
</ThemeProvider>
117+
</FeatureFlags>,
118+
)
119+
120+
const baseStyles = screen.getByTestId('base-styles')
121+
expect(baseStyles).toHaveAttribute('data-component', 'BaseStyles')
122+
expect(baseStyles).not.toHaveAttribute('data-color-mode')
123+
expect(baseStyles).not.toHaveAttribute('data-light-theme')
124+
expect(baseStyles).not.toHaveAttribute('data-dark-theme')
125+
})
126+
})
127+
})
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type React from 'react'
2+
import {
3+
ThemeProvider as PrimerThemeProvider,
4+
useTheme as primerUseTheme,
5+
BaseStyles as PrimerBaseStyles,
6+
} from '@primer/react'
7+
import type {
8+
ThemeProviderProps as PrimerThemeProviderProps,
9+
BaseStylesProps as PrimerBaseStylesProps,
10+
} from '@primer/react'
11+
import {useFeatureFlag} from '@primer/react/experimental'
12+
import {ThemeProvider as StyledThemeProvider, useTheme as styledUseTheme, useColorSchemeVar} from './ThemeProvider'
13+
import type {ThemeProviderProps as StyledThemeProviderProps} from './ThemeProvider'
14+
import {BaseStyles as StyledBaseStyles} from './BaseStyles'
15+
import type {BaseStylesProps as StyledBaseStylesProps} from './BaseStyles'
16+
17+
export type ThemeProviderProps = StyledThemeProviderProps
18+
19+
export type BaseStylesProps = StyledBaseStylesProps
20+
21+
export const ThemeProvider: React.FC<React.PropsWithChildren<ThemeProviderProps>> = ({children, ...props}) => {
22+
const enabled = useFeatureFlag('primer_react_styled_react_use_primer_theme_providers')
23+
if (enabled) {
24+
return <PrimerThemeProvider {...(props as PrimerThemeProviderProps)}>{children}</PrimerThemeProvider>
25+
}
26+
return <StyledThemeProvider {...props}>{children}</StyledThemeProvider>
27+
}
28+
29+
export function useTheme(): ReturnType<typeof primerUseTheme> {
30+
const enabled = useFeatureFlag('primer_react_styled_react_use_primer_theme_providers')
31+
const styledTheme = styledUseTheme()
32+
const primerTheme = primerUseTheme()
33+
if (enabled) {
34+
return primerTheme as ReturnType<typeof primerUseTheme>
35+
}
36+
return styledTheme
37+
}
38+
39+
export {useColorSchemeVar}
40+
41+
export function BaseStyles(props: BaseStylesProps) {
42+
const enabled = useFeatureFlag('primer_react_styled_react_use_primer_theme_providers')
43+
if (enabled) {
44+
return <PrimerBaseStyles {...(props as PrimerBaseStylesProps)} />
45+
}
46+
return <StyledBaseStyles {...props} />
47+
}

packages/styled-react/src/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export {
3939
* `@primer/primitives` and CSS Modules instead.
4040
*/
4141
type ThemeProviderProps,
42-
} from './components/ThemeProvider'
42+
} from './components/FeatureFlaggedTheming'
4343

4444
export {
4545
/**
@@ -53,7 +53,7 @@ export {
5353
* supported. Use the component from `@primer/react` with CSS Modules instead.
5454
*/
5555
type BaseStylesProps,
56-
} from './components/BaseStyles'
56+
} from './components/FeatureFlaggedTheming'
5757

5858
export {
5959
/**

0 commit comments

Comments
 (0)