How to override styles with higher specificity - styled components - styled-components

I read styled components docs and know the basics how to override some styles, but I can't figure out this in my case.
I need to override some class in my div
So my structure looks like this:
How Can I achieve this with higher specificity from styled components instead !important
export const SomeDiv = styled.div`
.someClassName {
left: ${(props) => props.left || 0} !important;
${(props) =>
props.top &&
`
top ${props.top} !important;
`}
}
`

export const SomeDiv = styled.div`
.someClassName {
&&& {
left: ${(props) => props.left || 0} !important;
${(props) =>
props.top &&
`
top ${props.top} !important;
`}
}
}
`
Github issue

Related

How to set initial value with styled component props

I am making app using styled components.
I would like to pass some props to position my element
In a situation where no props gets passed I want to set a value initial
Of course if I do something like in example I get error
Cannot find name 'initial'.ts(2304)
<SomeStyledComponent top="20px" />
const SomeStyledComponent = styled.div`
position: absolute;
top: ${(props) => props.top || initial} !important;
`
const SomeStyledComponent = styled.div`
position: absolute;
top: ${(props) => props.top ? props.top : initial} !important;
`
You get error because variable with name "initial" has not been defined.
You can define initial value direct in component.
In fourth variant , i show how fix error in your example.
// first variant
const Component = styled.div`
position: absolute;
top: ${(p) => `${p.top || 20}px`};
`
// second variant
const Component = styled.div`
position: absolute;
top: ${(p) => `${p.top}px`};
`
Component.defaultProps = {
top: 200
}
// third variant, set default value in wrapper component
const BaseComponent = styled.div`
position: absolute;
top: ${(p) => `${p.top}px`};
`;
const Component = ({ top = 50, children, ...rest }) => (
<BaseComponent top={top} {...rest}>
{children}
</BaseComponent>
);
// fourth variant, your example with fix
const initial = "200px"
const SomeStyledComponent = styled.div`
position: absolute;
top: ${(props) => props.top || initial} !important;
`

Target another component on hover using emotion-js

I understand this is very similar to Target another styled component on hover
However I would like to achieve the same effect with emotion-js
More specifically I am trying to recreate this example using emotion styled components
Here is my code and what I have tried.
import React from 'react';
import styled from '#emotion/styled';
import { Link } from 'gatsby';
const Dropdown = styled.div`
postition: relative;
display: inline-block;
`;
const Button = styled.div`
background-color: green;
color: white;
&:hover {
${DropDownContent} {
display: block;
}
}
`;
const DropDownContent = styled.div`
display: none;
position: absolute;
`;
const DropDownMenu = () => {
return (
<Dropdown>
<Button>Dropdown</Button>
<DropDownContent>
<Link to="/">Link 1</Link>
</DropDownContent>
</Dropdown>
);
};
export default DropDownMenu;
I would like the link to show up when I hover the button, but that is not working and I cannot figure out why
There are three issues here.
You're referencing DropdownContent before you've defined it. Rearrange your code so that the DropdownContent declaration comes before the tagged template literals that use it and you should be good to go.
The resulting css selector (something like button:hover .DropDownContent) does not match your HTML document, where DropDownContent is a sibling of Button.
Your position property on Dropdown is misspelled.
With all three issues resolved, your code may look something like this:
import React from 'react';
import styled from '#emotion/styled';
import { Link } from 'gatsby';
const Dropdown = styled.div`
position: relative;
display: inline-block;
`;
const DropDownContent = styled.div`
display: none;
position: absolute;
`;
const Button = styled.div`
background-color: green;
color: white;
&:hover + ${DropDownContent} {
display: block;
}
`;
const DropDownMenu = () => {
return (
<Dropdown>
<Button>Dropdown</Button>
<DropDownContent>
<Link to="/">Link 1</Link>
</DropDownContent>
</Dropdown>
);
};
export default DropDownMenu;
as #coreyward mentioned, rearrange the code.... and the
import styled from "#emotion/styled/macro";
and this will do the trick
This solution was already posted at Component selectors can only be used in conjunction with babel-plugin-emotion error while using emotion by https://stackoverflow.com/users/165215/ijk

How to test style of nested component jest-styled-components

