paper-toolbar.html 9.94 KB
<!--
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->

<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../paper-styles/paper-styles.html">

<!--
`paper-toolbar` is a horizontal bar containing items that can be used for
label, navigation, search and actions.  The items place inside the
`paper-toolbar` are projected into a `class="horizontal center layout"` container inside of
`paper-toolbar`'s Shadow DOM.  You can use flex attributes to control the items'
sizing.

Example:

    <paper-toolbar>
      <paper-icon-button icon="menu" on-tap="menuAction"></paper-icon-button>
      <div class="title">Title</div>
      <paper-icon-button icon="more-vert" on-tap="moreAction"></paper-icon-button>
    </paper-toolbar>

`paper-toolbar` has a standard height, but can made be taller by setting `tall`
class on the `paper-toolbar`.  This will make the toolbar 3x the normal height.

    <paper-toolbar class="tall">
      <paper-icon-button icon="menu"></paper-icon-button>
    </paper-toolbar>

Apply `medium-tall` class to make the toolbar medium tall.  This will make the
toolbar 2x the normal height.

    <paper-toolbar class="medium-tall">
      <paper-icon-button icon="menu"></paper-icon-button>
    </paper-toolbar>

When `tall`, items can pin to either the top (default), middle or bottom.  Use
`middle` class for middle content and `bottom` class for bottom content.

    <paper-toolbar class="tall">
      <paper-icon-button icon="menu"></paper-icon-button>
      <div class="middle title">Middle Title</div>
      <div class="bottom title">Bottom Title</div>
    </paper-toolbar>

For `medium-tall` toolbar, the middle and bottom contents overlap and are
pinned to the bottom.  But `middleJustify` and `bottomJustify` attributes are
still honored separately.

To make an element completely fit at the bottom of the toolbar, use `fit` along
with `bottom`.

    <paper-toolbar class="tall">
      <div id="progressBar" class="bottom fit"></div>
    </paper-toolbar>

### Styling

The following custom properties and mixins are available for styling:

Custom property | Description | Default
----------------|-------------|----------
`--paper-toolbar-background` | Toolbar background color     | `--default-primary-color`
`--paper-toolbar-color`      | Toolbar foreground color     | `--text-primary-color`
`--paper-toolbar`            | Mixin applied to the toolbar | `{}`

### Accessibility

`<paper-toolbar>` has `role="toolbar"` by default. Any elements with the class `title` will
be used as the label of the toolbar via `aria-labelledby`.

@demo demo/index.html
-->

<dom-module id="paper-toolbar">

  <style>
    :host {
      /* technical */
      display: block;
      position: relative;
      box-sizing: border-box;
      -moz-box-sizing: border-box;

      /* size */
      height: 64px;

      background: var(--paper-toolbar-background, --default-primary-color);
      color: var(--paper-toolbar-color, --text-primary-color);

      @apply(--paper-toolbar);
    }

    :host(.animate) {
      /* transition */
      transition: height 0.18s ease-in;
    }

    :host(.medium-tall) {
      height: 128px;
    }

    :host(.tall) {
      height: 192px;
    }

    .toolbar-tools {
      position: relative;
      height: 64px;
      padding: 0 16px;
      pointer-events: none;
    }

    /*
     * TODO: Where should media query breakpoints live so they can be shared between elements?
     */

    @media (max-width: 639px) {
      :host {
        height: 56px;
      }

      :host(.medium-tall) {
        height: 112px;
      }

      :host(.tall) {
        height: 168px;
      }

      .toolbar-tools {
        height: 56px;
      }
    }

    #topBar {
      position: relative;
    }

    /* middle bar */
    #middleBar {
      position: absolute;
      top: 0;
      right: 0;
      left: 0;
    }

    :host(.tall) #middleBar,
    :host(.medium-tall) #middleBar {
      -webkit-transform: translateY(100%);
      transform: translateY(100%);
    }

    /* bottom bar */
    #bottomBar {
      position: absolute;
      right: 0;
      bottom: 0;
      left: 0;
    }

    /*
     * make elements (e.g. buttons) respond to mouse/touch events
     *
     * `.toolbar-tools` disables touch events so multiple toolbars can stack and not
     * absorb events. All children must have pointer events re-enabled to work as
     * expected.
     */
    .toolbar-tools > ::content > *:not([disabled]) {
      pointer-events: auto;
    }

    .toolbar-tools > ::content .title {
      @apply(--paper-font-title);
      @apply(--layout-flex);

      pointer-events: none;
      text-overflow: ellipsis;
      white-space: nowrap;
      overflow: hidden;

      /*
       * Polymer/polymer/issues/1525
       * --paper-font-title defines a `font-weight`
       * let's override its value, but we need `important!`
       * because all mixins are resolved in rule's selector that has higher precedence
       * than the current selector.
       */
      font-weight: 400 !important;
    }

    /**
     * TODO: Refactor these selectors
     * Work in progress.
     */
    .toolbar-tools > ::content paper-icon-button[icon=menu] {
      margin-right: 24px;
    }

    .toolbar-tools > ::content > .title,
    .toolbar-tools > ::content[select=".middle"] > .title,
    .toolbar-tools > ::content[select=".bottom"] > .title {
      margin-left: 56px;
    }

    .toolbar-tools > ::content > paper-icon-button + .title,
    .toolbar-tools > ::content[select=".middle"] paper-icon-button + .title,
    .toolbar-tools > ::content[select=".bottom"] paper-icon-button + .title {
      margin-left: 0;
    }

    .toolbar-tools > ::content > .fit {
      position: absolute;
      top: auto;
      right: 0;
      bottom: 0;
      left: 0;
      width: auto;
      margin: 0;
    }

  </style>

  <template>

    <div id="topBar" class$="[[_computeBarClassName(justify)]]">
      <content select=":not(.middle):not(.bottom)"></content>
    </div>

    <div id="middleBar" class$="[[_computeBarClassName(middleJustify)]]">
      <content select=".middle"></content>
    </div>

    <div id="bottomBar" class$="[[_computeBarClassName(bottomJustify)]]">
      <content select=".bottom"></content>
    </div>

  </template>

