1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
import React from "react"
import { ViewStyle, Animated, Easing, TouchableWithoutFeedback } from "react-native"
import { color } from "../../theme"
import { SwitchProps } from "./switch.props"
// dimensions
const THUMB_SIZE = 30
const WIDTH = 56
const MARGIN = 2
const OFF_POSITION = -0.5
const ON_POSITION = WIDTH - THUMB_SIZE - MARGIN
const BORDER_RADIUS = (THUMB_SIZE * 3) / 4
// colors
const ON_COLOR = color.primary
const OFF_COLOR = color.palette.offWhite
const BORDER_ON_COLOR = ON_COLOR
const BORDER_OFF_COLOR = "rgba(0, 0, 0, 0.1)"
// animation
const DURATION = 250
// the track always has these props
const TRACK = {
height: THUMB_SIZE + MARGIN,
width: WIDTH,
borderRadius: BORDER_RADIUS,
borderWidth: MARGIN / 2,
backgroundColor: color.background,
}
// the thumb always has these props
const THUMB: ViewStyle = {
position: "absolute",
width: THUMB_SIZE,
height: THUMB_SIZE,
borderColor: BORDER_OFF_COLOR,
borderRadius: THUMB_SIZE / 2,
borderWidth: MARGIN / 2,
backgroundColor: color.background,
shadowColor: BORDER_OFF_COLOR,
shadowOffset: { width: 1, height: 2 },
shadowOpacity: 1,
shadowRadius: 2,
elevation: 2,
}
const makeAnimatedValue = (switchOn) => new Animated.Value(switchOn ? 1 : 0)
export function Switch(props: SwitchProps) {
const [timer] = React.useState<Animated.Value>(makeAnimatedValue(props.value))
const startAnimation = React.useMemo(
() => (newValue: boolean) => {
const toValue = newValue ? 1 : 0
const easing = Easing.out(Easing.circle)
Animated.timing(timer, {
toValue,
duration: DURATION,
easing,
useNativeDriver: true,
}).start()
},
[timer],
)
const [previousValue, setPreviousValue] = React.useState<boolean>(props.value)
React.useEffect(() => {
if (props.value !== previousValue) {
startAnimation(props.value)
setPreviousValue(props.value)
}
}, [props.value])
const handlePress = React.useMemo(() => () => props.onToggle && props.onToggle(!props.value), [
props.onToggle,
props.value,
])
if (!timer) {
return null
}
const translateX = timer.interpolate({
inputRange: [0, 1],
outputRange: [OFF_POSITION, ON_POSITION],
})
const style = props.style
const trackStyle = [
TRACK,
{
backgroundColor: props.value ? ON_COLOR : OFF_COLOR,
borderColor: props.value ? BORDER_ON_COLOR : BORDER_OFF_COLOR,
},
props.value ? props.trackOnStyle : props.trackOffStyle,
]
const thumbStyle = [
THUMB,
{
transform: [{ translateX }],
},
props.value ? props.thumbOnStyle : props.thumbOffStyle,
]
return (
<TouchableWithoutFeedback onPress={handlePress} style={style}>
<Animated.View style={trackStyle}>
<Animated.View style={thumbStyle} />
</Animated.View>
</TouchableWithoutFeedback>
)
}
|