From fabcf04398828ce990a628dab466980bec336801 Mon Sep 17 00:00:00 2001 From: Vicki League Date: Thu, 13 Feb 2025 14:56:43 -0500 Subject: [PATCH] [CL-335][CL-336][CL-374] Announce toasts more consistently (#13167) --- apps/browser/src/popup/app.component.ts | 9 ++-- apps/desktop/src/app/app.component.ts | 2 + apps/web/src/app/app.component.html | 2 + .../src/toast/toast-container.component.html | 1 + .../src/toast/toast-container.component.ts | 19 ++++++++ .../components/src/toast/toast.component.html | 1 + libs/components/src/toast/toast.mdx | 46 ++++++++++++++----- libs/components/src/toast/toast.module.ts | 5 +- libs/components/src/toast/toast.stories.ts | 7 ++- libs/components/src/toast/toastr.component.ts | 8 +++- 10 files changed, 80 insertions(+), 20 deletions(-) create mode 100644 libs/components/src/toast/toast-container.component.html create mode 100644 libs/components/src/toast/toast-container.component.ts diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index a97d001c54b..66be31969dd 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -35,9 +35,12 @@ import { DesktopSyncVerificationDialogComponent } from "./components/desktop-syn selector: "app-root", styles: [], animations: [routerTransition], - template: `
- -
`, + template: ` +
+ +
+ + `, }) export class AppComponent implements OnInit, OnDestroy { private viewCacheService = inject(PopupViewCacheService); diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 92b2c8fee37..61938bcaca6 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -89,6 +89,8 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours + + `, }) export class AppComponent implements OnInit, OnDestroy { diff --git a/apps/web/src/app/app.component.html b/apps/web/src/app/app.component.html index a39c045e0e3..471d89be1c1 100644 --- a/apps/web/src/app/app.component.html +++ b/apps/web/src/app/app.component.html @@ -17,3 +17,5 @@ + + diff --git a/libs/components/src/toast/toast-container.component.html b/libs/components/src/toast/toast-container.component.html new file mode 100644 index 00000000000..8896f7e953d --- /dev/null +++ b/libs/components/src/toast/toast-container.component.html @@ -0,0 +1 @@ +
diff --git a/libs/components/src/toast/toast-container.component.ts b/libs/components/src/toast/toast-container.component.ts new file mode 100644 index 00000000000..1cd33f67ac7 --- /dev/null +++ b/libs/components/src/toast/toast-container.component.ts @@ -0,0 +1,19 @@ +import { Component, OnInit, ViewChild } from "@angular/core"; +import { ToastContainerDirective, ToastrService } from "ngx-toastr"; + +@Component({ + selector: "bit-toast-container", + templateUrl: "toast-container.component.html", + standalone: true, + imports: [ToastContainerDirective], +}) +export class ToastContainerComponent implements OnInit { + @ViewChild(ToastContainerDirective, { static: true }) + toastContainer?: ToastContainerDirective; + + constructor(private toastrService: ToastrService) {} + + ngOnInit(): void { + this.toastrService.overlayContainer = this.toastContainer; + } +} diff --git a/libs/components/src/toast/toast.component.html b/libs/components/src/toast/toast.component.html index d78cc7783aa..bdbc9674184 100644 --- a/libs/components/src/toast/toast.component.html +++ b/libs/components/src/toast/toast.component.html @@ -2,6 +2,7 @@ class="tw-mb-1 tw-min-w-[--bit-toast-width] tw-text-main tw-flex tw-flex-col tw-justify-between tw-rounded-md tw-pointer-events-auto tw-cursor-default tw-overflow-hidden tw-shadow-lg {{ bgColor }}" + [attr.role]="variant === 'error' ? 'alert' : null" >
diff --git a/libs/components/src/toast/toast.mdx b/libs/components/src/toast/toast.mdx index 69b0ddf6a08..d27109b4772 100644 --- a/libs/components/src/toast/toast.mdx +++ b/libs/components/src/toast/toast.mdx @@ -10,21 +10,45 @@ import { ToastService } from "@bitwarden/components"; # Toast - - - -## Stories - -### Default +Toasts are ephemeral notifications. They most often communicate the result of a user action. Due to +their ephemeral nature, long messages and critical alerts should not utilize toasts. -### Long Content - -Avoid using long messages in toasts. - -### Service +## Displaying a toast + +Toasts are triggered via the `ToastService`, which must be called from a frontend Angular context. + +``` +toastService.showToast({ + variant: 'success', + message: 'Hi I'm a toast, +}); +``` + +The following options are accepted: + +| Option | Description | +| --------- | --------------------------------------------- | +| `variant` | `"success" \| "error" \| "info" \| "warning"` | +| `title` | Optional title `string` | +| `message` | Main toast content. Required `string` | + +## Toast container + +`bit-toast-container` should be added to the app root of consuming clients to ensure toasts are +properly announced to screenreaders. + +``` + + +``` + +## Accessibility + +In addition to the accessibility provided by the `bit-toast-container` component, the toast itself +will apply `aria-alert="true"` if the toast is of type `error`. diff --git a/libs/components/src/toast/toast.module.ts b/libs/components/src/toast/toast.module.ts index bf17fde223f..647ccadc4fe 100644 --- a/libs/components/src/toast/toast.module.ts +++ b/libs/components/src/toast/toast.module.ts @@ -1,11 +1,12 @@ import { ModuleWithProviders, NgModule } from "@angular/core"; import { DefaultNoComponentGlobalConfig, GlobalConfig, TOAST_CONFIG } from "ngx-toastr"; +import { ToastContainerComponent } from "./toast-container.component"; import { BitwardenToastrComponent } from "./toastr.component"; @NgModule({ - imports: [BitwardenToastrComponent], - exports: [BitwardenToastrComponent], + imports: [BitwardenToastrComponent, ToastContainerComponent], + exports: [BitwardenToastrComponent, ToastContainerComponent], }) export class ToastModule { static forRoot(config: Partial = {}): ModuleWithProviders { diff --git a/libs/components/src/toast/toast.stories.ts b/libs/components/src/toast/toast.stories.ts index abb737f5c23..0af4974eead 100644 --- a/libs/components/src/toast/toast.stories.ts +++ b/libs/components/src/toast/toast.stories.ts @@ -33,7 +33,7 @@ export default { decorators: [ moduleMetadata({ - imports: [CommonModule, BrowserAnimationsModule, ButtonModule], + imports: [CommonModule, BrowserAnimationsModule, ButtonModule, ToastModule], declarations: [ToastServiceExampleComponent], }), applicationConfig({ @@ -47,6 +47,7 @@ export default { success: "Success", error: "Error", warning: "Warning", + info: "Info", }); }, }, @@ -103,7 +104,9 @@ export const Service: Story = { props: { toastOptions: args, }, - template: ` + template: /*html*/ ` + + `, }), diff --git a/libs/components/src/toast/toastr.component.ts b/libs/components/src/toast/toastr.component.ts index 24209054948..c93e96150ad 100644 --- a/libs/components/src/toast/toastr.component.ts +++ b/libs/components/src/toast/toastr.component.ts @@ -1,6 +1,6 @@ import { animate, state, style, transition, trigger } from "@angular/animations"; import { Component } from "@angular/core"; -import { Toast as BaseToastrComponent } from "ngx-toastr"; +import { Toast as BaseToastrComponent, ToastPackage, ToastrService } from "ngx-toastr"; import { ToastComponent } from "./toast.component"; @@ -27,4 +27,8 @@ import { ToastComponent } from "./toast.component"; standalone: true, imports: [ToastComponent], }) -export class BitwardenToastrComponent extends BaseToastrComponent {} +export class BitwardenToastrComponent extends BaseToastrComponent { + constructor(toastrService: ToastrService, toastPackage: ToastPackage) { + super(toastrService, toastPackage); + } +}