@janiscommerce/ui-native
Version:
components library for Janis app
179 lines (176 loc) • 8.11 kB
JavaScript
/* istanbul ignore file */
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { Keyboard, Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
import { black, grey, primary } from '../../../theme/palette';
import { formatPlaceholderMulti } from './utils';
import Options from './Components/Options';
import Icon from '../../atoms/Icon';
import { horizontalScale, moderateScale, scaledForDevice } from '../../../scale';
var KeyboardTypes;
(function (KeyboardTypes) {
KeyboardTypes["Default"] = "default";
KeyboardTypes["NumberPad"] = "number-pad";
KeyboardTypes["DecimalPad"] = "decimal-pad";
KeyboardTypes["Numeric"] = "numeric";
KeyboardTypes["EmailAddress"] = "email-address";
KeyboardTypes["PhonePad"] = "phone-pad";
KeyboardTypes["URL"] = "url";
})(KeyboardTypes || (KeyboardTypes = {}));
export var VariantOptions;
(function (VariantOptions) {
VariantOptions["Dropdown"] = "Dropdown";
VariantOptions["Modal"] = "Modal";
})(VariantOptions || (VariantOptions = {}));
const Select = ({ options, label, value = null, variantOptions = VariantOptions.Dropdown, placeholder = '', optionStyles = {}, inputProps = {}, isMulti = false, isDisabled = false, noOptionsMessage = 'no options', multiOptionsText = null, keyboardType = KeyboardTypes.Default, onFocus = () => { }, onSelectOption = () => { }, customOptionComponent = null, modalAcceptText = 'accept', ...props }) => {
const [inputValue, setInputValue] = useState('');
const [selectedOptions, setSelectedOptions] = useState([]);
const [filteredOptions, setFilteredOptions] = useState(options);
const [isShowedOptions, setIsShowedOptions] = useState(false);
const [dropdownMeasures, setDropdownMeasures] = useState({
width: 0,
pageY: 0,
pageX: 0,
});
const inputRef = useRef(null);
const hasDefaultValue = !!value?.length && options?.some((option) => option?.label === value[0]?.label);
const isMoveLabel = isShowedOptions || inputValue;
const showDeleteIcon = isDisabled ? false : !!inputValue && !!selectedOptions?.length;
const isArrowRotated = isShowedOptions ? '180deg' : '0deg';
const filterOptions = (textValue) => {
if (typeof textValue !== 'string' || !textValue.length) {
return setFilteredOptions(options);
}
const filtered = options?.filter((option) => option.label.toLowerCase().includes(textValue.toLowerCase()));
return setFilteredOptions(filtered);
};
const handleChange = (textValue) => {
setInputValue(textValue);
filterOptions(textValue);
};
const handleOnFocus = () => {
Keyboard.dismiss();
setIsShowedOptions(true);
onFocus();
};
const setSingleOption = (option) => {
setIsShowedOptions(false);
setSelectedOptions([option]);
setInputValue(option.label);
};
const setMultiOptions = (option) => {
const optionMatcher = !!selectedOptions.find((previewOption) => previewOption.value === option.value);
const updateOption = optionMatcher
? selectedOptions.filter((previewOption) => previewOption.value !== option.value)
: [...selectedOptions, option];
setSelectedOptions(updateOption);
setInputValue(formatPlaceholderMulti(updateOption, multiOptionsText));
};
const handleSelectedOption = (option) => isMulti ? setMultiOptions(option) : setSingleOption(option);
const handleCloseDropdown = () => {
if (isDisabled) {
return null;
}
setIsShowedOptions(!isShowedOptions);
inputRef.current?.blur();
};
const handleResetOptions = () => {
setIsShowedOptions(false);
setInputValue('');
setSelectedOptions([]);
};
const memoizedSelectedOptions = useCallback(() => {
if (!!selectedOptions?.length && !!inputValue) {
onSelectOption(selectedOptions);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedOptions]);
useEffect(() => {
memoizedSelectedOptions();
}, [selectedOptions, memoizedSelectedOptions]);
useEffect(() => {
if (hasDefaultValue) {
setSelectedOptions(value);
setInputValue(formatPlaceholderMulti(value, multiOptionsText));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hasDefaultValue, value]);
useEffect(() => {
if (inputRef.current) {
inputRef.current.measure((x, y, width, height, pageX, pageY) => setDropdownMeasures({ width, pageX, pageY: pageY - 15 }));
}
}, [isShowedOptions]);
const moveLabel = isMoveLabel ? 38 : 10;
const validFontSize = scaledForDevice(16, moderateScale);
const validMarginBottom = scaledForDevice(10, moderateScale);
const validMarginTop = scaledForDevice(18, moderateScale);
const validHeightLabel = scaledForDevice(19, moderateScale);
const validPadding = scaledForDevice(8, moderateScale);
const validBottomLabel = scaledForDevice(moveLabel, moderateScale);
const validHeightInput = scaledForDevice(38, moderateScale);
const validRight = scaledForDevice(30, horizontalScale);
const validBorderBottomWidth = scaledForDevice(1, moderateScale);
const styles = StyleSheet.create({
wrapper: {
width: '100%',
marginBottom: validMarginBottom,
position: 'relative',
zIndex: isShowedOptions ? 10 : 0,
},
wrapperInput: {
position: 'relative',
width: '100%',
marginBottom: 0,
marginTop: validMarginTop,
},
label: {
position: 'absolute',
color: isMoveLabel && !isDisabled ? primary.main : black.main,
fontSize: validFontSize,
lineHeight: validHeightLabel,
letterSpacing: 0,
left: 0,
fontWeight: isMoveLabel ? '600' : '400',
bottom: validBottomLabel,
},
input: {
width: '100%',
height: validHeightInput,
padding: 0,
fontSize: validFontSize,
lineHeight: validHeightLabel,
letterSpacing: 0,
borderBottomWidth: validBorderBottomWidth,
color: black.main,
borderBottomColor: isShowedOptions ? primary.main : grey[200],
},
arrowIcon: {
position: 'absolute',
padding: validPadding,
right: 0,
bottom: 0,
zIndex: 1,
transform: [{ rotate: isArrowRotated }],
},
deleteIcon: {
position: 'absolute',
padding: validPadding,
right: validRight,
bottom: 0,
zIndex: 1,
},
});
return (<View style={styles.wrapper} {...props}>
<View style={styles.wrapperInput}>
{isMulti && showDeleteIcon && (<Pressable onPress={handleResetOptions} style={styles.deleteIcon}>
<Icon size={20} color={black.main} name="cross_circle_flat"/>
</Pressable>)}
<Pressable style={styles.arrowIcon} onPress={handleCloseDropdown}>
<Icon size={20} color={isDisabled ? black.main : primary.main} name="chevron_down"/>
</Pressable>
<Text style={styles.label}>{label}</Text>
<TextInput ref={inputRef} style={styles.input} value={inputValue} placeholder={isMoveLabel && placeholder} showSoftInputOnFocus={false} caretHidden={true} keyboardType={keyboardType} editable={!isDisabled} onFocus={handleOnFocus} onChangeText={handleChange} {...inputProps}/>
</View>
<Options variantOptions={variantOptions} dropdownMeasures={dropdownMeasures} setIsShowedOptions={setIsShowedOptions} isShowedOptions={isShowedOptions} filteredOptions={filteredOptions} selectedOptions={selectedOptions} noOptionsMessage={noOptionsMessage} optionStyles={optionStyles} callbackOption={handleSelectedOption} customOptionComponent={customOptionComponent} isMulti={isMulti} modalAcceptText={modalAcceptText}/>
</View>);
};
export default Select;