diff --git a/examples/nextjs/src/app/layout.tsx b/examples/nextjs/src/app/layout.tsx index ec8ff0f19a7..d193b551f09 100644 --- a/examples/nextjs/src/app/layout.tsx +++ b/examples/nextjs/src/app/layout.tsx @@ -1,5 +1,6 @@ import './global.css' -import {BaseStyles, ThemeProvider} from '@primer/react' +import {ThemeProvider} from '@primer/styled-react' +import {BaseStyles} from '@primer/react' import {StyledComponentsRegistry} from './registry' export const metadata = { diff --git a/examples/nextjs/src/app/page.tsx b/examples/nextjs/src/app/page.tsx index 7b9ad9f2e0d..a9519617d97 100644 --- a/examples/nextjs/src/app/page.tsx +++ b/examples/nextjs/src/app/page.tsx @@ -1,5 +1,24 @@ -import {Button} from '@primer/react' +'use client' + +import {Box, Button, Stack} from '@primer/react' +import styled from 'styled-components' + +const StyledDiv = styled.div(({theme}) => { + console.log({styledTheme: theme}) + return { + padding: theme.space[5], + backgroundColor: theme.colors.btn.primary.bg, + } +}) export default function IndexPage() { - return + return ( + + + Hello world + Hello world + + ) } diff --git a/packages/styled-react/rollup.config.js b/packages/styled-react/rollup.config.js index 8aae93d5045..743ab953efe 100644 --- a/packages/styled-react/rollup.config.js +++ b/packages/styled-react/rollup.config.js @@ -26,7 +26,109 @@ export default defineConfig({ extensions: ['.ts', '.tsx'], babelHelpers: 'bundled', }), + + /** + * This custom rollup plugin allows us to preserve directives in source + * code, such as "use client", in order to support React Server Components. + * + * The source for this plugin is inspired by: + * https://github.com/Ephem/rollup-plugin-preserve-directives + */ + { + name: 'preserve-directives', + transform(code) { + const ast = this.parse(code) + if (ast.type !== 'Program' || !ast.body) { + return { + code, + ast, + map: null, + } + } + + let hasClientDirective = false + + for (const node of ast.body) { + if (!node) { + continue + } + + if (node.type !== 'ExpressionStatement') { + continue + } + + if (node.directive === 'use client') { + hasClientDirective = true + break + } + } + + if (hasClientDirective) { + return { + code, + ast, + map: null, + meta: { + hasClientDirective: true, + }, + } + } + + return { + code, + ast, + map: null, + } + }, + renderChunk: { + order: 'post', + handler(code, chunk, options) { + // If `preserveModules` is not set to true, we can't be sure if the client + // directive corresponds to the whole chunk or just a part of it. + if (!options.preserveModules) { + return undefined + } + + let chunkHasClientDirective = false + + for (const moduleId of Object.keys(chunk.modules)) { + const hasClientDirective = this.getModuleInfo(moduleId)?.meta?.hasClientDirective + if (hasClientDirective) { + chunkHasClientDirective = true + break + } + } + + if (chunkHasClientDirective) { + const transformed = new MagicString(code) + transformed.prepend(`"use client";\n`) + const sourcemap = transformed.generateMap({ + includeContent: true, + }) + return { + code: transformed.toString(), + map: sourcemap, + } + } + + return null + }, + }, + }, ], + onwarn(warning, defaultHandler) { + // Dependencies or modules may use "use client" as an indicator for React + // Server Components that this module should only be loaded on the client. + if (warning.code === 'MODULE_LEVEL_DIRECTIVE' && warning.message.includes('use client')) { + return + } + + if (warning.code === 'CIRCULAR_DEPENDENCY') { + throw warning + } + + defaultHandler(warning) + }, output: { dir: 'dist', format: 'esm', diff --git a/packages/styled-react/src/components/ThemeProvider.tsx b/packages/styled-react/src/components/ThemeProvider.tsx new file mode 100644 index 00000000000..70a172a91f2 --- /dev/null +++ b/packages/styled-react/src/components/ThemeProvider.tsx @@ -0,0 +1,20 @@ +import React, {type PropsWithChildren} from 'react' +import {ThemeProvider as SCThemeProvider} from 'styled-components' +import { + ThemeProvider as PRCThemeProvider, + type ThemeProviderProps, + useTheme, + theme as fallbackTheme, +} from '@primer/react' + +export const ThemeProvider = (props: PropsWithChildren) => { + const {children, ...rest} = props + const {theme} = useTheme() + + console.log({theme}) + return ( + + {children} + + ) +} diff --git a/packages/styled-react/src/index.tsx b/packages/styled-react/src/index.tsx index 88e659b1ab6..136697450f8 100644 --- a/packages/styled-react/src/index.tsx +++ b/packages/styled-react/src/index.tsx @@ -1,3 +1,5 @@ +'use client' + export {Box, type BoxProps} from './components/Box' export {Button} from '@primer/react' export {Details} from '@primer/react' @@ -8,10 +10,10 @@ export {PageLayout} from '@primer/react' export {Select} from '@primer/react' export {Textarea} from '@primer/react' export {TextInput} from '@primer/react' -export {type TextInputProps} from '@primer/react' +// export {type TextInputProps} from '@primer/react' // theming depends on styled-components -export {ThemeProvider} from '@primer/react' +export {ThemeProvider} from './components/ThemeProvider' export {merge} from '@primer/react' export {theme} from '@primer/react' export {themeGet} from '@primer/react'