Skip to content
Open
5 changes: 5 additions & 0 deletions .changeset/fuzzy-kangaroos-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": patch
---

chore: add `primer_react_styled_react_use_primer_theme_providers` feature flag to DefaultFeatureFlags
5 changes: 5 additions & 0 deletions .changeset/styled-react-forward-theming.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/styled-react": patch
---

chore: forward @primer/react theming from @primer/styled-react under feature flag
1 change: 1 addition & 0 deletions packages/react/src/FeatureFlags/DefaultFeatureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export const DefaultFeatureFlags = FeatureFlagScope.create({
primer_react_css_anchor_positioning: false,
primer_react_select_panel_fullscreen_on_narrow: false,
primer_react_select_panel_order_selected_at_top: false,
primer_react_styled_react_use_primer_theme_providers: false,
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import {render, screen} from '@testing-library/react'
import {describe, expect, it, vi} from 'vitest'
import React from 'react'
import {ThemeProvider, useTheme, BaseStyles} from '../'
import {FeatureFlags} from '@primer/react/experimental'

Comment thread
francinelucca marked this conversation as resolved.
// window.matchMedia() is not implemented by JSDOM so we have to create a mock:
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
})

describe('FeatureFlaggedTheming', () => {
describe('when primer_react_styled_react_use_primer_theme_providers is disabled', () => {
it('ThemeProvider does not render a wrapper div with data-color-mode', () => {
render(
<FeatureFlags flags={{primer_react_styled_react_use_primer_theme_providers: false}}>
<ThemeProvider>
<div data-testid="child">Hello</div>
</ThemeProvider>
</FeatureFlags>,
)

// The styled ThemeProvider uses styled-components SCThemeProvider which
// does not inject a wrapper div. The child should not have a parent with data-color-mode.
const child = screen.getByTestId('child')
expect(child.parentElement).not.toHaveAttribute('data-color-mode')
})

it('useTheme returns styled theme context values', () => {
function ThemeConsumer() {
const theme = useTheme()
return <div data-testid="theme-consumer">{theme.colorMode ?? 'day'}</div>
}

render(
<FeatureFlags flags={{primer_react_styled_react_use_primer_theme_providers: false}}>
<ThemeProvider colorMode="night">
<ThemeConsumer />
</ThemeProvider>
</FeatureFlags>,
)

expect(screen.getByTestId('theme-consumer')).toHaveTextContent('night')
})

it('BaseStyles renders with data-color-mode and without data-component', () => {
render(
<FeatureFlags flags={{primer_react_styled_react_use_primer_theme_providers: false}}>
<ThemeProvider>
<BaseStyles data-testid="base-styles">
<div>Hello</div>
</BaseStyles>
</ThemeProvider>
</FeatureFlags>,
)

const baseStyles = screen.getByTestId('base-styles')
expect(baseStyles).toHaveAttribute('data-color-mode')
expect(baseStyles).toHaveAttribute('data-light-theme')
expect(baseStyles).toHaveAttribute('data-dark-theme')
expect(baseStyles).not.toHaveAttribute('data-component')
})
})

describe('when primer_react_styled_react_use_primer_theme_providers is enabled', () => {
it('ThemeProvider renders a wrapper div with data-color-mode', () => {
render(
<FeatureFlags flags={{primer_react_styled_react_use_primer_theme_providers: true}}>
<ThemeProvider>
<div data-testid="child">Hello</div>
</ThemeProvider>
</FeatureFlags>,
)

// The @primer/react ThemeProvider renders a <div> with data-color-mode
const child = screen.getByTestId('child')
expect(child.parentElement).toHaveAttribute('data-color-mode')
expect(child.parentElement).toHaveAttribute('data-light-theme')
expect(child.parentElement).toHaveAttribute('data-dark-theme')
})

it('useTheme returns primer theme context values', () => {
function ThemeConsumer() {
const theme = useTheme()
return <div data-testid="theme-consumer">{theme.colorMode ?? 'day'}</div>
}

render(
<FeatureFlags flags={{primer_react_styled_react_use_primer_theme_providers: true}}>
<ThemeProvider colorMode="night">
<ThemeConsumer />
</ThemeProvider>
</FeatureFlags>,
)

expect(screen.getByTestId('theme-consumer')).toHaveTextContent('night')
})

it('BaseStyles renders with data-component and without data-color-mode', () => {
render(
<FeatureFlags flags={{primer_react_styled_react_use_primer_theme_providers: true}}>
<ThemeProvider>
<BaseStyles data-testid="base-styles">
<div>Hello</div>
</BaseStyles>
</ThemeProvider>
</FeatureFlags>,
)

const baseStyles = screen.getByTestId('base-styles')
expect(baseStyles).toHaveAttribute('data-component', 'BaseStyles')
expect(baseStyles).not.toHaveAttribute('data-color-mode')
expect(baseStyles).not.toHaveAttribute('data-light-theme')
expect(baseStyles).not.toHaveAttribute('data-dark-theme')
})
})
})
47 changes: 47 additions & 0 deletions packages/styled-react/src/components/FeatureFlaggedTheming.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type React from 'react'
import {
ThemeProvider as PrimerThemeProvider,
useTheme as primerUseTheme,
BaseStyles as PrimerBaseStyles,
} from '@primer/react'
import type {
ThemeProviderProps as PrimerThemeProviderProps,
BaseStylesProps as PrimerBaseStylesProps,
} from '@primer/react'
import {useFeatureFlag} from '@primer/react/experimental'
import {ThemeProvider as StyledThemeProvider, useTheme as styledUseTheme, useColorSchemeVar} from './ThemeProvider'
import type {ThemeProviderProps as StyledThemeProviderProps} from './ThemeProvider'
import {BaseStyles as StyledBaseStyles} from './BaseStyles'
import type {BaseStylesProps as StyledBaseStylesProps} from './BaseStyles'

