How to set initial value with styled component props - styled-components

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;
`

Related

How to override styles with higher specificity - 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

styled-components css`` returns unexpected output when called inside a function - leading and trailing comma and interpolates code instead of values

EDIT: Actually I was wrong, the first example also doesn't work! It also interpolates the code!!
I'm experimenting with styled-components css prop api and I ran into the following problem:
This code works well:
const myCss = css<PropsWithTheme>`
width: 100px;
height: 100px;
color: ${props => props.theme.color};
`
const MyComponent = () => <div css={myCss.toString()} />
But the following does not:
const getCss = (color: strinbg) => css<PropsWithTheme>`
width: 100px;
height: 100px;
color: ${color}; // I tried injecting the color directly
color: ${() => color}; // And also returning it from a callback inside the css``, just as I would access props/theme
`
const MyComponent = () => <div css={getCss('red').toString()} />
The output css here is width: 100px; height: 100px; color: ,red,; ,() => color,;, which is obviously not valid.
Using template string interpolation to stringify the output solves the problem, but results in very bad readability due to Prettier enforcing this format:
const MyComponent = () => (
<div
css={`
${getCss('red')}
`}
/>
)
Unfortunately moving the inerpolation anywhere outside the css prop definition ( in component body or creating a stringify function) breaks the functionality (either prop/theme access, all css doesn't get applied at all).
It seems to be related to this issue: https://github.com/styled-components/styled-components/issues/1641, but the suggested solution there is to use the css helper function, which I'm already doing :(
Is there an easy fix to my problem?

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

Returning a Dynamic Styled Component

I'm trying to return this element but if I try to style it after importing it, it wont apply the correct styles.
Heading.js:
export default ({
element = 'h1',
}) => new styled[element]`
margin: 0 0 ${shevy[element].marginBottom};
font-family: Plain, sans-serif;
font-size: ${shevy[element].fontSize};
line-height: ${shevy[element].lineHeight};
text-transform: uppercase;
`()
SomeOtherComponent.js:
const LeadHeading = styled(Heading)`
margin: 0 0 ${bs(3)};
font-size: ${h4.fontSize};
line-height: ${h4.lineHeight};
color: red;
`
<LeadHeading>Yup</LeadHeading>
The color is applied correctly but font-size, line-height, and margin aren't applied correctly.
I was doing something hacky, instead I should've been using withComponent: https://www.styled-components.com/docs/basics#extending-styles.
Ended up doing:
import styled from 'styled-components'
import shevy from 'common/shared/utils/shevy'
const Heading = styled.h1`
font-family: Plain, sans-serif;
text-transform: uppercase;
`
const extend = tag => `
margin: 0 0 ${() => shevy[tag].marginBottom};
font-size: ${() => shevy[tag].fontSize};
line-height: ${() => shevy[tag].lineHeight};
`
Heading.h1 = Heading
Heading.h2 = Heading.withComponent('h2').extend(extend('h2'))
Heading.h3 = Heading.withComponent('h3').extend(extend('h3'))
Heading.h4 = Heading.withComponent('h4').extend(extend('h4'))
Heading.h5 = Heading.withComponent('h5').extend(extend('h5'))
Heading.h6 = Heading.withComponent('h6').extend(extend('h6'))
export default Heading

Resources