diff --git a/app/components/Base/HorizontalSelector/index.js b/app/components/Base/HorizontalSelector/index.js new file mode 100644 index 00000000000..079a400d545 --- /dev/null +++ b/app/components/Base/HorizontalSelector/index.js @@ -0,0 +1,271 @@ +import React, { Fragment, useCallback, useMemo } from 'react'; +import PropTypes from 'prop-types'; +import { View, StyleSheet, TouchableOpacity } from 'react-native'; +import Text from '../Text'; +import { colors } from '../../../styles/common'; + +const INNER_CIRCLE_SCALE = 0.445; +const OPTION_WIDTH = 110; +const styles = StyleSheet.create({ + selector: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-around', + alignItems: 'center' + }, + labels: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-around', + alignItems: 'flex-start' + }, + option: { + width: OPTION_WIDTH, + display: 'flex', + alignItems: 'center', + flex: 0, + flexDirection: 'column' + }, + circle: size => ({ + width: size, + height: size, + flexShrink: 0, + flexGrow: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderWidth: 2, + borderRadius: 9999, + borderColor: colors.grey200 + }), + circleSelected: { + borderColor: colors.blue + }, + circleError: { + borderColor: colors.red + }, + circleDisabled: { + opacity: 0.4 + }, + innerCircle: size => ({ + width: size * INNER_CIRCLE_SCALE, + height: size * INNER_CIRCLE_SCALE, + flexShrink: 0, + flexGrow: 0, + backgroundColor: colors.blue, + borderRadius: 999 + }), + innerCircleError: { + backgroundColor: colors.red + }, + verticalLine: { + marginTop: 2, + marginBottom: -1, + width: 0, + height: 4, + borderLeftWidth: 1, + borderColor: colors.grey200 + }, + topVerticalLine: { + marginTop: 0, + marginBottom: 2, + width: 0, + height: 4, + borderLeftWidth: 1, + borderColor: colors.blue + }, + line: { + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-around', + width: '100%', + marginBottom: 2 + }, + lineHolder: { + flexDirection: 'row', + alignItems: 'center', + borderWidth: 0 + }, + lineFill: { + flex: 1 + }, + lineVisible: { + borderTopWidth: 1, + borderColor: colors.grey200 + }, + circleHitSlop: { + top: 0, + bottom: 20, + left: 0, + right: 0 + } +}); + +function Circle({ size = 22, selected, disabled, error }) { + return ( + + {selected && ( + + )} + + ); +} +Circle.propTypes = { + size: PropTypes.number, + selected: PropTypes.bool, + disabled: PropTypes.bool, + error: PropTypes.bool +}; + +function Option({ onPress, name, ...props }) { + const handlePress = useCallback(() => onPress(name), [name, onPress]); + return ; +} + +Option.propTypes = { + onPress: PropTypes.func, + name: PropTypes.string +}; + +function HorizontalSelector({ options = [], selected, circleSize, onPress, disabled, ...props }) { + const hasTopLabels = useMemo(() => options.some(option => option.topLabel), [options]); + return ( + + {hasTopLabels && ( + + {options.map(option => + option.topLabel ? ( + + {typeof option.topLabel === 'string' ? ( + + {option.topLabel} + + ) : typeof option.topLabel === 'function' ? ( + option.topLabel(option.name === selected, option.disabled ?? disabled) + ) : ( + option.topLabel + )} + + + ) : ( + + ) + )} + + )} + + {options.map(option => ( + + ))} + + + {options.map((option, index, array) => ( + + + + + + + + + ))} + + + {options.map(option => ( + + ))} + + + ); +} + +HorizontalSelector.propTypes = { + /** + * Array of options + */ + options: PropTypes.arrayOf( + PropTypes.shape({ + /** + * Label of the option. It can be a string, component or a render + * function, which will be called with arguments (selected, disabled). + */ + label: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), + /** + * Top label of the option. It can be a string, component or a render function. + */ + topLabel: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), + /** + * Option name string, this is used as argument when calling the onPress function. + */ + name: PropTypes.string, + /** + * Boolean value to determine whether if option is disabled or not. + */ + disabled: PropTypes.bool, + /** + * Boolean value to determine if the option should represent an error + */ + error: PropTypes.bool + }) + ), + /** + * Boolean value to determine whether the options are disabked or not. + */ + disabled: PropTypes.bool, + /** + * Function that is called when pressing an option. The function is called with option.name argument. + */ + onPress: PropTypes.func, + /** + * Size of the option circle + */ + circleSize: PropTypes.number, + /** + * Current option name selected + */ + selected: PropTypes.string +}; + +export default HorizontalSelector; diff --git a/app/components/UI/EditGasFee1559/index.js b/app/components/UI/EditGasFee1559/index.js index 121d5afd1af..31b41bbd0c4 100644 --- a/app/components/UI/EditGasFee1559/index.js +++ b/app/components/UI/EditGasFee1559/index.js @@ -1,3 +1,4 @@ +/* eslint-disable react/display-name */ import React, { useCallback, useState } from 'react'; import { View, StyleSheet, TouchableOpacity } from 'react-native'; import Text from '../../Base/Text'; @@ -7,6 +8,7 @@ import { colors } from '../../../styles/common'; import InfoModal from '../Swaps/components/InfoModal'; import Icon from 'react-native-vector-icons/Ionicons'; import { strings } from '../../../../locales/i18n'; +import HorizontalSelector from '../../Base/HorizontalSelector'; const styles = StyleSheet.create({ root: { @@ -50,6 +52,7 @@ const EditGasFee1559 = () => { const [showRangeInfoModal, setShowRangeInfoModal] = useState(false); const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); const [maxPriorityFeeError, setMaxPriorityFeeError] = useState(null); + const [selectedOption, setSelectedOption] = useState(null); const toggleRangeInfoModal = useCallback(() => { setShowRangeInfoModal(showRangeInfoModal => !showRangeInfoModal); @@ -73,6 +76,44 @@ const EditGasFee1559 = () => { SELECTOR + {/* TODO: hook with controller, add strings i18n */} + Lower + }, + { + name: 'medium', + label: (selected, disabled) => ( + + Medium + + ) + }, + + { + name: 'high', + error: true, + label: (selected, disabled) => ( + + Higher + + ), + topLabel: ( + + + Recommended{' '} + + + + ) + } + ]} + />