1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-18 09:13:33 +00:00

PM-21688 finalize a11y UX concerns misc (#14805)

* PM-21688 finalize a11y UX concerns misc

* add Close to close button for SR, handle error new item SR

* fix hover state for badge

* dynamic update button

* fix types

* Update apps/browser/src/autofill/content/components/lit-stories/mock-data.ts

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

* handle undefined

---------

Co-authored-by: Jonathan Prusik <jprusik@users.noreply.github.com>
This commit is contained in:
Daniel Riera
2025-05-16 12:51:11 -04:00
committed by GitHub
parent c8629b92d3
commit 32727e23db
11 changed files with 53 additions and 6 deletions

View File

@@ -1071,6 +1071,10 @@
}, },
"description": "Aria label for the view button in notification bar confirmation message" "description": "Aria label for the view button in notification bar confirmation message"
}, },
"notificationNewItemAria": {
"message": "New Item, opens in new window",
"description": "Aria label for the new item button in notification bar confirmation message when error is prompted"
},
"notificationEditTooltip": { "notificationEditTooltip": {
"message": "Edit before saving", "message": "Edit before saving",
"description": "Tooltip and Aria label for edit button on cipher item" "description": "Tooltip and Aria label for edit button on cipher item"

View File

@@ -64,6 +64,10 @@ const actionButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: The
background-color: ${themes[theme].primary["700"]}; background-color: ${themes[theme].primary["700"]};
color: ${themes[theme].text.contrast}; color: ${themes[theme].text.contrast};
} }
:focus {
outline: 2px solid ${themes[theme].primary["600"]};
outline-offset: 1px;
}
`} `}
svg { svg {

View File

@@ -8,15 +8,19 @@ import { border, themes, typography, spacing } from "../constants/styles";
export type BadgeButtonProps = { export type BadgeButtonProps = {
buttonAction: (e: Event) => void; buttonAction: (e: Event) => void;
buttonText: string; buttonText: string;
itemName: string;
disabled?: boolean; disabled?: boolean;
theme: Theme; theme: Theme;
username?: string;
}; };
export function BadgeButton({ export function BadgeButton({
buttonAction, buttonAction,
buttonText, buttonText,
disabled = false, disabled = false,
itemName,
theme, theme,
username,
}: BadgeButtonProps) { }: BadgeButtonProps) {
const handleButtonClick = (event: Event) => { const handleButtonClick = (event: Event) => {
if (!disabled) { if (!disabled) {
@@ -28,6 +32,7 @@ export function BadgeButton({
<button <button
type="button" type="button"
title=${buttonText} title=${buttonText}
aria-label=${[buttonText, [itemName, username].filter(Boolean).join(" ")]}
class=${badgeButtonStyles({ disabled, theme })} class=${badgeButtonStyles({ disabled, theme })}
@click=${handleButtonClick} @click=${handleButtonClick}
> >
@@ -65,5 +70,9 @@ const badgeButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: Them
background-color: ${themes[theme].primary["600"]}; background-color: ${themes[theme].primary["600"]};
color: ${themes[theme].text.contrast}; color: ${themes[theme].text.contrast};
} }
:focus {
outline: 2px solid ${themes[theme].primary["600"]};
outline-offset: 2px;
}
`} `}
`; `;

View File

@@ -3,17 +3,24 @@ import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums"; import { Theme } from "@bitwarden/common/platform/enums";
import { I18n } from "../common-types";
import { spacing, themes } from "../constants/styles"; import { spacing, themes } from "../constants/styles";
import { Close as CloseIcon } from "../icons"; import { Close as CloseIcon } from "../icons";
export type CloseButtonProps = { export type CloseButtonProps = {
i18n: I18n;
handleCloseNotification: (e: Event) => void; handleCloseNotification: (e: Event) => void;
theme: Theme; theme: Theme;
}; };
export function CloseButton({ handleCloseNotification, theme }: CloseButtonProps) { export function CloseButton({ handleCloseNotification, i18n, theme }: CloseButtonProps) {
return html` return html`
<button type="button" class=${closeButtonStyles(theme)} @click=${handleCloseNotification}> <button
type="button"
aria-label=${i18n.close}
class=${closeButtonStyles(theme)}
@click=${handleCloseNotification}
>
${CloseIcon({ theme })} ${CloseIcon({ theme })}
</button> </button>
`; `;

View File

@@ -8,8 +8,10 @@ import { I18n } from "../common-types";
export type CipherActionProps = { export type CipherActionProps = {
handleAction?: (e: Event) => void; handleAction?: (e: Event) => void;
i18n: I18n; i18n: I18n;
itemName: string;
notificationType: typeof NotificationTypes.Change | typeof NotificationTypes.Add; notificationType: typeof NotificationTypes.Change | typeof NotificationTypes.Add;
theme: Theme; theme: Theme;
username?: string;
}; };
export function CipherAction({ export function CipherAction({
@@ -17,14 +19,18 @@ export function CipherAction({
/* no-op */ /* no-op */
}, },
i18n, i18n,
itemName,
notificationType, notificationType,
theme, theme,
username,
}: CipherActionProps) { }: CipherActionProps) {
return notificationType === NotificationTypes.Change return notificationType === NotificationTypes.Change
? BadgeButton({ ? BadgeButton({
buttonAction: handleAction, buttonAction: handleAction,
buttonText: i18n.notificationUpdate, buttonText: i18n.notificationUpdate,
itemName,
theme, theme,
username,
}) })
: EditButton({ : EditButton({
buttonAction: handleAction, buttonAction: handleAction,

View File

@@ -32,14 +32,21 @@ export function CipherItem({
notificationType, notificationType,
theme = ThemeTypes.Light, theme = ThemeTypes.Light,
}: CipherItemProps) { }: CipherItemProps) {
const { icon } = cipher; const { icon, name, login } = cipher;
const uri = (icon.imageEnabled && icon.image) || undefined; const uri = (icon.imageEnabled && icon.image) || undefined;
let cipherActionButton = null; let cipherActionButton = null;
if (notificationType === NotificationTypes.Change || notificationType === NotificationTypes.Add) { if (notificationType === NotificationTypes.Change || notificationType === NotificationTypes.Add) {
cipherActionButton = html`<div> cipherActionButton = html`<div>
${CipherAction({ handleAction, i18n, notificationType, theme })} ${CipherAction({
handleAction,
i18n,
itemName: name,
notificationType,
theme,
username: login?.username,
})}
</div>`; </div>`;
} }

View File

@@ -3,6 +3,7 @@ import { Meta, StoryObj } from "@storybook/web-components";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { CloseButton, CloseButtonProps } from "../../buttons/close-button"; import { CloseButton, CloseButtonProps } from "../../buttons/close-button";
import { mockI18n } from "../mock-data";
export default { export default {
title: "Components/Buttons/Close Button", title: "Components/Buttons/Close Button",
@@ -15,6 +16,7 @@ export default {
handleCloseNotification: () => { handleCloseNotification: () => {
alert("Close button clicked!"); alert("Close button clicked!");
}, },
i18n: mockI18n,
}, },
parameters: { parameters: {
design: { design: {

View File

@@ -127,6 +127,7 @@ export const mockI18n = {
notificationUnlock: "Unlock", notificationUnlock: "Unlock",
notificationUnlockDesc: "Unlock your Bitwarden vault to complete the autofill request.", notificationUnlockDesc: "Unlock your Bitwarden vault to complete the autofill request.",
notificationViewAria: `View $ITEMNAME$, opens in new window`, notificationViewAria: `View $ITEMNAME$, opens in new window`,
notificationNewItemAria: "New Item, opens in new window",
saveAction: "Save", saveAction: "Save",
saveAsNewLoginAction: "Save as new login", saveAsNewLoginAction: "Save as new login",
saveFailure: "Error saving", saveFailure: "Error saving",

View File

@@ -45,7 +45,9 @@ export function NotificationConfirmationContainer({
const headerMessage = getHeaderMessage(i18n, type, error); 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 = chrome.i18n.getMessage("notificationViewAria", [itemName]); const buttonAria = error
? i18n.notificationNewItemAria
: chrome.i18n.getMessage("notificationViewAria", [itemName]);
let messageDetails: string | undefined; let messageDetails: string | undefined;
let remainingTasksCount: number | undefined; let remainingTasksCount: number | undefined;
@@ -68,6 +70,7 @@ export function NotificationConfirmationContainer({
<div class=${notificationContainerStyles(theme)}> <div class=${notificationContainerStyles(theme)}>
${NotificationHeader({ ${NotificationHeader({
handleCloseNotification, handleCloseNotification,
i18n,
message: headerMessage, message: headerMessage,
theme, theme,
})} })}

View File

@@ -53,6 +53,7 @@ export function NotificationContainer({
<div class=${notificationContainerStyles(theme)}> <div class=${notificationContainerStyles(theme)}>
${NotificationHeader({ ${NotificationHeader({
handleCloseNotification, handleCloseNotification,
i18n,
message: headerMessage, message: headerMessage,
theme, theme,
})} })}

View File

@@ -4,6 +4,7 @@ import { html } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
import { CloseButton } from "../buttons/close-button"; import { CloseButton } from "../buttons/close-button";
import { I18n } from "../common-types";
import { spacing, themes } from "../constants/styles"; import { spacing, themes } from "../constants/styles";
import { BrandIconContainer } from "../icons/brand-icon-container"; import { BrandIconContainer } from "../icons/brand-icon-container";
@@ -16,6 +17,7 @@ const { css } = createEmotion({
}); });
export type NotificationHeaderProps = { export type NotificationHeaderProps = {
i18n: I18n;
message?: string; message?: string;
standalone?: boolean; standalone?: boolean;
theme: Theme; theme: Theme;
@@ -23,6 +25,7 @@ export type NotificationHeaderProps = {
}; };
export function NotificationHeader({ export function NotificationHeader({
i18n,
message, message,
standalone = false, standalone = false,
theme = ThemeTypes.Light, theme = ThemeTypes.Light,
@@ -35,7 +38,7 @@ export function NotificationHeader({
<div class=${notificationHeaderStyles({ standalone, theme })}> <div class=${notificationHeaderStyles({ standalone, theme })}>
${showIcon ? BrandIconContainer({ theme }) : null} ${showIcon ? BrandIconContainer({ theme }) : null}
${message ? NotificationHeaderMessage({ message, theme }) : null} ${message ? NotificationHeaderMessage({ message, theme }) : null}
${isDismissable ? CloseButton({ handleCloseNotification, theme }) : null} ${isDismissable ? CloseButton({ handleCloseNotification, i18n, theme }) : null}
</div> </div>
`; `;
} }