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