I am trying to create responsive props for styled components as follows. To start with, we have a component (let's say a button):
<Button primary large>Click Me</Button>
This button will get a background-color of primary and a large size (as determined by a theme file).
I now want to create a responsive version of this button. This is how I would like that to work:
<Button
primary
large
mobile={{size: 'small', style: 'secondary'}}
tablet={size: 'small'}}
widescreen={{style: 'accent'}}
>
Click Me
</Button>
I now have my same button, but with the styles and sizes varied for different screen sizes.
Now, I have gotten this to work -- but it involves a lot of duplicate code. This is an example of what it looks like:
const Button = styled('button')(
({
mobile,
tablet,
tabletOnly,
desktop,
widescreen
}) => css`
${mobile &&
css`
#media screen and (max-width: ${theme.breakpoints.mobile.max}) {
background-color: ${colors[mobile.style] || mobile.style};
border: ${colors[mobile.style] || mobile.style};
border-radius: ${radii[mobile.radius] || mobile.radius};
color: ${mobile.style && rc(colors[mobile.style] || mobile.style)};
}
`}
${tablet &&
css`
#media screen and (min-width: ${theme.breakpoints.tablet.min}), print {
background-color: ${colors[tablet.style] || tablet.style};
border: ${colors[tablet.style] || tablet.style};
border-radius: ${radii[tablet.radius] || tablet.radius};
color: ${tablet.style && rc(colors[tablet.style] || tablet.style)};
}
`}
${tabletOnly &&
css`
#media screen and (min-width: ${theme.breakpoints.mobile.min}) and (max-width: ${theme.breakpoints.tablet.max}) {
background-color: ${colors[tabletOnly.style] || tabletOnly.style};
border: ${colors[tabletOnly.style] || tabletOnly.style};
border-radius: ${radii[tabletOnly.radius] || tabletOnly.radius};
color: ${tabletOnly.style &&
rc(colors[tabletOnly.style] || tabletOnly.style)};
}
`}
`
What I am looking for is a way to simplify this code. Basically, I want to only write the CSS styles ONCE and then generate the different props and media queries based off of a query object that something like this:
const mediaQueries = {
mobile: {
min: '0px',
max: '768px'
},
tablet: {
print: true,
min: '769px',
max: '1023px'
},
desktop: {
min: '1024px',
max: '1215px'
},
widescreen: {
min: '1216px',
max: '1407px'
},
fullhd: {
min: '1408px',
max: null
}
}
I imagine I should be able to create a function that loops through through the mediaQueries object and inserts the appropriate css for each iteration. However, I can't seem to figure out how to do this.
Any ideas on how to do this?
Also, thanks in advance for any help you can offer.
Maybe something like this is what you are looking for:
import { css } from "styled-components";
//mobile first approach min-width
const screenSizes = {
fullhd: 1408,
widescreen: 1215,
desktop: 1023,
tablet: 768,
mobile: 0
}
const media = Object
.keys(screenSizes)
.reduce((acc, label) => {
acc[label] = (...args) => css`
#media (min-width: ${screenSizes[label] / 16}rem) {
${css(...args)}
}
`
return acc
}, {});
Then you just import and use like so:
import media from './media'
const button = styled.button`
${({large , small})=> media.mobile`
color: red;
font-size: ${large ? '2em' : '1em'};
`}
`
Here's some further reading including using with theming:
Media queries in styled-components
Utilizing Props:
Using the same media query object from above:
Create a helper function to format the styles object to a css string:
const formatCss = (styleObject) => {
return JSON.stringify(styleObject)
.replace(/[{}"']/g,'')
.replace(/,/g,';')
+ ';'
}
Create another helper function to map over the styles and generate queries by mapping over its keys and using bracket notation dynamically add queries:
const mapQueries = (myQueries) =>{
return Object.keys(myQueries).map(key=> media[key]`
${formatCss(myQueries[key])}
`)
}
In your styled-component:
export const Button = styled.button`
${({myQueries}) => !myQueries ? '' : mapQueries(myQueries)}
`
Finally add a myQueries prop to your component like so (notice the use of css-formatted keys instead of javascriptFormatted keys for simplicity):
<Button myQueries={{
mobile:{ color:'red' },
tablet:{ color:'blue', "background-color":'green'},
desktop:{ height:'10rem' , width:'100%'}
}}>Button</Button>
For iterating through all your media queries, you can create a function similar to:
import { css } from "styled-components";
const sizes = {
desktop: 992,
tablet: 768,
phone: 576
};
// Iterate through the sizes and create a media template
const media = Object.keys(sizes).map(screenLabel => {
return {
query: (...args) => css`
#media (max-width: ${sizes[screenLabel] / 16}em) {
${css(...args)}
}
`,
screenLabel
};
});
export default media;
For usage in the component:
import media from "./media";
// The labels for this has to be same as the ones in sizes object
const colors = {
phone: "red",
tablet: "yellow",
desktop: "green"
};
const Heading = styled.h2`
color: blue;
${media.map(
({ query, screenLabel }) => query`
color: ${colors[screenLabel]};
`
)}
`;
Related
i'm making a React Native project, and basically, i wanted to change a styled component background color based on the value of the api, but no idea how i'll make this
(i'm using: React Native, Expo, typescript and styled components)
i wanted the ContainerRating (which is a styled component) background turn to green if levelRiskMorse in API is equal to "NoRisk"
This is the api server.json:
"allTasks": [
{
"patientName": "PacientOne",
"taskName": "Walk",
"executors": "Person3 - Doctor",
"institutionName": "IntitutionName",
"generalObservations": "Observations Test",
"planType": "1588",
"time": "07:00",
"risk": true,
"levelRiskMorse": "NoRisk",
"levelRiskBarden": "Low"
},
this is a fake API !
Component itself and the styles.ts:
[...]
const [risk, setRisk] = useState('');
//fetching data
useEffect(() => {
async function getRiskLevel(){
const { data } = await api.get('allTasks');
const response = data.forEach((item: any | undefined) => {
return item.levelRiskMorse
})
setRisk(response);
}
getRiskLevel();
}, [])
return(
<View>
<ContainerRating> // if levelRiskMorse === "NoRISK" color turn to green
<Text>Sem Risco</Text>
<Text>0-24</Text>
</ContainerRating>
</View>
)
}
export const ContainerRating = styled.View`
border-width: 0.6px;
border-color: ${({theme}) => theme.colors.default_color};
flex-direction: row;
justify-content: space-around;
height: 50px;
align-items: center;
margin-top: 2px;
border-left: unset;
`;
sorry if i didn't make it clear, im not used to ask questions here
Simple do something like:
<ContainerRating style={{color: levelRiskMorse==="NoRISK" ? 'green' : defaultColor }}
<Text>Sem Risco</Text>
<Text>0-24</Text>
</ContainerRating>
What's happening? I'm applying a style object directly in the component (every component has this property). levelRiskMorse==="NoRISK" it's an if condition inline. If it is true, 'green' will be set, otherwise a default color variable you want to use.
I have a slideshow based on an array of objects with its characteristics, one of them is the background-color of the current slide. I have a property called bg which stores it. This is what I am using to set each background-color, which changes to every image, however I am using an inline style to do that.
I'd like to know if there is a way to do that without using this inline style?
Here is a sample of my code:
import React from 'react'
import { Fragment } from 'react'
import classes from './MainPageHeader.module.css'
const MainPageHeader = props => {
let [minorSlideImg, setMinorSlideImg] = React.useState(0)
let minorSlides = [
{
img: require('../../../assets/images/Header/MinorSlider/imgSolo1-mainpage.png'),
alt: 'Produto 1',
linkText: ['PRODUTO 5', '$ 19.99'],
productId: 5,
bg: 'rgb(151, 105, 105)'
},
{
img: require('../../../assets/images/Header/MinorSlider/imgSolo2-mainpage.png'),
alt: 'Produto 2',
linkText: ['PRODUTO 13', '$ 199.99'],
productId: 13,
bg: '#fad3e0'
},
{
img: require('../../../assets/images/Header/MinorSlider/imgSolo3-mainpage.png'),
alt: 'Produto 3',
linkText: ['PRODUTO 10', '$ 499.99'],
productId: 10,
bg: '#ccc'
},
{
img: require('../../../assets/images/Header/MinorSlider/imgSolo4-mainpage.png'),
alt: 'Produto 4',
linkText: ['PRODUTO 11', '$ 999.99'],
productId: 11,
bg: 'rgb(238, 225, 183)'
},
]
const passSlideHandler = () => {
if (minorSlideImg < minorSlides.length - 1) {
setMinorSlideImg(minorSlideImg + 1)
} else {
setMinorSlideImg(0)
}
}
React.useEffect(() => {
const interval = setTimeout(() => {
passSlideHandler()
}, 5000);
return () => clearTimeout(interval);
});
return (
<Fragment>
<div
className={classes.MinorSlider_subContainer}
style={{backgroundColor: minorSlides[minorSlideImg].bg}} // <= This is what I'd like to remove
>
<img
src={minorSlides[minorSlideImg].img}
alt={"img-1"}
/>
</div>
</Fragment>
)
}
export default MainPageHeader
CSS:
.MinorSlider_subContainer {
height: 65%;
width: 50%;
background-color: #ccc;
}
.MinorSlider_subContainer img {
max-width: 100%;
}
.MinorSlider_subContainer div {
position: relative;
background-color: white;
width: 100%;
height: 65px;
}
.MinorSlider_subContainer div > *{
display: flex;
flex-direction: column;
justify-content: space-evenly;
width: 100%;
height: 100%;
padding-left: 25px;
text-decoration: none;
}
.MinorSlider_subContainer div p {
margin: 0;
color: rgb(75, 75, 75);
}
As can be seen, every five seconds the index of minorSlides changes, therefore, the slide that is being shown changes as well. This index is used to refer to each feature of the current slide.
So, is there a way to remove this inline style and make my JSX cleaner?
If I was using HTML, CSS and JS I could do that with JQuery or even plain JS, but I don't know how to do that here. I know I could create the element with a loop, but I'd like to keep changing only the index and not the whole element.
Here is the sildeshow:
If you can't create a css class for every color, the other option is to add the style tag and override the background-color property:
const subcontainerBackground = `.${classes.MinorSlider_subContainer} { background-color: ${minorSlides[minorSlideImg].bg}}`
return {(
<Fragment>
<style>
{subcontainerBackground}
</style>
<div className={classes.MinorSlider_subContainer} >
//....
</div>
</Fragment>
}
EDIT
Also you can add the style tag using Document.createElement():
useEffect(() => {
const content = `.${classes.MinorSlider_subContainer} { background-color: ${minorSlides[minorSlideImg].bg}}`;
const style = document.createElement("style");
style.innerHTML = content;
document.head.appendChild(style);
return () => document.head.removeChild(style);
}, [minorSlideImg]);
Well it should be alright to use inline styling in this case.
Additionally you can do something like:
{
...
bgClass: 'red'
}
add that class to the div element:
<div className={`classes.MinorSlider_subContainer ${minorSlides[minorSlideImg].bgClass}`} />
and style it in the end:
.red {
background: 'red';
}
Or you can try to use reference
const ref = useRef()
useEffect(() => {
if (ref.current == null) {
return
}
ref.current.style.backgroundColor = minorSlides[minorSlideImg].bg
}, [minorSlideImg, ref])
<div ref={ref} />
Here is my solutions for the same. Except, I add the colours in a list and use the rand function.
Here is the colour list
const color_list = [
"tomato",
"blueviolet",
"cornflowerblue",
"indianred",
"MediumAquaMarine",
"MediumPurple",
"Rebeccapurple",
"sandybrown",
"seagreen",
"palevioletred",
"lightsteelblue",
"Gold",
"teal",
];
I create a variable and add random function to it, to select a bunch of colours.
const rando_color = color_list[Math.floor(Math.random() * color_list.length)];
Simply pass the variable in the style option in the html div tag to dynamically assign background color
<div
p={4}
style={{
backgroundColor: rando_color, // Added variable here
fontFamily: "Oswald",
border: "solid 2px white",
}}
>
<h1>Some Content</h1>
</div>
To make it simple, just add the variable directly in the tag without any curly braces. Also you could try using this variable in a react state for better loading. Add a default prop as well.
Is there anyway that I can remove the atributes which are inherited by the styled-component which has been extended from another one?
I will state a really quite stupid piece of code just to illustrate what I mean:
const FormInput = styled.input.attrs({
type: "text",
})`
border-color: blue;
color: red;
${({$size}) => `
height: ${$size}rem;
width: ${$size}rem;
`}
`;
const Label = styled(FormInput)``
type: "text" is inherited by Label. Is there anyway that I could not have it rendered in this case? Let's say that I would render <Label as="Label" /> in this case
Since version 5.1 you can pass a shouldforwardprop predicate to .withConfig(), and filter out properties that you don't want to render:
const Label = styled(FormInput).withConfig({
shouldForwardProp: (prop, defaultValidatorFn) =>
!['type'].includes(prop)
&& defaultValidatorFn(prop),
})``;
To filter out several properties, extract the array to a const:
const forbiddenProps = ['type', 'placeholder'];
const Label = styled(FormInput).withConfig({
shouldForwardProp: (prop, defaultValidatorFn) =>
!forbiddenProps.includes(prop)
&& defaultValidatorFn(prop),
})``;
I'm trying to display a label that says "required" or "optional" at the end of a tag using the CSS after selector. My idea is to look like this.
I'm using React.js and the styled-components library, so I take props.required as an argument, "required" when {props.required == true}, "required" when {props.required == false} I tried to switch the display, such as "optional".
// form.js
<FormTitle required={true}>Name</FormTitle>
<FormTitle required={false}>Phone Number</FormTitle>
// styles.js
import styled from 'styled-components'
const FormTitle = styled.div`
...
&:after {
content: ${props => props.required ? "required": "optional"};
background-color: ${props => props.required ? "#FF0101": "#6FC173"};
...
}
`
But when I do this, the label does not appear.
Apparently, switching the content property of CSS with the if the statement of javascript causes a problem. Because if you don't use an if statement for the content property, everything else works.
...
&:after {
content: "required";
background-color: ${props => props.required ? "#FF0101": "#6FC173"};
...
}
`
Is there a way to solve this?
The content property expects the value to be wrapped by quotes. Wrap the expression with standard quotes:
const FormTitle = styled.div`
::after {
content: '${props => props.required ? "required": "optional"}';
background-color: ${props => props.required ? "#FF0101": "#6FC173"};
}
`
Another option is to use the styled component .attrs() to set a data attribute on the element, and then you use the attribute's value as the content of the pseudo element (::after):
const FormTitle = styled.div.attrs(props => ({
'data-required': props.required ? 'required': 'optional',
}))`
&::after {
content: attr(data-required);
background-color: ${props => props.required ? "#FF0101": "#6FC173"};
}
`
Examples - Sandbox
This is my first time using Material UI (I'm also a noob with react in general) and I cant seem to change the size of the toggle switch I'm using.
This is what I have so far -minus all the non related stuff:
import React, { Component } from "react";
import Switch from "#material-ui/core/Switch";
const styles = {
root: {
height: "500",
},
};
class ToggleActive extends Component {
state = {
checked: true,
};
handleChange = name => event => {
this.setState({ [name]: event.target.checked });
};
render() {
return (
<label htmlFor="normal-switch">
<Switch
classes={styles.root}
checked={this.state.checked}
onChange={this.handleChange("checked")}
/>
</label>
);
}
}
export default ToggleActive;
I just want to make it a bit larger, and change the color. Any help would be appreciated!
The change in the Switch component requires a little bit of detailed styling. I added some comments in parts that are not very obvious:
import {createStyles, makeStyles, Switch, Theme} from '#material-ui/core';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
width: 54,
height: 40,
padding: 0,
margin: theme.spacing(1),
},
switchBase: {
padding: 1,
'&$checked': {
// This is the part that animates the thumb when the switch is toggled (to the right)
transform: 'translateX(16px)',
// This is the thumb color
color: theme.palette.common.white,
'& + $track': {
// This is the track's background color (in this example, the iOS green)
backgroundColor: '#52d869',
opacity: 1,
border: 'none',
},
},
},
thumb: {
width: 36,
height: 36,
},
track: {
borderRadius: 19,
border: `1px solid ${theme.palette.grey[300]}`,
// This is the background color when the switch is off
backgroundColor: theme.palette.grey[200],
height: 30,
opacity: 1,
marginTop: 4,
transition: theme.transitions.create(['background-color', 'border']),
},
checked: {},
focusVisible: {},
})
);
You can implement this as a functional component:
import React, { useState } from 'react';
// import {createStyles, makeStyles, ...
// const useStyles = makeStyles((theme: Theme) => ...
export const ToggleItem: React.FC = () => {
const styles = useStyles();
const [toggle, setToggle] = useState<boolean>(false);
return (
<Switch
classes={{
root: styles.root,
switchBase: styles.switchBase,
thumb: styles.thumb,
track: styles.track,
checked: styles.checked,
}}
checked={toggle}
onChange={() => setToggle(!toggle)}
name={title}
inputProps={{'aria-label': 'my toggle'}}
/>
);
};
This is now even easier to accomplish because MUI has an official example in the documentation:
https://mui.com/material-ui/react-switch/#customization
Using that as an example, the minimum number of changes to accomplish making the switch bigger is actually just this:
export const MuiSwitchLarge = styled(Switch)(({ theme }) => ({
width: 68,
height: 34,
padding: 7,
"& .MuiSwitch-switchBase": {
margin: 1,
padding: 0,
transform: "translateX(6px)",
"&.Mui-checked": {
transform: "translateX(30px)",
},
},
"& .MuiSwitch-thumb": {
width: 32,
height: 32,
},
"& .MuiSwitch-track": {
borderRadius: 20 / 2,
},
}));
Here is the link to a forked sandbox with just a bigger switch:
https://codesandbox.io/s/customizedswitches-material-demo-forked-4m2t71
Consider this: I am not a front-end developer and did not develop in
Material-UI framework for years now. so just look for a different answer or send
me an edit version which works.
For changing the size of the switch component you should use size props that can be in two size 'small' || 'medium'.For example:
<Switch
size="small"
checked={this.state.checked}
onChange={this.handleChange("checked")}
color='primary'
/>
If it doesn't work for you then You need to change CSS style at root class:
const styles = {
root: {
height: 500,
width: 200},
};
Due to material-UI component API for changing the color of a switch you need to add a color props into you Switch JSX tag and choose from these enum:
enum: 'primary' |'secondary' | 'default'
your Switch should be like this:
<Switch
classes={styles.root}
checked={this.state.checked}
onChange={this.handleChange("checked")}
color='primary'
/>
Material-UI for switch size prop