mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 21:33:27 +00:00
[CL-854] feat: add bit-header component to component library (#17662)
Add new bit-header component to libs/components with: - Header component with left, center, and right content projection - Storybook stories for documentation - Export from component library index
This commit is contained in:
35
libs/components/src/header/header.component.html
Normal file
35
libs/components/src/header/header.component.html
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<header
|
||||||
|
class="-tw-mt-6 -tw-mx-8 tw-mb-3 tw-flex tw-flex-col tw-py-6 tw-px-8 has-[[data-tabs]:not(:empty)]:tw-border-0 has-[[data-tabs]:not(:empty)]:tw-border-b has-[[data-tabs]:not(:empty)]:tw-border-solid has-[[data-tabs]:not(:empty)]:tw-border-secondary-100 has-[[data-tabs]:not(:empty)]:tw-bg-background-alt has-[[data-tabs]:not(:empty)]:tw-pb-0"
|
||||||
|
>
|
||||||
|
<div class="tw-flex">
|
||||||
|
<div class="tw-flex tw-min-w-0 tw-flex-1 tw-flex-col tw-gap-2">
|
||||||
|
<ng-content select="[slot=breadcrumbs]"></ng-content>
|
||||||
|
<h1
|
||||||
|
bitTypography="h1"
|
||||||
|
noMargin
|
||||||
|
class="tw-m-0 tw-mr-2 tw-leading-10 tw-flex tw-gap-1"
|
||||||
|
[title]="title()"
|
||||||
|
>
|
||||||
|
<div class="tw-truncate">
|
||||||
|
@if (icon()) {
|
||||||
|
<i class="bwi {{ icon() }}" aria-hidden="true"></i>
|
||||||
|
}
|
||||||
|
|
||||||
|
{{ title() }}
|
||||||
|
</div>
|
||||||
|
<div><ng-content select="[slot=title-suffix]"></ng-content></div>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="tw-ml-auto tw-flex tw-flex-col tw-gap-4">
|
||||||
|
<div class="tw-flex tw-min-w-max tw-items-center tw-justify-end tw-gap-2">
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</div>
|
||||||
|
<div class="tw-ml-auto empty:tw-hidden">
|
||||||
|
<ng-content select="[slot=secondary]"></ng-content>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div data-tabs class="-tw-mx-4 -tw-mb-px empty:tw-hidden">
|
||||||
|
<ng-content select="[slot=tabs]"></ng-content>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
19
libs/components/src/header/header.component.ts
Normal file
19
libs/components/src/header/header.component.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, input } from "@angular/core";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "bit-header",
|
||||||
|
templateUrl: "./header.component.html",
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
|
})
|
||||||
|
export class HeaderComponent {
|
||||||
|
/**
|
||||||
|
* The title of the page
|
||||||
|
*/
|
||||||
|
readonly title = input.required<string>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon to show before the title
|
||||||
|
*/
|
||||||
|
readonly icon = input<string>();
|
||||||
|
}
|
||||||
189
libs/components/src/header/header.stories.ts
Normal file
189
libs/components/src/header/header.stories.ts
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
import { importProvidersFrom } from "@angular/core";
|
||||||
|
import { RouterModule } from "@angular/router";
|
||||||
|
import {
|
||||||
|
applicationConfig,
|
||||||
|
componentWrapperDecorator,
|
||||||
|
Meta,
|
||||||
|
moduleMetadata,
|
||||||
|
StoryObj,
|
||||||
|
} from "@storybook/angular";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import {
|
||||||
|
AvatarModule,
|
||||||
|
BreadcrumbsModule,
|
||||||
|
ButtonModule,
|
||||||
|
IconButtonModule,
|
||||||
|
IconModule,
|
||||||
|
InputModule,
|
||||||
|
MenuModule,
|
||||||
|
NavigationModule,
|
||||||
|
TabsModule,
|
||||||
|
TypographyModule,
|
||||||
|
} from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { I18nMockService } from "../utils";
|
||||||
|
|
||||||
|
import { HeaderComponent } from "./header.component";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Component Library/Header",
|
||||||
|
component: HeaderComponent,
|
||||||
|
decorators: [
|
||||||
|
componentWrapperDecorator(
|
||||||
|
(story) => `<div class="tw-min-h-screen tw-flex-1 tw-p-6 tw-text-main">${story}</div>`,
|
||||||
|
),
|
||||||
|
moduleMetadata({
|
||||||
|
imports: [
|
||||||
|
HeaderComponent,
|
||||||
|
AvatarModule,
|
||||||
|
BreadcrumbsModule,
|
||||||
|
ButtonModule,
|
||||||
|
IconButtonModule,
|
||||||
|
IconModule,
|
||||||
|
InputModule,
|
||||||
|
MenuModule,
|
||||||
|
NavigationModule,
|
||||||
|
TabsModule,
|
||||||
|
TypographyModule,
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
applicationConfig({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useFactory: () => {
|
||||||
|
return new I18nMockService({
|
||||||
|
moreBreadcrumbs: "More breadcrumbs",
|
||||||
|
loading: "Loading",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importProvidersFrom(
|
||||||
|
RouterModule.forRoot(
|
||||||
|
[
|
||||||
|
{ path: "", redirectTo: "foo", pathMatch: "full" },
|
||||||
|
{ path: "foo", component: HeaderComponent },
|
||||||
|
{ path: "bar", component: HeaderComponent },
|
||||||
|
],
|
||||||
|
{ useHash: true },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<HeaderComponent>;
|
||||||
|
|
||||||
|
export const KitchenSink: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: /*html*/ `
|
||||||
|
<bit-header title="LongTitleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" icon="bwi-bug">
|
||||||
|
<bit-breadcrumbs slot="breadcrumbs">
|
||||||
|
<bit-breadcrumb>Foo</bit-breadcrumb>
|
||||||
|
<bit-breadcrumb>Bar</bit-breadcrumb>
|
||||||
|
</bit-breadcrumbs>
|
||||||
|
<input
|
||||||
|
bitInput
|
||||||
|
placeholder="Ask Jeeves"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<button type="button" bitIconButton="bwi-filter" label="Switch products"></button>
|
||||||
|
<bit-avatar text="Will"></bit-avatar>
|
||||||
|
<button bitButton buttonType="primary">New</button>
|
||||||
|
<button bitButton slot="secondary">Click Me 🎉</button>
|
||||||
|
<bit-tab-nav-bar slot="tabs">
|
||||||
|
<bit-tab-link [route]="['foo']">Foo</bit-tab-link>
|
||||||
|
<bit-tab-link [route]="['bar']">Bar</bit-tab-link>
|
||||||
|
</bit-tab-nav-bar>
|
||||||
|
</bit-header>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Basic: Story = {
|
||||||
|
render: (args: any) => ({
|
||||||
|
props: args,
|
||||||
|
template: /*html*/ `
|
||||||
|
<bit-header title="Foobar" icon="bwi-bug" />
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithLongTitle: Story = {
|
||||||
|
render: (arg: any) => ({
|
||||||
|
props: arg,
|
||||||
|
template: /*html*/ `
|
||||||
|
<bit-header title="LongTitleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" icon="bwi-bug">
|
||||||
|
<ng-container slot="title-suffix"><i class="bwi bwi-key"></i></ng-container>
|
||||||
|
</bit-header>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithBreadcrumbs: Story = {
|
||||||
|
render: (args: any) => ({
|
||||||
|
props: args,
|
||||||
|
template: /*html*/ `
|
||||||
|
<bit-header title="Foobar" icon="bwi-bug" class="tw-text-main">
|
||||||
|
<bit-breadcrumbs slot="breadcrumbs">
|
||||||
|
<bit-breadcrumb>Foo</bit-breadcrumb>
|
||||||
|
<bit-breadcrumb>Bar</bit-breadcrumb>
|
||||||
|
</bit-breadcrumbs>
|
||||||
|
</bit-header>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithSearch: Story = {
|
||||||
|
render: (args: any) => ({
|
||||||
|
props: args,
|
||||||
|
template: /*html*/ `
|
||||||
|
<bit-header title="Foobar" icon="bwi-bug" class="tw-text-main">
|
||||||
|
<input
|
||||||
|
bitInput
|
||||||
|
placeholder="Ask Jeeves"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</bit-header>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithSecondaryContent: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: /*html*/ `
|
||||||
|
<bit-header title="Foobar" icon="bwi-bug" class="tw-text-main">
|
||||||
|
<button bitButton slot="secondary">Click Me 🎉</button>
|
||||||
|
</bit-header>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithTabs: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: /*html*/ `
|
||||||
|
<bit-header title="Foobar" icon="bwi-bug" class="tw-text-main">
|
||||||
|
<bit-tab-nav-bar slot="tabs">
|
||||||
|
<bit-tab-link [route]="['foo']">Foo</bit-tab-link>
|
||||||
|
<bit-tab-link [route]="['bar']">Bar</bit-tab-link>
|
||||||
|
</bit-tab-nav-bar>
|
||||||
|
</bit-header>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithTitleSuffixComponent: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: /*html*/ `
|
||||||
|
<bit-header title="Foobar" icon="bwi-bug" class="tw-text-main">
|
||||||
|
<ng-container slot="title-suffix"><i class="bwi bwi-spinner bwi-spin"></i></ng-container>
|
||||||
|
</bit-header>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
1
libs/components/src/header/index.ts
Normal file
1
libs/components/src/header/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./header.component";
|
||||||
@@ -19,6 +19,7 @@ export * from "./dialog";
|
|||||||
export * from "./disclosure";
|
export * from "./disclosure";
|
||||||
export * from "./drawer";
|
export * from "./drawer";
|
||||||
export * from "./form-field";
|
export * from "./form-field";
|
||||||
|
export * from "./header";
|
||||||
export * from "./icon-button";
|
export * from "./icon-button";
|
||||||
export * from "./icon";
|
export * from "./icon";
|
||||||
export * from "./icon-tile";
|
export * from "./icon-tile";
|
||||||
|
|||||||
Reference in New Issue
Block a user