mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 16:23:44 +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;
|
return config;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Meta, StoryObj } from "@storybook/web-components";
|
|||||||
import { ThemeTypes } from "@bitwarden/common/platform/enums";
|
import { ThemeTypes } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
import { NotificationTypes } from "../../../../../notification/abstractions/notification-bar";
|
import { NotificationTypes } from "../../../../../notification/abstractions/notification-bar";
|
||||||
|
import { getConfirmationHeaderMessage } from "../../../../../notification/bar";
|
||||||
import {
|
import {
|
||||||
NotificationConfirmationContainer,
|
NotificationConfirmationContainer,
|
||||||
NotificationConfirmationContainerProps,
|
NotificationConfirmationContainerProps,
|
||||||
@@ -35,8 +36,10 @@ export default {
|
|||||||
},
|
},
|
||||||
} as Meta<NotificationConfirmationContainerProps>;
|
} as Meta<NotificationConfirmationContainerProps>;
|
||||||
|
|
||||||
const Template = (args: NotificationConfirmationContainerProps) =>
|
const Template = (args: NotificationConfirmationContainerProps) => {
|
||||||
NotificationConfirmationContainer({ ...args });
|
const headerMessage = getConfirmationHeaderMessage(args.i18n, args.type, args.error);
|
||||||
|
return NotificationConfirmationContainer({ ...args, headerMessage });
|
||||||
|
};
|
||||||
|
|
||||||
export const Default: StoryObj<NotificationConfirmationContainerProps> = {
|
export const Default: StoryObj<NotificationConfirmationContainerProps> = {
|
||||||
render: Template,
|
render: Template,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { CipherType } from "@bitwarden/common/vault/enums";
|
|||||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||||
|
|
||||||
import { NotificationTypes } from "../../../../notification/abstractions/notification-bar";
|
import { NotificationTypes } from "../../../../notification/abstractions/notification-bar";
|
||||||
|
import { getNotificationHeaderMessage } from "../../../../notification/bar";
|
||||||
import { NotificationContainer, NotificationContainerProps } from "../../notification/container";
|
import { NotificationContainer, NotificationContainerProps } from "../../notification/container";
|
||||||
import { mockBrowserI18nGetMessage, mockI18n } from "../mock-data";
|
import { mockBrowserI18nGetMessage, mockI18n } from "../mock-data";
|
||||||
|
|
||||||
@@ -46,7 +47,10 @@ export default {
|
|||||||
},
|
},
|
||||||
} as Meta<NotificationContainerProps>;
|
} 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> = {
|
export const Default: StoryObj<NotificationContainerProps> = {
|
||||||
render: Template,
|
render: Template,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { html } from "lit";
|
|||||||
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
|
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
|
||||||
|
|
||||||
import { NotificationHeader, NotificationHeaderProps } from "../../notification/header";
|
import { NotificationHeader, NotificationHeaderProps } from "../../notification/header";
|
||||||
|
import { mockI18n } from "../mock-data";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "Components/Notifications/Header",
|
title: "Components/Notifications/Header",
|
||||||
@@ -17,6 +18,7 @@ export default {
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
theme: ThemeTypes.Light,
|
theme: ThemeTypes.Light,
|
||||||
handleCloseNotification: () => alert("Close Clicked"),
|
handleCloseNotification: () => alert("Close Clicked"),
|
||||||
|
i18n: mockI18n,
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
design: {
|
design: {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export type NotificationConfirmationContainerProps = NotificationBarIframeInitDa
|
|||||||
handleOpenTasks: (e: Event) => void;
|
handleOpenTasks: (e: Event) => void;
|
||||||
} & {
|
} & {
|
||||||
error?: string;
|
error?: string;
|
||||||
|
headerMessage?: string;
|
||||||
i18n: I18n;
|
i18n: I18n;
|
||||||
itemName: string;
|
itemName: string;
|
||||||
task?: NotificationTaskInfo;
|
task?: NotificationTaskInfo;
|
||||||
@@ -36,13 +37,13 @@ export function NotificationConfirmationContainer({
|
|||||||
handleCloseNotification,
|
handleCloseNotification,
|
||||||
handleOpenVault,
|
handleOpenVault,
|
||||||
handleOpenTasks,
|
handleOpenTasks,
|
||||||
|
headerMessage,
|
||||||
i18n,
|
i18n,
|
||||||
itemName,
|
itemName,
|
||||||
task,
|
task,
|
||||||
theme = ThemeTypes.Light,
|
theme = ThemeTypes.Light,
|
||||||
type,
|
type,
|
||||||
}: NotificationConfirmationContainerProps) {
|
}: NotificationConfirmationContainerProps) {
|
||||||
const headerMessage = getHeaderMessage(i18n, type, error);
|
|
||||||
const confirmationMessage = getConfirmationMessage(i18n, type, error);
|
const confirmationMessage = getConfirmationMessage(i18n, type, error);
|
||||||
const buttonText = error ? i18n.newItem : i18n.view;
|
const buttonText = error ? i18n.newItem : i18n.view;
|
||||||
const buttonAria = error
|
const buttonAria = error
|
||||||
@@ -125,20 +126,3 @@ function getConfirmationMessage(i18n: I18n, type?: NotificationType, error?: str
|
|||||||
? i18n.notificationLoginSaveConfirmation
|
? i18n.notificationLoginSaveConfirmation
|
||||||
: i18n.notificationLoginUpdatedConfirmation;
|
: 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[];
|
ciphers?: NotificationCipherData[];
|
||||||
collections?: CollectionView[];
|
collections?: CollectionView[];
|
||||||
folders?: FolderView[];
|
folders?: FolderView[];
|
||||||
|
headerMessage?: string;
|
||||||
i18n: I18n;
|
i18n: I18n;
|
||||||
organizations?: OrgView[];
|
organizations?: OrgView[];
|
||||||
personalVaultIsAllowed?: boolean;
|
personalVaultIsAllowed?: boolean;
|
||||||
@@ -40,13 +41,13 @@ export function NotificationContainer({
|
|||||||
ciphers,
|
ciphers,
|
||||||
collections,
|
collections,
|
||||||
folders,
|
folders,
|
||||||
|
headerMessage,
|
||||||
i18n,
|
i18n,
|
||||||
organizations,
|
organizations,
|
||||||
personalVaultIsAllowed = true,
|
personalVaultIsAllowed = true,
|
||||||
theme = ThemeTypes.Light,
|
theme = ThemeTypes.Light,
|
||||||
type,
|
type,
|
||||||
}: NotificationContainerProps) {
|
}: NotificationContainerProps) {
|
||||||
const headerMessage = getHeaderMessage(i18n, type);
|
|
||||||
const showBody = type !== NotificationTypes.Unlock;
|
const showBody = type !== NotificationTypes.Unlock;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
@@ -98,16 +99,3 @@ const notificationContainerStyles = (theme: Theme) => css`
|
|||||||
padding-right: ${spacing["3"]};
|
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 { AdjustNotificationBarMessageData } from "../background/abstractions/notification.background";
|
||||||
import { NotificationCipherData } from "../content/components/cipher/types";
|
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 { NotificationConfirmationContainer } from "../content/components/notification/confirmation/container";
|
||||||
import { NotificationContainer } from "../content/components/notification/container";
|
import { NotificationContainer } from "../content/components/notification/container";
|
||||||
import { selectedFolder as selectedFolderSignal } from "../content/components/signals/selected-folder";
|
import { selectedFolder as selectedFolderSignal } from "../content/components/signals/selected-folder";
|
||||||
@@ -113,6 +113,68 @@ const findElementById = <ElementType extends HTMLElement>(
|
|||||||
return element as ElementType;
|
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.
|
* 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);
|
const resolvedTheme = getResolvedTheme(theme ?? ThemeTypes.Light);
|
||||||
|
|
||||||
if (useComponentBar) {
|
if (useComponentBar) {
|
||||||
|
const resolvedType = resolveNotificationType(notificationBarIframeInitData);
|
||||||
|
const headerMessage = getNotificationHeaderMessage(i18n, resolvedType);
|
||||||
|
appendHeaderMessageToTitle(headerMessage);
|
||||||
|
|
||||||
document.body.innerHTML = "";
|
document.body.innerHTML = "";
|
||||||
// Current implementations utilize a require for scss files which creates the need to remove the node.
|
// 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());
|
document.head.querySelectorAll('link[rel="stylesheet"]').forEach((node) => node.remove());
|
||||||
@@ -156,7 +222,8 @@ async function initNotificationBar(message: NotificationBarWindowMessage) {
|
|||||||
return render(
|
return render(
|
||||||
NotificationContainer({
|
NotificationContainer({
|
||||||
...notificationBarIframeInitData,
|
...notificationBarIframeInitData,
|
||||||
type: NotificationTypes.Unlock,
|
headerMessage,
|
||||||
|
type: resolvedType,
|
||||||
theme: resolvedTheme,
|
theme: resolvedTheme,
|
||||||
personalVaultIsAllowed: !personalVaultDisallowed,
|
personalVaultIsAllowed: !personalVaultDisallowed,
|
||||||
handleCloseNotification,
|
handleCloseNotification,
|
||||||
@@ -199,7 +266,8 @@ async function initNotificationBar(message: NotificationBarWindowMessage) {
|
|||||||
return render(
|
return render(
|
||||||
NotificationContainer({
|
NotificationContainer({
|
||||||
...notificationBarIframeInitData,
|
...notificationBarIframeInitData,
|
||||||
type: notificationBarIframeInitData.type as NotificationType,
|
headerMessage,
|
||||||
|
type: resolvedType,
|
||||||
theme: resolvedTheme,
|
theme: resolvedTheme,
|
||||||
personalVaultIsAllowed: !personalVaultDisallowed,
|
personalVaultIsAllowed: !personalVaultDisallowed,
|
||||||
handleCloseNotification,
|
handleCloseNotification,
|
||||||
@@ -429,6 +497,8 @@ function handleSaveCipherConfirmation(message: NotificationBarWindowMessage) {
|
|||||||
const { cipherId, task, itemName } = data || {};
|
const { cipherId, task, itemName } = data || {};
|
||||||
const i18n = getI18n();
|
const i18n = getI18n();
|
||||||
const resolvedTheme = getResolvedTheme(theme ?? ThemeTypes.Light);
|
const resolvedTheme = getResolvedTheme(theme ?? ThemeTypes.Light);
|
||||||
|
const resolvedType = resolveNotificationType(notificationBarIframeInitData);
|
||||||
|
const headerMessage = getConfirmationHeaderMessage(i18n, resolvedType, error);
|
||||||
|
|
||||||
globalThis.setTimeout(() => sendPlatformMessage({ command: "bgCloseNotificationBar" }), 5000);
|
globalThis.setTimeout(() => sendPlatformMessage({ command: "bgCloseNotificationBar" }), 5000);
|
||||||
|
|
||||||
@@ -438,6 +508,7 @@ function handleSaveCipherConfirmation(message: NotificationBarWindowMessage) {
|
|||||||
type: type as NotificationType,
|
type: type as NotificationType,
|
||||||
theme: resolvedTheme,
|
theme: resolvedTheme,
|
||||||
handleCloseNotification,
|
handleCloseNotification,
|
||||||
|
headerMessage,
|
||||||
i18n,
|
i18n,
|
||||||
error,
|
error,
|
||||||
itemName: itemName ?? i18n.typeLogin,
|
itemName: itemName ?? i18n.typeLogin,
|
||||||
|
|||||||
Reference in New Issue
Block a user