diff --git a/src/demo-app/demo-app/demo-app.ts b/src/demo-app/demo-app/demo-app.ts index 26d96a4ee0e0..f1f9fa5cd178 100644 --- a/src/demo-app/demo-app/demo-app.ts +++ b/src/demo-app/demo-app/demo-app.ts @@ -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'} ]; diff --git a/src/demo-app/demo-app/demo-module.ts b/src/demo-app/demo-app/demo-module.ts index 59a632277ee1..80e21dbe5c26 100644 --- a/src/demo-app/demo-app/demo-module.ts +++ b/src/demo-app/demo-app/demo-module.ts @@ -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 {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 {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 {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 {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 {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 {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 {SliderDemo} from '../slider/slider-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 {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 {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'; @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, diff --git a/src/demo-app/demo-app/routes.ts b/src/demo-app/demo-app/routes.ts index 7ee134565fdb..cebe53bdb29e 100644 --- a/src/demo-app/demo-app/routes.ts +++ b/src/demo-app/demo-app/routes.ts @@ -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 {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 {LiveAnnouncerDemo} from '../live-announcer/live-announcer-demo'; -import {ListDemo} from '../list/list-demo'; +import {GridListDemo} from '../grid-list/grid-list-demo'; import {IconDemo} from '../icon/icon-demo'; -import {ToolbarDemo} from '../toolbar/toolbar-demo'; -import {CheckboxDemo} from '../checkbox/checkbox-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 {StepperDemo} from '../stepper/stepper-demo'; 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 {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 {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: 'snack-bar', component: SnackBarDemo}, + {path: 'stepper', component: StepperDemo}, + {path: 'table', component: TableDemo}, {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: 'toolbar', component: ToolbarDemo}, {path: 'tooltip', component: TooltipDemo}, - {path: 'snack-bar', component: SnackBarDemo}, - {path: 'platform', component: PlatformDemo}, - {path: 'focus-origin', component: FocusOriginDemo}, {path: 'typography', component: TypographyDemo}, - {path: 'expansion', component: ExpansionDemo}, - {path: 'stepper', component: StepperDemo} ]} ]; diff --git a/src/demo-app/drawer/drawer-demo.html b/src/demo-app/drawer/drawer-demo.html new file mode 100644 index 000000000000..3a2dddc4d1d2 --- /dev/null +++ b/src/demo-app/drawer/drawer-demo.html @@ -0,0 +1,87 @@ +

Basic Use Case

+ + + + Start Side Drawer +
+ +
+ +
+ +
Mode: {{start.mode}}
+
+ +
+ + + End Side Drawer +
+ +
+ +
+

My Content

+ +
+
Drawer
+ + +
+ + + + +
+
+ +

Drawer Already Opened

+ + + + Drawer + + +
+ +
+
+ +

Dynamic Position Drawer

+ + + Start + End + +
+ + +
+
+ +

Drawer with focus attributes

+ + + + + Link + Focus region start + Link + Initially focused + Focus region end + Link + + + +
+

My Content

+ +
+
Drawer
+ +
+
+
diff --git a/src/demo-app/drawer/drawer-demo.scss b/src/demo-app/drawer/drawer-demo.scss new file mode 100644 index 000000000000..1bf747c0615b --- /dev/null +++ b/src/demo-app/drawer/drawer-demo.scss @@ -0,0 +1,7 @@ +.demo-drawer-container { + border: 3px solid black; +} + +.demo-drawer-content { + padding: 15px; +} diff --git a/src/demo-app/drawer/drawer-demo.ts b/src/demo-app/drawer/drawer-demo.ts new file mode 100644 index 000000000000..87a740988f7e --- /dev/null +++ b/src/demo-app/drawer/drawer-demo.ts @@ -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; +} diff --git a/src/demo-app/sidenav/sidenav-demo.html b/src/demo-app/sidenav/sidenav-demo.html index bdddd69c381a..fdc13d483172 100644 --- a/src/demo-app/sidenav/sidenav-demo.html +++ b/src/demo-app/sidenav/sidenav-demo.html @@ -1,87 +1,60 @@ -

Basic Use Case

- - - - Start Side Drawer -
- -
- -
- -
Mode: {{start.mode}}
-
- -
- - - End Side Drawer -
- -
- -
-

My Content

- -
-
Sidenav
- - -
- - - - -
-
- -

Sidenav Already Opened

- - - - Drawer - - -
- -
-
- -

Dynamic Position Sidenav

- - - Start - End - -
- - -
-
- -

Sidenav with focus attributes

- - - - - Link - Focus region start - Link - Initially focused - Focus region end - Link - - - -
-

My Content

- -
-
Sidenav
- -
-
-
+ + +
+ Header + + + Start Side Sidenav +
+ +
+ +
+ +
Mode: {{start.mode}}
+
+ +
Filler Content
+
+ + + End Side Sidenav +
+ +
Filler Content
+
+ + + Header +
+
+ +
+ +
+

