1
0
mirror of https://github.com/bitwarden/jslib synced 2025-12-06 00:03:29 +00:00

Play with storybook

This commit is contained in:
Matt Gibson
2022-05-31 10:39:42 -04:00
parent 0d658ba26d
commit 2be777699f
16 changed files with 340 additions and 0 deletions

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"jest.jestCommandLine": "npm run test --"
}

View File

@@ -22,6 +22,9 @@ export class ExportComponent implements OnInit {
exportForm = this.formBuilder.group({
format: ["json"],
secret: [""],
passwordProtected: [false],
filePassword: [""],
confirmFilePassword: [""],
});
formatOptions = [

View File

@@ -0,0 +1,7 @@
export abstract class FormFieldControl<T> {
abstract value: T | null;
abstract readonly id: string;
abstract readonly required: boolean;
abstract readonly disabled: boolean;
abstract readonly inError: boolean;
}

View File

@@ -0,0 +1,18 @@
<div class="tw-mb-3.5">
<label [attr.for]="input.id" class="tw-inline-block tw-text-main tw-font-semibold">
<ng-content select="bit-label"></ng-content>
<small
class="tw-block tw-text-muted"
aria-hidden="true"
*ngIf="input.required && !input.disabled"
>(required)</small
>
</label>
<div class="tw-flex">
<ng-content></ng-content>
<div *ngSwitch="displayedMessage">
<small class="" *ngSwitchCase="'desc'"></small>
<small *ngSwitchCase="'error'"></small>
</div>
</div>
</div>

View File

@@ -0,0 +1,77 @@
// import { Component, DebugElement } from "@angular/core";
// import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
// import { By } from "@angular/platform-browser";
// import { FormFieldModule } from "./index";
// describe("Button", () => {
// let fixture: ComponentFixture<TestApp>;
// let testAppComponent: TestApp;
// let buttonDebugElement: DebugElement;
// let linkDebugElement: DebugElement;
// beforeEach(
// waitForAsync(() => {
// TestBed.configureTestingModule({
// imports: [FormFieldModule],
// declarations: [TestApp],
// });
// TestBed.compileComponents();
// fixture = TestBed.createComponent(TestApp);
// testAppComponent = fixture.debugElement.componentInstance;
// buttonDebugElement = fixture.debugElement.query(By.css("button"));
// linkDebugElement = fixture.debugElement.query(By.css("a"));
// })
// );
// it("should apply classes based on type", () => {
// testAppComponent.buttonType = "primary";
// fixture.detectChanges();
// expect(buttonDebugElement.nativeElement.classList.contains("tw-bg-primary-500")).toBe(true);
// expect(linkDebugElement.nativeElement.classList.contains("tw-bg-primary-500")).toBe(true);
// testAppComponent.buttonType = "secondary";
// fixture.detectChanges();
// expect(buttonDebugElement.nativeElement.classList.contains("tw-border-text-muted")).toBe(true);
// expect(linkDebugElement.nativeElement.classList.contains("tw-border-text-muted")).toBe(true);
// testAppComponent.buttonType = "danger";
// fixture.detectChanges();
// expect(buttonDebugElement.nativeElement.classList.contains("tw-border-danger-500")).toBe(true);
// expect(linkDebugElement.nativeElement.classList.contains("tw-border-danger-500")).toBe(true);
// testAppComponent.buttonType = null;
// fixture.detectChanges();
// expect(buttonDebugElement.nativeElement.classList.contains("tw-border-text-muted")).toBe(true);
// expect(linkDebugElement.nativeElement.classList.contains("tw-border-text-muted")).toBe(true);
// });
// it("should apply block when true and inline-block when false", () => {
// testAppComponent.block = true;
// fixture.detectChanges();
// expect(buttonDebugElement.nativeElement.classList.contains("tw-block")).toBe(true);
// expect(linkDebugElement.nativeElement.classList.contains("tw-block")).toBe(true);
// expect(buttonDebugElement.nativeElement.classList.contains("tw-inline-block")).toBe(false);
// expect(linkDebugElement.nativeElement.classList.contains("tw-inline-block")).toBe(false);
// testAppComponent.block = false;
// fixture.detectChanges();
// expect(buttonDebugElement.nativeElement.classList.contains("tw-inline-block")).toBe(true);
// expect(linkDebugElement.nativeElement.classList.contains("tw-inline-block")).toBe(true);
// expect(buttonDebugElement.nativeElement.classList.contains("tw-block")).toBe(false);
// expect(linkDebugElement.nativeElement.classList.contains("tw-block")).toBe(false);
// });
// });
// @Component({
// selector: "test-app",
// template: `
// <button type="button" bit-button [buttonType]="buttonType" [block]="block">Button</button>
// <a href="#" bit-button [buttonType]="buttonType" [block]="block"> Link </a>
// `,
// })
// class TestApp {
// buttonType: string;
// block: boolean;
// }

View File

@@ -0,0 +1,36 @@
import { Component, ContentChild, ContentChildren, OnInit, QueryList } from "@angular/core";
import { FormFieldControl } from "./form-field-control";
import { BitHint } from "./hint";
import { BitLabel } from "./label";
import { BitSuffix } from "./suffix";
@Component({
selector: "bit-form-field",
templateUrl: "form-field.component.html",
})
export class FormFieldComponent implements OnInit {
// @ViewChild("inputContainer") _inputContainerRef: ElementRef;
// @ViewChild("bit-label") private _label: ElementRef<HTMLElement>;
@ContentChild(BitLabel) label: BitLabel;
@ContentChildren(BitHint, { descendants: true }) hintChildren: QueryList<BitHint>;
@ContentChildren(BitSuffix, { descendants: true }) suffixChildren: QueryList<BitSuffix>;
@ContentChild(FormFieldControl) _inputNonStatic: FormFieldControl<any>;
@ContentChild(FormFieldControl, { static: true }) _inputStatic: FormFieldControl<any>;
get input() {
return this._inputExplicit || this._inputNonStatic || this._inputStatic;
}
set input(value: FormFieldControl<any>) {
this._inputExplicit = value;
}
private _inputExplicit: FormFieldControl<any>;
ngOnInit(): void {
return;
}
get displayedMessage(): "error" | "desc" {
return "desc";
}
}

View File

@@ -0,0 +1,12 @@
import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { FormFieldComponent } from "./form-field.component";
import { BitLabel } from "./label";
@NgModule({
imports: [CommonModule],
exports: [FormFieldComponent, BitLabel],
declarations: [FormFieldComponent, BitLabel],
})
export class FormFieldModule {}

View File

@@ -0,0 +1,39 @@
import { CommonModule } from "@angular/common";
import { Meta, Story, moduleMetadata } from "@storybook/angular";
import { BitInput } from "src/input/input.component";
import { FormFieldComponent } from "./form-field.component";
import { BitLabel } from "./label";
export default {
title: "Jslib/FormField",
component: FormFieldComponent,
decorators: [
moduleMetadata({
declarations: [FormFieldComponent, BitInput, BitLabel],
imports: [CommonModule],
}),
],
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/file/f32LSg3jaegICkMu7rPARm/Tailwind-Component-Library-Update?node-id=1717%3A14394",
},
},
} as Meta;
const TextTemplate: Story<FormFieldComponent> = (args: FormFieldComponent) => ({
props: args,
template: `
<bit-form-field>
<bit-label>email</bit-label>
<input bit-input type="text" [disabled]="disabled" [required]="required"/>
</bit-form-field>
`,
});
export const Text = TextTemplate.bind({});
Text.args = {
disabled: false,
required: true,
};

