1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-07 19:13:39 +00:00

[PM-16247] Autofill base common content components (#12668)

* PoC implementation

* build notification header components

* use emotion css, and add button row components

* add icons

* update close button component to use new icon

* add cipher components

* reorganize notification component to accomodate body overflow with static footer

* add action row component and fix overflow cases

* fix component directory casings

* add scrollbar styles

* fix edit button icon display size issue

* fix edit button interaction

* cleanup and add dropdown menu buttons

* fix footer display of full-width children

* use svg brand icon in header component

* refine body and footer overflow layout handling

* fix fallback cipher icon sizing and other cleanup

* component restructure and cleanup

* restructure icon components

* cleanup

* re-org notification body and footer components and add typing

* additional cleanup
This commit is contained in:
Jonathan Prusik
2025-01-07 15:09:43 -05:00
committed by GitHub
parent f99a3c4162
commit dbed5ff79b
37 changed files with 2019 additions and 46 deletions

View File

@@ -0,0 +1,66 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { border, themes, typography, spacing } from "../constants/styles";
export function ActionButton({
buttonAction,
buttonText,
disabled = false,
theme,
}: {
buttonAction: (e: Event) => void;
buttonText: string;
disabled?: boolean;
theme: Theme;
}) {
const handleButtonClick = (event: Event) => {
if (!disabled) {
buttonAction(event);
}
};
return html`
<button
type="button"
title=${buttonText}
class=${actionButtonStyles({ disabled, theme })}
@click=${handleButtonClick}
>
${buttonText}
</button>
`;
}
const actionButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: Theme }) => css`
${typography.body2}
user-select: none;
border: 1px solid transparent;
border-radius: ${border.radius.full};
padding: ${spacing["1"]} ${spacing["3"]};
width: 100%;
overflow: hidden;
text-align: center;
text-overflow: ellipsis;
font-weight: 700;
${disabled
? `
background-color: ${themes[theme].secondary["300"]};
color: ${themes[theme].text.muted};
`
: `
background-color: ${themes[theme].primary["600"]};
cursor: pointer;
color: ${themes[theme].text.contrast};
:hover {
border-color: ${themes[theme].primary["700"]};
background-color: ${themes[theme].primary["700"]};
color: ${themes[theme].text.contrast};
}
`}
`;

View File

@@ -0,0 +1,67 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { border, themes, typography, spacing } from "../constants/styles";
export function BadgeButton({
buttonAction,
buttonText,
disabled = false,
theme,
}: {
buttonAction: (e: Event) => void;
buttonText: string;
disabled?: boolean;
theme: Theme;
}) {
const handleButtonClick = (event: Event) => {
if (!disabled) {
buttonAction(event);
}
};
return html`
<button
type="button"
title=${buttonText}
class=${badgeButtonStyles({ disabled, theme })}
@click=${handleButtonClick}
>
${buttonText}
</button>
`;
}
const badgeButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: Theme }) => css`
${typography.helperMedium}
user-select: none;
border-radius: ${border.radius.full};
padding: ${spacing["1"]} ${spacing["2"]};
max-height: fit-content;
overflow: hidden;
text-align: center;
text-overflow: ellipsis;
font-weight: 500;
${disabled
? `
border: 0.5px solid ${themes[theme].secondary["300"]};
background-color: ${themes[theme].secondary["300"]};
color: ${themes[theme].text.muted};
`
: `
border: 0.5px solid ${themes[theme].primary["700"]};
background-color: ${themes[theme].primary["100"]};
cursor: pointer;
color: ${themes[theme].primary["700"]};
:hover {
border-color: ${themes[theme].primary["600"]};
background-color: ${themes[theme].primary["600"]};
color: ${themes[theme].text.contrast};
}
`}
`;

View File

@@ -0,0 +1,39 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { spacing, themes } from "../constants/styles";
import { Close as CloseIcon } from "../icons";
export function CloseButton({
handleCloseNotification,
theme,
}: {
handleCloseNotification: (e: Event) => void;
theme: Theme;
}) {
return html`
<button type="button" class=${closeButtonStyles(theme)} @click=${handleCloseNotification}>
${CloseIcon({ theme })}
</button>
`;
}
const closeButtonStyles = (theme: Theme) => css`
border: 1px solid transparent;
border-radius: ${spacing["1"]};
background-color: transparent;
cursor: pointer;
width: 36px;
height: 36px;
:hover {
border: 1px solid ${themes[theme].primary["600"]};
}
> svg {
width: 20px;
height: 20px;
}
`;

View File

@@ -0,0 +1,60 @@
import { css } from "@emotion/css";
import { html } from "lit";
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,
}: {
buttonAction: (e: Event) => void;
buttonText: string;
disabled?: boolean;
theme: Theme;
}) {
return html`
<button
type="button"
title=${buttonText}
class=${editButtonStyles({ disabled, theme })}
@click=${(event: Event) => {
!disabled && buttonAction(event);
}}
>
${PencilSquare({ disabled, theme })}
</button>
`;
}
const editButtonStyles = ({ disabled, theme }: { disabled?: boolean; theme: Theme }) => css`
${typography.helperMedium}
user-select: none;
display: flex;
border: 1px solid transparent;
border-radius: ${spacing["1"]};
background-color: transparent;
padding: ${spacing["1"]};
max-height: fit-content;
overflow: hidden;
${!disabled
? `
cursor: pointer;
:hover {
border-color: ${themes[theme].primary["600"]};
}
`
: ""}
> svg {
width: 16px;
height: fit-content;
}
`;

View File

@@ -0,0 +1,31 @@
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";
export function CipherAction({
handleAction = () => {
/* no-op */
},
notificationType,
theme,
}: {
handleAction?: (e: Event) => void;
notificationType: typeof NotificationTypes.Change | typeof NotificationTypes.Add;
theme: Theme;
}) {
return notificationType === NotificationTypes.Change
? BadgeButton({
buttonAction: handleAction,
// @TODO localize
buttonText: "Update item",
theme,
})
: EditButton({
buttonAction: handleAction,
// @TODO localize
buttonText: "Edit item",
theme,
});
}

View File

@@ -0,0 +1,33 @@
import { css } from "@emotion/css";
import { html } from "lit";
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,
}: {
color: string;
size: string;
theme: Theme;
uri?: string;
}) {
const iconClass = cipherIconStyle({ width: size });
return uri
? html`<img class=${iconClass} src=${uri} />`
: html`<span class=${iconClass}>${Globe({ color, theme })}</span>`;
}
const cipherIconStyle = ({ width }: { width: string }) => css`
width: ${width};
height: fit-content;
`;

View File

@@ -0,0 +1,35 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { themes } from "../../../content/components/constants/styles";
import { Business, Family } from "../../../content/components/icons";
// @TODO connect data source to icon checks
// @TODO support other indicator types (attachments, etc)
export function CipherInfoIndicatorIcons({
isBusinessOrg,
isFamilyOrg,
theme,
}: {
isBusinessOrg?: boolean;
isFamilyOrg?: boolean;
theme: Theme;
}) {
const indicatorIcons = [
...(isBusinessOrg ? [Business({ color: themes[theme].text.muted, theme })] : []),
...(isFamilyOrg ? [Family({ color: themes[theme].text.muted, theme })] : []),
];
return indicatorIcons.length
? html` <span class=${cipherInfoIndicatorIconsStyles}> ${indicatorIcons} </span> `
: null; // @TODO null case should be handled by parent
}
const cipherInfoIndicatorIconsStyles = css`
> svg {
width: fit-content;
height: 12px;
}
`;

View File

