import { importProvidersFrom, Component } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { Meta, StoryObj, applicationConfig, componentWrapperDecorator, moduleMetadata, } from "@storybook/angular"; import { of } from "rxjs"; import { ClientType } from "@bitwarden/common/enums"; import { EnvironmentService, Environment, } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ThemeType } from "@bitwarden/common/platform/enums"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { ButtonModule } from "@bitwarden/components"; // FIXME: remove `/apps` import from `/libs` // eslint-disable-next-line import/no-restricted-paths import { PreloadedEnglishI18nModule } from "../../../../../apps/web/src/app/core/tests"; import { LockIcon } from "../icons"; import { RegistrationCheckEmailIcon } from "../icons/registration-check-email.icon"; import { AnonLayoutWrapperDataService } from "./anon-layout-wrapper-data.service"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "./anon-layout-wrapper.component"; import { DefaultAnonLayoutWrapperDataService } from "./default-anon-layout-wrapper-data.service"; export default { title: "Auth/Anon Layout Wrapper", component: AnonLayoutWrapperComponent, } as Meta; const decorators = (options: { components: any[]; routes: Routes; applicationVersion?: string; clientType?: ClientType; hostName?: string; themeType?: ThemeType; }) => { return [ componentWrapperDecorator( /** * Applying a CSS transform makes a `position: fixed` element act like it is `position: relative` * https://github.com/storybookjs/storybook/issues/8011#issue-490251969 */ (story) => { return /* HTML */ `
${story}
`; }, ({ globals }) => { /** * avoid a bug with the way that we render the same component twice in the same iframe and how * that interacts with the router-outlet */ const themeOverride = globals["theme"] === "both" ? "light" : globals["theme"]; return { theme: themeOverride }; }, ), moduleMetadata({ declarations: options.components, imports: [RouterModule, ButtonModule], providers: [ { provide: AnonLayoutWrapperDataService, useClass: DefaultAnonLayoutWrapperDataService, }, { provide: EnvironmentService, useValue: { environment$: of({ getHostname: () => options.hostName || "storybook.bitwarden.com", } as Partial), } as Partial, }, { provide: PlatformUtilsService, useValue: { getApplicationVersion: () => Promise.resolve(options.applicationVersion || "FAKE_APP_VERSION"), getClientType: () => options.clientType || ClientType.Web, } as Partial, }, { provide: ThemeStateService, useValue: { selectedTheme$: of(options.themeType || ThemeType.Light), } as Partial, }, ], }), applicationConfig({ providers: [ importProvidersFrom(RouterModule.forRoot(options.routes)), importProvidersFrom(PreloadedEnglishI18nModule), ], }), ]; }; type Story = StoryObj; // Default Example @Component({ selector: "bit-default-primary-outlet-example-component", template: "

Primary Outlet Example:
your primary component goes here

", }) export class DefaultPrimaryOutletExampleComponent {} @Component({ selector: "bit-default-secondary-outlet-example-component", template: "

Secondary Outlet Example:
your secondary component goes here

", }) export class DefaultSecondaryOutletExampleComponent {} @Component({ selector: "bit-default-env-selector-outlet-example-component", template: "

Env Selector Outlet Example:
your env selector component goes here

", }) export class DefaultEnvSelectorOutletExampleComponent {} export const DefaultContentExample: Story = { render: (args) => ({ props: args, template: "", }), decorators: decorators({ components: [ DefaultPrimaryOutletExampleComponent, DefaultSecondaryOutletExampleComponent, DefaultEnvSelectorOutletExampleComponent, ], routes: [ { path: "**", redirectTo: "default-example", pathMatch: "full", }, { path: "", component: AnonLayoutWrapperComponent, children: [ { path: "default-example", data: {}, children: [ { path: "", component: DefaultPrimaryOutletExampleComponent, }, { path: "", component: DefaultSecondaryOutletExampleComponent, outlet: "secondary", }, { path: "", component: DefaultEnvSelectorOutletExampleComponent, outlet: "environment-selector", }, ], }, ], }, ], }), }; // Dynamic Content Example const initialData: AnonLayoutWrapperData = { pageTitle: "setAStrongPassword", pageSubtitle: "finishCreatingYourAccountBySettingAPassword", pageIcon: LockIcon, }; const changedData: AnonLayoutWrapperData = { pageTitle: "enterpriseSingleSignOn", pageSubtitle: { subtitle: "user@email.com (non-translated)", translate: false, }, pageIcon: RegistrationCheckEmailIcon, }; @Component({ selector: "bit-dynamic-content-example-component", template: ` `, }) export class DynamicContentExampleComponent { initialData = true; constructor(private anonLayoutWrapperDataService: AnonLayoutWrapperDataService) {} toggleData() { if (this.initialData) { this.anonLayoutWrapperDataService.setAnonLayoutWrapperData(changedData); } else { this.anonLayoutWrapperDataService.setAnonLayoutWrapperData(initialData); } this.initialData = !this.initialData; } } export const DynamicContentExample: Story = { render: (args) => ({ props: args, template: "", }), decorators: decorators({ components: [DynamicContentExampleComponent], routes: [ { path: "**", redirectTo: "dynamic-content-example", pathMatch: "full", }, { path: "", component: AnonLayoutWrapperComponent, children: [ { path: "dynamic-content-example", data: initialData, children: [ { path: "", component: DynamicContentExampleComponent, }, ], }, ], }, ], }), };