I've got a question about composition I'm hoping someone can help me with.
I'm using react-css-modules with Sass, and I'd like to know the best way to compose things for one of our basic bottom-level components.
Here's our component:
import React, {PropTypes} from 'react'
import cssModules from 'react-css-modules'
import styles from './style.sass'
const Button = ({children = 'Submit', ...props}) => {
const align = props.align ? `-${props.align}` : ''
const type = props.type ? `-${props.type}` : ''
const styleName = `button${type}${align}`
return (
<button onClick={props.onClick} {...props} styleName={styleName}>
{children}
</button>
)
}
Button.propTypes = {
align: PropTypes.string,
onClick: PropTypes.func.isRequired,
type: PropTypes.string,
}
export default cssModules(Button, styles)
And here's the stylesheet so far:
#import "~components/styles/variables"
.button
color: $button-default
background-color: transparent
font-family: $font-family
font-size: $default-font-size
font-weight: $font-regular
line-height: $default-button-height
margin: 0 $pad 0 0
outline: none
padding: 0 $pad*2
.left
float: left
.right
float: right
.primary
color: $background-interaction
background-color: $button-default
.button-left
composes: button, left
.button-right
composes: button, right
.button-primary
composes: button, primary
.button-primary-left
composes: button, primary, left
.button-primary-right
composes: button, primary, right
Right now, it's pretty painful. Every configurable prop we add exponentially increases the number of composed classes we have to provide. We can currently configure align and type, and since both can be null we have 6 possible combinations, so 5 composed classes to create in addition to the base .button.
If we added just one more prop, say just a boolean bold, we now have to add a whole bunch of new composed class names: .button-bold, .button-left-bold, .button-right-bold, .button-primary-bold, .button-primary-left-bold, .button-primary-right-bold.
I know with react-css-modules we can just enable the allowMultiple setting to allow us to specify multiple modules to apply to an element, but my understanding is that is against best practices. I feel like we have to be missing something here. What are we doing wrong?
I think maybe there's a mix of concerns in the example, and that's why it doesn't fit the "one class per element" rule. One of the reasons to enforce the one class per element rule is so that we can easily change the appearance of an element's state without actually touching the element. (Basically the promise of CSS itself, finally realized.) If you have multiple classes on an element, it's difficult to control the appearance without changing the element. This is especially true if you use appearance (rather than semantic) classes.
A class like "button-primary-left-bold" has some semantic meaning ("button-primary"), but it also has some layout meaning ("left") and some text appearance meaning ("bold"). A Button component probably has no business controlling its own layout. Remember, you can compose React components too! So you can have something more like:
<Left><Button type="primary">Click Me!</Button></Left>
Now the CSS module for the Button component can worry just about the types of buttons. And the Button component can be used anywhere, with any layout, not just a float-based layout.
Even better, the float could be pushed into a more semantic component as well. Perhaps something like:
<ButtonBar>
<Button>Cancel</Button>
<Button type="primary">Save</Button>
</ButtonBar>
The ButtonBar can have its own CSS module that does the layout. And later you can swap out that janky float layout for a swanky flexbox layout. :-)
Your "bold" modifier example certainly has no place inside the Button component. Better to think about why something is bold, and then make a component or semantic property for that. Otherwise, if the design calls for changing those "bold" buttons to "italic green" buttons, you have to go around changing a bunch of elements.
If you do this (exchange visual/layout classes for semantic components and break up components and CSS modules), you'll have less "exponential increases". If you do end up in a situation where you really do have a need for multiple semantic properties being combined, there is still some value in having those states be spelled out. A good example might be "primary-disabled". This is better than "primary disabled" for a couple of reasons. First, it's easy to look in the CSS module and see the explicit state listed. Second, there aren't ambiguous uses and relationships of those classes. Is "primary disabled" actually a valid use of those classes? That can only be known if somebody documents that use. The "disabled" class may override something in the "primary" class, meaning there are implicit ordering dependencies. It's easy for a well-meaning edit in the future to break things, because the relationship between those classes isn't obvious. As is often the case, choosing something implicit to save some keystrokes can lead to subtle errors. Paying that bit of keystroke tax locks things in and makes it obvious what will work and what won't.
Another small point that will save you a few keystrokes: there's really no reason for the "button-" prefix. That's exactly what CSS modules are for. Do this instead:
.normal
color: $button-default
background-color: transparent
font-family: $font-family
font-size: $default-font-size
font-weight: $font-regular
line-height: $default-button-height
margin: 0 $pad 0 0
outline: none
padding: 0 $pad*2
.primary
composes: normal
color: $background-interaction
background-color: $button-default
The filename itself essentially is the "button" prefix.
I think I'd move away from compose in this case and nest your classes. Here's my suggestion (pardon me if my jsx is a tad off):
import React, {PropTypes} from 'react'
import cssModules from 'react-css-modules'
import styles from './style.sass'
const Button = ({children = 'Submit', ...props}) => {
const align = props.align ? `${props.align}` : ''
const type = props.type ? `${props.type}` : ''
const styleName = `button ${type} ${align}`
return (
<button onClick={props.onClick} {...props} styleName={styleName}>
{children}
</button>
)
}
Button.propTypes = {
align: PropTypes.string,
onClick: PropTypes.func.isRequired,
type: PropTypes.string,
}
export default cssModules(Button, styles)
And the SASS:
#import "~components/styles/variables"
.button
color: $button-default
background-color: transparent
font-family: $font-family
font-size: $default-font-size
font-weight: $font-regular
line-height: $default-button-height
margin: 0 $pad 0 0
outline: none
padding: 0 $pad*2
&.left
float: left
&.right
float: right
&.primary
color: $background-interaction
background-color: $button-default
Fully acknowledging that "left" and "primary" could conflict with other class names in your app. So it might not be a bad idea to come up with some slightly better (more scoped) names.
Related
I have the following configuration in the antd.customize.less file:
#btn-primary-bg: #ffe900;
#btn-primary-color: #primary-color;
#btn-default-color: #primary-color;
#btn-default-bg: #ffffff;
On hovering a primary button everything is ok, but on hovering a default button the text color in the button changes to #btn-primary-bg, which I want to override, but I couldn't find any property like "#btn-default-hover-color" here. How can this be overridden, if at all?
I faced the same issue but I'm still looking for a better solution. However, for the moment, I can suggest that you add something like this to your global style file:
.ant-btn-default:hover, .ant-btn-default:focus {
border-color: #bee2e5;
color: #fff;
}
UPDATE
After antd has been updated to version 5.0.0 there is a prettier way to do it using ConfigProvider.
Let's suppose we have wrapped our App and assigned to the theme an object with parameters.
<ConfigProvider theme={{
components: {
Button: {
colorPrimaryBorderHover: 'red',
colorPrimaryHover: 'lightgray',
colorPrimary: 'red',
colorPrimaryActive: 'lightgray',
colorPrimaryTextHover: 'lightgray',
}
}
}}>
<App />
</ConfigProvider>
Acctually there are a lot of parameters to customize the appereance of your app which you can find in your
node_modules/antd/es/config-provider/context.d.ts file
However your config might be huge but to keep your code readable you might pass this object with interface of ThemeConfig as an exported value from another .ts file.
<ConfigProvider theme={myCustomTheme}>
...
Working with reassigning less variables. Thied to edit everything that connected with #primary-color, nothing changed color generated by Antd for hover/active state. Looks like there is no way to do this with less variables.
I did some search in github and here, but maybe I'm doing it the wrong way.
Using styled from #mui/material/styles generates random class names like this:
const TitleWrapper = styled('div')`
display: flex;
justify-content: space-between;
margin-bottom: 2rem;
`
Sometimes it becomes hard to debug the app when we don't know where the rendered component came from.
There's an option parameter that can be passed to styled with a label prop that adds a suffix to the class:
const TitleWrapper = styled('div', { label: 'TitleWrapper' })`
display: flex;
justify-content: space-between;
margin-bottom: 2rem;
`
Is there an automated way of adding that suffix to identify the component? It's really painful to do this in every single component.
The solution I found was to update my babel.config.js file with the following changes
plugins: [
[
'#emotion',
{
autoLabel: 'always',
importMap: {
'#mui/material': {
styled: {
canonicalImport: ['#emotion/styled', 'default'],
styledBaseImport: ['#mui/material', 'styled'],
},
},
},
},
],
'#babel/plugin-transform-runtime',
],
I hope this is useful to someone who faces the same issue.
In order to get a custom class name i would like to suggest you to use scss (your own custom classes and styles), then you can put every style you need there , actually that's what i am doing right now, except when i need some styles based on some dynamic data then i can use both: my custom scss classes and styled-component class
I can't seem to find in any of the Material-UI documentation where the "light", "dark" and "contrastText" properties of a PaletteIntention are applied or what they are used for? I know I can access them with hooks or HOC directly, but I am wondering if those variants are used in any of the built-in components automatically? This is probably obvious, so thanks in advance.
Yes, those theme values are used in MUI components.
The keyword PaletteIntention has only appeared in the document theme/palette/customization for explanation.
What it wants to say is that we can customize the theme default settings.
If we take a close look at the built-in component source like the most common one Button
Refer: the source of MUI Button
You can find that the default styles are using the theme
export const styles = (theme) => ({
/* Styles applied to the root element. */
root: {
...theme.typography.button,
boxSizing: 'border-box',
minWidth: 64,
padding: '6px 16px',
borderRadius: theme.shape.borderRadius,
color: theme.palette.text.primary,
transition: theme.transitions.create(['background-color', 'box-shadow', 'border'], {
duration: theme.transitions.duration.short,
}),
Which is quite common. I mean, why not they use the style solution inside their own package?
The document may not list all the default style one component is using, still, we can see it inside the source, and we can always customize them via the CSS API document, or override them using the MUI nesting selector or other style solution provided by MUI with theme.
Refer: document of
MUI style solutions
MUI theming
customizing-components
advanced/#theming
I had a styled component that was rendering 3 times, each having a parallax effect.
const ParallaxContainer = styled.section`
position: absolute;
left: 0;
right: 0;
height: 100%;
opacity: 0.3;
bottom: ${props => props.bottomValue}px;
`;
The parallax is achieved by updating the bottom value every scrollEvent through a pretty expensive calculation. Meaning, this component is getting re-rendered very often.
Unsurprisingly, I was getting the warning Over 200 classes were generated for component styled.section. Consider using the attrs method, together with a style object for frequently changed styles. So I tried to follow the advice, and refactored the component to this:
const ParallaxContainer = styled.section.attrs(
({ bottomValue }) => ({
style: {
bottom: bottomValue + "px"
}
})
)`
position: absolute;
left: 0;
right: 0;
height: 100%;
opacity: 0.3;
`;
But I am still getting the same error. What am I doing wrong?
Sandbox demonstrating my issue: https://codesandbox.io/embed/styled-components-is-yelling-at-me-attrs-zhnof
The problem is the lines you left commented out in your style. Remember, your style is just a template string. The CSS-style commented lines are only ignored after the string has been interpolated and passed to Styled Components.
The Over 200 classes error happened in the first place because the style string needed to be re-computed on every scroll event, which results in a completely new styled component instance. Moving it to attrs is like defining the style in JS in the old-fashioned React way, so these styles don't go through Styled Components.
I'm trying to create a class specificity in Material UI Next same way as they allow you to use pseudo classes, but for nested elements. For example, in Material UI Next (MUI-Next) I can create a class with styles in it:
const styles = {
appbar: {
background: '#6d6146',
'&:hover': {
background: '#9e8e6a',
},
},
};
and use it this way
<Toolbar className={classes.appbar}>
... blah blah blah
</Toolbar>
That paints my Toolbar element in color #6d6146 and hovers to #9e8e6a.
Now, if imagine I have some elements inside the Toolbar and I don't want to create a class for every single element in it. (especially if they are not MUI-Next elements, but some custom HTML) For the sake of an example, a hyperlink. Like this:
<Toolbar className={classes.appbar}>
<a href="www.google.com">
<span>Title</span>
</a>
</Toolbar>
Yes, there are ways to do this particular example correctly using MUI control properties, remember this is an example. Real world code is very complex and lots of code.
I would like to access that hyperlink by way of specificity using the main parent class as a hook class. The desired rendered css would look like this:
.appbar {
background: #6d6146;
}
.appbar:hover {
background: #9e8e6a;
}
.appbar a{
color: #d63302;
}
My attempt to create specificity is not working. This is what I tried:
const styles = {
appbar: {
background: '#6d6146',
'&:hover': {
background: '#9e8e6a',
},
'a': {
color: '#d63302',
},
},
};
According to how MUI-Next handles pseudo-classes to create specificity, this element specificity should work, but doesn't. Can this be done and I am not using the right syntax, or is this not supported?
Remember this is Material UI Next found here. Completely different than the older Material UI.
Here is a playground for ya. Thanks in advance.
SAMPLE CODE
Try this :
appbar: {
background: "#6d6146",
"&:hover": {
background: "#9e8e6a"
},
"& a": {
color: "black",
"&:hover": {
color: "red"
}
}
}
Working link