@@ -0,0 +1,48 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { themes, typography } from "../../../content/components/constants/styles";
import { CipherInfoIndicatorIcons } from "./cipher-indicator-icons";
import { CipherData } from "./types";
// @TODO support other cipher types (card, identity, notes, etc)
export function CipherInfo({ cipher, theme }: { cipher: CipherData; theme: Theme }) {
const { name, login } = cipher;
return html`
<div>
<span class=${cipherInfoPrimaryTextStyles(theme)}>
${[name, CipherInfoIndicatorIcons({ theme })]}
</span>
${login?.username
? html`<span class=${cipherInfoSecondaryTextStyles(theme)}>${login.username}</span>`
: null}
</div>
`;
}
const cipherInfoPrimaryTextStyles = (theme: Theme) => css`
${typography.body2}
gap: 2px;
display: flex;
display: block;
overflow-x: hidden;
text-overflow: ellipsis;
color: ${themes[theme].text.main};
font-weight: 500;
`;
const cipherInfoSecondaryTextStyles = (theme: Theme) => css`
${typography.helperMedium}
display: block;
overflow-x: hidden;
text-overflow: ellipsis;
color: ${themes[theme].text.muted};
font-weight: 400;
`;

View File

@@ -0,0 +1,65 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
import { spacing, themes } from "../../../content/components/constants/styles";
import {
NotificationType,
NotificationTypes,
} from "../../../notification/abstractions/notification-bar";
import { CipherAction } from "./cipher-action";
import { CipherIcon } from "./cipher-icon";
import { CipherInfo } from "./cipher-info";
import { CipherData } from "./types";
const cipherIconWidth = "24px";
export function CipherItem({
cipher,
handleAction,
notificationType,
theme = ThemeTypes.Light,
}: {
cipher: CipherData;
handleAction?: (e: Event) => void;
notificationType?: NotificationType;
theme: Theme;
}) {
const { icon } = cipher;
const uri = (icon.imageEnabled && icon.image) || undefined;
let cipherActionButton = null;
if (notificationType === NotificationTypes.Change || notificationType === NotificationTypes.Add) {
cipherActionButton = html`<div>
${CipherAction({ handleAction, notificationType, theme })}
</div>`;
}
return html`
<div class=${cipherItemStyles}>
${CipherIcon({ color: themes[theme].text.muted, size: cipherIconWidth, theme, uri })}
${CipherInfo({ theme, cipher })}
</div>
${cipherActionButton}
`;
}
const cipherItemStyles = css`
gap: ${spacing["2"]};
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: start;
> img,
> span {
display: flex;
}
> div {
max-width: calc(100% - ${cipherIconWidth} - ${spacing["2"]});
}
`;

View File

@@ -0,0 +1,5 @@
export * from "./cipher-action";
export * from "./cipher-icon";
export * from "./cipher-indicator-icons";
export * from "./cipher-info";
export * from "./cipher-item";

View File

@@ -0,0 +1,44 @@
const CipherTypes = {
Login: 1,
SecureNote: 2,
Card: 3,
Identity: 4,
} as const;
type CipherType = (typeof CipherTypes)[keyof typeof CipherTypes];
const CipherRepromptTypes = {
None: 0,
Password: 1,
} as const;
type CipherRepromptType = (typeof CipherRepromptTypes)[keyof typeof CipherRepromptTypes];
export type WebsiteIconData = {
imageEnabled: boolean;
image: string;
fallbackImage: string;
icon: string;
};
export type CipherData = {
id: string;
name: string;
type: CipherType;
reprompt: CipherRepromptType;
favorite: boolean;
icon: WebsiteIconData;
accountCreationFieldType?: string;
login?: {
username: string;
passkey: {
rpName: string;
userName: string;
} | null;
};
card?: string;
identity?: {
fullName: string;
username?: string;
};
};

View File

@@ -0,0 +1,206 @@
import { Theme } from "@bitwarden/common/platform/enums";
const lightTheme = {
transparent: {
hover: `rgb(0 0 0 / 0.02)`,
},
shadow: `rgba(168 179 200)`,
primary: {
100: `rgba(219, 229, 246)`,
300: `rgba(121, 161, 233)`,
600: `rgba(23, 93, 220)`,
700: `rgba(26, 65, 172)`,
},
secondary: {
100: `rgba(230, 233, 239)`,
300: `rgba(168, 179, 200)`,
500: `rgba(90, 109, 145)`,
600: `rgba(83, 99, 131)`,
700: `rgba(63, 75, 99)`,
},
success: {
100: `rgba(219, 229, 246)`,
600: `rgba(121, 161, 233)`,
700: `rgba(26, 65, 172)`,
},
danger: {
100: `rgba(255, 236, 239)`,
600: `rgba(203, 38, 58)`,
700: `rgba(149, 27, 42)`,
},
warning: {
100: `rgba(255, 248, 228)`,
600: `rgba(255, 191, 0)`,
700: `rgba(172, 88, 0)`,
},
info: {
100: `rgba(219, 229, 246)`,
600: `rgba(121, 161, 233)`,
700: `rgba(26, 65, 172)`,
},
art: {
primary: `rgba(2, 15, 102)`,
accent: `rgba(44, 221, 223)`,
},
text: {
main: `rgba(27, 32, 41)`,
muted: `rgba(90, 109, 145)`,
contrast: `rgba(255, 255, 255)`,
alt2: `rgba(255, 255, 255)`,
code: `rgba(192, 17, 118)`,
},
background: {
DEFAULT: `rgba(255, 255, 255)`,
alt: `rgba(243, 246, 249)`,
alt2: `rgba(23, 92, 219)`,
alt3: `rgba(26, 65, 172)`,
alt4: `rgba(2, 15, 102)`,
},
brandLogo: `rgba(23, 93, 220)`,
};
const darkTheme = {
transparent: {
hover: `rgb(255 255 255 / 0.02)`,
},
shadow: `rgba(0, 0, 0)`,
primary: {
100: `rgba(26, 39, 78)`,
300: `rgba(26, 65, 172)`,
600: `rgba(101, 171, 255)`,
700: `rgba(170, 195, 239)`,
},
secondary: {
100: `rgba(48, 57, 70)`,
300: `rgba(82, 91, 106)`,
500: `rgba(121, 128, 142)`,
600: `rgba(143, 152, 166)`,
700: `rgba(158, 167, 181)`,
},
success: {
100: `rgba(11, 111, 21)`,
600: `rgba(107, 241, 120)`,
700: `rgba(191, 236, 195)`,
},
danger: {
100: `rgba(149, 27, 42)`,
600: `rgba(255, 78, 99)`,
700: `rgba(255, 236, 239)`,
},
warning: {
100: `rgba(172, 88, 0)`,
600: `rgba(255, 191, 0)`,
700: `rgba(255, 248, 228)`,
},
info: {
100: `rgba(26, 65, 172)`,
600: `rgba(121, 161, 233)`,
700: `rgba(219, 229, 246)`,
},
art: {
primary: `rgba(243, 246, 249)`,
accent: `rgba(44, 221, 233)`,
},
text: {
main: `rgba(243, 246, 249)`,
muted: `rgba(136, 152, 181)`,
contrast: `rgba(18 26 39)`,
alt2: `rgba(255, 255, 255)`,
code: `rgba(255, 143, 208)`,
},
background: {
DEFAULT: `rgba(32, 39, 51)`,
alt: `rgba(18, 26, 39)`,
alt2: `rgba(47, 52, 61)`,
alt3: `rgba(48, 57, 70)`,
alt4: `rgba(18, 26, 39)`,
},
brandLogo: `rgba(255, 255, 255)`,
};
export const themes = {
light: lightTheme,
dark: darkTheme,
// For compatibility
system: lightTheme,
nord: lightTheme,
solarizedDark: darkTheme,
};
export const spacing = {
4: `16px`,
3: `12px`,
2: `8px`,
"1.5": `6px`,
1: `4px`,
};
export const border = {
radius: {
large: `8px`,
full: `9999px`,
},
};
export const typography = {
body1: `
line-height: 24px;
font-family: "DM Sans", sans-serif;
font-size: 16px;
`,
body2: `
line-height: 20px;
font-family: "DM Sans", sans-serif;
font-size: 14px;
`,
helperMedium: `
line-height: 16px;
font-family: "DM Sans", sans-serif;
font-size: 12px;
`,
};
export const ruleNames = {
fill: "fill",
stroke: "stroke",
} as const;
type RuleName = (typeof ruleNames)[keyof typeof ruleNames];
/*
* `color` is an intentionally generic name here, since either fill or stroke may apply, due to
* inconsistent SVG construction. This consequently precludes dynamic multi-colored icons here.
*/
export const buildIconColorRule = (color: string, rule: RuleName = ruleNames.fill) => `
${rule}: ${color};
`;
export function scrollbarStyles(theme: Theme) {
return {
default: `
/* FireFox & Chrome support */
scrollbar-color: ${themes[theme].secondary["500"]} ${themes[theme].background.alt};
`,
safari: `
/* Safari Support */
::-webkit-scrollbar {
overflow: auto;
}
::-webkit-scrollbar-thumb {
border-width: 4px;
border-style: solid;
border-radius: 0.5rem;
border-color: transparent;
background-clip: content-box;
background-color: ${themes[theme].secondary["500"]};
}
::-webkit-scrollbar-track {
${themes[theme].background.alt};
}
::-webkit-scrollbar-thumb:hover {
${themes[theme].secondary["600"]};
}
`,
};
}

