diff --git a/showcase/src/patterns/index.css b/showcase/src/patterns/index.css index 8427f45..979e275 100644 --- a/showcase/src/patterns/index.css +++ b/showcase/src/patterns/index.css @@ -1,87 +1,81 @@ /* Clap button */ .clap { - position: relative; - outline: 1px solid transparent; - border-radius: 50%; - border: 1px solid #bdc3c7; - width: 80px; - height: 80px; - background: none; - user-select: none; -} + position: relative; + outline: 1px solid transparent; + border-radius: 50%; + border: 1px solid #bdc3c7; + width: 80px; + height: 80px; + background: none; + user-select: none; + } .clap:after { - content: ''; - position: absolute; - top: 0; - left: 0; - display: block; - border-radius: 50%; - width: 79px; - height: 79px; + content: ""; + position: absolute; + top: 0; + left: 0; + display: block; + border-radius: 50%; + width: 79px; + height: 79px; } .clap:hover { - cursor: pointer; - border: 1px solid #27ae60; - transition: border-color 0.3s ease-in; + cursor: pointer; + border: 1px solid #27ae60; + transition: border-color 0.3s ease-in; } .clap:hover:after { - animation: shockwave 1s ease-in infinite; + animation: shockwave 1s ease-in infinite; } - + @keyframes shockwave { - 0% { - transform: scale(1); - box-shadow: 0 0 2px #27ae60; - opacity: 1; - } - 100% { - transform: scale(1); - opacity: 0; - box-shadow: 0 0 50px #145b32, inset 0 0 10px #27ae60; - } + 0% { + transform: scale(1); + box-shadow: 0 0 2px #27ae60; + opacity: 1; + } + 100% { + transform: scale(1); + opacity: 0; + box-shadow: 0 0 50px #145b32, inset 0 0 10px #27ae60; + } } - + /* Count */ .count { - position: absolute; - top: -50px; - left: 20px; - font-size: 0.8rem; - color: white; - background: #27ae60; - border-radius: 50%; - height: 40px; - width: 40px; - line-height: 40px; + position: absolute; + top: -50px; + left: 20px; + font-size: 0.8rem; + color: white; + background: #27ae60; + border-radius: 50%; + height: 40px; + width: 40px; + line-height: 40px; } /* Count total */ .total { - position: absolute; - font-size: 0.8rem; - width: 80px; - text-align: center; - left: 0; - top: -22px; - color: #bdc3c7; -} + position: absolute; + font-size: 0.8rem; + width: 80px; + text-align: center; + left: 0; + top: -22px; + color: #bdc3c7; + } /* Clap Icon */ .icon { - width: 40px; - fill: none; - stroke: #27ae60; - stroke-width: 2px; -} + width: 40px; + fill: none; + stroke: #27ae60; + stroke-width: 2px; + } .icon.checked { - fill: #27ae60; - stroke: #fff; - stroke-width: 1px; -} - -/* Clap Info */ -.info { - position: relative; - top: 47px; + fill: #27ae60; + stroke: #fff; + stroke-width: 1px; } diff --git a/showcase/src/patterns/index.js b/showcase/src/patterns/index.js new file mode 100644 index 0000000..34fb644 --- /dev/null +++ b/showcase/src/patterns/index.js @@ -0,0 +1,198 @@ +import React, { Component, useState } from 'react' +import mojs from 'mo-js' +import { generateRandomNumber } from '../utils/generateRandomNumber' +import styles from './index.css' + +/** ==================================== + * 🔰HOC +Higher Order Component for Animation +==================================== **/ +const withClapAnimation = WrappedComponent => { + class WithClapAnimation extends Component { + animationTimeline = new mojs.Timeline() + state = { + animationTimeline: this.animationTimeline + } + + componentDidMount () { + const tlDuration = 300 + + const triangleBurst = new mojs.Burst({ + parent: '#clap', + radius: { 50: 95 }, + count: 5, + angle: 30, + children: { + shape: 'polygon', + radius: { 6: 0 }, + scale: 1, + stroke: 'rgba(211,84,0 ,0.5)', + strokeWidth: 2, + angle: 210, + delay: 30, + speed: 0.2, + easing: mojs.easing.bezier(0.1, 1, 0.3, 1), + duration: tlDuration + } + }) + + const circleBurst = new mojs.Burst({ + parent: '#clap', + radius: { 50: 75 }, + angle: 25, + duration: tlDuration, + children: { + shape: 'circle', + fill: 'rgba(149,165,166 ,0.5)', + delay: 30, + speed: 0.2, + radius: { 3: 0 }, + easing: mojs.easing.bezier(0.1, 1, 0.3, 1) + } + }) + + const countAnimation = new mojs.Html({ + el: '#clapCount', + isShowStart: false, + isShowEnd: true, + y: { 0: -30 }, + opacity: { 0: 1 }, + duration: tlDuration + }).then({ + opacity: { 1: 0 }, + y: -80, + delay: tlDuration / 2 + }) + + const countTotalAnimation = new mojs.Html({ + el: '#clapCountTotal', + isShowStart: false, + isShowEnd: true, + opacity: { 0: 1 }, + delay: (3 * tlDuration) / 2, + duration: tlDuration, + y: { 0: -3 } + }) + + const scaleButton = new mojs.Html({ + el: '#clap', + duration: tlDuration, + scale: { 1.3: 1 }, + easing: mojs.easing.out + }) + + const clap = document.getElementById('clap') + clap.style.transform = 'scale(1, 1)' + + const newAnimationTimeline = this.animationTimeline.add([ + countAnimation, + countTotalAnimation, + scaleButton, + circleBurst, + triangleBurst + ]) + this.setState({ animationTimeline: newAnimationTimeline }) + } + + render () { + return ( + + ) + } + } + + WithClapAnimation.displayName = `WithClapAnimation(${getDisplayName( + WrappedComponent + )})` + + return WithClapAnimation +} + +function getDisplayName (WrappedComponent) { + return WrappedComponent.displayName || WrappedComponent.name || 'Component' +} + +/** ==================================== + * 🔰 MediumClap +==================================== **/ +const initialState = { + count: 0, + countTotal: generateRandomNumber(500, 10000), + isClicked: false +} + +const MediumClap = ({ animationTimeline }) => { + const MAXIMUM_USER_CLAP = 50 + const [clapState, setClapState] = useState(initialState) + const { count, countTotal, isClicked } = clapState + + const handleClapClick = () => { + // 👉 prop from HOC + animationTimeline.replay() + + setClapState({ + count: Math.min(count + 1, MAXIMUM_USER_CLAP), + countTotal: count < MAXIMUM_USER_CLAP ? countTotal + 1 : countTotal, + isClicked: true + }) + } + + return ( + + ) +} + +/** ==================================== + * 🔰SubComponents +Smaller Component used by +==================================== **/ + +const ClapIcon = ({ isClicked }) => { + return ( + + + + + + + ) +} +const ClapCount = ({ count }) => { + return ( + + +{count} + + ) +} +const CountTotal = ({ countTotal }) => { + return ( + + {countTotal} + + ) +} + +/** ==================================== + * 🔰USAGE + Below's how a potential user + may consume the component API +==================================== **/ + +const Usage = () => { + const AnimatedMediumClap = withClapAnimation(MediumClap) + return +} + +export default Usage