Skip to content

Commit cae41fd

Browse files
committed
fix(ui): add custom Switch component
1 parent 94798cc commit cae41fd

File tree

7 files changed

+126
-27
lines changed

7 files changed

+126
-27
lines changed

src/components/List.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
import { SvgProps } from 'react-native-svg';
1212
import isEqual from 'lodash/isEqual';
1313

14-
import { Switch } from '../styles/components';
1514
import {
1615
BodyM,
1716
BodyMSB,
@@ -21,6 +20,7 @@ import {
2120
Caption,
2221
} from '../styles/text';
2322
import { ChevronRight, Checkmark } from '../styles/icons';
23+
import Switch from '../components/Switch';
2424
import DraggableList from '../screens/Settings/PaymentPreference/DraggableList';
2525

2626
const _SectionHeader = memo(
@@ -87,14 +87,14 @@ export type ItemData = SwitchItem | ButtonItem | TextButtonItem | DraggableItem;
8787

8888
export type SwitchItem = {
8989
type: EItemType.switch;
90+
enabled: boolean;
9091
title: string;
9192
Icon?: React.FC<SvgProps>;
9293
iconColor?: string;
93-
enabled?: boolean;
9494
disabled?: boolean;
9595
hide?: boolean;
96-
onPress?: () => void;
9796
testID?: string;
97+
onPress?: () => void;
9898
};
9999

100100
export type ButtonItem = {
@@ -107,10 +107,10 @@ export type ButtonItem = {
107107
iconColor?: string;
108108
disabled?: boolean;
109109
enabled?: boolean;
110+
loading?: boolean;
110111
hide?: boolean;
111-
onPress?: () => void;
112112
testID?: string;
113-
loading?: boolean;
113+
onPress?: () => void;
114114
};
115115

116116
export type TextButtonItem = {
@@ -122,17 +122,17 @@ export type TextButtonItem = {
122122
iconColor?: string;
123123
enabled?: boolean;
124124
hide?: boolean;
125-
onPress?: () => void;
126125
testID?: string;
126+
onPress?: () => void;
127127
};
128128

129129
export type DraggableItem = {
130130
type: EItemType.draggable;
131131
value: TItemDraggable[];
132132
title: string;
133133
hide?: boolean;
134-
onDragEnd?: (data: TItemDraggable[]) => void;
135134
testID?: string;
135+
onDragEnd?: (data: TItemDraggable[]) => void;
136136
};
137137

138138
const _Item = memo((item: ItemData): ReactElement => {

src/components/Switch.tsx

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import React, { ReactElement } from 'react';
2+
import { Pressable, StyleSheet } from 'react-native';
3+
import Animated, {
4+
Easing,
5+
interpolate,
6+
interpolateColor,
7+
useAnimatedStyle,
8+
useDerivedValue,
9+
useSharedValue,
10+
withTiming,
11+
} from 'react-native-reanimated';
12+
13+
import colors from '../styles/colors';
14+
import { IThemeColors } from '../styles/themes';
15+
16+
const duration = 300;
17+
const defaultHeight = 32;
18+
const defaultWidth = 52;
19+
20+
const Switch = ({
21+
value,
22+
disabled,
23+
color,
24+
onValueChange,
25+
}: {
26+
value: boolean;
27+
disabled?: boolean;
28+
color?: keyof IThemeColors;
29+
onValueChange?: () => void;
30+
}): ReactElement => {
31+
const height = useSharedValue(defaultHeight);
32+
const width = useSharedValue(defaultWidth);
33+
const sharedValue = useDerivedValue(() => {
34+
return value ? 1 : 0;
35+
});
36+
37+
const thumbColor = disabled ? '#A0A0A0' : colors.white;
38+
const trackColor = color ? colors[color] : colors.brand;
39+
const trackColors = { on: trackColor, off: '#3A3A3C' };
40+
41+
const trackAnimatedStyle = useAnimatedStyle(() => {
42+
const animatedColor = interpolateColor(
43+
sharedValue.value,
44+
[0, 1],
45+
[trackColors.off, trackColors.on],
46+
);
47+
const colorValue = withTiming(animatedColor, {
48+
duration,
49+
easing: Easing.inOut(Easing.ease),
50+
});
51+
52+
return {
53+
backgroundColor: colorValue,
54+
borderRadius: height.value / 2,
55+
};
56+
});
57+
58+
const thumbAnimatedStyle = useAnimatedStyle(() => {
59+
const moveValue = interpolate(
60+
sharedValue.value,
61+
[0, 1],
62+
[0, width.value - height.value],
63+
);
64+
const translateValue = withTiming(moveValue, {
65+
duration,
66+
easing: Easing.bezier(0.61, 0.46, 0.3, 1.07),
67+
});
68+
69+
return {
70+
transform: [{ translateX: translateValue }],
71+
borderRadius: height.value / 2,
72+
};
73+
});
74+
75+
const onPress = (): void => {
76+
if (!disabled) {
77+
onValueChange?.();
78+
}
79+
};
80+
81+
return (
82+
<Pressable onPress={onPress}>
83+
<Animated.View
84+
style={[styles.track, trackAnimatedStyle]}
85+
onLayout={(e) => {
86+
height.value = e.nativeEvent.layout.height;
87+
width.value = e.nativeEvent.layout.width;
88+
}}>
89+
<Animated.View
90+
style={[
91+
styles.thumb,
92+
thumbAnimatedStyle,
93+
{ backgroundColor: thumbColor },
94+
]}
95+
/>
96+
</Animated.View>
97+
</Pressable>
98+
);
99+
};
100+
101+
const styles = StyleSheet.create({
102+
track: {
103+
alignItems: 'flex-start',
104+
height: defaultHeight,
105+
width: defaultWidth,
106+
padding: 4,
107+
},
108+
thumb: {
109+
height: '100%',
110+
aspectRatio: 1,
111+
},
112+
});
113+
114+
export default Switch;

src/components/SwitchRow.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import {
66
View,
77
ViewStyle,
88
} from 'react-native';
9-
import { Switch } from '../styles/components';
109
import { IThemeColors } from '../styles/themes';
10+
import Switch from '../components/Switch';
1111

1212
const SwitchRow = ({
1313
children,

src/screens/Settings/PIN/AskForBiometrics.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ import {
1515
} from 'react-native';
1616
import { useTranslation } from 'react-i18next';
1717

18-
import { Switch } from '../../../styles/components';
1918
import { BodyMSB, BodyM } from '../../../styles/text';
2019
import { FaceIdIcon, TouchIdIcon } from '../../../styles/icons';
2120
import BottomSheetNavigationHeader from '../../../components/BottomSheetNavigationHeader';
2221
import SafeAreaInset from '../../../components/SafeAreaInset';
2322
import GradientView from '../../../components/GradientView';
2423
import Button from '../../../components/buttons/Button';
24+
import Switch from '../../../components/Switch';
2525
import { IsSensorAvailableResult } from '../../../components/Biometrics';
2626
import { useAppDispatch } from '../../../hooks/redux';
2727
import { useBottomSheetScreenBackPress } from '../../../hooks/bottomSheet';

src/screens/Settings/PIN/Result.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import React, { memo, ReactElement, useMemo } from 'react';
22
import { StyleSheet, View, Pressable, Image } from 'react-native';
33
import { useTranslation } from 'react-i18next';
44

5-
import { Switch } from '../../../styles/components';
65
import { BodyM, BodyMSB } from '../../../styles/text';
76
import BottomSheetNavigationHeader from '../../../components/BottomSheetNavigationHeader';
87
import SafeAreaInset from '../../../components/SafeAreaInset';
98
import GradientView from '../../../components/GradientView';
109
import Button from '../../../components/buttons/Button';
10+
import Switch from '../../../components/Switch';
1111
import { useAppDispatch, useAppSelector } from '../../../hooks/redux';
1212
import { useBottomSheetScreenBackPress } from '../../../hooks/bottomSheet';
1313
import { closeSheet } from '../../../store/slices/ui';

src/screens/Wallets/Send/CoinSelection.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import { StyleSheet, View } from 'react-native';
33
import { BottomSheetScrollView } from '@gorhom/bottom-sheet';
44
import { useTranslation } from 'react-i18next';
55

6-
import { ScrollView, Switch } from '../../../styles/components';
6+
import { ScrollView } from '../../../styles/components';
77
import { Subtitle, BodyMSB, BodySSB, Caption13Up } from '../../../styles/text';
88
import GradientView from '../../../components/GradientView';
99
import BottomSheetNavigationHeader from '../../../components/BottomSheetNavigationHeader';
1010
import SafeAreaInset from '../../../components/SafeAreaInset';
1111
import Button from '../../../components/buttons/Button';
12+
import Switch from '../../../components/Switch';
1213
import Tag from '../../../components/Tag';
1314

1415
import useColors from '../../../hooks/colors';

src/styles/components.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
ColorValue,
33
Platform,
44
PressableProps,
5-
Switch as RNSwitch,
65
ScrollViewProps,
76
TouchableOpacity as RNTouchableOpacity,
87
TouchableHighlight as RNTouchableHighlight,
@@ -12,7 +11,6 @@ import {
1211
ViewProps,
1312
TextInput as RNTextInput,
1413
TextInputProps as RNTextInputProps,
15-
SwitchProps,
1614
} from 'react-native';
1715
import Color from 'color';
1816
import Animated, { AnimatedProps } from 'react-native-reanimated';
@@ -126,20 +124,6 @@ export const Pressable = styled(RNPressable)<PressableProps & ColorProps>(
126124
}),
127125
);
128126

129-
export const Switch = styled(RNSwitch).attrs<SwitchProps & ColorProps>(
130-
(props) => ({
131-
trackColor: {
132-
false: '#3A3A3C',
133-
true: props.color
134-
? props.theme.colors[props.color]
135-
: props.theme.colors.brand,
136-
},
137-
thumbColor: props.disabled ? '#A0A0A0' : 'white',
138-
ios_backgroundColor: '#3A3A3C',
139-
...props,
140-
}),
141-
)<SwitchProps & ColorProps>(() => ({}));
142-
143127
export const TextInput = styled(RNTextInput).attrs<TextInputProps>((props) => ({
144128
keyboardAppearance: props.theme.id,
145129
selectionColor: colors.brand,

0 commit comments

Comments
 (0)