Styled Components → Inline Styling of Pseudo Selector in attrs() - reactjs

MWE
https://codesandbox.io/s/slider-example-ev250?file=/src/App.js
Question
I am attempting to create an <input type='range'> Styled Component that has a custom appearance:
const SliderInput = styled.input.attrs((props) => {
const val = Number(props.value ?? 0);
const min = Number(props.min ?? 0);
const max = Number(props.max ?? 1);
const breakpoint = 100 * ((val - min) / (max - min));
return {
style: {
background: `linear-gradient(to right, ${props.color}, ${props.color} ${breakpoint}%, white ${breakpoint}%, white 100%)`,
border: `solid 1px ${props.color}`
}
};
})`
-webkit-appearance: none;
width: 200px;
height: 8px;
border-radius: 12px;
&::-webkit-slider-thumb {
-webkit-appearance: none;
background: ${(props) => props.color};
border: 2px solid white;
border-radius: 50%;
width: 16px;
height: 16px;
}
&:hover {
cursor: grab;
}
`;
The issue I am facing is that the background color of the thumb slider changes too frequently, which causes lagging and I see the following warning:
Over 200 classes were generated for component styled.input with the id of "sc-dlnjwi".
Consider using the attrs method, together with a style object for frequently changed styles.
Example:
const Component = styled.div.attrs(props => ({
style: {
background: props.background,
},
}))`width: 100%;`
How can this be achieved with pseudo-selectors like ::-webkit-slider-thumb?
I've tried:
style: {
background: `linear-gradient(to right, ${props.color}, ${props.color} ${breakpoint}%, white ${breakpoint}%, white 100%)`,
border: `solid 1px ${props.color}`
"::WebkitSliderThumb": {
background: props.color
}
}
To no avail.
Any suggestions?

It's not possible to style psuedo-selectors using inline styles (see this SO thread for more context). In other words, this isn't possible:
<input type="range" style="::WebkitSliderThumb:background:red" />
That's what styled-components's .attrs is doing under the hood, it's just applying inline styles [docs].
The only way to apply styles to psuedo-selectors is to use CSS, and as you've already seen it's not practical to do this with styled-components. I'd suggest determining a fixed number of slider steps, generating CSS classes for each of those ahead of time (you can use a preprocessor like SASS to do this effeciently), and then just dynamically applying the correct class when the slider's value changes.

Related

how do i pass mulltiple props to set a background for styled range input

