How to make styled components mixins with props as css property? - reactjs

I'm building an app with React and Styled components. I'd like to use mixins inside the styles, and pass a CSS property inside this mixin. I tried like this but it's not working:
const TestMixin = ({ color, property }) => css`
${property}: ${color === "blue"
? "blue"
: color === "green"
? "green"
: "red"};
&:hover {
${property}: ${color === "blue"
? "red"
: color === "green"
? "blue"
: "green"};
}
`
const Container = styled.div`
background-color: ${TestMixin({
property: "background-color"
})};
`
How could I make this work? Thanks!

You can't to pass CSS property as an object key. If you want to create a similar scss mixin you can only pass CSS values to the mixin. To set CSS mixin in Container, just use the mixin without CSS property.
import styled, { css } from 'styled-components';
const TestMixin = ({ color }) => css`
background-color: ${color === 'blue' ? 'blue' : color === 'green' ? 'green' : 'red'};
&:hover {
background-color: ${color === 'blue' ? 'red' : color === 'green' ? 'blue' : 'green'};
}
`;
const Container = styled.div`
${TestMixin({ color: 'blue' })};
padding: 1rem 2rem;
`;
export const App = () => {
return <Container>Some text</Container>;
};
Try this solution with Container and container props, it is more flexible and customizable. If you removed the props, the style will not be applied.
import styled, { css } from 'styled-components';
interface Colors {
color: string;
hover: string;
}
interface Props {
bgColor?: Colors;
borderColor?: Colors;
textColor?: Colors;
}
const bgMixin = (color: Colors) => css`
background-color: ${color.color};
&:hover {
background-color: ${color.hover};
}
`;
const borderMixin = (color: Colors) => css`
border-color: ${color.color};
&:hover {
border-color: ${color.hover};
}
`;
const textMixin = (color: Colors) => css`
color: ${color.color};
&:hover {
color: ${color.hover};
}
`;
const Container = styled.div<Props>`
padding: 1rem 2rem;
${({ bgColor }) => bgColor && bgMixin(bgColor)}
${({ textColor }) => textColor && textMixin(textColor)}
border-width: 2px;
border-style: solid;
${({ borderColor }) =>
borderColor
? borderMixin(borderColor)
: css`
border-color: transparent;
`}
`;
export const App = () => {
return (
<Container
bgColor={{ color: 'blue', hover: 'red' }}
borderColor={{ color: 'red', hover: 'blue' }}
textColor={{ color: 'white', hover: 'black' }}
>
Styled Components
</Container>
);
};

Related

How to pass props to styled component when using theme?

I want to use the color prop I get from Button component to use it for background color using styled component theme.
import React from "react";
import styled from "styled-components";
const StyledButton = styled("button")<{
color: string;
padding: string;
}>`
background: ${(props) => props.theme.colors.primary};
outline: none;
box-shadow: none;
border: none;
padding: ${(props) => props.padding};
border-radius: 5px;
color: white;
font-size: ${(props) => props.theme.fontSizes.small};
margin: 0 10px;
`;
interface Props extends React.ComponentPropsWithoutRef<"button"> {
color?: string;
padding?: string;
}
const Button = ({ children, color, padding }: Props) => {
return (
<StyledButton color={color!} padding={padding!}>
{children}
</StyledButton>
);
};
export default Button;
Theme:
import { DefaultTheme } from "styled-components";
const theme: DefaultTheme = {
colors: {
primary: "#5A8DEE",
boldPrimary: "#274d8c",
lightPrimary: "#dae3f5",
green: "#5CCf4C",
gray: "#F2F4F4",
white: "#FFFFFF",
text: "#5f5f63",
lightText: "#878791",
},
fontSizes: {
extraSmall: "0.75rem",
small: "1rem",
medium: "1.25rem",
large: "1.50rem",
},
};
export default theme;
Like when I get primary from Button props, I want it to get the color codes from the theme context I made.
If I'm understanding your question correctly you've several theme colors and you want to specify which to use from a prop passed to a component.
Given the theme colors:
colors: {
primary: "#5A8DEE",
boldPrimary: "#274d8c",
lightPrimary: "#dae3f5",
green: "#5CCf4C",
gray: "#F2F4F4",
white: "#FFFFFF",
text: "#5f5f63",
lightText: "#878791",
}
You can specify a color prop:
interface Props extends React.ComponentPropsWithoutRef<"button"> {
color?: string;
padding?: string;
}
In the styled component use the passed color prop to access into your theme
const StyledButton = styled("button")<{
color: string;
padding: string;
}>`
background: ${(props) => props.theme.colors[props.color]}; // <-- access by dynamic key
outline: none;
box-shadow: none;
border: none;
padding: ${(props) => props.padding};
border-radius: 5px;
color: white;
font-size: ${(props) => props.theme.fontSizes.small};
margin: 0 10px;
`;
const Button = ({ children, color, padding }: Props) => {
return (
<StyledButton color={color} padding={padding!}>
{children}
</StyledButton>
);
};
Then specify the color you want to use:
<Button color="primary" />
<Button color="boldPrimary" />
...etc...