</dom-module>

<script>

  (function() {

    'use strict';

    function classNames(obj) {
      var classNames = [];
      for (var key in obj) {
        if (obj.hasOwnProperty(key) && obj[key]) {
          classNames.push(key);
        }
      }

      return classNames.join(' ');
    }

    Polymer({

      is: 'paper-toolbar',

      hostAttributes: {
        'role': 'toolbar'
      },

      properties: {

        /**
         * Controls how the items are aligned horizontally when they are placed
         * at the bottom.
         * Options are `start`, `center`, `end`, `justified` and `around`.
         *
         * @attribute bottomJustify
         * @type string
         * @default ''
         */
        bottomJustify: {
          type: String,
          value: ''
        },

        /**
         * Controls how the items are aligned horizontally.
         * Options are `start`, `center`, `end`, `justified` and `around`.
         *
         * @attribute justify
         * @type string
         * @default ''
         */
        justify: {
          type: String,
          value: ''
        },

        /**
         * Controls how the items are aligned horizontally when they are placed
         * in the middle.
         * Options are `start`, `center`, `end`, `justified` and `around`.
         *
         * @attribute middleJustify
         * @type string
         * @default ''
         */
        middleJustify: {
          type: String,
          value: ''
        }

      },

      attached: function() {
        this._observer = this._observe(this);
        this._updateAriaLabelledBy();
      },

      detached: function() {
        if (this._observer) {
          this._observer.disconnect();
        }
      },

      _observe: function(node) {
        var observer = new MutationObserver(function() {
          this._updateAriaLabelledBy();
        }.bind(this));
        observer.observe(node, {
          childList: true,
          subtree: true
        });
        return observer;
      },

      _updateAriaLabelledBy: function() {
        var labelledBy = [];
        var contents = Polymer.dom(this.root).querySelectorAll('content');
        for (var content, index = 0; content = contents[index]; index++) {
          var nodes = Polymer.dom(content).getDistributedNodes();
          for (var node, jndex = 0; node = nodes[jndex]; jndex++) {
            if (node.classList && node.classList.contains('title')) {
              if (node.id) {
                labelledBy.push(node.id);
              } else {
                var id = 'paper-toolbar-label-' + Math.floor(Math.random() * 10000);
                node.id = id;
                labelledBy.push(id);
              }
            }
          }
        }
        if (labelledBy.length > 0) {
          this.setAttribute('aria-labelledby', labelledBy.join(' '));
        }
      },

      _computeBarClassName: function(barJustify) {
        var classObj = {
          'center': true,
          'horizontal': true,
          'layout': true,
          'toolbar-tools': true
        };

        // If a blank string or any falsy value is given, no other class name is
        // added.
        if (barJustify) {
          var justifyClassName = (barJustify === 'justified') ?
              barJustify :
              barJustify + '-justified';

          classObj[justifyClassName] = true;
        }

        return classNames(classObj);
      }

    });

  }());

</script>