View File

@@ -0,0 +1,17 @@
import { Directive, Input } from "@angular/core";
let nextUniqueId = 0;
@Directive({
selector: "bit-hint",
host: {
class: "bit-hint",
"[class.text-right]": 'align === "end"',
"[attr.id]": "id",
"[attr.align]": "null",
},
})
export class BitHint {
@Input() align: "start" | "end" = "start";
@Input() id = `bit-hint-${nextUniqueId++}`;
}

View File

@@ -0,0 +1,6 @@
export * from "./form-field.component";
export * from "./form-field.module";
export * from "./hint";
export * from "./label";
export * from "./suffix";
export * from "../input/input.component";

View File

@@ -0,0 +1,6 @@
import { Directive } from "@angular/core";
@Directive({
selector: "bit-label",
})
export class BitLabel {}

View File

@@ -0,0 +1,9 @@
import { Directive } from "@angular/core";
@Directive({
selector: "[bit-suffix]",
host: {
class: "bit-suffix text-right",
},
})
export class BitSuffix {}

View File

@@ -0,0 +1,55 @@
import { Directive, HostBinding, Input, OnChanges, OnInit } from "@angular/core";
import { FormFieldControl } from "src/form-field/form-field-control";
let nextUniqueId = 0;
@Directive({
selector: "input[bit-input], textarea[bit-input]",
host: {
"[required]": "required",
"[attr.name]": "name || null",
"[attr.aria-invalid]": "(empty && required) ? null : errorState",
"[attr.aria-required]": "required",
},
})
export class BitInput extends FormFieldControl<any> implements OnInit, OnChanges {
@HostBinding("class") @Input("class") classList = "";
override value: any;
@Input() override id = `bit-input-${nextUniqueId++}`;
@HostBinding() @Input() override required = false;
@HostBinding() @Input() override disabled = false;
override inError: boolean;
inFocus: boolean;
ngOnInit(): void {
this.classList = this.classes.join(" ");
}
ngOnChanges() {
this.ngOnInit();
}
private get classes() {
return [
"tw-grow",
"tw-block",
this.disabled ? "tw-bg-secondary-100" : "tw-bg-background-alt",
"tw-border",
"tw-border-solid",
"tw-border-secondary-500",
"tw-rounded",
];
}
private get borderColorClass() {
if (this.disabled) {
return "tw-border-secondary-500";
}
if (this.inError) {
return "tw-border-danger-500";
}
return this.inFocus ? "tw-border-primary-700" : "tw-border-secondary-500";
}
}

