how to apply a multiconditional to style a react component? - reactjs

I have a form where the borders of the inputs have 2 colors
-Grey: when the component is loaded
-blue: when the input is not empty
and I would like to apply a third color when the user presses the register button and the inputs are not empty that changes the border color to red. but I do not know how
Page
const hanleClear= () => {
setCompania(0);
setDepartamento(0);
setUnidad(0);
setLocalidad(0);
setActivoTipo(0);
setActivoTipoCatego(0);
setMarca("");
setColor("");
setModelo("");
setComponente("");
setSerial("");
setObservacion("");
};
const hanleSerial= (e) => {
setSerial(e.target.value.toUpperCase());
};
const handleSumit = function (e) {
e.preventDefault();
if (serial === 0) {
// Apply color red.
}
let form = {
idinformacion: compania,
iddepartamento: departamento,
idunidad: unidad,
};
let Data = JSON.stringify(form );
ServiceSerial.Create(Data);
};
<InputGroup
input_label={"Serial"}
input_type={"text"}
input_value={serial}
input_placeholder={"Serial"}
state_name={serial}
set_state_name={setSerial}
on_change={hanleSerial}
/>
<button onClick={hanleSumit}>Register</button>
<br />
<button onClick={hanleClear}>Limpiar</button>
<br />
Componenet
import {
Input,
Label,
InputGroupContainer,
WrapperInput,
} from "../components/FormStyled";
const InputGroup = function ({
input_label,
input_type,
input_value,
input_name,
input_placeholder,
on_change,
on_key_down,
}) {
return (
<InputGroupContainer>
<Label>{input_label}</Label>
<WrapperInput>
<Input
type={input_type}
placeholder={input_placeholder}
value={input_value}
name={input_name}
onChange={on_change}
onKeyDown={on_key_down}
className={!input_value ? "" : "Activated"}
/>
</WrapperInput>
{/* <span>{errorMessage}</span> */}
</InputGroupContainer>
);
};
export default InputGroup;
css
// LABEL
export const Label = styled.label`
font-size: 16px;
cursor: pointer;
letter-spacing: 0.8px;
color: black;
margin-top: 15px;
margin-bottom: 5px;
font-family: "Inter";
font-weight: 900;
`;
// INPUT
export const Input = styled.input`
font-size: 16px;
font-weight: normal;
font-family: "Inter";
letter-spacing: 0.3px;
width: 100%;
height: 40px;
line-height: 40px;
padding: 0px 5px 0px 10px;
border: 2px solid #ccc;
border-radius: 0px;
transition: 0.3s ease all;
border-radius: 3px;
color: black;
&:focus {
outline: none;
border: 2px solid #00aea9;
}
&.Activated {
border: 2px solid #00aea9;
}
`;
// Input Group
export const InputGroupContainer = styled.div`
margin: 15px 0px;
position: relative;
width: 100%;
`;

I can give you a general solution. If you get it, you can adapt it to your specific code.
I use a state called inputState to denote if the input is empty/filled/filled&pressed.
Then your component just composes style based on that state whenever rendering.
function ComponentABC() {
const [inputState, setInputState] = useState(1);
let inputStyle = {borderCorlor: 'grey'};
if(inputState == 1) { /* empty */
inputStyle = {borderCorlor: 'grey'};
}
else if(inputState == 2) { /* filled */
inputStyle = {borderCorlor: 'blue'};
}
else if(inputState == 3) { /* filled & pressed */
inputStyle = {borderCorlor: 'red'};
}
// when pressed button, change color
function handleRegister(e) {
if(inputValue) {
setInputState(3);
}
}
function handleInputChange(e) {
// when input is filled, change color
if(inputValue) {
setInputState(2);
}
// when input is empty, change color
else if(!inputValue) {
setInputState(1);
}
}
return (
<div>
<input style={inputStyle} onChange={handleInputChange}/>
<button onClick={handleRegister}>register</button>
</div>
);
}

Related

