1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

Merge branch 'master' into feature/org-admin-refresh

This commit is contained in:
Vincent Salucci
2022-09-20 14:00:02 -05:00
208 changed files with 2171 additions and 998 deletions

View File

@@ -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"
@@ -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>

View File

@@ -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", () => {
@@ -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);

View File

@@ -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],
})

View File

@@ -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>
`,
});

View File

@@ -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",
@@ -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",
@@ -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",
@@ -62,18 +54,17 @@ 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(
this.block == null || this.block === false ? ["tw-inline-block"] : ["tw-w-full", "tw-block"]
)
.concat(buttonStyles[this.buttonType ?? "secondary"]);
}
@Input()
buttonType: ButtonTypes = null;
@Input()
block = false;
@Input() buttonType: ButtonTypes = null;
@Input() block?: boolean;
}

View File

@@ -52,3 +52,21 @@ export const Disabled = DisabledTemplate.bind({});
Disabled.args = {
size: "small",
};
const BlockTemplate: Story<ButtonDirective> = (args: ButtonDirective) => ({
props: args,
template: `
<span class="tw-flex">
<button bitButton [buttonType]="buttonType" [block]="block">[block]="true" Button</button>
<a bitButton [buttonType]="buttonType" [block]="block" href="#" class="tw-ml-2">[block]="true" Link</a>
<button bitButton [buttonType]="buttonType" block class="tw-ml-2">block Button</button>
<a bitButton [buttonType]="buttonType" block href="#" class="tw-ml-2">block Link</a>
</span>
`,
});
export const Block = BlockTemplate.bind({});
Block.args = {
block: true,
};

View File

@@ -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";
@@ -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],

View File

@@ -3,19 +3,20 @@
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
type="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">

View File

@@ -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";
@@ -15,7 +16,7 @@ export default {
component: DialogComponent,
decorators: [
moduleMetadata({
imports: [SharedModule, ButtonModule],
imports: [ButtonModule, SharedModule, IconButtonModule],
declarations: [DialogTitleContainerDirective, DialogCloseDirective],
providers: [
{
@@ -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>
`,

View File

@@ -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";
@@ -60,13 +65,23 @@ export default {
decorators: [
moduleMetadata({
declarations: [
DialogCloseDirective,
SimpleDialogComponent,
DialogTitleContainerDirective,
StoryDialogContentComponent,
DialogCloseDirective,
DialogTitleContainerDirective,
SimpleDialogComponent,
],
imports: [SharedModule, IconButtonModule, ButtonModule, DialogModule],
providers: [
DialogService,
{
provide: I18nService,
useFactory: () => {
return new I18nMockService({
close: "Close",
});
},
},
],
imports: [ButtonModule, DialogModule],
providers: [DialogService],
}),
],
parameters: {

View File

@@ -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]);
}
}

View File

@@ -0,0 +1,11 @@
import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { BitIconButtonComponent } from "./icon-button.component";
@NgModule({
imports: [CommonModule],
declarations: [BitIconButtonComponent],
exports: [BitIconButtonComponent],
})
export class IconButtonModule {}

View File

@@ -0,0 +1,59 @@
import { Meta, Story } from "@storybook/angular";
import { BitIconButtonComponent } from "./icon-button.component";
export default {
title: "Component Library/Icon Button",
component: BitIconButtonComponent,
args: {
bitIconButton: "bwi-plus",
buttonType: "primary",
size: "default",
disabled: false,
},
} as Meta;
const Template: Story<BitIconButtonComponent> = (args: BitIconButtonComponent) => ({
props: args,
template: `
<div class="tw-p-5" [class.tw-bg-primary-500]="buttonType === 'contrast'">
<button
[bitIconButton]="bitIconButton"
[buttonType]="buttonType"
[size]="size"
[disabled]="disabled"
title="Example icon button"
aria-label="Example icon button"></button>
</div>
`,
});
export const Contrast = Template.bind({});
Contrast.args = {
buttonType: "contrast",
};
export const Main = Template.bind({});
Main.args = {
buttonType: "main",
};
export const Muted = Template.bind({});
Muted.args = {
buttonType: "muted",
};
export const Primary = Template.bind({});
Primary.args = {
buttonType: "primary",
};
export const Secondary = Template.bind({});
Secondary.args = {
buttonType: "secondary",
};
export const Danger = Template.bind({});
Danger.args = {
buttonType: "danger",
};

View File

@@ -0,0 +1 @@
export * from "./icon-button.module";

View File

@@ -4,6 +4,7 @@ export * from "./button";
export * from "./callout";
export * from "./form-field";
export * from "./icon";
export * from "./icon-button";
export * from "./menu";
export * from "./dialog";
export * from "./submit-button";

View File

@@ -1,4 +1,10 @@
<button bitButton type="submit" [buttonType]="buttonType" [disabled]="loading || disabled">
<button
bitButton
type="submit"
[block]="block"
[buttonType]="buttonType"
[disabled]="loading || disabled"
>
<span class="tw-relative">
<span [ngClass]="{ 'tw-invisible': loading }">
<ng-content></ng-content>

View File

@@ -1,4 +1,4 @@
import { Component, Input } from "@angular/core";
import { Component, HostBinding, Input } from "@angular/core";
import { ButtonTypes } from "../button";
@@ -10,4 +10,10 @@ export class SubmitButtonComponent {
@Input() buttonType: ButtonTypes = "primary";
@Input() disabled = false;
@Input() loading: boolean;
@Input() block?: boolean;
@HostBinding("class") get classList() {
return this.block == null || this.block === false ? [] : ["tw-w-full", "tw-block"];
}
}

View File

@@ -14,6 +14,7 @@ export default {
args: {
buttonType: "primary",
loading: false,
block: false,
},
parameters: {
design: {
@@ -25,7 +26,7 @@ export default {
const Template: Story<SubmitButtonComponent> = (args: SubmitButtonComponent) => ({
props: args,
template: `<bit-submit-button [buttonType]="buttonType" [loading]="loading" [disabled]="disabled">
template: `<bit-submit-button [buttonType]="buttonType" [loading]="loading" [disabled]="disabled" [block]="block">
Submit
</bit-submit-button>`,
});

View File

@@ -1,4 +1,6 @@
:root {
--color-transparent-hover: rgb(0 0 0 / 0.03);
--color-background: 255 255 255;
--color-background-alt: 251 251 251;
--color-background-alt2: 23 92 219;
@@ -37,6 +39,8 @@
}
.theme_dark {
--color-transparent-hover: rgb(255 255 255 / 0.12);
--color-background: 31 36 46;
--color-background-alt: 22 28 38;
--color-background-alt2: 47 52 61;

View File

@@ -12,7 +12,10 @@ module.exports = {
corePlugins: { preflight: false },
theme: {
colors: {
transparent: colors.transparent,
transparent: {
DEFAULT: colors.transparent,
hover: "var(--color-transparent-hover)",
},
current: colors.current,
black: colors.black,
primary: {