I've got 2 components that are conditionally shown or hidden based on a string value stored in useState as showModal
{showModal === 'SIGNIN' && <SignIn />}
{showModal === 'JOIN' && <Join />}
I want to fade in one component, then when the state changes, fade it out and fade in the other component.
Can this be done with react transition group?
I tried
<TransitionGroup>
<CSSTransition in={showModal === 'SIGNIN' ? true : false} classNames='fade' timeout={220} key={showModal}>
<div>
<SignIn />
</div>
</CSSTransition>
<CSSTransition in={showModal === 'JOIN' ? true : false} classNames='fade' timeout={220} key={showModal}>
<div>
<Join />
</div>
</CSSTransition>
</TransitionGroup>
I don't get any error, one component is shown, changing showModal from 'SIGNIN' to 'JOIN' does nothing. Inspecting the divs with the timeout set to 22000 shows that no new classes have been added.
SwitchTransition from react transition group might help.
Example
const { useState, useEffect } = React;
const { SwitchTransition, CSSTransition } = ReactTransitionGroup;
const SignIn = () => <div className="block sign-in">Sign In</div>;
const Join = () => <div className="block join">Join</div>;
const App = () => {
const [showModal, setModal] = useState("SIGNIN");
useEffect(() => {
let handle;
const loop = () => {
setModal(state => state === "JOIN" ? "SIGNIN" : "JOIN");
handle = setTimeout(loop, 2500);
};
handle = setTimeout(loop, 1000);
return () => {
clearTimeout(handle);
}
}, []);
const addEndListener = (node, done) => {
node.addEventListener("transitionend", done, false);
}
return <div>
<SwitchTransition mode="out-in">
<CSSTransition
key={showModal === "SIGNIN"}
addEndListener={addEndListener}
classNames="fade">
{showModal === "SIGNIN" ? <SignIn/> : <Join/>}
</CSSTransition>
</SwitchTransition>
</div>;
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
body {
margin: 0;
overflow: hidden;
font-family: Georgia, serif;
}
.block {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 10px;
color: white;
width: 200px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
font-size: 30px;
}
.sign-in {
background: #0984e3;
}
.join {
background: #6c5ce7;
}
.fade-enter {
opacity: 0;
transform: translate(-100%, -50%);
}
.fade-exit {
opacity: 1;
transform: translate(-50%, -50%);
}
.fade-enter-active {
opacity: 1;
transform: translate(-50%, -50%);
}
.fade-exit-active {
opacity: 0;
transform: translate(100%, -50%);
}
.fade-enter-active,
.fade-exit-active {
transition: opacity 500ms, transform 500ms;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/react-transition-group#4.4.2
/dist/react-transition-group.min.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>
Related
I'm new to next and I have been trying to make the game flappy bird. I used useEffect and styled components to animate the player(or bird) and pipes too. Basically when I run my app on chrome, it works fine but it flickers in safari. And after I play it a few times in safari it starts to work almost fine. For state management, I'm using redux.
Can someone help me to solve the problem and explain what is actually going on?
From what I think it is because of the re-rendering of the images but why is it working properly in chrome? And is there a better way to animate this?
This is my main code and I used the useEffect inside Bird and Pipes file to move them across the GameBox
import styled from 'styled-components'
import Pipes from './Pipes'
import { startGame, setBirdPosition, resetGame } from './features/app-slice'
import { store, constants } from './store'
import { useSelector } from 'react-redux'
import Bird from './Bird'
import { useEffect, useState } from 'react'
export default function GameBox() {
const [jumpAudio, setAudio] = useState(null)
useEffect(() => {
setAudio(new Audio('/sound-effects/jump.wav'))
// only run once on the first render on the client
}, [])
const birdPosition = useSelector((state) => state.birdPosition)
const score = useSelector((state) => state.score)
const gameStarted = useSelector(state => state.gameStarted)
const isGameOver = useSelector(state => state.isGameOver)
function jump() {
const JUMP = constants.JUMP
if (isGameOver) {
store.dispatch(resetGame())
return
}
else if (!gameStarted) {
// store.dispatch(resetGame())
store.dispatch(startGame())
}
else if (birdPosition - JUMP >= 0)
store.dispatch(setBirdPosition(-JUMP))
else store.dispatch(setBirdPosition(0))
jumpAudio.pause();
jumpAudio.currentTime = 0;
jumpAudio.play()
}
return (
<Box onClick={jump}>
{isGameOver ? <GameOver /> : null}
{gameStarted || isGameOver ? <Score>{score}</Score> : null}
{true ? <Bird /> : null}
<Pipes height={200} />
{!gameStarted && !isGameOver ? <GameStart /> : null}
{/* <Pipes height={200} position={props.width + 300} wh={props.height} /> */}
</Box>
)
}
const Box = styled.div`
user-select: none; /* supported by Chrome and Opera */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Firefox */
-ms-user-select: none;
background: no-repeat center/100% url('/img/background-day.png');
overflow: hidden;
position: relative;
width: ${constants.WINDOW_WIDTH}px;
height: ${constants.WINDOW_HEIGHT}px
`
const GameStart = styled.div`
background: no-repeat center/70% url('/img/gamestart.png');
text-align: center;
width: 100%;
height: 100%;
`
const GameOver = styled.div`
position: relative;
z-index: 10;
background: no-repeat center/70% url('/img/gameover.png');
text-align: center;
width: 100%;
height: 100%;
`
const Score = styled.div`
font-family: 'Gamja Flower', cursive;
color: white;
text-shadow: black 2px 2px;
position: absolute;
font-size: 3rem;
z-index:1;
right: 10%;
top: 0;
`
I am using React JS + Typescript for my app. For styling I am using styled-components. I am really new in styled components. I have created one dropdown. The logic works fine but the UI looks horrible. I uploaded my code in Code sand box. I want design my Dropdown like Tailwind. But since I am new styled-components, I just don't know how to do that.
This is my dropdown component
import React, { useState } from "react";
import styled from "styled-components";
import Arrow from './Arrow.svg'
const Wrapper = styled.div<
{
active: boolean;
}
>`
text-align: left;
width: 100%;
color: #bfc5cd;
font-size: 16px;
font-weight: 300;
position: relative;
margin: 2em 0;
#media (min-width: 400px) {
max-width: 300px;
}
svg {
fill: #798697;
transition: all 0.2s ease;
}
${props =>
props.active
? `
svg {
transform: rotate(180deg);
}
`
: ``}
`;
const MenuLabel = styled.span`
display:inline-block;
color: grey;
border: 1px solid green;
background: white;
box-shadow: 0 0 5px -1px rgba(0,0,0,0.2);
cursor:pointer;
vertical-align:middle;
max-width: 100px;
padding: 40px 40px;
font-size: 12px;
text-align: center;
border: 1px solid ${({ theme }) => theme.inputBorderColor};
&:focus {
outline: none;
box-shadow: 0px 0px 0px 1px ${({ theme }) => theme.inputBorderColorActive};
border: 1px solid ${({ theme }) => theme.inputBorderColorActive};
}
`;
const ItemList = styled.div`
color: #798697;
background: white;
line-height: 30px;
padding: .25em 2em .25em 2em;
cursor: defaul;
user-select: none;
transition: all .25s ease;
&:hover,
&.selected {
background: #F7F7F7;
color: #4A4A4A;
}
`;
export interface IOptions {
label: string;
value: number;
}
export interface IDropdown {
labelDefault: string;
options: IOptions[];
}
const Dropdown = ({ labelDefault, options }: IDropdown) => {
const [isOpened, setIsOpened] = useState(false);
const [selectedOption, setSelectedOption] = useState("");
const [label, setLabel] = useState("");
const handleSelectedItem = (obj: any) => {
setSelectedOption(obj.value);
setLabel(obj.label);
setIsOpened(!isOpened);
};
return (
<Wrapper active={isOpened}>
<MenuLabel onClick={() => setIsOpened(!isOpened)}>
{selectedOption ? label : labelDefault}
</MenuLabel>
<ul
style={
isOpened
? {
display: "block",
listStyleType: "none"
}
: { display: "none" }
}
>
{options.map(el => (
<ItemList
key={el.value.toString()}
onClick={() => handleSelectedItem(el)}
>
{el.label}
</ItemList>
))}
</ul>
</Wrapper>
);
}
export default Dropdown;
This is the parent component
import * as React from "react";
import Dropdown from "./dropdown";
const MockData = [
{ label: "one", value: 1 },
{ label: "two", value: 2 },
{ label: "three", value: 3 }
];
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Dropdown labelDefault="Select a label" options={MockData} />
</div>
);
}
I have gotten stuck on a rather simple aspect of the autosave feature and that is the current status of the action like found on the overview page: https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/saving-data.html#demo. But it doesn't look like they actually reference it anywhere (example below).
My html is just:
<textarea class="form-control" name="notes" id="notes">{!! $shipmentShortage->notes !!}</textarea>
My create script is below, the autosave feature works just fine, but the status just isn't there:
<script>
ClassicEditor
.create( document.querySelector( '#notes' ), {
toolbar: [ 'heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote', 'undo', 'redo' ],
image: {
toolbar: [ 'imageStyle:full', 'imageStyle:side', '|', 'imageTextAlternative' ],
},
autosave: {
save( editor ) {
console.log(editor.getData());
// The saveData() function must return a promise
// which should be resolved when the data is successfully saved.
return saveData( editor.getData() );
}
}
} );
// Save the data to a fake HTTP server (emulated here with a setTimeout()).
function saveData( data ) {
return new Promise( resolve => {
setTimeout( () => {
console.log( 'Saved', data );
$.ajax({
url: '/osd/shortages/update',
type: 'POST',
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
data: {
'shortage_id':'{{$shipmentShortage->id}}',
'notes': data,
},
dataType: 'json',
success: function (response) {
console.log('saved');
}
});
resolve();
}, 5000 );
} );
}
// Update the "Status: Saving..." info.
function displayStatus( editor ) {
const pendingActions = editor.plugins.get( 'PendingActions' );
const statusIndicator = document.querySelector( '#editor-status' );
pendingActions.on( 'change:hasAny', ( evt, propertyName, newValue ) => {
if ( newValue ) {
statusIndicator.classList.add( 'busy' );
} else {
statusIndicator.classList.remove( 'busy' );
}
} );
}
</script>
You are absolutely correct. They show us a sexy status updater but don't give us the code for it. Here is what I extracted from the demo page by looking at the page source. This should give you the Status updates as you asked. Let me know if you have any questions.
HTML:
<div id="snippet-autosave">
<textarea name="content" id="CKeditor_Notes">
Sample text
</textarea>
</div>
<!-- This will show the save status -->
<div id="snippet-autosave-header">
<div id="snippet-autosave-status" class="">
<div id="snippet-autosave-status_label">Status:</div>
<div id="snippet-autosave-status_spinner">
<span id="snippet-autosave-status_spinner-label"></span>
<span id="snippet-autosave-status_spinner-loader"></span>
</div>
</div>
</div>
CSS:
<style>
#snippet-autosave-header{
display: flex;
justify-content: space-between;
align-items: center;
background: var(--ck-color-toolbar-background);
border: 1px solid var(--ck-color-toolbar-border);
padding: 10px;
border-radius: var(--ck-border-radius);
/*margin-top: -1.5em;*/
margin-bottom: 1.5em;
border-top: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
#snippet-autosave-status_spinner {
display: flex;
align-items: center;
position: relative;
}
#snippet-autosave-status_spinner-label {
position: relative;
}
#snippet-autosave-status_spinner-label::after {
content: 'Saved!';
color: green;
display: inline-block;
margin-right: var(--ck-spacing-medium);
}
/* During "Saving" display spinner and change content of label. */
#snippet-autosave-status.busy #snippet-autosave-status_spinner-label::after {
content: 'Saving...';
color: red;
}
#snippet-autosave-status.busy #snippet-autosave-status_spinner-loader {
display: block;
width: 16px;
height: 16px;
border-radius: 50%;
border-top: 3px solid hsl(0, 0%, 70%);
border-right: 2px solid transparent;
animation: autosave-status-spinner 1s linear infinite;
}
#snippet-autosave-status,
#snippet-autosave-server {
display: flex;
align-items: center;
}
#snippet-autosave-server_label,
#snippet-autosave-status_label {
font-weight: bold;
margin-right: var(--ck-spacing-medium);
}
#snippet-autosave + .ck.ck-editor .ck-editor__editable {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
#snippet-autosave-lag {
padding: 4px;
}
#snippet-autosave-console {
max-height: 300px;
overflow: auto;
white-space: normal;
background: #2b2c26;
transition: background-color 500ms;
}
#snippet-autosave-console.updated {
background: green;
}
#keyframes autosave-status-spinner {
to {
transform: rotate( 360deg );
}
}
</style>
The rest is just initializing the Editor just like on the demo page here.
ClassicEditor
.create(document.querySelector('#CKeditor_Notes'), {
autosave: {
save(editor) {
return saveData(editor.getData());
}
}
})
.then(editor => {
window.editor = editor;
displayStatus(editor);
})
.catch(err => {
console.error(err.stack);
});
// Save the data to Server Side DB.
function saveData(data) {
return new Promise(resolve => {
setTimeout(() => {
console.log('Saved', data);
SaveDataToDB(data)
resolve();
});
});
}
// Update the "Status: Saving..." info.
function displayStatus(editor) {
const pendingActions = editor.plugins.get('PendingActions');
const statusIndicator = document.querySelector('#snippet-autosave-status');
pendingActions.on('change:hasAny', (evt, propertyName, newValue) => {
if (newValue) {
statusIndicator.classList.add('busy');
} else {
statusIndicator.classList.remove('busy');
}
});
}
I have this SVG of a paperplane, which I want to have inside of a circle.
However, when I do the following the SVG is cropped at the edges. The nature of SVG is being scaleable, I have tried fiddling with the properties of the SVG however the problem remains. What should I do to keep this SVG
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 18.7 14.3" style="enable-background:new 0 0 18.7 14.3;" xml:space="preserve">
<style type="text/css">
.st0{fill:#000;}
</style>
<path class="st0" d="M18.7,0L0.3,4.1l3,3.6L0,9.4l0.5,4.9l5.6-4.1l2.3,1.5L18.7,0z M1.3,4.4L16,1.2L3.8,7.4L1.3,4.4z M10.3,4.7
l-9.3,5.5l-0.2,1.7L0.6,9.7L10.3,4.7z M1.2,13.2l0.4-2.7l2.6-1.6l1.5,1L1.2,13.2z M4.7,8.6l12-7.1l-8.3,9.5L4.7,8.6z"/>
</svg>
This is my HTML and CSS
.choice {
background-color: #E0D6AF;
border-radius: 50%;
height: 200px;
width: 200px;
margin: 10px auto;
transition: transform 0.2s;
transform: scale(1);
z-index: 10;
}
<img id="paper" class="choice" value="paper" src="http://www.jerrylow.com/demo/rps/paper.svg" alt="a piece of paper"> </img>
Full CSS file
.container {
display: inline-grid;
padding-top:30px;
grid-gap: 2px;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
}
#submit {
border-radius: 50%;
height: 150px;
width: 150px;
margin: auto;
grid-column: 2;
grid-row: 2;
background-color:#E0D6AF;
}
#submit:hover {
transform: scale(1.3);
}
.choice {
background-color: #E0D6AF;
border-radius: 50%;
height: 200px;
width: 200px;
margin: 10px auto;
transition: transform 0.2s;
transform: scale(1);
z-index: 10;
}
.choice > img:hover {
transform: scale(1.1);
}
#rock{
grid-column: 1;
grid-row:1;
}
#paper{
grid-column: 1;
grid-row:2;
}
#scissor{
grid-column: 1;
grid-row:3;
}
#humanScore > p {
padding-top: 80px;
text-align: center !important;
}
#humanScore {
grid-column: 2;
grid-row: 3;
}
#computerScore > p {
padding-top: 80px;
text-align: center !important;
}
#computerScore {
grid-column: 2;
grid-row: 1;
}
#result {
margin: auto;
width: 150px;
height: 150px;
border-radius: 50%;
grid-column: 3;
grid-row: 2;
}
#keyframes pulse{
0%{
transform: scale(0);
}
100%{
transform: scale(1);
}
}
.resultWin{
background-color:lightgreen;
transition: transform 0.2s;
transform: scale(1.3);
animation-name: pulse;
animation-duration: 1s;
animation-iteration-count: 1;
animation-fill-mode: both;
}
.resultLoss{
background-color: crimson;
transition: transform 0.2s;
transform: scale(1.3);
animation-name: pulse;
animation-duration: 1s;
animation-iteration-count: 1;
animation-fill-mode: both;
}
.resultTie{
background-color:goldenrod;
transition: transform 0.2s;
transform: scale(1.3);
animation-name: pulse;
animation-duration: 1s;
animation-iteration-count: 1;
animation-fill-mode: both;
}
.chosen { filter: brightness(70%) }
Full react file - needs a ton of refactoring so difficult to read.
import React from 'react';
import './Game.css'
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
picked: '',
score: {
player: 0,
computer: 0
},
previousPicks: {
rock: 0,
scissor: 0,
paper: 0
},
result: { result: "", player: "", computer: "" }
};
}
unmarkImage = () => {
if (this.state.picked !== "") {
document.querySelector(`#${this.state.picked}`).classList.remove("chosen")
}
}
onClickHandler = (event) => {
this.unmarkImage();
document.querySelector(`#${event.target.attributes.value.value}`).classList.add("chosen");
this.setState({ picked: event.target.attributes.value.value });
}
onSubmit = () => {
if (this.state.picked === "") {
alert("pick your weapon, before fighting")
return
}
const self = this;
fetch(`/api/play/${this.state.picked}`)
.then(function (res) {
res.text().then((function (text) {
self.setState({ result: JSON.parse('{' + text.replace(/(\w+):(\w+)/g, `"$1":"$2"`) + '}') })
self.updateScore(self.state.result.result);
self.updateResult();
self.unmarkImage();
self.updateComputerChoice();
self.updatePreviousPicks(self.state.picked)
self.setState({ picked: "" })
}))
})
}
updateScore = (result) => {
let scoreClone = { ...this.state.score }
if (result === "win") {
scoreClone.player++
this.setState({
score: scoreClone
})
} else if (result === "loss") {
scoreClone.computer++
this.setState({
score: scoreClone
})
}
}
updatePreviousPicks = (pick) => {
let previousPicksClone = { ...this.state.previousPicks }
if (pick === "rock") {
previousPicksClone.rock++;
this.setState({
previousPicks: previousPicksClone
})
} else if (pick === "scissor") {
previousPicksClone.scissor++;
this.setState({
previousPicks: previousPicksClone
})
} else {
previousPicksClone.paper++;
this.setState({
previousPicks: previousPicksClone
})
}
}
onReset = () => { //looks clumsy, but good performance
let computerDiv = document.querySelector("#computer")
while (computerDiv.firstChild) {
computerDiv.removeChild(computerDiv.firstChild);
}
}
updateResult = () => {
let result = document.querySelector("#result")
if (this.state.result.result === "win") {
result.classList.add("resultWin");
}
else if (this.state.result.result === "loss") {
result.classList.add("resultLoss");
} else {
result.classList.add("resultTie");
}
}
updateComputerChoice = () => {
let computerChoice = this.state.result.computer;
let result = document.querySelector("#result");
while (result.firstChild) {
result.removeChild(result.firstChild);
}
let answer = document.createElement("img");
let img;
if (computerChoice === "rock") {
img = document.getElementById("rock").src;
} else if (computerChoice === "scissor") {
img = document.getElementById("scissor").src;
} else {
img = document.getElementById("paper").src;
}
answer.src = img;
result.appendChild(answer);
}
render() {
return (
<div className="container">
<img id="rock" className="choice" value="rock" src="resources/rock.svg" alt="a rock" onClick={this.onClickHandler}></img>
<img id="paper" className="choice" value="paper" src="resources/paper.svg" alt="a piece of paper" onClick={this.onClickHandler}></img>
<img id="scissor" className="choice" value="scissor" src="resources/scissor.svg" alt="a scissor" onClick={this.onClickHandler}></img>
<button id="submit" className="waves-effect waves-light btn" onClick={this.onSubmit}>Fight the AI!</button>
<div id="humanScore">
<p>You: {this.state.score.player}</p>
</div>
<div id="computerScore">
<p>Computer: {this.state.score.computer}</p>
</div>
<div id="result" className="">
</div>
</div>
)
}
}
export default Game
To demonstrate #exaneta 's original suggestion, which I can't imagine would have any effect on a grid system, run the snippet below. If that's not what you are trying to accomplish, I'll delete this answer.
.choice {
background-color: #E0D6AF;
border-radius: 50%;
height: 200px;
width: 200px;
margin: 10px auto;
transition: transform 0.2s;
transform: scale(1);
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
}
.choice > img#paper{
width: 60%;
}
<div class="choice">
<img
id="paper"
value="paper"
src="http://www.jerrylow.com/demo/rps/paper.svg"
alt="a piece of paper"
/>
</div>
I am using a new library to create React Components, Styled-Components.
I want to apply an animation Tremble on my component via an onClick.
export class FormLoginTest extends React.Component { // eslint-disable-line react/prefer-stateless-function
static propTypes = {
isTrembling: React.PropTypes.bool
};
static defaultProps = {
isTrembling: true
};
onMakeTremble() {
alert("hello")
}
render() {
return (
<Form >
<ContainerFluid>
<H2>Login</H2>
</ContainerFluid>
<ContainerFluid>
<Label htmlFor="username">Username</Label>
<Input type="text" id="username" placeholder="bob" autoCorrect="off" autoCapitalize="off" spellCheck="false" />
</ContainerFluid>
<ContainerFluid>
<Label htmlFor="password">Password</Label>
<Input id="password" type="password" placeholder="••••••••••" />
</ContainerFluid>
<ContainerFluid>
<Button nature="success" onClick={this.onMakeTremble}>Hello</Button>
</ContainerFluid>
</Form>
);
}
}
So there is no Style.css sheet with Styled Components, all css is applied via javascript. Form has already been applied a css:
export class Form extends React.Component { // eslint-disable-line react/prefer-stateless-function
static propTypes = {
children: React.PropTypes.node.isRequired,
className: React.PropTypes.string
};
//
static defaultProps = {
isTrembling: true
};
render() {
return (
<form className={this.props.className}>
{React.Children.toArray(this.props.children)}
</form>
);
}
}
// eslint-disable-next-line no-class-assign
Form = styled(Form)`
max-width: 800px;
margin:0 auto;
display: block;
height: 100%;
border: 1px solid grey;
& h2{
text-align:center;
};
`;
export default Form;
And I have a component Tremble as well:
const shake = keyframes`
10%, 90% {
transform: translate3d(-1px, 0, 0);
}
20%, 80% {
transform: translate3d(2px, 0, 0);
}
30%, 50%, 70% {
transform: translate3d(-4px, 0, 0);
}
40%, 60% {
transform: translate3d(4px, 0, 0);
}
`;
const Tremble = styled.div`
display: inline-block;
&:hover {
animation: ${shake} 0.82s cubic-bezier(.36,.07,.19,.97) both;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
perspective: 1000px;
}
`;
export default Tremble;
Any clue as to how this may work?
Check out the docs under the keyframes section.
https://www.npmjs.com/package/styled-components
You might try importing keyframes from 'styled-components' and use it like this:
Example
import styled, {keyframes} from 'styled-components';
const moveGradient = keyframes`
0%{background-position:38% 0%}
50%{background-position:63% 100%}
100%{background-position:38% 0%}
`;
const Wrapper = styled.div`
background-size: 800% 800%;
width: 100vw;
height: 100vh;
background: linear-gradient(${props => props.gradientRotation}, #cc6bbb, #ffa86d);
animation: ${moveGradient} 10s ease-out infinite;
`;
I am encountering a problem with keyframes myself, as this code doesnt work with my gradient animation, but it will with others.
I will link to my question/issue in a comment here later :)!