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

PM-19100 finalize a11y UX concerns title appendage (#14831)

* PM-19100

* move getheader to bar

* remove const

* revert flag value

* move into use component block

* include confirmtation container, clean up resolvedtype

* make header optional to account for undefined

* update stories to behave as expectec and use appropriate functions

* Update apps/browser/src/autofill/notification/bar.ts

Co-authored-by: Jonathan Prusik <jprusik@users.noreply.github.com>

---------

Co-authored-by: Jonathan Prusik <jprusik@users.noreply.github.com>
This commit is contained in:
Daniel Riera
2025-05-20 11:06:03 -04:00
committed by GitHub
parent ae0f9a6d79
commit 23506b0bc1
7 changed files with 94 additions and 38 deletions

View File

@@ -58,6 +58,10 @@ const config: StorybookConfig = {
},
],
});
config.module.rules.push({
test: /\.scss$/,
use: [require.resolve("css-loader"), require.resolve("sass-loader")],
});
}
return config;
},

View File

@@ -3,6 +3,7 @@ import { Meta, StoryObj } from "@storybook/web-components";
import { ThemeTypes } from "@bitwarden/common/platform/enums";
import { NotificationTypes } from "../../../../../notification/abstractions/notification-bar";
import { getConfirmationHeaderMessage } from "../../../../../notification/bar";
import {
NotificationConfirmationContainer,
NotificationConfirmationContainerProps,
@@ -35,8 +36,10 @@ export default {
},
} as Meta<NotificationConfirmationContainerProps>;
const Template = (args: NotificationConfirmationContainerProps) =>
NotificationConfirmationContainer({ ...args });
const Template = (args: NotificationConfirmationContainerProps) => {
const headerMessage = getConfirmationHeaderMessage(args.i18n, args.type, args.error);
return NotificationConfirmationContainer({ ...args, headerMessage });
};
export const Default: StoryObj<NotificationConfirmationContainerProps> = {
render: Template,

View File

@@ -5,6 +5,7 @@ import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { NotificationTypes } from "../../../../notification/abstractions/notification-bar";
import { getNotificationHeaderMessage } from "../../../../notification/bar";
import { NotificationContainer, NotificationContainerProps } from "../../notification/container";
import { mockBrowserI18nGetMessage, mockI18n } from "../mock-data";
@@ -46,7 +47,10 @@ export default {
},
} as Meta<NotificationContainerProps>;
const Template = (args: NotificationContainerProps) => NotificationContainer({ ...args });
const Template = (args: NotificationContainerProps) => {
const headerMessage = getNotificationHeaderMessage(args.i18n, args.type);
return NotificationContainer({ ...args, headerMessage });
};
export const Default: StoryObj<NotificationContainerProps> = {
render: Template,

View File

@@ -4,6 +4,7 @@ import { html } from "lit";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { NotificationHeader, NotificationHeaderProps } from "../../notification/header";
import { mockI18n } from "../mock-data";
export default {
title: "Components/Notifications/Header",
@@ -17,6 +18,7 @@ export default {
standalone: true,
theme: ThemeTypes.Light,
handleCloseNotification: () => alert("Close Clicked"),
i18n: mockI18n,
},
parameters: {
design: {

View File

@@ -25,6 +25,7 @@ export type NotificationConfirmationContainerProps = NotificationBarIframeInitDa
handleOpenTasks: (e: Event) => void;
} & {
error?: string;
headerMessage?: string;
i18n: I18n;
itemName: string;
task?: NotificationTaskInfo;
@@ -36,13 +37,13 @@ export function NotificationConfirmationContainer({
handleCloseNotification,
handleOpenVault,
handleOpenTasks,
headerMessage,
i18n,
itemName,
task,
theme = ThemeTypes.Light,
type,
}: NotificationConfirmationContainerProps) {
const headerMessage = getHeaderMessage(i18n, type, error);
const confirmationMessage = getConfirmationMessage(i18n, type, error);
const buttonText = error ? i18n.newItem : i18n.view;
const buttonAria = error
@@ -125,20 +126,3 @@ function getConfirmationMessage(i18n: I18n, type?: NotificationType, error?: str
? i18n.notificationLoginSaveConfirmation
: i18n.notificationLoginUpdatedConfirmation;
}
function getHeaderMessage(i18n: I18n, type?: NotificationType, error?: string) {
if (error) {
return i18n.saveFailure;
}
switch (type) {
case NotificationTypes.Add:
return i18n.loginSaveSuccess;
case NotificationTypes.Change:
return i18n.loginUpdateSuccess;
case NotificationTypes.Unlock:
return "";
default:
return undefined;
}
}

View File

@@ -27,6 +27,7 @@ export type NotificationContainerProps = NotificationBarIframeInitData & {
ciphers?: NotificationCipherData[];
collections?: CollectionView[];
folders?: FolderView[];
headerMessage?: string;
i18n: I18n;
organizations?: OrgView[];
personalVaultIsAllowed?: boolean;
@@ -40,13 +41,13 @@ export function NotificationContainer({
ciphers,
collections,
folders,
headerMessage,
i18n,
organizations,
personalVaultIsAllowed = true,
theme = ThemeTypes.Light,
type,
}: NotificationContainerProps) {
const headerMessage = getHeaderMessage(i18n, type);
const showBody = type !== NotificationTypes.Unlock;
return html`
@@ -98,16 +99,3 @@ const notificationContainerStyles = (theme: Theme) => css`
padding-right: ${spacing["3"]};
}
`;
function getHeaderMessage(i18n: I18n, type?: NotificationType) {
switch (type) {
case NotificationTypes.Add:
return i18n.saveLogin;
case NotificationTypes.Change:
return i18n.updateLogin;
case NotificationTypes.Unlock:
return i18n.unlockToSave;
default:
return undefined;
}
}

View File

@@ -6,7 +6,7 @@ import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view
import { AdjustNotificationBarMessageData } from "../background/abstractions/notification.background";
import { NotificationCipherData } from "../content/components/cipher/types";
import { CollectionView, OrgView } from "../content/components/common-types";
import { CollectionView, I18n, OrgView } from "../content/components/common-types";
import { NotificationConfirmationContainer } from "../content/components/notification/confirmation/container";
import { NotificationContainer } from "../content/components/notification/container";
import { selectedFolder as selectedFolderSignal } from "../content/components/signals/selected-folder";
@@ -113,6 +113,68 @@ const findElementById = <ElementType extends HTMLElement>(
return element as ElementType;
};
/**
* Returns the localized header message for the notification bar based on the notification type.
*
* @returns The localized header message string, or undefined if the type is not recognized.
*/
export function getNotificationHeaderMessage(i18n: I18n, type?: NotificationType) {
return type
? {
[NotificationTypes.Add]: i18n.saveLogin,
[NotificationTypes.Change]: i18n.updateLogin,
[NotificationTypes.Unlock]: i18n.unlockToSave,
}[type]
: undefined;
}
/**
* Returns the localized header message for the confirmation message bar based on the notification type.
*
* @returns The localized header message string, or undefined if the type is not recognized.
*/
export function getConfirmationHeaderMessage(i18n: I18n, type?: NotificationType, error?: string) {
if (error) {
return i18n.saveFailure;
}
return type
? {
[NotificationTypes.Add]: i18n.loginSaveSuccess,
[NotificationTypes.Change]: i18n.loginUpdateSuccess,
[NotificationTypes.Unlock]: "",
}[type]
: undefined;
}
/**
* Appends the header message to the document title.
* If the header message is already present, it avoids duplication.
*/
export function appendHeaderMessageToTitle(headerMessage?: string) {
if (!headerMessage) {
return;
}
const baseTitle = document.title.split(" - ")[0];
document.title = `${baseTitle} - ${headerMessage}`;
}
/**
* Determines the effective notification type to use based on initialization data.
*
* If the vault is locked, the notification type will be set to `Unlock`.
* Otherwise, the type provided in the init data is returned.
*
* @returns The resolved `NotificationType` to be used for rendering logic.
*/
function resolveNotificationType(initData: NotificationBarIframeInitData): NotificationType {
if (initData.isVaultLocked) {
return NotificationTypes.Unlock;
}
return initData.type as NotificationType;
}
/**
* Sets the text content of an element identified by ID within a template's content.
*
@@ -148,6 +210,10 @@ async function initNotificationBar(message: NotificationBarWindowMessage) {
const resolvedTheme = getResolvedTheme(theme ?? ThemeTypes.Light);
if (useComponentBar) {
const resolvedType = resolveNotificationType(notificationBarIframeInitData);
const headerMessage = getNotificationHeaderMessage(i18n, resolvedType);
appendHeaderMessageToTitle(headerMessage);
document.body.innerHTML = "";
// Current implementations utilize a require for scss files which creates the need to remove the node.
document.head.querySelectorAll('link[rel="stylesheet"]').forEach((node) => node.remove());
@@ -156,7 +222,8 @@ async function initNotificationBar(message: NotificationBarWindowMessage) {
return render(
NotificationContainer({
...notificationBarIframeInitData,
type: NotificationTypes.Unlock,
headerMessage,
type: resolvedType,
theme: resolvedTheme,
personalVaultIsAllowed: !personalVaultDisallowed,
handleCloseNotification,
@@ -199,7 +266,8 @@ async function initNotificationBar(message: NotificationBarWindowMessage) {
return render(
NotificationContainer({
...notificationBarIframeInitData,
type: notificationBarIframeInitData.type as NotificationType,
headerMessage,
type: resolvedType,
theme: resolvedTheme,
personalVaultIsAllowed: !personalVaultDisallowed,
handleCloseNotification,
@@ -429,6 +497,8 @@ function handleSaveCipherConfirmation(message: NotificationBarWindowMessage) {
const { cipherId, task, itemName } = data || {};
const i18n = getI18n();
const resolvedTheme = getResolvedTheme(theme ?? ThemeTypes.Light);
const resolvedType = resolveNotificationType(notificationBarIframeInitData);
const headerMessage = getConfirmationHeaderMessage(i18n, resolvedType, error);
globalThis.setTimeout(() => sendPlatformMessage({ command: "bgCloseNotificationBar" }), 5000);
@@ -438,6 +508,7 @@ function handleSaveCipherConfirmation(message: NotificationBarWindowMessage) {
type: type as NotificationType,
theme: resolvedTheme,
handleCloseNotification,
headerMessage,
i18n,
error,
itemName: itemName ?? i18n.typeLogin,