Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/mui/pigment-css into poc/…
Browse files Browse the repository at this point in the history
…sx-runtime
  • Loading branch information
siriwatknp committed May 1, 2024
2 parents b3ea954 + 72a14f8 commit ace4f80
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 13 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# [Versions](https://mui.com/versions/)

## v0.0.10

<!-- generated comparing v0.0.9..master -->

_May 1, 2024_

A big thanks to the 2 contributors who made this release possible.

- [react] Fix styled inheritance (#40) @siriwatknp
- [react] Fix prop forwarding when `as` component is provided to HTML styled component (#43) @siriwatknp
- [nextjs] Transform `!important` CSS to an intermediate representation (#38) @brijeshb42

All contributors of this release in alphabetical order: @brijeshb42, @siriwatknp

## v0.0.9

<!-- generated comparing v0.0.8..master -->
Expand Down
2 changes: 1 addition & 1 deletion packages/pigment-css-nextjs-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pigment-css/nextjs-plugin",
"version": "0.0.9",
"version": "0.0.10",
"main": "build/index.js",
"module": "build/index.mjs",
"types": "build/index.d.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ export const loader = function virtualFileLoader() {
const callback = this.async();
const resourceQuery = this.resourceQuery.slice(1);
const { source } = JSON.parse(decodeURIComponent(resourceQuery));
return callback(null, source);
return callback(null, source.replaceAll('__IMP__', '!important'));
};
2 changes: 1 addition & 1 deletion packages/pigment-css-react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pigment-css/react",
"version": "0.0.9",
"version": "0.0.10",
"main": "build/index.js",
"module": "build/index.mjs",
"types": "build/index.d.ts",
Expand Down
29 changes: 22 additions & 7 deletions packages/pigment-css-react/src/styled.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-underscore-dangle */
import * as React from 'react';
import clsx from 'clsx';
import isPropValid from '@emotion/is-prop-valid';
Expand Down Expand Up @@ -58,7 +59,12 @@ export default function styled(tag, componentMeta = {}) {
finalShouldForwardProp = slotShouldForwardProp;
}
}
const shouldUseAs = !finalShouldForwardProp('as');
let shouldUseAs = !finalShouldForwardProp('as');
if (typeof tag !== 'string' && tag.__styled_by_pigment_css) {
// If the tag is a Pigment styled component,
// render the styled component and pass the `as` prop down
shouldUseAs = false;
}
/**
* This is the runtime `styled` function that finally renders the component
* after transpilation through WyW-in-JS. It makes sure to add the base classes,
Expand All @@ -80,11 +86,11 @@ export default function styled(tag, componentMeta = {}) {
const { displayName, classes = [], vars: cssVars = {}, variants = [] } = options;

const StyledComponent = React.forwardRef(function StyledComponent(inProps, ref) {
const { as, className, style, ownerState, ...props } = inProps;
const Component = (shouldUseAs && as) || tag;
const { className, sx, style, ownerState, ...props } = inProps;
const Component = (shouldUseAs && inProps.as) || tag;
const varStyles = Object.entries(cssVars).reduce(
(acc, [cssVariable, [variableFunction, isUnitLess]]) => {
const value = variableFunction(props);
const value = variableFunction(inProps);
if (typeof value === 'undefined') {
return acc;
}
Expand All @@ -100,14 +106,25 @@ export default function styled(tag, componentMeta = {}) {

const finalClassName = clsx(classes, className, getVariantClasses(inProps, variants));

if (inProps.as && !shouldForwardProp) {
// Reassign `shouldForwardProp` if incoming `as` prop is a React component
if (!isHtmlTag(Component)) {
if (slot === 'Root' || slot === 'root') {
finalShouldForwardProp = rootShouldForwardProp;
} else {
finalShouldForwardProp = slotShouldForwardProp;
}
}
}

const newProps = {};
// eslint-disable-next-line no-restricted-syntax
for (const key in props) {
if (shouldUseAs && key === 'as') {
continue;
}

if (finalShouldForwardProp(key)) {
if (finalShouldForwardProp(key) || (!shouldUseAs && key === 'as')) {
newProps[key] = props[key];
}
}
Expand All @@ -116,7 +133,6 @@ export default function styled(tag, componentMeta = {}) {
<Component
{...newProps}
// pass down `ownerState` to nested styled components
// eslint-disable-next-line no-underscore-dangle
{...(Component.__styled_by_pigment_css && { ownerState })}
ref={ref}
className={finalClassName}
Expand All @@ -133,7 +149,6 @@ export default function styled(tag, componentMeta = {}) {
componentName = `${name}${slot ? `-${slot}` : ''}`;
}
StyledComponent.displayName = `Styled(${componentName})`;
// eslint-disable-next-line no-underscore-dangle
StyledComponent.__styled_by_pigment_css = true;

return StyledComponent;
Expand Down
187 changes: 187 additions & 0 deletions packages/pigment-css-react/tests/styled/runtime-styled.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,191 @@ describe('props filtering', () => {
expect(container.firstChild).to.have.class('root-123');
});
});

describe('as', () => {
it("child's classes still propagate to its parent", () => {
const StyledChild = styled('span')({
classes: ['child'],
});

const StyledParent = styled(StyledChild)({
classes: ['parent'],
});

const { container } = render(<StyledParent as="div" />);
expect(container.firstChild).to.have.class('child');
});

it("child's variants still propagate to its parent", () => {
const StyledChild = styled('span')({
classes: ['child'],
variants: [
{
props: ({ ownerState }) => ownerState.multiline,
className: 'multiline',
},
],
});

const StyledParent = styled(StyledChild)({
classes: ['parent'],
});

const { container } = render(<StyledParent as="div" ownerState={{ multiline: true }} />);
expect(container.firstChild).to.have.class('multiline');
});

it("child's vars still propagate to its parent", () => {
const StyledChild = styled('span')({
classes: ['child'],
vars: {
foo: [(props) => props.ownerState.width, false],
},
});

const StyledParent = styled(StyledChild)({
classes: ['parent'],
});

const { container } = render(<StyledParent as="div" ownerState={{ width: 300 }} />);
expect(container.firstChild).to.have.style('--foo', '300px');
});

it('use component forward prop if provided `as` is a component', () => {
const StyledDiv = styled('div')({
classes: ['root'],
});

function Component({ TagComponent = 'span', ...props }) {
return <TagComponent {...props} />;
}

const { container } = render(<StyledDiv as={Component} TagComponent="button" disabled />);

expect(container.firstChild).to.have.tagName('button');
});

it('should forward `as` prop', () => {
// The components below is a simplified version of the `NativeSelect` component from Material UI.

const InputBaseRoot = styled('div', { name: 'MuiInputBase', slot: 'Root' })({
classes: ['InputBase-root'],
});

const InputBaseInput = styled('input', { name: 'MuiInputBase', slot: 'Input' })({
classes: ['InputBase-input'],
});

function InputBase({
inputComponent = 'input',
slots = {},
slotProps = {},
inputProps: inputPropsProp = {},
}) {
const RootSlot = slots.root || InputBaseRoot;
const rootProps = slotProps.root || {};

const InputComponent = inputComponent;

const InputSlot = slots.input || InputBaseInput;
const inputProps = { ...inputPropsProp, ...slotProps.input };
return (
<RootSlot
{...rootProps}
{...(typeof Root !== 'string' && {
ownerState: rootProps.ownerState,
})}
>
<InputSlot
{...inputProps}
{...(typeof Input !== 'string' && {
as: InputComponent,
ownerState: inputProps.ownerState,
})}
/>
</RootSlot>
);
}

const InputRoot = styled(InputBaseRoot, { name: 'MuiInput', slot: 'Root' })({
classes: ['Input-root'],
});
const InputInput = styled(InputBaseInput, { name: 'MuiInput', slot: 'Input' })({
classes: ['Input-input'],
});
function Input({
inputComponent = 'input',
multiline = false,
slotProps,
slots = {},
type,
...other
}) {
const RootSlot = slots.root ?? InputRoot;
const InputSlot = slots.input ?? InputInput;
return (
<InputBase
slots={{ root: RootSlot, input: InputSlot }}
slotProps={slotProps}
inputComponent={inputComponent}
multiline={multiline}
type={type}
{...other}
/>
);
}

const defaultInput = <Input />;
const NativeSelectSelect = styled('select', {
name: 'MuiNativeSelect',
slot: 'Select',
})({
classes: ['NativeSelect-select'],
});
function NativeSelectInput(props) {
const { className, disabled, error, variant = 'standard', ...other } = props;

const ownerState = {
...props,
disabled,
variant,
error,
};

return (
<NativeSelectSelect
ownerState={ownerState}
className={className}
disabled={disabled}
{...other}
/>
);
}
function NativeSelect({ className, children, input = defaultInput, inputProps, ...other }) {
return React.cloneElement(input, {
inputComponent: NativeSelectInput,
inputProps: {
children,
type: undefined, // We render a select. We can ignore the type provided by the `Input`.
...inputProps,
...(input ? input.props.inputProps : {}),
},
...other,
className: `${input.props.className} ${className}`,
});
}

const { container } = render(
<NativeSelect>
<option value="foo">Foo</option>
<option value="bar">Bar</option>
</NativeSelect>,
);
expect(container.firstChild).to.have.tagName('div');
expect(container.firstChild).to.have.class('InputBase-root', 'Input-root');

expect(container.firstChild.firstChild).to.have.tagName('select');
expect(container.firstChild.firstChild).to.have.class('InputBase-input', 'Input-input');
});
});
});
2 changes: 1 addition & 1 deletion packages/pigment-css-unplugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pigment-css/unplugin",
"version": "0.0.9",
"version": "0.0.10",
"main": "build/index.js",
"module": "build/index.mjs",
"types": "build/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/pigment-css-unplugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ export const plugin = createUnplugin<PigmentOptions, true>((options) => {
const data = `${meta.placeholderCssFile}?${encodeURIComponent(
JSON.stringify({
filename: id.split('/').pop(),
source: cssText,
source: cssText.replaceAll('!important', '__IMP__'),
}),
)}`;
return {
Expand Down
1 change: 1 addition & 0 deletions packages/pigment-css-unplugin/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"compilerOptions": {
"resolveJsonModule": true,
"target": "ES2022",
"lib": ["ES2021", "DOM"],
"paths": {
"@babel/core": ["./node_modules/@babel/core"],
"@pigment-css/react": ["./packages/pigment-css-react/src"],
Expand Down
2 changes: 1 addition & 1 deletion packages/pigment-css-vite-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pigment-css/vite-plugin",
"version": "0.0.9",
"version": "0.0.10",
"main": "build/index.js",
"module": "build/index.mjs",
"types": "build/index.d.ts",
Expand Down

0 comments on commit ace4f80

Please sign in to comment.