Skip to content

feat(schematics): navigation schematic #10009

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

Merged
merged 5 commits into from
Mar 8, 2018
Merged

Conversation

amcdnl
Copy link
Contributor

@amcdnl amcdnl commented Feb 18, 2018

This PR creates a navigation/toolbar schematic.

Large Screen

Small Screen

@amcdnl amcdnl self-assigned this Feb 18, 2018
@amcdnl amcdnl requested a review from jelbourn February 18, 2018 17:09
@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Feb 18, 2018
@amcdnl amcdnl changed the title feat(schematics): add navigation schematic feat(schematics): navigation schematic Feb 18, 2018

mat-drawer {
width: 200px;
box-shadow: 3px 0 6px rgba(0,0,0,.24);
Copy link
Member

Choose a reason for hiding this comment

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

Should this be done with one of the elevation classes instead?

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 thought about using the elevation helper but that would only work if you are using SCSS. We have to assume CSS/inline css/etc.

@@ -0,0 +1,18 @@
<mat-drawer-container>
<mat-drawer mode="side" #drawer [opened]="!isHandset">
Copy link
Member

Choose a reason for hiding this comment

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

I believe this should be a mat-sidenav since it's at the top level. @mmalerba will know best

Copy link
Contributor

Choose a reason for hiding this comment

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

yep, if you make it a mat-sidenav you can also use the fixedInViewport option to make the sidenav fixed position on mobile and allow the address bar to scroll away. This is something I think we should include by default for users, or at least give an option to easily enable it, because it can be a little tricky to set up on your own.

@@ -0,0 +1,18 @@
<mat-drawer-container>
<mat-drawer mode="side" #drawer [opened]="!isHandset">
Copy link
Member

Choose a reason for hiding this comment

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

Should the mode change based on the screen size? I.e., when it gets large enough it goes to over.

<mat-drawer-content>
<mat-toolbar color="primary">
<button type="button" mat-icon-button (click)="drawer.toggle()" *ngIf="isHandset">
<mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
Copy link
Member

Choose a reason for hiding this comment

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

The aria-label should go on the button instead of the icon and should describe the button's action rather than describe the icon.

@@ -0,0 +1,18 @@
<mat-drawer-container>
<mat-drawer mode="side" #drawer [opened]="!isHandset">
<mat-toolbar color="primary">Menu</mat-toolbar>
Copy link
Member

Choose a reason for hiding this comment

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

Does the sidenav need a toolbar / header? Not sure if I typically see this.

Copy link
Contributor

Choose a reason for hiding this comment

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

I have seen this on Google products before, they will often put the product logo in a toolbar in the sidenav so it lines ups with the main toolbar when the sidenav opens. Google photos and play music are currently doing this, but it seems like most products are switching to the inbox style where the sidenav just opens below the toolbar

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jelbourn - Let me know what you'd like me to do here.


it('should create', () => {
expect(component).toBeTruthy();
});
Copy link
Member

Choose a reason for hiding this comment

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

It would be good to flesh out a couple of basic tests here (e.g. sidenav toggling)

const tree = runner.runSchematic('materialNav', { ...options }, createTestApp());
const files = tree.files;

expect(files.indexOf('/src/app/foo/foo.component.css')).toBeGreaterThanOrEqual(0);
Copy link
Member

Choose a reason for hiding this comment

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

Should be able to use toContain here

export class <%= classify(name) %>Component {
isHandset: boolean;
constructor(breakpointObserver: BreakpointObserver) {
this.isHandset = breakpointObserver.isMatched(Breakpoints.Handset);
Copy link
Member

Choose a reason for hiding this comment

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

Instead of just setting this once we should subscribe to the observer to keep it up-to-date.

Copy link
Member

Choose a reason for hiding this comment

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

This is a good case for the async pipe. See how it was done for the tooltip: https://github.com/angular/material2/blob/master/src/lib/tooltip/tooltip.html#L3

.compileComponents();
}));

beforeEach(() => {
Copy link
Member

Choose a reason for hiding this comment

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

The two beforeEach calls can be combined.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This code was copied from the base create component schematic.

it('should create', () => {
expect(component).toBeTruthy();
});
});
Copy link
Member

Choose a reason for hiding this comment

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

Missing a newline at the end of the file.

fixture.detectChanges();
});