View File

@@ -0,0 +1,121 @@
import { css } from "@emotion/css";
import { html, TemplateResult } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { border, themes, typography, spacing } from "./constants/styles";
import { AngleDown } from "./icons";
export function DropdownMenu({
buttonText,
icon,
disabled = false,
selectAction,
theme,
}: {
selectAction?: (e: Event) => void;
buttonText: string;
icon?: TemplateResult;
disabled?: boolean;
theme: Theme;
}) {
// @TODO placeholder/will not work; make stateful
const showDropdown = false;
const handleButtonClick = (event: Event) => {
// if (!disabled) {
// // show dropdown
// showDropdown = !showDropdown;
// this.requestUpdate();
// }
};
const dropdownMenuItems: TemplateResult[] = [];
return html`
<div class=${dropdownContainerStyles}>
<button
type="button"
title=${buttonText}
class=${dropdownButtonStyles({ disabled, theme })}
@click=${handleButtonClick}
>
${icon ?? null}
<span class=${dropdownButtonTextStyles}>${buttonText}</span>
${AngleDown({ color: themes[theme].text.muted, theme })}
</button>
${showDropdown
? html` <div class=${dropdownMenuStyles({ theme })}>${dropdownMenuItems}</div> `
: null}
</div>
`;
}
const iconSize = "15px";
const dropdownContainerStyles = css`
display: flex;
> div,
> button {
width: 100%;
}
`;
const dropdownButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: Theme }) => css`
${typography.body2}
font-weight: 400;
gap: ${spacing["1.5"]};
user-select: none;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: space-between;
border-radius: ${border.radius.full};
padding: ${spacing["1"]} ${spacing["2"]};
max-height: fit-content;
overflow: hidden;
text-align: center;
text-overflow: ellipsis;
> svg {
max-width: ${iconSize};
height: fit-content;
}
${disabled
? `
border: 1px solid ${themes[theme].secondary["300"]};
background-color: ${themes[theme].secondary["300"]};
color: ${themes[theme].text.muted};
`
: `
border: 1px solid ${themes[theme].text.muted};
background-color: transparent;
cursor: pointer;
color: ${themes[theme].text.muted};
:hover {
border-color: ${themes[theme].secondary["700"]};
background-color: ${themes[theme].secondary["100"]};
}
`}
`;
const dropdownButtonTextStyles = css`
max-width: calc(100% - ${iconSize} - ${iconSize});
overflow-x: hidden;
text-overflow: ellipsis;
`;
const dropdownMenuStyles = ({ theme }: { theme: Theme }) => css`
color: ${themes[theme].text.main};
border: 1px solid ${themes[theme].secondary["500"]};
border-radius: 0.5rem;
background-clip: padding-box;
background-color: ${themes[theme].background.DEFAULT};
padding: 0.25rem 0.75rem;
position: absolute;
overflow-y: auto;
`;

View File

@@ -0,0 +1,27 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function AngleDown({
color,
disabled,
theme,
}: {
color?: string;
disabled?: boolean;
theme: Theme;
}) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 12" fill="none">
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M12.004 11.244a2.705 2.705 0 0 1-1.75-.644L.266 2.154a.76.76 0 0 1-.263-.51.75.75 0 0 1 1.233-.637l9.99 8.445a1.186 1.186 0 0 0 1.565 0l10-8.54a.751.751 0 0 1 .973 1.141l-10 8.538a2.703 2.703 0 0 1-1.76.653Z"
/>
</svg>
`;
}

View File

@@ -0,0 +1,19 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { Shield } from "./shield";
export function BrandIconContainer({ iconLink, theme }: { iconLink?: URL; theme: Theme }) {
const Icon = html`<div class=${brandIconContainerStyles}>${Shield({ theme })}</div>`;
return iconLink ? html`<a href="${iconLink}" target="_blank" rel="noreferrer">${Icon}</a>` : Icon;
}
const brandIconContainerStyles = css`
> svg {
width: 20px;
height: fit-content;
}
`;

View File

@@ -0,0 +1,46 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Business({
color,
disabled,
theme,
}: {
color?: string;
disabled?: boolean;
theme: Theme;
}) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
fill-rule="evenodd"
d="M6.015 16.482a3.007 3.007 0 1 0 0-6.015 3.007 3.007 0 0 0 0 6.015Zm0 1.504a4.51 4.51 0 1 0 0-9.022 4.51 4.51 0 0 0 0 9.022Z"
clip-rule="evenodd"
/>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
fill-rule="evenodd"
d="M10.439 22.497c-.548-2.805-2.51-4.511-4.427-4.511-1.917 0-3.879 1.706-4.426 4.51h8.853Zm-8.934.525v-.002.002ZM.659 24h10.466c.645 0 .984-.424.888-1.18-.457-3.591-2.97-6.338-6-6.338-3.032 0-5.544 2.747-6.001 6.339-.066.511.143 1.18.647 1.18Z"
clip-rule="evenodd"
/>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
fill-rule="evenodd"
d="M7.46 1.387v7.577a.694.694 0 1 1-1.387 0V.97c0-.536.434-.971.97-.971H23.03c.536 0 .971.435.971.971v20.496a.971.971 0 0 1-.971.971h-11a.694.694 0 0 1 0-1.387h10.584V1.387H7.46Z"
clip-rule="evenodd"
/>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.stroke))}
stroke-linecap="round"
d="M14.033 3.953h2.007M9.522 3.953h2.007M18.544 3.953h2.007M14.033 8.464h2.007M9.522 8.464h2.007M18.544 8.464h2.007M14.033 12.975h2.007M9.522 12.975h2.007M18.544 12.975h2.007M14.033 17.485h2.007M18.544 17.485h2.007"
/>
</svg>
`;
}

View File

@@ -0,0 +1,27 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Close({
color,
disabled,
theme,
}: {
color?: string;
disabled?: boolean;
theme: Theme;
}) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="m19.809 19.21-8.487-8.226a.592.592 0 0 1 0-.852l8.382-8.13a.594.594 0 0 0 .175-.423.593.593 0 0 0-.182-.42.632.632 0 0 0-.872-.007l-8.383 8.126a.634.634 0 0 1-.88 0l-8.41-8.135a.642.642 0 0 0-.887-.008.602.602 0 0 0-.182.431.588.588 0 0 0 .19.428l8.41 8.139a.592.592 0 0 1 0 .852l-8.5 8.225a.605.605 0 0 0-.183.427c0 .16.066.313.183.426a.635.635 0 0 0 .88-.001l8.5-8.226a.635.635 0 0 1 .88 0l8.488 8.226a.64.64 0 0 0 .887.008.605.605 0 0 0 .182-.43.591.591 0 0 0-.19-.429h-.001Z"
/>
</svg>
`;
}

View File

@@ -0,0 +1,27 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function ExclamationTriangle({
color,
disabled,
theme,
}: {
color?: string;
disabled?: boolean;
theme: Theme;
}) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 22" fill="none">
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M21.627 21.877H2.373a2.28 2.28 0 0 1-1.195-.326 2.394 2.394 0 0 1-.869-.908 2.433 2.433 0 0 1 .015-2.404L9.951 1.33c.211-.368.511-.672.87-.883a2.322 2.322 0 0 1 2.357 0c.36.211.66.515.871.882l9.627 16.911a2.442 2.442 0 0 1 .015 2.404 2.39 2.39 0 0 1-.87.908c-.362.217-.775.33-1.194.326ZM12 1.677a.844.844 0 0 0-.436.115.883.883 0 0 0-.322.326l-9.625 16.91a.846.846 0 0 0 0 .844.876.876 0 0 0 .322.334.84.84 0 0 0 .44.117h19.248a.837.837 0 0 0 .44-.117.882.882 0 0 0 .322-.334.846.846 0 0 0 0-.843L12.758 2.118a.89.89 0 0 0-.322-.326.837.837 0 0 0-.436-.114Zm0 13.309a.735.735 0 0 1-.53-.228.794.794 0 0 1-.22-.55V7.105a.79.79 0 0 1 .22-.55.735.735 0 0 1 1.06 0c.14.146.22.344.22.55v7.105a.79.79 0 0 1-.22.55.74.74 0 0 1-.53.227Zm0 3.84c.491 0 .89-.412.89-.92 0-.51-.399-.922-.89-.922s-.89.412-.89.921c0 .51.399.922.89.922Z"
/>
</svg>
`;
}

