[CL-46] Dialogs (#3237)

This commit is contained in:
Oscar Hinton 2022-08-05 17:45:36 +02:00 committed by GitHub
parent e7220644d1
commit 762fac062f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 338 additions and 217 deletions

View File

@ -39,4 +39,4 @@ jobs:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
exitOnceUploaded: true
onlyChanged: true
externals: "[\"components/**/*.scss\", \"components/tailwind.config*.js\"]"
externals: "[\"libs/components/**/*.scss\", \"libs/components/tailwind.config*.js\"]"

View File

@ -0,0 +1,18 @@
import { DialogRef } from "@angular/cdk/dialog";
import { Directive, Input, Optional } from "@angular/core";
@Directive({
selector: "[bitDialogClose]",
host: {
"(click)": "close()",
},
})
export class DialogCloseDirective {
@Input("bit-dialog-close") dialogResult: any;
constructor(@Optional() public dialogRef: DialogRef<any>) {}
close() {
this.dialogRef.close(this.dialogResult);
}
}

View File

@ -0,0 +1,78 @@
import { DialogModule, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject } from "@angular/core";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { ButtonModule } from "../button";
import { DialogCloseDirective } from "./dialog-close.directive";
import { DialogService } from "./dialog.service";
import { DialogComponent } from "./dialog/dialog.component";
interface Animal {
animal: string;
}
@Component({
selector: "app-story-dialog",
template: `<button bitButton (click)="openDialog()">Open Dialog</button>`,
})
class StoryDialogComponent {
constructor(public dialogService: DialogService) {}
openDialog() {
this.dialogService.open(StoryDialogContentComponent, {
data: {
animal: "panda",
},
});
}
}
@Component({
selector: "story-dialog-content",
template: `
<bit-dialog [dialogSize]="large">
<span bit-dialog-title>Dialog Title</span>
<span bit-dialog-content>
Dialog body text goes here.
<br />
Animal: {{ animal }}
</span>
<div bit-dialog-footer class="tw-flex tw-flex-row tw-gap-2">
<button bitButton buttonType="primary" (click)="dialogRef.close()">Save</button>
<button bitButton buttonType="secondary" bitDialogClose>Cancel</button>
</div>
</bit-dialog>
`,
})
class StoryDialogContentComponent {
constructor(public dialogRef: DialogRef, @Inject(DIALOG_DATA) private data: Animal) {}
get animal() {
return this.data?.animal;
}
}
export default {
title: "Component Library/Dialogs/Service",
component: StoryDialogComponent,
decorators: [
moduleMetadata({
declarations: [DialogComponent, StoryDialogContentComponent, DialogCloseDirective],
imports: [ButtonModule, DialogModule],
providers: [DialogService],
}),
],
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library",
},
},
} as Meta;
const Template: Story<StoryDialogComponent> = (args: StoryDialogComponent) => ({
props: args,
});
export const Default = Template.bind({});

View File

@ -0,0 +1,16 @@
import { DialogModule as CdkDialogModule } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { DialogCloseDirective } from "./dialog-close.directive";
import { DialogService } from "./dialog.service";
import { DialogComponent } from "./dialog/dialog.component";
import { SimpleDialogComponent } from "./simple-dialog/simple-dialog.component";
@NgModule({
imports: [CommonModule, CdkDialogModule],
declarations: [DialogCloseDirective, DialogComponent, SimpleDialogComponent],
exports: [CdkDialogModule, DialogComponent, SimpleDialogComponent],
providers: [DialogService],
})
export class DialogModule {}

View File

