Skip to content
This repository has been archived by the owner on Mar 4, 2019. It is now read-only.

Commit

Permalink
Feat: Added Picker component
Browse files Browse the repository at this point in the history
Summary: Picker implementation for each platform. Will be used for example as nationality selector in passenger section of booking screen.
  • Loading branch information
JosefDuda authored and RobinCsl committed Feb 21, 2019
1 parent ea67a5e commit 4bbfed9
Show file tree
Hide file tree
Showing 12 changed files with 593 additions and 0 deletions.
39 changes: 39 additions & 0 deletions src/Picker/NativePicker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// @flow

import * as React from 'react';
import { Picker as RNPicker } from 'react-native';

import type { PickerOption } from './PickerTypes';
import type { StylePropType } from '../PlatformStyleSheet/StyleTypes';

export type Props = {|
+optionsData: $ReadOnlyArray<PickerOption>,
+onValueChange: (value: string) => void,
+selectedValue: string,
+style: StylePropType,
|};

export default function NativePicker({
optionsData,
selectedValue,
style,
onValueChange,
}: Props) {
const pickerOptions = optionsData.map(option => (
<RNPicker.Item
key={option.value}
label={option.label}
value={option.value}
/>
));

return (
<RNPicker
selectedValue={selectedValue}
style={style}
onValueChange={onValueChange}
>
{pickerOptions}
</RNPicker>
);
}
67 changes: 67 additions & 0 deletions src/Picker/Picker.android.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// @flow

import * as React from 'react';
import { View } from 'react-native';
import { defaultTokens } from '@kiwicom/orbit-design-tokens';

import { Text } from '../Text';
import { Icon } from '../Icon';
import { StyleSheet } from '../PlatformStyleSheet';
import type { Props } from './PickerTypes';
import { getSelectedLabel } from './PickerHelpers';
import NativePicker from './NativePicker';

const Picker = ({
optionsData,
selectedValue,
onValueChange,
placeholder = '',
iconName,
}: Props) => {
const selectedLabel = getSelectedLabel(
optionsData,
selectedValue,
placeholder,
);

return (
<View style={styles.container}>
<Text style={styles.label}>{selectedLabel}</Text>
<Icon name={iconName ?? 'chevron-right'} style={styles.icon} />
<NativePicker
optionsData={optionsData}
selectedValue={selectedValue}
style={styles.picker}
onValueChange={onValueChange}
/>
</View>
);
};

const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
width: '100%',
height: parseInt(defaultTokens.heightInputNormal, 10),
borderRadius: parseInt(defaultTokens.borderRadiusLarge, 10),
backgroundColor: defaultTokens.backgroundButtonSecondary,
},
label: {
flex: 1,
color: defaultTokens.colorTextAttention,
paddingLeft: parseInt(defaultTokens.spaceSmall, 10),
},
icon: {
marginRight: 10,
},
picker: {
position: 'absolute',
left: 0,
width: '100%',
opacity: 0, // NOTE: This workaround is required because picker label cannot be currently styled on Android
height: parseInt(defaultTokens.heightInputNormal, 10),
},
});

export default Picker;
139 changes: 139 additions & 0 deletions src/Picker/Picker.ios.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// @flow

import * as React from 'react';
import { View } from 'react-native';
import { defaultTokens } from '@kiwicom/orbit-design-tokens';

import { Button } from '../Button';
import { Icon } from '../Icon';
import { Modal } from '../Modal';
import { StyleSheet } from '../PlatformStyleSheet';
import type { Props } from './PickerTypes';
import { getSelectedLabel } from './PickerHelpers';
import NativePicker from './NativePicker';

export type State = {|
open: boolean,
selectedValue: string,
pickerValue: string,
|};