import React, { useState } from "react";
import styled from "styled-components";
const SliderInput = styled.input.attrs({ type: "range" })`
-webkit-appearance: none;
-moz-appearance: none;
margin-top: 3%;
outline: 0;
height: 80px;
border-radius: 15px;
background: ${({theme},{props}) =>
`linear-gradient(to right, ${theme.colors.primary} 0%, ${theme.colors.highlight} ${props.value/255*100}%, #fff ${props.value/255*100}%, #fff 100%);`};
box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.5);
::-webkit-slider-thumb {
-webkit-appearance: none;
width: 24px;
height: 24px;
background-image: radial-gradient(
circle,
${({ theme }) => theme.colors.primary} 40%,
${({ theme }) => theme.colors.secondary} 45%
);
border-radius: 50%;
box-shadow: 0px 0px 4px 2px rgba(0, 0, 0, 0.5);
}
::-moz-range-thumb {
width: 24px;
height: 24px;
-moz-appearance: none;
border-radius: 50%;
box-shadow: 0px 0px 4px 2px rgba(0, 0, 0, 0.5);
}
`;
const Slider = (props) => {
const [value, setValue] = useState(255);
function handleChange(e) {
setValue(e.target.value);
SetBrightness(e.target.value);
}
return (
<SliderInput
className={props.name}
min={0}
max={255}
value={value}
onChange={handleChange}
step={63.75}
/>
);
};
export default Slider;
function SetBrightness(value) {
if (window.fully !== undefined) {
window.fully.setScreenBrightness(parseFloat(value));
}
}
Im getting a bunch of errors trying to pass more than one prop to styled component.
If i just pass props.value it works and if i just pass theme that works but not both.
trying to make the slider fill with a gradient and it works if i declare the slider input in the same scope as the component and just use the value from state but that gives me an error in the console
"checkDynamicCreation.js:32 The component styled.input with the id of "sc-jrcTuL" has been created dynamically.
You may see this warning because you've called styled inside another component.
To resolve this only create new StyledComponents outside of any render method and function component."
There might be other issues, but it seems that the background line in SliderInput is trying to define two arguments while the props is one object.
Perhaps it could be changed to the following example to properly read the props.
Simplified live demo: stackblitz (omitted the SetBrightness function)
background: ${({ theme, value }) =>
`linear-gradient(to right, ${theme.colors.primary} 0%, ${
theme.colors.highlight
} ${(value / 255) * 100}%, #fff ${(value / 255) * 100}%, #fff 100%);`}

By using styled components, how can I add some opacity to a background color that comes from an external prop?

This is my code, below it you will find the question
import { printIntrospectionSchema } from 'graphql';
import React, { useContext, useState } from 'react';
import styled from 'styled-components';
import { Context } from '../../../Context/Context';
// DATA
function DurationIntervalSelection() {
const tabsData = [
{
text: 'Duration',
},
{
text: 'Interval',
},
];
// STATE
const [selectedIndex, setselectedIndex] = useState(0);
const { selected } = useContext(Context);
console.log(selected.color, 'selected color');
// FUNCTION
const handleIndexSelection = (index) => {
setselectedIndex(index);
};
return (
<Container>
<TabsContainer backgroundColor={selected.backgroundColor}>
<Indicator
backgroundColor={selected.color}
style={{
left: `${selectedIndex * 50}%`,
}}
/>
{tabsData.map((tab, index) => {
return (
<Tab
style={{
color: selectedIndex === index ? 'white' : 'black',
}}
onClick={() => handleIndexSelection(index)}
key={tab.text}
>
<p style={{ zIndex: 100 }}>{tab.text}</p>
</Tab>
);
})}
</TabsContainer>
</Container>
);
}
const Container = styled.div`
margin-top: 10px;
border: 1px solid #f2f2f7;
border-radius: 10px;
box-sizing: border-box;
padding: 10px 0;
`;
const Indicator = styled.div`
width: 50%;
position: absolute;
/* z-index: -1; */
height: 100%;
border-radius: 20px;
cursor: pointer;
transition: all 0.3s;
background-color: ${(props) => props.backgroundColor};
`;
const TabsContainer = styled.div`
display: flex;
width: 100%;
justify-content: space-between;
position: relative;
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2);
border-radius: 20px;
overflow: hidden;
background-color: ${(props) => props.backgroundColor};
`;
const Tab = styled.div`
display: flex;
width: 50%;
justify-content: center;
cursor: pointer;
align-items: center;
padding: 10px;
`;
export default DurationIntervalSelection;
As you can see from the above code i pass backgroundColor as a prop that takes some color from a React state.
<TabsContainer backgroundColor={selected.backgroundColor}>
And in the styled component I do:
background-color: ${(props) => props.backgroundColor};
But my question is, since i need to follow a design made by a colleague, how do i add opacity to the above background color without affecting the opacity of the whole div: TabsContainer. I mean, if i add on the TabsContainer -> opacity: 0.3 - for instance the whole TabsContainer div will be affected by the opacity, including the Tab div, while what I want is just to change opacity on the color itself so i do not affect any child. I hope it makes sense. Is there a way?
Okay if your hex value comes from your backend, you can either create your own javascript function that converts hex to rgba or you can use a library like polished. They have a function named rgba that converts a hex string and adds an alpha value to it
import { rgba } from 'polished'
const div = styled.div`
background: ${(props) => rgba(props.backgroundColor, 0.3)};
`
Or you can apply it directly to the prop
<TabsContainer backgroundColor={rgba(selected.backgroundColor, 0.3)}>
Example CodeSandbox.
Since the data from the backend where right but I could not find a solution I have realised that I could have added to
<TabsContainer backgroundColor="rgba(255,255,255, 0.5)">
A background color on the white notes with opacity so to emphasise the color behind it in a lighter way

How to prevent button from losing focus when clicking elsewhere on screen?

I am fairly new to React and I am using styled-components.
const Button = styled.button`
height: 35px;
width: 85px;
color: #0288d1;
border: 1px solid #0288d1;
background: white;
border-radius: 1px;
&:hover {
cursor: pointer;
background: #cfe8ef;
}
&:focus {
background: #0288d1;
color: white;
}
`;
I would like to prevent the button from losing focus when the user clicks somewhere else on the screen. Would I need to add additional JavaScript code to do this or is there a CSS property that can help me with this issue?
Try below:
const setFocus = e => {
e.target && e.target.focus();
};
return (
<button
onBlur={e => {
setFocus(e);
}}
>
My Button
</button>
);
You can add :active in global stylesheet.
button:active {
border: 2px solid #000;
}
So any time, button tags receive this style in active mode.

Design Bootstrap dynamic nav tabs component

I want to design a dynamic nav tabs component. when the card is clicked relevant tab is shown, with the connection arrow and border-color green.
sample code or a suggestion would be much helpful
.
You can use accordion by bootstrap. Use css flexbox to horizontally align the tabs next to each other and bind a javascript method that changes css color properties (arrow, green color) on clicking.
Here is the link - https://getbootstrap.com/docs/4.0/components/collapse/
Here is how you can do :
.js :
import React, { Component } from "react";
import { render } from "react-dom";
import "./style.css";
const App = () => {
const selectBlock = (e) => {
e.target.classList.toggle('selected');
}
return (
<div className="block" onClick={(e) => {selectBlock(e)}}>
<div>Here is the block</div>
<div className="arrow">
<FakeArrow />
</div>
</div>
);
};
const FakeArrow = () => {
return (
<div>
<span className="arrow-down-border" />
<span className="arrow-down" />
</div>
);
};
render(<App />, document.getElementById("root"));
.css :
.block {
position: relative;
width: 150px;
height: 50px;
text-align: center;
border: 2px solid black;
}
.arrow {
display: none;
}
.block.selected {
border: 2px solid #99d32c;
}
.block.selected .arrow {
display: block;
}
/* You need to fake the arrow border with another arrow behind */
.arrow-down-border {
position: absolute;
bottom: -20px;
left: 55px; /* 150px (main block) / 2 -20px (size of the triangle)*/
width: 0;
height: 0;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-top: 20px solid #99d32c;
}
.arrow-down{
position: absolute;
bottom: -17px;
left: 58px; /* 150px (main block) / 2 -17px (size of the triangle)*/
width: 0;
height: 0;
border-left: 17px solid transparent;
border-right: 17px solid transparent;
border-top: 17px solid #ffffff;
}
Here is the repro on Stackblitz.
Of course this is just an example, you have to set a color for the arrows so my advice would be to do it with constants or props. Same thing for the position and others functionality you can add to the FakeArrow component.
Now, it would be waaaayy easier to manage it with an image if you really need a border (this is the tricky part in your requirement), or a simple arrow without border.
You can take a look at this post, it's the same question actually, i used a slightly different way to do it with css, but the result seems to be the same.

Before and After pseudo classes used with styled-components

What is the proper way to apply :before and :after pseudo classes to styled components?
I know that you can use
&:hover {}
to apply the :hover pseudo class to a styled-component.
Does this work for All pseudo elements like before and after?
I have tried using the &:before and &:after strategy with some rather complex examples and i'm not sure if my attempts are not working because i've got something wrong with my example or it just doesn't work like that.
Does someone have some insight on this? Thank you.
Pseudo-selectors in styled-components work just like they do in CSS. (or rather, Sass) Whatever isn't working is likely a problem in your specific code, but that's hard to debug without seeing the actual code!
Here is an example of how to use a simple :after:
const UnicornAfter = styled.div`
&:after {
content: " 🦄";
}
`;
<UnicornAfter>I am a</UnicornAfter> // renders: "I am a 🦄"
If you post your code I'll likely be able to help debug your specific issue!
This will print the triangle at middle of the div.
const LoginBackground = styled.div`
& {
width: 30%;
height: 75%;
padding: 0.5em;
background-color: #f8d05d;
margin: 0 auto;
position: relative;
}
&:after {
border-right: solid 20px transparent;
border-left: solid 20px transparent;
border-top: solid 20px #f8d05d;
transform: translateX(-50%);
position: absolute;
z-index: -1;
content: "";
top: 100%;
left: 50%;
height: 0;
width: 0;
}
`;
This is good and simple answer:
https://stackoverflow.com/a/45871869/4499788 by mxstbr
but for elements requiring more complex logic I prefer this approach:
const Figure = styled.div`
${Square}:before,
${Square}:after,
${Square} div:before,
${Square} div:after {
background-color: white;
position: absolute;
content: "";
display: block;
-webkit-transition: all 0.4s ease-in-out;
transition: all 0.4s ease-in-out;
}
`;
As an object (note the double quotes):
const Div = styled.div({
'&:before': {
content: '"a string"',
},
})
Adding to #mxstbr answer
Note that when you want to render before based on props, don't forget to wrap it with double quotes (or single quotes) , example:
const Button = styled.button`
&:before {
content:"${(props)=>props.theme==='dark'?'dark theme':'white theme'}";
}
because
content:${(props)=>props.theme==='dark'?'dark theme':'white theme'};
will not work
Can try like this.
It works perfectly fine
var setValue="abc";
var elementstyle = '<style>YourClass:before { right:' + abc + 'px;}</style>'
$(".YourClass").append(setValue);
var rightMarginForNotificationPanelConnectorStyle = '<style>.authenticated-page.dx-authenticated .dx-notification .dx-notification-dialog.dx-content-frame:before { right:' + rightMarginForNotificationPanelConnectorWithBadge + 'px;}</style>'
$(".authenticated-page.dx-authenticated .dx-notification .dx-notification-dialog.dx-content-frame").append(rightMarginForNotificationPanelConnectorStyle);

Resources