mirror of
https://github.com/bitwarden/jslib
synced 2025-12-06 00:03:29 +00:00
Play with storybook
This commit is contained in:
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"jest.jestCommandLine": "npm run test --"
|
||||
}
|
||||
@@ -22,6 +22,9 @@ export class ExportComponent implements OnInit {
|
||||
exportForm = this.formBuilder.group({
|
||||
format: ["json"],
|
||||
secret: [""],
|
||||
passwordProtected: [false],
|
||||
filePassword: [""],
|
||||
confirmFilePassword: [""],
|
||||
});
|
||||
|
||||
formatOptions = [
|
||||
|
||||
7
components/src/form-field/form-field-control.ts
Normal file
7
components/src/form-field/form-field-control.ts
Normal 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;
|
||||
}
|
||||
18
components/src/form-field/form-field.component.html
Normal file
18
components/src/form-field/form-field.component.html
Normal 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>
|
||||
77
components/src/form-field/form-field.component.spec.ts
Normal file
77
components/src/form-field/form-field.component.spec.ts
Normal 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;
|
||||
// }
|
||||
36
components/src/form-field/form-field.component.ts
Normal file
36
components/src/form-field/form-field.component.ts
Normal 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";
|
||||
}
|
||||
}
|
||||
12
components/src/form-field/form-field.module.ts
Normal file
12
components/src/form-field/form-field.module.ts
Normal 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 {}
|
||||
39
components/src/form-field/form-field.stories.ts
Normal file
39
components/src/form-field/form-field.stories.ts
Normal 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,
|
||||
};
|
||||
17
components/src/form-field/hint.ts
Normal file
17
components/src/form-field/hint.ts
Normal 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++}`;
|
||||
}
|
||||
6
components/src/form-field/index.ts
Normal file
6
components/src/form-field/index.ts
Normal 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";
|
||||
6
components/src/form-field/label.ts
Normal file
6
components/src/form-field/label.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Directive } from "@angular/core";
|
||||
|
||||
@Directive({
|
||||
selector: "bit-label",
|
||||
})
|
||||
export class BitLabel {}
|
||||
9
components/src/form-field/suffix.ts
Normal file
9
components/src/form-field/suffix.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Directive } from "@angular/core";
|
||||
|
||||
@Directive({
|
||||
selector: "[bit-suffix]",
|
||||
host: {
|
||||
class: "bit-suffix text-right",
|
||||
},
|
||||
})
|
||||
export class BitSuffix {}
|
||||
55
components/src/input/input.component.ts
Normal file
55
components/src/input/input.component.ts
Normal 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";
|
||||
}
|
||||
}
|
||||
12
components/src/input/input.module.ts
Normal file
12
components/src/input/input.module.ts
Normal 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 {}
|
||||
36
components/src/input/input.stories.ts
Normal file
36
components/src/input/input.stories.ts
Normal 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
4
package-lock.json
generated
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user