View File

@@ -0,0 +1,27 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Family({
color,
disabled,
theme,
}: {
color?: string;
disabled?: boolean;
theme: Theme;
}) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 18" fill="none">
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M20.535 8.219a4.5 4.5 0 1 0-5.07 0c-.34.187-.657.414-.945.675a3 3 0 0 0-5.04 0 5.745 5.745 0 0 0-.945-.675 4.5 4.5 0 1 0-5.07 0A7.5 7.5 0 0 0 0 13.829C0 14.34.135 15 .645 15H8.07a6.6 6.6 0 0 0-.57 2.055c0 .405.105.945.48.945h7.83c.48 0 .735-.345.66-.945A7.503 7.503 0 0 0 15.93 15h7.17c.645 0 .975-.42.885-1.17a7.5 7.5 0 0 0-3.45-5.61ZM15 4.499a3 3 0 1 1 6 0 3 3 0 0 1-6 0Zm-3 4.5a1.5 1.5 0 0 1 1.5 1.11c.016.13.016.26 0 .39a1.5 1.5 0 0 1-.99 1.395 1.29 1.29 0 0 1-1.02 0 1.5 1.5 0 0 1-.99-1.395 1.778 1.778 0 0 1 0-.39A1.5 1.5 0 0 1 12 9Zm-9-4.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0Zm-1.425 9C2.13 10.71 4.08 9 6.075 9A4.035 4.035 0 0 1 9 10.499a3 3 0 0 0 .945 2.145A4.499 4.499 0 0 0 9 13.5H1.575Zm13.29 3h-5.73A5.07 5.07 0 0 1 9.75 15 2.865 2.865 0 0 1 12 13.5h.15a2.82 2.82 0 0 1 2.16 1.5c.27.465.457.972.555 1.5Zm.135-3a4.5 4.5 0 0 0-.945-.825A3 3 0 0 0 15 10.5 4.08 4.08 0 0 1 18 9a5.01 5.01 0 0 1 4.41 4.5H15Z"
/>
</svg>
`;
}

View File

@@ -0,0 +1,27 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Folder({
color,
disabled,
theme,
}: {
color?: string;
disabled?: boolean;
theme: Theme;
}) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 22" fill="none">
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M21.78 3.823h-7.97a.823.823 0 0 1-.584-.265.872.872 0 0 1-.23-.61v-.396A2.321 2.321 0 0 0 12.348.93a2.214 2.214 0 0 0-1.577-.681H2.22A2.214 2.214 0 0 0 .645.93 2.321 2.321 0 0 0 0 2.552v16.88c-.003.608.23 1.191.647 1.624.417.432.984.677 1.576.681l19.554.016c.288 0 .574-.058.84-.171.267-.113.51-.278.714-.487a2.31 2.31 0 0 0 .497-.756c.115-.284.174-.588.172-.894V6.129c0-.606-.233-1.189-.648-1.62a2.223 2.223 0 0 0-1.572-.686ZM2.223 1.678h8.552c.22.006.43.101.582.265a.865.865 0 0 1 .23.61v.396c0 .606.234 1.189.65 1.62.416.432.983.677 1.576.684h7.97c.22.006.43.1.582.264a.86.86 0 0 1 .23.607v1.707a.56.56 0 0 1-.16.389.535.535 0 0 1-.38.159H1.951a.531.531 0 0 1-.381-.16.558.558 0 0 1-.16-.389V2.551a.867.867 0 0 1 .23-.609.82.82 0 0 1 .582-.265ZM22.34 20.08a.779.779 0 0 1-.558.238l-19.558-.014a.823.823 0 0 1-.582-.264.864.864 0 0 1-.23-.608v-9.065a.566.566 0 0 1 .16-.39.547.547 0 0 1 .38-.16h20.104c.143-.001.28.057.382.16a.561.561 0 0 1 .16.39v9.083a.903.903 0 0 1-.259.63h.001Z"
/>
</svg>
`;
}

View File