export default class Picker extends React.Component<Props, State> {
static defaultProps = {
placeholder: '',
};

constructor(props: Props) {
super(props);

const { selectedValue } = this.props;

this.state = {
open: false,
selectedValue,
pickerValue: selectedValue,
};
}

componentDidUpdate = (prevProps: Props) => {
if (
this.props.selectedValue !== prevProps.selectedValue &&
this.props.selectedValue !== this.state.selectedValue
) {
this.setState({
selectedValue: this.props.selectedValue,
pickerValue: this.props.selectedValue,
});
}
};

handleOpenPress = () => {
this.setState({
open: true,
});
};

handleModalClose = () => {
this.setState({
open: false,
});
};

handlePickerValueChange = (value: string) => {
this.setState({ pickerValue: value });
};

handleOkPress = () => {
const { pickerValue } = this.state;
const { onValueChange } = this.props;
this.setState({
open: false,
selectedValue: pickerValue,
});
onValueChange(pickerValue);
};

render() {
const { open, selectedValue, pickerValue } = this.state;
const { optionsData, confirmLabel, placeholder, iconName } = this.props;
const selectedLabel = getSelectedLabel(
optionsData,
selectedValue,
placeholder,
);

return (
<View>
<Button
onPress={this.handleOpenPress}
type="secondary"
label={selectedLabel}
rightIcon={<Icon name={iconName ?? 'chevron-right'} />}
/>
<Modal
isVisible={open}
style={styles.modal}
onRequestClose={this.handleModalClose}
onBackdropPress={this.handleModalClose}
>
<View style={styles.container}>
<NativePicker
optionsData={optionsData}
selectedValue={pickerValue}
style={styles.picker}
onValueChange={this.handlePickerValueChange}
/>
<View style={styles.confirmContainer}>
<Button
onPress={this.handleOkPress}
type="secondary"
label={confirmLabel}
/>
</View>
</View>
</Modal>
</View>
);
}
}

const styles = StyleSheet.create({
modal: {
margin: 0,
justifyContent: 'flex-end',
},
container: {
width: '100%',
backgroundColor: defaultTokens.backgroundModal,
borderTopStartRadius: parseInt(defaultTokens.borderRadiusLarge, 10),
borderTopEndRadius: parseInt(defaultTokens.borderRadiusLarge, 10),
paddingTop: parseInt(defaultTokens.spaceXXSmall, 10),
paddingBottom: parseInt(defaultTokens.spaceXXLarge, 10),
},
picker: {
width: '100%',
},
confirmContainer: {
marginHorizontal: parseInt(defaultTokens.spaceXSmall, 10),
},
});
30 changes: 30 additions & 0 deletions src/Picker/Picker.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
name: Picker
menu: Components
route: /picker
---

import { Playground, PropsTable } from 'docz'
import { View } from 'react-native'
import { Picker } from '.'

# Picker

## Properties

<PropsTable of={Picker} />

## Basic usage

<Playground>
<Picker
optionsData={[
{ label: 'Option A', value: 'a' },
{ label: 'Option B', value: 'b' },
]}
selectedValue={'a'}
onValueChange={(value) => {}}
/>
</Playground>


47 changes: 47 additions & 0 deletions src/Picker/Picker.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// @flow

import * as React from 'react';
import { Platform } from 'react-native';
import { storiesOf } from '@storybook/react-native';
import { select, withKnobs } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';

import icons from '../Icon/icons.json';

import { Picker } from '.';

const optionsData = [
{ label: 'Option A', value: 'a' },
{ label: 'Option B', value: 'b' },
{ label: 'Option C', value: 'c' },
{ label: 'Option D', value: 'd' },
{ label: 'Option E', value: 'e' },
{ label: 'Option F', value: 'f' },
];
const onValueChange = action('onValueChange');

storiesOf('Picker', module)
.addDecorator(withKnobs)
.add('Playground', () => {
const selected = select(
'selectedValue',
optionsData.map(data => data.value),
optionsData[3].value,
);
const iconName = select(
'Icon name',
[...Object.keys(icons)],
Platform.OS === 'web' ? 'chevron-down' : 'chevron-right',
);

return (
<Picker
optionsData={optionsData}
selectedValue={selected}
onValueChange={onValueChange}
placeholder="Select option"
confirmLabel="OK"
iconName={iconName}
/>
);
});
Loading

0 comments on commit 4bbfed9

Please sign in to comment.