1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 05:43:41 +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"; 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({ export function ActionButton({
buttonText, buttonText,
disabled = false, disabled = false,
theme, theme,
handleClick, handleClick,
}: { }: ActionButtonProps) {
buttonText: string | TemplateResult;
disabled?: boolean;
theme: Theme;
handleClick: (e: Event) => void;
}) {
const handleButtonClick = (event: Event) => { const handleButtonClick = (event: Event) => {
if (!disabled) { if (!disabled) {
handleClick(event); handleClick(event);

View File

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

View File

@@ -6,13 +6,12 @@ import { Theme } from "@bitwarden/common/platform/enums";
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 function CloseButton({ export type CloseButtonProps = {
handleCloseNotification,
theme,
}: {
handleCloseNotification: (e: Event) => void; handleCloseNotification: (e: Event) => void;
theme: Theme; theme: Theme;
}) { };
export function CloseButton({ handleCloseNotification, theme }: CloseButtonProps) {
return html` return html`
<button type="button" class=${closeButtonStyles(theme)} @click=${handleCloseNotification}> <button type="button" class=${closeButtonStyles(theme)} @click=${handleCloseNotification}>
${CloseIcon({ theme })} ${CloseIcon({ theme })}

View File

@@ -6,17 +6,14 @@ import { Theme } from "@bitwarden/common/platform/enums";
import { themes, typography, spacing } from "../constants/styles"; import { themes, typography, spacing } from "../constants/styles";
import { PencilSquare } from "../icons"; import { PencilSquare } from "../icons";
export function EditButton({ export type EditButtonProps = {
buttonAction,
buttonText,
disabled = false,
theme,
}: {
buttonAction: (e: Event) => void; buttonAction: (e: Event) => void;
buttonText: string; buttonText: string;
disabled?: boolean; disabled?: boolean;
theme: Theme; theme: Theme;
}) { };
export function EditButton({ buttonAction, buttonText, disabled = false, theme }: EditButtonProps) {
return html` return html`
<button <button
type="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 { BadgeButton } from "../../../content/components/buttons/badge-button";
import { EditButton } from "../../../content/components/buttons/edit-button"; import { EditButton } from "../../../content/components/buttons/edit-button";
import { NotificationTypes } from "../../../notification/abstractions/notification-bar"; 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({ export function CipherAction({
handleAction = () => { handleAction = () => {
@@ -11,12 +19,7 @@ export function CipherAction({
i18n, i18n,
notificationType, notificationType,
theme, theme,
}: { }: CipherActionProps) {
handleAction?: (e: Event) => void;
i18n: { [key: string]: string };
notificationType: typeof NotificationTypes.Change | typeof NotificationTypes.Add;
theme: Theme;
}) {
return notificationType === NotificationTypes.Change return notificationType === NotificationTypes.Change
? BadgeButton({ ? BadgeButton({
buttonAction: handleAction, buttonAction: handleAction,

View File

@@ -5,21 +5,18 @@ import { Theme } from "@bitwarden/common/platform/enums";
import { Globe } from "../../../content/components/icons"; import { Globe } from "../../../content/components/icons";
/** export type CipherIconProps = {
* @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,
}: {
color: string; color: string;
size: string; size: string;
theme: Theme; theme: Theme;
uri?: string; 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 }); const iconClass = cipherIconStyle({ width: size });
return uri return uri

View File

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

View File

@@ -8,7 +8,9 @@ import { themes, typography } from "../../../content/components/constants/styles
import { CipherInfoIndicatorIcons } from "./cipher-indicator-icons"; import { CipherInfoIndicatorIcons } from "./cipher-indicator-icons";
import { NotificationCipherData } from "./types"; 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 { name, login, organizationCategories } = cipher;
const hasIndicatorIcons = organizationCategories?.length; const hasIndicatorIcons = organizationCategories?.length;

View File

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

View File

@@ -1,15 +1,8 @@
import { Meta, StoryObj } from "@storybook/web-components"; 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"; import { ActionButton, ActionButtonProps } from "../../buttons/action-button";
type Args = {
buttonText: string;
disabled: boolean;
theme: Theme;
handleClick: (e: Event) => void;
};
export default { export default {
title: "Components/Buttons/Action Button", 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", 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, render: Template,
}; };

View File

@@ -1,15 +1,8 @@
import { Meta, StoryObj } from "@storybook/web-components"; 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"; import { BadgeButton, BadgeButtonProps } from "../../buttons/badge-button";
type Args = {
buttonAction: (e: Event) => void;
buttonText: string;
disabled?: boolean;
theme: Theme;
};
export default { export default {
title: "Components/Buttons/Badge Button", 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", 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, render: Template,
}; };

View File

@@ -1,13 +1,9 @@
import { Meta, StoryObj } from "@storybook/web-components"; 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 { export default {
title: "Components/Buttons/Close Button", title: "Components/Buttons/Close Button",
argTypes: { argTypes: {
@@ -26,10 +22,10 @@ export default {
url: "https://www.figma.com/design/LEhqLAcBPY8uDKRfU99n9W/Autofill-notification-redesign?node-id=502-24633&t=2O7uCAkwRZCcjumm-4", 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, render: Template,
}; };

View File

@@ -1,28 +1,22 @@
import { Meta, StoryObj } from "@storybook/web-components"; 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 { export default {
title: "Components/Buttons/Edit Button", title: "Components/Buttons/Edit Button",
argTypes: { argTypes: {
buttonAction: { control: false },
buttonText: { control: "text" }, buttonText: { control: "text" },
disabled: { control: "boolean" }, disabled: { control: "boolean" },
theme: { control: "select", options: [...Object.values(ThemeTypes)] }, theme: { control: "select", options: [...Object.values(ThemeTypes)] },
buttonAction: { control: false },
}, },
args: { args: {
buttonAction: () => alert("Clicked"),
buttonText: "Click Me", buttonText: "Click Me",
disabled: false, disabled: false,
theme: ThemeTypes.Light, theme: ThemeTypes.Light,
buttonAction: () => alert("Clicked"),
}, },
parameters: { parameters: {
design: { design: {
@@ -30,10 +24,10 @@ export default {
url: "https://www.figma.com/design/LEhqLAcBPY8uDKRfU99n9W/Autofill-notification-redesign?node-id=502-24633&t=2O7uCAkwRZCcjumm-4", 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, 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 { 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 { 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 { export default {
title: "Components/Ciphers/Cipher Action", title: "Components/Ciphers/Cipher Action",
argTypes: { argTypes: {
@@ -24,14 +19,13 @@ export default {
args: { args: {
theme: ThemeTypes.Light, theme: ThemeTypes.Light,
notificationType: NotificationTypes.Change, notificationType: NotificationTypes.Change,
handleAction: () => { handleAction: () => alert("Action triggered!"),
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, render: Template,
}; };

View File

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

View File

@@ -1,33 +1,28 @@
import { Meta, StoryObj } from "@storybook/web-components"; import { Meta, StoryObj } from "@storybook/web-components";
import { html } from "lit"; 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"; import {
CipherInfoIndicatorIcons,
type Args = { CipherInfoIndicatorIconsProps,
showBusinessIcon?: boolean; } from "../../cipher/cipher-indicator-icons";
showFamilyIcon?: boolean; import { OrganizationCategories } from "../../cipher/types";
theme: Theme;
};
export default { export default {
title: "Components/Ciphers/Cipher Indicator Icons", title: "Components/Ciphers/Cipher Indicator Icons",
argTypes: { argTypes: {
showBusinessIcon: { control: "boolean" },
showFamilyIcon: { control: "boolean" },
theme: { control: "select", options: [...Object.values(ThemeTypes)] }, theme: { control: "select", options: [...Object.values(ThemeTypes)] },
}, },
args: { args: {
theme: ThemeTypes.Light, theme: ThemeTypes.Light,
showBusinessIcon: true, organizationCategories: [...Object.values(OrganizationCategories)],
showFamilyIcon: false,
}, },
} as Meta<Args>; } as Meta<CipherInfoIndicatorIconsProps>;
const Template: StoryObj<Args>["render"] = (args) => const Template: StoryObj<CipherInfoIndicatorIconsProps>["render"] = (args) =>
html`<div>${CipherInfoIndicatorIcons({ ...args })}</div>`; html`<div>${CipherInfoIndicatorIcons({ ...args })}</div>`;
export const Default: StoryObj<Args> = { export const Default: StoryObj<CipherInfoIndicatorIconsProps> = {
render: Template, 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 { Meta, StoryObj } from "@storybook/web-components";
import { html } from "lit"; 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"; import * as Icons from "../../icons";
type Args = { type Args = IconProps & {
color?: string;
disabled?: boolean;
theme: Theme;
size: number; size: number;
iconLink: URL; iconLink: URL;
}; };
@@ -16,21 +14,19 @@ type Args = {
export default { export default {
title: "Components/Icons", title: "Components/Icons",
argTypes: { argTypes: {
iconLink: { control: "text" },
color: { control: "color" }, color: { control: "color" },
disabled: { control: "boolean" }, disabled: { control: "boolean" },
theme: { control: "select", options: [...Object.values(ThemeTypes)] }, theme: { control: "select", options: [...Object.values(ThemeTypes)] },
size: { control: "number", min: 10, max: 100, step: 1 }, size: { control: "number", min: 10, max: 100, step: 1 },
}, },
args: { args: {
iconLink: new URL("https://bitwarden.com"),
disabled: false, disabled: false,
theme: ThemeTypes.Light, theme: ThemeTypes.Light,
size: 50, size: 50,
}, },
} as Meta<Args>; } 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 <div
style="width: ${args.size}px; height: ${args.size}px; display: flex; align-items: center; justify-content: center;" 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 { Meta, StoryObj } from "@storybook/web-components";
import { html } from "lit"; 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"; import * as Illustrations from "../../illustrations";
type Args = { type Args = IconProps & {
theme: Theme;
size: number; size: number;
}; };
@@ -24,7 +24,7 @@ export default {
const Template = ( const Template = (
args: Args, args: Args,
IllustrationComponent: (props: Args) => ReturnType<typeof html>, IllustrationComponent: (props: IconProps) => ReturnType<typeof html>,
) => html` ) => html`
<div <div
style="width: ${args.size}px; height: ${args.size}px; display: flex; align-items: center; justify-content: center;" 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 { 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", id: "unique-id1",
name: "A folder", name: "A folder",
@@ -35,34 +77,101 @@ export const mockFolderData = [
}, },
]; ];
export const mockOrganizationData = [ export const mockCiphers = [
{ {
id: "unique-id0", id: "1",
name: "Another personal vault", name: "Example Cipher",
}, type: CipherType.Login,
{ favorite: false,
id: "unique-id1", reprompt: CipherRepromptType.None,
name: "Acme, inc", icon: {
productTierType: ProductTierType.Teams, imageEnabled: true,
}, image: "",
{ fallbackImage: "https://example.com/fallback.png",
id: "unique-id2", icon: "icon-class",
name: "A Really Long Business Name That Just Kinda Goes On For A Really Long Time", },
productTierType: ProductTierType.TeamsStarter, login: { username: "user@example.com" },
},
{
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 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 { 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 { CipherType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { NotificationType } from "../../../../notification/abstractions/notification-bar"; import { NotificationTypes } from "../../../../notification/abstractions/notification-bar";
import { NotificationCipherData } from "../../cipher/types"; import { NotificationBody, NotificationBodyProps } from "../../notification/body";
import { NotificationBody } from "../../notification/body"; import { mockCiphers, mockI18n } from "../mock-data";
type Args = {
ciphers: NotificationCipherData[];
i18n: { [key: string]: string };
notificationType: NotificationType;
theme: Theme;
handleEditOrUpdateAction: (e: Event) => void;
};
export default { export default {
title: "Components/Notifications/Body", title: "Components/Notifications/Body",
argTypes: { argTypes: {
ciphers: { control: "object" },
theme: { control: "select", options: [...Object.values(ThemeTypes)] }, theme: { control: "select", options: [...Object.values(ThemeTypes)] },
notificationType: { notificationType: {
control: "select", control: "select",
options: ["add", "change", "unlock", "fileless-import"], options: [...Object.values(NotificationTypes)],
}, },
handleEditOrUpdateAction: { control: false },
}, },
args: { args: {
ciphers: [ ciphers: mockCiphers,
{ notificationType: NotificationTypes.Change,
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" },
},
],
theme: ThemeTypes.Light, theme: ThemeTypes.Light,
notificationType: "change", handleEditOrUpdateAction: () => window.alert("clicked!"),
i18n: mockI18n,
}, },
parameters: { parameters: {
design: { design: {
@@ -52,10 +29,10 @@ export default {
url: "https://www.figma.com/design/LEhqLAcBPY8uDKRfU99n9W/Autofill-notification-redesign?node-id=217-6841&m=dev", 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, render: Template,
}; };

View File

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

View File

@@ -7,6 +7,7 @@ import {
NotificationConfirmationContainer, NotificationConfirmationContainer,
NotificationConfirmationContainerProps, NotificationConfirmationContainerProps,
} from "../../../notification/confirmation/container"; } from "../../../notification/confirmation/container";
import { mockI18n, mockCiphers, mockBrowserI18nGetMessage, mockTasks } from "../../mock-data";
export default { export default {
title: "Components/Notifications/Confirmation", title: "Components/Notifications/Confirmation",
@@ -17,27 +18,14 @@ export default {
}, },
args: { args: {
error: "", error: "",
task: { task: mockTasks[0],
orgName: "Acme, Inc.", itemName: mockCiphers[0].name,
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",
},
type: NotificationTypes.Change, type: NotificationTypes.Change,
username: "Acme, Inc. Login",
theme: ThemeTypes.Light, theme: ThemeTypes.Light,
handleCloseNotification: () => alert("Close notification action triggered"),
handleOpenVault: () => alert("Open vault action triggered"),
handleOpenTasks: () => alert("Open tasks action triggered"),
i18n: mockI18n,
}, },
parameters: { parameters: {
design: { design: {
@@ -53,3 +41,10 @@ const Template = (args: NotificationConfirmationContainerProps) =>
export const Default: StoryObj<NotificationConfirmationContainerProps> = { export const Default: StoryObj<NotificationConfirmationContainerProps> = {
render: Template, render: Template,
}; };
window.chrome = {
...window.chrome,
i18n: {
getMessage: mockBrowserI18nGetMessage,
},
} as typeof chrome;

View File

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

View File

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

View File

@@ -6,6 +6,7 @@ import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-repromp
import { NotificationTypes } from "../../../../notification/abstractions/notification-bar"; import { NotificationTypes } from "../../../../notification/abstractions/notification-bar";
import { NotificationContainer, NotificationContainerProps } from "../../notification/container"; import { NotificationContainer, NotificationContainerProps } from "../../notification/container";
import { mockBrowserI18nGetMessage, mockI18n } from "../mock-data";
export default { export default {
title: "Components/Notifications", title: "Components/Notifications",
@@ -32,19 +33,10 @@ export default {
login: { username: "user@example.com" }, 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, type: NotificationTypes.Change,
username: "mockUsername", username: "mockUsername",
theme: ThemeTypes.Light, theme: ThemeTypes.Light,
i18n: mockI18n,
}, },
parameters: { parameters: {
design: { design: {
@@ -59,3 +51,10 @@ const Template = (args: NotificationContainerProps) => NotificationContainer({ .
export const Default: StoryObj<NotificationContainerProps> = { export const Default: StoryObj<NotificationContainerProps> = {
render: Template, 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 { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
import { NotificationTypes } from "../../../../notification/abstractions/notification-bar";
import { NotificationFooter, NotificationFooterProps } from "../../notification/footer"; import { NotificationFooter, NotificationFooterProps } from "../../notification/footer";
import { mockFolderData, mockOrganizationData } from "../mock-data"; import { mockCollections, mockI18n, mockFolders, mockOrganizations } from "../mock-data";
export default { export default {
title: "Components/Notifications/Footer", title: "Components/Notifications/Footer",
argTypes: { argTypes: {
notificationType: { notificationType: {
control: "select", control: "select",
options: ["add", "change", "unlock", "fileless-import"], options: [...Object.values(NotificationTypes)],
}, },
theme: { control: "select", options: [...Object.values(ThemeTypes)] }, theme: { control: "select", options: [...Object.values(ThemeTypes)] },
}, },
args: { args: {
folders: mockFolderData, collections: mockCollections,
i18n: { folders: mockFolders,
saveAction: "Save", notificationType: NotificationTypes.Add,
saveAsNewLoginAction: "Save as New Login", organizations: mockOrganizations,
},
notificationType: "add",
organizations: mockOrganizationData,
theme: ThemeTypes.Light, theme: ThemeTypes.Light,
handleSaveAction: () => alert("Save action triggered"), handleSaveAction: () => alert("Save action triggered"),
i18n: mockI18n,
}, },
parameters: { parameters: {
design: { design: {

View File

@@ -1,15 +1,9 @@
import { Meta, StoryObj } from "@storybook/web-components"; 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"; import { NotificationHeader, NotificationHeaderProps } from "../../notification/header";
type Args = {
message: string;
standalone: boolean;
theme: Theme;
handleCloseNotification: (e: Event) => void;
};
export default { export default {
title: "Components/Notifications/Header", 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", 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, render: Template,
}; };

View File

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

View File

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

View File

@@ -1,14 +1,8 @@
import { Meta, StoryObj } from "@storybook/web-components"; 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"; import { ItemRow, ItemRowProps } from "../../rows/item-row";
type Args = {
theme: Theme;
children: TemplateResult | TemplateResult[];
};
export default { export default {
title: "Components/Rows/Item Row", title: "Components/Rows/Item Row",
@@ -19,10 +13,10 @@ export default {
args: { args: {
theme: ThemeTypes.Light, 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, render: Template,
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,19 +14,21 @@ const { css } = createEmotion({
key: optionItemsTagName, key: optionItemsTagName,
}); });
export type OptionItemsProps = {
theme: Theme;
topOffset: number;
label?: string;
options: Option[];
handleOptionSelection: (selectedOption: Option) => void;
};
export function OptionItems({ export function OptionItems({
theme, theme,
topOffset, topOffset,
label, label,
options, options,
handleOptionSelection, handleOptionSelection,
}: { }: OptionItemsProps) {
theme: Theme;
topOffset: number;
label?: string;
options: Option[];
handleOptionSelection: (selectedOption: Option) => void;
}) {
// @TODO get client vendor from context // @TODO get client vendor from context
const isSafari = false; 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"; import { spacing, themes, typography } from "../../../content/components/constants/styles";
export function ActionRow({ export type ActionRowProps = {
handleAction,
itemText,
theme = ThemeTypes.Light,
}: {
itemText: string; itemText: string;
handleAction?: (e: Event) => void; handleAction?: (e: Event) => void;
theme: Theme; theme: Theme;
}) { };
export function ActionRow({ handleAction, itemText, theme = ThemeTypes.Light }: ActionRowProps) {
return html` return html`
<button type="button" @click=${handleAction} class=${actionRowStyles(theme)} title=${itemText}> <button type="button" @click=${handleAction} class=${actionRowStyles(theme)} title=${itemText}>
<span>${itemText}</span> <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"; import { spacing, themes, typography } from "../../../content/components/constants/styles";
export function ItemRow({ export type ItemRowProps = {
theme = ThemeTypes.Light,
children,
}: {
theme: Theme; theme: Theme;
children: TemplateResult | TemplateResult[]; children: TemplateResult | TemplateResult[];
}) { };
export function ItemRow({ theme = ThemeTypes.Light, children }: ItemRowProps) {
return html` <div class=${itemRowStyles({ theme })}>${children}</div> `; 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_locked.png")
: chrome.runtime.getURL("images/icon38.png"); : chrome.runtime.getURL("images/icon38.png");
setupLogoLink(i18n); setupLogoLink(i18n.appName);
// i18n for "Add" template // i18n for "Add" template
const addTemplate = document.getElementById("template-add") as HTMLTemplateElement; const addTemplate = document.getElementById("template-add") as HTMLTemplateElement;
@@ -523,9 +523,9 @@ function handleWindowMessage(event: MessageEvent) {
handler({ message }); handler({ message });
} }
function setupLogoLink(i18n: Record<string, string>) { function setupLogoLink(linkText: string) {
const logoLink = document.getElementById("logo-link") as HTMLAnchorElement; const logoLink = document.getElementById("logo-link") as HTMLAnchorElement;
logoLink.title = i18n.appName; logoLink.title = linkText;
const setWebVaultUrlLink = (webVaultURL: string) => { const setWebVaultUrlLink = (webVaultURL: string) => {
const newVaultURL = webVaultURL && decodeURIComponent(webVaultURL); const newVaultURL = webVaultURL && decodeURIComponent(webVaultURL);
if (newVaultURL && newVaultURL !== logoLink.href) { if (newVaultURL && newVaultURL !== logoLink.href) {