Created
July 15, 2017 18:32
-
-
Save lpillonel/cd5c2ffc3a402ffc022ac061b54b6a85 to your computer and use it in GitHub Desktop.
SlidePicker
This file contains 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, { PureComponent } from 'react' | |
import { PanResponder, Animated, View } from 'react-native' | |
import styled from 'styled-components/native' | |
const PRIMARY_COLOR = '#F9B120' | |
const SECONDARY_COLOR = 'rgb(26,126,248)' | |
const BACKGROUND_COLOR = '#F6F6F6' | |
const TEXT_COLOR = '#888888' | |
const DATA = [ | |
{ | |
title: 'Question 1', | |
values: [ | |
{ value: 3, label: "We gonna say a lot" }, | |
{ value: 2, label: "Often" }, | |
{ value: 1, label: "Once" }, | |
{ value: 0, label: "Never" }, | |
] | |
}, | |
{ | |
title: 'Sed feugiat, est quis hendrerit condimentum, eros massa euismod purus.', | |
values: [ | |
{ value: 10, label: "Romerus is rulis" }, | |
{ value: 7, label: "Phasellus bibendum blandit" }, | |
{ value: 5, label: "This build could be faster" }, | |
{ value: 4, label: "Nulla in felis non." }, | |
{ value: 3, label: "Custom Phone" }, | |
] | |
} | |
] | |
const TOOLBAR_HEIGHT = 56 | |
const SPACE_BOTTOM = 70 | |
const SPACE_TOP = 120 | |
const LABEL_HEIGHT = 22 | |
const STROKE_LENGTH = 75 | |
const MainView = styled.View` | |
flex: 1; | |
background-color: ${BACKGROUND_COLOR}; | |
` | |
const Title = styled.Text` | |
position: absolute; | |
top: 40; | |
right: 50; | |
left: ${STROKE_LENGTH / 2}; | |
color: ${TEXT_COLOR}; | |
` | |
const LabelsView = styled.View` | |
position: absolute; | |
left: 0; | |
bottom: ${SPACE_BOTTOM}; | |
width: 100%; | |
` | |
const LabelView = styled.View` | |
position: absolute; | |
left: 0; | |
top: ${({top}) => top}; | |
` | |
const LabelMark = styled.View` | |
width: ${STROKE_LENGTH}; | |
border-bottom-color: ${({ color }) => color}; | |
border-bottom-width: 2; | |
position: absolute; | |
left: 0; | |
top: 0; | |
` | |
const LabelText = styled.Text` | |
color: ${({ color }) => color}; | |
top: 5; | |
left: ${STROKE_LENGTH - 10}; | |
` | |
const KnobAnimated = Animated.createAnimatedComponent(styled.View` | |
background-color: ${PRIMARY_COLOR}; | |
width: 100%; | |
position: absolute; | |
bottom: 0; | |
left: 0; | |
overflow: hidden; | |
`) | |
const ButtonView = styled.View` | |
position: absolute; | |
bottom: 40; | |
right: 40; | |
width: 60; | |
` | |
const Button = styled.Button.attrs({ | |
color: SECONDARY_COLOR, | |
})`` | |
export default class SlidePicker extends PureComponent { | |
constructor (...props) { | |
super(...props) | |
// Functions binding | |
this.setLayout = this.setLayout.bind(this) | |
this.goToNextStep = this.goToNextStep.bind(this) | |
} | |
state = { | |
step: 0, | |
viewHeight: null, | |
knobHeight: new Animated.Value(0), | |
} | |
labelHeight = null | |
panResponder = PanResponder.create({ | |
onStartShouldSetPanResponder: () => true, | |
onPanResponderMove: Animated.event([null, { | |
moveY: this.state.knobHeight, | |
}]), | |
onPanResponderGrant: (e, gestureState) => { | |
Animated.timing(this.state.knobHeight, { | |
toValue: gestureState.y0, | |
duration: 100, | |
}).start() | |
}, | |
onPanResponderRelease: (e, gestureState) => { | |
// Get end value | |
const endValue = gestureState.moveY || gestureState.y0 | |
// Compute target value | |
let val = Math.round((endValue - SPACE_TOP - TOOLBAR_HEIGHT) / this.labelHeight) * this.labelHeight + SPACE_TOP + TOOLBAR_HEIGHT | |
// Check val is not after last one | |
if (val >= this.labelHeight * DATA[this.state.step].values.length + SPACE_TOP + TOOLBAR_HEIGHT) { | |
val = this.state.viewHeight + TOOLBAR_HEIGHT | |
} | |
Animated.timing(this.state.knobHeight, { | |
toValue: val, | |
duration: 500, | |
}).start() | |
}, | |
}) | |
setLayout (event, init = true, step = this.state.step) { | |
const height = event ? event.nativeEvent.layout.height : this.state.viewHeight | |
if (init === true) { | |
this.state.knobHeight.setValue(height + TOOLBAR_HEIGHT) | |
} else { | |
Animated.timing(this.state.knobHeight, { | |
toValue: this.state.viewHeight + TOOLBAR_HEIGHT, | |
duration: 100, | |
}).start() | |
} | |
const spaceBetween = (height - SPACE_TOP - SPACE_BOTTOM - ( LABEL_HEIGHT * DATA[step].values.length )) / (DATA[step].values.length - 1) | |
this.labelHeight = spaceBetween + LABEL_HEIGHT | |
if (event) { | |
this.setState({ | |
viewHeight: event.nativeEvent.layout.height, | |
}) | |
} | |
} | |
goToNextStep () { | |
const nextStep = this.state.step < DATA.length - 1 ? this.state.step + 1 : 0 | |
this.setState({ | |
step: nextStep, | |
}) | |
this.setLayout(null, false, nextStep) | |
} | |
renderDraggable () { | |
if ( ! this.state.viewHeight) { | |
return false | |
} | |
return ( | |
<KnobAnimated | |
style={{ | |
height: this.state.knobHeight.interpolate({ | |
inputRange: [ | |
0, | |
SPACE_TOP + TOOLBAR_HEIGHT, | |
this.state.viewHeight + TOOLBAR_HEIGHT - SPACE_BOTTOM, | |
this.state.viewHeight + TOOLBAR_HEIGHT, | |
], | |
outputRange: [ | |
this.state.viewHeight - SPACE_TOP, | |
this.state.viewHeight - SPACE_TOP, | |
SPACE_BOTTOM, | |
0, | |
], | |
}), | |
}} | |
> | |
{ this.renderScale({ | |
markColor: '#FFFFFF', | |
textColor: '#FFFFFF', | |
}) } | |
</KnobAnimated> | |
) | |
} | |
renderScale ({ | |
markColor = PRIMARY_COLOR, | |
textColor = TEXT_COLOR, | |
} = {}) { | |
if ( ! this.state.viewHeight) { | |
return false | |
} | |
const labels = DATA[this.state.step].values.map((step, index) => { | |
return ( | |
<LabelView key={step.value} top={index * this.labelHeight}> | |
<LabelMark color={markColor} /> | |
<LabelText color={textColor}>{step.label}</LabelText> | |
</LabelView> | |
) | |
}) | |
return ( | |
<LabelsView height={this.state.viewHeight - SPACE_TOP - SPACE_BOTTOM}> | |
{ labels } | |
</LabelsView> | |
) | |
} | |
render () { | |
return ( | |
<MainView {...this.panResponder.panHandlers} onLayout={ this.setLayout }> | |
<Title>{ DATA[this.state.step].title }</Title> | |
{ this.renderScale() } | |
{ this.renderDraggable() } | |
<ButtonView> | |
<Button onPress={this.goToNextStep} title="Next" /> | |
</ButtonView> | |
</MainView> | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment