Skip to content

Commit

Permalink
[system] Support props callback in the variant's definition (mui#40094)
Browse files Browse the repository at this point in the history
  • Loading branch information
mnajdova authored Dec 21, 2023
1 parent 7c2f0ca commit d534dd5
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,33 @@ declare module '@mui/material/Button' {

{{"demo": "GlobalThemeVariants.js"}}

The variant `props` can also be defined as a callback.
This is useful if you want to apply styles when using negation in the condition.
In other words, applying a different style if a particular property doesn't have a specific value.

:::info
This feature is available from version 5.15.2.
:::

```js
const theme = createTheme({
components: {
MuiButton: {
variants: [
{
props: (props) =>
props.variant === 'dashed' && props.color !== 'secondary',
style: {
textTransform: 'none',
border: `2px dashed ${blue[500]}`,
},
},
],
},
},
});
```

## Theme variables

Another way to override the look of all component instances is to adjust the [theme configuration variables](/material-ui/customization/theming/#theme-configuration-variables).
Expand Down
2 changes: 1 addition & 1 deletion packages/mui-styled-engine-sc/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export interface CSSObject
Omit<CSSOthersObject, 'variants'> {}

interface CSSObjectWithVariants<Props> extends Omit<CSSObject, 'variants'> {
variants: Array<{ props: Props; variants: CSSObject }>;
variants: Array<{ props: Props; style: CSSObject }>;
}

export type FalseyValue = undefined | null | false;
Expand Down
2 changes: 1 addition & 1 deletion packages/mui-styled-engine/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export interface CSSObject
Omit<CSSOthersObject, 'variants'> {}

interface CSSObjectWithVariants<Props> extends Omit<CSSObject, 'variants'> {
variants: Array<{ props: Props; variants: CSSObject }>;
variants: Array<{ props: Props; style: CSSObject }>;
}

export interface ComponentSelector {
Expand Down
35 changes: 28 additions & 7 deletions packages/mui-system/src/createStyled.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,18 @@ const getStyleOverrides = (name, theme) => {
};

const transformVariants = (variants) => {
let numOfCallbacks = 0;
const variantsStyles = {};

if (variants) {
variants.forEach((definition) => {
const key = propsToClassKey(definition.props);
let key = '';
if (typeof definition.props === 'function') {
key = `callback${numOfCallbacks}`;
numOfCallbacks += 1;
} else {
key = propsToClassKey(definition.props);
}
variantsStyles[key] = definition.style;
});
}
Expand All @@ -57,17 +64,31 @@ const getVariantStyles = (name, theme) => {
const variantsResolver = (props, styles, variants) => {
const { ownerState = {} } = props;
const variantsStyles = [];
let numOfCallbacks = 0;

if (variants) {
variants.forEach((variant) => {
let isMatch = true;
Object.keys(variant.props).forEach((key) => {
if (ownerState[key] !== variant.props[key] && props[key] !== variant.props[key]) {
isMatch = false;
}
});
if (typeof variant.props === 'function') {
const propsToCheck = { ...props, ...ownerState };
isMatch = variant.props(propsToCheck);
} else {
Object.keys(variant.props).forEach((key) => {
if (ownerState[key] !== variant.props[key] && props[key] !== variant.props[key]) {
isMatch = false;
}
});
}
if (isMatch) {
variantsStyles.push(styles[propsToClassKey(variant.props)]);
if (typeof variant.props === 'function') {
variantsStyles.push(styles[`callback${numOfCallbacks}`]);
} else {
variantsStyles.push(styles[propsToClassKey(variant.props)]);
}
}

if (typeof variant.props === 'function') {
numOfCallbacks += 1;
}
});
}
Expand Down
118 changes: 114 additions & 4 deletions packages/mui-system/src/createStyled.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ describe('createStyled', () => {
Filled
</Test>
<Test data-testid="text" color="blue" variant="text">
Filled
Text
</Test>
</React.Fragment>,
);
Expand Down Expand Up @@ -473,14 +473,46 @@ describe('createStyled', () => {
Filled
</Test>
<Test data-testid="text" color="blue" variant="text">
Filled
Text
</Test>
</React.Fragment>,
);
expect(getByTestId('filled')).toHaveComputedStyle({ backgroundColor: 'rgb(0, 0, 255)' });
expect(getByTestId('text')).toHaveComputedStyle({ color: 'rgb(0, 0, 255)' });
});

it('should accept variants in function style arg with props usage', () => {
const styled = createStyled({
defaultTheme: {
colors: { blue: 'rgb(0, 0, 255)', red: 'rgb(255, 0, 0)', green: 'rgb(0, 255, 0)' },
},
});

const Test = styled('div')(({ theme, color }) => ({
variants: [
{
props: (props) => props.color !== 'blue',
style: {
backgroundColor: theme.colors[color],
},
},
],
}));

const { getByTestId } = render(
<React.Fragment>
<Test data-testid="red" color="red">
Filled
</Test>
<Test data-testid="green" color="green">
Text
</Test>
</React.Fragment>,
);
expect(getByTestId('green')).toHaveComputedStyle({ backgroundColor: 'rgb(0, 255, 0)' });
expect(getByTestId('red')).toHaveComputedStyle({ backgroundColor: 'rgb(255, 0, 0)' });
});

it('should accept variants in arrays', () => {
const styled = createStyled({ defaultTheme: { colors: { blue: 'rgb(0, 0, 255)' } } });

Expand Down Expand Up @@ -526,7 +558,7 @@ describe('createStyled', () => {
Filled
</Test>
<Test data-testid="text" color="blue" variant="text">
Filled
Text
</Test>
<Test data-testid="outlined" color="blue" variant="outlined">
Outlined
Expand Down Expand Up @@ -580,12 +612,90 @@ describe('createStyled', () => {
Filled
</Test>
<Test data-testid="text" color="blue" variant="text">
Filled
Text
</Test>
</ThemeProvider>,
);
expect(getByTestId('filled')).toHaveComputedStyle({ backgroundColor: 'rgb(0, 0, 255)' });
expect(getByTestId('text')).toHaveComputedStyle({ color: 'rgb(0, 0, 220)' });
});

it('should accept variants in function props arg', () => {
const styled = createStyled({ defaultTheme: { colors: { blue: 'rgb(0, 0, 255)' } } });

const Test = styled('div')(({ theme }) => ({
variants: [
{
props: (props) => props.color === 'blue' && props.variant === 'filled',
style: {
backgroundColor: theme.colors.blue,
},
},
{
props: (props) => props.color === 'blue' && props.variant === 'text',
style: {
color: theme.colors.blue,
},
},
],
}));

const { getByTestId } = render(
<React.Fragment>
<Test data-testid="filled" color="blue" variant="filled">
Filled
</Test>
<Test data-testid="text" color="blue" variant="text">
Text
</Test>
</React.Fragment>,
);
expect(getByTestId('filled')).toHaveComputedStyle({ backgroundColor: 'rgb(0, 0, 255)' });
expect(getByTestId('text')).toHaveComputedStyle({ color: 'rgb(0, 0, 255)' });
});

it('should accept variants with both object and function props arg', () => {
const styled = createStyled({ defaultTheme: { colors: { blue: 'rgb(0, 0, 255)' } } });

const Test = styled('div')(({ theme }) => ({
variants: [
{
props: (props) => props.color === 'blue' && props.variant === 'filled',
style: {
backgroundColor: theme.colors.blue,
},
},
{
props: { color: 'blue', variant: 'outlined' },
style: {
borderColor: theme.colors.blue,
},
},
{
props: (props) => props.color === 'blue' && props.variant === 'text',
style: {
color: theme.colors.blue,
},
},
],
}));

const { getByTestId } = render(
<React.Fragment>
<Test data-testid="filled" color="blue" variant="filled">
Filled
</Test>
<Test data-testid="outlined" color="blue" variant="outlined">
Outlined
</Test>
<Test data-testid="text" color="blue" variant="text">
Text
</Test>
</React.Fragment>,
);
expect(getByTestId('filled')).toHaveComputedStyle({ backgroundColor: 'rgb(0, 0, 255)' });
expect(getByTestId('outlined')).toHaveComputedStyle({ borderTopColor: 'rgb(0, 0, 255)' });
expect(getByTestId('text')).toHaveComputedStyle({ color: 'rgb(0, 0, 255)' });
});
});
});
44 changes: 44 additions & 0 deletions packages/mui-system/src/styled.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,50 @@ describe('styled', () => {
});
});

it('should support variants with props callbacks', () => {
const customTheme = createTheme({
components: {
MuiTest: {
variants: [
{
props: ({ size }) => size === 'large',
style: {
width: '400px',
height: '400px',
},
},
{
props: ({ size }) => size === 'small',
style: {
width: '200px',
height: '200px',
},
},
],
},
},
});
const { getByTestId } = render(
<ThemeProvider theme={customTheme}>
<TestObj data-testid="large" size="large">
Test
</TestObj>
<TestObj data-testid="small" size="small">
Test
</TestObj>
</ThemeProvider>,
);

expect(getByTestId('large')).toHaveComputedStyle({
width: '400px',
height: '400px',
});
expect(getByTestId('small')).toHaveComputedStyle({
width: '200px',
height: '200px',
});
});

it('should resolve the sx prop of object type', () => {
const { container } = render(
<ThemeProvider theme={theme}>
Expand Down

0 comments on commit d534dd5

Please sign in to comment.