1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-13 06:43:35 +00:00

[EC-558] Reflecting async progress on buttons and forms (#3548)

* [EC-556] feat: convert button into component

* [EC-556] feat: implement loading state

* [EC-556] feat: remove loading from submit button

* [EC-556] fix: add missing import

* [EC-556] fix: disabling button using regular attribute

* [EC-556] feat: implement bitFormButton

* [EC-556] feat: use bitFormButton in submit button

* [EC-556] fix: missing import

* [EC-558] chore: rename file to match class name

* [EC-558] feat: allow skipping bitButton on form buttons

* [EC-558]: only show spinner on submit button

* [EC-558] feat: add new bit async directive

* [EC-558] feat: add functionToObservable util

* [EC-558] feat: implement bitAction directive

* [EC-558] refactor: simplify bitSubmit using functionToObservable

* [EC-558] feat: connect bit action with form button

* [EC-558] feat: execute function immediately to allow for form validation

* [EC-558] feat: disable form on loading

* [EC-558] chore: remove duplicate types

* [EC-558] feat: move validation service to common

* [EC-558] feat: add error handling using validation service

* [EC-558] feat: add support for icon button

* [EC-558] fix: icon button hover border styles

* [EC-558] chore: refactor icon button story to show all styles

* [EC-558] fix: better align loading spinner to middle

* [EC-558] fix: simplify try catch

* [EC-558] chore: reorganize async actions

* [EC-558] chore: rename stories

* [EC-558] docs: add documentation

* [EC-558] feat: decouple buttons and form buttons

* [EC-558] chore: rename button like abstraction

* [EC-558] chore: remove null check

* [EC-558] docs: add jsdocs to directives

* [EC-558] fix: switch abs imports to relative

* [EC-558] chore: add async actions module to web shared module

* [EC-558] chore: remove unecessary null check

* [EC-558] chore: apply suggestions from code review

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>

* [EC-558] fix: whitespaces

* [EC-558] feat: dont disable form by default

* [EC-558] fix: bug where form could be submit during a previous submit

* [EC-558] feat: remove ability to disable form

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
This commit is contained in:
Andreas Coroiu
2022-10-10 16:04:29 +02:00
committed by GitHub
parent 96c99058c4
commit bb4f063fe7
32 changed files with 955 additions and 65 deletions

View File

@@ -0,0 +1,156 @@
import { Component, Input } from "@angular/core";
import { FormsModule, ReactiveFormsModule, Validators, FormBuilder } from "@angular/forms";
import { action } from "@storybook/addon-actions";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { delay, of } from "rxjs";
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
import { I18nService } from "@bitwarden/common/src/abstractions/i18n.service";
import { ButtonModule } from "../button";
import { FormFieldModule } from "../form-field";
import { IconButtonModule } from "../icon-button";
import { InputModule } from "../input/input.module";
import { I18nMockService } from "../utils/i18n-mock.service";
import { BitActionDirective } from "./bit-action.directive";
import { BitSubmitDirective } from "./bit-submit.directive";
import { BitFormButtonDirective } from "./form-button.directive";
const template = `
<form [formGroup]="formObj" [bitSubmit]="submit" [disableFormOnLoading]="disableFormOnLoading">
<bit-form-field>
<bit-label>Name</bit-label>
<input bitInput formControlName="name" />
</bit-form-field>
<bit-form-field>
<bit-label>Email</bit-label>
<input bitInput formControlName="email" />
</bit-form-field>
<button class="tw-mr-2" type="submit" buttonType="primary" bitButton bitFormButton>Submit</button>
<button class="tw-mr-2" type="button" buttonType="secondary" bitButton bitFormButton>Cancel</button>
<button class="tw-mr-2" type="button" buttonType="danger" bitButton bitFormButton [bitAction]="delete">Delete</button>
<button class="tw-mr-2" type="button" buttonType="secondary" bitIconButton="bwi-star" bitFormButton [bitAction]="delete">Delete</button>
</form>`;
@Component({
selector: "app-promise-example",
template,
})
class PromiseExampleComponent {
formObj = this.formBuilder.group({
name: ["", [Validators.required]],
email: ["", [Validators.required, Validators.email]],
});
@Input() disableFormOnLoading: boolean;
constructor(private formBuilder: FormBuilder) {}
submit = async () => {
this.formObj.markAllAsTouched();
if (!this.formObj.valid) {
return;
}
await new Promise<void>((resolve, reject) => {
setTimeout(resolve, 2000);
});
};
delete = async () => {
await new Promise<void>((resolve, reject) => {
setTimeout(resolve, 2000);
});
};
}
@Component({
selector: "app-observable-example",
template,
})
class ObservableExampleComponent {
formObj = this.formBuilder.group({
name: ["", [Validators.required]],
email: ["", [Validators.required, Validators.email]],
});
@Input() disableFormOnLoading: boolean;
constructor(private formBuilder: FormBuilder) {}
submit = () => {
this.formObj.markAllAsTouched();
if (!this.formObj.valid) {
return undefined;
}
return of("fake observable").pipe(delay(2000));
};
delete = () => {
return of("fake observable").pipe(delay(2000));
};
}
export default {
title: "Component Library/Async Actions/In Forms",
decorators: [
moduleMetadata({
declarations: [
BitSubmitDirective,
BitFormButtonDirective,
PromiseExampleComponent,
ObservableExampleComponent,
BitActionDirective,
],
imports: [
FormsModule,
ReactiveFormsModule,
FormFieldModule,
InputModule,
ButtonModule,
IconButtonModule,
],
providers: [
{
provide: I18nService,
useFactory: () => {
return new I18nMockService({
required: "required",
inputRequired: "Input is required.",
inputEmail: "Input is not an email-address.",
});
},
},
{
provide: ValidationService,
useValue: {
showError: action("ValidationService.showError"),
} as Partial<ValidationService>,
},
],
}),
],
args: {
disableFormOnLoading: false,
},
} as Meta;
const PromiseTemplate: Story<PromiseExampleComponent> = (args: PromiseExampleComponent) => ({
props: args,
template: `<app-promise-example [disableFormOnLoading]="disableFormOnLoading"></app-promise-example>`,
});
export const UsingPromise = PromiseTemplate.bind({});
const ObservableTemplate: Story<PromiseExampleComponent> = (args: PromiseExampleComponent) => ({
props: args,
template: `<app-observable-example [disableFormOnLoading]="disableFormOnLoading"></app-observable-example>`,
});
export const UsingObservable = ObservableTemplate.bind({});