Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(top-app-bar): Add top app bar component #2225

Merged
merged 35 commits into from
Feb 21, 2018

Conversation

williamernest
Copy link
Contributor

refs: #2222

@codecov-io
Copy link

codecov-io commented Feb 12, 2018

Codecov Report

❗ No coverage uploaded for pull request base (master@f89cd10). Click here to learn what that means.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff            @@
##             master    #2225   +/-   ##
=========================================
  Coverage          ?   98.73%           
=========================================
  Files             ?       96           
  Lines             ?     3943           
  Branches          ?      508           
=========================================
  Hits              ?     3893           
  Misses            ?       50           
  Partials          ?        0
Impacted Files Coverage Δ
packages/mdc-top-app-bar/foundation.js 100% <100%> (ø)
packages/mdc-top-app-bar/index.js 100% <100%> (ø)
packages/mdc-top-app-bar/constants.js 100% <100%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f89cd10...dac8de5. Read the comment docs.

demos/index.html Outdated
@@ -215,6 +215,14 @@
</span>
</a>

<a href="top-app-bar.html" role="listitem" class="mdc-list-item">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p comes after o, so this should be after Toolbar if we're being consistent with alpha order.

OTOH I talked with Will about potential for confusion by adding this alongside toolbar before there are features implemented that differentiate it (and before we eventually relegate the toolbar package for different functionality...) and maybe we just shouldn't add this to the demo page until the distinguishing features are added? (So maybe we shouldn't add front-matter to the readme for the site yet either.)

@@ -0,0 +1,18 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have package-lock.json within any of our other packages folders, so I don't think this needs to be committed? (Or we should commit them across the board at some point later, but i.e. I don't think they get generated when doing npm install from the top-level which is usually all we need to do for development.)

}
}

