mirror of
https://github.com/bitwarden/jslib
synced 2026-01-06 10:33:15 +00:00
Component Library scaffolding (#625)
This commit is contained in:
49
components/src/button/Button.stories.ts
Normal file
49
components/src/button/Button.stories.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Meta, Story } from "@storybook/angular";
|
||||
|
||||
import { ButtonComponent } from "./button.component";
|
||||
|
||||
// More on default export: https://storybook.js.org/docs/angular/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: "Jslib/Button",
|
||||
component: ButtonComponent,
|
||||
args: {
|
||||
buttonType: "primary",
|
||||
},
|
||||
// More on argTypes: https://storybook.js.org/docs/angular/api/argtypes
|
||||
} as Meta;
|
||||
|
||||
// More on component templates: https://storybook.js.org/docs/angular/writing-stories/introduction#using-args
|
||||
const Template: Story<ButtonComponent> = (args: ButtonComponent) => ({
|
||||
props: args,
|
||||
template: `<button bit-button [buttonType]="buttonType" [block]="block">Test</button>`,
|
||||
});
|
||||
|
||||
export const Primary = Template.bind({});
|
||||
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||
Primary.args = {
|
||||
buttonType: "primary",
|
||||
};
|
||||
|
||||
export const Secondary = Template.bind({});
|
||||
Secondary.args = {
|
||||
buttonType: "secondary",
|
||||
};
|
||||
|
||||
export const Danger = Template.bind({});
|
||||
Danger.args = {
|
||||
buttonType: "danger",
|
||||
};
|
||||
|
||||
const DisabledTemplate: Story = (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<button bit-button disabled buttonType="primary" class="tw-mr-2">Primary</button>
|
||||
<button bit-button disabled buttonType="secondary" class="tw-mr-2">Secondary</button>
|
||||
<button bit-button disabled buttonType="danger" class="tw-mr-2">Danger</button>
|
||||
`,
|
||||
});
|
||||
|
||||
export const Disabled = DisabledTemplate.bind({});
|
||||
Disabled.args = {
|
||||
size: "small",
|
||||
};
|
||||
57
components/src/button/button.component.spec.ts
Normal file
57
components/src/button/button.component.spec.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { TestBed, waitForAsync } from "@angular/core/testing";
|
||||
import { By } from "@angular/platform-browser";
|
||||
|
||||
import { ButtonModule } from "./index";
|
||||
|
||||
describe("Button", () => {
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ButtonModule],
|
||||
declarations: [TestApp],
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
it("should apply classes based on type", () => {
|
||||
const fixture = TestBed.createComponent(TestApp);
|
||||
|
||||
const testAppComponent: TestApp = fixture.debugElement.componentInstance;
|
||||
const buttonDebugElement = fixture.debugElement.query(By.css("button"));
|
||||
const linkDebugElement = fixture.debugElement.query(By.css("a"));
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@Component({
|
||||
selector: "test-app",
|
||||
template: `
|
||||
<button type="button" bit-button [buttonType]="buttonType">Button</button>
|
||||
<a href="#" bit-button [buttonType]="buttonType"> Link </a>
|
||||
`,
|
||||
})
|
||||
class TestApp {
|
||||
buttonType: string;
|
||||
}
|
||||
77
components/src/button/button.component.ts
Normal file
77
components/src/button/button.component.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { Input, HostBinding, OnChanges, Directive } from "@angular/core";
|
||||
|
||||
export type ButtonTypes = "primary" | "secondary" | "danger";
|
||||
|
||||
const buttonStyles: Record<ButtonTypes, string> = {
|
||||
primary: [
|
||||
"tw-border-primary-500",
|
||||
"tw-bg-primary-500",
|
||||
"!tw-text-contrast",
|
||||
"hover:tw-bg-primary-700",
|
||||
"hover:tw-border-primary-700",
|
||||
"focus:tw-bg-primary-700",
|
||||
"focus:tw-border-primary-700",
|
||||
].join(" "),
|
||||
secondary: [
|
||||
"tw-bg-transparent",
|
||||
"tw-border-text-muted",
|
||||
"!tw-text-muted",
|
||||
"hover:tw-bg-secondary-500",
|
||||
"hover:tw-border-secondary-500",
|
||||
"hover:tw-text-contrast",
|
||||
"focus:tw-bg-secondary-500",
|
||||
"focus:tw-border-secondary-500",
|
||||
"focus:tw-text-contrast",
|
||||
].join(" "),
|
||||
danger: [
|
||||
"tw-bg-transparent",
|
||||
"tw-border-danger-500",
|
||||
"!tw-text-danger",
|
||||
"hover:tw-bg-danger-500",
|
||||
"hover:tw-border-danger-500",
|
||||
"hover:tw-text-contrast",
|
||||
"focus:tw-bg-danger-500",
|
||||
"focus:tw-border-danger-500",
|
||||
"focus:tw-text-contrast",
|
||||
].join(" "),
|
||||
};
|
||||
|
||||
@Directive({
|
||||
selector: "button[bit-button], a[bit-button]",
|
||||
})
|
||||
export class ButtonComponent implements OnChanges {
|
||||
@HostBinding("class") @Input("class") classList = "";
|
||||
|
||||
@Input()
|
||||
buttonType: ButtonTypes = "secondary";
|
||||
|
||||
@Input()
|
||||
block = false;
|
||||
|
||||
ngOnChanges() {
|
||||
this.classList = this.classes.join(" ");
|
||||
}
|
||||
|
||||
get classes(): string[] {
|
||||
return [
|
||||
"tw-font-semibold",
|
||||
"tw-py-1.5",
|
||||
"tw-px-3",
|
||||
"tw-rounded",
|
||||
"tw-transition",
|
||||
"tw-border",
|
||||
"tw-border-solid",
|
||||
"tw-text-center",
|
||||
"hover:tw-no-underline",
|
||||
"disabled:tw-bg-secondary-100",
|
||||
"disabled:tw-border-secondary-100",
|
||||
"disabled:!tw-text-main",
|
||||
"focus:tw-outline-none",
|
||||
"focus:tw-ring",
|
||||
"focus:tw-ring-offset-2",
|
||||
"focus:tw-ring-primary-700",
|
||||
this.block ? "tw-w-full tw-block" : "",
|
||||
buttonStyles[this.buttonType ?? "secondary"],
|
||||
];
|
||||
}
|
||||
}
|
||||
11
components/src/button/button.module.ts
Normal file
11
components/src/button/button.module.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { ButtonComponent } from "./button.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
exports: [ButtonComponent],
|
||||
declarations: [ButtonComponent],
|
||||
})
|
||||
export class ButtonModule {}
|
||||
2
components/src/button/index.ts
Normal file
2
components/src/button/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./button.component";
|
||||
export * from "./button.module";
|
||||
Reference in New Issue
Block a user