1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 05:13:29 +00:00

[PM-21211] Update Lit component stories (#14657)

* replace string with message catalog representation

* add I18n type

* update components

* update and add stories and mock data

* mock chrome.i18n.getMessage for lit stories that require it

* set a max-width for the header component story

* move i18n to bottom of story controls
This commit is contained in:
Jonathan Prusik
2025-05-07 09:47:04 -04:00
committed by GitHub
parent df40954b61
commit 8c43232558
48 changed files with 484 additions and 363 deletions

View File

@@ -5,17 +5,19 @@ import { Theme } from "@bitwarden/common/platform/enums";
import { border, themes, typography, spacing } from "../constants/styles";
export type ActionButtonProps = {
buttonText: string | TemplateResult;
disabled?: boolean;
theme: Theme;
handleClick: (e: Event) => void;
};
export function ActionButton({
buttonText,
disabled = false,
theme,
handleClick,
}: {
buttonText: string | TemplateResult;
disabled?: boolean;
theme: Theme;
handleClick: (e: Event) => void;
}) {
}: ActionButtonProps) {
const handleButtonClick = (event: Event) => {
if (!disabled) {
handleClick(event);

View File

@@ -5,17 +5,19 @@ import { Theme } from "@bitwarden/common/platform/enums";
import { border, themes, typography, spacing } from "../constants/styles";
export type BadgeButtonProps = {
buttonAction: (e: Event) => void;
buttonText: string;
disabled?: boolean;
theme: Theme;
};
export function BadgeButton({
buttonAction,
buttonText,
disabled = false,
theme,
}: {
buttonAction: (e: Event) => void;
buttonText: string;
disabled?: boolean;
theme: Theme;
}) {
}: BadgeButtonProps) {
const handleButtonClick = (event: Event) => {
if (!disabled) {
buttonAction(event);

View File

@@ -6,13 +6,12 @@ import { Theme } from "@bitwarden/common/platform/enums";
import { spacing, themes } from "../constants/styles";
import { Close as CloseIcon } from "../icons";
export function CloseButton({
handleCloseNotification,
theme,
}: {
export type CloseButtonProps = {
handleCloseNotification: (e: Event) => void;
theme: Theme;
}) {
};
export function CloseButton({ handleCloseNotification, theme }: CloseButtonProps) {
return html`
<button type="button" class=${closeButtonStyles(theme)} @click=${handleCloseNotification}>
${CloseIcon({ theme })}

View File

@@ -6,17 +6,14 @@ import { Theme } from "@bitwarden/common/platform/enums";
import { themes, typography, spacing } from "../constants/styles";
import { PencilSquare } from "../icons";
export function EditButton({
buttonAction,
buttonText,
disabled = false,
theme,
}: {
export type EditButtonProps = {
buttonAction: (e: Event) => void;
buttonText: string;
disabled?: boolean;
theme: Theme;
}) {
};
export function EditButton({ buttonAction, buttonText, disabled = false, theme }: EditButtonProps) {
return html`
<button
type="button"

View File

@@ -3,6 +3,14 @@ import { Theme } from "@bitwarden/common/platform/enums";
import { BadgeButton } from "../../../content/components/buttons/badge-button";
import { EditButton } from "../../../content/components/buttons/edit-button";
import { NotificationTypes } from "../../../notification/abstractions/notification-bar";
import { I18n } from "../common-types";
export type CipherActionProps = {
handleAction?: (e: Event) => void;
i18n: I18n;
notificationType: typeof NotificationTypes.Change | typeof NotificationTypes.Add;
theme: Theme;
};
export function CipherAction({
handleAction = () => {
@@ -11,12 +19,7 @@ export function CipherAction({
i18n,
notificationType,
theme,
}: {
handleAction?: (e: Event) => void;
i18n: { [key: string]: string };
notificationType: typeof NotificationTypes.Change | typeof NotificationTypes.Add;
theme: Theme;
}) {
}: CipherActionProps) {
return notificationType === NotificationTypes.Change
? BadgeButton({
buttonAction: handleAction,

View File

@@ -5,21 +5,18 @@ import { Theme } from "@bitwarden/common/platform/enums";
import { Globe } from "../../../content/components/icons";
/**
* @param {string} props.color contextual color override if no icon URI is available
* @param {string} props.size valid CSS `width` value, represents the width-basis of the graphic, with height maintaining original aspect-ratio
*/
export function CipherIcon({
color,
size,
theme,
uri,
}: {
export type CipherIconProps = {
color: string;
size: string;
theme: Theme;
uri?: string;
}) {
};
/**
* @param {string} props.color contextual color override if no icon URI is available
* @param {string} props.size valid CSS `width` value, represents the width-basis of the graphic, with height maintaining original aspect-ratio
*/
export function CipherIcon({ color, size, theme, uri }: CipherIconProps) {
const iconClass = cipherIconStyle({ width: size });
return uri

View File

@@ -16,13 +16,15 @@ const cipherIndicatorIconsMap: Record<
[OrganizationCategories.family]: Family,
};
export type CipherInfoIndicatorIconsProps = {
organizationCategories?: OrganizationCategory[];
theme: Theme;
};
export function CipherInfoIndicatorIcons({
organizationCategories = [],
theme,
}: {
organizationCategories?: OrganizationCategory[];
theme: Theme;
}) {
}: CipherInfoIndicatorIconsProps) {
return html`
<span class=${cipherInfoIndicatorIconsStyles}>
${organizationCategories.map((name) =>

View File

@@ -8,7 +8,9 @@ import { themes, typography } from "../../../content/components/constants/styles
import { CipherInfoIndicatorIcons } from "./cipher-indicator-icons";
import { NotificationCipherData } from "./types";
export function CipherInfo({ cipher, theme }: { cipher: NotificationCipherData; theme: Theme }) {
export type CipherInfoProps = { cipher: NotificationCipherData; theme: Theme };
export function CipherInfo({ cipher, theme }: CipherInfoProps) {
const { name, login, organizationCategories } = cipher;
const hasIndicatorIcons = organizationCategories?.length;

View File

@@ -8,6 +8,7 @@ import {
NotificationType,
NotificationTypes,
} from "../../../notification/abstractions/notification-bar";
import { I18n } from "../common-types";
import { CipherAction } from "./cipher-action";
import { CipherIcon } from "./cipher-icon";
@@ -16,19 +17,21 @@ import { NotificationCipherData } from "./types";
const cipherIconWidth = "24px";
export type CipherItemProps = {
cipher: NotificationCipherData;
handleAction?: (e: Event) => void;
i18n: I18n;
notificationType?: NotificationType;
theme: Theme;
};
export function CipherItem({
cipher,
handleAction,
i18n,
notificationType,
theme = ThemeTypes.Light,
}: {
cipher: NotificationCipherData;
handleAction?: (e: Event) => void;
i18n: { [key: string]: string };
notificationType?: NotificationType;
theme: Theme;
}) {
}: CipherItemProps) {
const { icon } = cipher;
const uri = (icon.imageEnabled && icon.image) || undefined;

View File

@@ -3,6 +3,10 @@ import { TemplateResult } from "lit";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { Theme } from "@bitwarden/common/platform/enums";
export type I18n = {
[key: string]: string;
};
export type IconProps = {
color?: string;
disabled?: boolean;

View File

@@ -1,15 +1,8 @@
import { Meta, StoryObj } from "@storybook/web-components";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { ActionButton } from "../../buttons/action-button";
type Args = {
buttonText: string;
disabled: boolean;
theme: Theme;
handleClick: (e: Event) => void;
};
import { ActionButton, ActionButtonProps } from "../../buttons/action-button";
export default {
title: "Components/Buttons/Action Button",
@@ -31,10 +24,10 @@ export default {
url: "https://www.figma.com/design/LEhqLAcBPY8uDKRfU99n9W/Autofill-notification-redesign?node-id=487-14755&t=2O7uCAkwRZCcjumm-4",
},
},
} as Meta<Args>;
} as Meta<ActionButtonProps>;
const Template = (args: Args) => ActionButton({ ...args });
const Template = (args: ActionButtonProps) => ActionButton({ ...args });
export const Default: StoryObj<Args> = {
export const Default: StoryObj<ActionButtonProps> = {
render: Template,
};

View File

@@ -1,15 +1,8 @@
import { Meta, StoryObj } from "@storybook/web-components";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { BadgeButton } from "../../buttons/badge-button";
type Args = {
buttonAction: (e: Event) => void;
buttonText: string;
disabled?: boolean;
theme: Theme;
};
import { BadgeButton, BadgeButtonProps } from "../../buttons/badge-button";
export default {
title: "Components/Buttons/Badge Button",
@@ -31,10 +24,10 @@ export default {
url: "https://www.figma.com/design/LEhqLAcBPY8uDKRfU99n9W/Autofill-notification-redesign?node-id=502-24973&t=2O7uCAkwRZCcjumm-4",
},
},
} as Meta<Args>;
} as Meta<BadgeButtonProps>;
const Template = (args: Args) => BadgeButton({ ...args });
const Template = (args: BadgeButtonProps) => BadgeButton({ ...args });
export const Default: StoryObj<Args> = {
export const Default: StoryObj<BadgeButtonProps> = {
render: Template,
};

View File

@@ -1,13 +1,9 @@
import { Meta, StoryObj } from "@storybook/web-components";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { CloseButton } from "../../buttons/close-button";
import { CloseButton, CloseButtonProps } from "../../buttons/close-button";
type Args = {
handleCloseNotification: (e: Event) => void;
theme: Theme;
};
export default {
title: "Components/Buttons/Close Button",
argTypes: {
@@ -26,10 +22,10 @@ export default {
url: "https://www.figma.com/design/LEhqLAcBPY8uDKRfU99n9W/Autofill-notification-redesign?node-id=502-24633&t=2O7uCAkwRZCcjumm-4",
},
},
} as Meta<Args>;
} as Meta<CloseButtonProps>;
const Template = (args: Args) => CloseButton({ ...args });
const Template = (args: CloseButtonProps) => CloseButton({ ...args });
export const Default: StoryObj<Args> = {
export const Default: StoryObj<CloseButtonProps> = {
render: Template,
};

View File

@@ -1,28 +1,22 @@
import { Meta, StoryObj } from "@storybook/web-components";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { EditButton } from "../../buttons/edit-button";
import { EditButton, EditButtonProps } from "../../buttons/edit-button";
type Args = {
buttonAction: (e: Event) => void;
buttonText: string;
disabled?: boolean;
theme: Theme;
};
export default {
title: "Components/Buttons/Edit Button",
argTypes: {
buttonAction: { control: false },
buttonText: { control: "text" },
disabled: { control: "boolean" },
theme: { control: "select", options: [...Object.values(ThemeTypes)] },
buttonAction: { control: false },
},
args: {
buttonAction: () => alert("Clicked"),
buttonText: "Click Me",
disabled: false,
theme: ThemeTypes.Light,
buttonAction: () => alert("Clicked"),
},
parameters: {
design: {
@@ -30,10 +24,10 @@ export default {
url: "https://www.figma.com/design/LEhqLAcBPY8uDKRfU99n9W/Autofill-notification-redesign?node-id=502-24633&t=2O7uCAkwRZCcjumm-4",
},
},
} as Meta<Args>;
} as Meta<EditButtonProps>;
const Template = (args: Args) => EditButton({ ...args });
const Template = (args: EditButtonProps) => EditButton({ ...args });
export const Default: StoryObj<Args> = {
export const Default: StoryObj<EditButtonProps> = {
render: Template,
};

View File

@@ -0,0 +1,38 @@
import { Meta, StoryObj } from "@storybook/web-components";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import {
OptionSelectionButton,
OptionSelectionButtonProps,
} from "../../buttons/option-selection-button";
export default {
title: "Components/Buttons/Option Selection Button",
argTypes: {
disabled: { control: "boolean" },
handleButtonClick: { control: false },
text: { control: "text" },
theme: { control: "select", options: [...Object.values(ThemeTypes)] },
toggledOn: { control: "boolean" },
},
args: {
disabled: false,
handleButtonClick: () => alert("Clicked"),
text: "Click Me",
theme: ThemeTypes.Light,
toggledOn: false,
},
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/design/LEhqLAcBPY8uDKRfU99n9W/Autofill-notification-redesign?node-id=502-24633&t=2O7uCAkwRZCcjumm-4",
},
},
} as Meta<OptionSelectionButtonProps>;
const Template = (args: OptionSelectionButtonProps) => OptionSelectionButton({ ...args });
export const Default: StoryObj<OptionSelectionButtonProps> = {
render: Template,
};

View File

@@ -1,16 +1,11 @@
import { Meta, StoryObj } from "@storybook/web-components";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { NotificationTypes } from "../../../../notification/abstractions/notification-bar";
import { CipherAction } from "../../cipher/cipher-action";
import { CipherAction, CipherActionProps } from "../../cipher/cipher-action";
import { mockI18n } from "../mock-data";
type Args = {
handleAction?: (e: Event) => void;
i18n: { [key: string]: string };
notificationType: typeof NotificationTypes.Change | typeof NotificationTypes.Add;
theme: Theme;
};
export default {
title: "Components/Ciphers/Cipher Action",
argTypes: {
@@ -24,14 +19,13 @@ export default {
args: {
theme: ThemeTypes.Light,
notificationType: NotificationTypes.Change,
handleAction: () => {
alert("Action triggered!");
},
handleAction: () => alert("Action triggered!"),
i18n: mockI18n,
},
} as Meta<Args>;
} as Meta<CipherActionProps>;
const Template = (args: Args) => CipherAction({ ...args });
const Template = (args: CipherActionProps) => CipherAction({ ...args });
export const Default: StoryObj<Args> = {
export const Default: StoryObj<CipherActionProps> = {
render: Template,
};

View File

@@ -1,16 +1,9 @@
import { Meta, StoryObj } from "@storybook/web-components";
import { html } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { CipherIcon } from "../../cipher/cipher-icon";
type Args = {
color: string;
size: string;
theme: Theme;
uri?: string;
};
import { CipherIcon, CipherIconProps } from "../../cipher/cipher-icon";
export default {
title: "Components/Ciphers/Cipher Icon",
@@ -25,9 +18,9 @@ export default {
theme: ThemeTypes.Light,
uri: "",
},
} as Meta<Args>;
} as Meta<CipherIconProps>;
const Template = (args: Args) => {
const Template = (args: CipherIconProps) => {
return html`
<div style="width: ${args.size}; height: ${args.size}; overflow: hidden;">
${CipherIcon({ ...args })}
@@ -35,6 +28,6 @@ const Template = (args: Args) => {
`;
};
export const Default: StoryObj<Args> = {
export const Default: StoryObj<CipherIconProps> = {
render: Template,
};

View File

@@ -1,33 +1,28 @@
import { Meta, StoryObj } from "@storybook/web-components";
import { html } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { CipherInfoIndicatorIcons } from "../../cipher/cipher-indicator-icons";
type Args = {
showBusinessIcon?: boolean;
showFamilyIcon?: boolean;
theme: Theme;
};
import {
CipherInfoIndicatorIcons,
CipherInfoIndicatorIconsProps,
} from "../../cipher/cipher-indicator-icons";
import { OrganizationCategories } from "../../cipher/types";
export default {
title: "Components/Ciphers/Cipher Indicator Icons",
argTypes: {
showBusinessIcon: { control: "boolean" },
showFamilyIcon: { control: "boolean" },
theme: { control: "select", options: [...Object.values(ThemeTypes)] },
},
args: {
theme: ThemeTypes.Light,
showBusinessIcon: true,
showFamilyIcon: false,
organizationCategories: [...Object.values(OrganizationCategories)],
},
} as Meta<Args>;
} as Meta<CipherInfoIndicatorIconsProps>;
const Template: StoryObj<Args>["render"] = (args) =>
const Template: StoryObj<CipherInfoIndicatorIconsProps>["render"] = (args) =>
html`<div>${CipherInfoIndicatorIcons({ ...args })}</div>`;
export const Default: StoryObj<Args> = {
export const Default: StoryObj<CipherInfoIndicatorIconsProps> = {
render: Template,
};

View File

@@ -0,0 +1,23 @@
import { Meta, StoryObj } from "@storybook/web-components";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { CipherInfo, CipherInfoProps } from "../../cipher/cipher-info";
import { mockCiphers } from "../mock-data";
export default {
title: "Components/Ciphers/Cipher Info",
argTypes: {
theme: { control: "select", options: [...Object.values(ThemeTypes)] },
},
args: {
cipher: mockCiphers[0],
theme: ThemeTypes.Light,
},
} as Meta<CipherInfoProps>;
const Template = (args: CipherInfoProps) => CipherInfo({ ...args });
export const Default: StoryObj<CipherInfoProps> = {
render: Template,
};

View File

@@ -0,0 +1,32 @@
import { Meta, StoryObj } from "@storybook/web-components";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { NotificationTypes } from "../../../../notification/abstractions/notification-bar";
import { CipherItem, CipherItemProps } from "../../cipher/cipher-item";
import { mockCiphers, mockI18n } from "../mock-data";
export default {
title: "Components/Ciphers/Cipher Item",
argTypes: {
theme: { control: "select", options: [...Object.values(ThemeTypes)] },
handleAction: { control: false },
notificationType: {
control: "select",
options: [NotificationTypes.Change, NotificationTypes.Add],
},
},
args: {
cipher: mockCiphers[0],
theme: ThemeTypes.Light,
notificationType: NotificationTypes.Change,
handleAction: () => alert("Clicked"),
i18n: mockI18n,
},
} as Meta<CipherItemProps>;
const Template = (args: CipherItemProps) => CipherItem({ ...args });
export const Default: StoryObj<CipherItemProps> = {
render: Template,
};

View File

@@ -1,14 +1,12 @@
import { Meta, StoryObj } from "@storybook/web-components";
import { html } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { IconProps } from "../../common-types";
import * as Icons from "../../icons";
type Args = {
color?: string;
disabled?: boolean;
theme: Theme;
type Args = IconProps & {
size: number;
iconLink: URL;
};
@@ -16,21 +14,19 @@ type Args = {
export default {
title: "Components/Icons",
argTypes: {
iconLink: { control: "text" },
color: { control: "color" },
disabled: { control: "boolean" },
theme: { control: "select", options: [...Object.values(ThemeTypes)] },
size: { control: "number", min: 10, max: 100, step: 1 },
},
args: {
iconLink: new URL("https://bitwarden.com"),
disabled: false,
theme: ThemeTypes.Light,
size: 50,
},
} as Meta<Args>;
const Template = (args: Args, IconComponent: (props: Args) => ReturnType<typeof html>) => html`
const Template = (args: Args, IconComponent: (props: IconProps) => ReturnType<typeof html>) => html`
<div
style="width: ${args.size}px; height: ${args.size}px; display: flex; align-items: center; justify-content: center;"
>

View File

@@ -1,12 +1,12 @@
import { Meta, StoryObj } from "@storybook/web-components";
import { html } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { IconProps } from "../../common-types";
import * as Illustrations from "../../illustrations";
type Args = {
theme: Theme;
type Args = IconProps & {
size: number;
};
@@ -24,7 +24,7 @@ export default {
const Template = (
args: Args,
IllustrationComponent: (props: Args) => ReturnType<typeof html>,
IllustrationComponent: (props: IconProps) => ReturnType<typeof html>,
) => html`
<div
style="width: ${args.size}px; height: ${args.size}px; display: flex; align-items: center; justify-content: center;"

View File

@@ -1,6 +1,48 @@
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
export const mockFolderData = [
export const mockOrganizations = [
{
id: "unique-id0",
name: "Another personal vault",
},
{
id: "unique-id1",
name: "Acme, inc",
productTierType: ProductTierType.Teams,
},
{
id: "unique-id2",
name: "A Really Long Business Name That Just Kinda Goes On For A Really Long Time",
productTierType: ProductTierType.TeamsStarter,
},
{
id: "unique-id3",
name: "Family Vault",
productTierType: ProductTierType.Families,
},
{
id: "unique-id4",
name: "Family Vault Trial",
productTierType: ProductTierType.Free,
},
{
id: "unique-id5",
name: "Exciting Enterprises, LLC",
productTierType: ProductTierType.Enterprise,
},
];
export const mockCollections = [
{
id: "collection-id-01",
name: "A collection for stuff",
organizationId: mockOrganizations[0].id,
},
];
export const mockFolders = [
{
id: "unique-id1",
name: "A folder",
@@ -35,34 +77,101 @@ export const mockFolderData = [
},
];
export const mockOrganizationData = [
export const mockCiphers = [
{
id: "unique-id0",
name: "Another personal vault",
},
{
id: "unique-id1",
name: "Acme, inc",
productTierType: ProductTierType.Teams,
},
{
id: "unique-id2",
name: "A Really Long Business Name That Just Kinda Goes On For A Really Long Time",
productTierType: ProductTierType.TeamsStarter,
},
{
id: "unique-id3",
name: "Family Vault",
productTierType: ProductTierType.Families,
},
{
id: "unique-id4",
name: "Family Vault Trial",
productTierType: ProductTierType.Free,
},
{
id: "unique-id5",
name: "Exciting Enterprises, LLC",
productTierType: ProductTierType.Enterprise,
id: "1",
name: "Example Cipher",
type: CipherType.Login,
favorite: false,
reprompt: CipherRepromptType.None,
icon: {
imageEnabled: true,
image: "",
fallbackImage: "https://example.com/fallback.png",
icon: "icon-class",
},
login: { username: "user@example.com" },
},
];
export const mockTasks = [
{
orgName: "Acme, Inc.",
remainingTasksCount: 0,
},
];
export const mockI18n = {
appName: "Bitwarden",
close: "Close",
collection: "Collection",
folder: "Folder",
loginSaveSuccess: "Login saved",
loginSaveConfirmation: "$ITEMNAME$ saved to Bitwarden.",
loginUpdateSuccess: "Login updated",
loginUpdatedConfirmation: "$ITEMNAME$ updated in Bitwarden.",
loginUpdateTaskSuccess:
"Great job! You took the steps to make you and $ORGANIZATION$ more secure.",
loginUpdateTaskSuccessAdditional:
"Thank you for making $ORGANIZATION$ more secure. You have $TASK_COUNT$ more passwords to update.",
nextSecurityTaskAction: "Change next password",
newItem: "New item",
never: "Never",
myVault: "My vault",
notificationAddDesc: "Should Bitwarden remember this password for you?",
notificationAddSave: "Save",
notificationChangeDesc: "Do you want to update this password in Bitwarden?",
notificationUpdate: "Update",
notificationEdit: "Edit",
notificationEditTooltip: "Edit before saving",
notificationUnlock: "Unlock",
notificationUnlockDesc: "Unlock your Bitwarden vault to complete the autofill request.",
notificationViewAria: `View $ITEMNAME$, opens in new window`,
saveAction: "Save",
saveAsNewLoginAction: "Save as new login",
saveFailure: "Error saving",
saveFailureDetails: "Oh no! We couldn't save this. Try entering the details manually.",
saveLogin: "Save login",
typeLogin: "Login",
updateLoginAction: "Update login",
updateLogin: "Update existing login",
vault: "Vault",
view: "View",
} as const;
type i18nMessageName = keyof typeof mockI18n;
type i18nMessageValue = (typeof mockI18n)[i18nMessageName];
/**
* Very basic mock of {@link chrome.i18n.getMessage} to enable stories
*
* @param {i18nMessageName} messageName must match a key in {@link mockI18n}
* @param {(string | string[])} [substitutions]
* @return {*} {(i18nMessageValue | string)}
*/
export function mockBrowserI18nGetMessage(
messageName: i18nMessageName,
substitutions?: string | string[],
): i18nMessageValue | string {
let normalizedSubstitutions: string[] = [];
if (substitutions) {
normalizedSubstitutions =
typeof substitutions === "string"
? [substitutions]
: substitutions.length
? substitutions
: [];
}
if (normalizedSubstitutions.length) {
const resolvedString = normalizedSubstitutions.reduce((builtString, substitution) => {
// Replace first found match each iteration, in order
return builtString.replace(/\$[A-Z_]+\$/, substitution);
}, mockI18n[messageName] || "");
return resolvedString;
}
return mockI18n[messageName];
}

View File

@@ -1,50 +1,27 @@
import { Meta, StoryObj } from "@storybook/web-components";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { NotificationType } from "../../../../notification/abstractions/notification-bar";
import { NotificationCipherData } from "../../cipher/types";
import { NotificationBody } from "../../notification/body";
type Args = {
ciphers: NotificationCipherData[];
i18n: { [key: string]: string };
notificationType: NotificationType;
theme: Theme;
handleEditOrUpdateAction: (e: Event) => void;
};
import { NotificationTypes } from "../../../../notification/abstractions/notification-bar";
import { NotificationBody, NotificationBodyProps } from "../../notification/body";
import { mockCiphers, mockI18n } from "../mock-data";
export default {
title: "Components/Notifications/Body",
argTypes: {
ciphers: { control: "object" },
theme: { control: "select", options: [...Object.values(ThemeTypes)] },
notificationType: {
control: "select",
options: ["add", "change", "unlock", "fileless-import"],
options: [...Object.values(NotificationTypes)],
},
handleEditOrUpdateAction: { control: false },
},
args: {
ciphers: [
{
id: "1",
name: "Example Cipher",
type: CipherType.Login,
favorite: false,
reprompt: CipherRepromptType.None,
icon: {
imageEnabled: true,
image: "",
fallbackImage: "https://example.com/fallback.png",
icon: "icon-class",
},
login: { username: "user@example.com" },
},
],
ciphers: mockCiphers,
notificationType: NotificationTypes.Change,
theme: ThemeTypes.Light,
notificationType: "change",
handleEditOrUpdateAction: () => window.alert("clicked!"),
i18n: mockI18n,
},
parameters: {
design: {
@@ -52,10 +29,10 @@ export default {
url: "https://www.figma.com/design/LEhqLAcBPY8uDKRfU99n9W/Autofill-notification-redesign?node-id=217-6841&m=dev",
},
},
} as Meta<Args>;
} as Meta<NotificationBodyProps>;
const Template = (args: Args) => NotificationBody({ ...args });
const Template = (args: NotificationBodyProps) => NotificationBody({ ...args });
export const Default: StoryObj<Args> = {
export const Default: StoryObj<NotificationBodyProps> = {
render: Template,
};

View File

@@ -11,6 +11,7 @@ export default {
title: "Components/Notifications/Confirmation/Body",
argTypes: {
error: { control: "text" },
buttonAria: { control: "text" },
buttonText: { control: "text" },
confirmationMessage: { control: "text" },
messageDetails: { control: "text" },
@@ -18,9 +19,12 @@ export default {
},
args: {
error: "",
buttonAria: "View",
buttonText: "View",
confirmationMessage: "[item name] updated in Bitwarden.",
messageDetails: "You can view it in your vault.",
tasksAreComplete: false,
handleOpenVault: () => window.alert("Link was clicked!"),
theme: ThemeTypes.Light,
},
parameters: {

View File

@@ -7,6 +7,7 @@ import {
NotificationConfirmationContainer,
NotificationConfirmationContainerProps,
} from "../../../notification/confirmation/container";
import { mockI18n, mockCiphers, mockBrowserI18nGetMessage, mockTasks } from "../../mock-data";
export default {
title: "Components/Notifications/Confirmation",
@@ -17,27 +18,14 @@ export default {
},
args: {
error: "",
task: {
orgName: "Acme, Inc.",
remainingTasksCount: 0,
},
handleCloseNotification: () => alert("Close notification action triggered"),
handleOpenTasks: () => alert("Open tasks action triggered"),
i18n: {
loginSaveSuccess: "Login saved",
loginUpdateSuccess: "Login updated",
loginUpdateTaskSuccessAdditional:
"Thank you for making your organization more secure. You have 3 more passwords to update.",
loginUpdateTaskSuccess:
"Great job! You took the steps to make you and your organization more secure.",
nextSecurityTaskAction: "Change next password",
saveFailure: "Error saving",
saveFailureDetails: "Oh no! We couldn't save this. Try entering the details manually.",
view: "View",
},
task: mockTasks[0],
itemName: mockCiphers[0].name,
type: NotificationTypes.Change,
username: "Acme, Inc. Login",
theme: ThemeTypes.Light,
handleCloseNotification: () => alert("Close notification action triggered"),
handleOpenVault: () => alert("Open vault action triggered"),
handleOpenTasks: () => alert("Open tasks action triggered"),
i18n: mockI18n,
},
parameters: {
design: {
@@ -53,3 +41,10 @@ const Template = (args: NotificationConfirmationContainerProps) =>
export const Default: StoryObj<NotificationConfirmationContainerProps> = {
render: Template,
};
window.chrome = {
...window.chrome,
i18n: {
getMessage: mockBrowserI18nGetMessage,
},
} as typeof chrome;

View File

@@ -7,6 +7,7 @@ import {
NotificationConfirmationFooter,
NotificationConfirmationFooterProps,
} from "../../../notification/confirmation/footer";
import { mockI18n } from "../../mock-data";
export default {
title: "Components/Notifications/Confirmation/Footer",
@@ -14,11 +15,9 @@ export default {
theme: { control: "select", options: [...Object.values(ThemeTypes)] },
},
args: {
handleButtonClick: () => alert("Action button triggered"),
i18n: {
nextSecurityTaskAction: "Change next password",
},
theme: ThemeTypes.Light,
handleButtonClick: () => alert("Action button triggered"),
i18n: mockI18n,
},
parameters: {
design: {

View File

@@ -10,6 +10,7 @@ import {
export default {
title: "Components/Notifications/Confirmation/Message",
argTypes: {
buttonAria: { control: "text" },
buttonText: { control: "text" },
message: { control: "text" },
messageDetails: { control: "text" },
@@ -19,6 +20,7 @@ export default {
buttonText: "View",
message: "[item name] updated in Bitwarden.",
messageDetails: "It was added to your vault.",
handleClick: () => window.alert("link was clicked!"),
theme: ThemeTypes.Light,
},
parameters: {

View File

@@ -6,6 +6,7 @@ import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-repromp
import { NotificationTypes } from "../../../../notification/abstractions/notification-bar";
import { NotificationContainer, NotificationContainerProps } from "../../notification/container";
import { mockBrowserI18nGetMessage, mockI18n } from "../mock-data";
export default {
title: "Components/Notifications",
@@ -32,19 +33,10 @@ export default {
login: { username: "user@example.com" },
},
],
i18n: {
loginSaveSuccess: "Login saved",
loginUpdateSuccess: "Login updated",
saveAction: "Save",
saveAsNewLoginAction: "Save as new login",
saveFailure: "Error saving",
saveFailureDetails: "Oh no! We couldn't save this. Try entering the details manually.",
updateLoginPrompt: "Update existing login?",
view: "View",
},
type: NotificationTypes.Change,
username: "mockUsername",
theme: ThemeTypes.Light,
i18n: mockI18n,
},
parameters: {
design: {
@@ -59,3 +51,10 @@ const Template = (args: NotificationContainerProps) => NotificationContainer({ .
export const Default: StoryObj<NotificationContainerProps> = {
render: Template,
};
window.chrome = {
...window.chrome,
i18n: {
getMessage: mockBrowserI18nGetMessage,
},
} as typeof chrome;

View File

@@ -3,28 +3,27 @@ import { html } from "lit";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { NotificationTypes } from "../../../../notification/abstractions/notification-bar";
import { NotificationFooter, NotificationFooterProps } from "../../notification/footer";
import { mockFolderData, mockOrganizationData } from "../mock-data";
import { mockCollections, mockI18n, mockFolders, mockOrganizations } from "../mock-data";
export default {
title: "Components/Notifications/Footer",
argTypes: {
notificationType: {
control: "select",
options: ["add", "change", "unlock", "fileless-import"],
options: [...Object.values(NotificationTypes)],
},
theme: { control: "select", options: [...Object.values(ThemeTypes)] },
},
args: {
folders: mockFolderData,
i18n: {
saveAction: "Save",
saveAsNewLoginAction: "Save as New Login",
},
notificationType: "add",
organizations: mockOrganizationData,
collections: mockCollections,
folders: mockFolders,
notificationType: NotificationTypes.Add,
organizations: mockOrganizations,
theme: ThemeTypes.Light,
handleSaveAction: () => alert("Save action triggered"),
i18n: mockI18n,
},
parameters: {
design: {

View File

@@ -1,15 +1,9 @@
import { Meta, StoryObj } from "@storybook/web-components";
import { html } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { NotificationHeader } from "../../notification/header";
type Args = {
message: string;
standalone: boolean;
theme: Theme;
handleCloseNotification: (e: Event) => void;
};
import { NotificationHeader, NotificationHeaderProps } from "../../notification/header";
export default {
title: "Components/Notifications/Header",
@@ -30,10 +24,11 @@ export default {
url: "https://www.figma.com/design/LEhqLAcBPY8uDKRfU99n9W/Autofill-notification-redesign?node-id=32-3461&m=dev",
},
},
} as Meta<Args>;
} as Meta<NotificationHeaderProps>;
const Template = (args: Args) => NotificationHeader({ ...args });
const Template = (args: NotificationHeaderProps) =>
html`<div style="max-width:400px;">${NotificationHeader({ ...args })}</div>`;
export const Default: StoryObj<Args> = {
export const Default: StoryObj<NotificationHeaderProps> = {
render: Template,
};

View File

@@ -7,11 +7,11 @@ import { Option } from "../../common-types";
import { themes } from "../../constants/styles";
import { User, Business } from "../../icons";
import "../../option-selection/option-selection";
import { mockOrganizationData } from "../mock-data";
import { mockOrganizations } from "../mock-data";
const mockOptions: Option[] = [
{ icon: User, text: "My Vault", value: "0" },
...mockOrganizationData.map(({ id, name }) => ({ icon: Business, text: name, value: id })),
...mockOrganizations.map(({ id, name }) => ({ icon: Business, text: name, value: id })),
];
type ComponentProps = {

View File

@@ -1,14 +1,8 @@
import { Meta, StoryObj } from "@storybook/web-components";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { ActionRow } from "../../rows/action-row";
type Args = {
itemText: string;
handleAction: (e: Event) => void;
theme: Theme;
};
import { ActionRow, ActionRowProps } from "../../rows/action-row";
export default {
title: "Components/Rows/Action Row",
@@ -22,10 +16,10 @@ export default {
theme: ThemeTypes.Light,
handleAction: () => alert("Action triggered"),
},
} as Meta<Args>;
} as Meta<ActionRowProps>;
const Template = (args: Args) => ActionRow({ ...args });
const Template = (args: ActionRowProps) => ActionRow({ ...args });
export const Default: StoryObj<Args> = {
export const Default: StoryObj<ActionRowProps> = {
render: Template,
};

View File

@@ -1,14 +1,8 @@
import { Meta, StoryObj } from "@storybook/web-components";
import { TemplateResult } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { ItemRow } from "../../rows/item-row";
type Args = {
theme: Theme;
children: TemplateResult | TemplateResult[];
};
import { ItemRow, ItemRowProps } from "../../rows/item-row";
export default {
title: "Components/Rows/Item Row",
@@ -19,10 +13,10 @@ export default {
args: {
theme: ThemeTypes.Light,
},
} as Meta<Args>;
} as Meta<ItemRowProps>;
const Template = (args: Args) => ItemRow({ ...args });
const Template = (args: ItemRowProps) => ItemRow({ ...args });
export const Default: StoryObj<Args> = {
export const Default: StoryObj<ItemRowProps> = {
render: Template,
};

View File

@@ -6,6 +6,7 @@ import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
import { NotificationType } from "../../../notification/abstractions/notification-bar";
import { CipherItem } from "../cipher";
import { NotificationCipherData } from "../cipher/types";
import { I18n } from "../common-types";
import { scrollbarStyles, spacing, themes, typography } from "../constants/styles";
import { ItemRow } from "../rows/item-row";
@@ -15,20 +16,21 @@ const { css } = createEmotion({
key: componentClassPrefix,
});
export type NotificationBodyProps = {
ciphers?: NotificationCipherData[];
i18n: I18n;
notificationType?: NotificationType;
theme: Theme;
handleEditOrUpdateAction: (e: Event) => void;
};
export function NotificationBody({
ciphers = [],
i18n,
notificationType,
theme = ThemeTypes.Light,
handleEditOrUpdateAction,
}: {
ciphers?: NotificationCipherData[];
customClasses?: string[];
i18n: { [key: string]: string };
notificationType?: NotificationType;
theme: Theme;
handleEditOrUpdateAction: (e: Event) => void;
}) {
}: NotificationBodyProps) {
// @TODO get client vendor from context
const isSafari = false;

View File

@@ -3,7 +3,7 @@ import { html } from "lit";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { Theme } from "@bitwarden/common/platform/enums";
import { Option, OrgView, FolderView, CollectionView } from "../common-types";
import { Option, OrgView, FolderView, I18n, CollectionView } from "../common-types";
import { Business, Family, Folder, User, CollectionShared } from "../icons";
import { ButtonRow } from "../rows/button-row";
import { selectedCollection as selectedCollectionSignal } from "../signals/selected-collection";
@@ -30,7 +30,7 @@ const defaultNoneSelectValue = "0";
export type NotificationButtonRowProps = {
collections?: CollectionView[];
folders?: FolderView[];
i18n: { [key: string]: string };
i18n: I18n;
organizations?: OrgView[];
primaryButton: {
text: string;
@@ -148,7 +148,7 @@ export function NotificationButtonRow({
? [
{
id: "collection",
label: "Collection", // @TODO localize
label: i18n.collection,
options: collectionOptions,
selectedSignal: selectedCollectionSignal,
},

View File

@@ -15,8 +15,8 @@ const { css } = createEmotion({
});
export type NotificationConfirmationBodyProps = {
buttonAria: string;
buttonText: string;
itemName: string;
confirmationMessage: string;
error?: string;
messageDetails?: string;
@@ -26,8 +26,8 @@ export type NotificationConfirmationBodyProps = {
};
export function NotificationConfirmationBody({
buttonAria,
buttonText,
itemName,
confirmationMessage,
error,
messageDetails,
@@ -44,8 +44,8 @@ export function NotificationConfirmationBody({
<div class=${iconContainerStyles(error)}>${IconComponent({ theme })}</div>
${showConfirmationMessage
? NotificationConfirmationMessage({
buttonAria,
buttonText,
itemName,
message: confirmationMessage,
messageDetails,
theme,

View File

@@ -9,6 +9,7 @@ import {
NotificationType,
NotificationTypes,
} from "../../../../notification/abstractions/notification-bar";
import { I18n } from "../../common-types";
import { themes, spacing } from "../../constants/styles";
import {
NotificationHeader,
@@ -24,7 +25,7 @@ export type NotificationConfirmationContainerProps = NotificationBarIframeInitDa
handleOpenTasks: (e: Event) => void;
} & {
error?: string;
i18n: { [key: string]: string };
i18n: I18n;
itemName: string;
task?: NotificationTaskInfo;
type: NotificationType;
@@ -44,6 +45,7 @@ export function NotificationConfirmationContainer({
const headerMessage = getHeaderMessage(i18n, type, error);
const confirmationMessage = getConfirmationMessage(i18n, itemName, type, error);
const buttonText = error ? i18n.newItem : i18n.view;
const buttonAria = chrome.i18n.getMessage("notificationViewAria", [itemName]);
let messageDetails: string | undefined;
let remainingTasksCount: number | undefined;
@@ -70,8 +72,8 @@ export function NotificationConfirmationContainer({
theme,
})}
${NotificationConfirmationBody({
buttonAria,
buttonText,
itemName,
confirmationMessage,
tasksAreComplete,
messageDetails,
@@ -106,7 +108,7 @@ const notificationContainerStyles = (theme: Theme) => css`
`;
function getConfirmationMessage(
i18n: { [key: string]: string },
i18n: I18n,
itemName: string,
type?: NotificationType,
error?: string,
@@ -117,14 +119,10 @@ function getConfirmationMessage(
if (error) {
return i18n.saveFailureDetails;
}
return type === "add" ? loginSaveConfirmation : loginUpdatedConfirmation;
return type === NotificationTypes.Add ? loginSaveConfirmation : loginUpdatedConfirmation;
}
function getHeaderMessage(
i18n: { [key: string]: string },
type?: NotificationType,
error?: string,
) {
function getHeaderMessage(i18n: I18n, type?: NotificationType, error?: string) {
if (error) {
return i18n.saveFailure;
}

View File

@@ -4,11 +4,12 @@ import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { ActionButton } from "../../buttons/action-button";
import { I18n } from "../../common-types";
import { spacing, themes } from "../../constants/styles";
import { ExternalLink } from "../../icons";
export type NotificationConfirmationFooterProps = {
i18n: { [key: string]: string };
i18n: I18n;
theme: Theme;
handleButtonClick: (event: Event) => void;
};

View File

@@ -6,8 +6,8 @@ import { Theme } from "@bitwarden/common/platform/enums";
import { themes, typography } from "../../constants/styles";
export type NotificationConfirmationMessageProps = {
buttonAria?: string;
buttonText?: string;
itemName: string;
message?: string;
messageDetails?: string;
handleClick: () => void;
@@ -15,15 +15,13 @@ export type NotificationConfirmationMessageProps = {
};
export function NotificationConfirmationMessage({
buttonAria,
buttonText,
itemName,
message,
messageDetails,
handleClick,
theme,
}: NotificationConfirmationMessageProps) {
const buttonAria = chrome.i18n.getMessage("notificationViewAria", [itemName]);
return html`
<div>
${message || buttonText

View File

@@ -9,7 +9,7 @@ import {
NotificationType,
} from "../../../notification/abstractions/notification-bar";
import { NotificationCipherData } from "../cipher/types";
import { CollectionView, FolderView, OrgView } from "../common-types";
import { CollectionView, FolderView, I18n, OrgView } from "../common-types";
import { themes, spacing } from "../constants/styles";
import { NotificationBody, componentClassPrefix as notificationBodyClassPrefix } from "./body";
@@ -27,7 +27,7 @@ export type NotificationContainerProps = NotificationBarIframeInitData & {
ciphers?: NotificationCipherData[];
collections?: CollectionView[];
folders?: FolderView[];
i18n: { [key: string]: string };
i18n: I18n;
organizations?: OrgView[];
personalVaultIsAllowed?: boolean;
type: NotificationType; // @TODO typing override for generic `NotificationBarIframeInitData.type`
@@ -99,7 +99,7 @@ const notificationContainerStyles = (theme: Theme) => css`
}
`;
function getHeaderMessage(i18n: { [key: string]: string }, type?: NotificationType) {
function getHeaderMessage(i18n: I18n, type?: NotificationType) {
switch (type) {
case NotificationTypes.Add:
return i18n.saveLogin;

View File

@@ -7,7 +7,7 @@ import {
NotificationType,
NotificationTypes,
} from "../../../notification/abstractions/notification-bar";
import { OrgView, FolderView, CollectionView } from "../common-types";
import { OrgView, FolderView, I18n, CollectionView } from "../common-types";
import { spacing, themes } from "../constants/styles";
import { NotificationButtonRow } from "./button-row";
@@ -15,7 +15,7 @@ import { NotificationButtonRow } from "./button-row";
export type NotificationFooterProps = {
collections?: CollectionView[];
folders?: FolderView[];
i18n: { [key: string]: string };
i18n: I18n;
notificationType?: NotificationType;
organizations?: OrgView[];
personalVaultIsAllowed: boolean;

View File

@@ -15,17 +15,19 @@ const { css } = createEmotion({
key: componentClassPrefix,
});
export type NotificationHeaderProps = {
message?: string;
standalone?: boolean;
theme: Theme;
handleCloseNotification: (e: Event) => void;
};
export function NotificationHeader({
message,
standalone = false,
theme = ThemeTypes.Light,
handleCloseNotification,
}: {
message?: string;
standalone?: boolean;
theme: Theme;
handleCloseNotification: (e: Event) => void;
}) {
}: NotificationHeaderProps) {
const showIcon = true;
const isDismissable = true;

View File

@@ -12,16 +12,12 @@ const { css } = createEmotion({
key: optionItemTagName,
});
export function OptionItem({
icon,
text,
value,
theme,
handleSelection,
}: Option & {
export type OptionItemProps = Option & {
theme: Theme;
handleSelection: () => void;
}) {
};
export function OptionItem({ icon, text, value, theme, handleSelection }: OptionItemProps) {
const handleSelectionKeyUpProxy = (event: KeyboardEvent) => {
const listenedForKeys = new Set(["Enter", "Space"]);
if (listenedForKeys.has(event.code) && event.target instanceof Element) {

View File

@@ -14,19 +14,21 @@ const { css } = createEmotion({
key: optionItemsTagName,
});
export type OptionItemsProps = {
theme: Theme;
topOffset: number;
label?: string;
options: Option[];
handleOptionSelection: (selectedOption: Option) => void;
};
export function OptionItems({
theme,
topOffset,
label,
options,
handleOptionSelection,
}: {
theme: Theme;
topOffset: number;
label?: string;
options: Option[];
handleOptionSelection: (selectedOption: Option) => void;
}) {
}: OptionItemsProps) {
// @TODO get client vendor from context
const isSafari = false;

View File

@@ -5,15 +5,13 @@ import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
import { spacing, themes, typography } from "../../../content/components/constants/styles";
export function ActionRow({
handleAction,
itemText,
theme = ThemeTypes.Light,
}: {
export type ActionRowProps = {
itemText: string;
handleAction?: (e: Event) => void;
theme: Theme;
}) {
};
export function ActionRow({ handleAction, itemText, theme = ThemeTypes.Light }: ActionRowProps) {
return html`
<button type="button" @click=${handleAction} class=${actionRowStyles(theme)} title=${itemText}>
<span>${itemText}</span>

View File

@@ -5,13 +5,12 @@ import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
import { spacing, themes, typography } from "../../../content/components/constants/styles";
export function ItemRow({
theme = ThemeTypes.Light,
children,
}: {
export type ItemRowProps = {
theme: Theme;
children: TemplateResult | TemplateResult[];
}) {
};
export function ItemRow({ theme = ThemeTypes.Light, children }: ItemRowProps) {
return html` <div class=${itemRowStyles({ theme })}>${children}</div> `;
}

View File

@@ -192,7 +192,7 @@ async function initNotificationBar(message: NotificationBarWindowMessage) {
? chrome.runtime.getURL("images/icon38_locked.png")
: chrome.runtime.getURL("images/icon38.png");
setupLogoLink(i18n);
setupLogoLink(i18n.appName);
// i18n for "Add" template
const addTemplate = document.getElementById("template-add") as HTMLTemplateElement;
@@ -523,9 +523,9 @@ function handleWindowMessage(event: MessageEvent) {
handler({ message });
}
function setupLogoLink(i18n: Record<string, string>) {
function setupLogoLink(linkText: string) {
const logoLink = document.getElementById("logo-link") as HTMLAnchorElement;
logoLink.title = i18n.appName;
logoLink.title = linkText;
const setWebVaultUrlLink = (webVaultURL: string) => {
const newVaultURL = webVaultURL && decodeURIComponent(webVaultURL);
if (newVaultURL && newVaultURL !== logoLink.href) {