From 62986be1a2f928c80ea6a373b286e2d2edb3caf3 Mon Sep 17 00:00:00 2001 From: Will Martin Date: Mon, 30 Jan 2023 10:58:40 -0500 Subject: [PATCH] [SM-325] update header layout to support projected content (#4509) * expose breadcrumbs to shared module; remove extra whitespace from breadcrumb * update sm-header to accept projected content; update layout style & semantics * remove searchTitle route data; change title to titleId * update styles * update SM pages to use new header * add button slot to header * remove breadcrumbs from shared module * update project tabs * update content projection to use slot attribute * hide tabs container * add icon input to header * add breadcrumbs to service account page * update padding * update styles * code style changes * create access token button component * simplify access token logic * add create access token button to header * update sm-no-items content projection * revert irrelevant commits * add header title slot; rename template vars for clarity * add storybook stories * remove sm-new-menu from default view; refactor search/button content projection * remove unused styles; fix title truncate * remove unnecessary classes Co-authored-by: Oscar Hinton --------- Co-authored-by: Oscar Hinton --- .../layout/header.component.html | 150 +++++++++++------ .../layout/header.component.ts | 14 +- .../secrets-manager/layout/header.stories.ts | 157 ++++++++++++++++++ .../layout/layout.component.html | 6 +- .../layout/new-menu.component.html | 2 +- .../overview/overview.component.html | 4 +- .../projects/project/project.component.html | 18 +- .../projects/project/project.component.ts | 4 +- .../projects/projects.module.ts | 4 +- .../projects/projects/projects.component.html | 5 +- .../secrets/secrets.component.html | 5 +- .../service-account.component.html | 25 ++- .../service-account.component.ts | 22 ++- .../service-accounts.component.html | 5 +- .../service-accounts.module.ts | 4 +- .../app/secrets-manager/sm-routing.module.ts | 9 +- .../src/breadcrumbs/breadcrumb.component.html | 3 +- .../tabs/tab-nav-bar/tab-nav-bar.component.ts | 3 + 18 files changed, 345 insertions(+), 95 deletions(-) create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/header.stories.ts diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.component.html index 115dac5f521..b68af9c4ef2 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.component.html @@ -1,56 +1,96 @@ -
-

{{ routeData.title | i18n }}

-
- -
- - - - - - -
-
- -
- {{ "loggedInAs" | i18n }} - - {{ account.name }} - -
-
- - - - - - {{ "accountSettings" | i18n }} - - - - {{ "getHelp" | i18n }} - - - - {{ "getApps" | i18n }} - - - - - - +
+
+
+ +
+
- - -
+

+ + {{ title || (routeData.titleId | i18n) }} +

+
+
+
+ + + + + + +
+
+ +
+ {{ "loggedInAs" | i18n }} + + {{ account.name }} + +
+
+ + + + + + {{ "accountSettings" | i18n }} + + + + {{ "getHelp" | i18n }} + + + + {{ "getApps" | i18n }} + + + + + + +
+
+
+
+
+ +
+
+
+
+ +
+ diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.component.ts index 8ad4e6a68e2..6d299579a32 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.component.ts @@ -10,18 +10,24 @@ import { AccountProfile } from "@bitwarden/common/models/domain/account"; templateUrl: "./header.component.html", }) export class HeaderComponent { + /** + * Custom title that overrides the route data `titleId` + */ @Input() title: string; - @Input() searchTitle: string; - protected routeData$: Observable<{ title: string; searchTitle: string }>; + /** + * Icon to show before the title + */ + @Input() icon: string; + + protected routeData$: Observable<{ titleId: string }>; protected account$: Observable; constructor(private route: ActivatedRoute, private stateService: StateService) { this.routeData$ = this.route.data.pipe( map((params) => { return { - title: params.title, - searchTitle: params.searchTitle, + titleId: params.titleId, }; }) ); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.stories.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.stories.ts new file mode 100644 index 00000000000..cb932b3f026 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/header.stories.ts @@ -0,0 +1,157 @@ +import { Component, Injectable } from "@angular/core"; +import { RouterModule } from "@angular/router"; +import { Meta, Story, moduleMetadata, componentWrapperDecorator } from "@storybook/angular"; +import { Observable } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { StateService } from "@bitwarden/common/abstractions/state.service"; +import { + BreadcrumbsModule, + ButtonModule, + NavigationModule, + IconModule, + IconButtonModule, + TabsModule, +} from "@bitwarden/components"; +import { InputModule } from "@bitwarden/components/src/input/input.module"; +import { PreloadedEnglishI18nModule } from "@bitwarden/web-vault/app/tests/preloaded-english-i18n.module"; + +import { HeaderComponent } from "./header.component"; + +@Injectable() +class MockStateService { + activeAccount$ = new Observable(); + accounts$ = new Observable(); +} + +@Component({ + selector: "product-switcher", + template: ``, +}) +class MockProductSwitcher {} + +export default { + title: "Web/Header", + component: HeaderComponent, + decorators: [ + componentWrapperDecorator( + (story) => `
${story}
` + ), + moduleMetadata({ + imports: [ + JslibModule, + RouterModule.forRoot( + [ + { + path: "", + component: HeaderComponent, + }, + ], + { useHash: true } + ), + BreadcrumbsModule, + ButtonModule, + InputModule, + IconModule, + IconButtonModule, + NavigationModule, + PreloadedEnglishI18nModule, + TabsModule, + ], + declarations: [HeaderComponent, MockProductSwitcher], + providers: [{ provide: StateService, useClass: MockStateService }], + }), + ], +} as Meta; + +export const KitchenSink: Story = (args) => ({ + props: args, + template: ` + + + Foo + Bar + + + + + + Foo + Bar + + + `, +}); + +export const Basic: Story = (args) => ({ + props: args, + template: ` + + `, +}); + +export const WithLongTitle: Story = (args) => ({ + props: args, + template: ` + + `, +}); + +export const WithBreadcrumbs: Story = (args) => ({ + props: args, + template: ` + + + Foo + Bar + + + `, +}); + +export const WithSearch: Story = (args) => ({ + props: args, + template: ` + + + + `, +}); + +export const WithSecondaryContent: Story = (args) => ({ + props: args, + template: ` + + + + `, +}); + +export const WithTabs: Story = (args) => ({ + props: args, + template: ` + + + Foo + Bar + + + `, +}); + +export const WithCustomTitleComponent: Story = (args) => ({ + props: args, + template: ` + +

Bitwarden

+
+ `, +}); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.html index 5d15daf96b5..36f6600d7ff 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.html @@ -1,8 +1,8 @@
- -
+ +
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/new-menu.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/layout/new-menu.component.html index 67d54c90f9f..a1f08ff76e2 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/new-menu.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/new-menu.component.html @@ -1,4 +1,4 @@ - diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html index 4192b1713ca..2944c848777 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html @@ -1 +1,3 @@ - + + + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.html index f9c39aaed13..6044d8d9885 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.html @@ -1,8 +1,12 @@ - - - - Secrets - Access - - + + + {{ "projects" | i18n }} + + + {{ "secrets" | i18n }} + {{ "people" | i18n }} + {{ "serviceAccounts" | i18n }} + + + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts index 8720b30c010..393ea33d55e 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts @@ -10,12 +10,12 @@ import { ProjectService } from "../project.service"; templateUrl: "./project.component.html", }) export class ProjectComponent implements OnInit { - project: Observable; + project$: Observable; constructor(private route: ActivatedRoute, private projectService: ProjectService) {} ngOnInit(): void { - this.project = this.route.params.pipe( + this.project$ = this.route.params.pipe( switchMap((params) => { return this.projectService.getByProjectId(params.projectId); }) diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects.module.ts index 56c9840cdfd..be15bd8306d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects.module.ts @@ -1,5 +1,7 @@ import { NgModule } from "@angular/core"; +import { BreadcrumbsModule } from "@bitwarden/components"; + import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; import { ProjectDeleteDialogComponent } from "./dialog/project-delete-dialog.component"; @@ -11,7 +13,7 @@ import { ProjectsRoutingModule } from "./projects-routing.module"; import { ProjectsComponent } from "./projects/projects.component"; @NgModule({ - imports: [SecretsManagerSharedModule, ProjectsRoutingModule], + imports: [SecretsManagerSharedModule, ProjectsRoutingModule, BreadcrumbsModule], declarations: [ ProjectsComponent, ProjectsListComponent, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.html index 040df33abc1..2e6c092caa6 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.html @@ -1,4 +1,7 @@ - + + + + + + + + - - - {{ "secrets" | i18n }} - {{ "people" | i18n }} - {{ "accessTokens" | i18n }} - - + + + {{ + "serviceAccounts" | i18n + }} + + + + {{ "secrets" | i18n }} + {{ "people" | i18n }} + {{ "accessTokens" | i18n }} + + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts index f355ede8e43..d0288866f37 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts @@ -1,7 +1,27 @@ import { Component } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { switchMap } from "rxjs"; + +import { ServiceAccountService } from "./service-account.service"; @Component({ selector: "sm-service-account", templateUrl: "./service-account.component.html", }) -export class ServiceAccountComponent {} +export class ServiceAccountComponent { + /** + * TODO: remove when a server method is available that fetches a service account by ID + */ + protected serviceAccount$ = this.route.params.pipe( + switchMap((params) => + this.serviceAccountService + .getServiceAccounts(params.organizationId) + .then((saList) => saList.find((sa) => sa.id === params.serviceAccountId)) + ) + ); + + constructor( + private route: ActivatedRoute, + private serviceAccountService: ServiceAccountService + ) {} +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.html index 27e6873d7be..d9285b34cdf 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.html @@ -1,4 +1,7 @@ - + + + + SecretsModule, data: { - title: "secrets", - searchTitle: "searchSecrets", + titleId: "secrets", }, }, { path: "projects", loadChildren: () => ProjectsModule, data: { - title: "projects", - searchTitle: "searchProjects", + titleId: "projects", }, }, { path: "service-accounts", loadChildren: () => ServiceAccountsModule, data: { - title: "serviceAccounts", - searchTitle: "searchServiceAccounts", + titleId: "serviceAccounts", }, }, { diff --git a/libs/components/src/breadcrumbs/breadcrumb.component.html b/libs/components/src/breadcrumbs/breadcrumb.component.html index 5291f0cab4c..dd5bac9beb4 100644 --- a/libs/components/src/breadcrumbs/breadcrumb.component.html +++ b/libs/components/src/breadcrumbs/breadcrumb.component.html @@ -1,4 +1,3 @@ - - + diff --git a/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts b/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts index 0dd65922f68..e782a446c4c 100644 --- a/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts +++ b/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts @@ -13,6 +13,9 @@ import { TabLinkComponent } from "./tab-link.component"; @Component({ selector: "bit-tab-nav-bar", templateUrl: "tab-nav-bar.component.html", + host: { + class: "tw-block", + }, }) export class TabNavBarComponent implements AfterContentInit { @ContentChildren(forwardRef(() => TabLinkComponent)) tabLabels: QueryList;