Skip to content

Commit

Permalink
[EC-457] Component library icon buttons (#3372)
Browse files Browse the repository at this point in the history
* [EC-457] feat: initial version of icon button

* [EC-457] feat: modify template and start adding inputs

* [EC-457] feat: implement all styles

* [EC-457] chore: cleanup

* [EC-457] feat: fix hover styles after discussions

* [EC-457] feat: add focus ring workaround

* [EC-457] chore: refactor stories a bit

* [EC-457] fix: button style attr name reserved word collision

* [EC-356] feat: match padding with figma

* [EC-457] feat: use icon button in banner

* [EC-457] chore: cleanup css classes

* [EC-457] feat: improve aria

* [EC-457] feat: use icon button in dialog

* [EC-457] fix: make focus and hover styles independent

* [EC-457] fix: remove primary 500 border

* [EC-457] chore: cleanup

* [EC-457] chore: move css class to common list

* [EC-457] fix: use focus-visible

* [EC-457] chore: expand on workaround fix

* [EC-457] fix: default sizing

* [EC-457] fix: align trash icon right

* [EC-457] fix: add missing aria labels

* [EC-457] fix: add i18n service to banner tests

* [EC-457] chore: rename size `default` to `button`

* [EC-457] feat: double padding

* [EC-457] feat: simplify sizes - update default

* [EC-457] fix: revert selector fix - gonna create separate pr

* [EC-457] chore: remove superfluous dependencies

* [EC-457] fix: remove non-working onClose handler

Removing this storybook action because we already test it as part of the dialog service stories.
It requries mocking the dialogRef to capture the close function which makes this story more complex
but adds very little value as we already test it elsewhere.
  • Loading branch information
coroiu authored Sep 15, 2022
1 parent d2065cc commit d666d66
Show file tree
Hide file tree
Showing 16 changed files with 292 additions and 35 deletions.
13 changes: 9 additions & 4 deletions libs/components/src/banner/banner.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div
class="tw-flex tw-items-center tw-gap-2 tw-py-2.5 tw-px-4 tw-text-contrast"
class="tw-flex tw-items-center tw-gap-2 tw-py-2.5 tw-px-4 tw-pr-2.5 tw-text-contrast"
[ngClass]="bannerClass"
[attr.role]="useAlertRole ? 'status' : null"
[attr.aria-live]="useAlertRole ? 'polite' : null"
Expand All @@ -8,7 +8,12 @@
<span class="tw-grow tw-text-base">
<ng-content></ng-content>
</span>
<button class="tw-border-0 tw-bg-transparent tw-p-0 tw-text-contrast" (click)="onClose.emit()">
<i class="bwi bwi-close tw-text-sm" *ngIf="icon" aria-hidden="true"></i>
</button>
<button
bitIconButton="bwi-close"
buttonType="contrast"
size="default"
(click)="onClose.emit()"
[attr.title]="'close' | i18n"
[attr.aria-label]="'close' | i18n"
></button>
</div>
15 changes: 15 additions & 0 deletions libs/components/src/banner/banner.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";

import { I18nService } from "@bitwarden/common/abstractions/i18n.service";

import { SharedModule } from "../shared/shared.module";
import { I18nMockService } from "../utils/i18n-mock.service";

import { BannerComponent } from "./banner.component";

describe("BannerComponent", () => {
Expand All @@ -8,7 +13,17 @@ describe("BannerComponent", () => {

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SharedModule],
declarations: [BannerComponent],
providers: [
{
provide: I18nService,
useFactory: () =>
new I18nMockService({
close: "Close",
}),
},
],
}).compileComponents();

fixture = TestBed.createComponent(BannerComponent);
Expand Down
5 changes: 4 additions & 1 deletion libs/components/src/banner/banner.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";

import { IconButtonModule } from "../icon-button";
import { SharedModule } from "../shared/shared.module";

import { BannerComponent } from "./banner.component";

@NgModule({
imports: [CommonModule],
imports: [CommonModule, SharedModule, IconButtonModule],
exports: [BannerComponent],
declarations: [BannerComponent],
})
Expand Down
28 changes: 26 additions & 2 deletions libs/components/src/banner/banner.stories.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,43 @@
import { Meta, Story } from "@storybook/angular";
import { Meta, moduleMetadata, Story } from "@storybook/angular";

import { I18nService } from "@bitwarden/common/abstractions/i18n.service";

import { IconButtonModule } from "../icon-button";
import { SharedModule } from "../shared/shared.module";
import { I18nMockService } from "../utils/i18n-mock.service";

import { BannerComponent } from "./banner.component";

export default {
title: "Component Library/Banner",
component: BannerComponent,
decorators: [
moduleMetadata({
imports: [SharedModule, IconButtonModule],
providers: [
{
provide: I18nService,
useFactory: () => {
return new I18nMockService({
close: "Close",
});
},
},
],
}),
],
args: {
bannerType: "warning",
},
argTypes: {
onClose: { action: "onClose" },
},
} as Meta;

const Template: Story<BannerComponent> = (args: BannerComponent) => ({
props: args,
template: `
<bit-banner [bannerType]="bannerType">Content Really Long Text Lorem Ipsum Ipsum Ipsum <button>Button</button></bit-banner>
<bit-banner [bannerType]="bannerType" (onClose)="onClose($event)">Content Really Long Text Lorem Ipsum Ipsum Ipsum <button>Button</button></bit-banner>
`,
});

Expand Down
16 changes: 4 additions & 12 deletions libs/components/src/button/button.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ const buttonStyles: Record<ButtonTypes, string[]> = {
"!tw-text-contrast",
"hover:tw-bg-primary-700",
"hover:tw-border-primary-700",
"focus:tw-bg-primary-700",
"focus:tw-border-primary-700",
"disabled:tw-bg-primary-500/60",
"disabled:tw-border-primary-500/60",
"disabled:!tw-text-contrast/60",
Expand All @@ -23,9 +21,6 @@ const buttonStyles: Record<ButtonTypes, string[]> = {
"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",
"disabled:tw-bg-transparent",
"disabled:tw-border-text-muted/60",
"disabled:!tw-text-muted/60",
Expand All @@ -37,9 +32,6 @@ const buttonStyles: Record<ButtonTypes, string[]> = {
"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",
"disabled:tw-bg-transparent",
"disabled:tw-border-danger-500/60",
"disabled:!tw-text-danger/60",
Expand All @@ -62,10 +54,10 @@ export class ButtonDirective {
"tw-text-center",
"hover:tw-no-underline",
"focus:tw-outline-none",
"focus:tw-ring",
"focus:tw-ring-offset-2",
"focus:tw-ring-primary-700",
"focus:tw-z-10",
"focus-visible:tw-ring",
"focus-visible:tw-ring-offset-2",
"focus-visible:tw-ring-primary-700",
"focus-visible:tw-z-10",
]
.concat(this.block ? ["tw-w-full", "tw-block"] : ["tw-inline-block"])
.concat(buttonStyles[this.buttonType ?? "secondary"]);
Expand Down
5 changes: 3 additions & 2 deletions libs/components/src/dialog/dialog.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DialogModule as CdkDialogModule } from "@angular/cdk/dialog";
import { NgModule } from "@angular/core";

import { IconButtonModule } from "../icon-button";
import { SharedModule } from "../shared";

import { DialogService } from "./dialog.service";
Expand All @@ -10,11 +11,11 @@ import { DialogTitleContainerDirective } from "./directives/dialog-title-contain
import { SimpleDialogComponent } from "./simple-dialog/simple-dialog.component";

@NgModule({
imports: [SharedModule, CdkDialogModule],
imports: [SharedModule, IconButtonModule, CdkDialogModule],
declarations: [
DialogCloseDirective,
DialogComponent,
DialogTitleContainerDirective,
DialogComponent,
SimpleDialogComponent,
],
exports: [CdkDialogModule, DialogComponent, SimpleDialogComponent],
Expand Down
14 changes: 7 additions & 7 deletions libs/components/src/dialog/dialog/dialog.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
class="tw-my-4 tw-flex tw-max-h-screen tw-flex-col tw-overflow-hidden tw-rounded tw-border tw-border-solid tw-border-secondary-300 tw-bg-text-contrast tw-text-main"
>
<div
class="tw-flex tw-gap-4 tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300 tw-p-4"
class="tw-flex tw-items-center tw-gap-4 tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300 tw-p-4"
>
<h1 bitDialogTitleContainer class="tw-mb-0 tw-grow tw-text-lg tw-uppercase">
<ng-content select="[bitDialogTitle]"></ng-content>
</h1>
<button
bitIconButton="bwi-close"
buttonType="main"
size="default"
bitDialogClose
class="tw-border-0 tw-bg-transparent tw-p-0"
title="{{ 'close' | i18n }}"
attr.aria-label="{{ 'close' | i18n }}"
>
<i class="bwi bwi-close tw-text-xs tw-font-bold tw-text-main" aria-hidden="true"></i>
</button>
[attr.title]="'close' | i18n"
[attr.aria-label]="'close' | i18n"
></button>
</div>

<div class="tw-overflow-y-auto tw-p-4 tw-pb-8">
Expand Down
12 changes: 10 additions & 2 deletions libs/components/src/dialog/dialog/dialog.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";

import { ButtonModule } from "../../button";
import { IconButtonModule } from "../../icon-button";
import { SharedModule } from "../../shared";
import { I18nMockService } from "../../utils/i18n-mock.service";
import { DialogCloseDirective } from "../directives/dialog-close.directive";
Expand All @@ -15,7 +16,7 @@ export default {
component: DialogComponent,
decorators: [
moduleMetadata({
imports: [SharedModule, ButtonModule],
imports: [ButtonModule, SharedModule, IconButtonModule],
declarations: [DialogTitleContainerDirective, DialogCloseDirective],
providers: [
{
Expand Down Expand Up @@ -46,9 +47,16 @@ const Template: Story<DialogComponent> = (args: DialogComponent) => ({
<bit-dialog [dialogSize]="dialogSize">
<span bitDialogTitle>{{title}}</span>
<span bitDialogContent>Dialog body text goes here.</span>
<div bitDialogFooter class="tw-flex tw-flex-row tw-gap-2">
<div bitDialogFooter class="tw-flex tw-items-center tw-flex-row tw-gap-2">
<button bitButton buttonType="primary">Save</button>
<button bitButton buttonType="secondary">Cancel</button>
<button
class="tw-ml-auto"
bitIconButton="bwi-trash"
buttonType="danger"
size="default"
title="Delete"
aria-label="Delete"></button>
</div>
</bit-dialog>
`,
Expand Down
23 changes: 19 additions & 4 deletions libs/components/src/dialog/simple-dialog.service.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { DialogModule, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject } from "@angular/core";
import { Meta, moduleMetadata, Story } from "@storybook/angular";

import { I18nService } from "@bitwarden/common/abstractions/i18n.service";

import { ButtonModule } from "../button";
import { IconButtonModule } from "../icon-button";
import { SharedModule } from "../shared/shared.module";
import { I18nMockService } from "../utils/i18n-mock.service";

import { DialogService } from "./dialog.service";
import { DialogCloseDirective } from "./directives/dialog-close.directive";
Expand Down Expand Up @@ -60,13 +65,23 @@ export default {
decorators: [
moduleMetadata({
declarations: [
StoryDialogContentComponent,
DialogCloseDirective,
SimpleDialogComponent,
DialogTitleContainerDirective,
StoryDialogContentComponent,
SimpleDialogComponent,
],
imports: [SharedModule, IconButtonModule, ButtonModule, DialogModule],
providers: [
DialogService,
{
provide: I18nService,
useFactory: () => {
return new I18nMockService({
close: "Close",
});
},
},
],
imports: [ButtonModule, DialogModule],
providers: [DialogService],
}),
],
parameters: {
Expand Down
115 changes: 115 additions & 0 deletions libs/components/src/icon-button/icon-button.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { Component, HostBinding, Input } from "@angular/core";

export type IconButtonStyle = "contrast" | "main" | "muted" | "primary" | "secondary" | "danger";

const styles: Record<IconButtonStyle, string[]> = {
contrast: [
"tw-bg-transparent",
"!tw-text-contrast",
"tw-border-transparent",
"hover:tw-bg-transparent-hover",
"hover:tw-border-text-contrast",
"focus-visible:before:tw-ring-text-contrast",
"disabled:hover:tw-bg-transparent",
],
main: [
"tw-bg-transparent",
"!tw-text-main",
"tw-border-transparent",
"hover:tw-bg-transparent-hover",
"hover:tw-border-text-main",
"focus-visible:before:tw-ring-text-main",
"disabled:hover:tw-bg-transparent",
],
muted: [
"tw-bg-transparent",
"!tw-text-muted",
"tw-border-transparent",
"hover:tw-bg-transparent-hover",
"hover:tw-border-primary-700",
"focus-visible:before:tw-ring-primary-700",
"disabled:hover:tw-bg-transparent",
],
primary: [
"tw-bg-primary-500",
"!tw-text-contrast",
"tw-border-primary-500",
"hover:tw-bg-primary-700",
"hover:tw-border-primary-700",
"focus-visible:before:tw-ring-primary-700",
"disabled:hover:tw-bg-primary-500",
],
secondary: [
"tw-bg-transparent",
"!tw-text-muted",
"tw-border-text-muted",
"hover:!tw-text-contrast",
"hover:tw-bg-text-muted",
"focus-visible:before:tw-ring-primary-700",
"disabled:hover:tw-bg-transparent",
"disabled:hover:!tw-text-muted",
"disabled:hover:tw-border-text-muted",
],
danger: [
"tw-bg-transparent",
"!tw-text-danger",
"tw-border-danger-500",
"hover:!tw-text-contrast",
"hover:tw-bg-danger-500",
"focus-visible:before:tw-ring-primary-700",
"disabled:hover:tw-bg-transparent",
"disabled:hover:!tw-text-danger",
"disabled:hover:tw-border-danger-500",
],
};

export type IconButtonSize = "default" | "small";

const sizes: Record<IconButtonSize, string[]> = {
default: ["tw-px-2.5", "tw-py-1.5"],
small: ["tw-leading-none", "tw-text-base", "tw-p-1"],
};

@Component({
selector: "button[bitIconButton]",
template: `<i class="bwi" [ngClass]="icon" aria-hidden="true"></i>`,
})
export class BitIconButtonComponent {
@Input("bitIconButton") icon: string;

@Input() buttonType: IconButtonStyle = "main";

@Input() size: IconButtonSize = "default";

@HostBinding("class") get classList() {
return [
"tw-font-semibold",
"tw-border",
"tw-border-solid",
"tw-rounded",
"tw-transition",
"hover:tw-no-underline",
"disabled:tw-opacity-60",
"disabled:hover:tw-border-transparent",
"focus:tw-outline-none",

// Workaround for box-shadow with transparent offset issue:
// https://github.com/tailwindlabs/tailwindcss/issues/3595
// Remove `before:` and use regular `tw-ring` when browser no longer has bug, or better:
// switch to `outline` with `outline-offset` when Safari supports border radius on outline.
// Using `box-shadow` to create outlines is a hack and as such `outline` should be preferred.
"tw-relative",
"before:tw-content-['']",
"before:tw-block",
"before:tw-absolute",
"before:-tw-inset-[3px]",
"before:tw-rounded-md",
"before:tw-transition",
"before:tw-ring",
"focus-visible:before:tw-ring-text-contrast",
"focus-visible:tw-z-10",
]
.concat(styles[this.buttonType])
.concat(sizes[this.size]);
}
}
Loading

0 comments on commit d666d66

Please sign in to comment.