implementing dark mode with Themeprovider and styled components - reactjs

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)'};
}`

Related

REACT + TypeScript Accordion transition doesn't works

Sup guys i have a problem, i would like to do an animated accordion, it should to have a transition animated when it opens and collapse, and in icon switching
I'm creating by myself an accordion in react + typescript but this transition doesnt works and idk why, code below:
This my index.tsx
import { useState } from "react";
import { AccordionButton, AccordionContent, Wrapper } from "./styles";
import { AccordionProps } from "./interfaces";
import { BsChevronDown, BsChevronUp } from "react-icons/bs";
export default function Accordion({ title, text }: AccordionProps) {
const [isOpen, setIsOpen] = useState(false);
const handleClick = () => {
setIsOpen(!isOpen);
returnIcon();
};
const returnIcon = () => {
return isOpen ? <BsChevronUp /> : <BsChevronDown />;
};
return (
<Wrapper>
<AccordionButton onClick={handleClick}>
{title} {returnIcon()}
</AccordionButton>
<AccordionContent isOpen={isOpen}>
<p>{text}</p>
</AccordionContent>
</Wrapper>
);
}
and this is my styled component below:
import styled from "styled-components";
import { AccordionContentProps } from "./interfaces";
export const AccordionButton = styled.button`
background-color: #606582;
color: #ffffff;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
transition: 0.6s;
&:hover {
background-color: #60658295;
}
> svg {
float: right;
}
`;
export const AccordionContent = styled.div<AccordionContentProps>`
display: ${(props) => (props.isOpen === false ? "none" : "block")};
padding: 0 18px;
background-color: white;
overflow: hidden;
`;
export const Content = styled.div`
padding: 10px 0px;
flex-wrap: wrap;
max-width: 750px;
margin-right: auto;
margin-left: auto;
#media only screen and (max-width: 600px) {
max-width: 80%;
}
`;
export const Wrapper = styled.div`
padding: 10px 0px;
`;
I've tried to add this code but still doesnt working
-webkit-transition: all 0.4s ease-in;
-moz-transition: all 0.4s ease-in;
-o-transition: all 0.4s ease-in;
transition: all 0.4s ease-in;
Try to replace the return block with the following:
return (
<Wrapper>
{isOpen && <AccordionButton onClick={handleClick}>
{title} <BsChevronUp />
</AccordionButton>}
{!isOpen && <AccordionButton onClick={handleClick}>
{title} <BsChevronDown />
</AccordionButton>}
<AccordionContent isOpen={isOpen}>
<p>{text}</p>
</AccordionContent>
</Wrapper>)
and we can also delete the function returnIcon - we don't need it

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

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>
);
};

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

Give diferent className to MenuProps and Parent Select Componens with styled-components

I'm trying to customize a MUI TextField Select component with styled components.
The ideia is styled-compoents provide diferent classes to Select field and Menu, so i can have their styled separated.
const StyledSelect = styled(({ className, ...props }) => {
return (
<TextField {...props}
classes={{ root: className }}
SelectProps={{
MenuProps: {
classes: { paper: className, list: className },
anchorOrigin: {
vertical: "bottom",
horizontal: "left"
},
transformOrigin: {
vertical: "top",
horizontal: "left"
},
getContentAnchorEl: null
},
}}
/>
)
})`
& {
background-color: #454D5D;
border-radius: 10px;
margin-top: 5px;
}
& li {
color: #FFF;
}
&.MuiFormControl-root {
background-color: transparent;
}
& .MuiListItem-root {
font-size: 18px;
}
& .MuiListItem-root.Mui-selected {
background-color: #1A2233;
}
& .MuiFormLabel-root {
font-family: 'Roboto';
font-weight: 300;
}
& .MuiInputLabel-shrink {
color: ${props => props.color};
font-weight: normal;
}
& .MuiInput-underline:after {
border-bottom: 2px solid ${props => props.errors[props.field.name] && props.touched[props.field.name]
? CASABLANCA : props.color};
transition: none;
transform: none;
}
& .MuiInput-underline:before {
border-bottom: 1px solid ${props => props.color}
}
& .MuiSelect-roo {
color: black;
font-family: 'Roboto';
font-weight: 300;
}
& .MuiSelect-select:focus {
background: transparent;
}
`;
I wish my TextField class would be different from MenuProps class
One way to solve this is to have one wrapper component per class name you need to generate. In my example below, StyledTextField takes care of the root class name for TextField (the className property is equivalent to classes.root) and then MenuPaperClass provides an additional class name.
import React from "react";
import ReactDOM from "react-dom";
import TextField from "#material-ui/core/TextField";
import MenuItem from "#material-ui/core/MenuItem";
import styled from "styled-components";
const StyledTextField = styled(TextField)`
/* && to add specificity */
&& {
border: 1px solid green;
}
`;
const MenuPaperClass = styled(({ className, ...props }) => {
return (
<StyledTextField
SelectProps={{ MenuProps: { classes: { paper: className } } }}
value="1"
select
{...props}
>
<MenuItem value="1">One</MenuItem>
<MenuItem value="2">Two</MenuItem>
<MenuItem value="3">Three</MenuItem>
</StyledTextField>
);
})`
&& {
background-color: lightblue;
}
`;
function App() {
return (
<div className="App">
<MenuPaperClass />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
This isn't a particularly elegant solution and gets pretty tedious if you have 3 or more separate classes you want to use, so I may come back to this later to consider alternative approaches/syntax, but this does work.

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