From a1aa0a5f4cf85bbd887fafe27960cf54ddce0202 Mon Sep 17 00:00:00 2001 From: Brijesh Bittu Date: Wed, 6 Mar 2024 12:27:39 +0530 Subject: [PATCH] Handle theme method spreads in the sx prop --- packages/pigment-react/README.md | 4 +-- packages/pigment-react/src/index.ts | 1 - .../src/utils/pre-linaria-plugin.ts | 4 +-- .../src/utils/sxObjectExtractor.ts | 31 +++++++++++-------- .../src/utils/sxPropConverter.ts | 2 +- .../tests/fixtures/styled.input.js | 10 +++--- .../tests/fixtures/styled.output.css | 8 ++--- .../tests/fixtures/sxProps2.input.js | 14 +++++++++ .../tests/fixtures/sxProps2.output.css | 1 + .../tests/fixtures/sxProps2.output.js | 4 +++ packages/pigment-react/tests/pigment.test.ts | 28 ++++++++++------- 11 files changed, 68 insertions(+), 39 deletions(-) create mode 100644 packages/pigment-react/tests/fixtures/sxProps2.input.js create mode 100644 packages/pigment-react/tests/fixtures/sxProps2.output.css create mode 100644 packages/pigment-react/tests/fixtures/sxProps2.output.js diff --git a/packages/pigment-react/README.md b/packages/pigment-react/README.md index 74c5468d771e95..bcc74baef96a77 100644 --- a/packages/pigment-react/README.md +++ b/packages/pigment-react/README.md @@ -217,7 +217,7 @@ const Heading = styled('h1')({ }); ``` -Zero-runtime will replace the callback with a CSS variable and inject the value through inline style. This makes it possible to create a static CSS file while still allowing dynamic styles. +Pigment CSS will replace the callback with a CSS variable and inject the value through inline style. This makes it possible to create a static CSS file while still allowing dynamic styles. ```css .Heading_class_akjsdfb { @@ -322,7 +322,7 @@ const Heading = styled('h1')(({ theme }) => ({ #### CSS variables support -Zero-runtime can generate CSS variables from the theme values when you wrap your theme with `extendTheme` utility. For example, in a `next.config.js` file: +Pigment CSS can generate CSS variables from the theme values when you wrap your theme with `extendTheme` utility. For example, in a `next.config.js` file: ```js const { withPigment, extendTheme } = require('@pigment-css/nextjs-plugin'); diff --git a/packages/pigment-react/src/index.ts b/packages/pigment-react/src/index.ts index 25f0d5f3772c7f..52c2a5e4bc993f 100644 --- a/packages/pigment-react/src/index.ts +++ b/packages/pigment-react/src/index.ts @@ -4,4 +4,3 @@ export { default as keyframes } from './keyframes'; export { generateAtomics, atomics } from './generateAtomics'; export { default as css } from './css'; export { default as createUseThemeProps } from './createUseThemeProps'; -export { clsx } from 'clsx'; diff --git a/packages/pigment-react/src/utils/pre-linaria-plugin.ts b/packages/pigment-react/src/utils/pre-linaria-plugin.ts index 08eb51cbea9e86..a2e59449d60ba2 100644 --- a/packages/pigment-react/src/utils/pre-linaria-plugin.ts +++ b/packages/pigment-react/src/utils/pre-linaria-plugin.ts @@ -2,7 +2,7 @@ import { addNamed } from '@babel/helper-module-imports'; import { declare } from '@babel/helper-plugin-utils'; import { NodePath } from '@babel/core'; import * as Types from '@babel/types'; -import { sxPropConvertor } from './sxPropConverter'; +import { sxPropConverter } from './sxPropConverter'; function replaceNodePath( expressionPath: NodePath, @@ -19,7 +19,7 @@ function replaceNodePath( ); }; - sxPropConvertor(expressionPath, wrapWithSxCall); + sxPropConverter(expressionPath, wrapWithSxCall); } export const babelPlugin = declare<{ propName?: string; importName?: string }>( diff --git a/packages/pigment-react/src/utils/sxObjectExtractor.ts b/packages/pigment-react/src/utils/sxObjectExtractor.ts index c5dc53b8b078c8..28f5866781f3e5 100644 --- a/packages/pigment-react/src/utils/sxObjectExtractor.ts +++ b/packages/pigment-react/src/utils/sxObjectExtractor.ts @@ -23,7 +23,9 @@ function validateObjectKey( return; } if (!parentCall) { - throw keyPath.buildCodeFrameError('Expressions in css object keys are not supported.'); + throw keyPath.buildCodeFrameError( + `${process.env.PACKAGE_NAME}: Expressions in css object keys are not supported.`, + ); } if ( !identifiers.every((item) => { @@ -41,7 +43,7 @@ function validateObjectKey( }) ) { throw keyPath.buildCodeFrameError( - 'Variables in css object keys should only use the passed theme(s) object or variables that are defined in the root scope.', + `${process.env.PACKAGE_NAME}: Variables in css object keys should only use the passed theme(s) object or variables that are defined in the root scope.`, ); } } @@ -59,14 +61,14 @@ function traverseObjectExpression( const value = property.get('value'); if (!value.isExpression()) { throw value.buildCodeFrameError( - 'This value is not supported. It can only be static values or local variables.', + `${process.env.PACKAGE_NAME}: This value is not supported. It can only be static values or local variables.`, ); } if (value.isObjectExpression()) { traverseObjectExpression(value, parentCall); } else if (value.isArrowFunctionExpression()) { throw value.buildCodeFrameError( - 'Arrow functions are not supported as values of sx object.', + `${process.env.PACKAGE_NAME}: Arrow functions are not supported as values of sx object.`, ); } else if (!value.isLiteral() && !isStaticObjectOrArrayExpression(value)) { const identifiers = findIdentifiers([value], 'reference'); @@ -86,7 +88,7 @@ function traverseObjectExpression( localIdentifiers.push(id); } else { throw id.buildCodeFrameError( - 'Consider moving this variable to the root scope if it has all static values.', + `${process.env.PACKAGE_NAME}: Consider moving this variable to the root scope if it has all static values.`, ); } }); @@ -103,20 +105,23 @@ function traverseObjectExpression( if ( !identifiers.every((id) => { const binding = property.scope.getBinding(id.node.name); - if (!binding || binding.scope !== rootScope) { - return false; - } - return true; + // the indentifier definition should either be in the root scope or in the same scope + // as the object property, ie, ({theme}) => ({...theme.applyStyles()}) + return binding && (binding.scope === rootScope || binding.scope === property.scope); }) ) { throw property.buildCodeFrameError( - 'You can only use variables that are defined in the root scope of the file.', + `${process.env.PACKAGE_NAME}: You can only use variables in the spread that are defined in the root scope of the file.`, ); } } else if (property.isObjectMethod()) { - throw property.buildCodeFrameError('sx prop object does not support ObjectMethods.'); + throw property.buildCodeFrameError( + `${process.env.PACKAGE_NAME}: sx prop object does not support ObjectMethods.`, + ); } else { - throw property.buildCodeFrameError('Unknown property in object.'); + throw property.buildCodeFrameError( + `${process.env.PACKAGE_NAME}: Unknown property in object.`, + ); } }); } @@ -128,7 +133,7 @@ export function sxObjectExtractor(nodePath: NodePath ({color: 'red'}). You can accept theme object in the params if required.", + `${process.env.PACKAGE_NAME}: sx prop only supports arrow functions directly returning an object, e.g. () => ({color: 'red'}). You can accept theme object in the params if required.`, ); } traverseObjectExpression(body, nodePath); diff --git a/packages/pigment-react/src/utils/sxPropConverter.ts b/packages/pigment-react/src/utils/sxPropConverter.ts index c01ddea2b3787d..838184239a2b19 100644 --- a/packages/pigment-react/src/utils/sxPropConverter.ts +++ b/packages/pigment-react/src/utils/sxPropConverter.ts @@ -8,7 +8,7 @@ function isAllowedExpression( return node.isObjectExpression() || node.isArrowFunctionExpression(); } -export function sxPropConvertor( +export function sxPropConverter( node: NodePath, wrapWithSxCall: (expPath: NodePath) => void, ) { diff --git a/packages/pigment-react/tests/fixtures/styled.input.js b/packages/pigment-react/tests/fixtures/styled.input.js index 949c7e6c7cb3e6..a81455d24d914e 100644 --- a/packages/pigment-react/tests/fixtures/styled.input.js +++ b/packages/pigment-react/tests/fixtures/styled.input.js @@ -10,13 +10,13 @@ const rotateKeyframe = keyframes({ }); const Component = styled.div(({ theme }) => ({ - color: theme.palette.primary.main, + color: (theme.vars ?? theme).palette.primary.main, animation: `${rotateKeyframe} 2s ease-out 0s infinite`, })); const cls1 = css` - color: ${({ theme }) => theme.palette.primary.main}; - font-size: ${({ theme }) => theme.size.font.h1}; + color: ${({ theme }) => (theme.vars ?? theme).palette.primary.main}; + font-size: ${({ theme }) => (theme.vars ?? theme).size.font.h1}; `; export const SliderRail = styled('span', { @@ -28,11 +28,11 @@ export const SliderRail = styled('span', { border-radius: inherit; background-color: currentColor; opacity: 0.38; - font-size: ${({ theme }) => theme.size.font.h1}; + font-size: ${({ theme }) => (theme.vars ?? theme).size.font.h1}; `; const SliderRail2 = styled.span` display: block; opacity: 0.38; - font-size: ${({ theme }) => theme.size.font.h1}; + font-size: ${({ theme }) => (theme.vars ?? theme).size.font.h1}; `; diff --git a/packages/pigment-react/tests/fixtures/styled.output.css b/packages/pigment-react/tests/fixtures/styled.output.css index 64f8bb93f42fc4..3f2285743dffc4 100644 --- a/packages/pigment-react/tests/fixtures/styled.output.css +++ b/packages/pigment-react/tests/fixtures/styled.output.css @@ -1,6 +1,6 @@ @keyframes r1ub6j9g{from{transform:rotate(360deg);}to{transform:rotate(0deg);}} -.c1y26wbb{color:red;animation:r1ub6j9g 2s ease-out 0s infinite;} -.ct00dwm{color:red;font-size:3rem;} -.soujkwr{display:block;position:absolute;border-radius:inherit;background-color:currentColor;opacity:0.38;font-size:3rem;} +.c1y26wbb{color:var(--mui-palette-primary-main);animation:r1ub6j9g 2s ease-out 0s infinite;} +.ct00dwm{color:var(--mui-palette-primary-main);font-size:var(--mui-size-font-h1);} +.soujkwr{display:block;position:absolute;border-radius:inherit;background-color:currentColor;opacity:0.38;font-size:var(--mui-size-font-h1);} .soujkwr-1{font-size:3rem;} -.s14dtw5g{display:block;opacity:0.38;font-size:3rem;} +.s14dtw5g{display:block;opacity:0.38;font-size:var(--mui-size-font-h1);} diff --git a/packages/pigment-react/tests/fixtures/sxProps2.input.js b/packages/pigment-react/tests/fixtures/sxProps2.input.js new file mode 100644 index 00000000000000..db0d60d71d12f1 --- /dev/null +++ b/packages/pigment-react/tests/fixtures/sxProps2.input.js @@ -0,0 +1,14 @@ +import { SliderRail } from './styled.input'; + +function App2(props) { + return ( + ({ + mb: 1, + ...theme.applyStyles('dark', { + color: 'white', + }), + })} + /> + ); +} diff --git a/packages/pigment-react/tests/fixtures/sxProps2.output.css b/packages/pigment-react/tests/fixtures/sxProps2.output.css new file mode 100644 index 00000000000000..077f9b1e76b507 --- /dev/null +++ b/packages/pigment-react/tests/fixtures/sxProps2.output.css @@ -0,0 +1 @@ +.soujkwr.sqyvpfz{margin-bottom:8px;}@media (prefers-color-scheme: dark){.soujkwr.sqyvpfz{color:white;}} diff --git a/packages/pigment-react/tests/fixtures/sxProps2.output.js b/packages/pigment-react/tests/fixtures/sxProps2.output.js new file mode 100644 index 00000000000000..00f52dd87d649b --- /dev/null +++ b/packages/pigment-react/tests/fixtures/sxProps2.output.js @@ -0,0 +1,4 @@ +import { SliderRail } from './styled.input'; +function App2(props) { + return ; +} diff --git a/packages/pigment-react/tests/pigment.test.ts b/packages/pigment-react/tests/pigment.test.ts index 9fae7017657331..b37ba5a4dfb9f2 100644 --- a/packages/pigment-react/tests/pigment.test.ts +++ b/packages/pigment-react/tests/pigment.test.ts @@ -4,26 +4,31 @@ import { expect } from 'chai'; import { asyncResolveFallback } from '@wyw-in-js/shared'; import { transformAsync } from '@babel/core'; import { TransformCacheCollection, transform, createFileReporter } from '@wyw-in-js/transform'; -import { preprocessor } from '@pigment-css/react/utils'; +import { preprocessor, extendTheme } from '@pigment-css/react/utils'; import sxTransformPlugin from '../exports/sx-plugin'; const files = fs .readdirSync(path.join(__dirname, 'fixtures')) .filter((file) => file.endsWith('.input.js')); -const theme = { - palette: { - primary: { - main: 'red', - }, - }, - size: { - font: { - h1: '3rem', +const theme = extendTheme({ + colorSchemes: { + light: { + palette: { + primary: { + main: 'red', + }, + }, + size: { + font: { + h1: '3rem', + }, + }, }, }, components: { MuiSlider: { + defaultProps: {}, styleOverrides: { rail: { fontSize: '3rem', @@ -31,7 +36,8 @@ const theme = { }, }, }, -}; + cssVarPrefix: 'mui', +}); async function transformWithSx(code: string, filename: string) { const babelResult = await transformAsync(code, {