Skip to content

Commit

Permalink
fix(ui): add custom Switch component (#2427)
Browse files Browse the repository at this point in the history
  • Loading branch information
pwltr authored Jan 9, 2025
1 parent ae22219 commit 41f33e8
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 27 deletions.
14 changes: 7 additions & 7 deletions src/components/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
import { SvgProps } from 'react-native-svg';
import isEqual from 'lodash/isEqual';

import { Switch } from '../styles/components';
import {
BodyM,
BodyMSB,
Expand All @@ -21,6 +20,7 @@ import {
Caption,
} from '../styles/text';
import { ChevronRight, Checkmark } from '../styles/icons';
import Switch from '../components/Switch';
import DraggableList from '../screens/Settings/PaymentPreference/DraggableList';

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

export type SwitchItem = {
type: EItemType.switch;
enabled: boolean;
title: string;
Icon?: React.FC<SvgProps>;
iconColor?: string;
enabled?: boolean;
disabled?: boolean;
hide?: boolean;
onPress?: () => void;
testID?: string;
onPress?: () => void;
};

export type ButtonItem = {
Expand All @@ -107,10 +107,10 @@ export type ButtonItem = {
iconColor?: string;
disabled?: boolean;
enabled?: boolean;
loading?: boolean;
hide?: boolean;
onPress?: () => void;
testID?: string;
loading?: boolean;
onPress?: () => void;
};

export type TextButtonItem = {
Expand All @@ -122,17 +122,17 @@ export type TextButtonItem = {
iconColor?: string;
enabled?: boolean;
hide?: boolean;
onPress?: () => void;
testID?: string;
onPress?: () => void;
};

export type DraggableItem = {
type: EItemType.draggable;
value: TItemDraggable[];
title: string;
hide?: boolean;
onDragEnd?: (data: TItemDraggable[]) => void;
testID?: string;
onDragEnd?: (data: TItemDraggable[]) => void;
};

const _Item = memo((item: ItemData): ReactElement => {
Expand Down
114 changes: 114 additions & 0 deletions src/components/Switch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { ReactElement } from 'react';
import { Pressable, StyleSheet } from 'react-native';
import Animated, {
Easing,
interpolate,
interpolateColor,
useAnimatedStyle,
useDerivedValue,
useSharedValue,
withTiming,
} from 'react-native-reanimated';

import colors from '../styles/colors';
import { IThemeColors } from '../styles/themes';

const duration = 300;
const defaultHeight = 32;
const defaultWidth = 52;

const Switch = ({
value,
disabled,
color,
onValueChange,
}: {
value: boolean;
disabled?: boolean;
color?: keyof IThemeColors;
onValueChange?: () => void;
}): ReactElement => {
const height = useSharedValue(defaultHeight);
const width = useSharedValue(defaultWidth);
const sharedValue = useDerivedValue(() => {
return value ? 1 : 0;
});

const thumbColor = disabled ? '#A0A0A0' : colors.white;
const trackColor = color ? colors[color] : colors.brand;
const trackColors = { on: trackColor, off: '#3A3A3C' };

const trackAnimatedStyle = useAnimatedStyle(() => {
const animatedColor = interpolateColor(
sharedValue.value,
[0, 1],
[trackColors.off, trackColors.on],
);
const colorValue = withTiming(animatedColor, {
duration,
easing: Easing.inOut(Easing.ease),
});

return {
backgroundColor: colorValue,
borderRadius: height.value / 2,
};
});

const thumbAnimatedStyle = useAnimatedStyle(() => {
const moveValue = interpolate(
sharedValue.value,
[0, 1],
[0, width.value - height.value],
);
const translateValue = withTiming(moveValue, {
duration,
easing: Easing.bezier(0.61, 0.46, 0.3, 1.07),
});

return {
transform: [{ translateX: translateValue }],
borderRadius: height.value / 2,
};
});

const onPress = (): void => {
if (!disabled) {
onValueChange?.();
}
};

return (
<Pressable onPress={onPress}>
<Animated.View
style={[styles.track, trackAnimatedStyle]}
onLayout={(e) => {
height.value = e.nativeEvent.layout.height;
width.value = e.nativeEvent.layout.width;
}}>
<Animated.View
style={[
styles.thumb,
thumbAnimatedStyle,
{ backgroundColor: thumbColor },
]}
/>
</Animated.View>
</Pressable>
);
};

const styles = StyleSheet.create({
track: {
alignItems: 'flex-start',
height: defaultHeight,
width: defaultWidth,
padding: 4,
},
thumb: {
height: '100%',
aspectRatio: 1,
},
});

export default Switch;
2 changes: 1 addition & 1 deletion src/components/SwitchRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
View,
ViewStyle,
} from 'react-native';
import { Switch } from '../styles/components';
import { IThemeColors } from '../styles/themes';
import Switch from '../components/Switch';

const SwitchRow = ({
children,
Expand Down
2 changes: 1 addition & 1 deletion src/screens/Settings/PIN/AskForBiometrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import {
} from 'react-native';
import { useTranslation } from 'react-i18next';

import { Switch } from '../../../styles/components';
import { BodyMSB, BodyM } from '../../../styles/text';
import { FaceIdIcon, TouchIdIcon } from '../../../styles/icons';
import BottomSheetNavigationHeader from '../../../components/BottomSheetNavigationHeader';
import SafeAreaInset from '../../../components/SafeAreaInset';
import GradientView from '../../../components/GradientView';
import Button from '../../../components/buttons/Button';
import Switch from '../../../components/Switch';
import { IsSensorAvailableResult } from '../../../components/Biometrics';
import { useAppDispatch } from '../../../hooks/redux';
import { useBottomSheetScreenBackPress } from '../../../hooks/bottomSheet';
Expand Down
2 changes: 1 addition & 1 deletion src/screens/Settings/PIN/Result.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import React, { memo, ReactElement, useMemo } from 'react';
import { StyleSheet, View, Pressable, Image } from 'react-native';
import { useTranslation } from 'react-i18next';

import { Switch } from '../../../styles/components';
import { BodyM, BodyMSB } from '../../../styles/text';
import BottomSheetNavigationHeader from '../../../components/BottomSheetNavigationHeader';
import SafeAreaInset from '../../../components/SafeAreaInset';
import GradientView from '../../../components/GradientView';
import Button from '../../../components/buttons/Button';
import Switch from '../../../components/Switch';
import { useAppDispatch, useAppSelector } from '../../../hooks/redux';
import { useBottomSheetScreenBackPress } from '../../../hooks/bottomSheet';
import { closeSheet } from '../../../store/slices/ui';
Expand Down
3 changes: 2 additions & 1 deletion src/screens/Wallets/Send/CoinSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { StyleSheet, View } from 'react-native';
import { BottomSheetScrollView } from '@gorhom/bottom-sheet';
import { useTranslation } from 'react-i18next';

import { ScrollView, Switch } from '../../../styles/components';
import { ScrollView } from '../../../styles/components';
import { Subtitle, BodyMSB, BodySSB, Caption13Up } from '../../../styles/text';
import GradientView from '../../../components/GradientView';
import BottomSheetNavigationHeader from '../../../components/BottomSheetNavigationHeader';
import SafeAreaInset from '../../../components/SafeAreaInset';
import Button from '../../../components/buttons/Button';
import Switch from '../../../components/Switch';
import Tag from '../../../components/Tag';

import useColors from '../../../hooks/colors';
Expand Down
16 changes: 0 additions & 16 deletions src/styles/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
ColorValue,
Platform,
PressableProps,
Switch as RNSwitch,
ScrollViewProps,
TouchableOpacity as RNTouchableOpacity,
TouchableHighlight as RNTouchableHighlight,
Expand All @@ -12,7 +11,6 @@ import {
ViewProps,
TextInput as RNTextInput,
TextInputProps as RNTextInputProps,
SwitchProps,
} from 'react-native';
import Color from 'color';
import Animated, { AnimatedProps } from 'react-native-reanimated';
Expand Down Expand Up @@ -126,20 +124,6 @@ export const Pressable = styled(RNPressable)<PressableProps & ColorProps>(
}),
);

export const Switch = styled(RNSwitch).attrs<SwitchProps & ColorProps>(
(props) => ({
trackColor: {
false: '#3A3A3C',
true: props.color
? props.theme.colors[props.color]
: props.theme.colors.brand,
},
thumbColor: props.disabled ? '#A0A0A0' : 'white',
ios_backgroundColor: '#3A3A3C',
...props,
}),
)<SwitchProps & ColorProps>(() => ({}));

export const TextInput = styled(RNTextInput).attrs<TextInputProps>((props) => ({
keyboardAppearance: props.theme.id,
selectionColor: colors.brand,
Expand Down

0 comments on commit 41f33e8

Please sign in to comment.