Skip to content

Instantly share code, notes, and snippets.

@jevakallio
Created December 14, 2017 12:06
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save jevakallio/f9d704d36786b9d5dae49028f0238f67 to your computer and use it in GitHub Desktop.
Save jevakallio/f9d704d36786b9d5dae49028f0238f67 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>
);
}
}
@lindskogen
Copy link

Thank you very much for this gist! You saved me from needing to do real programming!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment