1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

[CL-750][CL-751][CL-752] Fix nav item truncation, clickable area, and shield logo (#15522)

This commit is contained in:
Vicki League
2025-07-10 17:02:42 -04:00
committed by GitHub
parent bf50160a47
commit 2f1ab48c37
6 changed files with 46 additions and 20 deletions

View File

@@ -11,7 +11,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { IconModule, Icon } from "../icon"; import { IconModule, Icon } from "../icon";
import { BitwardenLogo } from "../icon/icons"; import { BitwardenLogo } from "../icon/icons";
import { BitwardenShield } from "../icon/logos"; import { AnonLayoutBitwardenShield } from "../icon/logos";
import { SharedModule } from "../shared"; import { SharedModule } from "../shared";
import { TypographyModule } from "../typography"; import { TypographyModule } from "../typography";
@@ -84,7 +84,7 @@ export class AnonLayoutComponent implements OnInit, OnChanges {
// If there is no icon input, then use the default icon // If there is no icon input, then use the default icon
if (this.icon == null) { if (this.icon == null) {
this.icon = BitwardenShield; this.icon = AnonLayoutBitwardenShield;
} }
} }

View File

@@ -1,4 +1,4 @@
export { default as AdminConsoleLogo } from "./admin-console"; export { default as AdminConsoleLogo } from "./admin-console";
export { default as BitwardenShield } from "./shield"; export * from "./shield";
export { default as PasswordManagerLogo } from "./password-manager"; export { default as PasswordManagerLogo } from "./password-manager";
export { default as SecretsManagerLogo } from "./secrets-manager"; export { default as SecretsManagerLogo } from "./secrets-manager";

View File

@@ -1,7 +1,16 @@
import { svgIcon } from "../../icon"; import { svgIcon } from "../../icon";
/**
* Shield logo with extra space in the viewbox.
*/
const AnonLayoutBitwardenShield = svgIcon`
<svg viewBox="0 0 120 132" xmlns="http://www.w3.org/2000/svg">
<path class="tw-fill-marketing-logo" d="M82.2944 69.1899V37.2898H60V93.9624C63.948 91.869 67.4812 89.5927 70.5998 87.1338C78.3962 81.0196 82.2944 75.0383 82.2944 69.1899ZM91.8491 30.9097V69.1899C91.8491 72.0477 91.2934 74.8805 90.182 77.6883C89.0706 80.4962 87.6938 82.9884 86.0516 85.1649C84.4094 87.3415 82.452 89.4598 80.1794 91.5201C77.9068 93.5803 75.8084 95.2916 73.8842 96.654C71.96 98.0164 69.9528 99.304 67.8627 100.517C65.7726 101.73 64.288 102.552 63.4088 102.984C62.5297 103.416 61.8247 103.748 61.2939 103.981C60.8958 104.18 60.4645 104.28 60 104.28C59.5355 104.28 59.1042 104.18 58.7061 103.981C58.1753 103.748 57.4703 103.416 56.5911 102.984C55.712 102.552 54.2273 101.73 52.1372 100.517C50.0471 99.304 48.04 98.0164 46.1158 96.654C44.1916 95.2916 42.0932 93.5803 39.8206 91.5201C37.548 89.4598 35.5906 87.3415 33.9484 85.1649C32.3062 82.9884 30.9294 80.4962 29.818 77.6883C28.7066 74.8805 28.1509 72.0477 28.1509 69.1899V30.9097C28.1509 30.0458 28.4661 29.2981 29.0964 28.6668C29.7267 28.0354 30.4732 27.7197 31.3358 27.7197H88.6642C89.5268 27.7197 90.2732 28.0354 90.9036 28.6668C91.5339 29.2981 91.8491 30.0458 91.8491 30.9097Z" />
</svg>
`;
const BitwardenShield = svgIcon` const BitwardenShield = svgIcon`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 28 33"><path d="M26.696.403A1.274 1.274 0 0 0 25.764 0H1.83C1.467 0 1.16.137.898.403a1.294 1.294 0 0 0-.398.944v16.164c0 1.203.235 2.405.697 3.587.462 1.188 1.038 2.24 1.728 3.155.682.922 1.5 1.815 2.453 2.68a28.077 28.077 0 0 0 2.63 2.167 32.181 32.181 0 0 0 2.518 1.628c.875.511 1.493.857 1.863 1.045.37.18.661.324.882.417.163.087.348.13.54.13.192 0 .377-.043.54-.13.221-.1.52-.237.882-.417.37-.18.989-.534 1.863-1.045a34.4 34.4 0 0 0 2.517-1.628c.804-.576 1.679-1.296 2.631-2.168a20.206 20.206 0 0 0 2.454-2.68 13.599 13.599 0 0 0 1.72-3.154c.463-1.189.697-2.384.697-3.587V1.347a1.406 1.406 0 0 0-.42-.944ZM23.61 17.662c0 5.849-9.813 10.89-9.813 10.89V3.458h9.813v14.205Z" class="tw-fill-marketing-logo"/></svg> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 28 33"><path d="M26.696.403A1.274 1.274 0 0 0 25.764 0H1.83C1.467 0 1.16.137.898.403a1.294 1.294 0 0 0-.398.944v16.164c0 1.203.235 2.405.697 3.587.462 1.188 1.038 2.24 1.728 3.155.682.922 1.5 1.815 2.453 2.68a28.077 28.077 0 0 0 2.63 2.167 32.181 32.181 0 0 0 2.518 1.628c.875.511 1.493.857 1.863 1.045.37.18.661.324.882.417.163.087.348.13.54.13.192 0 .377-.043.54-.13.221-.1.52-.237.882-.417.37-.18.989-.534 1.863-1.045a34.4 34.4 0 0 0 2.517-1.628c.804-.576 1.679-1.296 2.631-2.168a20.206 20.206 0 0 0 2.454-2.68 13.599 13.599 0 0 0 1.72-3.154c.463-1.189.697-2.384.697-3.587V1.347a1.406 1.406 0 0 0-.42-.944ZM23.61 17.662c0 5.849-9.813 10.89-9.813 10.89V3.458h9.813v14.205Z" class="tw-fill-marketing-logo"/></svg>
`; `;
export default BitwardenShield; export { AnonLayoutBitwardenShield, BitwardenShield };

View File

@@ -17,16 +17,16 @@
<ng-template #anchorAndButtonContent> <ng-template #anchorAndButtonContent>
<div <div
[title]="text" [title]="text"
class="tw-truncate tw-gap-2 tw-items-center tw-font-bold" class="tw-gap-2 tw-items-center tw-font-bold tw-h-full tw-content-center"
[ngClass]="{ 'tw-text-center': !open, 'tw-flex': open }" [ngClass]="{ 'tw-text-center': !open, 'tw-flex': open }"
> >
<i <i
class="!tw-m-0 tw-w-4 bwi bwi-fw tw-text-alt2 {{ icon }}" class="!tw-m-0 tw-w-4 tw-shrink-0 bwi bwi-fw tw-text-alt2 {{ icon }}"
[attr.aria-hidden]="open" [attr.aria-hidden]="open"
[attr.aria-label]="text" [attr.aria-label]="text"
></i> ></i>
@if (open) { @if (open) {
<span>{{ text }}</span> <span class="tw-truncate">{{ text }}</span>
} }
</div> </div>
</ng-template> </ng-template>
@@ -36,7 +36,7 @@
<!-- The `data-fvw` attribute passes focus to `this.focusVisibleWithin$` --> <!-- The `data-fvw` attribute passes focus to `this.focusVisibleWithin$` -->
<!-- The following `class` field should match the `#isButton` class field below --> <!-- The following `class` field should match the `#isButton` class field below -->
<a <a
class="tw-w-full tw-px-3 tw-block tw-truncate tw-border-none tw-bg-transparent tw-text-start !tw-text-alt2 hover:tw-text-alt2 hover:tw-no-underline focus:tw-outline-none" class="tw-size-full tw-px-3 tw-block tw-truncate tw-border-none tw-bg-transparent tw-text-start !tw-text-alt2 hover:tw-text-alt2 hover:tw-no-underline focus:tw-outline-none"
data-fvw data-fvw
[routerLink]="route" [routerLink]="route"
[relativeTo]="relativeTo" [relativeTo]="relativeTo"
@@ -56,7 +56,7 @@
<!-- Class field should match `#isAnchor` class field above --> <!-- Class field should match `#isAnchor` class field above -->
<button <button
type="button" type="button"
class="tw-w-full tw-px-3 tw-truncate tw-border-none tw-bg-transparent tw-text-start !tw-text-alt2 hover:tw-text-alt2 hover:tw-no-underline focus:tw-outline-none" class="tw-size-full tw-px-3 tw-truncate tw-border-none tw-bg-transparent tw-text-start !tw-text-alt2 hover:tw-text-alt2 hover:tw-no-underline focus:tw-outline-none"
data-fvw data-fvw
(click)="mainContentClicked.emit()" (click)="mainContentClicked.emit()"
> >

