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:
@@ -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"
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -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>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -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,
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -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>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user