@ -0,0 +1,18 @@
import { Dialog, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { ComponentType } from "@angular/cdk/overlay";
import { Injectable, TemplateRef } from "@angular/core";
@Injectable()
export class DialogService extends Dialog {
override open<R = unknown, D = unknown, C = unknown>(
componentOrTemplateRef: ComponentType<C> | TemplateRef<C>,
config?: DialogConfig<D, DialogRef<R, C>>
): DialogRef<R, C> {
config = {
backdropClass: ["tw-fixed", "tw-bg-black", "tw-bg-opacity-30", "tw-inset-0", "tw-z-40"],
...config,
};
return super.open(componentOrTemplateRef, config);
}
}

View File

@ -6,20 +6,20 @@
class="tw-flex tw-gap-4 tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300 tw-p-4"
>
<h2 class="tw-mb-0 tw-grow tw-text-lg tw-uppercase">
<ng-content select="[bit-modal-title]"></ng-content>
<ng-content select="[bit-dialog-title]"></ng-content>
</h2>
<button class="tw-border-0 tw-bg-transparent tw-p-0">
<button class="tw-border-0 tw-bg-transparent tw-p-0" bitDialogClose>
<i class="bwi bwi-close tw-text-xs tw-font-bold tw-text-main" aria-hidden="true"></i>
</button>
</div>
<div class="tw-overflow-y-auto tw-p-4 tw-pb-8">
<ng-content select="[bit-modal-content]"></ng-content>
<ng-content select="[bit-dialog-content]"></ng-content>
</div>
<div
class="tw-border-0 tw-border-t tw-border-solid tw-border-secondary-300 tw-bg-background-alt tw-p-4"
>
<ng-content select="[bit-modal-footer]"></ng-content>
<ng-content select="[bit-dialog-footer]"></ng-content>
</div>
</div>

View File

@ -0,0 +1,23 @@
import { Component, Input } from "@angular/core";
@Component({
selector: "bit-dialog",
templateUrl: "./dialog.component.html",
})
export class DialogComponent {
@Input() dialogSize: "small" | "default" | "large" = "default";
get width() {
switch (this.dialogSize) {
case "small": {
return "tw-w-96";
}
case "large": {
return "tw-w-75vw";
}
default: {
return "tw-w-50vw";
}
}
}
}

View File

@ -0,0 +1,81 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { ButtonModule } from "../../button";
import { DialogComponent } from "./dialog.component";
export default {
title: "Component Library/Dialogs/Dialog",
component: DialogComponent,
decorators: [
moduleMetadata({
imports: [ButtonModule],
}),
],
args: {
dialogSize: "small",
},
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library",
},
},
} as Meta;
const Template: Story<DialogComponent> = (args: DialogComponent) => ({
props: args,
template: `
<bit-dialog [dialogSize]="dialogSize">
<span bit-dialog-title>{{title}}</span>
<span bit-dialog-content>Dialog body text goes here.</span>
<div bit-dialog-footer class="tw-flex tw-flex-row tw-gap-2">
<button bitButton buttonType="primary">Save</button>
<button bitButton buttonType="secondary">Cancel</button>
</div>
</bit-dialog>
`,
});
export const Default = Template.bind({});
Default.args = {
dialogSize: "default",
title: "Default",
};
export const Small = Template.bind({});
Small.args = {
dialogSize: "small",
title: "Small",
};
export const Large = Template.bind({});
Large.args = {
dialogSize: "large",
title: "Large",
};
const TemplateScrolling: Story<DialogComponent> = (args: DialogComponent) => ({
props: args,
template: `
<bit-dialog [dialogSize]="dialogSize">
<span bit-dialog-title>Scrolling Example</span>
<span bit-dialog-content>
Dialog body text goes here.<br>
<ng-container *ngFor="let _ of [].constructor(100)">
repeating lines of characters <br>
</ng-container>
end of sequence!
</span>
<div bit-dialog-footer class="tw-flex tw-flex-row tw-gap-2">
<button bitButton buttonType="primary">Save</button>
<button bitButton buttonType="secondary">Cancel</button>
</div>
</bit-dialog>
`,
});
export const ScrollingContent = TemplateScrolling.bind({});
ScrollingContent.args = {
dialogSize: "small",
};

View File

@ -0,0 +1,2 @@
export * from "./dialog.module";
export * from "./dialog.service";

View File

@ -2,18 +2,18 @@
class="tw-my-4 tw-flex tw-max-h-screen tw-max-w-sm 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-flex-col tw-items-center tw-gap-2 tw-px-4 tw-pt-4 tw-text-center">
<ng-content *ngIf="hasIcon; else elseBlock" select="[bit-modal-icon]"></ng-content>
<ng-content *ngIf="hasIcon; else elseBlock" select="[bit-dialog-icon]"></ng-content>
<ng-template #elseBlock>
<i class="bwi bwi-exclamation-triangle tw-text-3xl tw-text-warning" aria-hidden="true"></i>
</ng-template>
<h2 class="tw-mb-0 tw-text-base tw-font-semibold">
<ng-content select="[bit-modal-title]"></ng-content>
<ng-content select="[bit-dialog-title]"></ng-content>
</h2>
</div>
<div class="tw-overflow-y-auto tw-px-4 tw-pt-2 tw-pb-4 tw-text-center tw-text-base">
<ng-content select="[bit-modal-content]"></ng-content>
<ng-content select="[bit-dialog-content]"></ng-content>
</div>
<div class="tw-border-0 tw-border-t tw-border-solid tw-border-secondary-300 tw-p-4">
<ng-content select="[bit-modal-footer]"></ng-content>
<ng-content select="[bit-dialog-footer]"></ng-content>
</div>
</div>

