How to use props variable for css-modules className? - reactjs

Is it possible to use a props variable for a css-modules className?
// Component.js
import styles from "./Component.module.scss"
const Component = ({ color }) =>
<div className={`${styles.component}` `${styles.color}`>
Component
</div>
// Component.module.scss
.component { border: 1px black solid; }
.red { color: red; }
.green { color: green; }
Then I could use the Component like so:
// App.js
<Component color="red" />
<Component color="green" />
And have the two Components be red and green respectively.

I think you've missed a bracket
const Component = ({ color }) => {
const cssColor = color;
return (
<div className={`${styles.component}` `${styles[cssColor]}`}>
Component
</div>
)
}
To use Component level CSS you can get it loaded in your webpack as using a loader (Reference)
When using webpack, you can add the loader and also include the module to your webpack.config.js in other to make CSS modules work with Webpack.
test: /\.css$/,
loader: 'style!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'
}
Alternatively, you could use a library called classnames

Following works:
const Component = ({ color }) => {
const cssColor = color;
return (
<div className={`${styles.component}` `${styles[cssColor]}`>
Component
</div>
)
}

Related

Dark theme does not work with React Stitches

I'm using Stitches in React to handle my CSS and theming. I have the following code:
import React from 'react'
import { createStitches, globalCss } from '#stitches/react'
const { theme, createTheme } = createStitches({
theme: {
colors: {
text: 'blue',
bodyBg: 'lightgray',
},
},
})
const darkTheme = createTheme('dark-theme', {
colors: {
bodyBg: 'black',
},
})
const globalStyles = globalCss({
'body': {
color: '$text',
backgroundColor: '$bodyBg'
},
});
function App() {
globalStyles()
return (
<div className='App'>
<h1>Hello World!</h1>
</div>
)
}
export default App
As you can see, I have a default theme, and then a dark theme that extends the default theme while overriding some properties (in this case, the bodyBg). I'm applying these styles directly in my <body>. The default theme works fine, but the dark theme does not. When I add the .dark-theme class to my <html>, nothing changes (the background should turn black). What exactly am I doing wrong here?
You are probably trying to add the class directly to the body in the developer tools which doesn't work.
I managed to make it work with a button onClick event:
const darkTheme = createTheme('dark-theme', {
colors: {
bodyBg: 'black',
},
})
function App() {
const setBlackTheme = () => {
document.body.classList.remove(...document.body.classList);
// Here we set the darkTheme as a class to the body tag
document.body.classList.add(darkTheme);
}
globalStyles()
return (
<div className='App'>
<button onClick={setBlackTheme}>Dark Theme</button>
<h1>Hello World!</h1>
</div>
)
}
export default App
Try it and let's see if it works for you as well.

React testing library not rendering Emotion CSS

Running React Testing Library to generate snapshots on JSX which uses the Emotion css prop results in no CSS being rendered.
I have tried using the "#emotion/jest/serializer" but still no luck.
Component:
<button
role="button"
css={(theme)=> {
backgroundColor: 'hotpink',
'&:hover': {
color: theme('lightgreen'),
},
}}
/>
Test:
import React from 'react';
import { render } from '#testing-library/react';
import { createSerializer } from '#emotion/jest';
import { Component } from './component';
expect.addSnapshotSerializer(createSerializer());
describe('IconComponent', () => {
it('should match the snapshot for the given props', () => {
const { asFragment } = render(<Component icon="knownIcon" />);
expect(asFragment()).toMatchSnapshot();
});
Snapshot:
(This gets rendered as an anonymous object rather than CSS)
exports[` 1`] = `
<DocumentFragment>
<button
css="[object Object]"
role="button"
/>
</DocumentFragment>
`;
I think you are just missing the final step.
https://emotion.sh/docs/css-prop
Set the jsx pragma at the top of your source file that uses the css prop. This option works best for testing out the css prop feature ...... such as Create React App 4 then /** #jsx jsx / pragma might not work and you should use /* #jsxImportSource #emotion/react */ instead.
From the emotion doc, adding /* #jsxImportSource #emotion/react */ on the top of your component file helps css option to render probably in the test.
CustomButton.js
/** #jsxImportSource #emotion/react */
export function CustomButton() {
return (
<button
css={{
"backgroundColor": "hotpink",
"&:hover": {
color: "lightgreen"
}
}}
></button>
);
}
Result
exports[`IconComponent should match the snapshot for the given props 1`] = `
<DocumentFragment>
.emotion-0 {
background-color: hotpink;
}
.emotion-0:hover {
color: lightgreen;
}
<button
class="emotion-0"
/>
</DocumentFragment>
`;
If you are not using create-react-app, use the follow instead:
/** #jsx jsx */
import { jsx } from '#emotion/react'
Here is the repo, you can clone it to test it.
Older Version
For older version of react (< 16.4), you will need to use back "#emotion/core" instead of "#emotion/react" to transpile the file in old way.
package.json
"#emotion/core": "10.1.1",
Button.js
/** #jsx jsx */
import { jsx } from '#emotion/core' <--- use the #emotion/core to transpile the file in old way.
import React from "react";
const Button = () => {
return (
<button
css={{
backgroundColor: "hotpink",
"&:hover": {
color: "lightgreen"
}
}}
></button>
);
};
export default Button;
Here is the repo for demonstration
Another solution https://emotion.sh/docs/testing#writing-a-test
import React from 'react'
import renderer from 'react-test-renderer'
const Button = props => (
<button
css={{
color: 'hotpink'
}}
{...props}
/>
)
test('Button renders correctly', () => {
expect(
renderer.create(<Button>This is hotpink.</Button>).toJSON()
).toMatchSnapshot()
})

Merge classes in CSS modules using React, SASS and webpack

I am trying to create a simple way to override CSS classes in React components that use CSS Modules.
I can see the stringified names of the classes being imported from styles and being passed in via theme, but I can't seem to figure out how to see the actual CSS properties.
Is there a way to get access to these and merge them into a new class which can then be applied via className?
// component1.scss
.base {
background-color: black; <-- override this property
}
// component1.js
import Component2 from './component2';
import styles from './component1.scss';
const theme = {
base: styles.base
};
function Component1() {
return <Component2 theme={theme} />;
}
// component2.scss
.base {
background-color: yellow;
text-align: center;
ul {
margin: 10px;
}
}
// component2.js
import Component2 from './component2';
import styles from './component2.scss';
function Component2({ theme }) {
return (
<div className={() => someFunctionThatMergesCss(styles, theme)}>
<ul>
...
</ul>
</div>
);
}

Passing custom props to each styled component through Provider

I would like to pass a custom prop (exactly: theme name as string) to each passed styled component through Provider, so it was available throughout the css definition.
ThemeProvider almost does it, but it expects object, not the string. I do not want to pass whole object with theme settings, just the name of my theme.
I do not want to use special theme prop or similar, because then I would have to it manually every single time I create new styled component. Provider seems like the best option if only it cooperated with string.
Is there any possibility to pass a string through Provider to Consumer builded in styled components?
EDIT:
[PARTIAL SOLUTION]
I found what I was looking for when I realized styled-components exports their inner context. That was it. Having access to pure react context gives you original Provider, without any 'only objects' restriction ('only objects' is a styled-components custom provider restriction).
Now I can push to each styled component exactly what I want and if I want.
import styled, { ThemeContext } from 'styled-components';
const StyledComponent = styled.div`
color: ${props => props.theme == 'dark' ? 'white' : 'black'};
`;
const Component = props => {
const theme = 'dark';
return (
<ThemeContext.Provider value={theme}>
<NextLevelComponent>
<StyledComponent />
</NextLevelComponent>
</ThemeContext.Provider>
);
};
Hope I have this correct, from what I've been able to glean. I haven't tried this out but it seems it might work for you. This is lifted directly from the reactjs.org docs regarding context. It passed the string name of the theme down.
const ThemeContext = React.createContext('green');
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="blue">
<SomeComponent />
</ThemeContext.Provider>
);
}
}
function SomeComponent(props) {
return (
<div>
<OtherComponent />
</div>
);
}
class OtherComponent extends React.Component {
static contextType = ThemeContext;
render() {
return <ThirdComponent theme={this.context} />
}
}
I hope this helps you understand the idea behind ThemeContext from styled-components. I've passed string "blue" to ThemeContext just to show, that it should not be object and you can use just string.
import React, { useContext } from "react";
import ReactDOM from "react-dom";
import styled, { ThemeContext } from "styled-components";
// Define styled button
const Button = styled.button`
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border-radius: 3px;
color: ${props => props.theme};
border: 2px solid ${props => props.theme};
`;
// Define the name of the theme / color (string)
const themeName = "blue";
const ThemedButton = () => {
// Get the name from context
const themeName = useContext(ThemeContext);
return <Button theme={themeName}>Themed color: {themeName}</Button>;
};
function App() {
return (
<div className="App">
<ThemeContext.Provider value={themeName}>
<ThemedButton />
</ThemeContext.Provider>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Demo: https://codesandbox.io/s/styled-components-example-with-themecontext-cso55

how to pass multiple classNames to inner children with emotion js

I want to split the components into baseUI one and styled one:
eg.
MyComponent.jsx
export default class MyComponent extends React.Component {
...
...
render() {
const { wrapperClassName, className, childClassName } = this.props;
return (
<div className={wrapperClassName>
<div className={className />
<div className={childClassName} />
</div>
)
}
}
StyledMyComponent.jsx
import styled from 'react-emotion'
const StyledMyComponent = styled(MyComponent)(
...
...
)
export default StyledMyComponent
however anything I put to the styled function's argument they will go to the className only, is there a way I specify which props goes to which className?
also can I do something like sass/less with children selector?
hypothetically something like this:
const classes = css`
color: red;
span { // this works
color: black;
}
.childClassName { // this doesn't work
color: green;
}
`
<MyComponent className={classes} />
No you can't.
What you can do, is create specific components for the underlying div. This is how I make my components:
const MyComponentStyle = styled('div')....;
const MySecondComponentStyle = styled('div')...;
const MyThirdStyle = styled('div')...;
const MyComponent = ({ wrapperClassName, childClassName, className }) =>
<MyComponentStyle className={wrapperClassName}>
<MySecondComponentStyle className={className} />
<MyThirdStyle className={childClassName} />
</MyComponentStyle>
)
}
}
Conditionally styling the element and its children based on class names
You can conditionally change the styling of stuff below the main component based on its classes.
Taking your example:
const Something = () => (
<MyComponent className={classes}>
<div className="childClassName">child</div>
<div className="otherChildClassName">child</div>
</MyComponent>
You can style the children like so:
const classes = css`
color: red;
span {
color: black;
}
& .childClassName {
color: green;
}
`
note the & character. It essentially means "this class". So & .childClassName means "childrens of this element with class childClassName.
You could also use &.someClassName (note the lack of space), which would mean: "this element when it also has a class named someClassName.

Resources