mirror of
https://github.com/bitwarden/browser
synced 2026-02-25 09:03:28 +00:00
* update responsive behavior of three panel layout; give sidenav extra top padding on electron; add stories that show mix of drawer and sidenav states --------- Co-authored-by: Will Martin <contact@willmartian.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,8 @@
|
||||
.vault > .groupings > .content > .inner-content {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
--bit-sidenav-macos-extra-top-padding: 28px;
|
||||
}
|
||||
|
||||
.environment-selector-btn {
|
||||
|
||||
@@ -25,7 +25,7 @@ const render: Story["render"] = (args) => ({
|
||||
...args,
|
||||
},
|
||||
template: `
|
||||
<bit-dialog [dialogSize]="dialogSize" [disablePadding]="disablePadding" disableAnimations>
|
||||
<bit-dialog disableAnimations>
|
||||
<span bitDialogTitle>Access selector</span>
|
||||
<span bitDialogContent>
|
||||
<bit-access-selector
|
||||
|
||||
@@ -1,68 +1,66 @@
|
||||
<div class="tw-mt-auto">
|
||||
@let accessibleProducts = accessibleProducts$ | async;
|
||||
@if (accessibleProducts && accessibleProducts.length > 1) {
|
||||
<!-- [attr.icon] is used to keep the icon attribute on the bit-nav-item after prod mode is enabled. Matches other navigation items and assists in automated testing. -->
|
||||
<bit-nav-item
|
||||
*ngFor="let product of accessibleProducts"
|
||||
[icon]="product.icon"
|
||||
[text]="product.name"
|
||||
[route]="product.appRoute"
|
||||
[attr.icon]="product.icon"
|
||||
[forceActiveStyles]="product.isActive"
|
||||
focusAfterNavTarget="body"
|
||||
>
|
||||
</bit-nav-item>
|
||||
}
|
||||
@let accessibleProducts = accessibleProducts$ | async;
|
||||
@if (accessibleProducts && accessibleProducts.length > 1) {
|
||||
<!-- [attr.icon] is used to keep the icon attribute on the bit-nav-item after prod mode is enabled. Matches other navigation items and assists in automated testing. -->
|
||||
<bit-nav-item
|
||||
*ngFor="let product of accessibleProducts"
|
||||
[icon]="product.icon"
|
||||
[text]="product.name"
|
||||
[route]="product.appRoute"
|
||||
[attr.icon]="product.icon"
|
||||
[forceActiveStyles]="product.isActive"
|
||||
focusAfterNavTarget="body"
|
||||
>
|
||||
</bit-nav-item>
|
||||
}
|
||||
|
||||
@if (shouldShowPremiumUpgradeButton$ | async) {
|
||||
<app-upgrade-nav-button></app-upgrade-nav-button>
|
||||
}
|
||||
@if (shouldShowPremiumUpgradeButton$ | async) {
|
||||
<app-upgrade-nav-button></app-upgrade-nav-button>
|
||||
}
|
||||
|
||||
@let moreProducts = moreProducts$ | async;
|
||||
@if (moreProducts && moreProducts.length > 0) {
|
||||
<section class="tw-mt-2 tw-flex tw-w-full tw-flex-col tw-gap-2 tw-border-0">
|
||||
<span class="tw-text-xs !tw-text-alt2 tw-p-2 tw-pb-0">{{ "moreFromBitwarden" | i18n }}</span>
|
||||
<ng-container *ngFor="let more of moreProducts">
|
||||
<div class="tw-ps-2 tw-pe-2">
|
||||
<!-- <a> for when the marketing route is external -->
|
||||
<a
|
||||
*ngIf="more.marketingRoute.external"
|
||||
[href]="more.marketingRoute.route"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="tw-flex tw-px-3 tw-py-2 tw-rounded-md tw-font-medium !tw-text-alt2 !tw-no-underline hover:tw-bg-hover-contrast [&>:not(.bwi)]:hover:tw-underline"
|
||||
>
|
||||
<i class="bwi bwi-fw {{ more.icon }} tw-mt-1 tw-mx-1"></i>
|
||||
<div>
|
||||
{{ more.otherProductOverrides?.name ?? more.name }}
|
||||
<div
|
||||
*ngIf="more.otherProductOverrides?.supportingText"
|
||||
class="tw-text-xs tw-font-normal"
|
||||
>
|
||||
{{ more.otherProductOverrides.supportingText }}
|
||||
</div>
|
||||
@let moreProducts = moreProducts$ | async;
|
||||
@if (moreProducts && moreProducts.length > 0) {
|
||||
<section class="tw-mt-2 tw-flex tw-w-full tw-flex-col tw-gap-2 tw-border-0">
|
||||
<span class="tw-text-xs !tw-text-alt2 tw-p-2 tw-pb-0">{{ "moreFromBitwarden" | i18n }}</span>
|
||||
<ng-container *ngFor="let more of moreProducts">
|
||||
<div class="tw-ps-2 tw-pe-2">
|
||||
<!-- <a> for when the marketing route is external -->
|
||||
<a
|
||||
*ngIf="more.marketingRoute.external"
|
||||
[href]="more.marketingRoute.route"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="tw-flex tw-px-3 tw-py-2 tw-rounded-md tw-font-medium !tw-text-alt2 !tw-no-underline hover:tw-bg-hover-contrast [&>:not(.bwi)]:hover:tw-underline"
|
||||
>
|
||||
<i class="bwi bwi-fw {{ more.icon }} tw-mt-1 tw-mx-1"></i>
|
||||
<div>
|
||||
{{ more.otherProductOverrides?.name ?? more.name }}
|
||||
<div
|
||||
*ngIf="more.otherProductOverrides?.supportingText"
|
||||
class="tw-text-xs tw-font-normal"
|
||||
>
|
||||
{{ more.otherProductOverrides.supportingText }}
|
||||
</div>
|
||||
</a>
|
||||
<!-- <a> for when the marketing route is internal, it needs to use [routerLink] instead of [href] like the external <a> uses. -->
|
||||
<a
|
||||
*ngIf="!more.marketingRoute.external"
|
||||
[routerLink]="more.marketingRoute.route"
|
||||
rel="noreferrer"
|
||||
class="tw-flex tw-px-3 tw-py-2 tw-rounded-md tw-font-medium !tw-text-alt2 !tw-no-underline hover:tw-bg-hover-contrast [&>:not(.bwi)]:hover:tw-underline"
|
||||
>
|
||||
<i class="bwi bwi-fw {{ more.icon }} tw-mt-1 tw-mx-1"></i>
|
||||
<div>
|
||||
{{ more.otherProductOverrides?.name ?? more.name }}
|
||||
<div
|
||||
*ngIf="more.otherProductOverrides?.supportingText"
|
||||
class="tw-text-xs tw-font-normal"
|
||||
>
|
||||
{{ more.otherProductOverrides.supportingText }}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<!-- <a> for when the marketing route is internal, it needs to use [routerLink] instead of [href] like the external <a> uses. -->
|
||||
<a
|
||||
*ngIf="!more.marketingRoute.external"
|
||||
[routerLink]="more.marketingRoute.route"
|
||||
rel="noreferrer"
|
||||
class="tw-flex tw-px-3 tw-py-2 tw-rounded-md tw-font-medium !tw-text-alt2 !tw-no-underline hover:tw-bg-hover-contrast [&>:not(.bwi)]:hover:tw-underline"
|
||||
>
|
||||
<i class="bwi bwi-fw {{ more.icon }} tw-mt-1 tw-mx-1"></i>
|
||||
<div>
|
||||
{{ more.otherProductOverrides?.name ?? more.name }}
|
||||
<div
|
||||
*ngIf="more.otherProductOverrides?.supportingText"
|
||||
class="tw-text-xs tw-font-normal"
|
||||
>
|
||||
{{ more.otherProductOverrides.supportingText }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</ng-container>
|
||||
</section>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</ng-container>
|
||||
</section>
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { BehaviorSubject } from "rxjs";
|
||||
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { FakeGlobalStateProvider } from "@bitwarden/common/spec";
|
||||
import { IconButtonModule, NavigationModule } from "@bitwarden/components";
|
||||
import { IconButtonModule, NavigationModule, SideNavService } from "@bitwarden/components";
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { NavItemComponent } from "@bitwarden/components/src/navigation/nav-item.component";
|
||||
@@ -86,6 +86,9 @@ describe("NavigationProductSwitcherComponent", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NavigationProductSwitcherComponent);
|
||||
// SideNavService.open starts false (managed by LayoutComponent's ResizeObserver in a real
|
||||
// app). Set it to true so NavItemComponent renders text labels (used in text-content checks).
|
||||
TestBed.inject(SideNavService).open.set(true);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Component, Directive, importProvidersFrom, Input } from "@angular/core";
|
||||
import { provideNoopAnimations } from "@angular/platform-browser/animations";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
||||
import { BehaviorSubject, Observable, of } from "rxjs";
|
||||
|
||||
import { PasswordManagerLogo } from "@bitwarden/assets/svg";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
@@ -17,13 +19,13 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import {
|
||||
I18nMockService,
|
||||
LayoutComponent,
|
||||
NavigationModule,
|
||||
StorybookGlobalStateProvider,
|
||||
} from "@bitwarden/components";
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { I18nMockService } from "@bitwarden/components/src/utils/i18n-mock.service";
|
||||
import { positionFixedWrapperDecorator } from "@bitwarden/components/src/stories/storybook-decorators";
|
||||
import { GlobalStateProvider } from "@bitwarden/state";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
|
||||
@@ -109,15 +111,6 @@ class MockConfigService implements Partial<ConfigService> {
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
|
||||
@Component({
|
||||
selector: "story-layout",
|
||||
template: `<ng-content></ng-content>`,
|
||||
standalone: false,
|
||||
})
|
||||
class StoryLayoutComponent {}
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
|
||||
@Component({
|
||||
@@ -132,17 +125,23 @@ const translations: Record<string, string> = {
|
||||
secureYourInfrastructure: "Secure your infrastructure",
|
||||
protectYourFamilyOrBusiness: "Protect your family or business",
|
||||
skipToContent: "Skip to content",
|
||||
toggleSideNavigation: "Toggle side navigation",
|
||||
resizeSideNavigation: "Resize side navigation",
|
||||
submenu: "submenu",
|
||||
toggleCollapse: "toggle collapse",
|
||||
close: "Close",
|
||||
loading: "Loading",
|
||||
};
|
||||
|
||||
export default {
|
||||
title: "Web/Navigation Product Switcher",
|
||||
decorators: [
|
||||
positionFixedWrapperDecorator(),
|
||||
moduleMetadata({
|
||||
declarations: [
|
||||
NavigationProductSwitcherComponent,
|
||||
MockOrganizationService,
|
||||
MockProviderService,
|
||||
StoryLayoutComponent,
|
||||
StoryContentComponent,
|
||||
],
|
||||
imports: [NavigationModule, RouterModule, LayoutComponent, I18nPipe],
|
||||
@@ -174,19 +173,11 @@ export default {
|
||||
}),
|
||||
applicationConfig({
|
||||
providers: [
|
||||
provideNoopAnimations(),
|
||||
importProvidersFrom(
|
||||
RouterModule.forRoot([
|
||||
{
|
||||
path: "",
|
||||
component: StoryLayoutComponent,
|
||||
children: [
|
||||
{
|
||||
path: "**",
|
||||
component: StoryContentComponent,
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
RouterModule.forRoot([{ path: "**", component: StoryContentComponent }], {
|
||||
useHash: true,
|
||||
}),
|
||||
),
|
||||
{
|
||||
provide: GlobalStateProvider,
|
||||
@@ -203,12 +194,47 @@ type Story = StoryObj<
|
||||
|
||||
const Template: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
props: { ...args, logo: PasswordManagerLogo },
|
||||
template: `
|
||||
<router-outlet [mockOrgs]="mockOrgs" [mockProviders]="mockProviders"></router-outlet>
|
||||
<div class="tw-bg-background-alt3 tw-w-60">
|
||||
<navigation-product-switcher></navigation-product-switcher>
|
||||
</div>
|
||||
<bit-layout>
|
||||
<bit-side-nav>
|
||||
<bit-nav-logo [openIcon]="logo" route="." label="Bitwarden"></bit-nav-logo>
|
||||
<bit-nav-item text="Vault" icon="bwi-lock"></bit-nav-item>
|
||||
<bit-nav-item text="Send" icon="bwi-send"></bit-nav-item>
|
||||
<bit-nav-group text="Tools" icon="bwi-key" [open]="true">
|
||||
<bit-nav-item text="Generator"></bit-nav-item>
|
||||
<bit-nav-item text="Import"></bit-nav-item>
|
||||
<bit-nav-item text="Export"></bit-nav-item>
|
||||
</bit-nav-group>
|
||||
<bit-nav-group text="Organizations" icon="bwi-business" [open]="true">
|
||||
<bit-nav-item text="Acme Corp" icon="bwi-collection-shared"></bit-nav-item>
|
||||
<bit-nav-item text="Acme Corp — Vault" variant="tree"></bit-nav-item>
|
||||
<bit-nav-item text="Acme Corp — Members" variant="tree"></bit-nav-item>
|
||||
<bit-nav-item text="Acme Corp — Settings" variant="tree"></bit-nav-item>
|
||||
<bit-nav-item text="My Family" icon="bwi-collection-shared"></bit-nav-item>
|
||||
<bit-nav-item text="My Family — Vault" variant="tree"></bit-nav-item>
|
||||
<bit-nav-item text="My Family — Members" variant="tree"></bit-nav-item>
|
||||
<bit-nav-item text="Initech" icon="bwi-collection-shared"></bit-nav-item>
|
||||
<bit-nav-item text="Initech — Vault" variant="tree"></bit-nav-item>
|
||||
<bit-nav-item text="Initech — Members" variant="tree"></bit-nav-item>
|
||||
<bit-nav-item text="Initech — Settings" variant="tree"></bit-nav-item>
|
||||
<bit-nav-item text="Umbrella Corp" icon="bwi-collection-shared"></bit-nav-item>
|
||||
<bit-nav-item text="Umbrella Corp — Vault" variant="tree"></bit-nav-item>
|
||||
<bit-nav-item text="Umbrella Corp — Members" variant="tree"></bit-nav-item>
|
||||
<bit-nav-item text="Umbrella Corp — Settings" variant="tree"></bit-nav-item>
|
||||
<bit-nav-item text="Stark Industries" icon="bwi-collection-shared"></bit-nav-item>
|
||||
<bit-nav-item text="Stark Industries — Vault" variant="tree"></bit-nav-item>
|
||||
<bit-nav-item text="Stark Industries — Members" variant="tree"></bit-nav-item>
|
||||
<bit-nav-item text="Stark Industries — Settings" variant="tree"></bit-nav-item>
|
||||
</bit-nav-group>
|
||||
<bit-nav-item text="Settings" icon="bwi-cog"></bit-nav-item>
|
||||
<ng-container slot="product-switcher">
|
||||
<bit-nav-divider></bit-nav-divider>
|
||||
<navigation-product-switcher [mockOrgs]="mockOrgs" [mockProviders]="mockProviders"></navigation-product-switcher>
|
||||
</ng-container>
|
||||
</bit-side-nav>
|
||||
<router-outlet></router-outlet>
|
||||
</bit-layout>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<bit-side-nav [variant]="variant">
|
||||
<ng-content></ng-content>
|
||||
|
||||
<ng-container slot="footer">
|
||||
<ng-container slot="product-switcher">
|
||||
<bit-nav-divider></bit-nav-divider>
|
||||
<navigation-product-switcher></navigation-product-switcher>
|
||||
</ng-container>
|
||||
|
||||
<ng-container slot="footer">
|
||||
<app-toggle-width></app-toggle-width>
|
||||
</ng-container>
|
||||
</bit-side-nav>
|
||||
|
||||
Reference in New Issue
Block a user