.demo-controls-container {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see this used anywhere?

@@ -2,6 +2,16 @@
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"JSONStream": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these changes seem to just reorder things, so I'm not sure this is needed in this PR?

@@ -151,6 +151,7 @@
"text-field",
"theme",
"toolbar",
"top-app-bar",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about closure whitelist?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toolbar doesn't currently exist in closure whitelist, so I didn't add this one either.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think any component we are writing from scratch should aim to be in the whitelist at the get-go at this point, because it's easier to deal with incrementally than having to come back through and clean up later.

`mdc-top-app-bar-ink-color($color)` | Sets the ink color of the top app bar
`mdc-top-app-bar-fill-color($color)` | Sets the fill color of the top app bar
`mdc-top-app-bar-fill-color-accessible($color)` | Sets the fill color of the top app bar and automatically sets a high-contrast ink color
`mdc-top-app-bar-icon-ink-color($color)` | Sets the ink color of a top app bar icon
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"of top app bar icons"?

@mixin mdc-top-app-bar-icon-ink-color($color) {
.mdc-top-app-bar__icon,
.mdc-top-app-bar__menu-icon {
@include mdc-theme-prop(color, $color);
Copy link
Contributor

@kfranqueiro kfranqueiro Feb 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave mdc-states out of the private mixin, and include it here and pass $color. (Edit: this was done)

/* eslint no-unused-vars: [2, {"args": "none"}] */

/**
* Adapter for MDC Top App Bar .
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove space before period

removeClass(className) {}

/**
* Returns true if the root element contains the given class name.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capitalize Element to be consistent with above, or lowercase it everywhere?

*/
static get defaultAdapter() {
return /** @type {!MDCTopAppBarAdapter} */ {
hasClass: (/* className: string */) => /* boolean */ false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure this should be {} on the RHS, as in, it's an empty function (not a return value).

I realize we seem to be 50/50 on this throughout the repo currently but I'm inclined to think it's a mistake.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was copied from the toolbar.

@kfranqueiro
Copy link
Contributor

To explain what's going on here in case anyone's wondering, we're planning on reorganizing toolbar into common logic + specializations. Since mdc-toolbar was originally created primarily with scenarios for top app bars in mind, it's likely that some functionality currently present in mdc-toolbar will eventually manifest here and be removed from the mdc-toolbar package.

Copy link
Contributor

@kfranqueiro kfranqueiro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI I found a bug: It seems like everything in the top app bar is vertically misaligned too low in Firefox, Edge, and IE.

</div>
</main>

<script src="/assets/material-components-web.js" async></script>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Load /assets/common.js above this line, to automatically get preventDefault on a elements (to avoid the browser navigating to #)

section: components
excerpt: "A container for multiple rows of items such as application title, navigation menu, or tabs."
iconId: toolbar
path: /catalog/topappbar/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're adding this front-matter now, I would hyphenate top-app-bar here just like the package name.

@@ -0,0 +1,112 @@
<!--docs:
title: "Toolbars"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update title if we're adding this front-matter now


# Top App Bar

MDC Top App Bar acts as a container for items such as
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the line wrapping here seems weird

```html
<header class="mdc-top-app-bar">
<div class="mdc-top-app-bar__row">
<section class="mdc-top-app-bar__section mdc-top-app-bar__section--align-start">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity... does design have more semantic names we should be using for the align-start and align-end sections? Or are they general-purpose enough that there's no name that would fit all cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think designers know we split the toolbar into two sides so we can show some things on the left and some things on the right. I'll get with the designer to see if they have any input bit it seems like this was named by a developer smashing together align-items and flex-start. Which makes sense for developers to use.

}

&__icon,
&__menu-icon {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for grouping these two together to reduce CSS output <3

removeClass(className) {}

/**
* Returns true if the root Element contains the given class name.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We say just "class" in the other two descriptions but "class name" here (and "className" in the readme)... should we just reduce this description to class as well?

const icons = [].slice.call(this.root_.querySelectorAll(strings.ACTION_ICON_SELECTOR));
icons.push(this.navIcon_);

this.iconRipples_ = icons.map(function(icon) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use fat arrow here and in the forEach below

this.navIconClick_ = this.navigationEvent_.bind(this);

if (this.navIcon_) {
this.navIcon_.addEventListener('click', this.navIconClick_);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This much logic should not be in the component. This should be hooked up through the foundation with [de]registerInteractionHandler APIs.

Look at textfield/icon for example (not saying this has to be a subcomponent, but that shows an example of hooking up icon interaction + event emit through the foundation and that's almost the only thing that one does).

],
"dependencies": {
"@material/base": "^0.29.0",
"@material/elevation": "^0.28.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we're planning to use this later? It's not currently used, seemingly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is a remnant of my prototype.

@williamernest williamernest removed the request for review from lynnmercier February 16, 2018 15:48
padding: 20px 28px;
}

@media (max-width: 599px) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reference $mdc-top-app-bar-mobile-breakpoint ?

// limitations under the License.
//

@import "@material/elevation/mixins";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I flagged this in package.json earlier, and since we removed it there, we should remove it here to avoid breaking things.

@import "../packages/mdc-button/mdc-button";
@import "../packages/mdc-list/mdc-list";
@import "../packages/mdc-menu/mdc-menu";
@import "../packages/mdc-theme/color-palette";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import?

// limitations under the License.
//

@import "@material/theme/variables"; // for mdc-theme-accessible-ink-color
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically missing theme/mixins for mdc-theme-prop?


@import "@material/elevation/mixins";
@import "@material/rtl/mixins";
@import "@material/theme/mixins";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think theme/mixins is needed in this file? (I commented about it being missing in the mixins file though.)

test('#init calls component event registrations', () => {
const {foundation} = setupTest();

foundation.init();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't testing anything? Should this be mocking adapter#registerNavigationInteractionHandler?

There's also no reciprocal test for #destroy and deregister.

foundation.init();
});

test('foundation returns strings', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not combine this with the "exports strings" test? e.g.

assert.deepEqual(MDCCheckboxFoundation.strings, strings);


let click;

td.when(mockAdapter.registerNavigationIconInteractionHandler('click', td.matchers.isA(Function)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be able to use captureHandlers in test/unit/helpers/foundation.js to help with this.

See e.g. how ripple uses it:

const handlers = captureHandlers(adapter, 'registerInteractionHandler');

test('adapter#hasClass returns true if the root element has specified class', () => {
const {root, component} = setupTest();
root.classList.add('foo');
assert.isOk(component.getDefaultFoundation().adapter_.hasClass('foo'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I know we do this a bunch of places, but I'd advocate using isTrue and isFalse specifically given that the API is supposed to return a boolean.

@@ -0,0 +1,104 @@

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove leading newline?

Copy link
Contributor

@acdvorak acdvorak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Goldens and css-bundle-factory LGTM!

Copy link
Contributor

@kfranqueiro kfranqueiro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm noticing that in seemingly every browser except chrome, the ripples are not positioned/sized correctly. This might be a case of missing position: relative on the surface, which we've run into before (and I should really look at the techdebt issue of centralizing...).

<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="/images/logo_components_color_2x_web_48dp.png">
<link rel="stylesheet" href="/assets/top-app-bar.css">
<link rel="stylesheet" href="/assets/drawer/drawer.css">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how I feel about this vs. importing mdc-drawer in the top-app-bar demo scss. Including this instead also picks up a bunch of demo styles specific to the drawer page that aren't being used here.

</div>
</header>

<aside class="mdc-drawer mdc-drawer--temporary demo-drawer">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the demo-drawer and demo-drawer-list-item classes are being styled at all in this demo page.

<script src="/assets/material-components-web.js" async></script>
<script type="text/javascript">
demoReady(function() {
var appBarElement = document.getElementById('demo-top-app-bar');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Shorten to appBarEl to match drawerEl?

`removeClass(className: string) => void` | Removes a class from the root element of the component.
`registerNavigationIconInteractionHandler(evtType: string, handler: EventListener) => void` | Registers an event listener on the native navigation icon element for a given event.
`deregisterNavigationIconInteractionHandler(evtType: string, handler: EventListener) => void` | Deregisters an event listener on the native navigation icon element for a given event.
`notifyNavigationIconClicked() => void` | Emits a custom event "MDCTopAppBar:nav" when the navigation icon is clicked.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: switch from quotes to backticks around the custom event name

Copy link
Contributor

@kfranqueiro kfranqueiro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI we currently have no tests for the top-app-bar component's destroy logic (to iterate and destroy ripples), but that might require adding a factory argument along the lines of what textfield/index.js does. I commented on a couple of other things we can probably test more easily to increase coverage.


Event Name | Event Data Structure | Description
--- | --- | ---
`MDCTopAppBar:nav` | None | Emits when the menu icon is clicked.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we say "navigation icon" a few lines up, but "menu icon" here... is there one right word for it?

/** @enum {string} */
const strings = {
NAVIGATION_EVENT: 'MDCTopAppBar:nav',
TITLE_SELECTOR: '.mdc-top-app-bar__title',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used (or will it be)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be used in the next PR. It won't hurt to leave it in for now.

assert.isFalse(root.classList.contains('foo'));
});

test('#adapter.registerNavigationIconInteractionHandler adds a handler to the nav icon ' +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How hard would it be to add tests to verify that no handler gets registered if there is no navigation icon? Currently the implicit "else" branch isn't covered in the register/deregister logic.

return {root, adjust, component, icon};
}

class FakeRipple {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this above setupTest? It's jarring to see things defined after they're used.

}

suite('MDCTopAppBar');

test('attachTo initializes and returns an MDCTopAppBar instance', () => {
test('initialize and returns an MDCTopAppBar instance', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this test name rephrased? It still does the same thing (and now it's grammatically incorrect).

assert.isTrue(MDCTopAppBar.attachTo(getFixture()) instanceof MDCTopAppBar);
});

test('constructor instantiates icon ripples', () => {
const {root, component} = setupTest();
const icons = root.querySelectorAll('.mdc-top-app-bar__icon').length + 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this reuse the two *_ICON_SELECTOR constants together in one selector and forego the +1?

Copy link
Contributor

@kfranqueiro kfranqueiro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM provided that CI passes

@williamernest williamernest merged commit 0ad69c4 into master Feb 21, 2018
@williamernest williamernest deleted the feat/top-app-bar/new-component branch February 21, 2018 23:07
@williamernest williamernest mentioned this pull request Mar 1, 2018
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants