Skip to content

Instantly share code, notes, and snippets.

@872409
Forked from jevakallio/HorizontalPicker.js
Created June 13, 2018 13:34
Show Gist options
  • Save 872409/36dd37593886c11c6bd97252dd2c2495 to your computer and use it in GitHub Desktop.
Save 872409/36dd37593886c11c6bd97252dd2c2495 to your computer and use it in GitHub Desktop.
React Native Horizontal Picker
// @flow
// https://twitter.com/jevakallio/status/941258932529614848
import React, { Component, type Node } from 'react';
import styled from 'styled-components/native';
import Touchable from 'react-native-platform-touchable';
import Carousel from 'react-native-snap-carousel';
// $FlowFixMe
import { LinearGradient } from 'expo';
const Container = styled.View``;
// react-native-snap-carousel doesn't support setting different opacities
// for inactive items based on their distance from the active item, so we'll
// fake it here by fading the view in and out with gradients
const StartGradient = styled(LinearGradient).attrs({
pointerEvents: 'none',
colors: ['rgba(242, 242, 242, 0.6)', 'rgba(242, 242, 242, 0.6)', 'rgba(242, 242, 242, 0)'],
start: [0, 0.8, 1],
end: [1, 0.8, 1]
})`
width: ${p => p.width + 10}px;
height: ${p => p.width}px;
position: absolute;
top: 0;
left: 0;
`;
const EndGradient = styled(LinearGradient).attrs({
pointerEvents: 'none',
colors: ['rgba(242, 242, 242, 0)', 'rgba(242, 242, 242, 0.6)', 'rgba(242, 242, 242, 0.6)'],
start: [0, 0.8, 1],
end: [1, 0.8, 1]
})`
width: ${p => p.width + 10}px;
height: ${p => p.width}px;
position: absolute;
top: 0;
right: 0;
`;
// show a ring around the focused item
const FocusCircle = styled.View.attrs({ pointerEvents: 'none' })`
position: absolute;
top: 0;
left: ${p => p.width * p.offset};
bottom: 0;
width: ${p => p.width};
height: ${p => p.width};
border-radius: ${p => p.width};
border-color: white;
border-width: 2;
`;
// when an item is touched, we should snap to it
const ItemWrapper = styled(Touchable).attrs({
activeOpacity: 0.8,
background: Touchable.SelectableBackgroundBorderless()
})`
background-color: transparent;
width: ${p => p.width}px;
height: ${p => p.width}px;
align-items: center;
justify-content: center;
`;
type Props<T> = {
onItemSelected: (item: T) => void,
renderItem: (item: T) => Node,
items: T[],
initialItem: T,
itemWidth: number,
visibleItemCount: number
};
export default class HorizontalPicker extends Component<Props<*>, *> {
carouselRef = null;
props: Props<*>;
static defaultProps = {
itemWidth: 60,
visibleItemCount: 5
};
moveToItem = (index: number) => {
if (this.carouselRef) {
this.carouselRef.snapToItem(index);
}
};
onSnapToItem = (index: number) => {
this.props.onItemSelected(this.props.items[index]);
};
renderItem = ({ item, index }: { item: *, index: number }) => {
return (
<ItemWrapper width={this.props.itemWidth} onPress={() => this.moveToItem(index)}>
{this.props.renderItem(item)}
</ItemWrapper>
);
};
render() {
const { items, initialItem, itemWidth, visibleItemCount } = this.props;
return (
<Container>
<Carousel
ref={ref => {
this.carouselRef = ref;
}}
data={items}
firstItem={items.indexOf(initialItem)}
renderItem={this.renderItem}
onSnapToItem={this.onSnapToItem}
sliderWidth={visibleItemCount * itemWidth}
itemWidth={itemWidth}
activeSlideOffset={10}
inactiveSlideScale={0.8}
inactiveSlideShift={-16}
/>
<StartGradient width={itemWidth} />
<FocusCircle width={itemWidth} offset={(visibleItemCount - 1) / 2} />
<EndGradient width={itemWidth} />
</Container>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment