Instantly share code, notes, and snippets.
Created
June 18, 2019 07:52
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save thw0rted/554a0c14b7d8a82a3c3d4668fdf8756c to your computer and use it in GitHub Desktop.
Material Banner component
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
div.container { | |
width: 100%; | |
box-sizing: border-box; | |
/* Per Banner spec, with bigger left gutter to account for menu button */ | |
padding: 8px 8px 8px 72px; | |
display: flex; | |
flex-direction: row; | |
align-items: flex-end; | |
overflow: hidden; | |
} | |
p.message { | |
flex-grow: 1; | |
} | |
.action-row { | |
margin: 0 0 0 90px; | |
flex-grow: 0; | |
white-space: nowrap; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="container mat-background-default" *ngIf="opened"> | |
<p class="message mat-body-strong" [innerHTML]="message"></p> | |
<div class="action-row"> | |
<button *ngFor="let action of actions; let idx=index;" | |
type="button" mat-button color="accent" | |
(click)="actionClicked(idx)"> | |
{{action}} | |
</button> | |
</div> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Component } from "@angular/core"; | |
import { Observable, Subject } from "rxjs"; | |
import { BannerService } from "./banner.service"; | |
@Component({ | |
selector: "banner-outlet", | |
templateUrl: "./banner.component.html", | |
styleUrls: [ "./banner.component.css" ], | |
}) | |
export class BannerOutlet { | |
// Text message to display | |
private _message?: string; | |
public get message(): string | undefined { return this._message; } | |
// List of button labels to show | |
private _actions?: string[]; | |
public get actions(): string[] | undefined { return this._actions; } | |
// Emits one value when the user picks an action | |
private _clicks?: Subject<number>; | |
// True if the panel is opened | |
public get opened(): boolean { return !!this._clicks; } | |
constructor(bannerService: BannerService) { | |
bannerService.init(this); | |
} | |
// Open this banner with a message and at least one action | |
open(message: string, actions: string[]): Observable<number> { | |
if (this._clicks) { | |
throw "Tried to open banner when outlet was already opened."; | |
} | |
if (actions.length === 0) { | |
throw "Tried to open banner without any action buttons."; | |
} | |
this._message = message; | |
this._actions = actions; | |
this._clicks = new Subject(); | |
return this._clicks.asObservable(); | |
} | |
actionClicked(idx: number): void { | |
if (!this._clicks) { | |
console.log("Developer Error: banner action clicked but observable available!"); | |
return; | |
} | |
// Click subject can only ever emit one value | |
this._clicks.next(idx); | |
this._clicks.complete(); | |
this._clicks.unsubscribe(); | |
this._clicks = undefined; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Injectable } from "@angular/core"; | |
import { Observable, never, Subject } from "rxjs"; | |
import { BannerOutlet } from "./banner.component"; | |
@Injectable({ | |
providedIn: "root", | |
}) | |
export class BannerService { | |
private outlet?: BannerOutlet; | |
private active?: Observable<number>; | |
private pending: {message: string, actions: string[], ret: Subject<number>}[] = []; | |
// Use the supplied BannerOutlet to display messages | |
public init(val: BannerOutlet) { | |
if (this.outlet) { throw "Can't have more than one Outlet for Banner Service!"; } | |
this.outlet = val; | |
} | |
// Display a message with at least one action. Returned observable will | |
// emit the index of the selected action once the user clicks a button. | |
public open(message: string, actions: string[]): Observable<number> { | |
if (!this.outlet) { | |
console.log("Tried to open banner but no outlet was defined.", message, actions); | |
return never(); | |
} | |
if (!this.active) { | |
return this.doOpen(message, actions); | |
} else { | |
const ret: Subject<number> = new Subject(); | |
this.pending.push({message, actions, ret}); | |
return ret.asObservable(); | |
} | |
} | |
// Actually show the banner in the outlet | |
private doOpen(message: string, actions: string[]): Observable<number> { | |
// Open the outlet and save the observable | |
this.active = this.outlet!.open(message, actions); | |
// When the user selects an action, the banner will close | |
this.active.subscribe(next => { | |
// That means we stop watching the old banner, and... | |
this.active = undefined; | |
// If there was another queued, we show it | |
const args = this.pending.shift(); | |
if (args) { | |
setTimeout(() => this.doOpen(args.message, args.actions).subscribe(args.ret), 500); | |
} | |
}); | |
return this.active; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment