I am trying to create a selectable button, so that it is easy, medium and difficult to choose when creating a task in the form
In this case, when he clicks on the easy, changing to the color is choosing, as for example:
easy: green
medium: yellow
high: red
my tsx component looks like this
interface IButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
name: string;
}
const Priority: React.FC<IButtonProps> = ({ name, ...props }) => {
const inputRef = useRef<HTMLButtonElement>(null);
const [priority, setPriorite] = useState("");
const { fieldName, defaultValue, error, registerField } = useField(name);
useEffect(() => {
registerField({
name: fieldName,
ref: inputRef.current,
path: "value",
});
}, [fieldName, registerField]);
return (
<Container
ref={inputRef}
isActive={priority === "Low"}
activeColor="red"
defaultValue={defaultValue}
{...props}
/>
);
};
And my styled-component looks like this
interface RadioBoxProps {
isActive: boolean;
activeColor: string;
}
const colors: { [key: string]: any } = {
red: "red",
green: "green",
blue: "blue",
};
export const Container = styled.button<RadioBoxProps>`
display: flex;
align-items: center;
justify-content: center;
height: 2.5rem;
border: 2px solid;
font: 1rem Poppins;
background: transparent;
border-radius: 0.25rem;
transition: border-color 0.2s;
background: ${(props) =>
props.isActive ? colors[props.activeColor] : "transparent"};
`;
I would like to know the bug that when I select it, it doesn't have the color assigned to it to be sent to the backend
Related
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.
I have a custom radio button component and I would like to group them somehow. Selecting one de-selects another I have to detect the selection of the one and deselect the other one, so the question is where I do that?
It has to be done somewhere but the mechanic is always the same. Any recommendations?
Here's how my component looks like:
const StyledRadio = styled.div<RadioProps>`
display: flex;
background: ${(p) =>
!p.checked
? "transparent"
: "radial-gradient(#34515E 0%, #34515E 50%, transparent 50%, transparent)"};
border-radius: 50%;
border: ${(p) => `2px solid ${theme.color.secondary}`};
height: ${(p) => `${radioSizes[p.size]}px`};
margin-right: 5px;
opacity: ${(p) => (p.disabled ? 0.5 : 1)};
width: ${(p) => `${radioSizes[p.size]}px`};
&:hover {
cursor: pointer;
background: radial-gradient(
circle,
#34515e 0%,
#34515e 50%,
transparent 50%,
transparent
);
transform: scale (1.02);
display: block;
}
&:active {
transform: scale(1.1);
}
`;
const RadioLabel = styled.label<{ size: string }>`
color: ${theme.color.secondary};
cursor: pointer;
display: flex;
flex-direction: row-reverse;
font-size: ${theme.font.size.small};
`;
type RadioProps = {
checked?: boolean;
children?: any;
disabled?: boolean;
label?: string;
onClick?: (value?: any) => void;
size?: RadioSize;
value?: any;
};
export const Radio: FC<RadioProps> = ({
disabled = false,
onClick,
children,
checked,
label = "",
value,
size = "small",
...rest
}) => {
return (
<RadioGroup
disabled = {!!disabled}
onClick = {() => (!disabled && onClick ? onClick(value) : null)}
>
<StyledRadio
{...rest}
disabled = {disabled}
size = {size}
checked = {checked}
/>
<RadioLabel size={size}>{label}</RadioLabel>
</RadioGroup>
);
};
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...
I've been trying to create one component to render my Menu at one react page using TS + React
And that component is this
interface Routes {
label: string,
link: string,
active: boolean,
}
const Menu: React.FC<Routes[]> = ({routes}) => {
const [links, setLinks] = useState<Array<Routes>>([
routes
]);
const handleClick = (key: number) => {
const activeRoute = links.map((value, index) => {
if(value.active) {
value.active = false;
}
if(index === key) {
value.active = true
}
return value
})
setLinks(activeRoute)
}
const createMenu = () => links.map((value, index) => {
let markUp = value.active ?
<LinkActive href={value.link}>{value.label}</LinkActive> : <Link href={value.link}>{value.label}</Link>
return (
<Item key={index} onClick={() => handleClick(index)}>
{markUp}
</Item>
)
})
return (
<MenuBar>
<MenuRight>
<List>
{createMenu()}
</List>
</MenuRight>
</MenuBar>
)
}
const Link = styled.a`
color: #3a3e47;
display: inline-block;
height: 100%;
font-size: 16px;
text-decoration: none;
text-transform: uppercase;
display: flex;
align-items: center;
padding: 0 3px;
:hover {
color: #ffaa3b
};
`
const LinkActive = styled(Link)`
color: #ffaa3b;
border-top: 4px solid #ffaa3b;
font-weight: 700;
`
const MenuBar = styled.div`
background-color: white;
box-shadow: 0px 2px 24px 0px rgba(0, 0, 0, 0.15);
border-radius: 8px;
width: 752px;
height: 80px;
display: flex;
justify-content: space-between;
padding: 0 40px;
position: relative;
`
const MenuRight = styled.div`
display: flex;
align-items: center;
height: 100%;
`
const List = styled.ul`
list-style-type: none;
padding: 0;
height: 100%;
margin: 0;
margin-right: 40px;
`
const Item = styled.li`
display: inline-block;
height: 100%;
margin-right: 20px;
:last-child {
margin-right: 0;
}
`
export default Menu;
And the component who calls him is that:
const BasicPage: React.FC = ({children}) => {
let routes = [
{ label: 'Início', link: '/', active: true },
{ label: 'Quem Somos', link: '/quemSomos', active: false},
{ label: 'Contato', link: '/contato', active: false},
]
return (
<Container>
<Menu routes={routes} />
{children}
</Container>
)
}
const Container = styled.div`
width: 100%;
`
export default BasicPage
After putting the code on the page, And send the props to another page de manage the state of menu, I have this error.
Type '{ routes: { label: string; link: string; active: boolean; }[]; }' is not assignable to type 'IntrinsicAttributes & Routes[] & { children?: ReactNode; }'.
Property 'routes' does not exist on type 'IntrinsicAttributes & Routes[] & { children?: ReactNode; }'. TS2322
12 | return (
13 | <Container>
> 14 | <Menu routes={routes} />
| ^
15 | {children}
16 | </Container>
17 | )
I try to force the type of the routes variable to the Interface in the menu code but isn't works.
Try to define your routes array like the following, in the Menu Component.
let routes : Routes[] = [
{ label: 'Início', link: '/', active: true },
{ label: 'Quem Somos', link: '/quemSomos', active: false},
{ label: 'Contato', link: '/contato', active: false},
]
For this, you'll have to define the interface outside the BasicPage file and export it, in order to import the interface here.
I am trying to implement a radio button set in React using Styled components using this pen as an example however the sibling selector is tripping me up. How to turn this
.radio-input:checked ~ .radio-fill {
width: calc(100% - 4px);
height: calc(100% - 4px);
transition: width 0.2s ease-out, height 0.2s ease-out;
}
.radio-input:checked ~ .radio-fill::before {
opacity: 1;
transition: opacity 1s ease;
}
into css in a styled component?
Can anyone point out my mistake or make a quick pen demo? Thanks! Here is my full code:
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { colors } from '../../../theme/vars';
export class RadioGroup extends React.Component {
getChildContext() {
const { name, selectedValue, onChange } = this.props;
return {
radioGroup: {
name, selectedValue, onChange,
},
};
}
render() {
const { Component, name, selectedValue, onChange, children, ...rest } = this.props;
return <Component role="radiogroup" {...rest}>{children}</Component>;
}
}
RadioGroup.childContextTypes = {
radioGroup: PropTypes.object,
};
RadioGroup.propTypes = {
name: PropTypes.string,
selectedValue: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.bool,
]),
children: PropTypes.node.isRequired,
Component: PropTypes.oneOfType([
PropTypes.string,
PropTypes.func,
PropTypes.object,
]),
};
RadioGroup.defaultProps = {
name: '',
selectedValue: '',
Component: 'div',
};
// eslint-disable-next-line react/no-multi-comp
export class Radio extends React.Component {
render() {
const { name, selectedValue } = this.context.radioGroup;
const { onChange, value, labelText } = this.props;
let checked = false;
if (selectedValue !== undefined) {
checked = (value === selectedValue);
}
console.log('value: ', value);
console.log('checked: ', checked);
console.log('selectedValue: ', selectedValue);
return (
<Root>
<Input
type="radio"
name={name}
value={value}
checked={checked}
aria-checked={checked}
onChange={onChange}
/>
<Fill />
{/* <div style={{ marginLeft: '25px' }}>{labelText}</div> */}
</Root>
);
}
}
Radio.contextTypes = {
radioGroup: PropTypes.object,
};
Radio.propTypes = {
onChange: PropTypes.func,
value: PropTypes.string,
labelText: PropTypes.string,
};
Radio.defaultProps = {
onChange: () => {},
value: '',
labelText: '',
};
const Root = styled.div`
width: ${props => props.size ? props.size : 20}px;
height: ${props => props.size ? props.size : 20}px;
position: relative;
&::before {
content: '';
border-radius: 100%;
border: 1px solid ${props => props.borderColor ? props.borderColor : '#DDD'};
background: ${props => props.backgroundColor ? props.backgroundColor : '#FAFAFA'};
width: 100%;
height: 100%;
position: absolute;
top: 0;
box-sizing: border-box;
pointer-events: none;
z-index: 0;
}
`;
const Fill = styled.div`
background: ${props => props.fillColor ? props.fillColor : '#A475E4'};
width: 0;
height: 0;
border-radius: 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transition: width 0.2s ease-in, height 0.2s ease-in;
pointer-events: none;
z-index: 1;
&::before {
content: '';
opacity: 0;
width: calc(20px - 4px);
position: absolute;
height: calc(20px - 4px);
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 1px solid ${props => props.borderActive ? props.borderActive : '#A475E4'};
border-radius: 100%;
}
`;
const Input = styled.input`
opacity: 0;
z-index: 2;
position: absolute;
width: 100%;
height: 100%;
margin: 0;
cursor: pointer;
&:focus {
outline: none;
}
&:checked {
${Fill} {
width: calc(100% - 8px);
height: calc(100% - 8px);
transition: width 0.2s ease-out, height 0.2s ease-out;
&::before {
opacity: 1;
transition: opacity 1s ease;
}
}
}
`;
and the usage of the component:
<RadioGroup name="setYAxis" onChange={e => this.toggleSetYAxis(e)} selectedValue={this.state.setYAxis}>
<Radio value="autoscale" labelText="Autoscale" />
<Radio value="manual" labelText="Manual" />
</RadioGroup>
I created a codesandbox to fix the css issue and the state management problem.
The provided code is simpler and does not rely on an outdated React context API (doc here)
You are missing the ~ before the Fill selector:
&:checked {
& ~ ${Fill} {
width: calc(100% - 8px);
height: calc(100% - 8px);
transition: width 0.2s ease-out, height 0.2s ease-out;
&::before {
opacity: 1;
transition: opacity 1s ease;
}
}
You also seem to have an issue with the way you're actually updating the state in this example but that's unrelated to styling: on RadioGroup, you aren't passing the onChange prop down to Radio. The way the spread operator makes it so your const rest only includes the properties that you haven't already defined inside the const. So you need to remove it the onChange declaration from there to make it go inside rest.
export class RadioGroup extends React.Component {
getChildContext() {
const { name, selectedValue, onChange } = this.props;
return {
radioGroup: {
name,
selectedValue,
onChange
}
};
}
render() {
const {
Component,
name,
selectedValue,
// Remove onChange from here
// onChange,
children,
...rest
} = this.props;
return (
<Component role="radiogroup" {...rest}>
{children}
</Component>
);
}
}
Working example: https://codesandbox.io/s/serene-cdn-1fw1i