Styled Component: hover style passed in props - css

I have a StyledButton, and I use it like this:
const StyledButton = styled.div<{
hoverStyle?: CSSProperties
}>`
color: black;
background: blue;
// What can I do here to apply the hoverStyle?
`
<StyledButton
hoverStyle={{
backgroundColor: 'red',
color: 'white',
}}
>
My button
</StyledButton>
Are there any ways to do that?

Usually we provide individual values in extra styling props, but it is still definitely possible to provide a set of CSS rules (a CSSObject).
First you need to use an interpolation function to adapt the style based on runtime props:
const StyledButton = styled.div<{
hoverStyle?: CSSProperties
}>`
color: black;
background: blue;
// What can I do here to apply the hoverStyle?
${(props) => props.hoverStyle && inlineRules(props.hoverStyle)}
`
Here I used an inlineRules function to convert the CSSObject into a list of rules:
function inlineRules(rulesObj: CSSProperties) {
return Object.entries(rulesObj).map(([property, value]) => `${property}: ${value};`).join('');
}
Then, based on the "hoverStyle" name of your styling prop, it sounds like these rules should be applied only on :hover, rather than being directly merged with the root styles of your <div>. In that case, you need to place them in a nested block, for which you can use the & ampersand keyword to refer to the self style:
& a single ampersand refers to all instances of the component; it is used for applying broad overrides
&:hover {
${(props) => props.hoverStyle && inlineRules(props.hoverStyle)}
}
Live demo on CodeSandbox: https://codesandbox.io/s/sleepy-brook-h515v7?file=/src/App.tsx
Note: CSS rules in styled-components actually use the normal "kebab-case" notation, whereas CSSProperties are defined in camelCase ("background-color" v.s. "backgroundColor" in your example). In the above demo, I used a template literal conversion type to convert them into kebab-case, based on TypeScript add kebab case types form actual camel case keys

Related

Style child element by id prop using styled-component

I'm creating AccordionSections for every element I get from my backend this way
<AccordionSection
defaultOpen={activePackage}
headingLevel="h3"
id={
activePackage
? `active-${packageObj.type}-${key.toString()}`
: `${packageObj.type}-${key.toString()}`
}
label={label}
key={`${key.toString()}-${packageObj.type}`}
>
I'm wrapping this Accordion sections in a styled Accordion component:
<Wrapper spaceStackEnd="s">{content}</Wrapper>;
I want to show the label with the green colour only for the AccordionSection that start with active in the id:
const Wrapper = styled(Accordion)`
span > {
&:first-child {
${(properties) =>
properties.id?.startsWith('active') &&
css`
color: green;
`};
}
}
`;
Span is because the generated elements are spans. Nothing is happening. am I missing something ?
You can pass a parameter active to your component. If you have children I assume that you have multiple components. So in order to render multiple components developers usually use the map() function. The second argument of the map() function is the place of the component you want to render based on the array of data you have. you can simply do this:
<YourComponent {...yourProps} active={index === 0? "green" :
"black(default color you set)"} />
and then pass that active prop to your styled-components.
if you want know more about this you can visit this link.

Using Styled Components and passing props to psedo element before not working

I have the follwing styled component, I am trying to pass a prop to it and use it in a pseudo element selector, in this case before. However, it does not work as expected.
However, if I pass the same prop to the background css property or any other property, it works completely fine
Is the following allowed when using styled components?
const Label = styled.label`
/* This did not work */
&::before {
content: ${(props) => (props.field ? "Hi" : "")};
}
/*But this works perfectly fine*/
background: ${(props) => (props.field ? "palevioletred" : "white")};
`;
const Input = styled.input`
height: 32px;
`;
If you check devtools, you'll see that your CSS rule is making it to the DOM, but it's an invalid value for the content property. It's raw text, with no quotes: Hi. It needs to be "Hi".
Try doing (notice the backticks for a new template string literal):
content: ${(props) => (props.field ? `"Hi"` : "")};

How to customize react-select component?