@@ -0,0 +1,28 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Globe({
color,
disabled,
theme,
}: {
color?: string;
disabled?: boolean;
theme: Theme;
}) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
fill-rule="evenodd"
d="M19.958 18.85A10.476 10.476 0 0 1 12 22.5c-1.674 0-3.256-.392-4.66-1.088l.084-.195c.03-.07.06-.135.084-.194l.086-.2c.11-.262.23-.548.372-.832.18-.36.374-.67.58-.875.035-.035.137-.096.372-.146.225-.049.512-.076.855-.095.187-.01.392-.018.607-.026.542-.02 1.15-.043 1.72-.118.426-.057.769-.169 1.025-.349.27-.19.422-.44.477-.713a1.531 1.531 0 0 0-.04-.697 3.99 3.99 0 0 0-.162-.458l-.014-.037a5.335 5.335 0 0 0-.198-.425c-.17-.34-.321-.64-.39-1.395-.04-.442.122-.939.34-1.463l.121-.283c.069-.158.137-.317.191-.456.086-.22.174-.482.174-.728 0-.242-.077-.5-.18-.731a3.271 3.271 0 0 0-.434-.704c-.335-.416-.863-.86-1.485-.86-.597 0-1.367.217-2.02.473-.334.13-.652.278-.922.425-.262.143-.507.3-.67.46-1.262 1.236-2.01 1.593-2.458 1.619-.376.021-.649-.194-.999-.676-.085-.117-.168-.24-.257-.373l-.01-.014c-.084-.126-.174-.26-.268-.39-.192-.269-.42-.55-.708-.771a1.946 1.946 0 0 0-1.085-.416 2.206 2.206 0 0 0-.393.011 10.477 10.477 0 0 1 2.7-5.06c.074.44.198.804.369 1.1.311.54.762.822 1.258.921.55.11 1.163-.08 1.711-.277.17-.062.337-.126.505-.19.42-.16.84-.321 1.287-.439.35-.092.788-.073 1.3.017.36.062.72.151 1.088.242.16.04.323.08.488.119.515.12 1.063.228 1.542.19.51-.041.996-.254 1.27-.802.171-.343.168-.67.03-.966-.117-.253-.324-.457-.475-.604-.352-.344-.558-.55-.558-.881 0-.161.05-.258.122-.338.086-.095.219-.18.411-.272.08-.039.162-.075.25-.114l.048-.02c.102-.046.214-.097.32-.153.064-.033.133-.072.202-.119 2.628.96 4.765 2.941 5.932 5.462a12.186 12.186 0 0 0-.043-.005c-.514-.06-1.002-.07-1.442.08-.463.158-.82.472-1.111.951a9.428 9.428 0 0 1-.628.862 82.02 82.02 0 0 0-.13.165c-.281.362-.585.765-.788 1.182-.204.42-.332.908-.191 1.418.14.512.524.95 1.136 1.332.134.085.304.145.427.188l.027.01c.146.052.264.095.363.15a.422.422 0 0 1 .17.147c.021.038.05.112.029.264a7.15 7.15 0 0 0-.07 1.33c.022.375.096.759.295 1.04.16.224.162.542.147 1.01v.014c-.007.198-.015.447.029.666.03.149.089.312.203.45Zm1.843.075A11.945 11.945 0 0 0 24 12c0-6.627-5.373-12-12-12S0 5.373 0 12c0 4.495 2.471 8.413 6.13 10.468v.12h.218A11.946 11.946 0 0 0 12 24c4.011 0 7.563-1.968 9.742-4.991a.73.73 0 0 0 .065-.08l-.006-.004Zm-15.253 2.05.059-.135.074-.17.078-.185c.11-.262.246-.584.403-.896.193-.387.439-.803.749-1.111.222-.221.532-.327.818-.388a6.64 6.64 0 0 1 .994-.114c.22-.012.445-.02.672-.029.525-.02 1.062-.039 1.587-.108.346-.046.532-.127.626-.193.081-.057.103-.108.112-.155a.653.653 0 0 0-.027-.285c-.032-.12-.078-.234-.128-.358l-.014-.035a3.953 3.953 0 0 0-.141-.298c-.183-.362-.422-.837-.508-1.776-.063-.685.187-1.366.406-1.892l.137-.32c.062-.143.117-.27.166-.398.085-.218.113-.34.113-.402 0-.066-.025-.192-.102-.364a2.38 2.38 0 0 0-.314-.507c-.283-.352-.58-.524-.783-.524-.427 0-1.07.167-1.692.41-.303.12-.587.251-.82.378-.24.131-.397.242-.47.314-1.271 1.244-2.23 1.827-3.036 1.873-.878.05-1.406-.532-1.78-1.045-.095-.132-.188-.27-.275-.4l-.006-.01c-.087-.13-.17-.252-.255-.371-.176-.245-.344-.442-.527-.583a1.047 1.047 0 0 0-.592-.23 1.54 1.54 0 0 0-.495.058 10.494 10.494 0 0 0 4.971 10.25Zm14.072-2.978c0-.07.002-.15.005-.247l.002-.059c.014-.392.036-1.01-.314-1.502-.05-.07-.111-.244-.13-.572a6.267 6.267 0 0 1 .063-1.158c.042-.314-.005-.595-.142-.833a1.308 1.308 0 0 0-.51-.481 3.197 3.197 0 0 0-.497-.213l-.004-.001a1.74 1.74 0 0 1-.278-.112c-.501-.314-.686-.592-.746-.809-.06-.217-.02-.468.134-.786.156-.321.403-.656.689-1.022l.119-.152.001-.002c.242-.308.5-.636.696-.958.206-.339.41-.491.632-.567.244-.084.566-.094 1.048-.038.192.023.372.05.537.08.373 1.077.575 2.232.575 3.435 0 2.23-.695 4.297-1.88 5.997ZM14.33 1.759C13.58 1.59 12.8 1.5 12 1.5c-2.567 0-4.918.92-6.742 2.45.033.693.163 1.14.325 1.42.177.306.401.438.655.489.272.054.657-.035 1.228-.242.14-.05.29-.107.447-.167.434-.166.925-.354 1.422-.485.541-.143 1.133-.096 1.685 0 .392.068.802.17 1.184.264.153.038.301.074.442.107.52.122.942.195 1.265.17.294-.024.443-.12.537-.307.053-.107.037-.147.02-.185-.038-.08-.12-.175-.289-.34a12.25 12.25 0 0 0-.059-.056c-.295-.285-.77-.743-.77-1.468 0-.393.138-.704.356-.944.186-.204.418-.343.624-.447Z"
/>
</svg>
`;
}

View File

@@ -0,0 +1,12 @@
export { AngleDown } from "./angle-down";
export { BrandIconContainer } from "./brand-icon-container";
export { Business } from "./business";
export { Close } from "./close";
export { ExclamationTriangle } from "./exclamation-triangle";
export { Family } from "./family";
export { Folder } from "./folder";
export { Globe } from "./globe";
export { PartyHorn } from "./party-horn";
export { PencilSquare } from "./pencil-square";
export { Shield } from "./shield";
export { User } from "./user";

View File

@@ -0,0 +1,174 @@
import { html } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
// This icon has static multi-colors for each theme
export function PartyHorn({ theme }: { theme: Theme }) {
if (theme === ThemeTypes.Dark) {
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52" fill="none">
<path
fill="#030C1B"
d="M33.044 37.587 5.716 48.972a2.275 2.275 0 0 1-2.975-2.975L14.127 18.67a4.31 4.31 0 0 1 5.217-2.47l.77.23a22.625 22.625 0 0 1 15.17 15.17l.23.77a4.31 4.31 0 0 1-2.47 5.217Z"
/>
<path
fill="#FFBF00"
fill-rule="evenodd"
d="M15.947 44.71a47.282 47.282 0 0 1-4.768-4.175 47.292 47.292 0 0 1-4.175-4.768l.48-1.154a45.963 45.963 0 0 0 4.457 5.16 45.967 45.967 0 0 0 5.16 4.456l-1.154.481ZM11.4 46.604a56.295 56.295 0 0 1-6.292-6.291l-.463 1.112a57.493 57.493 0 0 0 5.642 5.643l1.113-.464Zm10.65-4.437c-2.38-1.42-4.765-3.271-7-5.505-2.233-2.234-4.084-4.62-5.504-6.999l.52-1.25c1.414 2.52 3.347 5.087 5.747 7.487 2.4 2.4 4.966 4.332 7.486 5.746l-1.25.52Zm-9.634-19.393c.894 3.259 3.09 6.93 6.342 10.181 3.251 3.252 6.922 5.448 10.18 6.341l1.747-.727a12.588 12.588 0 0 1-1.756-.396c-2.975-.885-6.364-2.934-9.41-5.98-3.045-3.045-5.094-6.434-5.98-9.41a12.584 12.584 0 0 1-.395-1.755l-.728 1.746Z"
clip-rule="evenodd"
/>
<path
fill="#0E3377"
stroke="#91B4F2"
stroke-linecap="round"
stroke-width="1.077"
d="M27.985 23.73c2.174 2.174 3.79 4.462 4.653 6.387.432.965.663 1.811.699 2.491.035.68-.125 1.13-.4 1.406-.276.275-.726.436-1.406.4-.68-.035-1.526-.267-2.49-.699-1.926-.863-4.214-2.478-6.389-4.653-2.175-2.175-3.79-4.463-4.653-6.388-.432-.965-.664-1.811-.7-2.49-.035-.68.126-1.131.401-1.407.275-.275.726-.436 1.406-.4.68.036 1.526.267 2.49.7 1.926.862 4.214 2.478 6.389 4.652Z"
/>
<path
stroke="#91B4F2"
stroke-linecap="round"
stroke-width="1.077"
d="M33.044 37.587 5.716 48.972a2.275 2.275 0 0 1-2.975-2.975L14.127 18.67a4.31 4.31 0 0 1 5.217-2.47l.77.23a22.625 22.625 0 0 1 15.17 15.17l.23.77a4.31 4.31 0 0 1-2.47 5.217Z"
/>
<path
fill="#91B4F2"
d="M25.46 2.47a.407.407 0 0 1 .793 0l.02.086a3.232 3.232 0 0 0 2.415 2.414l.086.02a.407.407 0 0 1 0 .793l-.086.02a3.232 3.232 0 0 0-2.414 2.415l-.02.086a.407.407 0 0 1-.794 0l-.02-.086a3.232 3.232 0 0 0-2.414-2.414l-.087-.02a.407.407 0 0 1 0-.794l.087-.02a3.232 3.232 0 0 0 2.414-2.414l.02-.086ZM45.93 10.55a.407.407 0 0 1 .794 0l.02.086a3.232 3.232 0 0 0 2.414 2.415l.086.02a.407.407 0 0 1 0 .793l-.086.02a3.232 3.232 0 0 0-2.414 2.414l-.02.087a.407.407 0 0 1-.794 0l-.02-.087a3.232 3.232 0 0 0-2.414-2.414l-.086-.02a.407.407 0 0 1 0-.793l.086-.02a3.232 3.232 0 0 0 2.414-2.415l.02-.086ZM38.928 43.41a.407.407 0 0 1 .793 0l.02.086a3.232 3.232 0 0 0 2.414 2.414l.086.02a.407.407 0 0 1 0 .794l-.086.02a3.232 3.232 0 0 0-2.414 2.414l-.02.086a.407.407 0 0 1-.793 0l-.02-.086a3.232 3.232 0 0 0-2.415-2.414l-.086-.02a.407.407 0 0 1 0-.793l.086-.02a3.232 3.232 0 0 0 2.414-2.415l.02-.086Z"
/>
<path
stroke="#91B4F2"
stroke-linecap="round"
stroke-width="1.077"
d="M37.708 27.827a4.31 4.31 0 0 1 6.095 0M36.63 24.873a6.95 6.95 0 0 1 9.495-2.544M17.238 13.467a4.31 4.31 0 0 0-4.31-4.31M18.583 10.392a4.31 4.31 0 0 0-2.533-5.544"
/>
<circle
cx="32.86"
cy="11.313"
r="1.616"
fill="#0E3377"
stroke="#91B4F2"
stroke-linecap="round"
stroke-width="1.077"
/>
<circle
cx="36.631"
cy="17.777"
r="1.077"
fill="#030C1B"
stroke="#91B4F2"
stroke-linecap="round"
stroke-width="1.077"
/>
<circle
cx="30.705"
cy="44.172"
r="1.077"
fill="#030C1B"
stroke="#91B4F2"
stroke-linecap="round"
stroke-width="1.077"
/>
<circle
cx="44.711"
cy="34.476"
r="2.155"
fill="#FFBF00"
stroke="#91B4F2"
stroke-linecap="round"
stroke-width="1.077"
/>
<circle
cx="41.479"
cy="5.926"
r="3.232"
fill="#202733"
stroke="#91B4F2"
stroke-linecap="round"
stroke-width="1.077"
/>
</svg>
`;
}
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52" fill="none">
<path
fill="#DFE9FB"
d="M33.044 37.587 5.716 48.972a2.275 2.275 0 0 1-2.975-2.975L14.127 18.67a4.31 4.31 0 0 1 5.217-2.47l.77.23a22.625 22.625 0 0 1 15.17 15.17l.23.77a4.31 4.31 0 0 1-2.47 5.217Z"
/>
<path
fill="#FFBF00"
fill-rule="evenodd"
d="M15.947 44.71a47.27 47.27 0 0 1-4.768-4.175 47.284 47.284 0 0 1-4.175-4.768l.48-1.154a45.953 45.953 0 0 0 4.457 5.16 45.958 45.958 0 0 0 5.16 4.456l-1.154.481ZM11.4 46.604a56.295 56.295 0 0 1-6.292-6.291l-.463 1.112a57.493 57.493 0 0 0 5.642 5.643l1.113-.464Zm10.65-4.437c-2.38-1.42-4.765-3.271-7-5.505-2.233-2.234-4.084-4.62-5.504-6.999l.52-1.25c1.414 2.52 3.347 5.087 5.747 7.487 2.4 2.4 4.966 4.332 7.486 5.746l-1.25.52Zm-9.634-19.393c.894 3.259 3.09 6.93 6.342 10.181 3.251 3.252 6.922 5.448 10.18 6.341l1.747-.727a12.586 12.586 0 0 1-1.756-.396c-2.975-.885-6.364-2.934-9.41-5.98-3.045-3.045-5.094-6.434-5.98-9.41a12.585 12.585 0 0 1-.395-1.755l-.728 1.746Z"
clip-rule="evenodd"
/>
<path
fill="#99BAF4"
stroke="#0E3781"
stroke-linecap="round"
stroke-width="1.077"
d="M27.985 23.73c2.175 2.174 3.79 4.462 4.653 6.387.432.965.663 1.811.699 2.491.036.68-.125 1.13-.4 1.406-.276.275-.726.436-1.406.4-.68-.035-1.526-.267-2.49-.699-1.926-.863-4.214-2.478-6.389-4.653-2.175-2.175-3.79-4.463-4.653-6.388-.432-.965-.664-1.811-.7-2.49-.035-.68.126-1.131.401-1.407.275-.275.726-.436 1.406-.4.68.036 1.526.267 2.49.7 1.926.862 4.214 2.478 6.389 4.652Z"
/>
<path
stroke="#0E3781"
stroke-linecap="round"
stroke-width="1.077"
d="M33.044 37.587 5.716 48.972a2.275 2.275 0 0 1-2.975-2.975L14.127 18.67a4.31 4.31 0 0 1 5.217-2.47l.77.23a22.625 22.625 0 0 1 15.17 15.17l.23.77a4.31 4.31 0 0 1-2.47 5.217Z"
/>
<path
fill="#0E3781"
d="M25.46 2.47a.407.407 0 0 1 .793 0l.02.086a3.232 3.232 0 0 0 2.415 2.414l.086.02a.407.407 0 0 1 0 .793l-.086.02a3.232 3.232 0 0 0-2.414 2.415l-.02.086a.407.407 0 0 1-.794 0l-.02-.086a3.232 3.232 0 0 0-2.414-2.414l-.086-.02a.407.407 0 0 1 0-.794l.086-.02a3.232 3.232 0 0 0 2.414-2.414l.02-.086ZM45.93 10.55a.407.407 0 0 1 .794 0l.02.086a3.232 3.232 0 0 0 2.414 2.415l.086.02a.407.407 0 0 1 0 .793l-.086.02a3.232 3.232 0 0 0-2.414 2.414l-.02.087a.407.407 0 0 1-.794 0l-.02-.087a3.232 3.232 0 0 0-2.414-2.414l-.086-.02a.407.407 0 0 1 0-.793l.086-.02a3.232 3.232 0 0 0 2.414-2.415l.02-.086ZM38.928 43.41a.407.407 0 0 1 .793 0l.02.086a3.232 3.232 0 0 0 2.414 2.414l.086.02a.407.407 0 0 1 0 .794l-.086.02a3.232 3.232 0 0 0-2.414 2.414l-.02.086a.407.407 0 0 1-.793 0l-.02-.086a3.232 3.232 0 0 0-2.415-2.414l-.086-.02a.407.407 0 0 1 0-.793l.086-.02a3.232 3.232 0 0 0 2.414-2.415l.02-.086Z"
/>
<path
stroke="#0E3781"
stroke-linecap="round"
stroke-width="1.077"
d="M37.708 27.827a4.31 4.31 0 0 1 6.095 0M36.63 24.873a6.95 6.95 0 0 1 9.495-2.544M17.238 13.467a4.31 4.31 0 0 0-4.31-4.31M18.583 10.392a4.31 4.31 0 0 0-2.533-5.544"
/>
<circle
cx="32.86"
cy="11.313"
r="1.616"
fill="#99BAF4"
stroke="#0E3781"
stroke-linecap="round"
stroke-width="1.077"
/>
<circle
cx="36.631"
cy="17.777"
r="1.077"
fill="#DFE9FB"
stroke="#0E3781"
stroke-linecap="round"
stroke-width="1.077"
/>
<circle
cx="30.705"
cy="44.172"
r="1.077"
fill="#DFE9FB"
stroke="#0E3781"
stroke-linecap="round"
stroke-width="1.077"
/>
<circle
cx="44.711"
cy="34.476"
r="2.155"
fill="#FFBF00"
stroke="#0E3781"
stroke-linecap="round"
stroke-width="1.077"
/>
<circle
cx="41.479"
cy="5.926"
r="3.232"
fill="#fff"
stroke="#0E3781"
stroke-linecap="round"
stroke-width="1.077"
/>
</svg>
`;
}

View File

@@ -0,0 +1,27 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function PencilSquare({
color,
disabled,
theme,
}: {
color?: string;
disabled?: boolean;
theme: Theme;
}) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M17.799 24H2.709a2.422 2.422 0 0 1-1.729-.735 2.533 2.533 0 0 1-.715-1.768V6.03c0-.663.257-1.299.715-1.769a2.416 2.416 0 0 1 1.728-.734h7.996c.216 0 .424.088.577.244a.846.846 0 0 1 0 1.18.808.808 0 0 1-.577.245H2.708a.809.809 0 0 0-.576.244.844.844 0 0 0-.238.59v15.467c0 .221.085.433.238.59.153.156.36.244.576.244h15.09a.809.809 0 0 0 .577-.244.843.843 0 0 0 .238-.59v-6.754a.832.832 0 0 1 .494-.801.796.796 0 0 1 .64 0 .82.82 0 0 1 .442.472.836.836 0 0 1 .052.33v6.753a2.53 2.53 0 0 1-.715 1.768c-.458.47-1.08.734-1.727.735ZM9.24 15.417a.812.812 0 0 1-.677-.373.852.852 0 0 1-.074-.783l1.32-3.239c.121-.297.297-.567.52-.795L19.615.714A2.394 2.394 0 0 1 21.325 0c.638.002 1.25.263 1.703.726.452.463.706 1.09.707 1.744a2.502 2.502 0 0 1-.7 1.746l-9.229 9.455c-.274.28-.609.489-.977.61l-3.34 1.09a.801.801 0 0 1-.248.047Zm12.084-13.76a.771.771 0 0 0-.558.235l-9.282 9.514a.828.828 0 0 0-.17.26l-.642 1.572 1.663-.543a.778.778 0 0 0 .317-.198l9.231-9.455a.812.812 0 0 0 .172-.88.805.805 0 0 0-.29-.363.78.78 0 0 0-.44-.136v-.006h-.001Z"
/>
</svg>
`;
}