Im testing using react-testing-library and jest-styled-components.
I have a wrapper component that renders the styles of its child button dependant on a selected prop passed to it.
This is the code:
const selectedStyles = css`
background-image: url(../image);
background-position: center;
background-repeat: no-repeat;
border-color: ${color.grey6};
height: 38px;
width: 58px;
& span {
display: none;
}
`;
const ButtonWrapper = styled.div`
& button {
font-size: 15px;
line-height: 20px;
padding: 8px 12px;
${props =>
props.selected
? css`
${selectedStyles}
`
: ""}
&:hover,
:focus {
${props =>
props.selected
? css`
${selectedStyles}
`
: ""}
}
}
`;
and the test
test("it renders the correct styles when selected ", () => {
const { container } = render(<CheckButton selected>Add</CheckButton>);
const button = container.querySelector("button");
expect(button).toHaveStyleRule("background-position", "center");
});
but its failing with "Property 'background-position' not found in style rules" which is true for the original button, however when its parent is passed the selected prop this style applies.
I am also doing snapshot testing with the component however not testing the props getting passed brings the test coverage down.
Can anyone help?
In general as far as nested styles testing is concerned, I would recommend testing directly the nested element.
I personally haven't figured out a way to test nested styles using the .toHaveStyle(``); (not even a simple
a {
text-decoration: none;
}
)
so I ended up querying for the exact component I wanted to test, eg:
expect(screen.getByText(/text-within-the-child-component/i)).toHaveStyle(`
text-decoration: none;
`);
In your specific case I believe the way to go is to render your component in your test directly with the props that trigger the styles you want for each case (selected in your code example).
For those who are facing the same problem toHaveStyleRule accept a third "options" parameter after property and value where you can path a modifier:
test("it renders the correct styles when selected ", () => {
render(<CheckButton selected>Add</CheckButton>);
const button = container.querySelector("button");
expect(screen.getByText("Add").parentElement).toHaveStyleRule("background-position", "center", { modifier: 'button' });
});
Here I state on the fact that "Add" is the button text and its parent is the component ButtonWrapper.
By the way, you should avoid as much as possible using querySelector (here I'm using react testing library).
https://github.com/styled-components/jest-styled-components

Reuse a 'mixin' with Styled Components across different files?

How can I reuse a collection of styles with Styled Components across different files?
With SASS I can define and use a mixin like so:
#mixin section( $radius:4px ) {
border-radius: $radius;
background: white;
}
.box { #include section(); }
With Styled Components you can extend a style, but this means I would need to import that component into every page. This is pretty cumbersome compared to how variables are available everywhere with the ThemeProvider.
https://www.styled-components.com/docs/basics#extending-styles
Just adding on to the answer by #Evanss
You can make the mixin a function (as in OP) by doing:
const theme = {
sectionMixin: (radius) => `border-radius: ${radius};`
}
and then use it like:
const Button = styled.button`
${props => props.theme.sectionMixin('3px')}
`
or simply:
const Button = styled.button`
${({ theme }) => theme.sectionMixin('3px')}
`
You can create a string with multiple CSS rules and pass that to the ThemeProvider.
const theme = {
sectionMixin:
'background: white; border-radius: 5px; border: 1px solid blue;',
}
<ThemeProvider theme={theme}>

Using Material Icons with Styled Components

Just started playing around with Styled Components. Is there a way to style third party icons such as Material Design Icons? This is the code I have so far but obviously it isn't working. Relavant code is below Content component Thanks!
const MaterialIcon = (props) => <i className="material-icons">account_balance</i>;
const Icon = styled(MaterialIcon)`
background-color: green;
font-size: 50px;
`;
const CheckThisOut = props => (
<Wrapper>
<Header>
<h5>{props.title}</h5>
<Icon />
</Header>
<Divider />
<Content>
<p>5% of all participants in this campaign were first time users.</p>
</Content>
</Wrapper>
);
export default CheckThisOut;
For the styled(AnyComp) notation to work AnyComp needs to accept the incoming className prop and attach it to a DOM node.
For your example to work MaterialIcon has to use the passed in className, otherwise the styles are injected but no DOM node is targeted:
const MaterialIcon = (props) => (
<i className={`material-icons ${props.className}`}>account_balance</i>
);
// WORKS 🎉
const Icon = styled(MaterialIcon)`
background-color: green;
font-size: 50px;
`;
See our documentation page about styling any component for more information!
I know this is an old post, but here's another way to write it as a single expression. Using styled components' .attrs() (see docs) for the class name and the CSS ::after selector for the icon name.
const ThumbUp = styled.i.attrs(() => ({ className: "material-icons" }))`
background-color: green;
font-size: 50px;
&:after {
content: "thumb_up";
}
`;
Writing this in a more generic way will allow you to use it for any of the available icons. This makes use of styled component ability to adapt based on props (see styled components docs for more info).
const MaterialIcon = styled.i.attrs(() => ({ className: "material-icons" }))`
background-color: green;
font-size: 50px;
&:after {
content: ${props => props.iconName || "help"};
}
`;
Which you could then use like this:
<MaterialIcon iconName="check" />

Resources