Skip to content

Commit 3032026

Browse files
authored
styled-react: Add ThemeProvider and BaseStyles (#6958)
1 parent f61238c commit 3032026

File tree

14 files changed

+604
-10
lines changed

14 files changed

+604
-10
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@primer/react": patch
3+
"@primer/styled-react": minor
4+
---
5+
6+
@primer/react: Export `useId` and `useSyncedState`
7+
@primer/styled-react: Add `ThemeProvider` and `BaseStyles`

e2e/components/IconButton.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ const stories = [
4545
disableAnimations: true,
4646
async setup(page: Page) {
4747
await page.keyboard.press('Tab') // focus on icon button
48-
await page.getByText('Bold').waitFor({
49-
state: 'visible',
50-
})
48+
await page.getByText('Bold').waitFor({state: 'visible'})
49+
// eslint-disable-next-line playwright/no-wait-for-timeout
50+
await page.waitForTimeout(1000) // wait until after "tooltip delay" for a stable screenshot
5151
},
5252
},
5353
{

examples/nextjs/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
},
1212
"dependencies": {
1313
"@primer/react": "38.0.0-rc.6",
14+
"@primer/styled-react": "1.0.0-rc.7",
1415
"next": "^15.2.3",
1516
"react": "18.3.1",
1617
"react-dom": "18.3.1",

examples/nextjs/src/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import './global.css'
2-
import {BaseStyles, ThemeProvider} from '@primer/react'
2+
import {ThemeProvider, BaseStyles} from '@primer/styled-react'
33
import {StyledComponentsRegistry} from './registry'
44

55
export const metadata = {

examples/nextjs/src/app/page.tsx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,39 @@
1-
import {Button} from '@primer/react'
1+
'use client'
2+
3+
import {Button, Stack, Box} from '@primer/react'
4+
import {useTheme} from '@primer/styled-react'
5+
import styled from 'styled-components'
6+
7+
const StyledDiv = styled.div(({theme}) => {
8+
return {
9+
padding: theme.space[5],
10+
backgroundColor: theme.colors.btn.primary.bg,
11+
}
12+
})
13+
14+
const ThemeUser = () => {
15+
const {theme} = useTheme()
16+
return (
17+
<div
18+
style={{
19+
padding: theme?.space[5],
20+
backgroundColor: theme?.colors.btn.primary.bg,
21+
}}
22+
>
23+
Hello world
24+
</div>
25+
)
26+
}
227

328
export default function IndexPage() {
4-
return <Button>Hello world</Button>
29+
return (
30+
<Stack direction="horizontal">
31+
<Button variant="primary" sx={{padding: 5}}>
32+
Hello world
33+
</Button>
34+
<Box sx={{padding: 5, backgroundColor: 'btn.primary.bg'}}>Hello world</Box>
35+
<StyledDiv>Hello world</StyledDiv>
36+
<ThemeUser />
37+
</Stack>
38+
)
539
}

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react/src/FeatureFlags/DefaultFeatureFlags.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export const DefaultFeatureFlags = FeatureFlagScope.create({
88
primer_react_select_panel_fullscreen_on_narrow: false,
99
primer_react_select_panel_order_selected_at_top: false,
1010
primer_react_select_panel_remove_active_descendant: false,
11+
primer_react_use_styled_react_theming: false,
1112
})

packages/react/src/__tests__/__snapshots__/exports.test.ts.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ exports[`@primer/react > should not update exports without a semver change 1`] =
214214
"useFocusTrap",
215215
"useFocusZone",
216216
"useFormControlForwardedProps",
217+
"useId",
217218
"useIsomorphicLayoutEffect",
218219
"useOnEscapePress",
219220
"useOnOutsideClick",
@@ -224,6 +225,7 @@ exports[`@primer/react > should not update exports without a semver change 1`] =
224225
"useResizeObserver",
225226
"useResponsiveValue",
226227
"useSafeTimeout",
228+
"useSyncedState",
227229
"useTheme",
228230
"VisuallyHidden",
229231
"type VisuallyHiddenProps",

packages/react/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ export {useResizeObserver} from './hooks/useResizeObserver'
4646
export {useResponsiveValue, type ResponsiveValue} from './hooks/useResponsiveValue'
4747
export {default as useIsomorphicLayoutEffect} from './utils/useIsomorphicLayoutEffect'
4848
export {useProvidedRefOrCreate} from './hooks/useProvidedRefOrCreate'
49+
export {useId} from './hooks/useId'
50+
export {useSyncedState} from './hooks/useSyncedState'
4951

5052
// Utils
5153
export {createComponent} from './utils/create-component'

packages/styled-react/rollup.config.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import babel from '@rollup/plugin-babel'
22
import {defineConfig} from 'rollup'
33
import typescript from 'rollup-plugin-typescript2'
44
import packageJson from './package.json' with {type: 'json'}
5+
import MagicString from 'magic-string'
56

67
const dependencies = [
78
...Object.keys(packageJson.peerDependencies ?? {}),
@@ -26,9 +27,111 @@ export default defineConfig({
2627
extensions: ['.ts', '.tsx'],
2728
babelHelpers: 'bundled',
2829
}),
30+
/**
31+
* This custom rollup plugin allows us to preserve directives in source
32+
* code, such as "use client", in order to support React Server Components.
33+
*
34+
* The source for this plugin is inspired by:
35+
* https://github.com/Ephem/rollup-plugin-preserve-directives
36+
*/
37+
{
38+
name: 'preserve-directives',
39+
transform(code) {
40+
const ast = this.parse(code)
41+
if (ast.type !== 'Program' || !ast.body) {
42+
return {
43+
code,
44+
ast,
45+
map: null,
46+
}
47+
}
48+
49+
let hasClientDirective = false
50+
51+
for (const node of ast.body) {
52+
if (!node) {
53+
continue
54+
}
55+
56+
if (node.type !== 'ExpressionStatement') {
57+
continue
58+
}
59+
60+
if (node.directive === 'use client') {
61+
hasClientDirective = true
62+
break
63+
}
64+
}
65+
66+
if (hasClientDirective) {
67+
return {
68+
code,
69+
ast,
70+
map: null,
71+
meta: {
72+
hasClientDirective: true,
73+
},
74+
}
75+
}
76+
77+
return {
78+
code,
79+
ast,
80+
map: null,
81+
}
82+
},
83+
renderChunk: {
84+
order: 'post',
85+
handler(code, chunk, options) {
86+
// If `preserveModules` is not set to true, we can't be sure if the client
87+
// directive corresponds to the whole chunk or just a part of it.
88+
if (!options.preserveModules) {
89+
return undefined
90+
}
91+
92+
let chunkHasClientDirective = false
93+
94+
for (const moduleId of Object.keys(chunk.modules)) {
95+
const hasClientDirective = this.getModuleInfo(moduleId)?.meta?.hasClientDirective
96+
if (hasClientDirective) {
97+
chunkHasClientDirective = true
98+
break
99+
}
100+
}
101+
102+
if (chunkHasClientDirective) {
103+
const transformed = new MagicString(code)
104+
transformed.prepend(`"use client";\n`)
105+
const sourcemap = transformed.generateMap({
106+
includeContent: true,
107+
})
108+
return {
109+
code: transformed.toString(),
110+
map: sourcemap,
111+
}
112+
}
113+
114+
return null
115+
},
116+
},
117+
},
29118
],
119+
onwarn(warning, defaultHandler) {
120+
// Dependencies or modules may use "use client" as an indicator for React
121+
// Server Components that this module should only be loaded on the client.
122+
if (warning.code === 'MODULE_LEVEL_DIRECTIVE' && warning.message.includes('use client')) {
123+
return
124+
}
125+
126+
if (warning.code === 'CIRCULAR_DEPENDENCY') {
127+
throw warning
128+
}
129+
130+
defaultHandler(warning)
131+
},
30132
output: {
31133
dir: 'dist',
32134
format: 'esm',
135+
preserveModules: true,
33136
},
34137
})

0 commit comments

Comments
 (0)