So my aim is to have a base button component and then a variant button which has the same markup as the base button but obviously has different styling, animations.
My base file is button.js
import React from 'react';
import styled,{ keyframes, ThemeProvider} from 'styled-components';
import theme from '../../theme/default';
// import {rotatecw, rotateccw} from '../../../theme/keyframes';
const ButtonWrapper = styled.button`
position: relative;
color: ${(props) => props.theme.colors.primary};
width: 256px;
height: 64px;
line-height: 64px;
background: none;
border: 1px solid ${(props) => props.theme.colors.primary};
&:hover {
cursor: pointer;
color: ${(props) => props.theme.colors.grey};
border: 1px solid ${(props) => props.theme.colors.grey};
}
}
`;
const ButtonText = styled.span`
// transition: all 0.1s;
// tranform: scale(1, 1);
`;
function Button(props) {
return (
<ThemeProvider theme={theme}>
<ButtonWrapper>
<ButtonText>
{props.text}
</ButtonText>
</ButtonWrapper>
</ThemeProvider>
);
}
export default Button;
So far so good.
My AnimatedButton file is like that
import React from 'react';
import Styled, { keyframes, ThemeProvider} from 'styled-components';
import theme from '../../theme/default';
import Button from '../../components/button/button'
// import {rotatecw, rotateccw} from '../../../theme/keyframes';
const rotatecw = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`
const rotateccw = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(-360deg);
}
`
const AnimatedButtonWrapper = Styled(Button)`
transition: all 0.3s;
&:before,
&:after {
content: '';
position: absolute;
width: 100%;
height: 100%;
bottom: 0;
left: 0;
z-index: 1;
transition: all 0.3s;
border: 1px solid ${(props) => props.theme.colors.primary};
}
&:hover {
cursor: pointer;
&:after {
animation-name: ${rotatecw};
animation-duration: 2s;
}
&:before {
animation-name: ${rotateccw};
animation-duration: 3s;
}
&:before,
&:after {
left: 96px;
width: 64px;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
`;
function AnimatedButton(props) {
return (
<ThemeProvider theme={theme}>
<AnimatedButtonWrapper>
</AnimatedButtonWrapper>
</ThemeProvider>
);
}
export default AnimatedButton;
What confused me is the bottom part. seems like a repeat ... How do I ensure it generates the same markup as Button ? I want my animated button to extend the markup and the css.
Eventually, is there a way to call my button this way
<Button animatedButton text="test"></Button>
When you extend a Button with styled(Button), you are essentially creating a more specific version of it and is planning to use the new specific one instead.
But when you want to use the button as:
<Button animatedButton text="test"></Button>
which is a variant passing in animatedButton as a prop, you are looking to incorporate these changes in the ButtonComponent itself.
const ButtonWrapper = styled.button`
All the normal button stuff
${props.animated && `
All the animation stuff goes here
`}
`
function Button(props) {
return (
<ThemeProvider theme={theme}>
<ButtonWrapper animated={props.animatedButton}>
<ButtonText>
{props.text}
</ButtonText>
</ButtonWrapper>
</ThemeProvider>
);
}
If you have lots of variants as such, creating styles for these like this can be excruciating. This is why styled-components has a helper library styled-theming that can help out. (It isn't going be much help for the animated part since that's pretty much adding code rather than changing.
Related
So i am working on putting a button in my application website. The thing is that the button css element keeps on resizing when i switch screen and i want it to stay like my other elements as well.
Here is my Button code in Home.Js:
import React from 'react';
import { Link } from "react-router-dom";
import HeroSection from '../../HeroSection';
import { homeObjOne, homeObjTwo, homeObjThree } from './Data';
import './Home.css';
function Home() {
return (
<>
<HeroSection {...homeObjOne} />
<Link to="/Sign-up">
<button className="button" type="button">
Sign In or Register Today!
</button>
</Link>
<HeroSection {...homeObjThree} />
<HeroSection {...homeObjTwo} />
</>
);
}
export default Home;
Here is my Home.css file
.button {
padding: 12px 64px;
border-radius: 5px;
border: 1px solid var(--primary);
color: rgb(0, 0, 0);
font-size: 22px;
cursor: pointer;
transition: all 0.3s ease-out;
position: absolute;
left: 18.4%;
bottom: 450px;
}
.button:hover {
color: #fff;
background-color: #278087;
transition: all 0.2s ease-out;
}
I have tried putting position:absolute and position:relative but still causes the same issue and couldn't figure out why it keeps happening.
I have a HeaverNav component, a pretty simple fixed position header. When the user scrolls past a certain dimension a class is added that just adds a border-bottom to the existing class. Applying an animation for the border to enter is simple enough with a
transition: all 300ms ease; But having the border animate out when the class is no longer active is tricky. Currently the border just flips off suddenly with no smoothness, how can I achieve the effect of the border animating out smoothly?
MY CODE
import React, { useEffect, useState } from 'react';
import navStyles from '../styles/nav-header.module.scss';
import { MdOutlineAddBox } from 'react-icons/md';
import { RiHeartLine } from 'react-icons/ri';
import { FiSend } from 'react-icons/fi';
const NavHeader = () => {
const [isScrolling, setIsScrolling] = useState(false);
console.log(isScrolling)
useEffect(() => {
if (typeof window !== "undefined") {
window.addEventListener("scroll", () =>
setIsScrolling(window.pageYOffset > 100)
);
}
}, []);
return (
<nav className={isScrolling ? `${navStyles.nav} ${navStyles.navIsScrolling}` : `${navStyles.nav}`}>
<h1>Instagram</h1>
<ul>
<li><MdOutlineAddBox /></li>
<li><RiHeartLine /></li>
<li><FiSend /></li>
</ul>
</nav>
)
}
export default NavHeader;
SCSS
#import "../styles/global.scss";
#font-face {
font-family: "Instagram";
src: url("../assets/instagram-font.otf") format("woff2");
}
.nav {
display: flex;
align-items: center;
justify-content: space-between;
position: fixed;
width: 100%;
padding: 0.8rem;
background-color: $color-black;
h1 {
font-family: "Instagram", sans-serif;
color: $color-white;
font-size: 2rem;
}
ul {
display: flex;
margin-right: 1.5rem;
li {
color: $color-white;
font-size: $icon-size;
margin: 0 0.5rem;
transform: translateY(2px);
cursor: pointer;
}
}
}
.navIsScrolling {
border-bottom: $nav-border;
transition: all 300ms ease;
}
I am attempting to transition from css to styled components. I am creating a toggle switch component. The animation on the :active class works when the switch is checked but, when the checkbox is checked the switch does not move to the left like it would with just normal css.
import React from 'react';
import styled from 'styled-components'
const Button = styled.span`
content: '';
position: absolute;
top: 3.7px;
left: 5px;
width: 42px;
height: 42px;
border-radius: 45px;
transition: 0.2s;
background: #fff;
box-shadow: 0 0 2px 0 rgba(10, 10, 10, 0.29);
`;
const Label = styled.label`
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
width: 100px;
height: 50px;
background: dodgerblue;
border-radius: 100px;
position: relative;
transition: background-color 0.2s;
&:active ${Button} {
width: 50px;
}
`;
const Input = styled.input`
height: 0;
width: 0;
visibility: hidden;
&:checked ${Button} {
left: calc(100% - 5px);
transform: translateX(-100%);
}
`;
const Switch = ({ isOn, handleToggle, onColor }) => {
return (
<>
<Input
checked={isOn}
onChange={handleToggle}
className='react-switch-checkbox'
id={`react-switch-new`}
type='checkbox'
/>
<Label
style={{ background: isOn && onColor }}
className='react-switch-label'
htmlFor={`react-switch-new`}>
<Button className={`react-switch-button`} />
</Label>
</>
);
};
export default Switch;
Your problem is not related to styled components. The rule &:checked ${Button} { assumes the Button is a child of Input, but it's actually a child of Input's sibling Label.
Update the styled component rule to:
&:checked + ${Label} ${Button} {
left: calc(100% - 5px);
transform: translateX(-100%);
}
Sandbox
I have a ShowcaseMovie component which fetches data on componentDidMount() and sets it to state. The component renders Card components to display the data as well as four button elements upcoming, top_rated, popular and now_playing which allow the user to toggle between the relevant data. Each button has an onClick event which calls changeFilter and sets state currentFilter to the selected key.
The problem: When the filter buttons are clicked, sometimes the page will jump to the top (if it's not already there). I've tried to find solutions to this but I can't seem to understand what is happening. Any suggestions will be a great help, thank you in advance.
Update: This issue seems to happen when there is no height set to an element with dynamic children. If I set the height on ShowcaseMovie to something large like height: 200vh it goes away.
I believe I've solved my problem but would love to hear other thoughts as to why this happens and some other ways to fix it. It's difficult to set a height to a parent when you don't know how much content is going to be rendered (or the height of that content). min-height would help but still kind of a quick fix.
ShowcaseMovie.js
import React, { Component } from "react";
import Card from "./Card";
import "../css/ShowcaseMovie.css";
import { v4 as uuidv4 } from "uuid";
import { formatString, buildMovieState } from "../utilities";
class ShowcaseMovie extends Component {
static defaultProps = {
filterNames: ["upcoming", "popular", "now_playing", "top_rated"]
};
constructor(props) {
super(props);
this.state = {
upcoming: [],
now_playing: [],
popular: [],
top_rated: [],
currentFilter: this.props.filterNames[0]
};
}
changeFilter = e => {
e.preventDefault();
const type = e.target.name;
this.setState({ currentFilter: type });
};
componentDidMount() {
this.props.filterNames.map(name => this.fetchMovies(name));
// setInterval(() => {
// this.timeoutFilter();
// }, 10000);
}
async fetchMovies(type) {
try {
const res = await fetch(
`url`
);
const data = await res.json();
if (data) {
this.setState(state => ({
...state,
[type]: buildMovieState(data)
}));
}
} catch (error) {
console.log(error);
}
}
render() {
const { currentFilter } = this.state;
const movies = this.state[currentFilter].map((movie, i) => (
<Card key={uuidv4()} movie={movie} index={i} />
));
const buttons = this.props.filterNames.map(name => (
<button
type="button"
key={name}
name={name}
className={`ShowcaseMovie-btn ${
currentFilter === name ? "active" : ""
}`}
disabled={currentFilter === name}
onClick={this.changeFilter}>
{formatString(name)}
</button>
));
return (
<section className="ShowcaseMovie">
<div className="ShowcaseMovie-container">
<h2 className="ShowcaseMovie-header">Movies</h2>
<div className="ShowcaseMovie-btn-container">{buttons}</div>
</div>
<div className="ShowcaseMovie-grid">{movies}</div>
</section>
);
}
}
ShowcaseMovie.css
.ShowcaseMovie {
padding: 4rem 10%;
}
.ShowcaseMovie-container {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.ShowcaseMovie-button-container {
display: flex;
justify-content: center;
align-items: center;
}
.ShowcaseMovie-container::after {
content: "";
background-color: #64b5f6;
height: 80%;
width: 6px;
left: 0;
position: absolute;
border-radius: 1px;
}
.ShowcaseMovie-header {
font-size: 3rem;
font-weight: 200;
margin: 0 5rem;
}
.ShowcaseMovie-btn {
outline: none;
border: none;
background-color: transparent;
font-size: 1.6rem;
font-weight: 500;
letter-spacing: 1px;
padding: 1rem;
margin-left: 4rem;
color: white;
opacity: 0.5;
cursor: pointer;
transition-property: opacity;
transition-duration: 300ms;
transition-timing-function: ease;
}
.ShowcaseMovie-btn:hover {
opacity: 1;
transition-property: opacity;
transition-duration: 300ms;
transition-timing-function: ease;
}
.ShowcaseMovie-btn.active {
opacity: 1;
cursor: auto;
color: #64b5f6;
}
.ShowcaseMovie-grid {
display: grid;
gap: 3rem;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
Card.js
import React, { Component } from "react";
import "../css/Card.css";
class Card extends Component {
render() {
const { title, poster_path } = this.props.movie;
const style = { animationDelay: `${80 * this.props.index}ms` };
return (
<div className="Card">
<div className="Card-inner" style={style}>
<img
src={`https://image.tmdb.org/t/p/w500/${poster_path}`}
alt=""
className="Card-img"
/>
<p className="Card-name">{title}</p>
</div>
</div>
);
}
}
export default Card;
Card.css
.Card {
display: block;
transition: transform 300ms ease;
}
.Card:hover {
transform: translateY(-5px);
transition: transform 300ms ease;
}
.Card-inner {
position: relative;
display: block;
cursor: pointer;
height: 100%;
opacity: 0;
animation-name: moveUp;
animation-duration: 500ms;
animation-delay: 50ms;
animation-timing-function: ease;
animation-fill-mode: forwards;
}
.Card-inner::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: linear-gradient(transparent, rgba(33, 47, 61, 0.8));
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
z-index: 100;
opacity: 1;
transition: opacity 300ms ease;
}
.Card-inner:hover::after {
opacity: 0;
transition: opacity 300ms ease;
}
.Card-inner::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: linear-gradient(transparent, rgba(100, 180, 246, 0.6));
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
z-index: 100;
opacity: 0;
transition: opacity 300ms ease;
}
.Card-inner:hover::before {
opacity: 1;
transition: opacity 300ms ease;
}
.Card-img {
display: block;
position: relative;
object-fit: cover;
max-width: 100%;
min-height: 100%;
z-index: 0;
border-radius: 2px;
}
.Card-name {
position: absolute;
bottom: 0;
left: 0;
margin: 0 2rem 2rem 2rem;
z-index: 150;
font-weight: 400;
text-transform: uppercase;
font-size: 1.4rem;
letter-spacing: 2px;
}
#keyframes moveUp {
0% {
transform: translateY(5rem);
}
100% {
transform: translateY(0);
opacity: 1;
}
}
utilities.js
export const formatString = name => {
return name
.replace("_", " ")
.split(" ")
.map(w => w[0].toUpperCase() + w.slice(1))
.join(" ");
};
export const buildMovieState = data => {
if (data.results) {
const movies = data.results.filter(
d => d.backdrop_path && d.id && d.title && d.poster_path
);
return movies.length > 10 ? movies.slice(0, 10) : movies;
} else {
return [];
}
};
I was able to stop this from happening by returning false from the onClick call. Like this:
onClick={
doSomething()
return false
}
The page jumping could be from components unnecessarily re-rendering. Try wrapping all your components with React.memo( component name)
Is there a way that I could slide out my modal which slides in?I've written a code that makes the modal slide in from left to right and I cant figure out how to slide out the modal. Can anyone help me with this? When the user clicks on the x button, the modal should slide out from right to left.
Thanks in advance!!
.headerContactModal {
background-color: white;
color: #1a66b1;
margin-left: 15px;
height:70px;
width:100%;
padding-top: 20px;
font-weight: 700;
}
.headerContactModalMessage {
padding-left: 20px;
padding-right: 5px;
}
.headerContactModalExit{
z-index: 999;
float: right;
display: inline;
color: $dark_gray;
position: absolute;
top: 5px;
}
.headerContactModalPhone {
font-size: 18px;
padding-right: 10px;
}
.slidein {
animation-duration: 700ms;
animation-name: slidein;
animation-iteration-count: 1;
animation-direction: alternate;
}
#keyframes slidein {
from {
margin-left:100%;
}
to {
margin-left:0%;
}
}
import React, { PropTypes, Component } from 'react';
import classNames from 'classnames';
import Button from '../../../common_ui/leafs/Button';
import GenericTextField from '../../components/GenericTextField';
import Icon from '../../../common_ui/leafs/Icon';
class HeaderContactModal extends Component {
static propTypes = {
phoneNumber: PropTypes.string,
messageText: PropTypes.string
}
static defaultProps = {
phoneNumber: '1-800-CALLUS',
messageText: 'Call us:'
}
render() {
return (
<div className="headerContactModal slidein">
<span className="headerContactModalMessage">{this.props.messageText}</span>
<span className="headerContactModalPhone">{this.props.phoneNumber}</span>
<span onClick={this.props.contractContactUs}><Icon className="fa-close headerContactModalExit"/></span>
</div>
);
}
}
export default HeaderContactModal;
What you're looking for is ReactCSSTransitionGroup. Simple addon to react to handle transition animations when moving between states (such as open/closed on your modal).