View File

@@ -68,6 +68,13 @@ export const WithoutIcon: Story = {
}, },
}; };
export const WithLongText: Story = {
...Default,
args: {
text: "Hello World This Is a Cool Item",
},
};
export const WithoutRoute: Story = { export const WithoutRoute: Story = {
render: () => ({ render: () => ({
template: ` template: `
@@ -80,7 +87,7 @@ export const WithChildButtons: Story = {
render: (args) => ({ render: (args) => ({
props: args, props: args,
template: ` template: `
<bit-nav-item text="Hello World" [route]="['']" icon="bwi-collection-shared"> <bit-nav-item text="Hello World Very Cool World" [route]="['']" icon="bwi-collection-shared">
<button <button
slot="end" slot="end"
class="tw-ms-auto" class="tw-ms-auto"
@@ -106,8 +113,8 @@ export const MultipleItemsWithDivider: Story = {
render: (args) => ({ render: (args) => ({
props: args, props: args,
template: ` template: `
<bit-nav-item text="Hello World" icon="bwi-collection-shared"></bit-nav-item> <bit-nav-item text="Hello World"></bit-nav-item>
<bit-nav-item text="Hello World" icon="bwi-collection-shared"></bit-nav-item> <bit-nav-item text="Hello World Long Text Long"></bit-nav-item>
<bit-nav-divider></bit-nav-divider> <bit-nav-divider></bit-nav-divider>
<bit-nav-item text="Hello World" icon="bwi-collection-shared"></bit-nav-item> <bit-nav-item text="Hello World" icon="bwi-collection-shared"></bit-nav-item>
<bit-nav-item text="Hello World" icon="bwi-collection-shared"></bit-nav-item> <bit-nav-item text="Hello World" icon="bwi-collection-shared"></bit-nav-item>

View File

@@ -14,6 +14,7 @@ import {
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PasswordManagerLogo } from "../../icon";
import { LayoutComponent } from "../../layout"; import { LayoutComponent } from "../../layout";
import { I18nMockService } from "../../utils/i18n-mock.service"; import { I18nMockService } from "../../utils/i18n-mock.service";
import { positionFixedWrapperDecorator } from "../storybook-decorators"; import { positionFixedWrapperDecorator } from "../storybook-decorators";
@@ -77,9 +78,13 @@ type Story = StoryObj<LayoutComponent>;
export const Default: Story = { export const Default: Story = {
render: (args) => { render: (args) => {
return { return {
props: args, props: {
...args,
logo: PasswordManagerLogo,
},
template: /* HTML */ `<bit-layout> template: /* HTML */ `<bit-layout>
<bit-side-nav> <bit-side-nav>
<bit-nav-logo [openIcon]="logo" route="." [label]="Logo"></bit-nav-logo>
<bit-nav-group text="Password Managers" icon="bwi-collection-shared" [open]="true"> <bit-nav-group text="Password Managers" icon="bwi-collection-shared" [open]="true">
<bit-nav-item text="Child A" route="a" icon="bwi-filter"></bit-nav-item> <bit-nav-item text="Child A" route="a" icon="bwi-filter"></bit-nav-item>
<bit-nav-item text="Child B" route="b"></bit-nav-item> <bit-nav-item text="Child B" route="b"></bit-nav-item>
@@ -99,10 +104,15 @@ export const Default: Story = {
</bit-layout>`, </bit-layout>`,
}; };
}, },
parameters: {
chromatic: {
viewports: [640, 1280],
},
},
}; };
export const MenuOpen: Story = { export const MenuOpen: Story = {
...Default, render: Default.render,
play: async (context) => { play: async (context) => {
const canvas = context.canvasElement; const canvas = context.canvasElement;
const table = getByRole(canvas, "table"); const table = getByRole(canvas, "table");
@@ -116,7 +126,7 @@ export const MenuOpen: Story = {
}; };
export const DialogOpen: Story = { export const DialogOpen: Story = {
...Default, render: Default.render,
play: async (context) => { play: async (context) => {
const canvas = context.canvasElement; const canvas = context.canvasElement;
const dialogButton = getByRole(canvas, "button", { const dialogButton = getByRole(canvas, "button", {
@@ -129,7 +139,7 @@ export const DialogOpen: Story = {
}; };
export const DrawerOpen: Story = { export const DrawerOpen: Story = {
...Default, render: Default.render,
play: async (context) => { play: async (context) => {
const canvas = context.canvasElement; const canvas = context.canvasElement;
const drawerButton = getByRole(canvas, "button", { const drawerButton = getByRole(canvas, "button", {
@@ -142,7 +152,7 @@ export const DrawerOpen: Story = {
}; };
export const PopoverOpen: Story = { export const PopoverOpen: Story = {
...Default, render: Default.render,
play: async (context) => { play: async (context) => {
const canvas = context.canvasElement; const canvas = context.canvasElement;
const passwordLabelIcon = getByLabelText(canvas, "A random password (required)", { const passwordLabelIcon = getByLabelText(canvas, "A random password (required)", {
@@ -154,7 +164,7 @@ export const PopoverOpen: Story = {
}; };
export const SimpleDialogOpen: Story = { export const SimpleDialogOpen: Story = {
...Default, render: Default.render,
play: async (context) => { play: async (context) => {
const canvas = context.canvasElement; const canvas = context.canvasElement;
const submitButton = getByRole(canvas, "button", { const submitButton = getByRole(canvas, "button", {
@@ -167,7 +177,7 @@ export const SimpleDialogOpen: Story = {
}; };
export const EmptyTab: Story = { export const EmptyTab: Story = {
...Default, render: Default.render,
play: async (context) => { play: async (context) => {
const canvas = context.canvasElement; const canvas = context.canvasElement;
const emptyTab = getByRole(canvas, "tab", { name: "Empty tab" }); const emptyTab = getByRole(canvas, "tab", { name: "Empty tab" });
@@ -176,7 +186,7 @@ export const EmptyTab: Story = {
}; };
export const VirtualScrollBlockingDialog: Story = { export const VirtualScrollBlockingDialog: Story = {
...Default, render: Default.render,
play: async (context) => { play: async (context) => {
const canvas = context.canvasElement; const canvas = context.canvasElement;
const navItem = getByText(canvas, "Virtual Scroll"); const navItem = getByText(canvas, "Virtual Scroll");