Increase min-width of item - material ui (custom component)

Component which i have built
// libs
import React from 'react';
// material components
// styles
import {
StyledSelect,
StyledFormControl,
StyledMenuList,
ISelectProps,
} from './styles';
function Select(props: ISelectProps) {
const {
displayEmpty,
iconBordered,
options,
value,
children,
onChange,
className,
} = props;
return (
<StyledFormControl className={className}>
<StyledSelect
MenuProps={{
classes: { paper: 'Menupaper' },
anchorOrigin: {
vertical: 'bottom',
horizontal: 'left',
},
getContentAnchorEl: null,
}}
IconComponent={(props) => {
let iconClass = 'icon-container';
if (props.className.split(' ').indexOf('MuiSelect-iconOpen') > -1) {
iconClass += ' icon-rotate';
}
iconClass += ` ${props.className}`;
return (
<>
<span className="icon-border" />
<span className={iconClass}>
<i className="icon-arrow-down icon-rotate" />
</span>
</>
);
}}
autoWidth
iconBordered={iconBordered}
displayEmpty={displayEmpty}
onChange={onChange}
variant="outlined"
value={value}
>
{!!options
? options.map(({ value, label }) => (
<StyledMenuList disableRipple value={value}>
{label}
</StyledMenuList>
))
: children}
</StyledSelect>
</StyledFormControl>
);
}
export default Select;
Accessing the component:
<FormField>
<Left>
<Label>{t('search_pane.status')}</Label>
<Select
displayEmpty
menuClass="menu"
className="searchSelect"
value={chatState}
onChange={handleChatStatusChange}
options={Object.keys(CHAT_STATE_OPTIONS).map((el) => {
return { value: el, label: CHAT_STATE_OPTIONS[el] };
})}
/>
</Left>
<Right>
<Label>{t('search_pane.date')}</Label>
<Select
className="searchSelect"
menuClass="menu"
value={date}
onChange={handleDateChange}
options={Object.keys(DATE_OPTIONS).map((el) => {
return { value: el, label: DATE_OPTIONS[el] };
})}
/>
</Right>
</FormField>
Style of the MenuList:
export const StyledMenuList = styled(MenuItem)`
min-width: 125px;
margin-left: 6px;
margin-right: 6px;
padding-left: 8px;
font-size: ${({ theme }) => theme.font.sizes[500]};
color: ${({ theme }) => theme.colors.SECONDARY[600]};
transition: all 300ms ease;
transition-property: background-color, color;
&:focus {
background-color: ${({ theme }) => theme.colors.WHITE};
}
&:hover {
background-color: ${({ theme }) => theme.colors.PRIMARY[100]} !important;
color: ${({ theme }) => theme.colors.PRIMARY[600]};
border-radius: 4px;
}
`;
I want my min-width to be changeable from where I am calling the component
const FormField = styled.div`
display: flex;
margin-top: 18px;
margin-bottom: ${({ theme }) => theme.spacing[400]};
width: 100%;
.searchSelect {
width: 100%;
margin-top: 7px;
height: ${({ theme }) => theme.spacing[700]};
.MuiOutlinedInput-root {
height: ${({ theme }) => theme.spacing[700]};
}
.icon-container {
top: 22px;
}
li {
min-width: 192px;
}
}
`;
All the css changes takes effect except the li 192px can someone help?
I have tried passing a menuClass css etc but nothing has worked
EDIT:
I have also tried:
<StyledMenuList
classes={{ root: menuClass }}
disableRipple
value={value}
>
{label}
</StyledMenuList>
passing menuClass via props
Without a codesandbox it is a little harder, providing one would be of great help.
My suggest based on how material-ui Select works is that your li-MenuItems are not in the same DOM level as the .searchSelect element as your styling expects and that's why your styling is not getting applied.
Try to put it outside - something like this:
const FormField = styled.div`
...
.searchSelect {
width: 100%;
margin-top: 7px;
height: ${({ theme }) => theme.spacing[700]};
.MuiOutlinedInput-root {
height: ${({ theme }) => theme.spacing[700]};
}
.icon-container {
top: 22px;
}
}
li {
min-width: 192px;
}
`;

implementing dark mode with Themeprovider and styled components