View File

@@ -0,0 +1,12 @@
import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { FormFieldModule } from "src/form-field";
import { BitInput } from "./input.component";
@NgModule({
imports: [CommonModule],
exports: [BitInput, FormFieldModule],
declarations: [BitInput],
})
export class InputModule {}

View File

@@ -0,0 +1,36 @@
import { Meta, Story } from "@storybook/angular";
import { BitInput } from "./input.component";
export default {
title: "Jslib/Input",
component: BitInput,
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/file/f32LSg3jaegICkMu7rPARm/Tailwind-Component-Library-Update?node-id=1717%3A14394",
},
},
} as Meta;
const TextTemplate: Story<BitInput> = (args: BitInput) => ({
props: args,
template: `
<input bit-input [type]="type" [required]="required" [disabled]="disabled"/>
`,
});
const TextAreaTemplate: Story<BitInput> = (args: BitInput) => ({
props: args,
template: `
<textarea bit-input [required]="required" [disabled]="disabled"></textarea>
`,
});
export const Text = TextTemplate.bind({});
Text.args = {
type: "text",
};
export const TextArea = TextAreaTemplate.bind({});
TextArea.args = {};

4
package-lock.json generated
View File

@@ -55,6 +55,7 @@
}
},
"angular": {
"name": "@bitwarden/jslib-angular",
"version": "0.0.0",
"license": "GPL-3.0",
"dependencies": {
@@ -80,6 +81,7 @@
}
},
"common": {
"name": "@bitwarden/jslib-common",
"version": "0.0.0",
"license": "GPL-3.0",
"dependencies": {
@@ -106,6 +108,7 @@
}
},
"electron": {
"name": "@bitwarden/jslib-electron",
"version": "0.0.0",
"license": "GPL-3.0",
"dependencies": {
@@ -122,6 +125,7 @@
}
},
"node": {
"name": "@bitwarden/jslib-node",
"version": "0.0.0",
"license": "GPL-3.0",
"dependencies": {