Skip to content

Commit dfbe09a

Browse files
committed
initial refactor #7
1 parent 4fe7f2d commit dfbe09a

File tree

1 file changed

+244
-0
lines changed
  • showcase/src/patterns

1 file changed

+244
-0
lines changed

showcase/src/patterns/07.js

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
import React, {
2+
useState,
3+
useLayoutEffect,
4+
useCallback,
5+
useRef,
6+
useEffect
7+
} from 'react'
8+
import mojs from 'mo-js'
9+
import styles from './index.css'
10+
11+
const INITIAL_STATE = {
12+
count: 0,
13+
countTotal: 267,
14+
isClicked: false
15+
}
16+
17+
/**
18+
* Custom Hook for animation
19+
*/
20+
const useClapAnimation = ({ clapEl, countEl, clapTotalEl }) => {
21+
const [animationTimeline, setAnimationTimeline] = useState(
22+
() => new mojs.Timeline()
23+
)
24+
25+
useLayoutEffect(() => {
26+
if (!clapEl || !countEl || !clapTotalEl) {
27+
return
28+
}
29+
30+
const tlDuration = 300
31+
const scaleButton = new mojs.Html({
32+
el: clapEl,
33+
duration: tlDuration,
34+
scale: { 1.3: 1 },
35+
easing: mojs.easing.ease.out
36+
})
37+
38+
const triangleBurst = new mojs.Burst({
39+
parent: clapEl,
40+
radius: { 50: 95 },
41+
count: 5,
42+
angle: 30,
43+
children: {
44+
shape: 'polygon',
45+
radius: { 6: 0 },
46+
stroke: 'rgba(211,54,0,0.5)',
47+
strokeWidth: 2,
48+
angle: 210,
49+
delay: 30,
50+
speed: 0.2,
51+
easing: mojs.easing.bezier(0.1, 1, 0.3, 1),
52+
duration: tlDuration
53+
}
54+
})
55+
56+
const circleBurst = new mojs.Burst({
57+
parent: clapEl,
58+
radius: { 50: 75 },
59+
angle: 25,
60+
duration: tlDuration,
61+
children: {
62+
shape: 'circle',
63+
fill: 'rgba(149,165,166,0.5)',
64+
delay: 30,
65+
speed: 0.2,
66+
radius: { 3: 0 },
67+
easing: mojs.easing.bezier(0.1, 1, 0.3, 1)
68+
}
69+
})
70+
71+
const countAnimation = new mojs.Html({
72+
el: countEl,
73+
opacity: { 0: 1 },
74+
y: { 0: -30 },
75+
duration: tlDuration
76+
}).then({
77+
opacity: { 1: 0 },
78+
y: -80,
79+
delay: tlDuration / 2
80+
})
81+
82+
const countTotalAnimation = new mojs.Html({
83+
el: clapTotalEl,
84+
opacity: { 0: 1 },
85+
delay: (3 * tlDuration) / 2,
86+
duration: tlDuration,
87+
y: { 0: -3 }
88+
})
89+
90+
if (typeof clapEl === 'string') {
91+
const clap = document.getElementById('clap')
92+
clap.style.transform = 'scale(1,1)'
93+
} else {
94+
clapEl.style.transform = 'scale(1,1)'
95+
}
96+
97+
const newAnimationTimeline = animationTimeline.add([
98+
scaleButton,
99+
countTotalAnimation,
100+
countAnimation,
101+
triangleBurst,
102+
circleBurst
103+
])
104+
setAnimationTimeline(newAnimationTimeline)
105+
}, [clapEl, countEl, clapTotalEl])
106+
107+
return animationTimeline
108+
}
109+
110+
/**
111+
* useDOMRef Hook
112+
*/
113+
const useDOMRef = () => {
114+
const [DOMRef, setRefState] = useState({})
115+
116+
const setRef = useCallback(node => {
117+
setRefState(prevRefState => ({
118+
...prevRefState,
119+
[node.dataset.refkey]: node
120+
}))
121+
}, [])
122+
123+
return [DOMRef, setRef]
124+
}
125+
126+
/**
127+
* custom hook for useClapState
128+
*/
129+
const useClapState = (initialState = INITIAL_STATE) => {
130+
const MAXIMUM_USER_CLAP = 50
131+
const [clapState, setClapState] = useState(initialState)
132+
const { count, countTotal } = clapState
133+
134+
const updateClapState = useCallback(() => {
135+
setClapState(({ count, countTotal }) => ({
136+
isClicked: true,
137+
count: Math.min(count + 1, MAXIMUM_USER_CLAP),
138+
countTotal: count < MAXIMUM_USER_CLAP ? countTotal + 1 : countTotal
139+
}))
140+
}, [count, countTotal])
141+
142+
return [clapState, updateClapState]
143+
}
144+
145+
/**
146+
* custom useEffectAfterMount hook
147+
*/
148+
const useEffectAfterMount = (cb, deps) => {
149+
const componentJustMounted = useRef(true)
150+
useEffect(() => {
151+
if (!componentJustMounted.current) {
152+
return cb()
153+
}
154+
componentJustMounted.current = false
155+
}, deps)
156+
}
157+
158+
/**
159+
* subcomponents
160+
*/
161+
162+
const ClapContainer = ({ children, handleClick, setRef, ...restProps }) => {
163+
return (
164+
<button
165+
ref={setRef}
166+
className={styles.clap}
167+
onClick={handleClick}
168+
{...restProps}
169+
>
170+
{children}
171+
</button>
172+
)
173+
}
174+
175+
const ClapIcon = ({ isClicked, ...restProps }) => {
176+
return (
177+
<span>
178+
<svg
179+
xmlns='http://www.w3.org/2000/svg'
180+
viewBox='-549 338 100.1 125'
181+
className={`${styles.icon} ${isClicked && styles.checked}`}
182+
{...restProps}
183+
>
184+
<path d='M-471.2 366.8c1.2 1.1 1.9 2.6 2.3 4.1.4-.3.8-.5 1.2-.7 1-1.9.7-4.3-1-5.9-2-1.9-5.2-1.9-7.2.1l-.2.2c1.8.1 3.6.9 4.9 2.2zm-28.8 14c.4.9.7 1.9.8 3.1l16.5-16.9c.6-.6 1.4-1.1 2.1-1.5 1-1.9.7-4.4-.9-6-2-1.9-5.2-1.9-7.2.1l-15.5 15.9c2.3 2.2 3.1 3 4.2 5.3zm-38.9 39.7c-.1-8.9 3.2-17.2 9.4-23.6l18.6-19c.7-2 .5-4.1-.1-5.3-.8-1.8-1.3-2.3-3.6-4.5l-20.9 21.4c-10.6 10.8-11.2 27.6-2.3 39.3-.6-2.6-1-5.4-1.1-8.3z' />
185+
<path d='M-527.2 399.1l20.9-21.4c2.2 2.2 2.7 2.6 3.5 4.5.8 1.8 1 5.4-1.6 8l-11.8 12.2c-.5.5-.4 1.2 0 1.7.5.5 1.2.5 1.7 0l34-35c1.9-2 5.2-2.1 7.2-.1 2 1.9 2 5.2.1 7.2l-24.7 25.3c-.5.5-.4 1.2 0 1.7.5.5 1.2.5 1.7 0l28.5-29.3c2-2 5.2-2 7.1-.1 2 1.9 2 5.1.1 7.1l-28.5 29.3c-.5.5-.4 1.2 0 1.7.5.5 1.2.4 1.7 0l24.7-25.3c1.9-2 5.1-2.1 7.1-.1 2 1.9 2 5.2.1 7.2l-24.7 25.3c-.5.5-.4 1.2 0 1.7.5.5 1.2.5 1.7 0l14.6-15c2-2 5.2-2 7.2-.1 2 2 2.1 5.2.1 7.2l-27.6 28.4c-11.6 11.9-30.6 12.2-42.5.6-12-11.7-12.2-30.8-.6-42.7m18.1-48.4l-.7 4.9-2.2-4.4m7.6.9l-3.7 3.4 1.2-4.8m5.5 4.7l-4.8 1.6 3.1-3.9' />
186+
</svg>
187+
</span>
188+
)
189+
}
190+
const ClapCount = ({ count, setRef, ...restProps }) => {
191+
return (
192+
<span ref={setRef} className={styles.count} {...restProps}>
193+
+ {count}
194+
</span>
195+
)
196+
}
197+
198+
const CountTotal = ({ countTotal, setRef, ...restProps }) => {
199+
return (
200+
<span ref={setRef} className={styles.total} {...restProps}>
201+
{countTotal}
202+
</span>
203+
)
204+
}
205+
206+
/**
207+
* Usage
208+
*/
209+
210+
const Usage = () => {
211+
const [clapState, updateClapState] = useClapState()
212+
const { count, countTotal, isClicked } = clapState
213+
214+
const [{ clapRef, clapCountRef, clapTotalRef }, setRef] = useDOMRef()
215+
216+
const animationTimeline = useClapAnimation({
217+
clapEl: clapRef,
218+
countEl: clapCountRef,
219+
clapTotalEl: clapTotalRef
220+
})
221+
222+
useEffectAfterMount(() => {
223+
animationTimeline.replay()
224+
}, [count])
225+
226+
return (
227+
<ClapContainer
228+
setRef={setRef}
229+
data-refkey='clapRef'
230+
className={styles.clap}
231+
onClick={updateClapState}
232+
>
233+
<ClapIcon isClicked={isClicked} />
234+
<ClapCount count={count} setRef={setRef} data-refkey='clapCountRef' />
235+
<CountTotal
236+
countTotal={countTotal}
237+
setRef={setRef}
238+
data-refkey='clapTotalRef'
239+
/>
240+
</ClapContainer>
241+
)
242+
}
243+
244+
export default Usage

0 commit comments

Comments
 (0)