View File

@@ -0,0 +1,19 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Shield({ color, theme }: { color?: string; theme: Theme }) {
const shapeColor = color || themes[theme].brandLogo;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 24" fill="none">
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M19.703.3A.97.97 0 0 0 19 0H1a.958.958 0 0 0-.702.3.962.962 0 0 0-.3.702v12c.004.913.18 1.818.522 2.665a9.95 9.95 0 0 0 1.297 2.345c.552.72 1.169 1.387 1.844 1.993a21.721 21.721 0 0 0 1.975 1.61c.6.426 1.23.83 1.89 1.21.66.381 1.126.639 1.398.773.275.135.497.241.662.312.129.062.27.093.414.09a.87.87 0 0 0 .406-.095c.168-.073.387-.177.665-.312.277-.135.75-.393 1.398-.772.648-.38 1.285-.785 1.89-1.21.69-.499 1.35-1.036 1.978-1.61a14.458 14.458 0 0 0 1.844-1.994c.535-.72.972-1.508 1.297-2.344a7.185 7.185 0 0 0 .522-2.666v-12A.944.944 0 0 0 19.703.3Zm-2.32 12.811c0 4.35-7.382 8.087-7.382 8.087V2.57h7.381v10.54Z"
/>
</svg>
`;
}

View File

@@ -0,0 +1,27 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function User({
color,
disabled,
theme,
}: {
color?: string;
disabled?: boolean;
theme: Theme;
}) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M23.16 20.895a11.343 11.343 0 0 0-7.756-8.425 6.624 6.624 0 0 0 3.374-5.74 6.73 6.73 0 1 0-13.46 0 6.624 6.624 0 0 0 3.343 5.722 11.334 11.334 0 0 0-7.82 8.443A2.57 2.57 0 0 0 3.362 24h17.274a2.573 2.573 0 0 0 2.523-3.106v.001ZM6.933 6.73a5.12 5.12 0 0 1 3.12-4.766 5.115 5.115 0 0 1 4.845 8.962 5.114 5.114 0 0 1-2.848.866A5.097 5.097 0 0 1 6.933 6.73v.001ZM21.38 22.053a.94.94 0 0 1-.748.35H3.363a.938.938 0 0 1-.74-.35.986.986 0 0 1-.204-.833A9.812 9.812 0 0 1 12 13.516a9.807 9.807 0 0 1 9.581 7.704.98.98 0 0 1-.202.833Z"
/>
</svg>
`;
}