Implementing animation when removing Toast

I have a working ToastList that enables me to click a button multiple times and generate a toast each time. On entry, I have an animation, but when I remove the toast, I do not get an animation. I am using Typescript and functional components.
My component is as follows:
import React, { useCallback, useEffect, useState } from 'react';
import * as Styled from './Toast.styled';
export interface ToastItem {
id: number;
title: string;
description: string;
backgroundColor: string;
}
export interface ToastProps {
toastList: ToastItem[];
setList: React.Dispatch<React.SetStateAction<ToastItem[]>>;
}
export default function Toast(props: ToastProps) {
const deleteToast = useCallback(
(id: number) => {
const toastListItem = props.toastList.filter((e) => e.id !== id);
props.setList(toastListItem);
},
[props.toastList, props.setList]
);
useEffect(() => {
const interval = setInterval(() => {
if (props.toastList.length) {
deleteToast(props.toastList[0].id);
}
}, 2000);
return () => {
clearInterval(interval);
};
}, [props.toastList, deleteToast]);
return (
<Styled.BottomRight>
{props.toastList.map((toast, i) => (
<Styled.Notification
key={i}
style={{ backgroundColor: toast.backgroundColor }}
>
<button onClick={() => deleteToast(toast.id)}>X</button>
<div>
<Styled.Title>{toast.title}</Styled.Title>
<Styled.Description>{toast.description}</Styled.Description>
</div>
</Styled.Notification>
))}
</Styled.BottomRight>
);
}
And my styling is done using styled-components and is as follows:
import styled, { keyframes } from 'styled-components';
export const Container = styled.div`
font-size: 14px;
position: fixed;
z-index: 10;
& button {
float: right;
background: none;
border: none;
color: #fff;
opacity: 0.8;
cursor: pointer;
}
`;
const toastEnter = keyframes`
from {
transform: translateX(100%);
}
to {
transform: translateX(0%);
}
}
`;
export const BottomRight = styled(Container)`
bottom: 2rem;
right: 1rem;
`;
export const Notification = styled.div`
width: 365px;
color: #fff;
padding: 15px 15px 10px 10px;
margin-bottom: 1rem;
border-radius: 4px;
box-shadow: 0 0 10px #999;
opacity: 0.9;
transition .1s ease;
animation: ${toastEnter} 0.5s;
&:hover {
box-shadow: 0 0 12px #fff;
opacity: 1;
}
`;
export const Title = styled.p`
font-weight: 700;
font-size: 16px;
text-align: left;
margin-bottom: 6px;
`;
export const Description = styled.p`
text-align: left;
`;
When I click a button, I just add an element to the state list, like:
toastProps = {
id: list.length + 1,
title: 'Success',
description: 'Sentence copied to clipboard!',
backgroundColor: '#5cb85c',
};
setList([...list, toastProps]);
My component is rendered like:
<Toast toastList={list} setList={setList}></Toast>
I would like to add animation when a toast exits, but do not know how. I have tried changing the style according to an additional prop I would send to the styled components, but this way all the toasts animate at the same time. My intuition is that I should use useRef(), but I am not sure how. Thanks in advance for any help you can provide.

How to interpolate a CSS property in styled-components?

