From 466f191281369188aac769de139c6b6713e40c92 Mon Sep 17 00:00:00 2001 From: William Martin Date: Sat, 8 Feb 2025 23:52:38 -0500 Subject: [PATCH 1/2] fix web header stories --- .../layouts/header/web-header.skip-stories.ts | 232 ---------------- .../app/layouts/header/web-header.stories.ts | 258 ++++++++++++++++++ 2 files changed, 258 insertions(+), 232 deletions(-) delete mode 100644 apps/web/src/app/layouts/header/web-header.skip-stories.ts create mode 100644 apps/web/src/app/layouts/header/web-header.stories.ts diff --git a/apps/web/src/app/layouts/header/web-header.skip-stories.ts b/apps/web/src/app/layouts/header/web-header.skip-stories.ts deleted file mode 100644 index 8db4aea3061..00000000000 --- a/apps/web/src/app/layouts/header/web-header.skip-stories.ts +++ /dev/null @@ -1,232 +0,0 @@ -// import { CommonModule } from "@angular/common"; -// import { Component, importProvidersFrom, Injectable, Input } from "@angular/core"; -// import { RouterModule } from "@angular/router"; -// import { -// applicationConfig, -// componentWrapperDecorator, -// Meta, -// moduleMetadata, -// Story, -// } from "@storybook/angular"; -// import { BehaviorSubject, combineLatest, map, of } from "rxjs"; - -// import { JslibModule } from "@bitwarden/angular/jslib.module"; -// import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -// import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; -// import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -// import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -// import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -// import { -// AvatarModule, -// BreadcrumbsModule, -// ButtonModule, -// IconButtonModule, -// IconModule, -// InputModule, -// MenuModule, -// NavigationModule, -// TabsModule, -// TypographyModule, -// } from "@bitwarden/components"; - -// import { DynamicAvatarComponent } from "../../components/dynamic-avatar.component"; -// import { PreloadedEnglishI18nModule } from "../../core/tests"; -// import { WebHeaderComponent } from "../header/web-header.component"; - -// import { WebLayoutMigrationBannerService } from "./web-layout-migration-banner.service"; - -// @Injectable({ -// providedIn: "root", -// }) -// class MockStateService { -// activeAccount$ = new BehaviorSubject("1").asObservable(); -// accounts$ = new BehaviorSubject({ "1": { profile: { name: "Foo" } } }).asObservable(); -// } - -// class MockMessagingService implements MessagingService { -// send(subscriber: string, arg?: any) { -// alert(subscriber); -// } -// } - -// class MockVaultTimeoutService { -// availableVaultTimeoutActions$() { -// return new BehaviorSubject([VaultTimeoutAction.Lock]).asObservable(); -// } -// } - -// class MockPlatformUtilsService { -// isSelfHost() { -// return false; -// } -// } - -// @Component({ -// selector: "product-switcher", -// template: ``, -// }) -// class MockProductSwitcher {} - -// @Component({ -// selector: "dynamic-avatar", -// template: ``, -// standalone: true, -// imports: [CommonModule, AvatarModule], -// }) -// class MockDynamicAvatar implements Partial { -// protected name$ = combineLatest([ -// this.stateService.accounts$, -// this.stateService.activeAccount$, -// ]).pipe( -// map( -// ([accounts, activeAccount]) => accounts[activeAccount as keyof typeof accounts].profile.name, -// ), -// ); - -// @Input() -// text: string; - -// constructor(private stateService: MockStateService) {} -// } - -// export default { -// title: "Web/Header", -// component: WebHeaderComponent, -// decorators: [ -// componentWrapperDecorator( -// (story) => `
${story}
`, -// ), -// moduleMetadata({ -// imports: [ -// JslibModule, -// AvatarModule, -// BreadcrumbsModule, -// ButtonModule, -// IconButtonModule, -// IconModule, -// InputModule, -// MenuModule, -// TabsModule, -// TypographyModule, -// NavigationModule, -// MockDynamicAvatar, -// ], -// declarations: [WebHeaderComponent, MockProductSwitcher], -// providers: [ -// { provide: StateService, useClass: MockStateService }, -// { -// provide: WebLayoutMigrationBannerService, -// useValue: { -// showBanner$: of(false), -// } as Partial, -// }, -// { provide: PlatformUtilsService, useClass: MockPlatformUtilsService }, -// { provide: VaultTimeoutSettingsService, useClass: MockVaultTimeoutService }, -// { -// provide: MessagingService, -// useFactory: () => { -// return new MockMessagingService(); -// }, -// }, -// ], -// }), -// applicationConfig({ -// providers: [ -// importProvidersFrom(RouterModule.forRoot([], { useHash: true })), -// importProvidersFrom(PreloadedEnglishI18nModule), -// ], -// }), -// ], -// } 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 WithTitleSuffixComponent: Story = (args) => ({ -// props: args, -// template: ` -// -// -// -// `, -// }); diff --git a/apps/web/src/app/layouts/header/web-header.stories.ts b/apps/web/src/app/layouts/header/web-header.stories.ts new file mode 100644 index 00000000000..da00d75e416 --- /dev/null +++ b/apps/web/src/app/layouts/header/web-header.stories.ts @@ -0,0 +1,258 @@ +import { CommonModule } from "@angular/common"; +import { Component, importProvidersFrom, Injectable, Input } from "@angular/core"; +import { RouterModule } from "@angular/router"; +import { + applicationConfig, + componentWrapperDecorator, + Meta, + moduleMetadata, + StoryObj, +} from "@storybook/angular"; +import { BehaviorSubject, combineLatest, map, of } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { + AvatarModule, + BreadcrumbsModule, + ButtonModule, + IconButtonModule, + IconModule, + InputModule, + MenuModule, + NavigationModule, + TabsModule, + TypographyModule, +} from "@bitwarden/components"; + +import { DynamicAvatarComponent } from "../../components/dynamic-avatar.component"; +import { PreloadedEnglishI18nModule } from "../../core/tests"; +import { WebHeaderComponent } from "../header/web-header.component"; + +import { WebLayoutMigrationBannerService } from "./web-layout-migration-banner.service"; + +@Injectable({ + providedIn: "root", +}) +class MockStateService { + activeAccount$ = new BehaviorSubject("1").asObservable(); + accounts$ = new BehaviorSubject({ "1": { profile: { name: "Foo" } } }).asObservable(); +} + +@Component({ + selector: "product-switcher", + template: ``, +}) +class MockProductSwitcher {} + +@Component({ + selector: "dynamic-avatar", + template: ``, + standalone: true, + imports: [CommonModule, AvatarModule], +}) +class MockDynamicAvatar implements Partial { + protected name$ = combineLatest([ + this.stateService.accounts$, + this.stateService.activeAccount$, + ]).pipe( + map( + ([accounts, activeAccount]) => accounts[activeAccount as keyof typeof accounts].profile.name, + ), + ); + + @Input() + text?: string; + + constructor(private stateService: MockStateService) {} +} + +export default { + title: "Web/Header", + component: WebHeaderComponent, + decorators: [ + componentWrapperDecorator( + (story) => `
${story}
`, + ), + moduleMetadata({ + imports: [ + JslibModule, + AvatarModule, + BreadcrumbsModule, + ButtonModule, + IconButtonModule, + IconModule, + InputModule, + MenuModule, + TabsModule, + TypographyModule, + NavigationModule, + MockDynamicAvatar, + ], + declarations: [WebHeaderComponent, MockProductSwitcher], + providers: [ + { provide: StateService, useClass: MockStateService }, + { + provide: AccountService, + useValue: { + activeAccount$: of({ + name: "Foobar Warden", + }), + } as Partial, + }, + { + provide: WebLayoutMigrationBannerService, + useValue: { + showBanner$: of(false), + } as Partial, + }, + { + provide: PlatformUtilsService, + useValue: { + isSelfHost() { + return false; + }, + } as Partial, + }, + { + provide: VaultTimeoutSettingsService, + useValue: { + availableVaultTimeoutActions$() { + return new BehaviorSubject([VaultTimeoutAction.Lock]).asObservable(); + }, + } as Partial, + }, + { + provide: MessagingService, + useValue: { + send: (...args: any[]) => { + // eslint-disable-next-line no-console + console.log("MessagingService.send", args); + }, + } as Partial, + }, + ], + }), + applicationConfig({ + providers: [ + importProvidersFrom(RouterModule.forRoot([], { useHash: true })), + importProvidersFrom(PreloadedEnglishI18nModule), + ], + }), + ], +} as Meta; + +type Story = StoryObj; + +export const KitchenSink: Story = { + render: (args) => ({ + props: args, + template: ` + + + Foo + Bar + + + + + + Foo + Bar + + + `, + }), +}; + +export const Basic: Story = { + render: (args: any) => ({ + props: args, + template: ` + + `, + }), +}; + +export const WithLongTitle: Story = { + render: (arg: any) => ({ + props: arg, + template: ` + + `, + }), +}; + +export const WithBreadcrumbs: Story = { + render: (args: any) => ({ + props: args, + template: ` + + + Foo + Bar + + + `, + }), +}; + +export const WithSearch: Story = { + render: (args: any) => ({ + props: args, + template: ` + + + + `, + }), +}; + +export const WithSecondaryContent: Story = { + render: (args) => ({ + props: args, + template: ` + + + + `, + }), +}; + +export const WithTabs: Story = { + render: (args) => ({ + props: args, + template: ` + + + Foo + Bar + + + `, + }), +}; + +export const WithTitleSuffixComponent: Story = { + render: (args) => ({ + props: args, + template: ` + + + + `, + }), +}; From 741f5815c5188cd862c1fcda3921410d2e8b13cc Mon Sep 17 00:00:00 2001 From: William Martin Date: Sun, 9 Feb 2025 22:36:27 -0500 Subject: [PATCH 2/2] prevent title suffix slot content from being truncated --- .../src/app/layouts/header/web-header.component.html | 10 ++++++---- apps/web/src/app/layouts/header/web-header.stories.ts | 4 +++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/web/src/app/layouts/header/web-header.component.html b/apps/web/src/app/layouts/header/web-header.component.html index 7cba19b29ad..28d786f2d64 100644 --- a/apps/web/src/app/layouts/header/web-header.component.html +++ b/apps/web/src/app/layouts/header/web-header.component.html @@ -12,12 +12,14 @@

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

diff --git a/apps/web/src/app/layouts/header/web-header.stories.ts b/apps/web/src/app/layouts/header/web-header.stories.ts index da00d75e416..80e98ba7a57 100644 --- a/apps/web/src/app/layouts/header/web-header.stories.ts +++ b/apps/web/src/app/layouts/header/web-header.stories.ts @@ -187,7 +187,9 @@ export const WithLongTitle: Story = { render: (arg: any) => ({ props: arg, template: ` - + + + `, }), };