1
1
import { distance } from "@crayon-ui/utils"
2
- import { AnimationEvent , PointerEvent , useCallback , useRef , useState } from "react"
2
+ import {
3
+ createRef ,
4
+ DragEventHandler ,
5
+ FocusEvent ,
6
+ FocusEventHandler ,
7
+ MouseEvent ,
8
+ MouseEventHandler ,
9
+ Ref ,
10
+ TouchEvent ,
11
+ TouchEventHandler ,
12
+ useCallback ,
13
+ useEffect ,
14
+ useRef ,
15
+ useState
16
+ } from "react"
17
+ import { TransitionGroup } from "react-transition-group"
3
18
4
19
import { useMeasure } from "../../hooks"
5
20
import { ColorVariant } from "../../theme"
6
- import { fadeOut , RippleEffect } from "./Ripple.styled"
21
+ import { TweenTransition } from "../Transition"
22
+ import { RippleEffect } from "./Ripple.styled"
23
+
24
+ const delay = 80
25
+ const timeout = 300
26
+ const animation = {
27
+ timeout,
28
+ transition : `opacity ${ timeout } ms ease-in-out` ,
29
+ begin : {
30
+ opacity : 0
31
+ } ,
32
+ end : {
33
+ opacity : 0.4
34
+ }
35
+ }
36
+
37
+ interface BindRippleHandlers {
38
+ ref : Ref < HTMLElement >
39
+ onBlur : FocusEventHandler
40
+ onContextMenu : MouseEventHandler
41
+ onDragLeave : DragEventHandler
42
+ onMouseDown : MouseEventHandler
43
+ onMouseLeave : MouseEventHandler
44
+ onMouseUp : MouseEventHandler
45
+ onTouchEnd : TouchEventHandler
46
+ onTouchMove : TouchEventHandler
47
+ onTouchStart : TouchEventHandler
48
+ }
7
49
8
50
interface Props {
9
51
color ?: ColorVariant
@@ -12,76 +54,79 @@ interface Props {
12
54
}
13
55
14
56
export const useRipple = ( { color = "primary" , center = false , disabled = false } : Props ) => {
15
- const rippleKey = useRef < string | null > ( )
16
- const { measure, rect } = useMeasure < HTMLSpanElement > ( )
57
+ const { measure, rect } = useMeasure < HTMLElement > ( )
17
58
const [ ripples , setRipples ] = useState < JSX . Element [ ] > ( [ ] )
59
+ const isIgnoreMouseDown = useRef < boolean > ( false )
60
+ const timer = useRef < NodeJS . Timeout > ( )
18
61
19
- const removeRippleByKey = useCallback (
20
- ( key : string ) => ( e : AnimationEvent ) => {
21
- if ( e . animationName === fadeOut . name ) {
22
- setRipples ( ( prev ) => prev . filter ( ( effect ) => effect . key !== key ) )
23
- }
62
+ useEffect (
63
+ ( ) => ( ) => {
64
+ timer . current && clearTimeout ( timer . current )
24
65
} ,
25
66
[ ]
26
67
)
27
68
28
- const hide = useCallback ( ( ) => {
29
- if ( ! rippleKey . current ) {
30
- return
69
+ const commit = useCallback ( ( radius : number , cx : number , cy : number ) => {
70
+ const ref = createRef < HTMLElement > ( )
71
+ setRipples ( ( prev ) => [
72
+ ...prev ,
73
+ < TweenTransition { ...animation } key = { `${ performance . now ( ) } ` } >
74
+ < RippleEffect ref = { ref } color = { color } radius = { radius } cx = { cx } cy = { cy } timeout = { timeout } />
75
+ </ TweenTransition >
76
+ ] )
77
+ } , [ ] )
78
+
79
+ const stop = useCallback ( ( e : FocusEvent | MouseEvent | TouchEvent ) => {
80
+ if ( e . type === "touchmove" ) {
81
+ timer . current && clearTimeout ( timer . current )
31
82
}
32
- const key = rippleKey . current
33
- rippleKey . current = null
34
- setRipples ( ( prev ) =>
35
- prev . map ( ( ripple ) => {
36
- if ( ripple . key !== key ) {
37
- return ripple
38
- }
39
- return (
40
- < RippleEffect
41
- key = { key }
42
- { ...ripple . props }
43
- mount = { false }
44
- onAnimationEnd = { removeRippleByKey ( key ) }
45
- />
46
- )
47
- } )
48
- )
49
- } , [ removeRippleByKey ] )
83
+ setRipples ( ( prev ) => ( prev . length > 0 ? prev . slice ( 1 ) : prev ) )
84
+ } , [ ] )
50
85
51
- const show = useCallback (
52
- ( { clientX, clientY } : PointerEvent < HTMLSpanElement > ) => {
53
- if ( rippleKey . current ) {
54
- hide ( )
86
+ const start = useCallback (
87
+ ( event : TouchEvent | MouseEvent ) => {
88
+ const isTouchEvent = "touches" in event && event . touches . length > 0
89
+ if ( ! isTouchEvent && isIgnoreMouseDown . current ) {
90
+ isIgnoreMouseDown . current = false
91
+ return
55
92
}
93
+ isIgnoreMouseDown . current = isTouchEvent
94
+
56
95
const { left, top, width, height } = rect ( )
96
+ const { clientX, clientY } = isTouchEvent ? event . touches [ 0 ] : ( event as MouseEvent )
57
97
const cx = center ? width / 2 : clientX - left
58
98
const cy = center ? height / 2 : clientY - top
59
99
const radius = distance ( [ 0 , 0 ] , [ Math . max ( cx , width - cx ) , Math . max ( cy , height - cy ) ] )
60
- const key = `${ performance . now ( ) } `
61
- rippleKey . current = key
62
- setRipples ( ( prev ) => [
63
- ...prev ,
64
- < RippleEffect key = { key } color = { color } radius = { radius } cx = { cx } cy = { cy } />
65
- ] )
100
+ if ( isTouchEvent ) {
101
+ timer . current = setTimeout ( ( ) => commit ( radius , cx , cy ) , delay )
102
+ return
103
+ }
104
+ commit ( radius , cx , cy )
66
105
} ,
67
- [ hide ]
106
+ [ stop ]
68
107
)
69
108
70
- const bind = useCallback ( ( ) => {
109
+ const bind = useCallback ( ( ) : Partial < BindRippleHandlers > => {
71
110
if ( disabled ) {
72
111
return measure ( )
73
112
}
74
113
75
114
return {
76
115
...measure ( ) ,
77
- onPointerDown : show ,
78
- onPointerMove : hide ,
79
- onPointerUp : hide
116
+ onBlur : stop ,
117
+ onContextMenu : stop ,
118
+ onDragLeave : stop ,
119
+ onMouseDown : start ,
120
+ onMouseUp : stop ,
121
+ onMouseLeave : stop ,
122
+ onTouchStart : start ,
123
+ onTouchMove : stop ,
124
+ onTouchEnd : stop
80
125
}
81
- } , [ measure , show , hide , disabled ] )
126
+ } , [ measure , start , stop , disabled ] )
82
127
83
128
return {
84
129
bind,
85
- ripples
130
+ ripple : < TransitionGroup component = { null } > { ripples } </ TransitionGroup >
86
131
}
87
132
}
0 commit comments