| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 | // @flow
import React, { Component } from 'react';
import {
    Modal,
    Text,
    TouchableHighlight,
    TouchableWithoutFeedback,
    View
} from 'react-native';
import { connect } from 'react-redux';
import { Icon } from '../../font-icons';
import { simpleBottomSheet as styles } from './styles';
/**
 * Underlay color for the buttons on the sheet.
 *
 * @type {string}
 */
const BUTTON_UNDERLAY_COLOR = '#eee';
type Option = {
    /**
     * Name of the icon which will be rendered on the right.
     */
    iconName: string,
    /**
     * True if the element is selected (will be highlighted in blue),
     * false otherwise.
     */
    selected: boolean,
    /**
     * Text which will be rendered in the row.
     */
    text: string
};
/**
 * The type of {@code SimpleBottomSheet}'s React {@code Component} prop types.
 */
type Props = {
    /**
     * Handler for the cancel event, which happens when the user dismisses
     * the sheet.
     */
    onCancel: Function,
    /**
     * Handler for the event when an option has been selected in the sheet.
     */
    onSubmit: Function,
    /**
     * Array of options which will be rendered as rows.
     */
    options: Array<Option>
};
/**
 * A component emulating Android's BottomSheet, in a simplified form.
 * It supports text options with an icon, which the user can tap. The style has
 * been implemented following the Material Design guidelines for bottom
 * sheets: https://material.io/guidelines/components/bottom-sheets.html
 *
 * For all intents and purposes, this component has been designed to work and
 * behave as a {@code Dialog}.
 */
class SimpleBottomSheet extends Component<Props> {
    /**
     * Initializes a new {@code SimpleBottomSheet} instance.
     *
     * @param {Object} props - The read-only React {@code Component} props with
     * which the new instance is to be initialized.
     */
    constructor(props) {
        super(props);
        this._onButtonPress = this._onButtonPress.bind(this);
        this._onCancel = this._onCancel.bind(this);
    }
    /**
     * Implements React's {@link Component#render()}.
     *
     * @inheritdoc
     * @returns {ReactElement}
     */
    render() {
        return (
            <Modal
                animationType = { 'slide' }
                onRequestClose = { this._onCancel }
                transparent = { true }
                visible = { true }>
                <View style = { styles.container }>
                    <TouchableWithoutFeedback
                        onPress = { this._onCancel } >
                        <View style = { styles.overlay } />
                    </TouchableWithoutFeedback>
                    <View style = { styles.sheet }>
                        <View style = { styles.rowsWrapper }>
                            { this._renderOptions() }
                        </View>
                    </View>
                </View>
            </Modal>
        );
    }
    _onButtonPress: (?Object) => void;
    /**
     * Handle pressing of one of the options. The sheet will be hidden and the
     * onSubmit prop will be called with the selected option.
     *
     * @param {Object} option - The option which the user selected.
     * @private
     * @returns {void}
     */
    _onButtonPress(option) {
        const { onSubmit } = this.props;
        onSubmit && onSubmit(option);
    }
    _onCancel: () => void;
    /**
     * Cancels the dialog by calling the onCancel prop callback.
     *
     * @private
     * @returns {void}
     */
    _onCancel() {
        const { onCancel } = this.props;
        onCancel && onCancel();
    }
    /**
     * Renders sheet rows based on the options prop.
     *
     * @private
     * @returns {Array} - Array of rows to be rendered in the sheet.
     */
    _renderOptions() {
        return this.props.options.map(
            (option, index) => this._renderRow(option, index));
    }
    /**
     * Renders a single row of the sheet.
     *
     * @param {Object} option - Single option which needs to be rendered.
     * @param {int} index - Option index, used as a key for React.
     * @private
     * @returns {ReactElement} - A row element with an icon and text.
     */
    _renderRow(option, index) {
        const { iconName, selected, text } = option;
        const selectedStyle = selected ? styles.rowSelectedText : {};
        return (
            <TouchableHighlight
                key = { index }
                // TODO The following disables an eslint error alerting about a
                // known potential/theoretical performance pernalty:
                //
                // A bind call or arrow function in a JSX prop will create a
                // brand new function on every single render. This is bad for
                // performance, as it will result in the garbage collector being
                // invoked way more than is necessary. It may also cause
                // unnecessary re-renders if a brand new function is passed as a
                // prop to a component that uses reference equality check on the
                // prop to determine if it should update.
                //
                // I'm not addressing the potential/theoretical performance
                // penalty at the time of this writing because it doesn't seem
                // to me that it's a practical performance penalty in the case.
                //
                // eslint-disable-next-line react/jsx-no-bind
                onPress = { this._onButtonPress.bind(this, option) }
                underlayColor = { BUTTON_UNDERLAY_COLOR } >
                <View style = { styles.row } >
                    <Icon
                        name = { iconName }
                        style = { [ styles.rowIcon, selectedStyle ] } />
                    <View style = { styles.rowPadding } />
                    <Text style = { [ styles.rowText, selectedStyle ] } >
                        { text }
                    </Text>
                </View>
            </TouchableHighlight>
        );
    }
}
export default connect()(SimpleBottomSheet);
 |