View File

@@ -0,0 +1,69 @@
import createEmotion from "@emotion/css/create-instance";
import { html } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
import { NotificationType } from "../../../notification/abstractions/notification-bar";
import { CipherItem } from "../cipher";
import { CipherData } from "../cipher/types";
import { scrollbarStyles, spacing, themes, typography } from "../constants/styles";
import { ItemRow } from "../rows/item-row";
export const componentClassPrefix = "notification-body";
const { css } = createEmotion({
key: componentClassPrefix,
});
export function NotificationBody({
ciphers,
notificationType,
theme = ThemeTypes.Light,
}: {
ciphers: CipherData[];
customClasses?: string[];
notificationType?: NotificationType;
theme: Theme;
}) {
// @TODO get client vendor from context
const isSafari = false;
return html`
<div class=${notificationBodyStyles({ isSafari, theme })}>
${ciphers.map((cipher) =>
ItemRow({
theme,
children: CipherItem({
cipher,
notificationType,
theme,
handleAction: () => {
// @TODO connect update or edit actions to handler
},
}),
}),
)}
</div>
`;
}
const notificationBodyStyles = ({ isSafari, theme }: { isSafari: boolean; theme: Theme }) => css`
${typography.body1}
gap: ${spacing["1.5"]};
display: flex;
flex-flow: column;
background-color: ${themes[theme].background.alt};
max-height: 123px;
overflow-x: hidden;
overflow-y: auto;
white-space: nowrap;
color: ${themes[theme].text.main};
font-weight: 400;
:last-child {
border-radius: 0 0 ${spacing["4"]} ${spacing["4"]};
}
${isSafari ? scrollbarStyles(theme).safari : scrollbarStyles(theme).default}
`;

View File

@@ -0,0 +1,99 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
import {
NotificationBarIframeInitData,
NotificationTypes,
NotificationType,
} from "../../../notification/abstractions/notification-bar";
import { createAutofillOverlayCipherDataMock } from "../../../spec/autofill-mocks";
import { CipherData } from "../cipher/types";
import { themes, spacing } from "../constants/styles";
import { NotificationBody, componentClassPrefix as notificationBodyClassPrefix } from "./body";
import { NotificationFooter } from "./footer";
import {
NotificationHeader,
componentClassPrefix as notificationHeaderClassPrefix,
} from "./header";
export function NotificationContainer({
handleCloseNotification,
i18n,
theme = ThemeTypes.Light,
type,
}: NotificationBarIframeInitData & { handleCloseNotification: (e: Event) => void } & {
i18n: { [key: string]: string };
type: NotificationType; // @TODO typing override for generic `NotificationBarIframeInitData.type`
}) {
const headerMessage = getHeaderMessage(i18n, type);
const showBody = true;
// @TODO remove mock ciphers for development
const ciphers = [
createAutofillOverlayCipherDataMock(1),
{ ...createAutofillOverlayCipherDataMock(2), icon: { imageEnabled: false } },
{
...createAutofillOverlayCipherDataMock(3),
icon: { imageEnabled: true, image: "https://localhost:8443/icons/webtests.dev/icon.png" },
},
] as CipherData[];
return html`
<div class=${notificationContainerStyles(theme)}>
${NotificationHeader({
handleCloseNotification,
standalone: showBody,
message: headerMessage,
theme,
})}
${showBody
? NotificationBody({
ciphers,
notificationType: type,
theme,
})
: null}
${NotificationFooter({
theme,
notificationType: type,
})}
</div>
`;
}
const notificationContainerStyles = (theme: Theme) => css`
position: absolute;
right: 20px;
border: 1px solid ${themes[theme].secondary["300"]};
border-radius: ${spacing["4"]};
box-shadow: -2px 4px 6px 0px #0000001a;
background-color: ${themes[theme].background.alt};
width: 400px;
[class*="${notificationHeaderClassPrefix}-"] {
border-radius: ${spacing["4"]} ${spacing["4"]} 0 0;
}
[class*="${notificationBodyClassPrefix}-"] {
margin: ${spacing["3"]} 0 ${spacing["1.5"]} ${spacing["3"]};
padding-right: ${spacing["3"]};
}
`;
function getHeaderMessage(i18n: { [key: string]: string }, type?: NotificationType) {
switch (type) {
case NotificationTypes.Add:
return i18n.saveAsNewLoginAction;
case NotificationTypes.Change:
return i18n.updateLoginPrompt;
case NotificationTypes.Unlock:
return "";
case NotificationTypes.FilelessImport:
return "";
default:
return undefined;
}
}

