ToolbarAndroid.android.js 6.96 KB
/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule ToolbarAndroid
 */

'use strict';

var Image = require('Image');
var NativeMethodsMixin = require('NativeMethodsMixin');
var React = require('React');
var PropTypes = require('prop-types');
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
var UIManager = require('UIManager');
var ViewPropTypes = require('ViewPropTypes');
var ColorPropType = require('ColorPropType');

var createReactClass = require('create-react-class');
var requireNativeComponent = require('requireNativeComponent');
var resolveAssetSource = require('resolveAssetSource');

var optionalImageSource = PropTypes.oneOfType([
  Image.propTypes.source,
  // Image.propTypes.source is required but we want it to be optional, so we OR
  // it with a nullable propType.
  PropTypes.oneOf([]),
]);

/**
 * React component that wraps the Android-only [`Toolbar` widget][0]. A Toolbar can display a logo,
 * navigation icon (e.g. hamburger menu), a title & subtitle and a list of actions. The title and
 * subtitle are expanded so the logo and navigation icons are displayed on the left, title and
 * subtitle in the middle and the actions on the right.
 *
 * If the toolbar has an only child, it will be displayed between the title and actions.
 *
 * Although the Toolbar supports remote images for the logo, navigation and action icons, this
 * should only be used in DEV mode where `require('./some_icon.png')` translates into a packager
 * URL. In release mode you should always use a drawable resource for these icons. Using
 * `require('./some_icon.png')` will do this automatically for you, so as long as you don't
 * explicitly use e.g. `{uri: 'http://...'}`, you will be good.
 *
 * Example:
 *
 * ```
 * render: function() {
 *   return (
 *     <ToolbarAndroid
 *       logo={require('./app_logo.png')}
 *       title="AwesomeApp"
 *       actions={[{title: 'Settings', icon: require('./icon_settings.png'), show: 'always'}]}
 *       onActionSelected={this.onActionSelected} />
 *   )
 * },
 * onActionSelected: function(position) {
 *   if (position === 0) { // index of 'Settings'
 *     showSettings();
 *   }
 * }
 * ```
 *
 * [0]: https://developer.android.com/reference/android/support/v7/widget/Toolbar.html
 */
var ToolbarAndroid = createReactClass({
  displayName: 'ToolbarAndroid',
  mixins: [NativeMethodsMixin],

  propTypes: {
    ...ViewPropTypes,
    /**
     * Sets possible actions on the toolbar as part of the action menu. These are displayed as icons
     * or text on the right side of the widget. If they don't fit they are placed in an 'overflow'
     * menu.
     *
     * This property takes an array of objects, where each object has the following keys:
     *
     * * `title`: **required**, the title of this action
     * * `icon`: the icon for this action, e.g. `require('./some_icon.png')`
     * * `show`: when to show this action as an icon or hide it in the overflow menu: `always`,
     * `ifRoom` or `never`
     * * `showWithText`: boolean, whether to show text alongside the icon or not
     */
    actions: PropTypes.arrayOf(PropTypes.shape({
      title: PropTypes.string.isRequired,
      icon: optionalImageSource,
      show: PropTypes.oneOf(['always', 'ifRoom', 'never']),
      showWithText: PropTypes.bool
    })),
    /**
     * Sets the toolbar logo.
     */
    logo: optionalImageSource,
    /**
     * Sets the navigation icon.
     */
    navIcon: optionalImageSource,
    /**
     * Callback that is called when an action is selected. The only argument that is passed to the
     * callback is the position of the action in the actions array.
     */
    onActionSelected: PropTypes.func,
    /**
     * Callback called when the icon is selected.
     */
    onIconClicked: PropTypes.func,
    /**
     * Sets the overflow icon.
     */
    overflowIcon: optionalImageSource,
    /**
     * Sets the toolbar subtitle.
     */
    subtitle: PropTypes.string,
    /**
     * Sets the toolbar subtitle color.
     */
    subtitleColor: ColorPropType,
    /**
     * Sets the toolbar title.
     */
    title: PropTypes.string,
    /**
     * Sets the toolbar title color.
     */
    titleColor: ColorPropType,
    /**
     * Sets the content inset for the toolbar starting edge.
     *
     * The content inset affects the valid area for Toolbar content other than
     * the navigation button and menu. Insets define the minimum margin for
     * these components and can be used to effectively align Toolbar content
     * along well-known gridlines.
     */
    contentInsetStart: PropTypes.number,
    /**
     * Sets the content inset for the toolbar ending edge.
     *
     * The content inset affects the valid area for Toolbar content other than
     * the navigation button and menu. Insets define the minimum margin for
     * these components and can be used to effectively align Toolbar content
     * along well-known gridlines.
     */
    contentInsetEnd: PropTypes.number,
    /**
     * Used to set the toolbar direction to RTL.
     * In addition to this property you need to add
     *
     *   android:supportsRtl="true"
     *
     * to your application AndroidManifest.xml and then call
     * `setLayoutDirection(LayoutDirection.RTL)` in your MainActivity
     * `onCreate` method.
     */
    rtl: PropTypes.bool,
    /**
     * Used to locate this view in end-to-end tests.
     */
    testID: PropTypes.string,
  },

  render: function() {
    var nativeProps = {
      ...this.props,
    };
    if (this.props.logo) {
      nativeProps.logo = resolveAssetSource(this.props.logo);
    }
    if (this.props.navIcon) {
      nativeProps.navIcon = resolveAssetSource(this.props.navIcon);
    }
    if (this.props.overflowIcon) {
      nativeProps.overflowIcon = resolveAssetSource(this.props.overflowIcon);
    }
    if (this.props.actions) {
      var nativeActions = [];
      for (var i = 0; i < this.props.actions.length; i++) {
        var action = {
          ...this.props.actions[i],
        };
        if (action.icon) {
          action.icon = resolveAssetSource(action.icon);
        }
        if (action.show) {
          action.show = UIManager.ToolbarAndroid.Constants.ShowAsAction[action.show];
        }
        nativeActions.push(action);
      }
      nativeProps.nativeActions = nativeActions;
    }

    return <NativeToolbar onSelect={this._onSelect} {...nativeProps} />;
  },

  _onSelect: function(event) {
    var position = event.nativeEvent.position;
    if (position === -1) {
      this.props.onIconClicked && this.props.onIconClicked();
    } else {
      this.props.onActionSelected && this.props.onActionSelected(position);
    }
  },
});

var NativeToolbar = requireNativeComponent('ToolbarAndroid', ToolbarAndroid, {
  nativeOnly: {
    nativeActions: true,
  }
});

module.exports = ToolbarAndroid;