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

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.

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

CSS Module with ClassNames Emotion migration

An example react file
const classNames = {
...defaultClassNames,
disabled: 'MyComponent--disabled',
selected: 'MyComponent--selected',
container: classnames('MyComponent', 'MyComponent-v2'),
};
return (
<MyComponent
classNames={classNames}
/>
An example CSS module file
.MyComponent-v2 {
.MyComponent-wrapper {
padding-bottom: 0;
}
.MyComponent:not(.MyComponent--disabled){
&:hover {
background: none !important;
.MyComponent-v2 {
background-color: red;
border-radius: 50%;
}
}
}
.MyComponent:focus,
}
How do I migrate this css module to emotion 10, all the examples I can find are using inline emotion with just a single css property like,
css={css`
background-color: hotpink;
&:hover {
color: ${color};
}
`}

I want to reuse certain CSS properties in Emotion/styled component

import styled from '#emotion/styled';
type ColorProps = {
Coloured: boolean,
}
const BoxStyled = styled.div`
${(props:ColorProps) =>
props.Coloured ? {
background: "#304f8f",
border: "1.85px solid #304f8f",
color: "white",
width: "4rem",
height: "2.5rem",
padding:"0 11px"
} :
{
border: "1.98px solid #2c8090",
width: "4rem",
height: "2.5rem",
padding:"0 11px"
}
}`
here in BoxStyled I don't want to write width: "4rem", height: "2.5rem", padding:"0 11px" twice how do I achieve this ?
Use css to make a variable of style.
import { css } from 'styled-components'
const reuse = css`
width: 4rem;
height: 2.5rem;
padding: 0 11px;
`
const StyleA = styled.div`
background-color: black;
${reuse}
`
const StyleB = styled.div`
background-color: red;
${reuse}
`
Check this for more information.
You can define the main style and when needed to change overwrite new style.
import styled from '#emotion/styled';
type ColorProps = {
Coloured: boolean,
}
const BoxStyled = styled.div`
border: "1.98px solid #2c8090",
width: "4rem",
height: "2.5rem",
padding:"0 11px"
${(props:ColorProps) =>
props.Coloured && {
background: "#304f8f",
border: "1.85px solid #304f8f",
color: "white",
}
}`

I'm creating a reusable alert box in Angular 9. When I'm changing the alert type, the proper class is not applied

I'm trying to create a reusable alert component in angular 9. But I'm getting an issue. In the alert-box selector when I'm trying to give the alert type, it's not at all changing the alert type.
Only I'm getting the plain text. Styles are also not being applied.
Can someone please help me to fix this issue.
Usage:
<app-alert-box alertType="warning">
Hi this is alert
</app-alert-box>
My Code:
alert-box.component.html
<ng-container>
<div
[ngClass]="{
'alert-danger': alertType == 'danger',
'alert-info': alertType == 'info',
'alert-success': alertType == 'success',
'alert-warning': alertType == 'warning'
}"
class="alert"
role="alert"
>
<span class="alert-content" #alertContent>
<ng-content></ng-content>
</span>
<button (click)="alertClose()" *ngIf="closeButton" aria-label="Close">
X
</button>
</div>
</ng-container>
alert-box.component.scss
.alert {
padding: 20px;
color: white;
opacity: 1;
transition: opacity 0.6s;
margin-bottom: 15px;
.danger {
background-color: #f44336;
}
.success {
background-color: #4caf50;
}
.info {
background-color: #2196f3;
}
.warning {
background-color: #ff9800;
}
}
.closebtn {
margin-left: 15px;
color: white;
font-weight: bold;
float: right;
font-size: 22px;
line-height: 20px;
cursor: pointer;
transition: 0.3s;
}
.closebtn:hover {
color: black;
}
alert-box.component.ts
import { Component, OnInit, Input } from "#angular/core";
#Component({
selector: "app-alert-box",
templateUrl: "./alert-box.component.html",
styleUrls: ["./alert-box.component.scss"]
})
export class AlertBoxComponent implements OnInit {
#Input() alertType = "info";
#Input() closeButton = false;
#Input() autoClose = false;
#Input() autoCloseAfter = 5000;
alertOpen = true;
constructor() {}
ngOnInit(): void {
if (this.autoClose) {
const timer = setTimeout(() => {
this.alertClose();
clearTimeout(timer);
}, this.autoCloseAfter);
}
}
alertClose(): void {
this.alertOpen = false;
}
}
You classes are called alert-danger, alert-success,... but your css is just referring to alert, success
Change to css to
.alert {
padding: 20px;
color: white;
opacity: 1;
transition: opacity 0.6s;
margin-bottom: 15px;
&.alert-danger {
background-color: #f44336;
}
&.alert-success {
background-color: #4caf50;
}
&.alert-info {
background-color: #2196f3;
}
&.alert-warning {
background-color: #ff9800;
}
}
Stackblitz example

Resources