View File

@ -1,13 +1,13 @@
import { Component, ContentChild, Directive } from "@angular/core";
@Directive({ selector: "[bit-modal-icon]" })
@Directive({ selector: "[bit-dialog-icon]" })
export class IconDirective {}
@Component({
selector: "bit-simple-modal",
templateUrl: "./modal-simple.component.html",
selector: "bit-simple-dialog",
templateUrl: "./simple-dialog.component.html",
})
export class ModalSimpleComponent {
export class SimpleDialogComponent {
@ContentChild(IconDirective) icon!: IconDirective;
get hasIcon() {

View File

@ -0,0 +1,81 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { ButtonModule } from "../../button";
import { IconDirective, SimpleDialogComponent } from "./simple-dialog.component";
export default {
title: "Component Library/Dialogs/Simple Dialog",
component: SimpleDialogComponent,
decorators: [
moduleMetadata({
imports: [ButtonModule],
declarations: [IconDirective],
}),
],
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library",
},
},
} as Meta;
const Template: Story<SimpleDialogComponent> = (args: SimpleDialogComponent) => ({
props: args,
template: `
<bit-simple-dialog>
<span bit-dialog-title>Alert Dialog</span>
<span bit-dialog-content>Message Content</span>
<div bit-dialog-footer class="tw-flex tw-flex-row tw-gap-2">
<button bitButton buttonType="primary">Yes</button>
<button bitButton buttonType="secondary">No</button>
</div>
</bit-simple-dialog>
`,
});
export const Default = Template.bind({});
const TemplateWithIcon: Story<SimpleDialogComponent> = (args: SimpleDialogComponent) => ({
props: args,
template: `
<bit-simple-dialog>
<i bit-dialog-icon class="bwi bwi-star tw-text-3xl tw-text-success" aria-hidden="true"></i>
<span bit-dialog-title>Premium Subscription Available</span>
<span bit-dialog-content> Message Content</span>
<div bit-dialog-footer class="tw-flex tw-flex-row tw-gap-2">
<button bitButton buttonType="primary">Yes</button>
<button bitButton buttonType="secondary">No</button>
</div>
</bit-simple-dialog>
`,
});
export const CustomIcon = TemplateWithIcon.bind({});
const TemplateScroll: Story<SimpleDialogComponent> = (args: SimpleDialogComponent) => ({
props: args,
template: `
<bit-simple-dialog>
<span bit-dialog-title>Alert Dialog</span>
<span bit-dialog-content>
Message Content
Message text goes here.<br>
<ng-container *ngFor="let _ of [].constructor(100)">
repeating lines of characters <br>
</ng-container>
end of sequence!
</span>
<div bit-dialog-footer class="tw-flex tw-flex-row tw-gap-2">
<button bitButton buttonType="primary">Yes</button>
<button bitButton buttonType="secondary">No</button>
</div>
</bit-simple-dialog>
`,
});
export const ScrollingContent = TemplateScroll.bind({});
ScrollingContent.args = {
useDefaultIcon: true,
};

View File

@ -5,7 +5,7 @@ export * from "./callout";
export * from "./form-field";
export * from "./icon";
export * from "./menu";
export * from "./modal";
export * from "./dialog";
export * from "./submit-button";
export * from "./tabs";
export * from "./toggle-group";

View File

@ -1,3 +0,0 @@
export * from "./modal.component";
export * from "./modal-simple.component";
export * from "./modal.module";

View File

@ -1,85 +0,0 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { ButtonModule } from "../button";
import { IconDirective, ModalSimpleComponent } from "./modal-simple.component";
export default {
title: "Component Library/Modals/Simple Modal",
component: ModalSimpleComponent,
decorators: [
moduleMetadata({
imports: [ButtonModule],
declarations: [IconDirective],
}),
],
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library",
},
},
} as Meta;
const Template: Story<ModalSimpleComponent> = (args: ModalSimpleComponent) => ({
props: args,
template: `
<bit-simple-modal>
<span bit-modal-title> Alert Modal
</span>
<span bit-modal-content> Message Content
</span>
<div bit-modal-footer class="tw-flex tw-flex-row tw-gap-2">
<button bitButton buttonType="primary"> Yes </button>
<button bitButton buttonType="secondary"> No </button>
</div>
</bit-simple-modal>
`,
});
export const Default = Template.bind({});
const TemplateWithIcon: Story<ModalSimpleComponent> = (args: ModalSimpleComponent) => ({
props: args,
template: `
<bit-simple-modal>
<i bit-modal-icon class="bwi bwi-star tw-text-3xl tw-text-success" aria-hidden="true"></i>
<span bit-modal-title> Premium Subscription Available
</span>
<span bit-modal-content> Message Content
</span>
<div bit-modal-footer class="tw-flex tw-flex-row tw-gap-2">
<button bitButton buttonType="primary"> Yes </button>
<button bitButton buttonType="secondary"> No </button>
</div>
</bit-simple-modal>
`,
});
export const CustomIcon = TemplateWithIcon.bind({});
const TemplateScroll: Story<ModalSimpleComponent> = (args: ModalSimpleComponent) => ({
props: args,
template: `
<bit-simple-modal>
<span bit-modal-title> Alert Modal
</span>
<span bit-modal-content> Message Content
Message text goes here.<br>
<ng-container *ngFor="let _ of [].constructor(100)">
repeating lines of characters <br>
</ng-container>
end of sequence!
</span>
<div bit-modal-footer class="tw-flex tw-flex-row tw-gap-2">
<button bitButton buttonType="primary"> Yes </button>
<button bitButton buttonType="secondary"> No </button>
</div>
</bit-simple-modal>
`,
});
export const ScrollingContent = TemplateScroll.bind({});
ScrollingContent.args = {
useDefaultIcon: true,
};

