Skip to content

Instantly share code, notes, and snippets.

@lpillonel
Created July 15, 2017 18:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lpillonel/cd5c2ffc3a402ffc022ac061b54b6a85 to your computer and use it in GitHub Desktop.
Save lpillonel/cd5c2ffc3a402ffc022ac061b54b6a85 to your computer and use it in GitHub Desktop.
SlidePicker
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