Skip to content

Commit 193bd15

Browse files
committed
component(): sidenav component.
1 parent bc6d2bc commit 193bd15

File tree

13 files changed

+508
-9
lines changed

13 files changed

+508
-9
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<div class="md-sidenav-backdrop"
2+
(click)="(start_?.mode == 'side' || start_.close()) && (end_?.mode == 'side' || end_.close())"
3+
[class.md-sidenav-shown]="(start_?.mode != 'side' && start_.opened) ||
4+
(end_?.mode != 'side' && end_.opened)"></div>
5+
6+
<ng-content select="md-sidenav"></ng-content>
7+
8+
<md-content [style.margin-left.px]="(left_?.mode == 'side' && left_.opened) ? left_.width : 0"
9+
[style.margin-right.px]="(right_?.mode == 'side' && right_.opened) ? right_.width : 0">
10+
<ng-content></ng-content>
11+
</md-content>
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
@import "default-theme";
2+
@import "variables";
3+
@import "shadows";
4+
5+
6+
$md-sidenav-background-color: md-color($md-background, 100) !default;
7+
8+
9+
@mixin md-sidenav-transition($open, $close) {
10+
transform: translateX($close);
11+
12+
&.md-sidenav-closed {
13+
visibility: hidden;
14+
}
15+
&.md-sidenav-closing {
16+
transform: translateX($close);
17+
will-change: transform;
18+
}
19+
&.md-sidenav-opening {
20+
visibility: visible;
21+
transform: translateX($open);
22+
will-change: transform;
23+
box-shadow: $md-shadow-bottom-z-1;
24+
}
25+
&.md-sidenav-opened {
26+
transform: translateX($open);
27+
box-shadow: $md-shadow-bottom-z-1;
28+
}
29+
}
30+
31+
32+
:host {
33+
position: relative;
34+
display: block;
35+
// Use a transform to create a new stacking context.
36+
transform: translate3D(0, 0, 0);
37+
overflow-x: hidden;
38+
39+
transition: margin-left $swift-ease-out-duration $swift-ease-out-timing-function,
40+
margin-right $swift-ease-out-duration $swift-ease-out-timing-function;
41+
42+
& > .md-sidenav-backdrop {
43+
position: absolute;
44+
top: 0;
45+
left: 0;
46+
right: 0;
47+
bottom: 0;
48+
z-index: $z-index-drawer;
49+
visibility: hidden;
50+
display: block;
51+
52+
&.md-sidenav-shown {
53+
visibility: visible;
54+
background-color: rgba(0, 0, 0, 0.21);
55+
transition: background-color $swift-ease-out-duration $swift-ease-out-timing-function;
56+
}
57+
}
58+
59+
& > md-content {
60+
display: block;
61+
transition: margin-left $swift-ease-out-duration $swift-ease-out-timing-function,
62+
margin-right $swift-ease-out-duration $swift-ease-out-timing-function;
63+
}
64+
65+
> md-sidenav {
66+
position: fixed;
67+
top: 0;
68+
bottom: 0;
69+
z-index: $z-index-drawer + 1;
70+
background-color: $md-sidenav-background-color;
71+
72+
transition: transform $swift-ease-out-duration $swift-ease-out-timing-function;
73+
74+
@include md-sidenav-transition(0, -100%);
75+
76+
&.md-sidenav-side {
77+
z-index: $z-index-drawer - 1;
78+
}
79+
80+
&.md-sidenav-end {
81+
right: 0;
82+
83+
@include md-sidenav-transition(0, 100%);
84+
}
85+
}
86+
}
87+
88+
89+
:host-context([dir="rtl"]) {
90+
> md-sidenav {
91+
@include md-sidenav-transition(0, 100%);
92+
93+
&.md-sidenav-end {
94+
left: 0;
95+
right: auto;
96+
97+
@include md-sidenav-transition(0, -100%);
98+
}
99+
}
100+
}

