1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 16:23:44 +00:00

[CL-428] create drawer component (#12812)

* remove private/protected/lifecycle fields from Storybook docs table

* move theme override decorator into util method

* implement base drawer component

* update bit-layout to be drawer container

* create drawer helper components

* expose new APIs to DS barrel file

* write docs

* update docs; add role input

* use host directive instead of service

* clean up logic a tad

* add start slot to story

* update docs

* Apply suggestions from code review

Co-authored-by: Victoria League <vleague@bitwarden.com>

* update docs

* Update libs/components/src/drawer/drawer.mdx

Co-authored-by: Victoria League <vleague@bitwarden.com>

* update docs / stories

* add non text element to drawer

---------

Co-authored-by: Victoria League <vleague@bitwarden.com>
This commit is contained in:
Will Martin
2025-01-16 15:43:04 -05:00
committed by GitHub
parent 3917f50fdd
commit ea052b9e07
23 changed files with 634 additions and 55 deletions

View File

@@ -1,5 +1,5 @@
import { DialogRef } from "@angular/cdk/dialog";
import { Component } from "@angular/core";
import { Component, signal } from "@angular/core";
import { DialogService } from "../../../dialog";
import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module";
@@ -28,13 +28,7 @@ class KitchenSinkDialog {
@Component({
standalone: true,
selector: "bit-tab-main",
imports: [
KitchenSinkSharedModule,
KitchenSinkTable,
KitchenSinkToggleList,
KitchenSinkForm,
KitchenSinkDialog,
],
imports: [KitchenSinkSharedModule, KitchenSinkTable, KitchenSinkToggleList, KitchenSinkForm],
template: `
<bit-banner bannerType="info" class="-tw-m-6 tw-flex tw-flex-col tw-pb-6">
Kitchen Sink test zone
@@ -48,6 +42,11 @@ class KitchenSinkDialog {
</bit-breadcrumbs>
</p>
<div class="tw-mb-6 tw-mt-6">
<h1 bitTypography="h1">Bitwarden Kitchen Sink<bit-avatar text="Bit Warden"></bit-avatar></h1>
<a bitLink linkType="primary" href="#">Learn more</a>
</div>
<bit-callout type="info" title="About the Kitchen Sink">
<p bitTypography="body1">
The purpose of this story is to compose together all of our components. When snapshot tests
@@ -63,18 +62,14 @@ class KitchenSinkDialog {
</p>
</bit-callout>
<div class="tw-mb-6 tw-mt-6">
<h1 bitTypography="h1">Bitwarden <bit-avatar text="Bit Warden"></bit-avatar></h1>
<a bitLink linkType="primary" href="#">Learn more</a>
</div>
<bit-tab-group label="Main content tabs" class="tw-text-main">
<bit-tab label="Evaluation">
<bit-section>
<h2 bitTypography="h2" class="tw-mb-6">About</h2>
<bit-kitchen-sink-table></bit-kitchen-sink-table>
<button bitButton (click)="openDefaultDialog()">Open Dialog</button>
<button bitButton (click)="openDialog()">Open Dialog</button>
<button bitButton (click)="openDrawer()">Open Drawer</button>
</bit-section>
<bit-section>
<h2 bitTypography="h2" class="tw-mb-6">Companies using Bitwarden</h2>
@@ -99,15 +94,87 @@ class KitchenSinkDialog {
</bit-section>
</bit-tab>
</bit-tab-group>
<bit-drawer [(open)]="drawerOpen">
<bit-drawer-header title="Foo ipsum"></bit-drawer-header>
<bit-drawer-body>
<p bitTypography="body1">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<bit-form-field>
<bit-label>What did foo say to bar?</bit-label>
<input bitInput value="Baz" />
</bit-form-field>
<p bitTypography="body1">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<p bitTypography="body1">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<p bitTypography="body1">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<p bitTypography="body1">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<p bitTypography="body1">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<p bitTypography="body1">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
</bit-drawer-body>
</bit-drawer>
`,
})
export class KitchenSinkMainComponent {
constructor(public dialogService: DialogService) {}
openDefaultDialog() {
protected drawerOpen = signal(false);
openDialog() {
this.dialogService.open(KitchenSinkDialog);
}
openDrawer() {
this.drawerOpen.set(true);
}
navItems = [
{ icon: "bwi-collection", name: "Password Managers", route: "/" },
{ icon: "bwi-collection", name: "Favorites", route: "/" },

View File

@@ -13,6 +13,7 @@ import { CalloutModule } from "../../callout";
import { CheckboxModule } from "../../checkbox";
import { ColorPasswordModule } from "../../color-password";
import { DialogModule } from "../../dialog";
import { DrawerModule } from "../../drawer";
import { FormControlModule } from "../../form-control";
import { FormFieldModule } from "../../form-field";
import { IconModule } from "../../icon";
@@ -48,6 +49,7 @@ import { TypographyModule } from "../../typography";
ColorPasswordModule,
CommonModule,
DialogModule,
DrawerModule,
FormControlModule,
FormFieldModule,
FormsModule,
@@ -85,6 +87,7 @@ import { TypographyModule } from "../../typography";
ColorPasswordModule,
CommonModule,
DialogModule,
DrawerModule,
FormControlModule,
FormFieldModule,
FormsModule,

View File

@@ -1,13 +1,7 @@
import { importProvidersFrom } from "@angular/core";
import { provideNoopAnimations } from "@angular/platform-browser/animations";
import { RouterModule } from "@angular/router";
import {
Meta,
StoryObj,
applicationConfig,
componentWrapperDecorator,
moduleMetadata,
} from "@storybook/angular";
import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular";
import {
userEvent,
getAllByRole,
@@ -23,6 +17,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { DialogService } from "../../dialog";
import { LayoutComponent } from "../../layout";
import { I18nMockService } from "../../utils/i18n-mock.service";
import { disableBothThemeDecorator, positionFixedWrapperDecorator } from "../storybook-decorators";
import { DialogVirtualScrollBlockComponent } from "./components/dialog-virtual-scroll-block.component";
import { KitchenSinkForm } from "./components/kitchen-sink-form.component";
@@ -35,25 +30,8 @@ export default {
title: "Documentation / Kitchen Sink",
component: LayoutComponent,
decorators: [
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 */ `<div class="tw-scale-100 tw-border-2 tw-border-solid tw-border-[red]">
${story}
</div>`;
},
({ 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 };
},
),
positionFixedWrapperDecorator(),
disableBothThemeDecorator,
moduleMetadata({
imports: [
KitchenSinkSharedModule,
@@ -135,7 +113,7 @@ export const MenuOpen: Story = {
},
};
export const DefaultDialogOpen: Story = {
export const DialogOpen: Story = {
...Default,
play: async (context) => {
const canvas = context.canvasElement;
@@ -148,6 +126,19 @@ export const DefaultDialogOpen: Story = {
},
};
export const DrawerOpen: Story = {
...Default,
play: async (context) => {
const canvas = context.canvasElement;
const drawerButton = getByRole(canvas, "button", {
name: "Open Drawer",
});
// workaround for userEvent not firing in FF https://github.com/testing-library/user-event/issues/1075
await fireEvent.click(drawerButton);
},
};
export const PopoverOpen: Story = {
...Default,
play: async (context) => {

View File

@@ -0,0 +1,31 @@
import { componentWrapperDecorator } from "@storybook/angular";
/**
* Render a story that uses `position: fixed`
* Used in layout and navigation components
**/
export const positionFixedWrapperDecorator = (wrapper?: (story: string) => string) =>
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) =>
/* HTML */ `<div
class="tw-scale-100 tw-h-screen tw-border-2 tw-border-solid tw-border-secondary-300 tw-overflow-auto"
>
${wrapper ? wrapper(story) : story}
</div>`,
);
export const disableBothThemeDecorator = componentWrapperDecorator(
(story) => 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 };
},
);