1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-19 17:53:39 +00:00

[CL-927] anon layout header actions slot (#17796)

* add a slot for consumers to show user actions in anon layout header

* remove commented code

* ensure logo stays top aligned

* switch to dashed naming

* fix ngif statements

* remove empty selector

* remove unnecessary containers

* use smaller logo on smaller screens

* remove commented code from extension layout

* remove dupe slot

* only take extension screenshots on small screens

* take screenshot at 380

* take large and small screenshot

* update story to use new control flow
This commit is contained in:
Bryan Cunningham
2025-12-11 13:56:13 -05:00
committed by GitHub
parent f7d2dd0cd0
commit 4c971c70c0
6 changed files with 78 additions and 41 deletions

View File

@@ -1,31 +1,21 @@
<popup-page [disablePadding]="true">
<popup-header
slot="header"
[background]="'alt'"
[showBackButton]="showBackButton"
[pageTitle]="''"
>
<div class="tw-w-32">
<bit-icon *ngIf="showLogo" [icon]="logo" [ariaLabel]="'appLogoLabel' | i18n"></bit-icon>
</div>
<ng-container slot="end">
<app-pop-out></app-pop-out>
<app-current-account *ngIf="showAcctSwitcher && hasLoggedInAccount"></app-current-account>
</ng-container>
</popup-header>
<auth-anon-layout
[title]="pageTitle"
[subtitle]="pageSubtitle"
[icon]="pageIcon"
[showReadonlyHostname]="showReadonlyHostname"
[hideLogo]="true"
[hideLogo]="!showLogo"
[maxWidth]="maxWidth"
[hideFooter]="hideFooter"
[hideCardWrapper]="hideCardWrapper"
>
<router-outlet></router-outlet>
<div class="tw-flex tw-gap-2" slot="header-actions">
<app-pop-out></app-pop-out>
@if (showAcctSwitcher && hasLoggedInAccount) {
<app-current-account></app-current-account>
}
</div>
<router-outlet slot="secondary" name="secondary"></router-outlet>
<router-outlet slot="environment-selector" name="environment-selector"></router-outlet>
</auth-anon-layout>

View File

@@ -238,6 +238,11 @@ export const DefaultContentExample: Story = {
},
],
}),
parameters: {
chromatic: {
viewports: [380, 1280],
},
},
};
// Dynamic Content Example

View File

@@ -7,6 +7,7 @@
[hideCardWrapper]="hideCardWrapper"
[hideBackgroundIllustration]="hideBackgroundIllustration"
>
<router-outlet slot="header-actions" name="header-actions"></router-outlet>
<router-outlet></router-outlet>
<router-outlet slot="secondary" name="secondary"></router-outlet>
<router-outlet slot="environment-selector" name="environment-selector"></router-outlet>

View File

@@ -130,6 +130,15 @@ export class DefaultSecondaryOutletExampleComponent {}
})
export class DefaultEnvSelectorOutletExampleComponent {}
// 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: "bit-header-actions-outlet-example-component",
template: "<p>Header Actions Outlet Example: <br> your header actions component goes here</p>",
standalone: false,
})
export class DefaultHeaderActionsOutletExampleComponent {}
export const DefaultContentExample: Story = {
render: (args) => ({
props: args,
@@ -171,6 +180,11 @@ export const DefaultContentExample: Story = {
component: DefaultEnvSelectorOutletExampleComponent,
outlet: "environment-selector",
},
{
path: "",
component: DefaultHeaderActionsOutletExampleComponent,
outlet: "header-actions",
},
],
},
],

View File