src/components/sidenav/sidenav.ts

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import {
2+
AfterContentInit,
3+
Component,
4+
ContentChildren,
5+
ElementRef,
6+
EventEmitter,
7+
Host,
8+
HostBinding,
9+
Input,
10+
View,
11+
ViewEncapsulation,
12+
OnChanges,
13+
Optional,
14+
Output,
15+
Query,
16+
QueryList,
17+
SimpleChange
18+
} from 'angular2/core';
19+
import {BaseException} from 'angular2/src/facade/exceptions';
20+
import {Dir} from "../../directives/dir/dir";
21+
import {MdMediaQuery} from "../../services/media-query/media-query";
22+
23+
24+
/**
25+
* Exception thrown when a MdSidenavContainer is missing both sidenavs.
26+
*/
27+
export class MdMissingSidenavException extends BaseException {}
28+
export class MdDuplicatedSidenavException extends BaseException {
29+
constructor(align: string) {
30+
super(`A sidenav was already declared for 'align="${align}"'`);
31+
}
32+
}
33+
34+
35+
@Component({
36+
selector: 'md-sidenav',
37+
template: '<ng-content></ng-content>',
38+
})
39+
export class MdSidenav {
40+
@Input('align') private align_: string = 'start';
41+
@Input('mode') private mode_: string = 'over';
42+
43+
@Input('opened') private opened_: boolean = false;
44+
45+
@Input() get width() {
46+
if (this.elementRef_.nativeElement) {
47+
return this.elementRef_.nativeElement.offsetWidth;
48+
}
49+
return 0;
50+
}
51+
52+
@Output('open-start') onOpenStart = new EventEmitter<void>();
53+
@Output('open') onOpen = new EventEmitter<void>();
54+
@Output('close-start') onCloseStart = new EventEmitter<void>();
55+
@Output('close') onClose = new EventEmitter<void>();
56+
57+
@HostBinding('class.md-sidenav-closing') get isClosing() {
58+
return !this.opened_ && this.transition_;
59+
}
60+
@HostBinding('class.md-sidenav-opening') get isOpening() {
61+
return this.opened_ && this.transition_;
62+
}
63+
@HostBinding('class.md-sidenav-closed') get isClosed() {
64+
return !this.opened_ && !this.transition_;
65+
}
66+
@HostBinding('class.md-sidenav-opened') get isOpened() {
67+
return this.opened_ && !this.transition_;
68+
}
69+
@HostBinding('class.md-sidenav-end') get isEnd() {
70+
return this.align_ == 'end';
71+
}
72+
@HostBinding('class.md-sidenav-side') get modeSide() {
73+
return this.mode_ == 'side';
74+
}
75+
@HostBinding('class.md-sidenav-over') get modeOver() {
76+
return this.mode_ == 'over';
77+
}
78+
79+
private transition_: boolean = false;
80+
81+
private onTransitionEnd_(): void {
82+
this.transition_ = false;
83+
if (this.opened_) {
84+
this.onOpen.emit(null);
85+
} else {
86+
this.onClose.emit(null);
87+
}
88+
}
89+
90+
// TODO(hansl): Get rid of ElementRef.
91+
constructor(private mediaQuery_: MdMediaQuery,
92+
private elementRef_: ElementRef) {
93+
elementRef_.nativeElement.addEventListener('transitionend',
94+
(e: TransitionEvent) => {
95+
if (e.target == elementRef_.nativeElement && e.propertyName == 'transform') {
96+
return this.onTransitionEnd_();
97+
}
98+
}, false);
99+
}
100+
101+
get align(): string { return this.align_; }
102+
set align(v: string) {
103+
if (v != 'end') {
104+
v = 'start';
105+
}
106+
this.align_ = v;
107+
}
108+
109+
get mode(): string { return this.mode_; }
110+
set mode(v: string) {
111+
if (v != 'side') {
112+
v = 'over';
113+
}
114+
this.mode_ = v;
115+
}
116+
117+
get opened(): boolean { return this.opened_; }
118+
set opened(v: boolean) {
119+
this.opened_ = v;
120+
this.transition_ = true;
121+
if (v) {
122+
this.onOpenStart.emit(null);
123+
} else {
124+
this.onCloseStart.emit(null);
125+
}
126+
}
127+
128+
open(): Promise<void> {
129+
return this.toggle(true);
130+
}
131+
close(): Promise<void> {
132+
return this.toggle(false);
133+
}
134+
135+
toggle(isOpen: boolean = !this.opened): Promise<void> {
136+
if (isOpen === this.opened) {
137+
return Promise.resolve();
138+
}
139+
140+
this.opened = isOpen;
141+
142+
// We hook up both onOpen and onClose, but we reject the promise if
143+
// the animation was cut or cancelled for some reason.
144+
return new Promise<void>((resolve, reject) => {
145+
const property = isOpen ? this.onOpen : this.onClose;
146+
const other = isOpen ? this.onClose : this.onOpen;
147+
const subscription = property.subscribe(() => {
148+
resolve();
149+
property.remove(subscription);
150+
other.remove(otherSubscription);
151+
});
152+
const otherSubscription = property.subscribe(() => {
153+
reject();
154+
property.remove(subscription);
155+
other.remove(otherSubscription);
156+
});
157+
});
158+
}
159+
}
160+
161+
162+
@Component({
163+
selector: 'md-sidenav-container',
164+
directives: [MdSidenav],
165+
templateUrl: './components/sidenav/sidenav.html',
166+
styleUrls: ['./components/sidenav/sidenav.css'],
167+
})
168+
export class MdSidenavContainer implements AfterContentInit {
169+
@ContentChildren(MdSidenav) private drawers_: QueryList<MdSidenav>;
170+
171+
@Input() get start() { return this.start_; }
172+
@Input() get end() { return this.end_; }
173+
174+
private start_: MdSidenav;
175+
private end_: MdSidenav;
176+
private right_: MdSidenav;
177+
private left_: MdSidenav;
178+
179+
private validateDrawers_() {
180+
this.start_ = this.end_ = null;
181+
if (this.drawers_.length === 0) {
182+
throw new MdMissingSidenavException();
183+
}
184+
185+
for (const drawer of this.drawers_.toArray()) {
186+
if (drawer.align == 'end') {
187+
if (this.end_) {
188+
throw new MdDuplicatedSidenavException('end');
189+
}
190+
this.end_ = drawer;
191+
} else {
192+
if (this.start_) {
193+
throw new MdDuplicatedSidenavException('start');
194+
}
195+
this.start_ = drawer;
196+
}
197+
}
198+
199+
this.right_ = this.left_ = null;
200+
this.left_ = this.start_;
201+
this.right_ = this.end_;
202+
203+
// Detect if we're LTR or RTL.
204+
if (this.dir_.dir == 'ltr') {
205+
this.left_ = this.start_;
206+
this.right_ = this.end_;
207+
} else {
208+
this.left_ = this.end_;
209+
this.right_ = this.start_;
210+
}
211+
}
212+
213+
constructor(@Optional() @Host() private dir_: Dir) {
214+
this.dir_.onDirChange.subscribe(() => this.validateDrawers_());
215+
}
216+
217+
ngAfterContentInit() {
218+
// On changes, assert on consistency.
219+
this.drawers_.changes.subscribe(() => this.validateDrawers_());
220+
this.validateDrawers_();
221+
}
222+
}

src/core/style/_variables.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ $md-body-font-size-base: rem(1.400) !default;
66

77
// z-index master list
88
$z-index-fab: 20 !default;
9+
$z-index-drawer: 100 !default;
910

1011
// Easing Curves
1112
// TODO(jelbourn): all of these need to be revisited

src/demo-app/demo-app.html

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,43 @@
1-
<p>
2-
material2 Works!
1+
<button md-raised-button (click)="root.dir = (root.dir == 'rtl' ? 'ltr' : 'rtl')">
2+
{{root.dir.toUpperCase()}}
3+
</button>
34

4-
<button md-button>HELLO</button>
5-
<button md-raised-button class="md-primary">HELLO</button>
6-
<button md-fab class="md-accent">HI</button>
7-
</p>
5+
<div #root="$implicit" dir="ltr">
6+
<h1>material2 Works!</h1>
7+
8+
<md-sidenav-container>
9+
<md-sidenav #start (open)="myinput.focus()" mode="side">
10+
Start Side Drawer
11+
<br>
12+
<button md-button (click)="start.close()">Close</button>
13+
<br>
14+
<button md-button (click)="end.open()">Open End Side</button>
15+
<br>
16+
<button md-button (click)="start.mode = (start.mode == 'side' ? 'over': 'side')">Toggle Mode</button>
17+
<br>
18+
<input #myinput>
19+
</md-sidenav>
20+
21+
<md-sidenav #end align="end">
22+
End Side Drawer
23+
<br>
24+
<button md-button (click)="end.close()">Close</button>
25+
</md-sidenav>
26+
27+
<div class="content">
28+
<h1>My Content</h1>
29+
30+
<div>
31+
<header>Sidenav</header>
32+
<button md-button (click)="start.toggle()">Toggle Start Side Drawer</button>
33+
<button md-button (click)="end.toggle()">Toggle End Side Drawer</button>
34+
</div>
35+
36+
<button md-button>HELLO</button>
37+
<button md-raised-button class="md-primary">HELLO</button>
38+
<button md-fab class="md-accent">HI</button>
39+
</div>
40+
</md-sidenav-container>
41+
42+
<h2>Content after Sidenav</h2>
43+
</div>

0 commit comments

Comments
 (0)