I don't know much about styled-components, I am using a toggle switch to change themes and my theme does switch from dark to light but the icons I'm using don't switch icons. The icons are valid, when I switch the order of the component the Moon icon shows only instead, I'm guessing this is something with syntax?
import React from 'react'
import { func, string } from 'prop-types';
import styled from 'styled-components';
import { ReactComponent as MoonIcon } from '../components/icons/moon.svg';
import { ReactComponent as SunIcon } from '../components/icons/sun.svg';
const ToggleContainer = styled.button`
background: ${({ theme }) => theme.gradient};
border: 2px solid ${({ theme }) => theme.toggleBorder};
border-radius: 30px;
cursor: pointer;
display: flex;
font-size: 0.5rem;
justify-content: space-between;
margin: 0 auto;
overflow: hidden;
padding: 0.5rem;
position: relative;
width: 8rem;
height: 4rem;
svg {
height: auto;
width: 2.5rem;
transition: all 0.3s linear;
// sun icon
&:first-child {
transform: ${({ lightTheme }) => lightTheme ? 'translateY(0)' : 'translateY(100px)'};
}
// moon icon
&:nth-child(2) {
transform: ${({ lightTheme }) => lightTheme ? 'translateY(-100px)' : 'translateY(0)'};
}
}
`;
const Toggle = ({ theme, toggleTheme }) => {
const isLight = theme === 'light';
return (
<ToggleContainer onClick={toggleTheme} >
<MoonIcon />
<SunIcon />
</ToggleContainer>
);
};
Toggle.propTypes = {
theme: string.isRequired,
toggleTheme: func.isRequired,
}
export default Toggle;
lightmode
darkmode
Add lightTheme={isLight} to this code
At: <ToggleContainer onClick={toggleTheme} >
Final: <ToggleContainer onClick={toggleTheme} lightTheme={isLight}>
Also, you can play with transform like below for switching between,
`&:first-child {
transform: ${({ lightTheme }) => lightTheme ? 'translateX(0px)' : 'translateX(-150px)'};
}
&:nth-child(2) {
transform: ${({ lightTheme }) => lightTheme ? 'translateX(100px)' : 'translateX(0px)'};
}`

How to pass a prop to all styled component css properties at once?

Instead of repeatedly picking the prop like this...
const BubbleContainer = styled.View`
background-color: ${({ theme }) => (theme.debug ? 'white' : 'blue')};
border-width: ${({ theme }) => (theme.debug ? '1px' : '0px')};
color: ${({ theme }) => (theme.debug ? 'red' : 'black')};
`;
I'd like to do it once like this (pseudo code)...
const BubbleContainer = styled.View`
${({ theme }) =>
background-color: {theme.debug ? 'white' : 'blue'};
border-width: {theme.debug ? '1px' : '0px'};
color: {theme.debug ? 'red' : 'black'};
}
`;
You were close. You just need to use a backtick string for interpolation and multiline support.
const BubbleContainer = styled.View`
${({ theme }) => `
background-color: ${theme.debug ? 'white' : 'blue'};
border-width: ${theme.debug ? '1px' : '0px'};
color: ${theme.debug ? 'red' : 'black'};
`}
`;
There are a lot of options, this may be a little more legible:
const BubbleContainer = styled.View`
background-color: blue;
border-width: 0px;
color: black;
${({ theme }) => theme.debug && `
background-color: white;
border-width: 1px;
color: red;
`}
`;

React Styled-Components passing props issue

Trying to create a nested component that accepts multiple arguments, eg. styles for the button, styles for text and a possible icon. Everything (passing props) works fine if I directly render the button.
Below is the code I've written
import React from "react";
import styled from "styled-components";
import _ from "lodash";
import { hexToRgb } from "../styles/helpers";
import * as MaterialIcons from "styled-icons/material";
const StyledButton = styled.button`
text-align: center;
border: ${props =>
props.outline ? `1px solid ${props.outlineColor}` : "none"};
background: ${props => (props.background ? props.background : "#000")};
border-color: ${props =>
props.outlineColor ? props.outlineColor : "transparent"};
min-width: 120px;
width: ${props => (props.width ? props.width : "auto")};
min-height: 40px;
border-radius: 25px;
color: ${props => (props.color ? props.color : "#FFF")};
transition: all ease 0.5s;
&:hover {
cursor: pointer;
background: ${props =>
props.background ? hexToRgb(props.background) : "#FFF"};
}
`;
const StyledText = styled.span`
font-size: 16px;
font-weight: ${props => (props.fontWeight ? props.fontWeight : 400)};
`;
const StyledIcon = styled(MaterialIcons.Lock)`
font-size: 15;
padding: 10px;
`;
const Button = props => {
let _icon = null;
const { children, icon } = props;
console.log("props", props);
if (icon) {
_icon = <StyledIcon size="48" title="Test icon" />;
}
console.log("StyledButton", StyledButton);
return (
<StyledButton>
<StyledText>{children}</StyledText>
{_icon}
</StyledButton>
);
};
export default Button;
If I directly export default StyledButton it works fine.
For some odd reason props were not passed onto the StyledComponent, however directly stating what I want works.
return (
<StyledButton
outlineColor={outlineColor}
background={background}
width={width}
color={color}
outline={outline}
>
<StyledText>{children}</StyledText>
{_icon}
</StyledButton>
);

Resources