rn-try-n-buy-abfrl
Version:
Virtual try-on plugin for ABFRL
587 lines (521 loc) • 15.4 kB
JavaScript
import React, {PureComponent, } from 'react';
import {
Keyboard,
Modal,
Platform,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import PropTypes from 'prop-types';
import {Picker} from '@react-native-picker/picker';
import {defaultStyles} from './NewPickerStyle';
import SceneUtils from '../utils/SceneUtils';
export default class RNPickerSelect extends PureComponent {
static propTypes = {
onValueChange: PropTypes.func.isRequired,
items: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.any.isRequired,
inputLabel: PropTypes.string,
key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
color: PropTypes.string,
}),
).isRequired,
value: PropTypes.any, // eslint-disable-line react/forbid-prop-types
placeholder: PropTypes.shape({
label: PropTypes.string,
value: PropTypes.any,
key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
color: PropTypes.string,
}),
disabled: PropTypes.bool,
itemKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
style: PropTypes.shape({}),
children: PropTypes.any, // eslint-disable-line react/forbid-prop-types
onOpen: PropTypes.func,
useNativeAndroidPickerStyle: PropTypes.bool,
fixAndroidTouchableBug: PropTypes.bool,
// Custom Modal props (iOS only)
doneText: PropTypes.string,
onDonePress: PropTypes.func,
onUpArrow: PropTypes.func,
onDownArrow: PropTypes.func,
onClose: PropTypes.func,
// Modal props (iOS only)
modalProps: PropTypes.shape({}),
// TextInput props
textInputProps: PropTypes.shape({}),
// Picker props
pickerProps: PropTypes.shape({}),
// Touchable Done props (iOS only)
touchableDoneProps: PropTypes.shape({}),
// Touchable wrapper props
touchableWrapperProps: PropTypes.shape({}),
// Custom Icon
Icon: PropTypes.func,
InputAccessoryView: PropTypes.func,
};
static defaultProps = {
value: 100,
placeholder: {
label: 'Select an item...',
value: null,
color: '#9EA0A4',
},
disabled: false,
itemKey: null,
style: {},
children: null,
useNativeAndroidPickerStyle: true,
fixAndroidTouchableBug: false,
doneText: 'Done',
onDonePress: null,
onUpArrow: null,
onDownArrow: null,
onOpen: null,
onClose: null,
modalProps: {},
textInputProps: {},
pickerProps: {},
touchableDoneProps: {},
touchableWrapperProps: {},
Icon: null,
InputAccessoryView: null,
};
static handlePlaceholder({placeholder}) {
if (placeholder !== {}) {
return [];
}
return [placeholder];
}
static getSelectedItem({items, key, value}) {
let idx = items.findIndex((item) => {
if (item.key && key) {
return item.key == key;
}
return item.value == value;
});
if (idx === -1) {
idx = 0;
}
return {
selectedItem: items[idx] || {},
idx,
};
}
static getDerivedStateFromProps(nextProps, prevState) {
// update items if items or placeholder prop changes
const items = RNPickerSelect.handlePlaceholder({
placeholder: nextProps.placeholder,
}).concat(nextProps.items);
const itemsChanged = prevState.items !== items;
// update selectedItem if value prop is defined and differs from currently selected item
const {selectedItem, idx} = RNPickerSelect.getSelectedItem({
items,
key: nextProps.itemKey,
value: nextProps.value,
});
const selectedItemChanged =
nextProps.value !== undefined &&
prevState.selectedItem !== selectedItem;
if (itemsChanged || selectedItemChanged) {
if (selectedItemChanged) {
nextProps.onValueChange(selectedItem.value, idx);
}
return {
...(itemsChanged ? {items} : {}),
...(selectedItemChanged ? {selectedItem} : {}),
};
}
return null;
}
constructor(props) {
super(props);
const items = RNPickerSelect.handlePlaceholder({
placeholder: props.placeholder,
}).concat(props.items);
const {selectedItem} = RNPickerSelect.getSelectedItem({
items,
key: props.itemKey,
value: props.value,
});
this.state = {
items,
selectedItem,
showPicker: false,
animationType: undefined,
orientation: 'portrait',
doneDepressed: false,
};
this.onUpArrow = this.onUpArrow.bind(this);
this.onDownArrow = this.onDownArrow.bind(this);
this.onValueChange = this.onValueChange.bind(this);
this.onOrientationChange = this.onOrientationChange.bind(this);
this.setInputRef = this.setInputRef.bind(this);
this.togglePicker = this.togglePicker.bind(this);
this.renderInputAccessoryView = this.renderInputAccessoryView.bind(this);
}
onUpArrow() {
const {onUpArrow} = this.props;
this.togglePicker(false, onUpArrow);
}
onDownArrow() {
const {onDownArrow} = this.props;
this.togglePicker(false, onDownArrow);
}
onValueChange(value, index) {
const {onValueChange} = this.props;
onValueChange(value, index);
this.setState((prevState) => {
return {
selectedItem: prevState.items[index],
};
});
}
onOrientationChange({nativeEvent}) {
this.setState({
orientation: nativeEvent.orientation,
});
}
setInputRef(ref) {
this.inputRef = ref;
}
getPlaceholderStyle() {
const {placeholder, style} = this.props;
const {selectedItem} = this.state;
if (!placeholder !== {} && selectedItem.label === placeholder.label) {
return {
...defaultStyles.placeholder,
...style.placeholder,
};
}
return {};
}
triggerOpenCloseCallbacks() {
const {onOpen, onClose} = this.props;
const {showPicker} = this.state;
if (!showPicker && onOpen) {
onOpen();
}
if (showPicker && onClose) {
onClose();
}
}
togglePicker(animate = false, postToggleCallback) {
const {modalProps, disabled} = this.props;
const {showPicker} = this.state;
if (disabled) {
return;
}
if (!showPicker) {
Keyboard.dismiss();
}
const animationType =
modalProps && modalProps.animationType
? modalProps.animationType
: 'slide';
this.triggerOpenCloseCallbacks();
this.setState(
(prevState) => {
return {
animationType: animate ? animationType : undefined,
showPicker: !prevState.showPicker,
};
},
() => {
if (postToggleCallback) {
postToggleCallback();
}
},
);
}
renderPickerItems() {
const {items} = this.state;
return items.map((item) => {
return (
<Picker.Item
label={item.label}
value={item.value}
key={item.key || item.label}
color={item.color ? item.color : SceneUtils.text_color_3}
/>
);
});
}
renderInputAccessoryView() {
const {
InputAccessoryView,
doneText,
onUpArrow,
onDownArrow,
onDonePress,
style,
touchableDoneProps,
} = this.props;
const {doneDepressed} = this.state;
if (InputAccessoryView) {
return <InputAccessoryView testID="custom_input_accessory_view" />;
}
return (
<View
style={[defaultStyles.modalViewMiddle, style.modalViewMiddle]}
testID="input_accessory_view">
<View style={[defaultStyles.chevronContainer, style.chevronContainer]}>
<TouchableOpacity
activeOpacity={onUpArrow ? 0.5 : 1}
onPress={onUpArrow ? this.onUpArrow : null}>
<View
style={[
defaultStyles.chevron,
style.chevron,
defaultStyles.chevronUp,
style.chevronUp,
onUpArrow
? [defaultStyles.chevronActive, style.chevronActive]
: {},
]}
/>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={onDownArrow ? 0.5 : 1}
onPress={onDownArrow ? this.onDownArrow : null}>
<View
style={[
defaultStyles.chevron,
style.chevron,
defaultStyles.chevronDown,
style.chevronDown,
onDownArrow
? [defaultStyles.chevronActive, style.chevronActive]
: {},
]}
/>
</TouchableOpacity>
</View>
<TouchableOpacity
testID="done_button"
onPress={() => {
this.togglePicker(true, onDonePress);
}}
onPressIn={() => {
this.setState({doneDepressed: true});
}}
onPressOut={() => {
this.setState({doneDepressed: false});
}}
hitSlop={{top: 4, right: 4, bottom: 4, left: 4}}
{...touchableDoneProps}>
<View testID="needed_for_touchable">
<Text
testID="done_text"
allowFontScaling={false}
style={[
defaultStyles.done,
style.done,
doneDepressed
? [defaultStyles.doneDepressed, style.doneDepressed]
: {},
]}>
{doneText}
</Text>
</View>
</TouchableOpacity>
</View>
);
}
renderIcon() {
const {style, Icon} = this.props;
if (!Icon) {
return null;
}
return (
<View
testID="icon_container"
style={[defaultStyles.iconContainer, style.iconContainer]}>
<Icon testID="icon" />
</View>
);
}
renderTextInputOrChildren() {
const {children, style, textInputProps} = this.props;
const {selectedItem} = this.state;
const containerStyle =
Platform.OS === 'ios'
? style.inputIOSContainer
: style.inputAndroidContainer;
if (children) {
return (
<View pointerEvents="box-only" style={containerStyle}>
{children}
</View>
);
}
return (
<View pointerEvents="box-only" style={containerStyle}>
<TextInput
testID="text_input"
style={[
Platform.OS === 'ios' ? style.inputIOS : style.inputAndroid,
this.getPlaceholderStyle(),
{fontSize: 14, color: SceneUtils.text_color},
]}
value={
selectedItem.inputLabel
? selectedItem.inputLabel
: selectedItem.label
}
ref={this.setInputRef}
editable={false}
{...textInputProps}
/>
{this.renderIcon()}
</View>
);
}
renderIOS() {
const {style, modalProps, pickerProps, touchableWrapperProps} = this.props;
const {animationType, orientation, selectedItem, showPicker} = this.state;
return (
<View style={[defaultStyles.viewContainer, style.viewContainer]}>
<TouchableOpacity
testID="ios_touchable_wrapper"
onPress={() => {
this.togglePicker(true);
}}
activeOpacity={1}
{...touchableWrapperProps}>
{this.renderTextInputOrChildren()}
</TouchableOpacity>
<Modal
testID="ios_modal"
visible={showPicker}
transparent
animationType={animationType}
supportedOrientations={['portrait', 'landscape']}
onOrientationChange={this.onOrientationChange}
{...modalProps}>
<TouchableOpacity
style={[defaultStyles.modalViewTop, style.modalViewTop]}
testID="ios_modal_top"
onPress={() => {
this.togglePicker(true);
}}
/>
{this.renderInputAccessoryView()}
<View
style={[
defaultStyles.modalViewBottom,
{height: orientation === 'portrait' ? 215 : 162},
style.modalViewBottom,
]}>
<Picker
testID="ios_picker"
mode="dropdown"
style={{color: '#fff', placeholderTextColor: '#fff'}}
onValueChange={this.onValueChange}
itemStyle={{fontSize:20}}
selectedValue={selectedItem.value}
{...pickerProps}>
{this.renderPickerItems()}
</Picker>
</View>
</Modal>
</View>
);
}
renderAndroidHeadless() {
const {
disabled,
Icon,
style,
pickerProps,
onOpen,
touchableWrapperProps,
fixAndroidTouchableBug,
} = this.props;
const {selectedItem} = this.state;
const Component = fixAndroidTouchableBug ? View : TouchableOpacity;
return (
<Component
testID="android_touchable_wrapper"
onPress={onOpen}
activeOpacity={1}
{...touchableWrapperProps}>
<View style={style.headlessAndroidContainer}>
{this.renderTextInputOrChildren()}
<Picker
style={[
Icon ? {backgroundColor: 'transparent'} : {}, // to hide native icon
defaultStyles.headlessAndroidPicker,
style.headlessAndroidPicker,
]}
testID="android_picker_headless"
enabled={!disabled}
onValueChange={this.onValueChange}
selectedValue={selectedItem.value}
{...pickerProps}>
{this.renderPickerItems()}
</Picker>
</View>
</Component>
);
}
renderAndroidNativePickerStyle() {
const {disabled, Icon, style, pickerProps} = this.props;
const {selectedItem} = this.state;
return (
<View style={[defaultStyles.viewContainer, style.viewContainer]}>
<Picker
style={[
Icon ? {backgroundColor: 'transparent'} : {}, // to hide native icon
style.inputAndroid,
this.getPlaceholderStyle(),
{backgroundColor: 'transparent', minWidth: 80}
]}
testID="android_picker"
enabled={!disabled}
onValueChange={this.onValueChange}
selectedValue={selectedItem.value}
{...pickerProps}>
{this.renderPickerItems()}
</Picker>
{/* {this.renderIcon()} */}
</View>
);
}
renderWeb() {
const {disabled, style, pickerProps} = this.props;
const {selectedItem} = this.state;
return (
<View style={[defaultStyles.viewContainer, style.viewContainer]}>
<Picker
style={[style.inputWeb]}
testID="web_picker"
enabled={!disabled}
onValueChange={this.onValueChange}
selectedValue={selectedItem.value}
{...pickerProps}>
{this.renderPickerItems()}
</Picker>
{this.renderIcon()}
</View>
);
}
render() {
const {children, useNativeAndroidPickerStyle} = this.props;
if (Platform.OS === 'ios') {
return this.renderIOS();
}
if (Platform.OS === 'web') {
return this.renderWeb();
}
if (children || !useNativeAndroidPickerStyle) {
return this.renderAndroidHeadless();
}
return this.renderAndroidNativePickerStyle();
}
}
export {defaultStyles};