[PM-2198] Async simple configurable dialogs (#5411)
Implements a new functionality for simple configurable dialogs that allows you to set an acceptAction which triggers a pending state. To use this set acceptAction to an async method, and it will be executed on accept prior to closing the dialog.
This commit is contained in:
parent
2187db2153
commit
4b1570b0b3
|
@ -48,4 +48,10 @@ export type SimpleDialogOptions = {
|
|||
|
||||
/** Whether or not the user can use escape or clicking the backdrop to close the dialog */
|
||||
disableClose?: boolean;
|
||||
|
||||
/**
|
||||
* Custom accept action. Runs when the user clicks the accept button and shows a loading spinner until the promise
|
||||
* is resolved.
|
||||
*/
|
||||
acceptAction?: () => Promise<void>;
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ export class BitActionDirective implements OnDestroy {
|
|||
|
||||
disabled = false;
|
||||
|
||||
@Input("bitAction") protected handler: FunctionReturningAwaitable;
|
||||
@Input("bitAction") handler: FunctionReturningAwaitable;
|
||||
|
||||
readonly loading$ = this._loading$.asObservable();
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ export class BitSubmitDirective implements OnInit, OnDestroy {
|
|||
private _loading$ = new BehaviorSubject<boolean>(false);
|
||||
private _disabled$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
@Input("bitSubmit") protected handler: FunctionReturningAwaitable;
|
||||
@Input("bitSubmit") handler: FunctionReturningAwaitable;
|
||||
|
||||
@Input() allowDisabledFormSubmit?: boolean = false;
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { DialogModule as CdkDialogModule } from "@angular/cdk/dialog";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { ReactiveFormsModule } from "@angular/forms";
|
||||
|
||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||
|
||||
import { AsyncActionsModule } from "../async-actions";
|
||||
import { ButtonModule } from "../button";
|
||||
import { IconButtonModule } from "../icon-button";
|
||||
import { SharedModule } from "../shared";
|
||||
|
@ -15,7 +17,14 @@ import { SimpleConfigurableDialogComponent } from "./simple-configurable-dialog/
|
|||
import { IconDirective, SimpleDialogComponent } from "./simple-dialog/simple-dialog.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, IconButtonModule, CdkDialogModule, ButtonModule],
|
||||
imports: [
|
||||
SharedModule,
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
CdkDialogModule,
|
||||
IconButtonModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
declarations: [
|
||||
DialogCloseDirective,
|
||||
DialogTitleContainerDirective,
|
||||
|
|
|
@ -1,28 +1,26 @@
|
|||
<bit-simple-dialog>
|
||||
<i bitDialogIcon class="bwi tw-text-3xl" [class]="iconClasses" aria-hidden="true"></i>
|
||||
<form [formGroup]="formGroup" [bitSubmit]="accept">
|
||||
<bit-simple-dialog>
|
||||
<i bitDialogIcon class="bwi tw-text-3xl" [class]="iconClasses" aria-hidden="true"></i>
|
||||
|
||||
<span bitDialogTitle>{{ title }}</span>
|
||||
<span bitDialogTitle>{{ title }}</span>
|
||||
|
||||
<div bitDialogContent>{{ content }}</div>
|
||||
<div bitDialogContent>{{ content }}</div>
|
||||
|
||||
<ng-container bitDialogFooter>
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
(click)="dialogRef.close(SimpleDialogCloseType.ACCEPT)"
|
||||
>
|
||||
{{ acceptButtonText }}
|
||||
</button>
|
||||
<ng-container bitDialogFooter>
|
||||
<button type="submit" bitButton bitFormButton buttonType="primary">
|
||||
{{ acceptButtonText }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
*ngIf="showCancelButton"
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
(click)="dialogRef.close(SimpleDialogCloseType.CANCEL)"
|
||||
>
|
||||
{{ cancelButtonText }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-simple-dialog>
|
||||
<button
|
||||
*ngIf="showCancelButton"
|
||||
type="button"
|
||||
bitButton
|
||||
bitFormButton
|
||||
buttonType="secondary"
|
||||
(click)="dialogRef.close(SimpleDialogCloseType.CANCEL)"
|
||||
>
|
||||
{{ cancelButtonText }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-simple-dialog>
|
||||
</form>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { FormGroup } from "@angular/forms";
|
||||
|
||||
import {
|
||||
SimpleDialogType,
|
||||
|
@ -29,8 +30,8 @@ const DEFAULT_COLOR: Record<SimpleDialogType, string> = {
|
|||
templateUrl: "./simple-configurable-dialog.component.html",
|
||||
})
|
||||
export class SimpleConfigurableDialogComponent {
|
||||
SimpleDialogType = SimpleDialogType;
|
||||
SimpleDialogCloseType = SimpleDialogCloseType;
|
||||
protected SimpleDialogType = SimpleDialogType;
|
||||
protected SimpleDialogCloseType = SimpleDialogCloseType;
|
||||
|
||||
get iconClasses() {
|
||||
return [
|
||||
|
@ -39,12 +40,13 @@ export class SimpleConfigurableDialogComponent {
|
|||
];
|
||||
}
|
||||
|
||||
title: string;
|
||||
content: string;
|
||||
acceptButtonText: string;
|
||||
cancelButtonText: string;
|
||||
protected title: string;
|
||||
protected content: string;
|
||||
protected acceptButtonText: string;
|
||||
protected cancelButtonText: string;
|
||||
protected formGroup = new FormGroup({});
|
||||
|
||||
showCancelButton = this.simpleDialogOpts.cancelButtonText !== null;
|
||||
protected showCancelButton = this.simpleDialogOpts.cancelButtonText !== null;
|
||||
|
||||
constructor(
|
||||
public dialogRef: DialogRef,
|
||||
|
@ -54,6 +56,14 @@ export class SimpleConfigurableDialogComponent {
|
|||
this.localizeText();
|
||||
}
|
||||
|
||||
protected accept = async () => {
|
||||
if (this.simpleDialogOpts.acceptAction) {
|
||||
await this.simpleDialogOpts.acceptAction();
|
||||
}
|
||||
|
||||
this.dialogRef.close(SimpleDialogCloseType.ACCEPT);
|
||||
};
|
||||
|
||||
private localizeText() {
|
||||
this.title = this.translate(this.simpleDialogOpts.title);
|
||||
this.content = this.translate(this.simpleDialogOpts.content);
|
||||
|
|
|
@ -15,96 +15,17 @@ import { DialogModule } from "../dialog.module";
|
|||
|
||||
@Component({
|
||||
template: `
|
||||
<h2 class="tw-text-main">Dialog Type Examples:</h2>
|
||||
<div class="tw-mb-4 tw-flex tw-flex-row tw-gap-2">
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
(click)="openSimpleConfigurableDialog(primaryLocalizedSimpleDialogOpts)"
|
||||
>
|
||||
Open Primary Type Simple Dialog
|
||||
</button>
|
||||
|
||||
<button
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
(click)="openSimpleConfigurableDialog(successLocalizedSimpleDialogOpts)"
|
||||
>
|
||||
Open Success Type Simple Dialog
|
||||
</button>
|
||||
|
||||
<button
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
(click)="openSimpleConfigurableDialog(infoLocalizedSimpleDialogOpts)"
|
||||
>
|
||||
Open Info Type Simple Dialog
|
||||
</button>
|
||||
|
||||
<button
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
(click)="openSimpleConfigurableDialog(warningLocalizedSimpleDialogOpts)"
|
||||
>
|
||||
Open Warning Type Simple Dialog
|
||||
</button>
|
||||
|
||||
<button
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
(click)="openSimpleConfigurableDialog(dangerLocalizedSimpleDialogOpts)"
|
||||
>
|
||||
Open Danger Type Simple Dialog
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h2 class="tw-text-main">Custom Button Examples:</h2>
|
||||
<div class="tw-mb-4 tw-flex tw-flex-row tw-gap-2">
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
(click)="openSimpleConfigurableDialog(primaryAcceptBtnOverrideSimpleDialogOpts)"
|
||||
>
|
||||
Open Simple Dialog with custom accept button text
|
||||
</button>
|
||||
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
(click)="openSimpleConfigurableDialog(primaryCustomBtnsSimpleDialogOpts)"
|
||||
>
|
||||
Open Simple Dialog with 2 custom buttons
|
||||
</button>
|
||||
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
(click)="openSimpleConfigurableDialog(primarySingleBtnSimpleDialogOpts)"
|
||||
>
|
||||
Open Single Button Simple Dialog
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h2 class="tw-text-main">Custom Icon Example:</h2>
|
||||
<div class="tw-mb-4 tw-flex tw-flex-row tw-gap-2">
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
(click)="openSimpleConfigurableDialog(primaryCustomIconSimpleDialogOpts)"
|
||||
>
|
||||
Open Simple Dialog with custom icon
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h2 class="tw-text-main">Additional Examples:</h2>
|
||||
<div class="tw-mb-4 tw-flex tw-flex-row tw-gap-2">
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
(click)="openSimpleConfigurableDialog(primaryDisableCloseSimpleDialogOpts)"
|
||||
>
|
||||
Open Simple Dialog with backdrop click / escape key press disabled
|
||||
</button>
|
||||
<div *ngFor="let group of dialogs">
|
||||
<h2>{{ group.title }}</h2>
|
||||
<div class="tw-mb-4 tw-flex tw-flex-row tw-gap-2">
|
||||
<button
|
||||
*ngFor="let dialog of group.dialogs"
|
||||
bitButton
|
||||
(click)="openSimpleConfigurableDialog(dialog)"
|
||||
>
|
||||
{{ dialog.title }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<bit-callout *ngIf="showCallout" [type]="calloutType" title="Dialog Close Result">
|
||||
|
@ -113,72 +34,93 @@ import { DialogModule } from "../dialog.module";
|
|||
`,
|
||||
})
|
||||
class StoryDialogComponent {
|
||||
primaryLocalizedSimpleDialogOpts: SimpleDialogOptions = {
|
||||
title: this.i18nService.t("primaryTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.PRIMARY,
|
||||
};
|
||||
|
||||
successLocalizedSimpleDialogOpts: SimpleDialogOptions = {
|
||||
title: this.i18nService.t("successTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.SUCCESS,
|
||||
};
|
||||
|
||||
infoLocalizedSimpleDialogOpts: SimpleDialogOptions = {
|
||||
title: this.i18nService.t("infoTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.INFO,
|
||||
};
|
||||
|
||||
warningLocalizedSimpleDialogOpts: SimpleDialogOptions = {
|
||||
title: this.i18nService.t("warningTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.WARNING,
|
||||
};
|
||||
|
||||
dangerLocalizedSimpleDialogOpts: SimpleDialogOptions = {
|
||||
title: this.i18nService.t("dangerTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.DANGER,
|
||||
};
|
||||
|
||||
primarySingleBtnSimpleDialogOpts: SimpleDialogOptions = {
|
||||
title: this.i18nService.t("primaryTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.PRIMARY,
|
||||
acceptButtonText: "Ok",
|
||||
cancelButtonText: null,
|
||||
};
|
||||
|
||||
primaryCustomBtnsSimpleDialogOpts: SimpleDialogOptions = {
|
||||
title: this.i18nService.t("primaryTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.PRIMARY,
|
||||
acceptButtonText: this.i18nService.t("accept"),
|
||||
cancelButtonText: this.i18nService.t("decline"),
|
||||
};
|
||||
|
||||
primaryAcceptBtnOverrideSimpleDialogOpts: SimpleDialogOptions = {
|
||||
title: this.i18nService.t("primaryTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.PRIMARY,
|
||||
acceptButtonText: "Ok",
|
||||
};
|
||||
|
||||
primaryCustomIconSimpleDialogOpts: SimpleDialogOptions = {
|
||||
title: this.i18nService.t("primaryTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.PRIMARY,
|
||||
icon: "bwi-family",
|
||||
};
|
||||
|
||||
primaryDisableCloseSimpleDialogOpts: SimpleDialogOptions = {
|
||||
title: this.i18nService.t("primaryTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.PRIMARY,
|
||||
disableClose: true,
|
||||
};
|
||||
protected dialogs: { title: string; dialogs: SimpleDialogOptions[] }[] = [
|
||||
{
|
||||
title: "Regular",
|
||||
dialogs: [
|
||||
{
|
||||
title: this.i18nService.t("primaryTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.PRIMARY,
|
||||
},
|
||||
{
|
||||
title: this.i18nService.t("successTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.SUCCESS,
|
||||
},
|
||||
{
|
||||
title: this.i18nService.t("infoTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.INFO,
|
||||
},
|
||||
{
|
||||
title: this.i18nService.t("warningTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.WARNING,
|
||||
},
|
||||
{
|
||||
title: this.i18nService.t("dangerTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.DANGER,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Custom",
|
||||
dialogs: [
|
||||
{
|
||||
title: this.i18nService.t("primaryTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.PRIMARY,
|
||||
acceptButtonText: "Ok",
|
||||
cancelButtonText: null,
|
||||
},
|
||||
{
|
||||
title: this.i18nService.t("primaryTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.PRIMARY,
|
||||
acceptButtonText: this.i18nService.t("accept"),
|
||||
cancelButtonText: this.i18nService.t("decline"),
|
||||
},
|
||||
{
|
||||
title: this.i18nService.t("primaryTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.PRIMARY,
|
||||
acceptButtonText: "Ok",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Icon",
|
||||
dialogs: [
|
||||
{
|
||||
title: this.i18nService.t("primaryTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.PRIMARY,
|
||||
icon: "bwi-family",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Additional",
|
||||
dialogs: [
|
||||
{
|
||||
title: this.i18nService.t("primaryTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
type: SimpleDialogType.PRIMARY,
|
||||
disableClose: true,
|
||||
},
|
||||
{
|
||||
title: this.i18nService.t("asyncTypeSimpleDialog"),
|
||||
content: this.i18nService.t("dialogContent"),
|
||||
acceptAction: () => {
|
||||
return new Promise((resolve) => setTimeout(resolve, 10000));
|
||||
},
|
||||
type: SimpleDialogType.PRIMARY,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
showCallout = false;
|
||||
calloutType = "info";
|
||||
|
@ -216,6 +158,7 @@ export default {
|
|||
infoTypeSimpleDialog: "Info Type Simple Dialog",
|
||||
warningTypeSimpleDialog: "Warning Type Simple Dialog",
|
||||
dangerTypeSimpleDialog: "Danger Type Simple Dialog",
|
||||
asyncTypeSimpleDialog: "Async",
|
||||
dialogContent: "Dialog content goes here",
|
||||
yes: "Yes",
|
||||
no: "No",
|
||||
|
|
Loading…
Reference in New Issue