Skip to content

Commit d39c353

Browse files
CP-9963 - [Component] AnimatedPressable (#2324)
1 parent fc9106f commit d39c353

File tree

7 files changed

+175
-53
lines changed

7 files changed

+175
-53
lines changed

packages/k2-alpine/src/components/Animated/Animated.stories.tsx

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
import React, { useEffect, useState } from 'react'
2-
import { ScrollView, Text } from '../Primitives'
2+
import { useTheme } from '../../hooks'
3+
import { alpha } from '../../utils'
4+
import { showAlert } from '../Alert/Alert'
5+
import { ScrollView, Text, View } from '../Primitives'
6+
import { AnimatedPressable } from './AnimatedPressable'
37
import { AnimatedText } from './AnimatedText'
48

59
export default {
610
title: 'Animated'
711
}
812

913
export const All = (): JSX.Element => {
14+
const {
15+
theme: { colors }
16+
} = useTheme()
1017
const [characters, setCharacters] = useState(107.25)
1118

1219
useEffect(() => {
1320
const interval = setInterval(() => {
14-
setCharacters(prev => parseFloat((prev + 0.22).toFixed(2)))
21+
setCharacters(prev => parseFloat((prev + 0.23).toFixed(2)))
1522
}, 2000)
1623
return () => {
1724
clearInterval(interval)
@@ -24,8 +31,40 @@ export const All = (): JSX.Element => {
2431
width: '100%'
2532
}}
2633
contentContainerStyle={{ padding: 16, gap: 16, alignItems: 'center' }}>
27-
<Text variant="heading6">Animated Text</Text>
28-
<AnimatedText characters={`$${characters}`} />
34+
<View
35+
style={{
36+
gap: 16
37+
}}>
38+
<Text variant="heading6">Animated Text</Text>
39+
<AnimatedText characters={`$${characters}`} />
40+
</View>
41+
<View
42+
style={{
43+
gap: 16,
44+
alignItems: 'center'
45+
}}>
46+
<Text variant="heading6">Animated Pressable</Text>
47+
<AnimatedPressable
48+
onPress={() =>
49+
showAlert({
50+
title: 'Pressed',
51+
buttons: [
52+
{
53+
text: 'OK'
54+
}
55+
]
56+
})
57+
}>
58+
<View
59+
sx={{
60+
width: 100,
61+
height: 100,
62+
backgroundColor: alpha(colors.$textPrimary, 0.1),
63+
borderRadius: 18
64+
}}
65+
/>
66+
</AnimatedPressable>
67+
</View>
2968
</ScrollView>
3069
)
3170
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import React, { memo, useCallback } from 'react'
2+
import { GestureResponderEvent, PressableProps } from 'react-native'
3+
import { Pressable } from 'dripsy'
4+
import Animated, {
5+
runOnJS,
6+
useAnimatedStyle,
7+
useSharedValue,
8+
withSpring,
9+
withTiming
10+
} from 'react-native-reanimated'
11+
import { throttle } from 'lodash'
12+
import { ANIMATED } from '../../utils'
13+
14+
interface AnimatedPressable extends PressableProps {
15+
children: JSX.Element
16+
}
17+
18+
const AnimatedPress = Animated.createAnimatedComponent(Pressable)
19+
20+
export const AnimatedPressable = memo(
21+
({ children, onPress, ...props }: AnimatedPressable) => {
22+
const opacity = useSharedValue(1)
23+
const scale = useSharedValue(1)
24+
25+
const onPressIn = (): void => {
26+
'worklet'
27+
opacity.value = withTiming(0.5, ANIMATED.TIMING_CONFIG)
28+
scale.value = withSpring(ANIMATED.SCALE, ANIMATED.SPRING_CONFIG)
29+
}
30+
31+
const onPressOut = (event: GestureResponderEvent): void => {
32+
'worklet'
33+
opacity.value = withTiming(1, ANIMATED.TIMING_CONFIG)
34+
scale.value = withSpring(1, ANIMATED.SPRING_CONFIG, () => {
35+
if (onPress) {
36+
runOnJS(onPressEvent)(event)
37+
}
38+
})
39+
}
40+
41+
const onPressEvent = useCallback(
42+
(event: GestureResponderEvent) => {
43+
throttle(
44+
() => {
45+
onPress?.(event)
46+
},
47+
1000,
48+
{ leading: true, trailing: false }
49+
)
50+
},
51+
[onPress]
52+
)
53+
54+
const animatedStyle = useAnimatedStyle(() => {
55+
return {
56+
opacity: opacity.value,
57+
transform: [{ scale: scale.value }]
58+
}
59+
})
60+
61+
return (
62+
<AnimatedPress
63+
onPressIn={onPressIn}
64+
onPressOut={onPressOut}
65+
{...props}
66+
style={[props.style, animatedStyle]}>
67+
{children}
68+
</AnimatedPress>
69+
)
70+
}
71+
)
Lines changed: 34 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import React, { useEffect } from 'react'
1+
import React, { memo, useCallback, useEffect } from 'react'
22

33
import { SxProp } from 'dripsy'
44
import { LayoutChangeEvent } from 'react-native'
55
import Animated, {
6-
Easing,
76
FadeIn,
87
FadeOut,
98
LinearTransition,
@@ -14,6 +13,7 @@ import Animated, {
1413
withTiming
1514
} from 'react-native-reanimated'
1615
import { TextVariant } from '../../theme/tokens/text'
16+
import { ANIMATED } from '../../utils'
1717
import { Text } from '../Primitives'
1818

1919
export const AnimatedText = ({
@@ -26,7 +26,7 @@ export const AnimatedText = ({
2626
variant?: TextVariant
2727
sx?: SxProp
2828
onLayout?: (event: LayoutChangeEvent) => void
29-
}) => {
29+
}): JSX.Element => {
3030
return (
3131
<Animated.View
3232
layout={LinearTransition.springify()}
@@ -51,51 +51,38 @@ export const AnimatedText = ({
5151
)
5252
}
5353

54-
export const AnimateFadeScale = ({
55-
children,
56-
delay = 0
57-
}: {
58-
children: JSX.Element
59-
delay?: number
60-
}) => {
61-
const opacity = useSharedValue(0)
62-
const scale = useSharedValue(0.8)
54+
export const AnimateFadeScale = memo(
55+
({ children, delay = 0 }: { children: JSX.Element; delay?: number }) => {
56+
const opacity = useSharedValue(0)
57+
const scale = useSharedValue(0.8)
6358

64-
useEffect(() => {
65-
animate()
66-
}, [])
59+
const animate = useCallback(() => {
60+
'worklet'
61+
opacity.value = withDelay(delay, withTiming(1, ANIMATED.TIMING_CONFIG))
62+
scale.value = withDelay(delay, withSpring(1, ANIMATED.SPRING_CONFIG))
63+
}, [delay, opacity, scale])
6764

68-
const animate = () => {
69-
'worklet'
70-
opacity.value = withDelay(
71-
delay,
72-
withTiming(1, {
73-
duration: 500,
74-
easing: Easing.bezier(0.25, 1, 0.5, 1)
75-
})
76-
)
77-
scale.value = withDelay(
78-
delay,
79-
withSpring(1, { damping: 10, stiffness: 200, mass: 0.5 })
80-
)
81-
}
65+
useEffect(() => {
66+
animate()
67+
}, [animate])
8268

83-
const animatedStyle = useAnimatedStyle(() => {
84-
return {
85-
opacity: opacity.value,
86-
transform: [{ scale: scale.value }]
87-
}
88-
}, [])
69+
const animatedStyle = useAnimatedStyle(() => {
70+
return {
71+
opacity: opacity.value,
72+
transform: [{ scale: scale.value }]
73+
}
74+
}, [])
8975

90-
return (
91-
<Animated.View
92-
entering={FadeIn}
93-
exiting={FadeOut}
94-
style={[
95-
animatedStyle,
96-
{ justifyContent: 'flex-start', alignItems: 'flex-start' }
97-
]}>
98-
{children}
99-
</Animated.View>
100-
)
101-
}
76+
return (
77+
<Animated.View
78+
entering={FadeIn}
79+
exiting={FadeOut}
80+
style={[
81+
animatedStyle,
82+
{ justifyContent: 'flex-start', alignItems: 'flex-start' }
83+
]}>
84+
{children}
85+
</Animated.View>
86+
)
87+
}
88+
)

packages/k2-alpine/src/components/Tooltip/Tooltip.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ export const Tooltip = ({
2626
bottom: 13,
2727
left: 13
2828
}
29-
}: TooltipProps) => {
29+
}: TooltipProps): JSX.Element => {
3030
const { theme } = useTheme()
3131

32-
const onPress = () => {
32+
const onPress = (): void => {
3333
showAlert({
3434
title,
3535
description,

packages/k2-alpine/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ export * from './Header/NavigationTitleHeader'
2222
export * from './Dropdown/usePopoverAnchor'
2323
export * from './Toggle/Toggle'
2424
export * from './Animated/AnimatedText'
25+
export * from './Animated/AnimatedPressable'
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Easing } from 'react-native-reanimated'
2+
3+
export const SCALE = 0.96
4+
export const EASING = Easing.bezier(0.25, 1, 0.5, 1) // EaseOutQuart
5+
export const DURATION = 500
6+
7+
export const SPRING_CONFIG = {
8+
damping: 10,
9+
stiffness: 200,
10+
mass: 0.5
11+
}
12+
export const TIMING_CONFIG = {
13+
duration: DURATION,
14+
easing: EASING
15+
}
16+
17+
export const ANIMATED = {
18+
DURATION,
19+
EASING,
20+
SPRING_CONFIG,
21+
TIMING_CONFIG,
22+
SCALE
23+
}

packages/k2-alpine/src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './colors'
22
export * from './screens'
3+
export * from './animations'

0 commit comments

Comments
 (0)