I have a little problem with react-select component restyling. If you take a look on their official doc you'll see all the attributes that can be restyled. The problem I have is that around the text I write there's blue borders, and can't remove them. Also around the container.
If I inspect it here's what I get:
It has as id react-select-3-input and can't remove that even if I write directly on chrome inspection let alone doing it in the code.
I am using nextjs as framework and I added a file style.css and import it into _app.tsx. I tried adding this to see what happens:
#react-select-2-input {
background-color: red;
color: red;
font-size: 40;
background-color: red;
}
But nothing happens.
How do you think I can fix this?
If you read the documentation for react-select you can see that the docs steer you towards using emotion more than straight forward CSS.
When implementing this you work should do something like this:
const reactSelectStyles = {
singleValue: (provided, state) => ({
...provided,
color: 'inherit',
}),
menu: (provided, state) => ({
...provided,
'z-index': 9,
}),
multiValue: (provided, state) => ({
...provided,
color: state.isDisabled ? "#000" : "#fff",
}),
};
This will create an object that you then can use for styling
<Select
styles={reactSelectStyles}
/>
Provided in the code above makes sure that the default styling is included (so you can omit it if you want to) and the state can be used to do render different styles depending on the state of the react-select.
I would say that you overall will have a harder time solving this using regular CSS since react-select (using emotion) will generate dynamic CSS classes that does not collide with each other.

How to pass multiple props to conditional rendered styled components

I have passed props to styled components before and am familiar with this convention.
<Card data={props.color}>
const Card = styled.div`
margin-right: ${(props) => (props.data === "BlueSquare" || "RedSquare" ? "5px" : "15px")};
`;
What I am having trouble with is converting this type of conditional styling to a styled component
background: linear-gradient(${ColorFunction({value: props.audited ? props.score : props.remScore,
})}, white);
Any help would be greatly appreciated.
Pass callback which accept props and return css property value in template literal with used values from props.
In your case, pass callback which accept props and return linear-gradient in template literal with used values from props.
background: ${props => `linear-gradient(${ColorFunction({value: props.audited ? props.score : props.remScore,
})}, white)`};

How should I write Jest Test cases for styled component and spy on css to verify if the styles?

I have a component in React-redux, which has a PagedGrid component (basically a table which renders data row-wise).
<UsersContainer>
<Title>{t('users')}</Title>
<PagedGrid
data-auto-container="user:table"
pageData={user.data}
columns={this.column}
/>
</UsersContainer>
I have created a function for the custom styled component which applies css to the rows of the table inside PagedGrid
const UsersContainer = styled.div`
> table > tbody {
${props => customcss(props)};
}
`;
function customcss({ data = [] }) {
let styles = '';
if (data.length > 0) {
data.forEach((value, index) => {
if (value.mycondition) {
const rowStyle = `& > tr:nth-child(${index + 1}) {
background-color: ${LIGHT_BLUE}
}`;
}
});
}
return css` ${rowStyle} `;
}
Now I want to create a test case using jest to spy on the css of this table and check if the styles are getting applied or not. Can anyone help me on creating a test case for this.
Assuming that you use the #testing-library/react library, you could test your component's style by getting it directly from the html document, and see precisely what style is used for your specific element.
In your example, you can do something like below (assuming that the ${LIGHT_BLUE} value is blue):
import React from 'react';
import { render } from '#testing-library/react';
import UsersContainer from '../UsersContainer';
it('should have the background color set to blue', () => {
const { container, getAllByTestId } = render(
<UsersContainer />
);
// Replace <YOUR_CSS_SELECTOR> by your component's css selector (that can be found in the inspector panel)
let contentDiv = document.querySelector('<YOUR_CSS_SELECTOR>');
let style = window.getComputedStyle(contentDiv[0]);
expect(style.color).toBe('blue');
}
Here, to get the style of your element, I am using the element's CSS Selector. However, it could also work with the element's className, or id directly if it has one, respectively using the methods document.getElementByClassName('YOUR_DIV_CLASS_NAME'), document.getElementId('YOUR_DIV_ID') instead of document.querySelector('<YOUR_CSS_SELECTOR>'). Note that the given name here should be unique, either with the id technique, or the className.

Resources