Skip to content

Commit bbf62cd

Browse files
crisbetommalerba
authored andcommitted
feat: add bottom sheet component (#9764)
* Adds an initial implementation of the new `MatBottomSheet` service that allows users to display component-based or template-based bottom sheets. * Sets up the various boilerplate and infrastructure necessary for a new component. Note: this is an initial implementation that has the necessary functionality, styles and accessibility. More docs, examples and touch gestures will be added in a follow-up.
1 parent 4a5287c commit bbf62cd

29 files changed

+1430
-0
lines changed

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Angular Material components
22
/src/lib/* @jelbourn
33
/src/lib/autocomplete/** @kara @crisbeto
4+
/src/lib/bottom-sheet/** @jelbourn @crisbeto
45
/src/lib/button-toggle/** @tinayuangao
56
/src/lib/button/** @tinayuangao
67
/src/lib/card/** @jelbourn
@@ -88,6 +89,7 @@
8889
/src/demo-app/* @jelbourn
8990
/src/demo-app/a11y/** @tinayuangao
9091
/src/demo-app/autocomplete/** @kara @crisbeto
92+
/src/demo-app/bottom-sheet/** @jelbourn @crisbeto
9193
/src/demo-app/baseline/** @mmalerba
9294
/src/demo-app/button-toggle/** @tinayuangao
9395
/src/demo-app/button/** @tinayuangao
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<h1>Bottom sheet demo</h1>
2+
3+
<button mat-raised-button color="primary" (click)="openComponent()">Open component sheet</button>
4+
<button mat-raised-button color="accent" (click)="openTemplate()">Open template sheet</button>
5+
6+
<mat-card class="demo-dialog-card">
7+
<mat-card-content>
8+
<h2>Options</h2>
9+
10+
<p>
11+
<mat-checkbox [(ngModel)]="config.hasBackdrop">Has backdrop</mat-checkbox>
12+
</p>
13+
14+
<p>
15+
<mat-checkbox [(ngModel)]="config.disableClose">Disable close</mat-checkbox>
16+
</p>
17+
18+
<p>
19+
<mat-form-field>
20+
<input matInput [(ngModel)]="config.backdropClass" placeholder="Backdrop class">
21+
</mat-form-field>
22+
</p>
23+
24+
<p>
25+
<mat-form-field>
26+
<mat-select placeholder="Direction" [(ngModel)]="config.direction">
27+
<mat-option value="ltr">LTR</mat-option>
28+
<mat-option value="rtl">RTL</mat-option>
29+
</mat-select>
30+
</mat-form-field>
31+
</p>
32+
33+
</mat-card-content>
34+
</mat-card>
35+
36+
37+
<ng-template let-bottomSheetRef="bottomSheetRef">
38+
<mat-nav-list>
39+
<mat-list-item (click)="bottomSheetRef.dismiss()" *ngFor="let action of [1, 2, 3]">
40+
<mat-icon mat-list-icon>folder</mat-icon>
41+
<span mat-line>Action {{ link }}</span>
42+
<span mat-line>Description</span>
43+
</mat-list-item>
44+
</mat-nav-list>
45+
</ng-template>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.demo-dialog-card {
2+
max-width: 405px;
3+
margin: 20px 0;
4+
}
5+
6+
.mat-raised-button {
7+
margin-right: 5px;
8+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Component, ViewEncapsulation, TemplateRef, ViewChild} from '@angular/core';
10+
import {
11+
MatBottomSheet,
12+
MatBottomSheetRef,
13+
MatBottomSheetConfig,
14+
} from '@angular/material/bottom-sheet';
15+
16+
const defaultConfig = new MatBottomSheetConfig();
17+
18+
@Component({
19+
moduleId: module.id,
20+
selector: 'bottom-sheet-demo',
21+
styleUrls: ['bottom-sheet-demo.css'],
22+
templateUrl: 'bottom-sheet-demo.html',
23+
encapsulation: ViewEncapsulation.None,
24+
preserveWhitespaces: false,
25+
})
26+
export class BottomSheetDemo {
27+
config: MatBottomSheetConfig = {
28+
hasBackdrop: defaultConfig.hasBackdrop,
29+
disableClose: defaultConfig.disableClose,
30+
backdropClass: defaultConfig.backdropClass,
31+
direction: 'ltr'
32+
};
33+
34+
@ViewChild(TemplateRef) template: TemplateRef<any>;
35+
36+
constructor(private _bottomSheet: MatBottomSheet) {}
37+
38+
openComponent() {
39+
this._bottomSheet.open(ExampleBottomSheet, this.config);
40+
}
41+
42+
openTemplate() {
43+
this._bottomSheet.open(this.template, this.config);
44+
}
45+
}
46+
47+
48+
@Component({
49+
template: `
50+
<mat-nav-list>
51+
<a href="#" mat-list-item (click)="handleClick($event)" *ngFor="let action of [1, 2, 3]">
52+
<mat-icon mat-list-icon>folder</mat-icon>
53+
<span mat-line>Action {{ link }}</span>
54+
<span mat-line>Description</span>
55+
</a>
56+
</mat-nav-list>
57+
`
58+
})
59+
export class ExampleBottomSheet {
60+
constructor(private sheet: MatBottomSheetRef) {}
61+
62+
handleClick(event: MouseEvent) {
63+
event.preventDefault();
64+
this.sheet.dismiss();
65+
}
66+
}

src/demo-app/demo-app/demo-app.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export class DemoApp {
5151
dark = false;
5252
navItems = [
5353
{name: 'Autocomplete', route: '/autocomplete'},
54+
{name: 'Bottom sheet', route: '/bottom-sheet'},
5455
{name: 'Button Toggle', route: '/button-toggle'},
5556
{name: 'Button', route: '/button'},
5657
{name: 'Card', route: '/card'},

src/demo-app/demo-app/demo-module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {NgModule} from '@angular/core';
1212
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
1313
import {RouterModule} from '@angular/router';
1414
import {AutocompleteDemo} from '../autocomplete/autocomplete-demo';
15+
import {BottomSheetDemo, ExampleBottomSheet} from '../bottom-sheet/bottom-sheet-demo';
1516
import {BaselineDemo} from '../baseline/baseline-demo';
1617
import {ButtonToggleDemo} from '../button-toggle/button-toggle-demo';
1718
import {ButtonDemo} from '../button/button-demo';
@@ -71,6 +72,7 @@ import {TableDemoModule} from '../table/table-demo-module';
7172
],
7273
declarations: [
7374
AutocompleteDemo,
75+
BottomSheetDemo,
7476
BaselineDemo,
7577
ButtonDemo,
7678
ButtonToggleDemo,
@@ -120,6 +122,7 @@ import {TableDemoModule} from '../table/table-demo-module';
120122
ToolbarDemo,
121123
TooltipDemo,
122124
TypographyDemo,
125+
ExampleBottomSheet,
123126
],
124127
providers: [
125128
{provide: OverlayContainer, useClass: FullscreenOverlayContainer},
@@ -133,6 +136,7 @@ import {TableDemoModule} from '../table/table-demo-module';
133136
RotiniPanel,
134137
ScienceJoke,
135138
SpagettiPanel,
139+
ExampleBottomSheet,
136140
],
137141
})
138142
export class DemoModule {}

src/demo-app/demo-app/routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {Routes} from '@angular/router';
1010
import {AccessibilityDemo} from '../a11y/a11y';
1111
import {ACCESSIBILITY_DEMO_ROUTES} from '../a11y/routes';
1212
import {AutocompleteDemo} from '../autocomplete/autocomplete-demo';
13+
import {BottomSheetDemo} from '../bottom-sheet/bottom-sheet-demo';
1314
import {BaselineDemo} from '../baseline/baseline-demo';
1415
import {ButtonToggleDemo} from '../button-toggle/button-toggle-demo';
1516
import {ButtonDemo} from '../button/button-demo';
@@ -55,6 +56,7 @@ export const DEMO_APP_ROUTES: Routes = [
5556
{path: '', component: DemoApp, children: [
5657
{path: '', component: Home},
5758
{path: 'autocomplete', component: AutocompleteDemo},
59+
{path: 'bottom-sheet', component: BottomSheetDemo},
5860
{path: 'baseline', component: BaselineDemo},
5961
{path: 'button', component: ButtonDemo},
6062
{path: 'button-toggle', component: ButtonToggleDemo},

src/demo-app/demo-material-module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {CdkTableModule} from '@angular/cdk/table';
1717
import {NgModule} from '@angular/core';
1818
import {
1919
MatAutocompleteModule,
20+
MatBottomSheetModule,
2021
MatButtonModule,
2122
MatButtonToggleModule,
2223
MatCardModule,
@@ -57,6 +58,7 @@ import {
5758
@NgModule({
5859
exports: [
5960
MatAutocompleteModule,
61+
MatBottomSheetModule,
6062
MatButtonModule,
6163
MatButtonToggleModule,
6264
MatCardModule,

src/demo-app/system-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ System.config({
5959
'@angular/cdk/table': 'dist/packages/cdk/table/index.js',
6060

6161
'@angular/material/autocomplete': 'dist/packages/material/autocomplete/index.js',
62+
'@angular/material/bottom-sheet': 'dist/packages/material/bottom-sheet/index.js',
6263
'@angular/material/button': 'dist/packages/material/button/index.js',
6364
'@angular/material/button-toggle': 'dist/packages/material/button-toggle/index.js',
6465
'@angular/material/card': 'dist/packages/material/card/index.js',

src/e2e-app/system-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ System.config({
5151
'@angular/material-examples': 'dist/bundles/material-examples.umd.js',
5252

5353
'@angular/material/autocomplete': 'dist/bundles/material-autocomplete.umd.js',
54+
'@angular/material/bottom-sheet': 'dist/bundles/material-bottom-sheet.umd.js',
5455
'@angular/material/button': 'dist/bundles/material-button.umd.js',
5556
'@angular/material/button-toggle': 'dist/bundles/material-button-toggle.umd.js',
5657
'@angular/material/card': 'dist/bundles/material-card.umd.js',

src/lib/bottom-sheet/BUILD.bazel

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package(default_visibility=["//visibility:public"])
2+
load("@angular//:index.bzl", "ng_module")
3+
load("@io_bazel_rules_sass//sass:sass.bzl", "sass_library", "sass_binary")
4+
5+
6+
ng_module(
7+
name = "bottom-sheet",
8+
srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]),
9+
module_name = "@angular/material/bottom_sheet",
10+
assets = [
11+
":bottom_sheet_container_css",
12+
],
13+
deps = [
14+
"//src/lib/core",
15+
"//src/cdk/a11y",
16+
"//src/cdk/overlay",
17+
"//src/cdk/portal",
18+
"//src/cdk/layout",
19+
"@rxjs",
20+
],
21+
tsconfig = ":tsconfig-build.json",
22+
)
23+
24+
25+
sass_binary(
26+
name = "bottom_sheet_container_scss",
27+
src = "bottom-sheet-container.scss",
28+
deps = ["//src/lib/core:core_scss_lib"],
29+
)
30+
31+
# TODO(jelbourn): remove this when sass_binary supports specifying an output filename and dir.
32+
# Copy the output of the sass_binary such that the filename and path match what we expect.
33+
genrule(
34+
name = "bottom_sheet_container_css",
35+
srcs = [":bottom_sheet_container_scss"],
36+
outs = ["bottom-sheet-container.css"],
37+
cmd = "cat $(locations :bottom_sheet_container_scss) > $@",
38+
)

src/lib/bottom-sheet/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Please see the official documentation at https://material.angular.io/components/component/bottom-sheet
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
@import '../core/typography/typography-utils';
2+
@import '../core/theming/palette';
3+
4+
@mixin mat-bottom-sheet-theme($theme) {
5+
$background: map-get($theme, background);
6+
$foreground: map-get($theme, foreground);
7+
8+
.mat-bottom-sheet-container {
9+
background: mat-color($background, dialog);
10+
color: mat-color($foreground, text);
11+
}
12+
}
13+
14+
@mixin mat-bottom-sheet-typography($config) {
15+
.mat-bottom-sheet-container {
16+
// Note: we don't use the line-height, because it's way too big.
17+
font-family: mat-font-family($config);
18+
font-size: mat-font-size($config, subheading-2);
19+
font-weight: mat-font-weight($config, subheading-2);
20+
}
21+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import {
9+
animate,
10+
state,
11+
style,
12+
transition,
13+
trigger,
14+
AnimationTriggerMetadata,
15+
} from '@angular/animations';
16+
import {AnimationCurves, AnimationDurations} from '@angular/material/core';
17+
18+
/** Animations used by the Material bottom sheet. */
19+
export const matBottomSheetAnimations: {
20+
readonly bottomSheetState: AnimationTriggerMetadata;
21+
} = {
22+
/** Animation that shows and hides a bottom sheet. */
23+
bottomSheetState: trigger('state', [
24+
state('void, hidden', style({transform: 'translateY(100%)'})),
25+
state('visible', style({transform: 'translateY(0%)'})),
26+
transition('visible => void, visible => hidden',
27+
animate(`${AnimationDurations.COMPLEX} ${AnimationCurves.ACCELERATION_CURVE}`)),
28+
transition('void => visible',
29+
animate(`${AnimationDurations.EXITING} ${AnimationCurves.DECELERATION_CURVE}`)),
30+
])
31+
};
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {ViewContainerRef, InjectionToken} from '@angular/core';
10+
import {Direction} from '@angular/cdk/bidi';
11+
12+
/** Injection token that can be used to access the data that was passed in to a bottom sheet. */
13+
export const MAT_BOTTOM_SHEET_DATA = new InjectionToken<any>('MatBottomSheetData');
14+
15+
/**
16+
* Configuration used when opening a bottom sheet.
17+
*/
18+
export class MatBottomSheetConfig<D = any> {
19+
/** The view container to place the overlay for the bottom sheet into. */
20+
viewContainerRef?: ViewContainerRef;
21+
22+
/** Extra CSS classes to be added to the bottom sheet container. */
23+
panelClass?: string | string[];
24+
25+
/** Text layout direction for the bottom sheet. */
26+
direction?: Direction = 'ltr';
27+
28+
/** Data being injected into the child component. */
29+
data?: D | null = null;
30+
31+
/** Whether the bottom sheet has a backdrop. */
32+
hasBackdrop?: boolean = true;
33+
34+
/** Custom class for the backdrop. */
35+
backdropClass?: string;
36+
37+
/** Whether the user can use escape or clicking outside to close the bottom sheet. */
38+
disableClose?: boolean = false;
39+
40+
/** Aria label to assign to the bottom sheet element. */
41+
ariaLabel?: string | null = null;
42+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<ng-template cdkPortalOutlet></ng-template>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
@import '../core/style/elevation';
2+
3+
// The bottom sheet minimum width on larger screen sizes is based
4+
// on increments of the toolbar, according to the spec. See:
5+
// https://material.io/guidelines/components/bottom-sheets.html#bottom-sheets-specs
6+
$_mat-bottom-sheet-width-increment: 64px;
7+
$mat-bottom-sheet-container-vertical-padding: 8px !default;
8+
$mat-bottom-sheet-container-horizontal-padding: 16px !default;
9+
10+
.mat-bottom-sheet-container {
11+
@include mat-elevation(16);
12+
13+
padding: $mat-bottom-sheet-container-vertical-padding
14+
$mat-bottom-sheet-container-horizontal-padding;
15+
min-width: 100vw;
16+
box-sizing: border-box;
17+
display: block;
18+
outline: 0;
19+
}
20+
21+
.mat-bottom-sheet-container-medium {
22+
min-width: $_mat-bottom-sheet-width-increment * 6;
23+
}
24+
25+
.mat-bottom-sheet-container-large {
26+
min-width: $_mat-bottom-sheet-width-increment * 8;
27+
}
28+
29+
.mat-bottom-sheet-container-xlarge {
30+
min-width: $_mat-bottom-sheet-width-increment * 9;
31+
}

0 commit comments

Comments
 (0)