I am trying to create a Styled Component which can receive a prop for the type of border. The Section can either have border-top or border-bottom based on the prop passed to it.
Here is the code
type TSectionBorder = 'top' | 'bottom';
<Section edge="top">
{children}
</Section>
How can I achieve this in styled components, something along these lines -
const Section = styled.section<{edge: TSectionBorder}>`
border-${edge}: 1px solid black;
`;
const Section = styled.section<{edge: TSectionBorder}>`
${({ edge }) => `border-${edge}: 1px solid black`};
`;
I deconstructed the props just to keep your syntax, however, this is one way to do so. They also have a css helper to look into.
Documentation:
https://styled-components.com/docs/basics#passed-props
https://styled-components.com/docs/api#css
First you will import:
import styled from 'styled-components';
Here is the function ButtonBlock:
function ButtonBlock(props) {
return (
<BtnBlock type="primary" {...props}>
{props.children}
</BtnBlock>
);
}
const handleColorType = (color) => {
switch (color) {
case 'grey':
return '#D4D5D6';
default:
return '#6633CC';
}
};
const hoverColorType = (color) => {
switch (color) {
case 'grey':
return '#6a7581';
default:
return '#99CC00';
}
};
Styled Component:
const BtnBlock = styled(Button)`
&& {
max-width: none;
width: 100%;
text-align: center;
display: block;
font-size: 14px;
font-weight: 600;
line-height: 50px;
height: 50px;
text-transform: uppercase;
border-radius: 0;
border: 0px;
padding: 0;
background: ${({ color }) => handleColorType(color)};
&[disabled] {
background-color: #c6c6c6;
}
:hover {
background-color: ${({ color }) => hoverColorType(color)};
}
&[disabled]:hover {
background-color: #c6c6c6;
}
}
`;

React input checkbox checked

I have use React-redux and styled components for my app. I store my initial state theme as a string which is light and dark. then I connect my styled components intial light theme and dark theme in my root app. My dark mood works fine when i used select options but when i used input checkbox it does not work. I never used input checkbox, after reading couple example I used checked and put my initial theme(which is coming from my redux store), then in my handleChange I did, if the event target has dark then dispatch the dark theme. But nothing happening in that handle change. don't know what i am doing wrong.
Here is my toggle component
import React, { useState } from 'react';
import styled from 'styled-components';
import { useDispatch, useSelector } from 'react-redux';
import { appSettings } from '../../state/appSettings';
import { TRootState } from '../../state/index';
export default function Toggle({ }: IProp) {
const dispatch = useDispatch();
const { "appSettings": appSettingState } = useSelector((state: TRootState) => state);
const { theme } = appSettingState || {};
console.log(theme); // inital state which is "light".
return (
<>
{/* This input checkbox does not work */}
<CheckBoxWrapper>
<CheckBox
onChange={(e) => { // This function does not work
e.target.value === `dark` ?
dispatch(appSettings?.actions?.enableDarkTheme()) :
dispatch(appSettings?.actions?.enableLightTheme());
}}
id="toggleSwitch"
type="checkbox"
Checked={theme === `light`}
/>
<CheckBoxLabel htmlFor="toggleSwitch" />
</CheckBoxWrapper>
<br></br>
{/* THIS SELECT OPTIONS WORK FINE. AND I CAN GET DARK AND LIGHT THEME */}
<h2>Theme</h2>
<select
name="theme"
id="theme-select"
value={theme}
onChange={(e) => {
if (e.target.value === `dark`) {
dispatch(appSettings?.actions?.enableDarkTheme());
} else {
dispatch(appSettings?.actions?.enableLightTheme());
}
}}
>
<option value="dark">Dark</option>
<option value="light">Light</option>
</select>
</>
);
}
// This toogle input styled
const CheckBoxWrapper = styled.div`
position: fixed;
top:10px;
right:10px;
`;
const CheckBoxLabel = styled.label`
position: absolute;
top: 0;
left: 0;
width: 42px;
height: 26px;
border-radius: 15px;
background: #bebebe;
cursor: pointer;
&::after {
content: "";
display: block;
border-radius: 50%;
width: 18px;
height: 18px;
margin: 3px;
background: #ffffff;
box-shadow: 1px 3px 3px 1px rgba(0, 0, 0, 0.2);
transition: 0.2s;
}
`;
const CheckBox = styled.input`
opacity: 0;
z-index: 1;
border-radius: 15px;
width: 42px;
height: 26px;
&:checked + ${CheckBoxLabel} {
background: #4fbe79;
&::after {
content: "";
display: block;
border-radius: 50%;
width: 18px;
height: 18px;
margin-left: 21px;
transition: 0.2s;
}
}
`;
Check the change event's checked property. e.target.checked
<CheckBox
onChange={(e: any) => {
e.target.checked
? dispatch(appSettings?.actions?.enableDarkTheme())
: dispatch(appSettings?.actions?.enableLightTheme());
}}
id="toggleSwitch"
type="checkbox"
Checked={theme === `light`}
/>