Sidenav

+ + + Fixed mode + Sidenav covers header/footer +
+ +
+

Header / Footer

+ Show header + Show footer +
+ +
Filler Content
+
+ Footer +
+
+ Footer +
diff --git a/src/demo-app/sidenav/sidenav-demo.scss b/src/demo-app/sidenav/sidenav-demo.scss index a04b3bb58124..b9b359f85460 100644 --- a/src/demo-app/sidenav/sidenav-demo.scss +++ b/src/demo-app/sidenav/sidenav-demo.scss @@ -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: 15px; + .demo-sidenav-content { + padding: 32px; + } + + .demo-filler-content { + padding: 60px; + } } diff --git a/src/demo-app/sidenav/sidenav-demo.ts b/src/demo-app/sidenav/sidenav-demo.ts index 60be081c2a85..37a8873aad82 100644 --- a/src/demo-app/sidenav/sidenav-demo.ts +++ b/src/demo-app/sidenav/sidenav-demo.ts @@ -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; } } diff --git a/src/lib/sidenav/drawer-container.html b/src/lib/sidenav/drawer-container.html index f305c0545a87..a7f8457f08ca 100644 --- a/src/lib/sidenav/drawer-container.html +++ b/src/lib/sidenav/drawer-container.html @@ -1,8 +1,10 @@
- + -
+ + + -
+ diff --git a/src/lib/sidenav/drawer-transitions.scss b/src/lib/sidenav/drawer-transitions.scss index f14d83784f24..7be6e763b610 100644 --- a/src/lib/sidenav/drawer-transitions.scss +++ b/src/lib/sidenav/drawer-transitions.scss @@ -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; } } diff --git a/src/lib/sidenav/drawer.html b/src/lib/sidenav/drawer.html deleted file mode 100644 index 6dbc74306383..000000000000 --- a/src/lib/sidenav/drawer.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/lib/sidenav/drawer.scss b/src/lib/sidenav/drawer.scss index 9d46906c21fd..d3efaa66d351 100644 --- a/src/lib/sidenav/drawer.scss +++ b/src/lib/sidenav/drawer.scss @@ -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; +} diff --git a/src/lib/sidenav/drawer.spec.ts b/src/lib/sidenav/drawer.spec.ts index 6d0a5171a06b..d858a6a2d8a1 100644 --- a/src/lib/sidenav/drawer.spec.ts +++ b/src/lib/sidenav/drawer.spec.ts @@ -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; diff --git a/src/lib/sidenav/drawer.ts b/src/lib/sidenav/drawer.ts index ef241cacd3fa..3d306303fac0 100644 --- a/src/lib/sidenav/drawer.ts +++ b/src/lib/sidenav/drawer.ts @@ -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: '', + 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(); + }); + } +} + + /** * component. * @@ -61,7 +100,7 @@ export class MdDrawerToggleResult { @Component({ moduleId: module.id, selector: 'md-drawer, mat-drawer', - templateUrl: 'drawer.html', + template: '', 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(); + /** + * 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 { } } + /** * component. * @@ -322,6 +374,8 @@ export class MdDrawer implements AfterContentInit, OnDestroy { export class MdDrawerContainer implements AfterContentInit, OnDestroy { @ContentChildren(MdDrawer) _drawers: QueryList; + @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. + + let left = 0; + let right = 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; + } + } - /** - * 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; - - this._styles = { - marginLeft: `${marginLeft}px`, - marginRight: `${marginRight}px`, - transform: `translate3d(${leftWidth - rightWidth}px, 0, 0)` - }; + 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}); } } diff --git a/src/lib/sidenav/sidenav-container.html b/src/lib/sidenav/sidenav-container.html new file mode 100644 index 000000000000..7068bbdf4382 --- /dev/null +++ b/src/lib/sidenav/sidenav-container.html @@ -0,0 +1,10 @@ +
+ + + + + + + + diff --git a/src/lib/sidenav/sidenav-module.ts b/src/lib/sidenav/sidenav-module.ts index 16f14d9d98fc..577f9205826c 100644 --- a/src/lib/sidenav/sidenav-module.ts +++ b/src/lib/sidenav/sidenav-module.ts @@ -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 {} diff --git a/src/lib/sidenav/sidenav.spec.ts b/src/lib/sidenav/sidenav.spec.ts new file mode 100644 index 000000000000..0de4571a2638 --- /dev/null +++ b/src/lib/sidenav/sidenav.spec.ts @@ -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; + 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: ` + + + Drawer. + + + Some content. + + `, +}) +class SidenavWithFixedPosition { + fixed = true; + fixedTop = 20; + fixedBottom = 30; +} diff --git a/src/lib/sidenav/sidenav.ts b/src/lib/sidenav/sidenav.ts index 12e8b925bcfa..aff0a5b25137 100644 --- a/src/lib/sidenav/sidenav.ts +++ b/src/lib/sidenav/sidenav.ts @@ -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: '', + 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: '', 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; }