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:
@@ -58,6 +58,10 @@ const config: StorybookConfig = {
|
||||
},
|
||||
],
|
||||
});
|
||||
config.module.rules.push({
|
||||
test: /\.scss$/,
|
||||
use: [require.resolve("css-loader"), require.resolve("sass-loader")],
|
||||
});
|
||||
}
|
||||
return config;
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user