Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/fuzzy-kangaroos-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@primer/react": patch
"@primer/styled-react": patch
---

chore: forward @primer/react theming from @primer/styled-react under FF
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nit) would this same changelog show up for both the packages?

I'd suggest splitting them or skipping the changelog for primer/react

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() {
const enabled = useFeatureFlag('primer_react_styled_react_use_primer_theme_providers')
const styledTheme = styledUseTheme()
const primerTheme = primerUseTheme()
if (enabled) {
return primerTheme
}
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