mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 14:23:32 +00:00
[CL-335][CL-336][CL-374] Announce toasts more consistently (#13167)
This commit is contained in:
@@ -35,9 +35,12 @@ import { DesktopSyncVerificationDialogComponent } from "./components/desktop-syn
|
|||||||
selector: "app-root",
|
selector: "app-root",
|
||||||
styles: [],
|
styles: [],
|
||||||
animations: [routerTransition],
|
animations: [routerTransition],
|
||||||
template: ` <div [@routerTransition]="getRouteElevation(outlet)">
|
template: `
|
||||||
<router-outlet #outlet="outlet"></router-outlet>
|
<div [@routerTransition]="getRouteElevation(outlet)">
|
||||||
</div>`,
|
<router-outlet #outlet="outlet"></router-outlet>
|
||||||
|
</div>
|
||||||
|
<bit-toast-container></bit-toast-container>
|
||||||
|
`,
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit, OnDestroy {
|
export class AppComponent implements OnInit, OnDestroy {
|
||||||
private viewCacheService = inject(PopupViewCacheService);
|
private viewCacheService = inject(PopupViewCacheService);
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
|
|||||||
</div>
|
</div>
|
||||||
<router-outlet *ngIf="!loading"></router-outlet>
|
<router-outlet *ngIf="!loading"></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<bit-toast-container></bit-toast-container>
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit, OnDestroy {
|
export class AppComponent implements OnInit, OnDestroy {
|
||||||
|
|||||||
@@ -17,3 +17,5 @@
|
|||||||
<ng-container *ngIf="!loading; else loadingState">
|
<ng-container *ngIf="!loading; else loadingState">
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<bit-toast-container></bit-toast-container>
|
||||||
|
|||||||
1
libs/components/src/toast/toast-container.component.html
Normal file
1
libs/components/src/toast/toast-container.component.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<div role="status" aria-live="polite" aria-atomic="true" toastContainer></div>
|
||||||
19
libs/components/src/toast/toast-container.component.ts
Normal file
19
libs/components/src/toast/toast-container.component.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {{
|
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
|
bgColor
|
||||||
}}"
|
}}"
|
||||||
|
[attr.role]="variant === 'error' ? 'alert' : null"
|
||||||
>
|
>
|
||||||
<div class="tw-flex tw-items-center tw-gap-4 tw-px-2 tw-pb-1 tw-pt-2">
|
<div class="tw-flex tw-items-center tw-gap-4 tw-px-2 tw-pb-1 tw-pt-2">
|
||||||
<i aria-hidden="true" class="bwi tw-text-xl tw-py-1.5 tw-px-2.5 {{ iconClass }}"></i>
|
<i aria-hidden="true" class="bwi tw-text-xl tw-py-1.5 tw-px-2.5 {{ iconClass }}"></i>
|
||||||
|
|||||||
@@ -10,21 +10,45 @@ import { ToastService } from "@bitwarden/components";
|
|||||||
|
|
||||||
# Toast
|
# Toast
|
||||||
|
|
||||||
<Primary />
|
Toasts are ephemeral notifications. They most often communicate the result of a user action. Due to
|
||||||
<Controls />
|
their ephemeral nature, long messages and critical alerts should not utilize toasts.
|
||||||
|
|
||||||
## Stories
|
|
||||||
|
|
||||||
### Default
|
|
||||||
|
|
||||||
<Canvas of={stories.Default} />
|
<Canvas of={stories.Default} />
|
||||||
|
|
||||||
### Long Content
|
|
||||||
|
|
||||||
Avoid using long messages in toasts.
|
|
||||||
|
|
||||||
<Canvas of={stories.LongContent} />
|
<Canvas of={stories.LongContent} />
|
||||||
|
|
||||||
### 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` |
|
||||||
|
|
||||||
<Canvas of={stories.Service} />
|
<Canvas of={stories.Service} />
|
||||||
|
|
||||||
|
## Toast container
|
||||||
|
|
||||||
|
`bit-toast-container` should be added to the app root of consuming clients to ensure toasts are
|
||||||
|
properly announced to screenreaders.
|
||||||
|
|
||||||
|
```
|
||||||
|
<other app file html here>
|
||||||
|
<bit-toast-container></bit-toast-container>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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`.
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { ModuleWithProviders, NgModule } from "@angular/core";
|
import { ModuleWithProviders, NgModule } from "@angular/core";
|
||||||
import { DefaultNoComponentGlobalConfig, GlobalConfig, TOAST_CONFIG } from "ngx-toastr";
|
import { DefaultNoComponentGlobalConfig, GlobalConfig, TOAST_CONFIG } from "ngx-toastr";
|
||||||
|
|
||||||
|
import { ToastContainerComponent } from "./toast-container.component";
|
||||||
import { BitwardenToastrComponent } from "./toastr.component";
|
import { BitwardenToastrComponent } from "./toastr.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [BitwardenToastrComponent],
|
imports: [BitwardenToastrComponent, ToastContainerComponent],
|
||||||
exports: [BitwardenToastrComponent],
|
exports: [BitwardenToastrComponent, ToastContainerComponent],
|
||||||
})
|
})
|
||||||
export class ToastModule {
|
export class ToastModule {
|
||||||
static forRoot(config: Partial<GlobalConfig> = {}): ModuleWithProviders<ToastModule> {
|
static forRoot(config: Partial<GlobalConfig> = {}): ModuleWithProviders<ToastModule> {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default {
|
|||||||
|
|
||||||
decorators: [
|
decorators: [
|
||||||
moduleMetadata({
|
moduleMetadata({
|
||||||
imports: [CommonModule, BrowserAnimationsModule, ButtonModule],
|
imports: [CommonModule, BrowserAnimationsModule, ButtonModule, ToastModule],
|
||||||
declarations: [ToastServiceExampleComponent],
|
declarations: [ToastServiceExampleComponent],
|
||||||
}),
|
}),
|
||||||
applicationConfig({
|
applicationConfig({
|
||||||
@@ -47,6 +47,7 @@ export default {
|
|||||||
success: "Success",
|
success: "Success",
|
||||||
error: "Error",
|
error: "Error",
|
||||||
warning: "Warning",
|
warning: "Warning",
|
||||||
|
info: "Info",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -103,7 +104,9 @@ export const Service: Story = {
|
|||||||
props: {
|
props: {
|
||||||
toastOptions: args,
|
toastOptions: args,
|
||||||
},
|
},
|
||||||
template: `
|
template: /*html*/ `
|
||||||
|
<!-- Toast container is used here to more closely align with how toasts are used in the clients, which allows for more accurate SR testing in storybook -->
|
||||||
|
<bit-toast-container></bit-toast-container>
|
||||||
<toast-service-example [toastOptions]="toastOptions"></toast-service-example>
|
<toast-service-example [toastOptions]="toastOptions"></toast-service-example>
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { animate, state, style, transition, trigger } from "@angular/animations";
|
import { animate, state, style, transition, trigger } from "@angular/animations";
|
||||||
import { Component } from "@angular/core";
|
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";
|
import { ToastComponent } from "./toast.component";
|
||||||
|
|
||||||
@@ -27,4 +27,8 @@ import { ToastComponent } from "./toast.component";
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [ToastComponent],
|
imports: [ToastComponent],
|
||||||
})
|
})
|
||||||
export class BitwardenToastrComponent extends BaseToastrComponent {}
|
export class BitwardenToastrComponent extends BaseToastrComponent {
|
||||||
|
constructor(toastrService: ToastrService, toastPackage: ToastPackage) {
|
||||||
|
super(toastrService, toastPackage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user