[CL-146] Add kitchen sink story (#8310)
This commit is contained in:
parent
f70639d792
commit
0f375c3a0e
|
@ -0,0 +1,176 @@
|
||||||
|
import { Component } from "@angular/core";
|
||||||
|
import { FormBuilder, Validators } from "@angular/forms";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
|
import { DialogService } from "../../../dialog";
|
||||||
|
import { I18nMockService } from "../../../utils/i18n-mock.service";
|
||||||
|
import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: "bit-kitchen-sink-form",
|
||||||
|
imports: [KitchenSinkSharedModule],
|
||||||
|
providers: [
|
||||||
|
DialogService,
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useFactory: () => {
|
||||||
|
return new I18nMockService({
|
||||||
|
close: "Close",
|
||||||
|
checkboxRequired: "Option is required",
|
||||||
|
fieldsNeedAttention: "__$1__ field(s) above need your attention.",
|
||||||
|
inputEmail: "Input is not an email-address.",
|
||||||
|
inputMaxValue: (max) => `Input value must not exceed ${max}.`,
|
||||||
|
inputMinValue: (min) => `Input value must be at least ${min}.`,
|
||||||
|
inputRequired: "Input is required.",
|
||||||
|
multiSelectClearAll: "Clear all",
|
||||||
|
multiSelectLoading: "Retrieving options...",
|
||||||
|
multiSelectNotFound: "No items found",
|
||||||
|
multiSelectPlaceholder: "-- Type to Filter --",
|
||||||
|
required: "required",
|
||||||
|
selectPlaceholder: "-- Select --",
|
||||||
|
toggleVisibility: "Toggle visibility",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
template: `
|
||||||
|
<form [formGroup]="formObj" [bitSubmit]="submit">
|
||||||
|
<div class="tw-mb-6">
|
||||||
|
<bit-progress [barWidth]="50"></bit-progress>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<bit-form-field>
|
||||||
|
<bit-label>Your favorite feature</bit-label>
|
||||||
|
<input bitInput formControlName="favFeature" />
|
||||||
|
</bit-form-field>
|
||||||
|
|
||||||
|
<bit-form-field>
|
||||||
|
<bit-label>Your favorite color</bit-label>
|
||||||
|
<bit-select formControlName="favColor">
|
||||||
|
<bit-option
|
||||||
|
*ngFor="let color of colors"
|
||||||
|
[value]="color.value"
|
||||||
|
[label]="color.name"
|
||||||
|
></bit-option>
|
||||||
|
</bit-select>
|
||||||
|
</bit-form-field>
|
||||||
|
|
||||||
|
<bit-form-field>
|
||||||
|
<bit-label>Your top 3 worst passwords</bit-label>
|
||||||
|
<bit-multi-select formControlName="topWorstPasswords" [baseItems]="worstPasswords">
|
||||||
|
</bit-multi-select>
|
||||||
|
</bit-form-field>
|
||||||
|
|
||||||
|
<bit-form-field>
|
||||||
|
<bit-label>How many passwords do you have?</bit-label>
|
||||||
|
<input bitInput type="number" formControlName="numPasswords" min="0" max="150" />
|
||||||
|
</bit-form-field>
|
||||||
|
|
||||||
|
<bit-form-field>
|
||||||
|
<bit-label>
|
||||||
|
A random password
|
||||||
|
<button
|
||||||
|
bitLink
|
||||||
|
linkType="primary"
|
||||||
|
[bitPopoverTriggerFor]="myPopover"
|
||||||
|
#triggerRef="popoverTrigger"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-question-circle"></i>
|
||||||
|
</button>
|
||||||
|
</bit-label>
|
||||||
|
<input bitInput type="password" formControlName="password" />
|
||||||
|
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||||
|
</bit-form-field>
|
||||||
|
|
||||||
|
<div class="tw-mb-6">
|
||||||
|
<span bitTypography="body1" class="tw-text-main">
|
||||||
|
An example of a strong password:
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<bit-color-password
|
||||||
|
class="tw-text-base"
|
||||||
|
[password]="'Wq$Jk😀7j DX#rS5Sdi!z'"
|
||||||
|
[showCount]="true"
|
||||||
|
></bit-color-password>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<bit-form-control>
|
||||||
|
<bit-label>Check if you love security</bit-label>
|
||||||
|
<input type="checkbox" bitCheckbox formControlName="loveSecurity" />
|
||||||
|
<bit-hint>Hint: the correct answer is yes!</bit-hint>
|
||||||
|
</bit-form-control>
|
||||||
|
|
||||||
|
<bit-radio-group formControlName="current">
|
||||||
|
<bit-label>Do you currently use Bitwarden?</bit-label>
|
||||||
|
<bit-radio-button value="yes">
|
||||||
|
<bit-label>Yes</bit-label>
|
||||||
|
</bit-radio-button>
|
||||||
|
<bit-radio-button value="no">
|
||||||
|
<bit-label>No</bit-label>
|
||||||
|
</bit-radio-button>
|
||||||
|
</bit-radio-group>
|
||||||
|
|
||||||
|
<button bitButton bitFormButton buttonType="primary" type="submit">Submit</button>
|
||||||
|
<bit-error-summary [formGroup]="formObj"></bit-error-summary>
|
||||||
|
|
||||||
|
<bit-popover [title]="'Password help'" #myPopover>
|
||||||
|
<div>A strong password has the following:</div>
|
||||||
|
<ul class="tw-mt-2 tw-mb-0 tw-pl-4">
|
||||||
|
<li>Letters</li>
|
||||||
|
<li>Numbers</li>
|
||||||
|
<li>Special characters</li>
|
||||||
|
</ul>
|
||||||
|
</bit-popover>
|
||||||
|
</form>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class KitchenSinkForm {
|
||||||
|
constructor(
|
||||||
|
public dialogService: DialogService,
|
||||||
|
public formBuilder: FormBuilder,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
formObj = this.formBuilder.group({
|
||||||
|
favFeature: ["", [Validators.required]],
|
||||||
|
favColor: [undefined as string | undefined, [Validators.required]],
|
||||||
|
topWorstPasswords: [undefined as string | undefined],
|
||||||
|
loveSecurity: [false, [Validators.requiredTrue]],
|
||||||
|
current: ["yes"],
|
||||||
|
numPasswords: [null, [Validators.min(0), Validators.max(150)]],
|
||||||
|
password: ["", [Validators.required]],
|
||||||
|
});
|
||||||
|
|
||||||
|
submit = async () => {
|
||||||
|
await this.dialogService.openSimpleDialog({
|
||||||
|
title: "Confirm",
|
||||||
|
content: "Are you sure you want to submit?",
|
||||||
|
type: "primary",
|
||||||
|
acceptButtonText: "Yes",
|
||||||
|
cancelButtonText: "No",
|
||||||
|
acceptAction: async () => this.acceptDialog(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
acceptDialog() {
|
||||||
|
this.formObj.markAllAsTouched();
|
||||||
|
this.dialogService.closeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
colors = [
|
||||||
|
{ value: "blue", name: "Blue" },
|
||||||
|
{ value: "white", name: "White" },
|
||||||
|
{ value: "gray", name: "Gray" },
|
||||||
|
];
|
||||||
|
|
||||||
|
worstPasswords = [
|
||||||
|
{ id: "1", listName: "1234", labelName: "1234" },
|
||||||
|
{ id: "2", listName: "admin", labelName: "admin" },
|
||||||
|
{ id: "3", listName: "password", labelName: "password" },
|
||||||
|
{ id: "4", listName: "querty", labelName: "querty" },
|
||||||
|
{ id: "5", listName: "letmein", labelName: "letmein" },
|
||||||
|
{ id: "6", listName: "trustno1", labelName: "trustno1" },
|
||||||
|
{ id: "7", listName: "1qaz2wsx", labelName: "1qaz2wsx" },
|
||||||
|
];
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
import { DialogRef } from "@angular/cdk/dialog";
|
||||||
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
|
import { DialogService } from "../../../dialog";
|
||||||
|
import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module";
|
||||||
|
|
||||||
|
import { KitchenSinkForm } from "./kitchen-sink-form.component";
|
||||||
|
import { KitchenSinkTable } from "./kitchen-sink-table.component";
|
||||||
|
import { KitchenSinkToggleList } from "./kitchen-sink-toggle-list.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
imports: [KitchenSinkSharedModule],
|
||||||
|
template: `
|
||||||
|
<bit-dialog title="Dialog Title" dialogSize="large">
|
||||||
|
<span bitDialogContent> Dialog body text goes here. </span>
|
||||||
|
<ng-container bitDialogFooter>
|
||||||
|
<button bitButton buttonType="primary" (click)="dialogRef.close()">OK</button>
|
||||||
|
<button bitButton buttonType="secondary" bitDialogClose>Cancel</button>
|
||||||
|
</ng-container>
|
||||||
|
</bit-dialog>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
class KitchenSinkDialog {
|
||||||
|
constructor(public dialogRef: DialogRef) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: "bit-tab-main",
|
||||||
|
imports: [
|
||||||
|
KitchenSinkSharedModule,
|
||||||
|
KitchenSinkTable,
|
||||||
|
KitchenSinkToggleList,
|
||||||
|
KitchenSinkForm,
|
||||||
|
KitchenSinkDialog,
|
||||||
|
],
|
||||||
|
template: `
|
||||||
|
<bit-banner bannerType="info" class="-tw-m-6 tw-flex tw-flex-col tw-pb-6">
|
||||||
|
Kitchen Sink test zone
|
||||||
|
</bit-banner>
|
||||||
|
|
||||||
|
<p class="tw-mt-4">
|
||||||
|
<bit-breadcrumbs>
|
||||||
|
<bit-breadcrumb *ngFor="let item of navItems" [icon]="item.icon" [route]="[item.route]">
|
||||||
|
{{ item.name }}
|
||||||
|
</bit-breadcrumb>
|
||||||
|
</bit-breadcrumbs>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<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
|
||||||
|
run, we'll be able to spot-check visual changes in a more app-like environment than just the
|
||||||
|
isolated stories. The stories for the Kitchen Sink exist to be tested by the Chromatic UI
|
||||||
|
tests.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p bitTypography="body1">
|
||||||
|
NOTE: These stories will treat "Light & Dark" mode as "Light" mode. This is done to avoid a
|
||||||
|
bug with the way that we render the same component twice in the same iframe and how that
|
||||||
|
interacts with the <code>router-outlet</code>.
|
||||||
|
</p>
|
||||||
|
</bit-callout>
|
||||||
|
|
||||||
|
<div class="tw-mb-6 tw-mt-6">
|
||||||
|
<h1 bitTypography="h1" class="tw-text-main">
|
||||||
|
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-text-main tw-mb-6">About</h2>
|
||||||
|
<bit-kitchen-sink-table></bit-kitchen-sink-table>
|
||||||
|
|
||||||
|
<button bitButton (click)="openDefaultDialog()">Open Dialog</button>
|
||||||
|
</bit-section>
|
||||||
|
<bit-section>
|
||||||
|
<h2 bitTypography="h2" class="tw-text-main tw-mb-6">Companies using Bitwarden</h2>
|
||||||
|
<bit-kitchen-sink-toggle-list></bit-kitchen-sink-toggle-list>
|
||||||
|
</bit-section>
|
||||||
|
<bit-section>
|
||||||
|
<h2 bitTypography="h2" class="tw-text-main tw-mb-6">Survey</h2>
|
||||||
|
<bit-kitchen-sink-form></bit-kitchen-sink-form>
|
||||||
|
</bit-section>
|
||||||
|
</bit-tab>
|
||||||
|
|
||||||
|
<bit-tab label="Empty tab" data-testid="empty-tab">
|
||||||
|
<bit-section>
|
||||||
|
<bit-no-items class="tw-text-main">
|
||||||
|
<ng-container slot="title">This tab is empty</ng-container>
|
||||||
|
<ng-container slot="description">
|
||||||
|
<p bitTypography="body2">Try searching for what you are looking for:</p>
|
||||||
|
<bit-search></bit-search>
|
||||||
|
<p bitTypography="helper">Note that the search bar is not functional</p>
|
||||||
|
</ng-container>
|
||||||
|
</bit-no-items>
|
||||||
|
</bit-section>
|
||||||
|
</bit-tab>
|
||||||
|
</bit-tab-group>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class KitchenSinkMainComponent {
|
||||||
|
constructor(public dialogService: DialogService) {}
|
||||||
|
|
||||||
|
openDefaultDialog() {
|
||||||
|
this.dialogService.open(KitchenSinkDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
navItems = [
|
||||||
|
{ icon: "bwi-collection", name: "Password Managers", route: "/" },
|
||||||
|
{ icon: "bwi-collection", name: "Favorites", route: "/" },
|
||||||
|
];
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
|
import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: "bit-kitchen-sink-table",
|
||||||
|
imports: [KitchenSinkSharedModule],
|
||||||
|
template: `
|
||||||
|
<bit-table>
|
||||||
|
<ng-container header>
|
||||||
|
<tr>
|
||||||
|
<th bitCell>Product</th>
|
||||||
|
<th bitCell>User</th>
|
||||||
|
<th bitCell>Options</th>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template body>
|
||||||
|
<tr bitRow>
|
||||||
|
<td bitCell>Password Manager</td>
|
||||||
|
<td bitCell>Everyone</td>
|
||||||
|
<td bitCell>
|
||||||
|
<button bitIconButton="bwi-ellipsis-v" [bitMenuTriggerFor]="menu1"></button>
|
||||||
|
<bit-menu #menu1>
|
||||||
|
<a href="#" bitMenuItem>Anchor link</a>
|
||||||
|
<a href="#" bitMenuItem>Another link</a>
|
||||||
|
<bit-menu-divider></bit-menu-divider>
|
||||||
|
<button type="button" bitMenuItem>Button after divider</button>
|
||||||
|
</bit-menu>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr bitRow>
|
||||||
|
<td bitCell>Secrets Manager</td>
|
||||||
|
<td bitCell>Developers</td>
|
||||||
|
<td bitCell>
|
||||||
|
<button bitIconButton="bwi-ellipsis-v" [bitMenuTriggerFor]="menu2"></button>
|
||||||
|
<bit-menu #menu2>
|
||||||
|
<a href="#" bitMenuItem>Anchor link</a>
|
||||||
|
<a href="#" bitMenuItem>Another link</a>
|
||||||
|
<bit-menu-divider></bit-menu-divider>
|
||||||
|
<button type="button" bitMenuItem>Button after divider</button>
|
||||||
|
</bit-menu>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</bit-table>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class KitchenSinkTable {}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
|
import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: "bit-kitchen-sink-toggle-list",
|
||||||
|
imports: [KitchenSinkSharedModule],
|
||||||
|
template: `
|
||||||
|
<div class="tw-mt-6 tw-mb-6">
|
||||||
|
<bit-toggle-group [(selected)]="selectedToggle" aria-label="Company list filter">
|
||||||
|
<bit-toggle value="all"> All <span bitBadge variant="info">3</span> </bit-toggle>
|
||||||
|
|
||||||
|
<bit-toggle value="large"> Enterprise <span bitBadge variant="info">2</span> </bit-toggle>
|
||||||
|
|
||||||
|
<bit-toggle value="small"> Mid-sized <span bitBadge variant="info">1</span> </bit-toggle>
|
||||||
|
</bit-toggle-group>
|
||||||
|
</div>
|
||||||
|
<ul *ngFor="let company of companyList">
|
||||||
|
<li *ngIf="company.size === selectedToggle || selectedToggle === 'all'">
|
||||||
|
{{ company.name }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class KitchenSinkToggleList {
|
||||||
|
selectedToggle: "all" | "large" | "small" = "all";
|
||||||
|
|
||||||
|
companyList = [
|
||||||
|
{ name: "A large enterprise company", size: "large" },
|
||||||
|
{ name: "Another enterprise company", size: "large" },
|
||||||
|
{ name: "A smaller company", size: "small" },
|
||||||
|
];
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./kitchen-sink.stories";
|
|
@ -0,0 +1,114 @@
|
||||||
|
import { CommonModule } from "@angular/common";
|
||||||
|
import { NgModule } from "@angular/core";
|
||||||
|
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||||
|
import { RouterModule } from "@angular/router";
|
||||||
|
|
||||||
|
import { AsyncActionsModule } from "../../async-actions";
|
||||||
|
import { AvatarModule } from "../../avatar";
|
||||||
|
import { BadgeModule } from "../../badge";
|
||||||
|
import { BannerModule } from "../../banner";
|
||||||
|
import { BreadcrumbsModule } from "../../breadcrumbs";
|
||||||
|
import { ButtonModule } from "../../button";
|
||||||
|
import { CalloutModule } from "../../callout";
|
||||||
|
import { CheckboxModule } from "../../checkbox";
|
||||||
|
import { ColorPasswordModule } from "../../color-password";
|
||||||
|
import { DialogModule } from "../../dialog";
|
||||||
|
import { FormControlModule } from "../../form-control";
|
||||||
|
import { FormFieldModule } from "../../form-field";
|
||||||
|
import { IconModule } from "../../icon";
|
||||||
|
import { IconButtonModule } from "../../icon-button";
|
||||||
|
import { InputModule } from "../../input";
|
||||||
|
import { LayoutComponent } from "../../layout";
|
||||||
|
import { LinkModule } from "../../link";
|
||||||
|
import { MenuModule } from "../../menu";
|
||||||
|
import { NavigationModule } from "../../navigation";
|
||||||
|
import { NoItemsModule } from "../../no-items";
|
||||||
|
import { PopoverModule } from "../../popover";
|
||||||
|
import { ProgressModule } from "../../progress";
|
||||||
|
import { RadioButtonModule } from "../../radio-button";
|
||||||
|
import { SearchModule } from "../../search";
|
||||||
|
import { SectionComponent } from "../../section";
|
||||||
|
import { SelectModule } from "../../select";
|
||||||
|
import { SharedModule } from "../../shared";
|
||||||
|
import { TableModule } from "../../table";
|
||||||
|
import { TabsModule } from "../../tabs";
|
||||||
|
import { ToggleGroupModule } from "../../toggle-group";
|
||||||
|
import { TypographyModule } from "../../typography";
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
AsyncActionsModule,
|
||||||
|
AvatarModule,
|
||||||
|
BadgeModule,
|
||||||
|
BannerModule,
|
||||||
|
BreadcrumbsModule,
|
||||||
|
ButtonModule,
|
||||||
|
CalloutModule,
|
||||||
|
CheckboxModule,
|
||||||
|
ColorPasswordModule,
|
||||||
|
CommonModule,
|
||||||
|
DialogModule,
|
||||||
|
FormControlModule,
|
||||||
|
FormFieldModule,
|
||||||
|
FormsModule,
|
||||||
|
IconButtonModule,
|
||||||
|
IconModule,
|
||||||
|
InputModule,
|
||||||
|
LayoutComponent,
|
||||||
|
LinkModule,
|
||||||
|
MenuModule,
|
||||||
|
NavigationModule,
|
||||||
|
NoItemsModule,
|
||||||
|
PopoverModule,
|
||||||
|
ProgressModule,
|
||||||
|
RadioButtonModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
RouterModule,
|
||||||
|
SearchModule,
|
||||||
|
SectionComponent,
|
||||||
|
SelectModule,
|
||||||
|
SharedModule,
|
||||||
|
TableModule,
|
||||||
|
TabsModule,
|
||||||
|
ToggleGroupModule,
|
||||||
|
TypographyModule,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
AsyncActionsModule,
|
||||||
|
AvatarModule,
|
||||||
|
BadgeModule,
|
||||||
|
BannerModule,
|
||||||
|
BreadcrumbsModule,
|
||||||
|
ButtonModule,
|
||||||
|
CalloutModule,
|
||||||
|
CheckboxModule,
|
||||||
|
ColorPasswordModule,
|
||||||
|
CommonModule,
|
||||||
|
DialogModule,
|
||||||
|
FormControlModule,
|
||||||
|
FormFieldModule,
|
||||||
|
FormsModule,
|
||||||
|
IconButtonModule,
|
||||||
|
IconModule,
|
||||||
|
InputModule,
|
||||||
|
LayoutComponent,
|
||||||
|
LinkModule,
|
||||||
|
MenuModule,
|
||||||
|
NavigationModule,
|
||||||
|
NoItemsModule,
|
||||||
|
PopoverModule,
|
||||||
|
ProgressModule,
|
||||||
|
RadioButtonModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
RouterModule,
|
||||||
|
SearchModule,
|
||||||
|
SectionComponent,
|
||||||
|
SelectModule,
|
||||||
|
SharedModule,
|
||||||
|
TableModule,
|
||||||
|
TabsModule,
|
||||||
|
ToggleGroupModule,
|
||||||
|
TypographyModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class KitchenSinkSharedModule {}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Meta, Story } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import * as stories from "./kitchen-sink.stories";
|
||||||
|
|
||||||
|
<Meta of={stories} />
|
||||||
|
|
||||||
|
# Kitchen Sink
|
||||||
|
|
||||||
|
The purpose of this story is to compose together all of our components. When snapshot tests run,
|
||||||
|
we'll be able to spot-check visual changes in a more app-like environment than just the isolated
|
||||||
|
stories. The stories for the Kitchen Sink exist to be tested by the Chromatic UI tests.
|
||||||
|
|
||||||
|
NOTE: These stories will treat "Light & Dark" mode as "Light" mode. This is done to 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`.
|
|
@ -0,0 +1,172 @@
|
||||||
|
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 {
|
||||||
|
userEvent,
|
||||||
|
getAllByRole,
|
||||||
|
getByRole,
|
||||||
|
getByLabelText,
|
||||||
|
fireEvent,
|
||||||
|
} from "@storybook/testing-library";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
|
import { DialogService } from "../../dialog";
|
||||||
|
import { LayoutComponent } from "../../layout";
|
||||||
|
import { I18nMockService } from "../../utils/i18n-mock.service";
|
||||||
|
|
||||||
|
import { KitchenSinkForm } from "./components/kitchen-sink-form.component";
|
||||||
|
import { KitchenSinkMainComponent } from "./components/kitchen-sink-main.component";
|
||||||
|
import { KitchenSinkTable } from "./components/kitchen-sink-table.component";
|
||||||
|
import { KitchenSinkToggleList } from "./components/kitchen-sink-toggle-list.component";
|
||||||
|
import { KitchenSinkSharedModule } from "./kitchen-sink-shared.module";
|
||||||
|
|
||||||
|
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 };
|
||||||
|
},
|
||||||
|
),
|
||||||
|
moduleMetadata({
|
||||||
|
imports: [
|
||||||
|
KitchenSinkSharedModule,
|
||||||
|
KitchenSinkForm,
|
||||||
|
KitchenSinkMainComponent,
|
||||||
|
KitchenSinkTable,
|
||||||
|
KitchenSinkToggleList,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
DialogService,
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useFactory: () => {
|
||||||
|
return new I18nMockService({
|
||||||
|
close: "Close",
|
||||||
|
search: "Search",
|
||||||
|
skipToContent: "Skip to content",
|
||||||
|
submenu: "submenu",
|
||||||
|
toggleCollapse: "toggle collapse",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
applicationConfig({
|
||||||
|
providers: [
|
||||||
|
provideNoopAnimations(),
|
||||||
|
importProvidersFrom(
|
||||||
|
RouterModule.forRoot(
|
||||||
|
[
|
||||||
|
{ path: "", redirectTo: "bitwarden", pathMatch: "full" },
|
||||||
|
{ path: "bitwarden", component: KitchenSinkMainComponent },
|
||||||
|
],
|
||||||
|
{ useHash: true },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<LayoutComponent>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: (args) => {
|
||||||
|
return {
|
||||||
|
props: args,
|
||||||
|
template: /* HTML */ `<bit-layout>
|
||||||
|
<nav slot="sidebar">
|
||||||
|
<bit-nav-group text="Password Managers" icon="bwi-collection" [open]="true">
|
||||||
|
<bit-nav-group text="Favorites" icon="bwi-collection" variant="tree" [open]="true">
|
||||||
|
<bit-nav-item text="Bitwarden" route="bitwarden"></bit-nav-item>
|
||||||
|
<bit-nav-divider></bit-nav-divider>
|
||||||
|
</bit-nav-group>
|
||||||
|
</bit-nav-group>
|
||||||
|
</nav>
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</bit-layout>`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MenuOpen: Story = {
|
||||||
|
...Default,
|
||||||
|
play: async (context) => {
|
||||||
|
const canvas = context.canvasElement;
|
||||||
|
const table = getByRole(canvas, "table");
|
||||||
|
|
||||||
|
const menuButton = getAllByRole(table, "button")[0];
|
||||||
|
await userEvent.click(menuButton);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DefaultDialogOpen: Story = {
|
||||||
|
...Default,
|
||||||
|
play: (context) => {
|
||||||
|
const canvas = context.canvasElement;
|
||||||
|
const dialogButton = getByRole(canvas, "button", {
|
||||||
|
name: "Open Dialog",
|
||||||
|
});
|
||||||
|
|
||||||
|
// workaround for userEvent not firing in FF https://github.com/testing-library/user-event/issues/1075
|
||||||
|
fireEvent.click(dialogButton);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PopoverOpen: Story = {
|
||||||
|
...Default,
|
||||||
|
play: async (context) => {
|
||||||
|
const canvas = context.canvasElement;
|
||||||
|
const passwordLabelIcon = getByLabelText(canvas, "A random password (required)", {
|
||||||
|
selector: "button",
|
||||||
|
});
|
||||||
|
|
||||||
|
await userEvent.click(passwordLabelIcon);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SimpleDialogOpen: Story = {
|
||||||
|
...Default,
|
||||||
|
play: (context) => {
|
||||||
|
const canvas = context.canvasElement;
|
||||||
|
const submitButton = getByRole(canvas, "button", {
|
||||||
|
name: "Submit",
|
||||||
|
});
|
||||||
|
|
||||||
|
// workaround for userEvent not firing in FF https://github.com/testing-library/user-event/issues/1075
|
||||||
|
fireEvent.click(submitButton);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EmptyTab: Story = {
|
||||||
|
...Default,
|
||||||
|
play: async (context) => {
|
||||||
|
const canvas = context.canvasElement;
|
||||||
|
const emptyTab = getByRole(canvas, "tab", { name: "Empty tab" });
|
||||||
|
await userEvent.click(emptyTab);
|
||||||
|
},
|
||||||
|
};
|
Loading…
Reference in New Issue