-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
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
[Button] Custom variant #21648
[Button] Custom variant #21648
Conversation
packages/material-ui-styles/src/getStylesCreator/getStylesCreator.js
Outdated
Show resolved
Hide resolved
@material-ui/core: parsed: +0.30% , gzip: +0.29% |
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.
A quick initial review
- How will the developers know which prop they can extend in the theme?
- Do you have a list of prior-art on this problem? I think that it would be really great to check out how other UI libraries have attempts to solve the problem in the past. I wanted to put a list together but I'm running out of time, time for a 🏃♂️
packages/material-ui-styles/src/getStylesCreator/getStylesCreator.js
Outdated
Show resolved
Hide resolved
My initial thought was that we should provide a way for clients to add custom variants/colors, for which they can use the classes key for defining the additional styles
Updated the PR description with my first round of bench marking 👍 |
I gave this problem more thought. It feels that if we want to have style engine independent, we will need to abstract away from the prop to class name mapping logic. A wild idea: theme.variants = {
MuiButton: [
{
prop: 'variant',
trigger: 'red',
styles: `
color: red;
`
},
{
prop: 'size',
trigger: 'xxsmall',
styles: `
color: red;
`
},
{
prop: 'toggle',
trigger: true,
styles: `
color: red;
`
},
{
prop: 'toggle',
trigger: (props, state) => props.toggle === 'red',
styles: `
color: red;
`
}
],
} |
I like this idea, although if we have a trigger I would argue why we need the prop.. Maybe a slightly better structure can be prop + value, or just trigger. Although, if we can define anything with the trigger fn I am not sure why we need to expose different APIs... So maybe something like this:
|
@mnajdova To be honest, I was making a wild guess. A couple of limitations I see with my previous exploration:
The idea was to use it to avoid forwarding extra props into the DOM nodes, but maybe is-prop-valid could serve the same purpose. |
@oliviertassinari some thoughts
I believe we will need some custom caching if we decide to go with this approach. As far as I know, for example emotion is pretty good with caching this kind of definitions.
I would propose maybe we should have only function so that there is one way to do it.. On the other hand, allowing users to use only classes key is better in my opinion as it is more restrictive
This may be hard...
If we have typings for the names, it should be doable..
This shouldn't be problem with the function
Anyway I would like to try more different approaches, and have them all on the PR, so we can eventually pick one of them... |
Slightly different idea. How about instead of trigger, we allow matcher, something like:
This should solve the issues we had with the previous proposal, and it still more user friendly than just specifying the class key directly, as the initial proposal with the additions. |
packages/material-ui-styles/src/getStylesCreator/getStylesCreator.js
Outdated
Show resolved
Hide resolved
packages/material-ui-styles/src/getStylesCreator/getStylesCreator.js
Outdated
Show resolved
Hide resolved
packages/material-ui-styles/src/getStylesCreator/getStylesCreator.js
Outdated
Show resolved
Hide resolved
@@ -67,6 +68,25 @@ const withStyles = (stylesOrCreator, options = {}) => (Component) => { | |||
if (withTheme && !more.theme) { | |||
more.theme = theme; | |||
} | |||
|
|||
if (theme && theme.variants && theme.variants[name]) { |
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.
Considering this failing test case:
it.only('should map the variant classkey to the component', function test() {
if (/jsdom/.test(window.navigator.userAgent)) {
// see https://github.com/jsdom/jsdom/issues/2953
this.skip();
}
const theme = createMuiTheme({
variants: {
MuiButton: [
{
props: { variant: 'test', color: 'primary', size: 'large' },
styles: { backgroundColor: 'rgb(255, 0, 0)' },
},
],
},
});
render(<WrappedComponent theme={theme} variant="test" size="large" />);
const style = window.getComputedStyle(screen.getByTestId('component'));
expect(style.getPropertyValue('background-color')).to.equal('rgb(255, 0, 0)');
});
Should we move the logic outside of withStyles, after the resolution of the default props?
@@ -41,7 +41,7 @@ The `MuiButton` name can be used for providing [default props](/customization/gl | |||
| <span class="prop-name">href</span> | <span class="prop-type">string</span> | | The URL to link to when the button is clicked. If defined, an `a` element will be used as the root node. | | |||
| <span class="prop-name">size</span> | <span class="prop-type">'large'<br>| 'medium'<br>| 'small'</span> | <span class="prop-default">'medium'</span> | The size of the button. `small` is equivalent to the dense button styling. | | |||
| <span class="prop-name">startIcon</span> | <span class="prop-type">node</span> | | Element placed before the children. | | |||
| <span class="prop-name">variant</span> | <span class="prop-type">'contained'<br>| 'outlined'<br>| 'text'</span> | <span class="prop-default">'text'</span> | The variant to use. | | |||
| <span class="prop-name">variant</span> | <span class="prop-type">'contained'<br>| 'outlined'<br>| 'text'<br>| string</span> | <span class="prop-default">'text'</span> | The variant to use. | |
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.
string
shouldn't be allowed by default. Will take a look later. Nothing that holds back this PR since it would also affect breakpoints.
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.
Content deleted
packages/material-ui-styles/src/useThemeVariants/useThemeVariants.test.js
Outdated
Show resolved
Hide resolved
packages/material-ui-styles/src/useThemeVariants/useThemeVariants.test.js
Outdated
Show resolved
Hide resolved
packages/material-ui-styles/src/useThemeVariants/useThemeVariants.test.js
Outdated
Show resolved
Hide resolved
@@ -278,6 +279,22 @@ const Button = React.forwardRef(function Button(props, ref) { | |||
...other | |||
} = props; | |||
|
|||
const themeVariantsClasses = useThemeVariants( | |||
{ | |||
color, |
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.
This is very brittle to maintain. Do we have any automated test that would verify that a newly added prop would be passed to useThemeVariants
with its default value?
Not important for this PR. I already have an idea for a lint rule and since it's a hook it should be pretty straight forward to track.
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.
Lint rule would be perfect in my opinion 👍 There isn't automated tests for this, but I am open for ideas on this one. Although if we have lint rules, won't be necessary..
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.
Yeah lint rule looks promising.
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.
https://astexplorer.net/#/gist/d094e92b26f3c9217cd78fa62cbd7306/02a10fbd4af430267ab603c7400303b8cda068dc should cover most cases. Should resolve #21648 (comment) first before we finalize the lint rule.
…nts.test.js Co-authored-by: Sebastian Silbermann <[email protected]>
Co-authored-by: Sebastian Silbermann <[email protected]>
…nts.test.js Co-authored-by: Sebastian Silbermann <[email protected]>
Co-authored-by: Sebastian Silbermann <[email protected]>
…nts.test.js Co-authored-by: Sebastian Silbermann <[email protected]>
@mnajdova Great job 🙏! |
This PR is a proposal for solving the problem of adding custom variants to the components, defined inside the theme. See #15573 for more details.
Currently users can do this by creating wrapper components:
From @oliviertassinari #21648 (review)
I've looked into several API, and decided to go with the following one, mainly for flexibility on defining the props for a combination of props, and having type safety.
Benchmarks
Here is a list of ideas on how other frameworks solve similar issues:
variant
prop is just a string, and the value is used for generating aclassName
(client's are responsible for associating styles with that className as far as I understood)type
prop, based on which they generate aclassName
which will be applied on the element, in similar way as Bootstrap - haven't seen API for adding the styles associated with the variant