Last active
April 9, 2020 21:44
-
-
Save Grohden/11640fdca517e218d629626d2917731a to your computer and use it in GitHub Desktop.
Slider prototype for react native
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable no-magic-numbers */ | |
import React from 'react' | |
import { StyleSheet, Text, View, ViewStyle } from 'react-native' | |
import { BaseColors } from '@constants/colors' | |
import fonts from '@constants/fonts' | |
type Props = { | |
style: ViewStyle | |
} | |
const LABEL_DIMEN = 32 | |
// hypotenuse value (and floor | 0) | |
export const LABEL_HEIGHT = Math.sqrt((LABEL_DIMEN ** 2) * 2) | 0 | |
const styles = StyleSheet.create({ | |
container: { | |
transform: [ | |
{ rotate: '-45deg' } | |
], | |
alignItems: 'center', | |
justifyContent: 'center', | |
backgroundColor: BaseColors.primary, | |
width: LABEL_DIMEN, | |
height: LABEL_DIMEN, | |
borderTopLeftRadius: LABEL_DIMEN, | |
borderTopRightRadius: LABEL_DIMEN, | |
borderBottomRightRadius: LABEL_DIMEN, | |
borderBottomLeftRadius: 0 | |
}, | |
font: { | |
...fonts.white.caption, | |
transform: [ | |
{ rotate: '45deg' } | |
] | |
} | |
}) | |
export const Label = (props: Props) => ( | |
<View style={ [ | |
styles.container, | |
props.style | |
] }> | |
<Text style={ styles.font }>0%</Text> | |
</View> | |
) | |
export default Label |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import { | |
Platform, | |
StyleSheet, | |
TouchableHighlight, | |
View, | |
ViewStyle | |
} from 'react-native' | |
export const MARKER_DIMENSION = Platform.select({ | |
default: 30, | |
android: 12 | |
}) | |
const styles = StyleSheet.create({ | |
markerStyle: { | |
...Platform.select({ | |
default: { | |
height: MARKER_DIMENSION, | |
width: MARKER_DIMENSION, | |
borderRadius: MARKER_DIMENSION, | |
borderWidth: 1, | |
borderColor: '#DDDDDD', | |
backgroundColor: '#FFFFFF', | |
shadowColor: '#000000', | |
shadowOffset: { | |
width: 0, | |
height: 3 | |
}, | |
shadowRadius: 1, | |
shadowOpacity: 0.2 | |
}, | |
android: { | |
height: MARKER_DIMENSION, | |
width: MARKER_DIMENSION, | |
borderRadius: MARKER_DIMENSION, | |
backgroundColor: '#0D8675' | |
} | |
}) | |
}, | |
pressedMarkerStyle: { | |
...Platform.select({ | |
default: {}, | |
android: { | |
height: 20, | |
width: 20, | |
borderRadius: 20 | |
} | |
}) | |
}, | |
disabled: { | |
backgroundColor: '#d3d3d3' | |
} | |
}) | |
type Props = { | |
enabled?: boolean | |
pressed?: boolean | |
markerStyle?: ViewStyle | |
pressedMarkerStyle?: ViewStyle | |
disabledMarkerStyle?: ViewStyle | |
} | |
const Marker = (props: Props) => { | |
const style = props.enabled | |
? [ | |
styles.markerStyle, | |
props.markerStyle, | |
props.pressed && styles.pressedMarkerStyle, | |
props.pressed && props.pressedMarkerStyle | |
] | |
: [ | |
styles.markerStyle, | |
styles.disabled, | |
props.disabledMarkerStyle | |
] | |
return ( | |
<TouchableHighlight> | |
<View style={ style } /> | |
</TouchableHighlight> | |
) | |
} | |
export default Marker |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable no-magic-numbers */ | |
import React, { useCallback, useState } from 'react' | |
import { | |
LayoutChangeEvent, | |
Platform, | |
StyleSheet, View | |
} from 'react-native' | |
import Marker, { MARKER_DIMENSION } from './Marker' | |
import XDraggable from '@components/filter/slider/XDraggable' | |
import Animated, { | |
call, | |
Extrapolate, | |
interpolate, | |
max, | |
min, | |
round, | |
sub, | |
useCode, | |
Value | |
} from 'react-native-reanimated' | |
import Label, { LABEL_HEIGHT } from '@components/filter/slider/Label' | |
type Values = [number, number] | |
type Props = { | |
values: Values | |
min: number | |
max: number | |
step: number | |
onValuesChange: (values: Values) => void | |
onValuesChangeFinish?: (values: Values) => void | |
} | |
const styles = StyleSheet.create({ | |
container: { | |
width: '100%', | |
height: MARKER_DIMENSION + 14 | |
}, | |
fullTrack: { | |
width: '100%', | |
height: 30, | |
paddingHorizontal: 24, | |
position: 'relative', | |
flexDirection: 'row', | |
alignContent: 'center' | |
}, | |
draggable: { | |
position: 'absolute' | |
}, | |
track: { | |
position: 'absolute', | |
// FIXME: need to fix position | |
// needs to be 50% - (height/2) | |
top: '50%', | |
...Platform.select({ | |
ios: { | |
height: 2, | |
borderRadius: 2 | |
}, | |
android: { | |
height: 2 | |
} | |
}) | |
}, | |
mainTrack: { | |
left: 0, | |
right: 0, | |
...Platform.select({ | |
default: { | |
backgroundColor: '#A7A7A7' | |
}, | |
android: { | |
backgroundColor: '#CECECE' | |
} | |
}) | |
}, | |
selectedTrack: { | |
...Platform.select({ | |
default: { | |
backgroundColor: '#095FFF' | |
}, | |
android: { | |
backgroundColor: '#0D8675' | |
} | |
}) | |
} | |
}) | |
const HALF_MARKER = (MARKER_DIMENSION / 2) | |
const Slider = (props: Props) => { | |
const [firstValue] = useState(() => new Value(0 as number)) | |
const [secondValue] = useState(() => new Value(0 as number)) | |
const [measure, setMeasure] = useState< | |
{ width: number, height: number } | null | |
>(null) | |
useCode(() => { | |
if(!measure) { | |
return false | |
} | |
const interpolateConfig = ({ | |
inputRange: [0, measure.width - HALF_MARKER], | |
outputRange: [0, 100], | |
extrapolate: Extrapolate.CLAMP | |
}) | |
const notifyDeps = [firstValue, secondValue] | |
.map(it => round( | |
interpolate(it, interpolateConfig) | |
)) | |
return call(notifyDeps, ([first, second]) => { | |
console.log(first, second) | |
}) | |
}, [firstValue, secondValue, measure]) | |
const renderMarkers = () => { | |
if(!measure) { | |
return | |
} | |
const max = measure.width - HALF_MARKER | |
// eslint-disable-next-line no-magic-numbers | |
return ( | |
<> | |
<XDraggable | |
value={ firstValue } | |
style={ styles.draggable } | |
axisMax={ max } | |
axisMin={ 0 }> | |
<Label | |
style={ { | |
position: 'absolute', | |
right: -1, | |
bottom: LABEL_HEIGHT | |
} } | |
/> | |
<Marker | |
enabled | |
pressed={ false } | |
/> | |
</XDraggable> | |
<XDraggable | |
value={ secondValue } | |
style={ styles.draggable } | |
axisMax={ max } | |
axisMin={ 0 }> | |
<Marker | |
enabled | |
pressed={ false } | |
/> | |
</XDraggable> | |
</> | |
) | |
} | |
const handleLayout = useCallback((event: LayoutChangeEvent) => { | |
const { height, width } = event.nativeEvent.layout | |
setMeasure({ width, height }) | |
}, []) | |
return ( | |
<View style={ styles.container }> | |
<View | |
style={ styles.fullTrack }> | |
<View | |
onLayout={ handleLayout } | |
style={ StyleSheet.absoluteFill }> | |
<View style={ [styles.track, styles.mainTrack] } /> | |
</View> | |
<Animated.View | |
style={ [ | |
styles.track, | |
styles.selectedTrack, | |
{ | |
left: min(firstValue, secondValue), | |
width: sub( | |
max(firstValue, secondValue), | |
min(firstValue, secondValue), | |
) | |
} | |
] } | |
/> | |
{ renderMarkers() } | |
</View> | |
</View> | |
) | |
} | |
export default Slider |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable no-magic-numbers */ | |
import React, { useState } from 'react' | |
import { | |
PanGestureHandler, | |
PanGestureHandlerGestureEvent, | |
State | |
} from 'react-native-gesture-handler' | |
import Animated, { | |
add, | |
block, | |
cond, | |
eq, | |
event, | |
greaterOrEq, | |
lessOrEq, | |
proc, | |
set, | |
Value | |
} from 'react-native-reanimated' | |
import { ViewStyle } from 'react-native' | |
type Props = { | |
style?: ViewStyle | |
children: React.ReactNode | |
value: Animated.Value<number> | |
axisMax: number | |
axisMin: number | |
} | |
const clamp = proc(( | |
value: Animated.Adaptable<number>, | |
min: Animated.Adaptable<number>, | |
max: Animated.Adaptable<number> | |
) => cond( | |
lessOrEq(value, min), | |
min, | |
cond(greaterOrEq(value, max), max, value)), | |
) | |
const XDraggable = (props: Props) => { | |
const [offsetX] = useState(() => new Value(0)) | |
const handlePan = event([{ | |
nativeEvent: ({ | |
translationX: x, | |
state | |
}: PanGestureHandlerGestureEvent['nativeEvent']) => { | |
return block([ | |
set(props.value, clamp( | |
add(x, offsetX), | |
props.axisMin, | |
props.axisMax | |
)), | |
cond(eq(state, State.END), [ | |
set(offsetX, add(offsetX, x)) | |
]) | |
]) | |
} | |
}]) | |
return ( | |
<PanGestureHandler | |
maxPointers={ 1 } | |
onGestureEvent={ handlePan } | |
onHandlerStateChange={ handlePan }> | |
<Animated.View | |
style={ [{ | |
transform: [{ | |
translateX: props.value | |
}] | |
}, props.style] }> | |
{ props.children } | |
</Animated.View> | |
</PanGestureHandler> | |
) | |
} | |
export default XDraggable |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment