feat(sidenav): Add support for fixed sidenavs (#6712)

This commit is contained in:
mmalerba
2017-09-21 13:47:09 -07:00
committed by Jeremy Elbourn
parent dbae36035d
commit 61579bc42b
19 changed files with 597 additions and 266 deletions
+9 -12
View File
@@ -1,10 +1,5 @@
import {
Component,
ViewEncapsulation,
ElementRef,
Renderer2,
} from '@angular/core';
import {OverlayContainer} from '@angular/cdk/overlay';
import {Component, ElementRef, Renderer2, ViewEncapsulation} from '@angular/core';
/**
* The entry app for demo site. Routes under `accessibility` will use AccessibilityDemo component,
@@ -48,22 +43,25 @@ export class DemoApp {
dark = false;
navItems = [
{name: 'Autocomplete', route: '/autocomplete'},
{name: 'Button', route: '/button'},
{name: 'Button Toggle', route: '/button-toggle'},
{name: 'Button', route: '/button'},
{name: 'Card', route: '/card'},
{name: 'Chips', route: '/chips'},
{name: 'Checkbox', route: '/checkbox'},
{name: 'Chips', route: '/chips'},
{name: 'Datepicker', route: '/datepicker'},
{name: 'Dialog', route: '/dialog'},
{name: 'Drawer', route: '/drawer'},
{name: 'Expansion Panel', route: '/expansion'},
{name: 'Focus Origin', route: '/focus-origin'},
{name: 'Gestures', route: '/gestures'},
{name: 'Grid List', route: '/grid-list'},
{name: 'Icon', route: '/icon'},
{name: 'Input', route: '/input'},
{name: 'List', route: '/list'},
{name: 'Menu', route: '/menu'},
{name: 'Live Announcer', route: '/live-announcer'},
{name: 'Menu', route: '/menu'},
{name: 'Overlay', route: '/overlay'},
{name: 'Platform', route: '/platform'},
{name: 'Portal', route: '/portal'},
{name: 'Progress Bar', route: '/progress-bar'},
{name: 'Progress Spinner', route: '/progress-spinner'},
@@ -71,16 +69,15 @@ export class DemoApp {
{name: 'Ripple', route: '/ripple'},
{name: 'Select', route: '/select'},
{name: 'Sidenav', route: '/sidenav'},
{name: 'Slider', route: '/slider'},
{name: 'Slide Toggle', route: '/slide-toggle'},
{name: 'Slider', route: '/slider'},
{name: 'Snack Bar', route: '/snack-bar'},
{name: 'Stepper', route: '/stepper'},
{name: 'Style', route: '/style'},
{name: 'Table', route: '/table'},
{name: 'Tabs', route: '/tabs'},
{name: 'Toolbar', route: '/toolbar'},
{name: 'Tooltip', route: '/tooltip'},
{name: 'Platform', route: '/platform'},
{name: 'Focus Origin', route: '/focus-origin'},
{name: 'Typography', route: '/typography'}
];
+57 -58
View File
@@ -1,52 +1,50 @@
import {NgModule} from '@angular/core';
import {FullscreenOverlayContainer, OverlayContainer} from '@angular/cdk/overlay';
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {RouterModule} from '@angular/router';
import {AutocompleteDemo} from '../autocomplete/autocomplete-demo';
import {BaselineDemo} from '../baseline/baseline-demo';
import {ButtonToggleDemo} from '../button-toggle/button-toggle-demo';
import {ButtonDemo} from '../button/button-demo';
import {CardDemo} from '../card/card-demo';
import {CheckboxDemo, MdCheckboxDemoNestedChecklist} from '../checkbox/checkbox-demo';
import {ChipsDemo} from '../chips/chips-demo';
import {DatepickerDemo} from '../datepicker/datepicker-demo';
import {DemoMaterialModule} from '../demo-material-module';
import {ContentElementDialog, DialogDemo, IFrameDialog, JazzDialog} from '../dialog/dialog-demo';
import {DrawerDemo} from '../drawer/drawer-demo';
import {ExpansionDemo} from '../expansion/expansion-demo';
import {FocusOriginDemo} from '../focus-origin/focus-origin-demo';
import {GesturesDemo} from '../gestures/gestures-demo';
import {GridListDemo} from '../grid-list/grid-list-demo';
import {IconDemo} from '../icon/icon-demo';
import {InputDemo} from '../input/input-demo';
import {ListDemo} from '../list/list-demo';
import {LiveAnnouncerDemo} from '../live-announcer/live-announcer-demo';
import {MenuDemo} from '../menu/menu-demo';
import {OverlayDemo, RotiniPanel, SpagettiPanel} from '../overlay/overlay-demo';
import {PlatformDemo} from '../platform/platform-demo';
import {PortalDemo, ScienceJoke} from '../portal/portal-demo';
import {ProgressBarDemo} from '../progress-bar/progress-bar-demo';
import {ProgressSpinnerDemo} from '../progress-spinner/progress-spinner-demo';
import {RadioDemo} from '../radio/radio-demo';
import {RippleDemo} from '../ripple/ripple-demo';
import {SelectDemo} from '../select/select-demo';
import {SidenavDemo} from '../sidenav/sidenav-demo';
import {SlideToggleDemo} from '../slide-toggle/slide-toggle-demo';
import {SliderDemo} from '../slider/slider-demo';
import {SnackBarDemo} from '../snack-bar/snack-bar-demo';
import {StepperDemo} from '../stepper/stepper-demo';
import {PeopleDatabase} from '../table/people-database';
import {TableDemo} from '../table/table-demo';
import {TableHeaderDemo} from '../table/table-header-demo';
import {FoggyTabContent, RainyTabContent, SunnyTabContent, TabsDemo} from '../tabs/tabs-demo';
import {ToolbarDemo} from '../toolbar/toolbar-demo';
import {TooltipDemo} from '../tooltip/tooltip-demo';
import {TypographyDemo} from '../typography/typography-demo';
import {DemoApp, Home} from './demo-app';
import {DEMO_APP_ROUTES} from './routes';
import {ProgressBarDemo} from '../progress-bar/progress-bar-demo';
import {ContentElementDialog, DialogDemo, IFrameDialog, JazzDialog} from '../dialog/dialog-demo';
import {RippleDemo} from '../ripple/ripple-demo';
import {IconDemo} from '../icon/icon-demo';
import {GesturesDemo} from '../gestures/gestures-demo';
import {CardDemo} from '../card/card-demo';
import {ChipsDemo} from '../chips/chips-demo';
import {RadioDemo} from '../radio/radio-demo';
import {ButtonToggleDemo} from '../button-toggle/button-toggle-demo';
import {ProgressSpinnerDemo} from '../progress-spinner/progress-spinner-demo';
import {TooltipDemo} from '../tooltip/tooltip-demo';
import {ListDemo} from '../list/list-demo';
import {BaselineDemo} from '../baseline/baseline-demo';
import {GridListDemo} from '../grid-list/grid-list-demo';
import {LiveAnnouncerDemo} from '../live-announcer/live-announcer-demo';
import {OverlayDemo, RotiniPanel, SpagettiPanel} from '../overlay/overlay-demo';
import {SlideToggleDemo} from '../slide-toggle/slide-toggle-demo';
import {ToolbarDemo} from '../toolbar/toolbar-demo';
import {ButtonDemo} from '../button/button-demo';
import {CheckboxDemo, MdCheckboxDemoNestedChecklist} from '../checkbox/checkbox-demo';
import {SelectDemo} from '../select/select-demo';
import {SliderDemo} from '../slider/slider-demo';
import {SidenavDemo} from '../sidenav/sidenav-demo';
import {SnackBarDemo} from '../snack-bar/snack-bar-demo';
import {PortalDemo, ScienceJoke} from '../portal/portal-demo';
import {MenuDemo} from '../menu/menu-demo';
import {FoggyTabContent, RainyTabContent, SunnyTabContent, TabsDemo} from '../tabs/tabs-demo';
import {PlatformDemo} from '../platform/platform-demo';
import {AutocompleteDemo} from '../autocomplete/autocomplete-demo';
import {InputDemo} from '../input/input-demo';
import {FocusOriginDemo} from '../focus-origin/focus-origin-demo';
import {TableDemo} from '../table/table-demo';
import {PeopleDatabase} from '../table/people-database';
import {DatepickerDemo} from '../datepicker/datepicker-demo';
import {TypographyDemo} from '../typography/typography-demo';
import {ExpansionDemo} from '../expansion/expansion-demo';
import {StepperDemo} from '../stepper/stepper-demo';
import {DemoMaterialModule} from '../demo-material-module';
import {
FullscreenOverlayContainer,
OverlayContainer,
} from '@angular/cdk/overlay';
import {TableHeaderDemo} from '../table/table-header-demo';
@NgModule({
imports: [
@@ -62,29 +60,34 @@ import {TableHeaderDemo} from '../table/table-header-demo';
ButtonDemo,
ButtonToggleDemo,
CardDemo,
ChipsDemo,
CheckboxDemo,
ChipsDemo,
ContentElementDialog,
DatepickerDemo,
DemoApp,
DialogDemo,
DrawerDemo,
ExpansionDemo,
FocusOriginDemo,
FoggyTabContent,
GesturesDemo,
GridListDemo,
Home,
IconDemo,
IFrameDialog,
InputDemo,
JazzDialog,
ContentElementDialog,
IFrameDialog,
ListDemo,
LiveAnnouncerDemo,
MdCheckboxDemoNestedChecklist,
MenuDemo,
SnackBarDemo,
OverlayDemo,
PlatformDemo,
PortalDemo,
ProgressBarDemo,
ProgressSpinnerDemo,
RadioDemo,
RainyTabContent,
RippleDemo,
RotiniPanel,
ScienceJoke,
@@ -92,30 +95,26 @@ import {TableHeaderDemo} from '../table/table-header-demo';
SidenavDemo,
SliderDemo,
SlideToggleDemo,
SnackBarDemo,
SpagettiPanel,
StepperDemo,
FocusOriginDemo,
SunnyTabContent,
TableDemo,
TableHeaderDemo,
TabsDemo,
ToolbarDemo,
TooltipDemo,
TableDemo,
TabsDemo,
SunnyTabContent,
RainyTabContent,
FoggyTabContent,
PlatformDemo,
TypographyDemo,
ExpansionDemo,
],
providers: [
{provide: OverlayContainer, useClass: FullscreenOverlayContainer},
PeopleDatabase
],
entryComponents: [
DemoApp,
JazzDialog,
ContentElementDialog,
DemoApp,
IFrameDialog,
JazzDialog,
RotiniPanel,
ScienceJoke,
SpagettiPanel,
+53 -52
View File
@@ -1,85 +1,86 @@
import {Routes} from '@angular/router';
import {Home} from './demo-app';
import {ButtonDemo} from '../button/button-demo';
import {AccessibilityDemo} from '../a11y/a11y';
import {ACCESSIBILITY_DEMO_ROUTES} from '../a11y/routes';
import {AutocompleteDemo} from '../autocomplete/autocomplete-demo';
import {BaselineDemo} from '../baseline/baseline-demo';
import {ButtonToggleDemo} from '../button-toggle/button-toggle-demo';
import {TabsDemo} from '../tabs/tabs-demo';
import {GridListDemo} from '../grid-list/grid-list-demo';
import {GesturesDemo} from '../gestures/gestures-demo';
import {LiveAnnouncerDemo} from '../live-announcer/live-announcer-demo';
import {ListDemo} from '../list/list-demo';
import {IconDemo} from '../icon/icon-demo';
import {ToolbarDemo} from '../toolbar/toolbar-demo';
import {ButtonDemo} from '../button/button-demo';
import {CardDemo} from '../card/card-demo';
import {CheckboxDemo} from '../checkbox/checkbox-demo';
import {ChipsDemo} from '../chips/chips-demo';
import {DatepickerDemo} from '../datepicker/datepicker-demo';
import {DialogDemo} from '../dialog/dialog-demo';
import {DrawerDemo} from '../drawer/drawer-demo';
import {ExpansionDemo} from '../expansion/expansion-demo';
import {FocusOriginDemo} from '../focus-origin/focus-origin-demo';
import {GesturesDemo} from '../gestures/gestures-demo';
import {GridListDemo} from '../grid-list/grid-list-demo';
import {IconDemo} from '../icon/icon-demo';
import {InputDemo} from '../input/input-demo';
import {ListDemo} from '../list/list-demo';
import {LiveAnnouncerDemo} from '../live-announcer/live-announcer-demo';
import {MenuDemo} from '../menu/menu-demo';
import {OverlayDemo} from '../overlay/overlay-demo';
import {PlatformDemo} from '../platform/platform-demo';
import {PortalDemo} from '../portal/portal-demo';
import {ProgressBarDemo} from '../progress-bar/progress-bar-demo';
import {ProgressSpinnerDemo} from '../progress-spinner/progress-spinner-demo';
import {RadioDemo} from '../radio/radio-demo';
import {RippleDemo} from '../ripple/ripple-demo';
import {SelectDemo} from '../select/select-demo';
import {SidenavDemo} from '../sidenav/sidenav-demo';
import {SlideToggleDemo} from '../slide-toggle/slide-toggle-demo';
import {SliderDemo} from '../slider/slider-demo';
import {RadioDemo} from '../radio/radio-demo';
import {CardDemo} from '../card/card-demo';
import {ChipsDemo} from '../chips/chips-demo';
import {MenuDemo} from '../menu/menu-demo';
import {RippleDemo} from '../ripple/ripple-demo';
import {DialogDemo} from '../dialog/dialog-demo';
import {TooltipDemo} from '../tooltip/tooltip-demo';
import {SnackBarDemo} from '../snack-bar/snack-bar-demo';
import {TABS_DEMO_ROUTES} from '../tabs/routes';
import {PlatformDemo} from '../platform/platform-demo';
import {AutocompleteDemo} from '../autocomplete/autocomplete-demo';
import {InputDemo} from '../input/input-demo';
import {FocusOriginDemo} from '../focus-origin/focus-origin-demo';
import {DatepickerDemo} from '../datepicker/datepicker-demo';
import {TableDemo} from '../table/table-demo';
import {TypographyDemo} from '../typography/typography-demo';
import {ExpansionDemo} from '../expansion/expansion-demo';
import {StepperDemo} from '../stepper/stepper-demo';
import {DemoApp} from './demo-app';
import {AccessibilityDemo} from '../a11y/a11y';
import {ACCESSIBILITY_DEMO_ROUTES} from '../a11y/routes';
import {TableDemo} from '../table/table-demo';
import {TABS_DEMO_ROUTES} from '../tabs/routes';
import {TabsDemo} from '../tabs/tabs-demo';
import {ToolbarDemo} from '../toolbar/toolbar-demo';
import {TooltipDemo} from '../tooltip/tooltip-demo';
import {TypographyDemo} from '../typography/typography-demo';
import {DemoApp, Home} from './demo-app';
export const DEMO_APP_ROUTES: Routes = [
{path: '', component: DemoApp, children: [
{path: '', component: Home},
{path: 'autocomplete', component: AutocompleteDemo},
{path: 'baseline', component: BaselineDemo},
{path: 'button', component: ButtonDemo},
{path: 'button-toggle', component: ButtonToggleDemo},
{path: 'card', component: CardDemo},
{path: 'checkbox', component: CheckboxDemo},
{path: 'chips', component: ChipsDemo},
{path: 'table', component: TableDemo},
{path: 'datepicker', component: DatepickerDemo},
{path: 'dialog', component: DialogDemo},
{path: 'drawer', component: DrawerDemo},
{path: 'expansion', component: ExpansionDemo},
{path: 'focus-origin', component: FocusOriginDemo},
{path: 'gestures', component: GesturesDemo},
{path: 'grid-list', component: GridListDemo},
{path: 'icon', component: IconDemo},
{path: 'input', component: InputDemo},
{path: 'list', component: ListDemo},
{path: 'live-announcer', component: LiveAnnouncerDemo},
{path: 'menu', component: MenuDemo},
{path: 'overlay', component: OverlayDemo},
{path: 'platform', component: PlatformDemo},
{path: 'portal', component: PortalDemo},
{path: 'progress-bar', component: ProgressBarDemo},
{path: 'progress-spinner', component: ProgressSpinnerDemo},
{path: 'radio', component: RadioDemo},
{path: 'ripple', component: RippleDemo},
{path: 'select', component: SelectDemo},
{path: 'sidenav', component: SidenavDemo},
{path: 'slide-toggle', component: SlideToggleDemo},
{path: 'slider', component: SliderDemo},
{path: 'progress-spinner', component: ProgressSpinnerDemo},
{path: 'progress-bar', component: ProgressBarDemo},
{path: 'portal', component: PortalDemo},
{path: 'overlay', component: OverlayDemo},
{path: 'checkbox', component: CheckboxDemo},
{path: 'input', component: InputDemo},
{path: 'toolbar', component: ToolbarDemo},
{path: 'icon', component: IconDemo},
{path: 'list', component: ListDemo},
{path: 'menu', component: MenuDemo},
{path: 'live-announcer', component: LiveAnnouncerDemo},
{path: 'gestures', component: GesturesDemo},
{path: 'grid-list', component: GridListDemo},
{path: 'tabs', component: TabsDemo, children: TABS_DEMO_ROUTES},
{path: 'button-toggle', component: ButtonToggleDemo},
{path: 'baseline', component: BaselineDemo},
{path: 'ripple', component: RippleDemo},
{path: 'dialog', component: DialogDemo},
{path: 'tooltip', component: TooltipDemo},
{path: 'snack-bar', component: SnackBarDemo},
{path: 'platform', component: PlatformDemo},
{path: 'focus-origin', component: FocusOriginDemo},
{path: 'stepper', component: StepperDemo},
{path: 'table', component: TableDemo},
{path: 'tabs', component: TabsDemo, children: TABS_DEMO_ROUTES},
{path: 'toolbar', component: ToolbarDemo},
{path: 'tooltip', component: TooltipDemo},
{path: 'typography', component: TypographyDemo},
{path: 'expansion', component: ExpansionDemo},
{path: 'stepper', component: StepperDemo}
]}
];
+87
View File
@@ -0,0 +1,87 @@
<h2>Basic Use Case</h2>
<md-drawer-container class="demo-drawer-container">
<md-drawer #start (open)="myinput.focus()" mode="side">
Start Side Drawer
<br>
<button md-button (click)="start.close()">Close</button>
<br>
<button md-button (click)="end.open()">Open End Side</button>
<br>
<button md-button
(click)="start.mode = (start.mode == 'push' ? 'over' : (start.mode == 'over' ? 'side' : 'push'))">Toggle Mode</button>
<div>Mode: {{start.mode}}</div>
<br>
<input #myinput>
</md-drawer>
<md-drawer #end position="end">
End Side Drawer
<br>
<button md-button (click)="end.close()">Close</button>
</md-drawer>
<div class="demo-drawer-content">
<h1>My Content</h1>
<div>
<header>Drawer</header>
<button md-button (click)="start.toggle()">Toggle Start Side Drawer</button>
<button md-button (click)="end.toggle()">Toggle End Side Drawer</button>
</div>
<button md-button>HELLO</button>
<button md-raised-button class="mat-primary">HELLO</button>
<button md-fab class="mat-accent">HI</button>
</div>
</md-drawer-container>
<h2>Drawer Already Opened</h2>
<md-drawer-container class="demo-drawer-container">
<md-drawer #start2 opened mode="side">
Drawer
</md-drawer>
<div class="demo-drawer-content">
<button md-button (click)="start2.toggle()">Toggle Start Side Drawer</button>
</div>
</md-drawer-container>
<h2>Dynamic Position Drawer</h2>
<md-drawer-container class="demo-drawer-container">
<md-drawer #dynamicPosDrawer1 mode="side" [position]="invert ? 'end' : 'start'">Start</md-drawer>
<md-drawer #dynamicPosDrawer2 mode="side" [position]="invert ? 'start' : 'end'">End</md-drawer>
<div class="demo-drawer-content">
<button (click)="dynamicPosDrawer1.toggle(); dynamicPosDrawer2.toggle()">
Toggle drawers
</button>
<button (click)="invert = !invert">Change sides</button>
</div>
</md-drawer-container>
<h2>Drawer with focus attributes</h2>
<md-drawer-container class="demo-drawer-container">
<md-drawer #focusDrawer>
<md-nav-list>
<a md-list-item routerLink>Link</a>
<a md-list-item routerLink cdk-focus-region-start>Focus region start</a>
<a md-list-item routerLink>Link</a>
<a md-list-item routerLink cdk-focus-initial>Initially focused</a>
<a md-list-item routerLink cdk-focus-region-end>Focus region end</a>
<a md-list-item routerLink>Link</a>
</md-nav-list>
</md-drawer>
<div class="demo-drawer-content">
<h1>My Content</h1>
<div>
<header>Drawer</header>
<button md-button (click)="focusDrawer.toggle()">Toggle Drawer</button>
</div>
</div>
</md-drawer-container>
+7
View File
@@ -0,0 +1,7 @@
.demo-drawer-container {
border: 3px solid black;
}
.demo-drawer-content {
padding: 15px;
}
+14
View File
@@ -0,0 +1,14 @@
import {Component, ViewEncapsulation} from '@angular/core';
@Component({
moduleId: module.id,
selector: 'drawer-demo',
templateUrl: 'drawer-demo.html',
styleUrls: ['drawer-demo.css'],
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
})
export class DrawerDemo {
invert = false;
}
+54 -81
View File
@@ -1,87 +1,60 @@
<h2>Basic Use Case</h2>
<button md-raised-button (click)="isLaunched = true" *ngIf="!isLaunched" color="primary">
Launch Fullscreen Sidenav Demo
</button>
<md-sidenav-container class="demo-sidenav-container">
<md-sidenav #start (open)="myinput.focus()" mode="side">
Start Side Drawer
<br>
<button md-button (click)="start.close()">Close</button>
<br>
<button md-button (click)="end.open()">Open End Side</button>
<br>
<button md-button
(click)="start.mode = (start.mode == 'push' ? 'over' : (start.mode == 'over' ? 'side' : 'push'))">Toggle Mode</button>
<div>Mode: {{start.mode}}</div>
<br>
<input #myinput>
</md-sidenav>
<div class="demo-sidenav-area" *ngIf="isLaunched">
<md-toolbar *ngIf="showHeader && !coverHeader">Header</md-toolbar>
<md-sidenav-container>
<md-sidenav #start (open)="myinput.focus()" [mode]="mode"
[fixedInViewport]="fixed" [fixedTopGap]="fixedTop" [fixedBottomGap]="fixedBottom">
Start Side Sidenav
<br>
<button md-button (click)="start.close()">Close</button>
<br>
<button md-button (click)="end.open()">Open End Side</button>
<br>
<button md-button (click)="modeIndex = (modeIndex + 1) % 3">Toggle Mode</button>
<div>Mode: {{start.mode}}</div>
<br>
<input #myinput>
<div class="demo-filler-content" *ngFor="let c of fillerContent">Filler Content</div>
</md-sidenav>
<md-sidenav #end position="end">
End Side Drawer
<br>
<button md-button (click)="end.close()">Close</button>
</md-sidenav>
<md-sidenav #end position="end"
[fixedInViewport]="fixed" [fixedTopGap]="fixedTop" [fixedBottomGap]="fixedBottom">
End Side Sidenav
<br>
<button md-button (click)="end.close()">Close</button>
<div class="demo-filler-content" *ngFor="let c of fillerContent">Filler Content</div>
</md-sidenav>
<div class="demo-sidenav-content">
<h1>My Content</h1>
<md-sidenav-content>
<md-toolbar *ngIf="showHeader && coverHeader">Header</md-toolbar>
<div class="demo-sidenav-content">
<div>
<button md-raised-button (click)="isLaunched = false">
Exit Fullscreen Sidenav Demo
</button>
</div>
<div>
<header>Sidenav</header>
<button md-button (click)="start.toggle()">Toggle Start Side Drawer</button>
<button md-button (click)="end.toggle()">Toggle End Side Drawer</button>
</div>
<div>
<h3>Sidenav</h3>
<button md-button (click)="start.toggle()">Toggle Start Side Sidenav</button>
<button md-button (click)="end.toggle()">Toggle End Side Sidenav</button>
<md-checkbox [(ngModel)]="fixed">Fixed mode</md-checkbox>
<md-checkbox [(ngModel)]="coverHeader">Sidenav covers header/footer</md-checkbox>
</div>
<button md-button>HELLO</button>
<button md-raised-button class="mat-primary">HELLO</button>
<button md-fab class="mat-accent">HI</button>
</div>
</md-sidenav-container>
<div>
<h3>Header / Footer</h3>
<md-checkbox [(ngModel)]="showHeader">Show header</md-checkbox>
<md-checkbox [(ngModel)]="showFooter">Show footer</md-checkbox>
</div>
<h2>Sidenav Already Opened</h2>
<md-sidenav-container class="demo-sidenav-container">
<md-sidenav #start2 opened mode="side">
Drawer
</md-sidenav>
<div class="demo-sidenav-content">
<button md-button (click)="start2.toggle()">Toggle Start Side Drawer</button>
</div>
</md-sidenav-container>
<h2>Dynamic Position Sidenav</h2>
<md-sidenav-container class="demo-sidenav-container">
<md-sidenav #dynamicPosSidenav1 mode="side" [position]="invert ? 'end' : 'start'">Start</md-sidenav>
<md-sidenav #dynamicPosSidenav2 mode="side" [position]="invert ? 'start' : 'end'">End</md-sidenav>
<div class="demo-sidenav-content">
<button (click)="dynamicPosSidenav1.toggle(); dynamicPosSidenav2.toggle()">
Toggle sidenavs
</button>
<button (click)="invert = !invert">Change sides</button>
</div>
</md-sidenav-container>
<h2>Sidenav with focus attributes</h2>
<md-sidenav-container class="demo-sidenav-container">
<md-sidenav #focusSidenav>
<md-nav-list>
<a md-list-item routerLink>Link</a>
<a md-list-item routerLink cdk-focus-region-start>Focus region start</a>
<a md-list-item routerLink>Link</a>
<a md-list-item routerLink cdk-focus-initial>Initially focused</a>
<a md-list-item routerLink cdk-focus-region-end>Focus region end</a>
<a md-list-item routerLink>Link</a>
</md-nav-list>
</md-sidenav>
<div class="demo-sidenav-content">
<h1>My Content</h1>
<div>
<header>Sidenav</header>
<button md-button (click)="focusSidenav.toggle()">Toggle Drawer</button>
</div>
</div>
</md-sidenav-container>
<div class="demo-filler-content" *ngFor="let c of fillerContent">Filler Content</div>
</div>
<md-toolbar *ngIf="showFooter && coverHeader">Footer</md-toolbar>
</md-sidenav-content>
</md-sidenav-container>
<md-toolbar *ngIf="showFooter && !coverHeader">Footer</md-toolbar>
</div>
+22 -8
View File
@@ -1,11 +1,25 @@
.demo-sidenav-container {
border: 3px solid black;
.demo-sidenav-area {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
flex-direction: column;
.mat-sidenav-focus-trap > .cdk-focus-trap-content {
padding: 10px;
.mat-toolbar {
flex: 0;
}
.mat-sidenav-container {
flex: 1;
}
.demo-sidenav-content {
padding: 32px;
}
.demo-filler-content {
padding: 60px;
}
}
.demo-sidenav-content {
padding: 15px;
}
+10 -2
View File
@@ -7,8 +7,16 @@ import {Component, ViewEncapsulation} from '@angular/core';
templateUrl: 'sidenav-demo.html',
styleUrls: ['sidenav-demo.css'],
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
})
export class SidenavDemo {
invert = false;
isLaunched = false;
fillerContent = Array(30);
fixed = false;
coverHeader = false;
showHeader = false;
showFooter = false;
modeIndex = 0;
get mode() { return ['side', 'over', 'push'][this.modeIndex]; }
get fixedTop() { return this.fixed && this.showHeader && !this.coverHeader ? 64 : 0; }
get fixedBottom() { return this.fixed && this.showFooter && !this.coverHeader ? 64 : 0; }
}
+5 -3
View File
@@ -1,8 +1,10 @@
<div class="mat-drawer-backdrop" (click)="_onBackdropClicked()"
[class.mat-drawer-shown]="_isShowingBackdrop()"></div>
<ng-content select="md-drawer, mat-drawer, md-sidenav, mat-sidenav"></ng-content>
<ng-content select="md-drawer, mat-drawer"></ng-content>
<div class="mat-drawer-content" [ngStyle]="_styles" cdk-scrollable>
<ng-content select="md-drawer-content, mat-drawer-content">
</ng-content>
<md-drawer-content *ngIf="!_content">
<ng-content></ng-content>
</div>
</md-drawer-content>
+1 -1
View File
@@ -6,7 +6,7 @@
transition: {
duration: $swift-ease-out-duration;
timing-function: $swift-ease-out-timing-function;
property: transform, margin-left, margin-right;
property: margin-left, margin-right;
}
}
-1
View File
@@ -1 +0,0 @@
<ng-content></ng-content>
+17 -8
View File
@@ -3,15 +3,21 @@
@import '../core/style/layout-common';
@import '../../cdk/a11y/a11y';
$mat-drawer-content-z-index: 1;
$mat-drawer-side-drawer-z-index: 2;
$mat-drawer-backdrop-z-index: 3;
$mat-drawer-over-drawer-z-index: 4;
// stylelint-disable max-line-length
// Mixin that creates a new stacking context.
// see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context
// stylelint-enable
@mixin mat-drawer-stacking-context() {
@mixin mat-drawer-stacking-context($z-index:1) {
position: relative;
// Use a transform to create a new stacking context.
transform: translate3d(0, 0, 0);
// Use a z-index to create a new stacking context. (We can't use transform because it breaks fixed
// positioning inside of the transformed element).
z-index: $z-index;
}
.mat-drawer-container {
@@ -47,7 +53,7 @@
// Because of the new stacking context, the z-index stack is new and we can use our own
// numbers.
z-index: 2;
z-index: $mat-drawer-backdrop-z-index;
// We use 'visibility: hidden | visible' because 'display: none' will not animate any
// transitions, while visibility will interpolate transitions properly.
@@ -65,7 +71,7 @@
}
.mat-drawer-content {
@include mat-drawer-stacking-context();
@include mat-drawer-stacking-context($mat-drawer-content-z-index);
display: block;
height: 100%;
@@ -73,7 +79,7 @@
}
.mat-drawer {
@include mat-drawer-stacking-context();
@include mat-drawer-stacking-context($mat-drawer-over-drawer-z-index);
display: block;
position: absolute;
@@ -83,12 +89,11 @@
min-width: 5vw;
outline: 0;
box-sizing: border-box;
height: 100%;
overflow-y: auto; // TODO(kara): revisit scrolling behavior for drawers
transform: translate3d(-100%, 0, 0);
&.mat-drawer-side {
z-index: 1;
z-index: $mat-drawer-side-drawer-z-index;
}
&.mat-drawer-end {
@@ -114,3 +119,7 @@
}
}
}
.mat-sidenav-fixed {
position: fixed;
}
+2
View File
@@ -76,6 +76,8 @@ describe('MdDrawer', () => {
it('should emit the backdropClick event when the backdrop is clicked', fakeAsync(() => {
let fixture = TestBed.createComponent(BasicTestApp);
fixture.detectChanges();
let testComponent: BasicTestApp = fixture.debugElement.componentInstance;
let openButtonElement = fixture.debugElement.query(By.css('.open')).nativeElement;
+101 -26
View File
@@ -16,9 +16,11 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChild,
ContentChildren,
ElementRef,
EventEmitter,
forwardRef,
Inject,
Input,
NgZone,
@@ -34,6 +36,7 @@ import {merge} from 'rxjs/observable/merge';
import {first} from 'rxjs/operator/first';
import {startWith} from 'rxjs/operator/startWith';
import {takeUntil} from 'rxjs/operator/takeUntil';
import {Subject} from 'rxjs/Subject';
import {Subscription} from 'rxjs/Subscription';
@@ -51,6 +54,42 @@ export class MdDrawerToggleResult {
constructor(public type: 'open' | 'close', public animationFinished: boolean) {}
}
@Component({
moduleId: module.id,
selector: 'md-drawer-content, mat-drawer-content',
template: '<ng-content></ng-content>',
host: {
'class': 'mat-drawer-content',
'[style.marginLeft.px]': '_margins.left',
'[style.marginRight.px]': '_margins.right',
},
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
})
export class MdDrawerContent implements AfterContentInit {
/**
* Margins to be applied to the content. These are used to push / shrink the drawer content when a
* drawer is open. We use margin rather than transform even for push mode because transform breaks
* fixed position elements inside of the transformed element.
*/
_margins: {left: number, right: number} = {left: 0, right: 0};
constructor(
private _changeDetectorRef: ChangeDetectorRef,
@Inject(forwardRef(() => MdDrawerContainer)) private _container: MdDrawerContainer) {
}
ngAfterContentInit() {
this._container._contentMargins.subscribe(margins => {
this._margins = margins;
this._changeDetectorRef.markForCheck();
});
}
}
/**
* <md-drawer> component.
*
@@ -61,7 +100,7 @@ export class MdDrawerToggleResult {
@Component({
moduleId: module.id,
selector: 'md-drawer, mat-drawer',
templateUrl: 'drawer.html',
template: '<ng-content></ng-content>',
animations: [
trigger('transform', [
state('open, open-instant', style({
@@ -121,7 +160,13 @@ export class MdDrawer implements AfterContentInit, OnDestroy {
set align(value) { this.position = value; }
/** Mode of the drawer; one of 'over', 'push' or 'side'. */
@Input() mode: 'over' | 'push' | 'side' = 'over';
@Input()
get mode() { return this._mode; }
set mode(value) {
this._mode = value;
this._modeChanged.next();
}
private _mode: 'over' | 'push' | 'side' = 'over';
/** Whether the drawer can be closed with the escape key or by clicking on the backdrop. */
@Input()
@@ -160,6 +205,12 @@ export class MdDrawer implements AfterContentInit, OnDestroy {
/** @deprecated */
@Output('align-changed') onAlignChanged = new EventEmitter<void>();
/**
* An observable that emits when the drawer mode changes. This is used by the drawer container to
* to know when to when the mode changes so it can adapt the margins on the content.
*/
_modeChanged = new Subject();
get isFocusTrapEnabled() {
// The focus trap is only enabled when the drawer is open in any mode other than side.
return this.opened && this.mode !== 'side';
@@ -298,6 +349,7 @@ export class MdDrawer implements AfterContentInit, OnDestroy {
}
}
/**
* <md-drawer-container> component.
*
@@ -322,6 +374,8 @@ export class MdDrawer implements AfterContentInit, OnDestroy {
export class MdDrawerContainer implements AfterContentInit, OnDestroy {
@ContentChildren(MdDrawer) _drawers: QueryList<MdDrawer>;
@ContentChild(MdDrawerContent) _content: MdDrawerContent;
/** The drawer child with the `start` position. */
get start() { return this._start; }
@@ -347,8 +401,7 @@ export class MdDrawerContainer implements AfterContentInit, OnDestroy {
/** Subscription to the Directionality change EventEmitter. */
private _dirChangeSubscription = Subscription.EMPTY;
/** Inline styles to be applied to the container. */
_styles: { marginLeft: string; marginRight: string; transform: string; };
_contentMargins = new Subject<{left: number, right: number}>();
constructor(@Optional() private _dir: Directionality, private _element: ElementRef,
private _renderer: Renderer2, private _ngZone: NgZone,
@@ -366,6 +419,7 @@ export class MdDrawerContainer implements AfterContentInit, OnDestroy {
this._drawers.forEach((drawer: MdDrawer) => {
this._watchDrawerToggle(drawer);
this._watchDrawerPosition(drawer);
this._watchDrawerMode(drawer);
});
});
}
@@ -394,7 +448,7 @@ export class MdDrawerContainer implements AfterContentInit, OnDestroy {
// Set the transition class on the container so that the animations occur. This should not
// be set initially because animations should only be triggered via a change in state.
this._renderer.addClass(this._element.nativeElement, 'mat-drawer-transition');
this._updateStyles();
this._updateContentMargins();
this._changeDetectorRef.markForCheck();
});
@@ -421,6 +475,16 @@ export class MdDrawerContainer implements AfterContentInit, OnDestroy {
});
}
/** Subscribes to changes in drawer mode so we can run change detection. */
private _watchDrawerMode(drawer: MdDrawer): void {
if (drawer) {
takeUntil.call(drawer._modeChanged, this._drawers.changes).subscribe(() => {
this._updateContentMargins();
this._changeDetectorRef.markForCheck();
});
}
}
/** Toggles the 'mat-drawer-opened' class on the main 'md-drawer-container' element. */
private _setContainerClass(isAdd: boolean): void {
if (isAdd) {
@@ -483,29 +547,40 @@ export class MdDrawerContainer implements AfterContentInit, OnDestroy {
}
/**
* Return the width of the drawer, if it's in the proper mode and opened.
* This may relayout the view, so do not call this often.
* @param drawer
* @param mode
* Recalculates and updates the inline styles for the content. Note that this should be used
* sparingly, because it causes a reflow.
*/
private _getDrawerEffectiveWidth(drawer: MdDrawer, mode: string): number {
return (this._isDrawerOpen(drawer) && drawer.mode == mode) ? drawer._width : 0;
}
private _updateContentMargins() {
// 1. For drawers in `over` mode, they don't affect the content.
// 2. For drawers in `side` mode they should shrink the content. We do this by adding to the
// left margin (for left drawer) or right margin (for right the drawer).
// 3. For drawers in `push` mode the should shift the content without resizing it. We do this by
// adding to the left or right margin and simultaneously subtracting the same amount of
// margin from the other side.
/**
* Recalculates and updates the inline styles. Note that this
* should be used sparingly, because it causes a reflow.
*/
private _updateStyles() {
const marginLeft = this._left ? this._getDrawerEffectiveWidth(this._left, 'side') : 0;
const marginRight = this._right ? this._getDrawerEffectiveWidth(this._right, 'side') : 0;
const leftWidth = this._left ? this._getDrawerEffectiveWidth(this._left, 'push') : 0;
const rightWidth = this._right ? this._getDrawerEffectiveWidth(this._right, 'push') : 0;
let left = 0;
let right = 0;
this._styles = {
marginLeft: `${marginLeft}px`,
marginRight: `${marginRight}px`,
transform: `translate3d(${leftWidth - rightWidth}px, 0, 0)`
};
if (this._left && this._left.opened) {
if (this._left.mode == 'side') {
left += this._left._width;
} else if (this._left.mode == 'push') {
let width = this._left._width;
left += width;
right -= width;
}
}
if (this._right && this._right.opened) {
if (this._right.mode == 'side') {
right += this._right._width;
} else if (this._right.mode == 'push') {
let width = this._right._width;
right += width;
left -= width;
}
}
this._contentMargins.next({left, right});
}
}
+10
View File
@@ -0,0 +1,10 @@
<div class="mat-drawer-backdrop" (click)="_onBackdropClicked()"
[class.mat-drawer-shown]="_isShowingBackdrop()"></div>
<ng-content select="md-sidenav, mat-sidenav"></ng-content>
<ng-content select="md-sidenav-content, mat-sidenav-content">
</ng-content>
<md-sidenav-content *ngIf="!_content">
<ng-content></ng-content>
</md-sidenav-content>
+21 -6
View File
@@ -6,18 +6,33 @@
* found in the LICENSE file at https://angular.io/license
*/
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {A11yModule} from '@angular/cdk/a11y';
import {OverlayModule} from '@angular/cdk/overlay';
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {MdCommonModule} from '@angular/material/core';
import {MdDrawer, MdDrawerContainer} from './drawer';
import {MdSidenav, MdSidenavContainer} from './sidenav';
import {MdDrawer, MdDrawerContainer, MdDrawerContent} from './drawer';
import {MdSidenav, MdSidenavContainer, MdSidenavContent} from './sidenav';
@NgModule({
imports: [CommonModule, MdCommonModule, A11yModule, OverlayModule],
exports: [MdDrawerContainer, MdDrawer, MdSidenavContainer, MdSidenav, MdCommonModule],
declarations: [MdDrawerContainer, MdDrawer, MdSidenavContainer, MdSidenav],
exports: [
MdCommonModule,
MdDrawer,
MdDrawerContainer,
MdDrawerContent,
MdSidenav,
MdSidenavContainer,
MdSidenavContent,
],
declarations: [
MdDrawer,
MdDrawerContainer,
MdDrawerContent,
MdSidenav,
MdSidenavContainer,
MdSidenavContent,
],
})
export class MdSidenavModule {}
+67
View File
@@ -0,0 +1,67 @@
import {Component} from '@angular/core';
import {async, TestBed, ComponentFixture} from '@angular/core/testing';
import {MdSidenav, MdSidenavModule} from './index';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {By} from '@angular/platform-browser';
describe('MdSidenav', () => {
let fixture: ComponentFixture<SidenavWithFixedPosition>;
let sidenavEl: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MdSidenavModule, NoopAnimationsModule],
declarations: [SidenavWithFixedPosition],
});
TestBed.compileComponents();
fixture = TestBed.createComponent(SidenavWithFixedPosition);
fixture.detectChanges();
sidenavEl = fixture.debugElement.query(By.directive(MdSidenav)).nativeElement;
}));
it('should be fixed position when in fixed mode', () => {
expect(sidenavEl.classList).toContain('mat-sidenav-fixed');
fixture.componentInstance.fixed = false;
fixture.detectChanges();
expect(sidenavEl.classList).not.toContain('mat-sidenav-fixed');
});
it('should set fixed bottom and top when in fixed mode', () => {
expect(sidenavEl.style.top).toBe('20px');
expect(sidenavEl.style.bottom).toBe('30px');
fixture.componentInstance.fixed = false;
fixture.detectChanges();
expect(sidenavEl.style.top).toBeFalsy();
expect(sidenavEl.style.bottom).toBeFalsy();
});
});
@Component({
template: `
<md-sidenav-container>
<md-sidenav
#drawer
[fixedInViewport]="fixed"
[fixedTopGap]="fixedTop"
[fixedBottomGap]="fixedBottom">
Drawer.
</md-sidenav>
<md-sidenav-content>
Some content.
</md-sidenav-content>
</md-sidenav-container>`,
})
class SidenavWithFixedPosition {
fixed = true;
fixedTop = 20;
fixedBottom = 30;
}
+60 -8
View File
@@ -7,19 +7,42 @@
*/
import {
ChangeDetectionStrategy,
Component,
ContentChildren,
ChangeDetectionStrategy, ChangeDetectorRef,
Component, ContentChild,
ContentChildren, forwardRef, Inject, Input,
ViewEncapsulation
} from '@angular/core';
import {MdDrawer, MdDrawerContainer} from './drawer';
import {MdDrawer, MdDrawerContainer, MdDrawerContent} from './drawer';
import {animate, state, style, transition, trigger} from '@angular/animations';
import {coerceBooleanProperty, coerceNumberProperty} from '@angular/cdk/coercion';
@Component({
moduleId: module.id,
selector: 'md-sidenav-content, mat-sidenav-content',
template: '<ng-content></ng-content>',
host: {
'class': 'mat-drawer-content mat-sidenav-content',
'[style.marginLeft.px]': '_margins.left',
'[style.marginRight.px]': '_margins.right',
},
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
})
export class MdSidenavContent extends MdDrawerContent {
constructor(
changeDetectorRef: ChangeDetectorRef,
@Inject(forwardRef(() => MdSidenavContainer)) container: MdSidenavContainer) {
super(changeDetectorRef, container);
}
}
@Component({
moduleId: module.id,
selector: 'md-sidenav, mat-sidenav',
templateUrl: 'drawer.html',
template: '<ng-content></ng-content>',
animations: [
trigger('transform', [
state('open, open-instant', style({
@@ -36,6 +59,7 @@ import {animate, state, style, transition, trigger} from '@angular/animations';
],
host: {
'class': 'mat-drawer mat-sidenav',
'tabIndex': '-1',
'[@transform]': '_animationState',
'(@transform.start)': '_onAnimationStart()',
'(@transform.done)': '_onAnimationEnd($event)',
@@ -46,19 +70,45 @@ import {animate, state, style, transition, trigger} from '@angular/animations';
'[class.mat-drawer-over]': 'mode === "over"',
'[class.mat-drawer-push]': 'mode === "push"',
'[class.mat-drawer-side]': 'mode === "side"',
'tabIndex': '-1',
'[class.mat-sidenav-fixed]': 'fixedInViewport',
'[style.top.px]': 'fixedInViewport ? fixedTopGap : null',
'[style.bottom.px]': 'fixedInViewport ? fixedBottomGap : null',
},
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
})
export class MdSidenav extends MdDrawer {}
export class MdSidenav extends MdDrawer {
/** Whether the sidenav is fixed in the viewport. */
@Input()
get fixedInViewport() { return this._fixedInViewport; }
set fixedInViewport(value) { this._fixedInViewport = coerceBooleanProperty(value); }
private _fixedInViewport = false;
/**
* The gap between the top of the sidenav and the top of the viewport when the sidenav is in fixed
* mode.
*/
@Input()
get fixedTopGap() { return this._fixedTopGap; }
set fixedTopGap(value) { this._fixedTopGap = coerceNumberProperty(value); }
private _fixedTopGap = 0;
/**
* The gap between the bottom of the sidenav and the bottom of the viewport when the sidenav is in
* fixed mode.
*/
@Input()
get fixedBottomGap() { return this._fixedBottomGap; }
set fixedBottomGap(value) { this._fixedBottomGap = coerceNumberProperty(value); }
private _fixedBottomGap = 0;
}
@Component({
moduleId: module.id,
selector: 'md-sidenav-container, mat-sidenav-container',
templateUrl: 'drawer-container.html',
templateUrl: 'sidenav-container.html',
styleUrls: [
'drawer.css',
'drawer-transitions.css',
@@ -72,4 +122,6 @@ export class MdSidenav extends MdDrawer {}
})
export class MdSidenavContainer extends MdDrawerContainer {
@ContentChildren(MdSidenav) _drawers;
@ContentChild(MdSidenavContent) _content;
}