View File

@@ -0,0 +1,42 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import {
NotificationType,
NotificationTypes,
} from "../../../notification/abstractions/notification-bar";
import { spacing, themes } from "../constants/styles";
import { ActionRow } from "../rows/action-row";
import { ButtonRow } from "../rows/button-row";
export function NotificationFooter({
notificationType,
theme,
}: {
notificationType?: NotificationType;
theme: Theme;
}) {
const isChangeNotification = notificationType === NotificationTypes.Change;
// @TODO localize
const saveNewItemText = "Save as new login";
return html`
<div class=${notificationFooterStyles({ theme })}>
${isChangeNotification
? ActionRow({ itemText: saveNewItemText, handleAction: () => {}, theme })
: ButtonRow({ theme })}
</div>
`;
}
const notificationFooterStyles = ({ theme }: { theme: Theme }) => css`
display: flex;
background-color: ${themes[theme].background.alt};
padding: 0 ${spacing[3]} ${spacing[3]} ${spacing[3]};
:last-child {
border-radius: 0 0 ${spacing["4"]} ${spacing["4"]};
}
`;

View File

@@ -0,0 +1,25 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { themes } from "../constants/styles";
export function NotificationHeaderMessage({ message, theme }: { message: string; theme: Theme }) {
return html`
<span title=${message} class=${notificationHeaderMessageStyles(theme)}>${message}</span>
`;
}
const notificationHeaderMessageStyles = (theme: Theme) => css`
flex-grow: 1;
overflow-x: hidden;
text-align: left;
text-overflow: ellipsis;
line-height: 28px;
white-space: nowrap;
color: ${themes[theme].text.main};
font-family: "DM Sans", sans-serif;
font-size: 18px;
font-weight: 600;
`;

View File

@@ -0,0 +1,61 @@
import createEmotion from "@emotion/css/create-instance";
import { html } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
import { CloseButton } from "../buttons/close-button";
import { themes } from "../constants/styles";
import { BrandIconContainer } from "../icons/brand-icon-container";
import { NotificationHeaderMessage } from "./header-message";
export const componentClassPrefix = "notification-header";
const { css } = createEmotion({
key: componentClassPrefix,
});
export function NotificationHeader({
message,
standalone,
theme = ThemeTypes.Light,
handleCloseNotification,
}: {
message?: string;
standalone: boolean;
theme: Theme;
handleCloseNotification: (e: Event) => void;
}) {
const showIcon = true;
const isDismissable = true;
return html`
<div class=${notificationHeaderStyles({ standalone, theme })}>
${showIcon ? BrandIconContainer({ theme }) : null}
${message ? NotificationHeaderMessage({ message, theme }) : null}
${isDismissable ? CloseButton({ handleCloseNotification, theme }) : null}
</div>
`;
}
const notificationHeaderStyles = ({
standalone,
theme,
}: {
standalone: boolean;
theme: Theme;
}) => css`
gap: 8px;
display: flex;
align-items: center;
justify-content: flex-start;
background-color: ${themes[theme].background.alt};
padding: 12px 16px 8px 16px;
white-space: nowrap;
${standalone
? css`
border-bottom: 0.5px solid ${themes[theme].secondary["300"]};
`
: css``}
`;

View File

@@ -0,0 +1,53 @@
import { css } from "@emotion/css";
import { html } from "lit";
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,
}: {
itemText: string;
handleAction?: (e: Event) => void;
theme: Theme;
}) {
return html`
<button type="button" @click=${handleAction} class=${actionRowStyles(theme)} title=${itemText}>
<span>${itemText}</span>
</button>
`;
}
const actionRowStyles = (theme: Theme) => css`
${typography.body2}
user-select: none;
border-width: 0 0 0.5px 0;
border-style: solid;
border-radius: ${spacing["2"]};
border-color: ${themes[theme].secondary["300"]};
background-color: ${themes[theme].background.DEFAULT};
cursor: pointer;
padding: ${spacing["2"]} ${spacing["3"]};
width: 100%;
min-height: 40px;
text-align: left;
color: ${themes[theme].primary["600"]};
font-weight: 700;
> span {
display: block;
width: calc(100% - 5px);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
:hover {
background-color: ${themes[theme].primary["100"]};
color: ${themes[theme].primary["600"]};
}
`;

View File

@@ -0,0 +1,73 @@
import { css } from "@emotion/css";
import { html, TemplateResult } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { ActionButton } from "../../../content/components/buttons/action-button";
import { spacing, themes } from "../../../content/components/constants/styles";
import { Folder, User } from "../../../content/components/icons";
import { DropdownMenu } from "../dropdown-menu";
export function ButtonRow({ theme }: { theme: Theme }) {
return html`
<div class=${buttonRowStyles}>
${[
ActionButton({
buttonAction: () => {},
buttonText: "Action Button",
theme,
}),
DropdownContainer({
children: [
DropdownMenu({
buttonText: "You",
icon: User({ color: themes[theme].text.muted, theme }),
theme,
}),
DropdownMenu({
buttonText: "Folder",
icon: Folder({ color: themes[theme].text.muted, theme }),
disabled: true,
theme,
}),
],
}),
]}
</div>
`;
}
function DropdownContainer({ children }: { children: TemplateResult[] }) {
return html` <div class=${dropdownContainerStyles}>${children}</div> `;
}
const buttonRowStyles = css`
gap: 16px;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
max-height: 52px;
white-space: nowrap;
> button {
max-width: min-content;
flex: 1 1 50%;
}
> div {
flex: 1 1 min-content;
}
`;
const dropdownContainerStyles = css`
gap: 8px;
display: flex;
align-items: center;
justify-content: flex-end;
overflow: hidden;
> div {
min-width: calc(50% - ${spacing["1.5"]});
}
`;

View File

@@ -0,0 +1,56 @@
import { css } from "@emotion/css";
import { html, TemplateResult } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
import { spacing, themes, typography } from "../../../content/components/constants/styles";
export function ItemRow({
theme = ThemeTypes.Light,
children,
}: {
theme: Theme;
children: TemplateResult | TemplateResult[];
}) {
return html` <div class=${itemRowStyles({ theme })}>${children}</div> `;
}
export const itemRowStyles = ({ theme }: { theme: Theme }) => css`
${typography.body1}
gap: ${spacing["2"]};
display: flex;
align-items: center;
justify-content: space-between;
border-width: 0 0 0.5px 0;
border-style: solid;
border-radius: ${spacing["2"]};
border-color: ${themes[theme].secondary["300"]};
background-color: ${themes[theme].background.DEFAULT};
padding: ${spacing["2"]} ${spacing["3"]};
min-height: min-content;
max-height: 52px;
overflow-x: hidden;
white-space: nowrap;
color: ${themes[theme].text.main};
font-weight: 400;
> div {
:first-child {
flex: 3 3 75%;
min-width: 25%;
}
:not(:first-child) {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-end;
max-width: 25%;
> button {
max-width: min-content;
}
}
}
`;

View File

@@ -1,7 +1,16 @@
import { Theme } from "@bitwarden/common/platform/enums";
const NotificationTypes = {
Add: "add",
Change: "change",
Unlock: "unlock",
FilelessImport: "fileless-import",
} as const;
type NotificationType = (typeof NotificationTypes)[keyof typeof NotificationTypes];
type NotificationBarIframeInitData = {
type?: string;
type?: string; // @TODO use `NotificationType`
isVaultLocked?: boolean;
theme?: Theme;
removeIndividualVault?: boolean;
@@ -24,6 +33,8 @@ type NotificationBarWindowMessageHandlers = {
};
export {
NotificationTypes,
NotificationType,
NotificationBarIframeInitData,
NotificationBarWindowMessage,
NotificationBarWindowMessageHandlers,