export type ThemeProviderProps = StyledThemeProviderProps

export type BaseStylesProps = StyledBaseStylesProps

export const ThemeProvider: React.FC<React.PropsWithChildren<ThemeProviderProps>> = ({children, ...props}) => {
const enabled = useFeatureFlag('primer_react_styled_react_use_primer_theme_providers')
if (enabled) {
return <PrimerThemeProvider {...(props as PrimerThemeProviderProps)}>{children}</PrimerThemeProvider>
}
return <StyledThemeProvider {...props}>{children}</StyledThemeProvider>
}
Comment thread
francinelucca marked this conversation as resolved.

export function useTheme(): ReturnType<typeof styledUseTheme> {
const enabled = useFeatureFlag('primer_react_styled_react_use_primer_theme_providers')
const styledTheme = styledUseTheme()
const primerTheme = primerUseTheme()
if (enabled) {
return primerTheme as ReturnType<typeof styledUseTheme>
}
return styledTheme
}

export {useColorSchemeVar}

export function BaseStyles(props: BaseStylesProps) {
const enabled = useFeatureFlag('primer_react_styled_react_use_primer_theme_providers')
if (enabled) {
return <PrimerBaseStyles {...(props as PrimerBaseStylesProps)} />
}
return <StyledBaseStyles {...props} />
}
4 changes: 2 additions & 2 deletions packages/styled-react/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export {
* `@primer/primitives` and CSS Modules instead.
*/
type ThemeProviderProps,
} from './components/ThemeProvider'
} from './components/FeatureFlaggedTheming'

export {
/**
Expand All @@ -53,7 +53,7 @@ export {
* supported. Use the component from `@primer/react` with CSS Modules instead.
*/
type BaseStylesProps,
} from './components/BaseStyles'
} from './components/FeatureFlaggedTheming'

export {
/**
Expand Down
Loading