The clientWidth property of ref button return undefined

Using the targetButton refs to get the Button dom, and I want to get clientWidth property when click this Button.
import React from "react";
import styled from "styled-components";
const Container = styled.div`
width: 600px;
height: 600px;
background-color: black;
`;
const Button = styled.button`
font-size: 24px;
padding: 1em 2em;
margin: 3px;
border: 0;
outline: 0;
color: white;
background-color: #2196f3;
border-radius: 0.15em;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
-webkit-appearance: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-tap-highlight-color: transparent;
`;
const Ripple = styled.div.attrs({
size: props => props.size
})`
width: ${props => props.size};
height: ${props => props.size};
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.7);
position: absolute;
`;
class RippleButton extends React.Component {
state = {
rippleNum: 0
};
addRipple() {
const { clientWidth, clientHeight } = this.targetButton;
console.log(clientWidth);
this.setState({
rippleNum: this.state.rippleNum + 1
});
}
render() {
const children = [];
for (let i = 0; i < this.state.rippleNum; i += 1)
children.push(
<Ripple
size={Math.max(
this.targetButton.clientWidth,
this.targetButton.clientHeight
)}
key={i}
/>
);
return (
<Container>
<Button
ref={elem => {
this.targetButton = elem;
}}
onClick={this.addRipple.bind(this)}
>
Button{children}
</Button>
</Container>
);
}
}
export default RippleButton;
The console reports undefined error message, after having search many ways to tweak it.
The problem in this code is when using styled-components, you should use innerRef instead of ref.

react-modal is rendered at bottom of the screen

I'm using react-modal in my app, but I can't easily get it to render on top of the current screen content. The modal always renders at the bottom of the screen (below the html body even).
This is my custom modal:
import Modal from 'react-modal';
var SimpleModal = React.createClass({
render() {
return (
<Modal
isOpen={this.props.isOpen}
className="modal-content"
contentLabel="modal"
onRequestClose={this.props.onClose} >
<h1 className="modal-header">{this.props.title}</h1>
<div className="modal-body">
<p>{this.props.message}</p>
</div>
<Button bsStyle={this.props.type} className="modal-button" onClick={this.props.closeModal}>Close</Button>
</Modal>
)
}
});
const mapStateToProps = (state) => {
return {
isOpen: state.modals.notification.isOpen,
type: state.modals.notification.type,
title: state.modals.notification.title,
message: state.modals.notification.message,
}
};
const mapDispatchToProps = (dispatch) => {
return {
closeModal: () => dispatch(skjeraActionCreators.closeNotificationModal()),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(SimpleModal)
The SimpleModal component is included in the render function of my top level container (AppContainer), like this:
render() {
return (
<div>
<SimpleModal />
<App
onLogout={this.logout}
isLoggedIn={this.props.isLoggedIn}
username={this.props.username}
subpages={this.props.children}
/>
</div>
)
}
Note that I haven't tweaked the style/css, so it uses the default styling, and thus the default positioning scheme.
Can anyone help me out tracking down this bug? Any help will be appreciated.
EDIT: This is the CSS entries (probably some redudant elements there) I've referred to in my code:
.modal-header {
background-color: inherit;
border: none;
}
.modal-body {
padding-top: 10px;
padding-bottom: 10px;
}
.modal-button {
padding-left: 10%;
margin-left: 20px;
}
.modal-content {
position: absolute;
background-color: white;
color: black;
top: auto;
bottom: auto;
overflow: auto;
right: auto;
left: 10px;
border-radius: 20px;
outline: none;
border: solid;
border-width: medium;
border-color: black;
padding-bottom: 10px;
}

Resources