Skip to content

Commit

Permalink
Handle theme method spreads in the sx prop
Browse files Browse the repository at this point in the history
  • Loading branch information
brijeshb42 committed Mar 7, 2024
1 parent 3a32e8d commit a1aa0a5
Show file tree
Hide file tree
Showing 11 changed files with 68 additions and 39 deletions.
4 changes: 2 additions & 2 deletions packages/pigment-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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');
Expand Down
1 change: 0 additions & 1 deletion packages/pigment-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
4 changes: 2 additions & 2 deletions packages/pigment-react/src/utils/pre-linaria-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Types.Expression>,
Expand All @@ -19,7 +19,7 @@ function replaceNodePath(
);
};

sxPropConvertor(expressionPath, wrapWithSxCall);
sxPropConverter(expressionPath, wrapWithSxCall);
}

export const babelPlugin = declare<{ propName?: string; importName?: string }>(
Expand Down
31 changes: 18 additions & 13 deletions packages/pigment-react/src/utils/sxObjectExtractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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.`,
);
}
}
Expand All @@ -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');
Expand All @@ -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.`,
);
}
});
Expand All @@ -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.`,
);
}
});
}
Expand All @@ -128,7 +133,7 @@ export function sxObjectExtractor(nodePath: NodePath<ObjectExpression | ArrowFun
const body = nodePath.get('body');
if (!body.isObjectExpression()) {
throw body.buildCodeFrameError(
"sx prop only supports arrow functions that directly return an object, e.g. () => ({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);
Expand Down
2 changes: 1 addition & 1 deletion packages/pigment-react/src/utils/sxPropConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function isAllowedExpression(
return node.isObjectExpression() || node.isArrowFunctionExpression();
}

export function sxPropConvertor(
export function sxPropConverter(
node: NodePath<Expression>,
wrapWithSxCall: (expPath: NodePath<Expression>) => void,
) {
Expand Down
10 changes: 5 additions & 5 deletions packages/pigment-react/tests/fixtures/styled.input.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', {
Expand All @@ -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};
`;
8 changes: 4 additions & 4 deletions packages/pigment-react/tests/fixtures/styled.output.css
Original file line number Diff line number Diff line change
@@ -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);}
14 changes: 14 additions & 0 deletions packages/pigment-react/tests/fixtures/sxProps2.input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { SliderRail } from './styled.input';

function App2(props) {
return (
<SliderRail
sx={({ theme }) => ({
mb: 1,
...theme.applyStyles('dark', {
color: 'white',
}),
})}
/>
);
}
1 change: 1 addition & 0 deletions packages/pigment-react/tests/fixtures/sxProps2.output.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.soujkwr.sqyvpfz{margin-bottom:8px;}@media (prefers-color-scheme: dark){.soujkwr.sqyvpfz{color:white;}}
4 changes: 4 additions & 0 deletions packages/pigment-react/tests/fixtures/sxProps2.output.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { SliderRail } from './styled.input';
function App2(props) {
return <SliderRail sx={"sqyvpfz"} />;
}
28 changes: 17 additions & 11 deletions packages/pigment-react/tests/pigment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,40 @@ 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',
},
},
},
},
};
cssVarPrefix: 'mui',
});

async function transformWithSx(code: string, filename: string) {
const babelResult = await transformAsync(code, {
Expand Down

0 comments on commit a1aa0a5

Please sign in to comment.