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:
@@ -1,31 +1,21 @@
|
|||||||
<popup-page [disablePadding]="true">
|
<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
|
<auth-anon-layout
|
||||||
[title]="pageTitle"
|
[title]="pageTitle"
|
||||||
[subtitle]="pageSubtitle"
|
[subtitle]="pageSubtitle"
|
||||||
[icon]="pageIcon"
|
[icon]="pageIcon"
|
||||||
[showReadonlyHostname]="showReadonlyHostname"
|
[showReadonlyHostname]="showReadonlyHostname"
|
||||||
[hideLogo]="true"
|
[hideLogo]="!showLogo"
|
||||||
[maxWidth]="maxWidth"
|
[maxWidth]="maxWidth"
|
||||||
[hideFooter]="hideFooter"
|
[hideFooter]="hideFooter"
|
||||||
[hideCardWrapper]="hideCardWrapper"
|
[hideCardWrapper]="hideCardWrapper"
|
||||||
>
|
>
|
||||||
<router-outlet></router-outlet>
|
<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="secondary" name="secondary"></router-outlet>
|
||||||
<router-outlet slot="environment-selector" name="environment-selector"></router-outlet>
|
<router-outlet slot="environment-selector" name="environment-selector"></router-outlet>
|
||||||
</auth-anon-layout>
|
</auth-anon-layout>
|
||||||
|
|||||||
@@ -238,6 +238,11 @@ export const DefaultContentExample: Story = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
parameters: {
|
||||||
|
chromatic: {
|
||||||
|
viewports: [380, 1280],
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Dynamic Content Example
|
// Dynamic Content Example
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
[hideCardWrapper]="hideCardWrapper"
|
[hideCardWrapper]="hideCardWrapper"
|
||||||
[hideBackgroundIllustration]="hideBackgroundIllustration"
|
[hideBackgroundIllustration]="hideBackgroundIllustration"
|
||||||
>
|
>
|
||||||
|
<router-outlet slot="header-actions" name="header-actions"></router-outlet>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
<router-outlet slot="secondary" name="secondary"></router-outlet>
|
<router-outlet slot="secondary" name="secondary"></router-outlet>
|
||||||
<router-outlet slot="environment-selector" name="environment-selector"></router-outlet>
|
<router-outlet slot="environment-selector" name="environment-selector"></router-outlet>
|
||||||
|
|||||||
@@ -130,6 +130,15 @@ export class DefaultSecondaryOutletExampleComponent {}
|
|||||||
})
|
})
|
||||||
export class DefaultEnvSelectorOutletExampleComponent {}
|
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 = {
|
export const DefaultContentExample: Story = {
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
@@ -171,6 +180,11 @@ export const DefaultContentExample: Story = {
|
|||||||
component: DefaultEnvSelectorOutletExampleComponent,
|
component: DefaultEnvSelectorOutletExampleComponent,
|
||||||
outlet: "environment-selector",
|
outlet: "environment-selector",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: DefaultHeaderActionsOutletExampleComponent,
|
||||||
|
outlet: "header-actions",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -5,13 +5,19 @@
|
|||||||
'tw-min-h-full': clientType === 'browser' || clientType === 'desktop',
|
'tw-min-h-full': clientType === 'browser' || clientType === 'desktop',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<a
|
<div class="tw-flex tw-justify-between tw-items-center tw-w-full tw-mb-12">
|
||||||
*ngIf="!hideLogo()"
|
@if (!hideLogo()) {
|
||||||
[routerLink]="['/']"
|
<a
|
||||||
class="tw-w-[200px] tw-block tw-mb-12 [&>*]:tw-align-top"
|
[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>
|
<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">
|
<div class="tw-text-center tw-mb-4 sm:tw-mb-6 tw-mx-auto" [ngClass]="maxWidthClass">
|
||||||
@let iconInput = icon();
|
@let iconInput = icon();
|
||||||
@@ -25,7 +31,7 @@
|
|||||||
<bit-icon [icon]="iconInput"></bit-icon>
|
<bit-icon [icon]="iconInput"></bit-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="title()">
|
@if (title()) {
|
||||||
<!-- Small screens -->
|
<!-- Small screens -->
|
||||||
<h1 bitTypography="h2" class="tw-mt-2 sm:tw-hidden">
|
<h1 bitTypography="h2" class="tw-mt-2 sm:tw-hidden">
|
||||||
{{ title() }}
|
{{ title() }}
|
||||||
@@ -34,9 +40,11 @@
|
|||||||
<h1 bitTypography="h1" class="tw-mt-2 tw-hidden sm:tw-block">
|
<h1 bitTypography="h1" class="tw-mt-2 tw-hidden sm:tw-block">
|
||||||
{{ title() }}
|
{{ title() }}
|
||||||
</h1>
|
</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>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -57,18 +65,20 @@
|
|||||||
<ng-content select="[slot=secondary]"></ng-content>
|
<ng-content select="[slot=secondary]"></ng-content>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer *ngIf="!hideFooter()" class="tw-text-center tw-mt-4 sm:tw-mt-6">
|
@if (!hideFooter()) {
|
||||||
<div *ngIf="showReadonlyHostname()" bitTypography="body2">
|
<footer class="tw-text-center tw-mt-4 sm:tw-mt-6">
|
||||||
{{ "accessing" | i18n }} {{ hostname }}
|
@if (showReadonlyHostname()) {
|
||||||
</div>
|
<div bitTypography="body2">{{ "accessing" | i18n }} {{ hostname }}</div>
|
||||||
<ng-container *ngIf="!showReadonlyHostname()">
|
} @else {
|
||||||
<ng-content select="[slot=environment-selector]"></ng-content>
|
<ng-content select="[slot=environment-selector]"></ng-content>
|
||||||
</ng-container>
|
}
|
||||||
<ng-container *ngIf="!hideYearAndVersion">
|
|
||||||
<div bitTypography="body2">© {{ year }} Bitwarden Inc.</div>
|
@if (!hideYearAndVersion) {
|
||||||
<div bitTypography="body2">{{ version }}</div>
|
<div bitTypography="body2">© {{ year }} Bitwarden Inc.</div>
|
||||||
</ng-container>
|
<div bitTypography="body2">{{ version }}</div>
|
||||||
</footer>
|
}
|
||||||
|
</footer>
|
||||||
|
}
|
||||||
|
|
||||||
@if (!hideBackgroundIllustration()) {
|
@if (!hideBackgroundIllustration()) {
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
|
|||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
|
||||||
|
import { AvatarModule } from "../avatar";
|
||||||
import { ButtonModule } from "../button";
|
import { ButtonModule } from "../button";
|
||||||
import { I18nMockService } from "../utils/i18n-mock.service";
|
import { I18nMockService } from "../utils/i18n-mock.service";
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ type StoryArgs = AnonLayoutComponent & {
|
|||||||
showSecondary: boolean;
|
showSecondary: boolean;
|
||||||
useDefaultIcon: boolean;
|
useDefaultIcon: boolean;
|
||||||
icon: Icon;
|
icon: Icon;
|
||||||
|
includeHeaderActions: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -30,7 +32,7 @@ export default {
|
|||||||
component: AnonLayoutComponent,
|
component: AnonLayoutComponent,
|
||||||
decorators: [
|
decorators: [
|
||||||
moduleMetadata({
|
moduleMetadata({
|
||||||
imports: [ButtonModule, RouterModule],
|
imports: [ButtonModule, RouterModule, AvatarModule],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: PlatformUtilsService,
|
provide: PlatformUtilsService,
|
||||||
@@ -76,6 +78,14 @@ export default {
|
|||||||
[hideFooter]="hideFooter"
|
[hideFooter]="hideFooter"
|
||||||
[hideBackgroundIllustration]="hideBackgroundIllustration"
|
[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">
|
<ng-container [ngSwitch]="contentLength">
|
||||||
<div *ngSwitchCase="'thin'" class="tw-text-center"> <div class="tw-font-medium">Thin Content</div></div>
|
<div *ngSwitchCase="'thin'" class="tw-text-center"> <div class="tw-font-medium">Thin Content</div></div>
|
||||||
<div *ngSwitchCase="'long'">
|
<div *ngSwitchCase="'long'">
|
||||||
@@ -116,7 +126,7 @@ export default {
|
|||||||
hideLogo: { control: "boolean" },
|
hideLogo: { control: "boolean" },
|
||||||
hideFooter: { control: "boolean" },
|
hideFooter: { control: "boolean" },
|
||||||
hideBackgroundIllustration: { control: "boolean" },
|
hideBackgroundIllustration: { control: "boolean" },
|
||||||
|
includeHeaderActions: { control: "boolean" },
|
||||||
contentLength: {
|
contentLength: {
|
||||||
control: "radio",
|
control: "radio",
|
||||||
options: ["normal", "long", "thin"],
|
options: ["normal", "long", "thin"],
|
||||||
@@ -138,6 +148,7 @@ export default {
|
|||||||
hideBackgroundIllustration: false,
|
hideBackgroundIllustration: false,
|
||||||
contentLength: "normal",
|
contentLength: "normal",
|
||||||
showSecondary: false,
|
showSecondary: false,
|
||||||
|
includeHeaderActions: false,
|
||||||
},
|
},
|
||||||
} satisfies Meta<StoryArgs>;
|
} 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 NoTitle: Story = { args: { title: undefined } };
|
||||||
|
|
||||||
export const NoSubtitle: Story = { args: { subtitle: undefined } };
|
export const NoSubtitle: Story = { args: { subtitle: undefined } };
|
||||||
|
|||||||
Reference in New Issue
Block a user