Styled compoenents/emotion: add a style object to apply to different components - css

I create different icons in Emotion (could be Styled Components as well), and I apply the exact styling to each icon:
const BellIcon = styled(Bell)`
width: ${(props) => props.theme.sizes.iconDimension};
height: ${(props) => props.theme.sizes.iconDimension};
flex-shrink: 0;
`;
const PlayIcon = styled(Play)`
width: ${(props) => props.theme.sizes.iconDimension};
height: ${(props) => props.theme.sizes.iconDimension};
flex-shrink: 0;
`;
const RefreshIcon = styled(Refresh)`
width: ${(props) => props.theme.sizes.iconDimension};
height: ${(props) => props.theme.sizes.iconDimension};
flex-shrink: 0;
`;
Is there a way to to outsource the styling into an object and apply the same styles to each icon using the object? (or another method? Just so the code isn't repetitive)

You can utilize the "css" function from "styled-components" to group styles in an object.
Like this:
import { css } from 'styled-components';
const GroupedStyles = css`
width: ${(props) => props.theme.sizes.iconDimension};
height: ${(props) => props.theme.sizes.iconDimension};
flex-shrink: 0;
`;
const RefreshIcon = styled(Refresh)`
${GroupedStyles};
`;
const PlayIcon = styled(Play)`
${GroupedStyles};
`;

Assuming that your icon components are SVGs, I'd do the following:
const IconWrapper = styled.div`
svg {
width: ${(props) => props.theme.sizes.iconDimension};
height: ${(props) => props.theme.sizes.iconDimension};
flex-shrink: 0;
}
`;
And then use it as:
<IconWrapper>
<Icon />
</IconWrapper>

Related

Styled component not being respected between files?

For whatever reason, the buttons (defined by AboutItem) are displaying light blue backgrounds when I hover over them. I want to make it #282828.
In a separate file I define some styled components for Buttons:
export const Button = styled.button`
display: inline-block;
font-size: 1em;
margin: 1em;
padding: 0em 1em;
`;
export const InfoButton = styled(Button)`
color: grey;
&:hover {
background-color: lightblue;
}
`;
which I then use:
interface AboutNavProps {
selected: boolean;
}
const AboutItem = styled(InfoButton) <AboutNavProps>`
color: grey;
background-color: ${props => props.selected ? "#282828" : "transparent"};
text-align: center;
&:hover {
background-color: #282828;
}
`;
export function AboutNavbar() {
const router = useRouter()
useEffect(() => {
router.prefetch("/method");
}, [])
return (
<AboutNav aria-label="Navbar">
<AboutItem as="a" href="method" selected={router.pathname == "/method"}>How It Works</AboutItem>
<AboutItem as="a" href="about" selected={router.pathname == "/about"}>About</AboutItem>
<Logo />
</AboutNav>
);
}
Is there a reason for this? I don't understand why the light blue overrides the color I define for the hover.

update properties of style component using switch statement

I know there's a similar question but what I need is different.
I have declared the common styles that my button will be using and I'm using a function with a switch statement that has different properies for different buttons that I will be using in other pages and for one case I need the border-radius and padding different from the common one, how can I update(or replace) the value without using !important or && {.
Here's the code
import { TTheme } from 'src/styles/Themes/Theme';
import styled, { css, FlattenSimpleInterpolation } from 'styled-components';
const getVariantCSS = <T extends TTheme>(props: T): FlattenSimpleInterpolation => {
const { theme } = props;
switch (props.variant) {
case 'secondary':
return css`
background-color: ${theme.colors.white};
color: ${theme.colors.primary};
border: ${theme.borders.solid1} ${theme.colors.primary};
&:hover {
color: ${theme.colors.white};
background-color: ${theme.colors.shades.hoverLight};
border: ${theme.borders.none};
box-shadow: ${theme.boxShadows.primary};
}
`;
case 'half':
return css`
color: ${theme.colors.white};
background-color: ${theme.colors.primary};
&& {
border-radius: ${theme.radius.half};
padding: ${theme.paddings.small};
}
border: ${theme.borders.solid2} ${theme.colors.white};
`;
case 'dark':
return css`
background-color: ${theme.colors.tertiary};
color: ${theme.colors.primary};
border: ${theme.borders.solid1} ${theme.colors.primary};
&:hover {
color: ${theme.colors.white};
background-color: ${theme.colors.shades.hoverDark};
box-shadow: ${theme.boxShadows.primary};
filter: ${theme.filter.brightness};
border: ${theme.borders.none};
opacity: ${theme.opacity.default};
}
`;
default:
return css`
background-color: ${theme.colors.primary};
color: ${theme.colors.white};
border: ${theme.borders.none};
box-shadow: ${theme.boxShadows.primary};
&:hover {
color: ${theme.colors.white};
background-color: ${theme.colors.shades.hoverLight};
}
`;
}
};
export const StyledButton = styled.button`
${getVariantCSS}
width: 100%;
height: 100%;
padding: ${(props) => props.theme.paddings.medium};
display: ${(props) => props.theme.content.display.flex};
flex-direction: ${(props) => props.theme.content.flexDirection.row};
justify-content: ${(props) => props.theme.content.justifyContent.center};
align-items: ${(props) => props.theme.content.alignItems.center};
flex-grow: ${(props) => props.theme.content.flexGrow.one};
border-radius: ${(props) => props.theme.radius.button};
I suggest using the cascade. just place "getVariantsCSS" below the main styles and then the desired styles will be applied and then "important" and "&&" is not needed.
https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance#the_cascade
export const StyledButton = styled.button`
width: 100%;
height: 100%;
padding: ${(props) => props.theme.paddings.medium};
display: ${(props) => props.theme.content.display.flex};
flex-direction: ${(props) => props.theme.content.flexDirection.row};
justify-content: ${(props) => props.theme.content.justifyContent.center};
align-items: ${(props) => props.theme.content.alignItems.center};
flex-grow: ${(props) => props.theme.content.flexGrow.one};
border-radius: ${(props) => props.theme.radius.button};
${getVariantCSS}
`

How to style previous siblings in react styled-components?

I have react component like this:
const StyledMenu = () => {
const [currentStep, setCurrentStep] = React.useState('template');
const steps = ['template', 'audience', 'demography', 'action'];
const goToNextStep = () => {
const index = steps.indexOf(currentStep);
setCurrentStep(steps[index + 1]);
};
const goToPreviousStep = () => {
const index = steps.indexOf(currentStep);
if (index === 0) {
return;
}
setCurrentStep(steps[index - 1]);
};
return(
<div>
<StepsWrapper>
{steps.map((step, index) => (
<Step key={step} active={step === currentStep} last={index === 3}>
<StepDescription>{step}</StepDescription>
</Step>
))}
</StepsWrapper>
</div>
)
}
const StepsWrapper = styled('div')`
display: grid;
grid-template-columns: repeat(4, 1fr);
width: 100%;
height: 69px;
line-height: 69px;
text-align: center;
position: relative;
top: 27px;
`;
const Step = styled('div')<{ active: boolean; last: boolean }>`
color: ${({ active }) =>
active ? '#44b3db' : '#c7e8f4'};
`;
const StepDescription = styled('div')`
width: 240px;
height: 29px;
font-family: Roboto;
font-size: 18px;
font-weight: 500;
font-style: normal;
letter-spacing: normal;
line-height: normal;
text-align: center;
text-transform: uppercase;
margin: 0 auto;
`;
and I'm trying to style the items which are before the active item. If the first item is active, then nothing will be styled. I want to achieve styles from these images:
and
and
How can I achieve this? Thanks in advance!
I achieved this by adding a new state for the active index, and to styled-component I sent a boolean if the active index is bigger than the index, so the indexes less than current active index will be styled differently as #vishnu mentioned in the comment above. The code looks now like this
const StyledMenu = () => {
const [currentStep, setCurrentStep] = React.useState('template');
const steps = ['template', 'audience', 'demography', 'action'];
//added this line of code
const [activeIndex, setActiveIndex] = React.useState(0);
const goToNextStep = () => {
const index = steps.indexOf(currentStep);
setCurrentStep(steps[index + 1]);
//set the new activeIndex
setActiveIndex(index + 1);
};
const goToPreviousStep = () => {
const index = steps.indexOf(currentStep);
if (index === 0) {
return;
}
setCurrentStep(steps[index - 1]);
//set the new activeIndex
setActiveIndex(index - 1);
};
return(
<div>
<StepsWrapper>
{steps.map((step, index) => (
//sending the checked props to the styled component
<Step key={step} active={step === currentStep} last={index
=== 3} checked={activeIndex > index}>
<StepDescription>{step}</StepDescription>
</Step>
))}
</StepsWrapper>
</div>
)
}
const StepsWrapper = styled('div')`
display: grid;
grid-template-columns: repeat(4, 1fr);
width: 100%;
height: 69px;
line-height: 69px;
text-align: center;
position: relative;
top: 27px;
`;
const Step = styled('div')<{ active: boolean; last: boolean;
checked: boolean }>`
//checking if component is marked to be styled differently
color: ${({ active, checked }) =>
active ? '#44b3db' : checked ? 'red' '#c7e8f4'};
`;
const StepDescription = styled('div')`
width: 240px;
height: 29px;
font-family: Roboto;
font-size: 18px;
font-weight: 500;
font-style: normal;
letter-spacing: normal;
line-height: normal;
text-align: center;
text-transform: uppercase;
margin: 0 auto;
`;

How to style multiple variations of a <button> with Styled Components

I have a simple colored button in my app that needs to change color based on some UI state:
const StyledButton = styled.button`
& {
border: 0;
color: white;
cursor: pointer;
}
&:hover {
background-color: ${(props) => props.theme.hoverColor};
}
&.base {
background-color: ${(props) => props.theme.baseColor};
}
&.selected {
background-color: ${(props) => props.theme.selectedColor};
}
&&.danger:hover {
background-color: ${(props) => props.theme.dangerColor};
}
&.disabled {
background-color: ${(props) => props.theme.disabledColor};
}
`;
Previously I was using CSS Modules to make sure classes would not "leak" to other styles and I have obtained the styled component by essentially a 1-to-1 translation.
.Button {
border: 0;
color: white;
cursor: pointer;
}
.Button:hover {
background-color: var(--hover-color);
}
.Button.base {
background-color: var(--base-color);
}
.Button.selected {
background-color: var(--selected-color);
}
.Button.Button.danger:hover {
background-color: var(--danger-color);
}
.Button.disabled {
background-color: var(--disabled-color);
}
I like the theming support in Styled Components, but I dislike how now my "local" classes are no longer local to the component. I consider this a significant tradeoff.
Am I missing the correct way of implementing multiple variants of a component?
At the moment I have found two solutions:
The first is to calculate by hand the end result and avoid any classes
const StyledButton = styled.button`
& {
border: 0;
color: white;
cursor: pointer;
}
${(props) =>
props.isBase &&
css`
&,
&:hover {
background-color: ${(props) => props.theme.baseColor};
}
`}
${(props) =>
props.selectedColor &&
css`
&,
&:hover {
background-color: ${(props) => props.theme.selectedColor};
}
`}
${(props) =>
props.isDisabled &&
css`
&,
&:hover {
background-color: ${(props) => props.theme.disabledColor};
}
`}
${(props) =>
props.isDanger &&
css`
& {
background-color: ${(props) => props.theme.dangerColor};
}
&:hover {
background-color: ${(props) => props.theme.dangerColor};
}
`}
`;
which works but is quite ugly
and the other is to split the StyledButton into many components:
const StyledButton = styled.button`
& {
border: 0;
color: white;
cursor: pointer;
background-color: ${(props) => props.theme.baseColor};
}
`;
const StyledSelectedButton = styled(StyledButton)`
& {
background-color: ${(props) => props.theme.selectedColor};
}
`;
const StyledDisabledButton = styled(StyledButton)`
& {
background-color: ${(props) => props.theme.disabledColor};
}
`;
const StyledDangerButton = styled(StyledButton)`
& {
background-color: ${(props) => props.theme.baseColorColor};
}
&&:hover {
background-color: ${(props) => props.theme.dangerColor};
}
`;
Which is better but I am afraid of an eventual combinatorial explosion once more complex states are involved (a selected danger component) or more pseudoclasses are involved.
Moreover if the button in question had a state (e.g. a click counter) changing style would destroy the state.
I cannot think of a solution around this problem.

React Styled Component's CSS method optimization

Which is more optimized and why?
I hear that the css method from Styled-component is pretty expensive but I'm wondering if the multiple nested ${(prop) => {}} is more expensive than the single fn with the css method used inside.
Approach 1
const Foo = styled.div`
border-radius: 50%;
display: flex;
width: ${(props) => props.size};
height: ${(props) => props.size};
color: ${(props) => props.bgColor};
background-color: ${(props) => props.bgColor};
`;
vs
Approach 2
const Bar = styled.div`
border-radius: 50%;
display: flex;
${(props) => {
return css`
width: ${props.size};
height: ${props.size};
color: ${props.bgColor};
background-color: ${props.bgColor};
`;
}}
`;
Performance difference should be negligible. The only optimization Styled Components would make is if your styles were static (with no interpolations).
Note that styled.div or any other of the Styled CSS methods already do the same work as css. And these methods accept functions as arguments (instead of interpolated strings). So you can take it a step further and do this:
const Baz = styled.div((props) => css`
border-radius: 50%;
display: flex;
width: ${props.size};
height: ${props.size};
color: ${props.bgColor};
background-color: ${props.bgColor};
`);

Resources