From 5f4611d65e40eae3ca6191a15f68d69ea5a1c4cb Mon Sep 17 00:00:00 2001 From: Kirill Rogovoy Date: Tue, 20 Jul 2021 21:24:52 +0300 Subject: WIP --- app_expo/components/switch/switch.props.ts | 39 ++++++++++ app_expo/components/switch/switch.story.tsx | 116 ++++++++++++++++++++++++++++ app_expo/components/switch/switch.tsx | 114 +++++++++++++++++++++++++++ 3 files changed, 269 insertions(+) create mode 100644 app_expo/components/switch/switch.props.ts create mode 100644 app_expo/components/switch/switch.story.tsx create mode 100644 app_expo/components/switch/switch.tsx (limited to 'app_expo/components/switch') diff --git a/app_expo/components/switch/switch.props.ts b/app_expo/components/switch/switch.props.ts new file mode 100644 index 0000000..2549a95 --- /dev/null +++ b/app_expo/components/switch/switch.props.ts @@ -0,0 +1,39 @@ +import { StyleProp, ViewStyle } from 'react-native' + +export interface SwitchProps { + /** + * On or off. + */ + value?: boolean + /** + * Fires when the on/off switch triggers. + * + * @param newValue The new value we're switching to. + */ + onToggle?: (newValue: boolean) => void + + /** + * A style override to apply to the container. Useful for margins and paddings. + */ + style?: StyleProp + + /** + * Additional track styling when on. + */ + trackOnStyle?: StyleProp + + /** + * Additional track styling when off. + */ + trackOffStyle?: StyleProp + + /** + * Additional thumb styling when on. + */ + thumbOnStyle?: StyleProp + + /** + * Additional thumb styling when off. + */ + thumbOffStyle?: StyleProp +} diff --git a/app_expo/components/switch/switch.story.tsx b/app_expo/components/switch/switch.story.tsx new file mode 100644 index 0000000..b10f8c6 --- /dev/null +++ b/app_expo/components/switch/switch.story.tsx @@ -0,0 +1,116 @@ +/* eslint-disable react-native/no-inline-styles */ +/* eslint-disable react-native/no-color-literals */ + +import * as React from 'react' +import { View, ViewStyle } from 'react-native' +import { storiesOf } from '@storybook/react-native' +import { StoryScreen, Story, UseCase } from '../../../storybook/views' +import { Toggle } from 'react-powerplug' +import { Switch } from './switch' + +declare let module + +const styleArray: ViewStyle[] = [{ borderColor: '#686868' }] + +const trackOffStyle: ViewStyle[] = [ + { backgroundColor: '#686868' }, + { + height: 80, + borderRadius: 0, + }, +] +const trackOnStyle: ViewStyle[] = [ + { + backgroundColor: '#b1008e', + borderColor: '#686868', + }, + { + height: 80, + borderRadius: 0, + }, +] +const thumbOffStyle: ViewStyle[] = [ + { + backgroundColor: '#b1008e', + borderColor: '#686868', + }, + { + height: 80, + borderRadius: 0, + }, +] +const thumbOnStyle: ViewStyle[] = [ + { backgroundColor: '#f0c' }, + { + height: 80, + borderRadius: 0, + borderColor: '#686868', + }, +] + +storiesOf('Switch', module) + .addDecorator((fn) => {fn()}) + .add('Behaviour', () => ( + + + + {({ on, toggle }) => } + + + + + + + + + + )) + .add('Styling', () => ( + + + + {({ on, toggle }) => ( + + + + )} + + + + + + {({ on, toggle }) => ( + + + + )} + + + + )) diff --git a/app_expo/components/switch/switch.tsx b/app_expo/components/switch/switch.tsx new file mode 100644 index 0000000..845d964 --- /dev/null +++ b/app_expo/components/switch/switch.tsx @@ -0,0 +1,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(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(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 ( + + + + + + ) +} -- cgit v1.2.3