@@ -5,13 +5,19 @@
'tw-min-h-full': clientType === 'browser' || clientType === 'desktop',
}"
>
<a
*ngIf="!hideLogo()"
[routerLink]="['/']"
class="tw-w-[200px] tw-block tw-mb-12 [&>*]:tw-align-top"
>
<bit-icon [icon]="logo" [ariaLabel]="'appLogoLabel' | i18n"></bit-icon>
</a>
<div class="tw-flex tw-justify-between tw-items-center tw-w-full tw-mb-12">
@if (!hideLogo()) {
<a
[routerLink]="['/']"
class="tw-w-32 sm:tw-w-[200px] tw-self-center sm:tw-self-start tw-block [&>*]:tw-align-top"
>
<bit-icon [icon]="logo" [ariaLabel]="'appLogoLabel' | i18n"></bit-icon>
</a>
}
<div class="tw-ms-auto">
<ng-content select="[slot=header-actions]"></ng-content>
</div>
</div>
<div class="tw-text-center tw-mb-4 sm:tw-mb-6 tw-mx-auto" [ngClass]="maxWidthClass">
@let iconInput = icon();
@@ -25,7 +31,7 @@
<bit-icon [icon]="iconInput"></bit-icon>
</div>
<ng-container *ngIf="title()">
@if (title()) {
<!-- Small screens -->
<h1 bitTypography="h2" class="tw-mt-2 sm:tw-hidden">
{{ title() }}
@@ -34,9 +40,11 @@
<h1 bitTypography="h1" class="tw-mt-2 tw-hidden sm:tw-block">
{{ title() }}
</h1>
</ng-container>
}
<div *ngIf="subtitle()" class="tw-text-sm sm:tw-text-base">{{ subtitle() }}</div>
@if (subtitle()) {
<div class="tw-text-sm sm:tw-text-base">{{ subtitle() }}</div>
}
</div>
<div
@@ -57,18 +65,20 @@
<ng-content select="[slot=secondary]"></ng-content>
</div>
<footer *ngIf="!hideFooter()" class="tw-text-center tw-mt-4 sm:tw-mt-6">
<div *ngIf="showReadonlyHostname()" bitTypography="body2">
{{ "accessing" | i18n }} {{ hostname }}
</div>
<ng-container *ngIf="!showReadonlyHostname()">
<ng-content select="[slot=environment-selector]"></ng-content>
</ng-container>
<ng-container *ngIf="!hideYearAndVersion">
<div bitTypography="body2">&copy; {{ year }} Bitwarden Inc.</div>
<div bitTypography="body2">{{ version }}</div>
</ng-container>
</footer>
@if (!hideFooter()) {
<footer class="tw-text-center tw-mt-4 sm:tw-mt-6">
@if (showReadonlyHostname()) {
<div bitTypography="body2">{{ "accessing" | i18n }} {{ hostname }}</div>
} @else {
<ng-content select="[slot=environment-selector]"></ng-content>
}
@if (!hideYearAndVersion) {
<div bitTypography="body2">&copy; {{ year }} Bitwarden Inc.</div>
<div bitTypography="body2">{{ version }}</div>
}
</footer>
}
@if (!hideBackgroundIllustration()) {
<div

View File

@@ -8,6 +8,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { AvatarModule } from "../avatar";
import { ButtonModule } from "../button";
import { I18nMockService } from "../utils/i18n-mock.service";
@@ -23,6 +24,7 @@ type StoryArgs = AnonLayoutComponent & {
showSecondary: boolean;
useDefaultIcon: boolean;
icon: Icon;
includeHeaderActions: boolean;
};
export default {
@@ -30,7 +32,7 @@ export default {
component: AnonLayoutComponent,
decorators: [
moduleMetadata({
imports: [ButtonModule, RouterModule],
imports: [ButtonModule, RouterModule, AvatarModule],
providers: [
{
provide: PlatformUtilsService,
@@ -76,6 +78,14 @@ export default {
[hideFooter]="hideFooter"
[hideBackgroundIllustration]="hideBackgroundIllustration"
>
@if (includeHeaderActions) {
<div slot="header-actions" class="tw-flex tw-items-center tw-gap-2">
<bit-avatar
size="small"
text="Bob Loblaw"
></bit-avatar>
</div>
}
<ng-container [ngSwitch]="contentLength">
<div *ngSwitchCase="'thin'" class="tw-text-center"> <div class="tw-font-medium">Thin Content</div></div>
<div *ngSwitchCase="'long'">
@@ -116,7 +126,7 @@ export default {
hideLogo: { control: "boolean" },
hideFooter: { control: "boolean" },
hideBackgroundIllustration: { control: "boolean" },
includeHeaderActions: { control: "boolean" },
contentLength: {
control: "radio",
options: ["normal", "long", "thin"],
@@ -138,6 +148,7 @@ export default {
hideBackgroundIllustration: false,
contentLength: "normal",
showSecondary: false,
includeHeaderActions: false,
},
} satisfies Meta<StoryArgs>;
@@ -188,6 +199,12 @@ export const SecondaryContent: Story = {
},
};
export const WithHeaderActions: Story = {
args: {
includeHeaderActions: true,
},
};
export const NoTitle: Story = { args: { title: undefined } };
export const NoSubtitle: Story = { args: { subtitle: undefined } };