View File

@ -1,23 +0,0 @@
import { Component, Input } from "@angular/core";
@Component({
selector: "bit-modal",
templateUrl: "./modal.component.html",
})
export class ModalComponent {
@Input() modalSize: "small" | "default" | "large";
get width() {
switch (this.modalSize) {
case "small": {
return "tw-max-w-xs";
}
case "large": {
return "tw-max-w-4xl";
}
default: {
return "tw-max-w-xl";
}
}
}
}

View File

@ -1,12 +0,0 @@
import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { ModalSimpleComponent } from "./modal-simple.component";
import { ModalComponent } from "./modal.component";
@NgModule({
imports: [CommonModule],
exports: [ModalComponent, ModalSimpleComponent],
declarations: [ModalComponent, ModalSimpleComponent],
})
export class ModalModule {}

View File

@ -1,80 +0,0 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { ButtonModule } from "../button";
import { ModalComponent } from "./modal.component";
export default {
title: "Component Library/Modals/Modal",
component: ModalComponent,
decorators: [
moduleMetadata({
imports: [ButtonModule],
}),
],
args: {
modalSize: "small",
},
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library",
},
},
} as Meta;
const Template: Story<ModalComponent> = (args: ModalComponent) => ({
props: args,
template: `
<bit-modal [modalSize]="modalSize">
<span bit-modal-title> Modal Title </span>
<span bit-modal-content>
Modal body text goes here.
</span>
<div bit-modal-footer class="tw-flex tw-flex-row tw-gap-2">
<button bitButton buttonType="primary"> Save </button>
<button bitButton buttonType="secondary"> Cancel </button>
</div>
</bit-modal>
`,
});
export const Default = Template.bind({});
Default.args = {
modalSize: "default",
};
export const Small = Template.bind({});
Small.args = {
modalSize: "small",
};
export const Large = Template.bind({});
Large.args = {
modalSize: "large",
};
const TemplateScrolling: Story<ModalComponent> = (args: ModalComponent) => ({
props: args,
template: `
<bit-modal [modalSize]="modalSize">
<span bit-modal-title> Modal Title </span>
<span bit-modal-content>
Modal body text goes here.<br>
<ng-container *ngFor="let _ of [].constructor(100)">
repeating lines of characters <br>
</ng-container>
end of sequence!
</span>
<div bit-modal-footer class="tw-flex tw-flex-row tw-gap-2">
<button bitButton buttonType="primary"> Save </button>
<button bitButton buttonType="secondary"> Cancel </button>
</div>
</bit-modal>
`,
});
export const ScrollingContent = TemplateScrolling.bind({});
ScrollingContent.args = {
modalSize: "small",
};

View File

@ -10,6 +10,7 @@ module.exports = {
colors: {
transparent: colors.transparent,
current: colors.current,
black: colors.black,
primary: {
300: "var(--color-primary-300)",
500: "var(--color-primary-500)",
@ -68,6 +69,12 @@ module.exports = {
DEFAULT: theme("colors.background"),
...theme("colors"),
}),
extend: {
width: {
"50vw": "50vw",
"75vw": "75vw",
},
},
},
plugins: [],
};