mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-25871] updated phishing warning UI (#16748)
* refactor phishing-warning.component * add hideBackground input to anon-layout component * add icon tile component to CL * add storybook story; fix binding bug in template * export icon-tile from CL * update design of phishing warning page * revert icon button to use string type; add comment to icon scss * update callout to allow no icon/title on all variants * update phishing warning styles * fix defects * crowdin messages cannot be changed, they must be replaced * add global css override * add phishing help link * update icon used in tile * tweak styles
This commit is contained in:
@@ -100,6 +100,7 @@ $icomoon-font-path: "~@bitwarden/angular/src/scss/bwicons/fonts/" !default;
|
||||
}
|
||||
|
||||
// For new icons - add their glyph name and value to the map below
|
||||
// Also add to `libs/components/src/shared/icon.ts`
|
||||
$icons: (
|
||||
"angle-down": "\e900",
|
||||
"angle-left": "\e901",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
[maxWidth]="maxWidth"
|
||||
[hideCardWrapper]="hideCardWrapper"
|
||||
[hideIcon]="hideIcon"
|
||||
[hideBackgroundIllustration]="hideBackgroundIllustration"
|
||||
>
|
||||
<router-outlet></router-outlet>
|
||||
<router-outlet slot="secondary" name="secondary"></router-outlet>
|
||||
|
||||
@@ -44,6 +44,10 @@ export interface AnonLayoutWrapperData {
|
||||
* Hide the card that wraps the default content. Defaults to false.
|
||||
*/
|
||||
hideCardWrapper?: boolean;
|
||||
/**
|
||||
* Hides the background illustration. Defaults to false.
|
||||
*/
|
||||
hideBackgroundIllustration?: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -60,6 +64,7 @@ export class AnonLayoutWrapperComponent implements OnInit {
|
||||
protected maxWidth?: AnonLayoutMaxWidth | null;
|
||||
protected hideCardWrapper?: boolean | null;
|
||||
protected hideIcon?: boolean | null;
|
||||
protected hideBackgroundIllustration?: boolean | null;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
@@ -117,6 +122,7 @@ export class AnonLayoutWrapperComponent implements OnInit {
|
||||
this.showReadonlyHostname = Boolean(firstChildRouteData["showReadonlyHostname"]);
|
||||
this.maxWidth = firstChildRouteData["maxWidth"];
|
||||
this.hideCardWrapper = Boolean(firstChildRouteData["hideCardWrapper"]);
|
||||
this.hideBackgroundIllustration = Boolean(firstChildRouteData["hideBackgroundIllustration"]);
|
||||
}
|
||||
|
||||
private listenForServiceDataChanges() {
|
||||
@@ -157,6 +163,10 @@ export class AnonLayoutWrapperComponent implements OnInit {
|
||||
this.hideCardWrapper = data.hideCardWrapper;
|
||||
}
|
||||
|
||||
if (data.hideBackgroundIllustration !== undefined) {
|
||||
this.hideBackgroundIllustration = data.hideBackgroundIllustration;
|
||||
}
|
||||
|
||||
if (data.hideIcon !== undefined) {
|
||||
this.hideIcon = data.hideIcon;
|
||||
}
|
||||
@@ -188,5 +198,6 @@ export class AnonLayoutWrapperComponent implements OnInit {
|
||||
this.maxWidth = null;
|
||||
this.hideCardWrapper = null;
|
||||
this.hideIcon = null;
|
||||
this.hideBackgroundIllustration = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,16 +68,18 @@
|
||||
</ng-container>
|
||||
</footer>
|
||||
|
||||
<div
|
||||
class="tw-hidden md:tw-block [&_svg]:tw-absolute tw-z-[1] tw-opacity-[.11] [&_svg]:tw-bottom-0 [&_svg]:tw-start-0 [&_svg]:tw-w-[35%] [&_svg]:tw-max-w-[450px]"
|
||||
>
|
||||
<bit-icon [icon]="leftIllustration"></bit-icon>
|
||||
</div>
|
||||
<div
|
||||
class="tw-hidden md:tw-block [&_svg]:tw-absolute tw-z-[1] tw-opacity-[.11] [&_svg]:tw-bottom-0 [&_svg]:tw-end-0 [&_svg]:tw-w-[35%] [&_svg]:tw-max-w-[450px]"
|
||||
>
|
||||
<bit-icon [icon]="rightIllustration"></bit-icon>
|
||||
</div>
|
||||
@if (!hideBackgroundIllustration()) {
|
||||
<div
|
||||
class="tw-hidden md:tw-block [&_svg]:tw-absolute tw-z-[1] tw-opacity-[.11] [&_svg]:tw-bottom-0 [&_svg]:tw-start-0 [&_svg]:tw-w-[35%] [&_svg]:tw-max-w-[450px]"
|
||||
>
|
||||
<bit-icon [icon]="leftIllustration"></bit-icon>
|
||||
</div>
|
||||
<div
|
||||
class="tw-hidden md:tw-block [&_svg]:tw-absolute tw-z-[1] tw-opacity-[.11] [&_svg]:tw-bottom-0 [&_svg]:tw-end-0 [&_svg]:tw-w-[35%] [&_svg]:tw-max-w-[450px]"
|
||||
>
|
||||
<bit-icon [icon]="rightIllustration"></bit-icon>
|
||||
</div>
|
||||
}
|
||||
</main>
|
||||
|
||||
<ng-template #defaultContent>
|
||||
|
||||
@@ -51,6 +51,7 @@ export class AnonLayoutComponent implements OnInit, OnChanges {
|
||||
readonly hideFooter = input<boolean>(false);
|
||||
readonly hideIcon = input<boolean>(false);
|
||||
readonly hideCardWrapper = input<boolean>(false);
|
||||
readonly hideBackgroundIllustration = input<boolean>(false);
|
||||
|
||||
/**
|
||||
* Max width of the anon layout title, subtitle, and content areas.
|
||||
|
||||
@@ -79,6 +79,7 @@ export default {
|
||||
[hideIcon]="hideIcon"
|
||||
[hideLogo]="hideLogo"
|
||||
[hideFooter]="hideFooter"
|
||||
[hideBackgroundIllustration]="hideBackgroundIllustration"
|
||||
>
|
||||
<ng-container [ngSwitch]="contentLength">
|
||||
<div *ngSwitchCase="'thin'" class="tw-text-center"> <div class="tw-font-bold">Thin Content</div></div>
|
||||
@@ -125,6 +126,7 @@ export default {
|
||||
hideIcon: { control: "boolean" },
|
||||
hideLogo: { control: "boolean" },
|
||||
hideFooter: { control: "boolean" },
|
||||
hideBackgroundIllustration: { control: "boolean" },
|
||||
|
||||
contentLength: {
|
||||
control: "radio",
|
||||
@@ -145,6 +147,7 @@ export default {
|
||||
hideIcon: false,
|
||||
hideLogo: false,
|
||||
hideFooter: false,
|
||||
hideBackgroundIllustration: false,
|
||||
contentLength: "normal",
|
||||
showSecondary: false,
|
||||
},
|
||||
@@ -221,6 +224,10 @@ export const NoFooter: Story = {
|
||||
args: { hideFooter: true },
|
||||
};
|
||||
|
||||
export const NoBackgroundIllustration: Story = {
|
||||
args: { hideBackgroundIllustration: true },
|
||||
};
|
||||
|
||||
export const ReadonlyHostname: Story = {
|
||||
args: { showReadonlyHostname: true },
|
||||
};
|
||||
@@ -234,5 +241,6 @@ export const MinimalState: Story = {
|
||||
hideIcon: true,
|
||||
hideLogo: true,
|
||||
hideFooter: true,
|
||||
hideBackgroundIllustration: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -36,11 +36,17 @@ let nextId = 0;
|
||||
export class CalloutComponent {
|
||||
readonly type = input<CalloutTypes>("info");
|
||||
readonly icon = input<string>();
|
||||
readonly title = input<string>();
|
||||
readonly title = input<string | null>();
|
||||
readonly useAlertRole = input(false);
|
||||
readonly iconComputed = computed(() => this.icon() ?? defaultIcon[this.type()]);
|
||||
readonly iconComputed = computed(() =>
|
||||
this.icon() === undefined ? defaultIcon[this.type()] : this.icon(),
|
||||
);
|
||||
readonly titleComputed = computed(() => {
|
||||
const title = this.title();
|
||||
if (title === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const type = this.type();
|
||||
if (title == null && defaultI18n[type] != null) {
|
||||
return this.i18nService.t(defaultI18n[type]);
|
||||
|
||||
7
libs/components/src/icon-tile/icon-tile.component.html
Normal file
7
libs/components/src/icon-tile/icon-tile.component.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<div
|
||||
[ngClass]="containerClasses()"
|
||||
[attr.aria-label]="ariaLabel()"
|
||||
[attr.role]="ariaLabel() ? 'img' : null"
|
||||
>
|
||||
<i [ngClass]="iconClasses()" aria-hidden="true"></i>
|
||||
</div>
|
||||
111
libs/components/src/icon-tile/icon-tile.component.ts
Normal file
111
libs/components/src/icon-tile/icon-tile.component.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { NgClass } from "@angular/common";
|
||||
import { Component, computed, input } from "@angular/core";
|
||||
|
||||
import { BitwardenIcon } from "../shared/icon";
|
||||
|
||||
export type IconTileVariant = "primary" | "success" | "warning" | "danger" | "muted";
|
||||
|
||||
export type IconTileSize = "small" | "default" | "large";
|
||||
|
||||
export type IconTileShape = "square" | "circle";
|
||||
|
||||
const variantStyles: Record<IconTileVariant, string[]> = {
|
||||
primary: ["tw-bg-primary-100", "tw-text-primary-700"],
|
||||
success: ["tw-bg-success-100", "tw-text-success-700"],
|
||||
warning: ["tw-bg-warning-100", "tw-text-warning-700"],
|
||||
danger: ["tw-bg-danger-100", "tw-text-danger-700"],
|
||||
muted: ["tw-bg-secondary-100", "tw-text-secondary-700"],
|
||||
};
|
||||
|
||||
const sizeStyles: Record<IconTileSize, { container: string[]; icon: string[] }> = {
|
||||
small: {
|
||||
container: ["tw-w-6", "tw-h-6"],
|
||||
icon: ["tw-text-sm"],
|
||||
},
|
||||
default: {
|
||||
container: ["tw-w-8", "tw-h-8"],
|
||||
icon: ["tw-text-base"],
|
||||
},
|
||||
large: {
|
||||
container: ["tw-w-10", "tw-h-10"],
|
||||
icon: ["tw-text-lg"],
|
||||
},
|
||||
};
|
||||
|
||||
const shapeStyles: Record<IconTileShape, Record<IconTileSize, string[]>> = {
|
||||
square: {
|
||||
small: ["tw-rounded"],
|
||||
default: ["tw-rounded-md"],
|
||||
large: ["tw-rounded-lg"],
|
||||
},
|
||||
circle: {
|
||||
small: ["tw-rounded-full"],
|
||||
default: ["tw-rounded-full"],
|
||||
large: ["tw-rounded-full"],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Icon tiles are static containers that display an icon with a colored background.
|
||||
* They are similar to icon buttons but are not interactive and are used for visual
|
||||
* indicators, status representations, or decorative elements.
|
||||
*
|
||||
* Use icon tiles to:
|
||||
* - Display status or category indicators
|
||||
* - Represent different types of content
|
||||
* - Create visual hierarchy in lists or cards
|
||||
* - Show app or service icons in a consistent format
|
||||
*/
|
||||
@Component({
|
||||
selector: "bit-icon-tile",
|
||||
templateUrl: "icon-tile.component.html",
|
||||
imports: [NgClass],
|
||||
})
|
||||
export class IconTileComponent {
|
||||
/**
|
||||
* The BWI icon name
|
||||
*/
|
||||
readonly icon = input.required<BitwardenIcon>();
|
||||
|
||||
/**
|
||||
* The visual theme of the icon tile
|
||||
*/
|
||||
readonly variant = input<IconTileVariant>("primary");
|
||||
|
||||
/**
|
||||
* The size of the icon tile
|
||||
*/
|
||||
readonly size = input<IconTileSize>("default");
|
||||
|
||||
/**
|
||||
* The shape of the icon tile
|
||||
*/
|
||||
readonly shape = input<IconTileShape>("square");
|
||||
|
||||
/**
|
||||
* Optional aria-label for accessibility when the icon has semantic meaning
|
||||
*/
|
||||
readonly ariaLabel = input<string>();
|
||||
|
||||
protected readonly containerClasses = computed(() => {
|
||||
const variant = this.variant();
|
||||
const size = this.size();
|
||||
const shape = this.shape();
|
||||
|
||||
return [
|
||||
"tw-inline-flex",
|
||||
"tw-items-center",
|
||||
"tw-justify-center",
|
||||
"tw-flex-shrink-0",
|
||||
...variantStyles[variant],
|
||||
...sizeStyles[size].container,
|
||||
...shapeStyles[shape][size],
|
||||
];
|
||||
});
|
||||
|
||||
protected readonly iconClasses = computed(() => {
|
||||
const size = this.size();
|
||||
|
||||
return ["bwi", this.icon(), ...sizeStyles[size].icon];
|
||||
});
|
||||
}
|
||||
114
libs/components/src/icon-tile/icon-tile.stories.ts
Normal file
114
libs/components/src/icon-tile/icon-tile.stories.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { Meta, StoryObj } from "@storybook/angular";
|
||||
|
||||
import { BITWARDEN_ICONS } from "../shared/icon";
|
||||
|
||||
import { IconTileComponent } from "./icon-tile.component";
|
||||
|
||||
export default {
|
||||
title: "Component Library/Icon Tile",
|
||||
component: IconTileComponent,
|
||||
args: {
|
||||
icon: "bwi-star",
|
||||
variant: "primary",
|
||||
size: "default",
|
||||
shape: "square",
|
||||
},
|
||||
argTypes: {
|
||||
variant: {
|
||||
options: ["primary", "success", "warning", "danger", "muted"],
|
||||
control: { type: "select" },
|
||||
},
|
||||
size: {
|
||||
options: ["small", "default", "large"],
|
||||
control: { type: "select" },
|
||||
},
|
||||
shape: {
|
||||
options: ["square", "circle"],
|
||||
control: { type: "select" },
|
||||
},
|
||||
icon: {
|
||||
options: BITWARDEN_ICONS,
|
||||
control: { type: "select" },
|
||||
},
|
||||
ariaLabel: {
|
||||
control: { type: "text" },
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
design: {
|
||||
type: "figma",
|
||||
url: "https://atlassian.design/components/icon/icon-tile/examples",
|
||||
},
|
||||
},
|
||||
} as Meta<IconTileComponent>;
|
||||
|
||||
type Story = StoryObj<IconTileComponent>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const AllVariants: Story = {
|
||||
render: () => ({
|
||||
template: `
|
||||
<div class="tw-flex tw-gap-4 tw-items-center tw-flex-wrap">
|
||||
<div class="tw-flex tw-flex-col tw-items-center tw-gap-2">
|
||||
<bit-icon-tile icon="bwi-collection" variant="primary"></bit-icon-tile>
|
||||
<span class="tw-text-sm tw-text-muted">Primary</span>
|
||||
</div>
|
||||
<div class="tw-flex tw-flex-col tw-items-center tw-gap-2">
|
||||
<bit-icon-tile icon="bwi-check-circle" variant="success"></bit-icon-tile>
|
||||
<span class="tw-text-sm tw-text-muted">Success</span>
|
||||
</div>
|
||||
<div class="tw-flex tw-flex-col tw-items-center tw-gap-2">
|
||||
<bit-icon-tile icon="bwi-exclamation-triangle" variant="warning"></bit-icon-tile>
|
||||
<span class="tw-text-sm tw-text-muted">Warning</span>
|
||||
</div>
|
||||
<div class="tw-flex tw-flex-col tw-items-center tw-gap-2">
|
||||
<bit-icon-tile icon="bwi-error" variant="danger"></bit-icon-tile>
|
||||
<span class="tw-text-sm tw-text-muted">Danger</span>
|
||||
</div>
|
||||
<div class="tw-flex tw-flex-col tw-items-center tw-gap-2">
|
||||
<bit-icon-tile icon="bwi-question-circle" variant="muted"></bit-icon-tile>
|
||||
<span class="tw-text-sm tw-text-muted">Muted</span>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const AllSizes: Story = {
|
||||
render: () => ({
|
||||
template: `
|
||||
<div class="tw-flex tw-gap-4 tw-items-center">
|
||||
<div class="tw-flex tw-flex-col tw-items-center tw-gap-2">
|
||||
<bit-icon-tile icon="bwi-star" variant="primary" size="small"></bit-icon-tile>
|
||||
<span class="tw-text-sm tw-text-muted">Small</span>
|
||||
</div>
|
||||
<div class="tw-flex tw-flex-col tw-items-center tw-gap-2">
|
||||
<bit-icon-tile icon="bwi-star" variant="primary" size="default"></bit-icon-tile>
|
||||
<span class="tw-text-sm tw-text-muted">Default</span>
|
||||
</div>
|
||||
<div class="tw-flex tw-flex-col tw-items-center tw-gap-2">
|
||||
<bit-icon-tile icon="bwi-star" variant="primary" size="large"></bit-icon-tile>
|
||||
<span class="tw-text-sm tw-text-muted">Large</span>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export const AllShapes: Story = {
|
||||
render: () => ({
|
||||
template: `
|
||||
<div class="tw-flex tw-gap-4 tw-items-center">
|
||||
<div class="tw-flex tw-flex-col tw-items-center tw-gap-2">
|
||||
<bit-icon-tile icon="bwi-user" variant="primary" shape="square"></bit-icon-tile>
|
||||
<span class="tw-text-sm tw-text-muted">Square</span>
|
||||
</div>
|
||||
<div class="tw-flex tw-flex-col tw-items-center tw-gap-2">
|
||||
<bit-icon-tile icon="bwi-user" variant="primary" shape="circle"></bit-icon-tile>
|
||||
<span class="tw-text-sm tw-text-muted">Circle</span>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
1
libs/components/src/icon-tile/index.ts
Normal file
1
libs/components/src/icon-tile/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./icon-tile.component";
|
||||
@@ -21,6 +21,7 @@ export * from "./drawer";
|
||||
export * from "./form-field";
|
||||
export * from "./icon-button";
|
||||
export * from "./icon";
|
||||
export * from "./icon-tile";
|
||||
export * from "./input";
|
||||
export * from "./item";
|
||||
export * from "./layout";
|
||||
|
||||
110
libs/components/src/shared/icon.ts
Normal file
110
libs/components/src/shared/icon.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Array of available Bitwarden Web Icons (bwi) font names.
|
||||
* These correspond to the actual icon names defined in the bwi-font.
|
||||
* This array serves as the single source of truth for all available icons.
|
||||
*/
|
||||
export const BITWARDEN_ICONS = [
|
||||
"bwi-angle-down",
|
||||
"bwi-angle-left",
|
||||
"bwi-angle-right",
|
||||
"bwi-angle-up",
|
||||
"bwi-archive",
|
||||
"bwi-bell",
|
||||
"bwi-billing",
|
||||
"bwi-bitcoin",
|
||||
"bwi-browser",
|
||||
"bwi-browser-alt",
|
||||
"bwi-brush",
|
||||
"bwi-bug",
|
||||
"bwi-business",
|
||||
"bwi-camera",
|
||||
"bwi-check",
|
||||
"bwi-check-circle",
|
||||
"bwi-cli",
|
||||
"bwi-clock",
|
||||
"bwi-clone",
|
||||
"bwi-close",
|
||||
"bwi-cog",
|
||||
"bwi-cog-f",
|
||||
"bwi-collection",
|
||||
"bwi-collection-shared",
|
||||
"bwi-credit-card",
|
||||
"bwi-dashboard",
|
||||
"bwi-desktop",
|
||||
"bwi-dollar",
|
||||
"bwi-down-solid",
|
||||
"bwi-download",
|
||||
"bwi-drag-and-drop",
|
||||
"bwi-ellipsis-h",
|
||||
"bwi-ellipsis-v",
|
||||
"bwi-envelope",
|
||||
"bwi-error",
|
||||
"bwi-exclamation-triangle",
|
||||
"bwi-external-link",
|
||||
"bwi-eye",
|
||||
"bwi-eye-slash",
|
||||
"bwi-family",
|
||||
"bwi-file",
|
||||
"bwi-file-text",
|
||||
"bwi-files",
|
||||
"bwi-filter",
|
||||
"bwi-folder",
|
||||
"bwi-generate",
|
||||
"bwi-globe",
|
||||
"bwi-hashtag",
|
||||
"bwi-id-card",
|
||||
"bwi-import",
|
||||
"bwi-info-circle",
|
||||
"bwi-key",
|
||||
"bwi-list",
|
||||
"bwi-list-alt",
|
||||
"bwi-lock",
|
||||
"bwi-lock-encrypted",
|
||||
"bwi-lock-f",
|
||||
"bwi-minus-circle",
|
||||
"bwi-mobile",
|
||||
"bwi-msp",
|
||||
"bwi-numbered-list",
|
||||
"bwi-paperclip",
|
||||
"bwi-passkey",
|
||||
"bwi-paypal",
|
||||
"bwi-pencil",
|
||||
"bwi-pencil-square",
|
||||
"bwi-plus",
|
||||
"bwi-plus-circle",
|
||||
"bwi-popout",
|
||||
"bwi-provider",
|
||||
"bwi-puzzle",
|
||||
"bwi-question-circle",
|
||||
"bwi-refresh",
|
||||
"bwi-search",
|
||||
"bwi-send",
|
||||
"bwi-share",
|
||||
"bwi-shield",
|
||||
"bwi-sign-in",
|
||||
"bwi-sign-out",
|
||||
"bwi-sliders",
|
||||
"bwi-spinner",
|
||||
"bwi-star",
|
||||
"bwi-star-f",
|
||||
"bwi-sticky-note",
|
||||
"bwi-tag",
|
||||
"bwi-trash",
|
||||
"bwi-undo",
|
||||
"bwi-universal-access",
|
||||
"bwi-unlock",
|
||||
"bwi-up-down-btn",
|
||||
"bwi-up-solid",
|
||||
"bwi-user",
|
||||
"bwi-user-monitor",
|
||||
"bwi-users",
|
||||
"bwi-vault",
|
||||
"bwi-wireless",
|
||||
"bwi-wrench",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Type-safe icon names derived from the BITWARDEN_ICONS array.
|
||||
* This ensures type safety while allowing runtime iteration and validation.
|
||||
*/
|
||||
export type BitwardenIcon = (typeof BITWARDEN_ICONS)[number];
|
||||
Reference in New Issue
Block a user