- @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";
}
- }
+ });
}
diff --git a/libs/components/src/dialog/dialog/dialog.component.html b/libs/components/src/dialog/dialog/dialog.component.html
index 83cfa21ed21..c077dc2ceb8 100644
--- a/libs/components/src/dialog/dialog/dialog.component.html
+++ b/libs/components/src/dialog/dialog/dialog.component.html
@@ -75,13 +75,19 @@
@let isScrollable = isScrollable$ | async;
@let showFooterBorder =
(!bodyHasScrolledFrom().top && isScrollable) || bodyHasScrolledFrom().bottom;
+