it('should create', () => {
Copy link
Member

Choose a reason for hiding this comment

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

should create -> should compile

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This code was copied from the base create component schematic.

let component: <%= classify(name) %>Component;
let fixture: ComponentFixture<<%= classify(name) %>Component>;

beforeEach(async(() => {
Copy link
Member

Choose a reason for hiding this comment

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

Use fakeAsync instead of async.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This code was copied from the base create component schematic.

<mat-drawer-content>
<mat-toolbar color="primary">
<button type="button" mat-icon-button (click)="drawer.toggle()" *ngIf="isHandset">
<mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
Copy link
Member

Choose a reason for hiding this comment

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

aria-label should be on the button.

export class <%= classify(name) %>Component {
isHandset: boolean;
constructor(breakpointObserver: BreakpointObserver) {
this.isHandset = breakpointObserver.isMatched(Breakpoints.Handset);
Copy link
Member

Choose a reason for hiding this comment

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

This is a good case for the async pipe. See how it was done for the tooltip: https://github.com/angular/material2/blob/master/src/lib/tooltip/tooltip.html#L3

},
"flat": {
"type": "boolean",
"description": "Flag to indicate if a dir is created.",
Copy link
Member

Choose a reason for hiding this comment

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

Unclear what "dir" it's referring to.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This code was copied from the base create component schematic.

# Conflicts:
#	schematics/collection.json
#	schematics/tsconfig.json
@amcdnl
Copy link
Contributor Author

amcdnl commented Feb 27, 2018

@jelbourn - Addressed feedback.

import { Observable } from 'rxjs/Observable';

@Component({
selector: '<%= selector %>',<% if(inlineTemplate) { %>
Copy link
Contributor

Choose a reason for hiding this comment

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

So I guess schematics doesn't have a way to insert contents of another file here? Do they have a way to add comments that will be stripped out of the generated file? If so it would be good to add some comments for people maintaining this file that they need to update in 2 places

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ya, its very painful. I'm thinking about making a util to handle this automatically.

}
`
]<% } else { %>
styleUrls: ['./<%= dasherize(name) %>.component.<%= styleext %>']<% } %><% if(!!viewEncapsulation) { %>,
Copy link
Contributor

Choose a reason for hiding this comment

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

should this be if(viewEncapsulation !== 'Emulated')?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This code was copied from the base create component schematic.

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 update the base schematic?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@hansl ^^

"viewEncapsulation": {
"description": "Specifies the view encapsulation strategy.",
"enum": ["Emulated", "Native", "None"],
"type": "string",
Copy link
Contributor

Choose a reason for hiding this comment

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

"default": "Emulated"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This code was copied from the base create component schematic.

Copy link
Member

Choose a reason for hiding this comment

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

Not really good enough reason on its own; Hans has mentioned that those base schematics are not meant to be normative.

mat-sidenav {
width: 200px;
box-shadow: 3px 0 6px rgba(0,0,0,.24);
}
Copy link
Member

Choose a reason for hiding this comment

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

Same comment about the styles as in #10011

</mat-sidenav>
<mat-sidenav-content>
<mat-toolbar color="primary">
<button type="button" mat-icon-button (click)="drawer.toggle()" *ngIf="(_isHandset | async)!.matches">
Copy link
Member

Choose a reason for hiding this comment

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

Button needs an aria-label

</mat-sidenav>
<mat-sidenav-content>
<mat-toolbar color="primary">
<button type="button" mat-icon-button (click)="drawer.toggle()" *ngIf="(_isHandset | async)!.matches">
Copy link
Member

Choose a reason for hiding this comment

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

We need to make sure the a11y here is sensible. We may need to add a focus-trap to the sidenav (which in turn might need role="dialog" added to the sidenav element along with everything that goes along with that role)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would you expect this role to only be present in mobile mode where its sliding out?

})
export class <%= classify(name) %>Component {
_isHandset: Observable<BreakpointState> = this._breakpointObserver.observe(Breakpoints.Handset);
constructor(private _breakpointObserver: BreakpointObserver) {}
Copy link
Member

Choose a reason for hiding this comment

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

Avoid prefixing with an underscore (which is a special thing we do in Angular Material that we don't recommend for most folks).

changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %>
})
export class <%= classify(name) %>Component {
_isHandset: Observable<BreakpointState> = this._breakpointObserver.observe(Breakpoints.Handset);
Copy link
Member

Choose a reason for hiding this comment

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

Update this as the viewport changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Its handled here: [opened]="!(isHandset | async)!.matches"

@amcdnl
Copy link
Contributor Author

amcdnl commented Mar 8, 2018

@jelbourn - Ready for another review.

Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

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

LGTM

@jelbourn jelbourn added pr: lgtm action: merge The PR is ready for merge by the caretaker labels Mar 8, 2018
@jelbourn jelbourn merged commit 279c112 into angular:master Mar 8, 2018
@jelbourn jelbourn added the target: major This PR is targeted for the next major release label Mar 8, 2018
@amcdnl amcdnl deleted the nav-schematic branch March 8, 2018 00:47
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 8, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement target: major This PR is targeted for the next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants