-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[system] Add support for variants
in the styled() util
#39073
Changes from all commits
f10f4e1
0414435
940164b
3fde4f6
f4927a4
30045bd
1c252b7
1eab4f9
4fba8b4
0651611
7887bad
017b00c
12e77b1
8e0f65b
0bdfd5a
8cf423b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,11 @@ | ||
/* eslint-disable no-underscore-dangle */ | ||
import styledEngineStyled, { internal_processStyles as processStyles } from '@mui/styled-engine'; | ||
import { getDisplayName, unstable_capitalize as capitalize } from '@mui/utils'; | ||
import { | ||
getDisplayName, | ||
unstable_capitalize as capitalize, | ||
isPlainObject, | ||
deepmerge, | ||
} from '@mui/utils'; | ||
import createTheme from './createTheme'; | ||
import propsToClassKey from './propsToClassKey'; | ||
import styleFunctionSx from './styleFunctionSx'; | ||
|
@@ -28,43 +33,53 @@ const getStyleOverrides = (name, theme) => { | |
return null; | ||
}; | ||
|
||
const transformVariants = (variants) => { | ||
const variantsStyles = {}; | ||
|
||
if (variants) { | ||
variants.forEach((definition) => { | ||
const key = propsToClassKey(definition.props); | ||
variantsStyles[key] = definition.style; | ||
}); | ||
} | ||
|
||
return variantsStyles; | ||
}; | ||
const getVariantStyles = (name, theme) => { | ||
let variants = []; | ||
if (theme && theme.components && theme.components[name] && theme.components[name].variants) { | ||
variants = theme.components[name].variants; | ||
} | ||
|
||
const variantsStyles = {}; | ||
|
||
variants.forEach((definition) => { | ||
const key = propsToClassKey(definition.props); | ||
variantsStyles[key] = definition.style; | ||
}); | ||
|
||
return variantsStyles; | ||
return transformVariants(variants); | ||
}; | ||
|
||
const variantsResolver = (props, styles, theme, name) => { | ||
const variantsResolver = (props, styles, variants) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simplified signature to be re-used in both theme & styled's variants |
||
const { ownerState = {} } = props; | ||
const variantsStyles = []; | ||
const themeVariants = theme?.components?.[name]?.variants; | ||
if (themeVariants) { | ||
themeVariants.forEach((themeVariant) => { | ||
|
||
if (variants) { | ||
variants.forEach((variant) => { | ||
let isMatch = true; | ||
Object.keys(themeVariant.props).forEach((key) => { | ||
if (ownerState[key] !== themeVariant.props[key] && props[key] !== themeVariant.props[key]) { | ||
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(themeVariant.props)]); | ||
variantsStyles.push(styles[propsToClassKey(variant.props)]); | ||
} | ||
}); | ||
} | ||
|
||
return variantsStyles; | ||
}; | ||
|
||
const themeVariantsResolver = (props, styles, theme, name) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the legacy |
||
const themeVariants = theme?.components?.[name]?.variants; | ||
return variantsResolver(props, styles, themeVariants); | ||
}; | ||
|
||
// Update /system/styled/#api in case if this changes | ||
export function shouldForwardProp(prop) { | ||
return prop !== 'ownerState' && prop !== 'theme' && prop !== 'sx' && prop !== 'as'; | ||
|
@@ -90,6 +105,30 @@ function defaultOverridesResolver(slot) { | |
return (props, styles) => styles[slot]; | ||
} | ||
|
||
const muiStyledFunctionResolver = ({ styledArg, props, defaultTheme, themeId }) => { | ||
const resolvedStyles = styledArg({ | ||
...props, | ||
theme: resolveTheme({ ...props, defaultTheme, themeId }), | ||
}); | ||
|
||
let optionalVariants; | ||
if (resolvedStyles && resolvedStyles.variants) { | ||
optionalVariants = resolvedStyles.variants; | ||
delete resolvedStyles.variants; | ||
} | ||
if (optionalVariants) { | ||
const variantsStyles = variantsResolver( | ||
props, | ||
transformVariants(optionalVariants), | ||
optionalVariants, | ||
); | ||
|
||
return [resolvedStyles, ...variantsStyles]; | ||
} | ||
|
||
return resolvedStyles; | ||
}; | ||
|
||
export default function createStyled(input = {}) { | ||
const { | ||
themeId, | ||
|
@@ -163,19 +202,72 @@ export default function createStyled(input = {}) { | |
// On the server Emotion doesn't use React.forwardRef for creating components, so the created | ||
// component stays as a function. This condition makes sure that we do not interpolate functions | ||
// which are basically components used as a selectors. | ||
return typeof stylesArg === 'function' && stylesArg.__emotion_real !== stylesArg | ||
? (props) => { | ||
return stylesArg({ | ||
...props, | ||
theme: resolveTheme({ ...props, defaultTheme, themeId }), | ||
if (typeof stylesArg === 'function' && stylesArg.__emotion_real !== stylesArg) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding the variants styled in case arrays are used for defining multiple styles. |
||
return (props) => | ||
muiStyledFunctionResolver({ styledArg: stylesArg, props, defaultTheme, themeId }); | ||
} | ||
if (isPlainObject(stylesArg)) { | ||
let transformedStylesArg = stylesArg; | ||
let styledArgVariants; | ||
|
||
if (stylesArg && stylesArg.variants) { | ||
styledArgVariants = stylesArg.variants; | ||
delete transformedStylesArg.variants; | ||
|
||
transformedStylesArg = (props) => { | ||
let result = stylesArg; | ||
const variantStyles = variantsResolver( | ||
props, | ||
transformVariants(styledArgVariants), | ||
styledArgVariants, | ||
); | ||
variantStyles.forEach((variantStyle) => { | ||
result = deepmerge(result, variantStyle); | ||
}); | ||
} | ||
: stylesArg; | ||
|
||
return result; | ||
}; | ||
} | ||
return transformedStylesArg; | ||
} | ||
return stylesArg; | ||
}) | ||
: []; | ||
|
||
let transformedStyleArg = styleArg; | ||
|
||
if (isPlainObject(styleArg)) { | ||
mnajdova marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let styledArgVariants; | ||
if (styleArg && styleArg.variants) { | ||
styledArgVariants = styleArg.variants; | ||
delete transformedStyleArg.variants; | ||
|
||
transformedStyleArg = (props) => { | ||
let result = styleArg; | ||
const variantStyles = variantsResolver( | ||
props, | ||
transformVariants(styledArgVariants), | ||
styledArgVariants, | ||
); | ||
variantStyles.forEach((variantStyle) => { | ||
result = deepmerge(result, variantStyle); | ||
}); | ||
|
||
return result; | ||
}; | ||
} | ||
} else if ( | ||
typeof styleArg === 'function' && | ||
// On the server Emotion doesn't use React.forwardRef for creating components, so the created | ||
// component stays as a function. This condition makes sure that we do not interpolate functions | ||
// which are basically components used as a selectors. | ||
styleArg.__emotion_real !== styleArg | ||
) { | ||
// If the type is function, we need to define the default theme. | ||
transformedStyleArg = (props) => | ||
muiStyledFunctionResolver({ styledArg: styleArg, props, defaultTheme, themeId }); | ||
} | ||
|
||
if (componentName && overridesResolver) { | ||
expressionsWithDefaultTheme.push((props) => { | ||
const theme = resolveTheme({ ...props, defaultTheme, themeId }); | ||
|
@@ -197,7 +289,7 @@ export default function createStyled(input = {}) { | |
if (componentName && !skipVariantsResolver) { | ||
expressionsWithDefaultTheme.push((props) => { | ||
const theme = resolveTheme({ ...props, defaultTheme, themeId }); | ||
return variantsResolver( | ||
return themeVariantsResolver( | ||
props, | ||
getVariantStyles(componentName, theme), | ||
theme, | ||
|
@@ -217,21 +309,7 @@ export default function createStyled(input = {}) { | |
// If the type is array, than we need to add placeholders in the template for the overrides, variants and the sx styles. | ||
transformedStyleArg = [...styleArg, ...placeholders]; | ||
transformedStyleArg.raw = [...styleArg.raw, ...placeholders]; | ||
} else if ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was moved above, so that we can do a transformation only once. |
||
typeof styleArg === 'function' && | ||
// On the server Emotion doesn't use React.forwardRef for creating components, so the created | ||
// component stays as a function. This condition makes sure that we do not interpolate functions | ||
// which are basically components used as a selectors. | ||
styleArg.__emotion_real !== styleArg | ||
) { | ||
// If the type is function, we need to define the default theme. | ||
transformedStyleArg = (props) => | ||
styleArg({ | ||
...props, | ||
theme: resolveTheme({ ...props, defaultTheme, themeId }), | ||
}); | ||
} | ||
|
||
const Component = defaultStyledResolver(transformedStyleArg, ...expressionsWithDefaultTheme); | ||
|
||
if (process.env.NODE_ENV !== 'production') { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extracted so that it can be re-used in multiple places