From ab543b929d3e3f053d60762f1eeb77d01fca00d9 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Wed, 26 Nov 2025 10:52:08 +0100 Subject: [PATCH] [CL-932] Migrate banner component to OnPush & Signals (#17387) Migrates the BannerComponent to use computed signals and OnPush. --- .../src/banner/banner.component.html | 6 +- .../components/src/banner/banner.component.ts | 71 ++++++++++++------- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/libs/components/src/banner/banner.component.html b/libs/components/src/banner/banner.component.html index bfde8135da9..19875d69f37 100644 --- a/libs/components/src/banner/banner.component.html +++ b/libs/components/src/banner/banner.component.html @@ -1,11 +1,11 @@
- @if (icon(); as icon) { - + @if (displayIcon(); as icon) { + } diff --git a/libs/components/src/banner/banner.component.ts b/libs/components/src/banner/banner.component.ts index 1f9bf960d4b..2f36353cfcd 100644 --- a/libs/components/src/banner/banner.component.ts +++ b/libs/components/src/banner/banner.component.ts @@ -1,5 +1,4 @@ -import { CommonModule } from "@angular/common"; -import { Component, OnInit, Output, EventEmitter, input, model } from "@angular/core"; +import { ChangeDetectionStrategy, Component, computed, input, output } from "@angular/core"; import { I18nPipe } from "@bitwarden/ui-common"; @@ -13,47 +12,69 @@ const defaultIcon: Record = { warning: "bwi-exclamation-triangle", danger: "bwi-error", }; -/** - * Banners are used for important communication with the user that needs to be seen right away, but has - * little effect on the experience. Banners appear at the top of the user's screen on page load and - * persist across all pages a user navigates to. - * - They should always be dismissible and never use a timeout. If a user dismisses a banner, it should not reappear during that same active session. - * - Use banners sparingly, as they can feel intrusive to the user if they appear unexpectedly. Their effectiveness may decrease if too many are used. - * - Avoid stacking multiple banners. - * - Banners can contain a button or anchor that uses the `bitLink` directive with `linkType="secondary"`. +/** + * Banners are used for important communication with the user that needs to be seen right away, but has + * little effect on the experience. Banners appear at the top of the user's screen on page load and + * persist across all pages a user navigates to. + * + * - They should always be dismissible and never use a timeout. If a user dismisses a banner, it should not reappear during that same active session. + * - Use banners sparingly, as they can feel intrusive to the user if they appear unexpectedly. Their effectiveness may decrease if too many are used. + * - Avoid stacking multiple banners. + * - Banners can contain a button or anchor that uses the `bitLink` directive with `linkType="secondary"`. */ -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-banner", templateUrl: "./banner.component.html", - imports: [CommonModule, IconButtonModule, I18nPipe], + imports: [IconButtonModule, I18nPipe], host: { // Account for bit-layout's padding class: "tw-flex tw-flex-col [bit-layout_&]:-tw-mx-8 [bit-layout_&]:-tw-my-6 [bit-layout_&]:tw-pb-6", }, + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class BannerComponent implements OnInit { +export class BannerComponent { + /** + * The type of banner, which determines its color scheme. + */ readonly bannerType = input("info"); - // passing `null` will remove the icon from element from the banner - readonly icon = model(); + /** + * The icon to display. If not provided, a default icon based on bannerType will be used. Explicitly passing null will remove the icon. + */ + readonly icon = input(); + + /** + * Whether to use ARIA alert role for screen readers. + */ readonly useAlertRole = input(true); + + /** + * Whether to show the close button. + */ readonly showClose = input(true); - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref - @Output() onClose = new EventEmitter(); + /** + * Emitted when the banner is closed via the close button. + */ + readonly onClose = output(); - ngOnInit(): void { - if (!this.icon() && this.icon() !== null) { - this.icon.set(defaultIcon[this.bannerType()]); + /** + * The computed icon to display, falling back to the default icon for the banner type. + * Returns null if icon is explicitly set to null (to hide the icon). + */ + protected readonly displayIcon = computed(() => { + // If icon is explicitly null, don't show any icon + if (this.icon() === null) { + return null; } - } - get bannerClass() { + // If icon is undefined, fall back to default icon + return this.icon() ?? defaultIcon[this.bannerType()]; + }); + + protected readonly bannerClass = computed(() => { switch (this.bannerType()) { case "danger": return "tw-bg-danger-100 tw-border-b-danger-700"; @@ -64,5 +85,5 @@ export class BannerComponent implements OnInit { case "warning": return "tw-bg-warning-100 tw-border-b-warning-700"; } - } + }); }