-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
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] Support variants in theme.styleOverrides
#40690
Merged
siriwatknp
merged 16 commits into
mui:master
from
siriwatknp:system/style-overrides-variants
Feb 6, 2024
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
f28ecf3
refactor createStyled to handle variants properly
siriwatknp efbfadb
fix all bugs
siriwatknp f605797
fix createStyled
siriwatknp 885d5b2
use deepmerge
siriwatknp 62f04ad
reduce functions to just processStyleArg
siriwatknp 5aa4c8e
restore
siriwatknp 783010d
fix variant callback
siriwatknp f2285c5
fix types
siriwatknp 3a3a2f8
Merge branch 'master' of https://github.com/mui/material-ui into syst…
siriwatknp 814fbc9
fix theme.variants style callback
siriwatknp c65c468
Merge branch 'master' of https://github.com/mui/material-ui into syst…
siriwatknp f69c1f4
fix component overrides type
siriwatknp 473e62d
replace deepmerge with array
siriwatknp 7d7c8a8
fix overrides type
siriwatknp f79c874
Merge branch 'master' of https://github.com/mui/material-ui into syst…
siriwatknp 9fdd007
fix overrides type to include ownerState
siriwatknp File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,7 @@ | ||
/* eslint-disable no-underscore-dangle */ | ||
import styledEngineStyled, { internal_processStyles as processStyles } from '@mui/styled-engine'; | ||
import { | ||
getDisplayName, | ||
unstable_capitalize as capitalize, | ||
isPlainObject, | ||
deepmerge, | ||
} from '@mui/utils'; | ||
import { getDisplayName, unstable_capitalize as capitalize, isPlainObject } from '@mui/utils'; | ||
import createTheme from './createTheme'; | ||
import propsToClassKey from './propsToClassKey'; | ||
import styleFunctionSx from './styleFunctionSx'; | ||
|
||
function isEmpty(obj) { | ||
|
@@ -25,82 +19,6 @@ function isStringTag(tag) { | |
); | ||
} | ||
|
||
const getStyleOverrides = (name, theme) => { | ||
if (theme.components && theme.components[name] && theme.components[name].styleOverrides) { | ||
return theme.components[name].styleOverrides; | ||
} | ||
|
||
return null; | ||
}; | ||
|
||
const transformVariants = (variants) => { | ||
let numOfCallbacks = 0; | ||
const variantsStyles = {}; | ||
|
||
if (variants) { | ||
variants.forEach((definition) => { | ||
let key = ''; | ||
if (typeof definition.props === 'function') { | ||
key = `callback${numOfCallbacks}`; | ||
numOfCallbacks += 1; | ||
} else { | ||
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; | ||
} | ||
|
||
return transformVariants(variants); | ||
}; | ||
|
||
const variantsResolver = (props, styles, variants) => { | ||
const { ownerState = {} } = props; | ||
const variantsStyles = []; | ||
let numOfCallbacks = 0; | ||
|
||
if (variants) { | ||
variants.forEach((variant) => { | ||
let isMatch = true; | ||
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) { | ||
if (typeof variant.props === 'function') { | ||
variantsStyles.push(styles[`callback${numOfCallbacks}`]); | ||
} else { | ||
variantsStyles.push(styles[propsToClassKey(variant.props)]); | ||
} | ||
} | ||
|
||
if (typeof variant.props === 'function') { | ||
numOfCallbacks += 1; | ||
} | ||
}); | ||
} | ||
|
||
return variantsStyles; | ||
}; | ||
|
||
const themeVariantsResolver = (props, styles, theme, name) => { | ||
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'; | ||
|
@@ -126,29 +44,49 @@ function defaultOverridesResolver(slot) { | |
return (props, styles) => styles[slot]; | ||
} | ||
|
||
const muiStyledFunctionResolver = ({ styledArg, props, defaultTheme, themeId }) => { | ||
const resolvedStyles = styledArg({ | ||
...props, | ||
theme: resolveTheme({ ...props, defaultTheme, themeId }), | ||
}); | ||
function processStyleArg(callableStyle, { ownerState, ...props }) { | ||
const resolvedStylesArg = | ||
typeof callableStyle === 'function' ? callableStyle({ ownerState, ...props }) : callableStyle; | ||
|
||
let optionalVariants; | ||
if (resolvedStyles && resolvedStyles.variants) { | ||
optionalVariants = resolvedStyles.variants; | ||
delete resolvedStyles.variants; | ||
} | ||
if (optionalVariants) { | ||
const variantsStyles = variantsResolver( | ||
props, | ||
transformVariants(optionalVariants), | ||
optionalVariants, | ||
if (Array.isArray(resolvedStylesArg)) { | ||
return resolvedStylesArg.flatMap((resolvedStyle) => | ||
processStyleArg(resolvedStyle, { ownerState, ...props }), | ||
); | ||
|
||
return [resolvedStyles, ...variantsStyles]; | ||
} | ||
|
||
return resolvedStyles; | ||
}; | ||
if ( | ||
!!resolvedStylesArg && | ||
typeof resolvedStylesArg === 'object' && | ||
Array.isArray(resolvedStylesArg.variants) | ||
) { | ||
const { variants = [], ...otherStyles } = resolvedStylesArg; | ||
let result = otherStyles; | ||
variants.forEach((variant) => { | ||
let isMatch = true; | ||
if (typeof variant.props === 'function') { | ||
isMatch = variant.props({ ownerState, ...props }); | ||
} else { | ||
Object.keys(variant.props).forEach((key) => { | ||
if (ownerState?.[key] !== variant.props[key] && props[key] !== variant.props[key]) { | ||
isMatch = false; | ||
} | ||
}); | ||
} | ||
if (isMatch) { | ||
if (!Array.isArray(result)) { | ||
result = [result]; | ||
} | ||
result.push( | ||
typeof variant.style === 'function' | ||
? variant.style({ ownerState, ...props }) | ||
: variant.style, | ||
); | ||
} | ||
}); | ||
return result; | ||
} | ||
return resolvedStylesArg; | ||
} | ||
|
||
export default function createStyled(input = {}) { | ||
const { | ||
|
@@ -217,105 +155,52 @@ export default function createStyled(input = {}) { | |
label, | ||
...options, | ||
}); | ||
const muiStyledResolver = (styleArg, ...expressions) => { | ||
const expressionsWithDefaultTheme = expressions | ||
? expressions.map((stylesArg) => { | ||
// 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. | ||
if (typeof stylesArg === 'function' && stylesArg.__emotion_real !== stylesArg) { | ||
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); | ||
}); | ||
|
||
return result; | ||
}; | ||
} | ||
return transformedStylesArg; | ||
} | ||
return stylesArg; | ||
}) | ||
: []; | ||
|
||
let transformedStyleArg = styleArg; | ||
|
||
if (isPlainObject(styleArg)) { | ||
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 | ||
|
||
const transformStyleArg = (stylesArg) => { | ||
// 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. | ||
if ( | ||
(typeof stylesArg === 'function' && stylesArg.__emotion_real !== stylesArg) || | ||
isPlainObject(stylesArg) | ||
) { | ||
// If the type is function, we need to define the default theme. | ||
transformedStyleArg = (props) => | ||
muiStyledFunctionResolver({ styledArg: styleArg, props, defaultTheme, themeId }); | ||
return (props) => | ||
processStyleArg(stylesArg, { | ||
...props, | ||
theme: resolveTheme({ theme: props.theme, defaultTheme, themeId }), | ||
}); | ||
} | ||
return stylesArg; | ||
}; | ||
const muiStyledResolver = (styleArg, ...expressions) => { | ||
let transformedStyleArg = transformStyleArg(styleArg); | ||
const expressionsWithDefaultTheme = expressions ? expressions.map(transformStyleArg) : []; | ||
|
||
if (componentName && overridesResolver) { | ||
expressionsWithDefaultTheme.push((props) => { | ||
const theme = resolveTheme({ ...props, defaultTheme, themeId }); | ||
const styleOverrides = getStyleOverrides(componentName, theme); | ||
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.
|
||
|
||
if (styleOverrides) { | ||
const resolvedStyleOverrides = {}; | ||
Object.entries(styleOverrides).forEach(([slotKey, slotStyle]) => { | ||
resolvedStyleOverrides[slotKey] = | ||
typeof slotStyle === 'function' ? slotStyle({ ...props, theme }) : slotStyle; | ||
}); | ||
return overridesResolver(props, resolvedStyleOverrides); | ||
if ( | ||
!theme.components || | ||
!theme.components[componentName] || | ||
!theme.components[componentName].styleOverrides | ||
) { | ||
return null; | ||
} | ||
|
||
return null; | ||
const styleOverrides = theme.components[componentName].styleOverrides; | ||
const resolvedStyleOverrides = {}; | ||
// TODO: v7 remove iteration and use `resolveStyleArg(styleOverrides[slot])` directly | ||
Object.entries(styleOverrides).forEach(([slotKey, slotStyle]) => { | ||
resolvedStyleOverrides[slotKey] = processStyleArg(slotStyle, { ...props, theme }); | ||
}); | ||
return overridesResolver(props, resolvedStyleOverrides); | ||
}); | ||
} | ||
|
||
if (componentName && !skipVariantsResolver) { | ||
expressionsWithDefaultTheme.push((props) => { | ||
const theme = resolveTheme({ ...props, defaultTheme, themeId }); | ||
return themeVariantsResolver( | ||
props, | ||
getVariantStyles(componentName, theme), | ||
theme, | ||
componentName, | ||
); | ||
const themeVariants = theme?.components?.[componentName]?.variants; | ||
return processStyleArg({ variants: themeVariants }, { ...props, theme }); | ||
}); | ||
} | ||
|
||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
I just noticed this
deepmerge
. I think we should remove it and let emotion/styled-components handle it. Both of them already support array of styles.@mnajdova Should we try to remove it? the perf should be a bit better while producing the same result.
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.
Hmm, would it work if we remove it? From what I remember some of these definitions are merged into one style arg, this is what I remember.
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.
Yes, it would work but there is a tiny difference in the dev tool
without merging style, you will see all the styles declared in the
variants
when props are matched:but the results (cascading) are the same.
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.
I'd not merge so that the logic does not depend on the
deepmerge
but rely on the style engine.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.
Have we tried removing the deepmerge and see if some test are failing?
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.
@mnajdova All green, see the change in this 473e62d