Skip to content

Commit

Permalink
feat(toast): Add toast component
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkoOleksiyenko authored and quentinderoubaix committed Feb 16, 2024
1 parent 405c1bf commit b8b87d1
Show file tree
Hide file tree
Showing 58 changed files with 1,885 additions and 17 deletions.
65 changes: 65 additions & 0 deletions angular/demo/src/app/samples/toast/action.route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type {ToastWidget} from '@agnos-ui/angular';
import {AgnosUIAngularModule, SlotComponent, SlotDirective, provideWidgetsConfig} from '@agnos-ui/angular';
import {Component, inject} from '@angular/core';
import {DomSanitizer} from '@angular/platform-browser';
import biArrowCounterClockwise from 'bootstrap-icons/icons/arrow-counterclockwise.svg';
import biCheckCircleFill from 'bootstrap-icons/icons/check-circle-fill.svg';

@Component({
standalone: true,
imports: [AgnosUIAngularModule],
providers: [
provideWidgetsConfig((config) => {
config.toast = {...config.toast, className: 'text-bg-success', autohide: false, dismissible: true, slotStructure: ToastIconComponent};
return config;
}),
],
template: `
<p>To put the action in the toast simply create custom contents.</p>
<p><strong>Note:</strong> When header is not present default display is <code>flex</code></p>
<div class="col-auto col-md-6">
<au-component #toast auToast>This is a toast with action</au-component>
</div>
<button class="btn btn-primary mt-2" (click)="toast.api.open()">Reset</button>
`,
})
export default class ActionToastComponent {}

@Component({
standalone: true,
imports: [SlotDirective],
providers: [SlotDirective],
selector: 'app-icon-structure',
host: {
style: 'display: contents;',
},
template: `
<div class="d-flex w-100">
<div class="d-flex align-items-center flex-grow-1 toast-body">
<span class="d-flex me-2" [innerHTML]="sanitizer.bypassSecurityTrustHtml(biCheckCircleFill)"></span>
<ng-template [auSlot]="state.slotDefault" [auSlotProps]="{widget, state}"></ng-template>
<button type="button" class="btn btn-sm ms-auto text-bg-success" (click)="actionDemo()">
<span class="me-2" [innerHTML]="sanitizer.bypassSecurityTrustHtml(biArrowCounterClockwise)"></span>Undo
</button>
</div>
@if (state.dismissible) {
<button
type="button"
class="btn-close btn-close-white me-2 m-auto"
(click)="widget.api.close()"
[attr.aria-label]="state.ariaCloseButtonLabel"
></button>
}
</div>
`,
})
export class ToastIconComponent extends SlotComponent<ToastWidget> {
sanitizer = inject(DomSanitizer);

biCheckCircleFill = biCheckCircleFill;
biArrowCounterClockwise = biArrowCounterClockwise;

actionDemo() {
window.alert('Undo');
}
}
39 changes: 39 additions & 0 deletions angular/demo/src/app/samples/toast/default.route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {ToastComponent, provideWidgetsConfig} from '@agnos-ui/angular';
import {Component} from '@angular/core';

@Component({
standalone: true,
imports: [ToastComponent],
providers: [
provideWidgetsConfig((config) => {
config.toast = {...config.toast, autohide: false, dismissible: false};
return config;
}),
],
template: `
<p class="mb-2">
Color schemes are based on the Bootstrap classes, in order to have out-of-the-box text accessibility use the helper classes
<code>.text-bg-*</code>, e.g. <code>.text.bg-primary</code>
</p>
<au-component #toast auToast auClassName="text-bg-primary" auSlotHeader="I am header" auDismissible>Simple primary toast</au-component>
<button class="btn btn-primary my-2" (click)="toast.api.open()">Reset</button>
<h3>Variations</h3>
<div class="row gy-2 gx-3">
<div class="col-auto">
<au-component auToast auClassName="text-bg-success">This is a success toast</au-component>
</div>
<div class="col-auto">
<au-component auToast auClassName="text-bg-danger">This is an error toast</au-component>
</div>
<div class="col-auto">
<au-component auToast auClassName="text-bg-info">This is an info toast</au-component>
</div>
<div class="col-auto">
<au-component auToast auClassName="text-bg-warning">This is a warning toast</au-component>
</div>
</div>
`,
})
export default class DefaultToastComponent {}
96 changes: 96 additions & 0 deletions angular/demo/src/app/samples/toast/dynamic.route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type {ToastProps} from '@agnos-ui/angular';
import {AgnosUIAngularModule, ToastComponent} from '@agnos-ui/angular';
import {Component, Injectable, inject} from '@angular/core';
import {FormsModule} from '@angular/forms';

export enum ToastPositions {
topLeft = 'top-0 start-0',
topCenter = 'top-0 start-50 translate-middle-x',
topRight = 'top-0 end-0',
middleLeft = 'top-50 start-0 translate-middle-y',
middleCenter = 'top-50 start-50 translate-middle',
middleRight = 'top-50 end-0 translate-middle-y',
bottomLeft = 'bottom-0 start-0',
bottomCenter = 'bottom-0 start-50 translate-middle-x',
bottomRight = 'bottom-0 end-0',
}

@Injectable({providedIn: 'root'})
class ToastService {
toastMap: Map<string, Partial<ToastProps>[]> = new Map(Object.values(ToastPositions).map((entry) => [entry, []]));

add(toast: Partial<ToastProps>) {
this.toastMap.get(toast.className!)?.push(toast);
}

remove(toast: Partial<ToastProps>) {
this.toastMap.set(
toast.className!,
this.toastMap.get(toast.className!)!.filter((t) => t !== toast),
);
}
}

@Component({
selector: 'app-toast-container',
standalone: true,
imports: [ToastComponent],
template: ` <div class="d-flex position-relative mt-2 w-100" aria-live="polite" aria-atomic="true" style="height: 500px; background-color: gray;">
@for (position of toastContainerService.toastMap.keys(); track position) {
<div class="toast-container p-3 {{ position }}">
@for (toast of toastContainerService.toastMap.get(position); track toast) {
<au-component auToast (auHidden)="removeToast(toast)" auSlotHeader="I am header" [auAutohide]="toast.autohide" [auDelay]="toast.delay"
>Simple toast</au-component
>
}
</div>
}
</div>`,
})
class ToastContainerComponent {
readonly toastContainerService = inject(ToastService);

removeToast(toast: Partial<ToastProps>) {
this.toastContainerService.remove(toast);
}
}

@Component({
standalone: true,
imports: [AgnosUIAngularModule, ToastContainerComponent, FormsModule],
template: `
<p class="mb-2">
To position toast wherever you want you should have a <code>toast-container</code> with a custom position defined by CSS classes.
</p>
<p class="mb-2">To <strong>stack</strong> toasts vertically, put them in the same container.</p>
<div class="d-flex justify-content-between">
<div class="d-flex form-group align-items-center">
<label class="me-3" for="positionSelect">Position: </label>
<select id="positionSelect" class="form-select w-auto" [(ngModel)]="position">
@for (position of positionList; track position) {
<option [value]="position.value">{{ position.label }}</option>
}
</select>
<button class="btn btn-primary addToast ms-2" (click)="addToast()">Show toast</button>
</div>
</div>
<app-toast-container />
`,
})
export default class DynamicToastComponent {
readonly toastContainerService = inject(ToastService);

positionList = Object.entries(ToastPositions).map((entry) => {
return {
value: entry[1],
label: entry[0],
};
});

position = this.positionList[0].value;

addToast() {
this.toastContainerService.add({autohide: true, delay: 3000, className: this.position});
}
}
23 changes: 23 additions & 0 deletions angular/demo/src/app/samples/toast/playground.route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type {ToastComponent} from '@agnos-ui/angular';
import {AgnosUIAngularModule} from '@agnos-ui/angular';
import {getToastDefaultConfig} from '@agnos-ui/core';
import {Component, ViewChild} from '@angular/core';
import {getUndefinedValues, hashChangeHook, provideHashConfig} from '../../utils';

const undefinedConfig = getUndefinedValues(getToastDefaultConfig());

@Component({
standalone: true,
imports: [AgnosUIAngularModule],
providers: provideHashConfig('toast'),
template: `<au-component auToast #widget></au-component>`,
})
export default class PlaygroundComponent {
@ViewChild('widget') widget: ToastComponent;

constructor() {
hashChangeHook((props) => {
this.widget?._widget.patch({...undefinedConfig, ...props});
});
}
}
1 change: 1 addition & 0 deletions angular/headless/src/components/toast/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './toast';
10 changes: 10 additions & 0 deletions angular/headless/src/components/toast/toast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type {AdaptWidgetSlots, WidgetProps, WidgetState, AdaptSlotContentProps, WidgetFactory} from '../../types';
import {createToast as coreCreateToast} from '@agnos-ui/core/components/toast';

export * from '@agnos-ui/core/components/toast';

export type ToastWidget = AdaptWidgetSlots<import('@agnos-ui/core/components/toast').ToastWidget>;
export type ToastProps = WidgetProps<ToastWidget>;
export type ToastState = WidgetState<ToastWidget>;
export type ToastContext = AdaptSlotContentProps<import('@agnos-ui/core/components/toast').ToastContext>;
export const createToast: WidgetFactory<ToastWidget> = coreCreateToast as any;
1 change: 1 addition & 0 deletions angular/headless/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ export * from './components/progressbar';
export * from './components/rating';
export * from './components/select';
export * from './components/slider';
export * from './components/toast';

export * from './generated';
5 changes: 4 additions & 1 deletion angular/lib/src/agnos-ui-angular.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
} from './components/accordion/accordion.component';
import {SliderComponent} from './components/slider/slider.component';
import {ProgressbarComponent, ProgressbarContentDirective} from './components/progressbar/progressbar.component';

import {ToastBodyDirective, ToastComponent, ToastStructureDirective} from './components/toast/toast.component';
/* istanbul ignore next */
const components = [
SlotDirective,
Expand Down Expand Up @@ -65,6 +65,9 @@ const components = [
SliderComponent,
ProgressbarComponent,
ProgressbarContentDirective,
ToastComponent,
ToastStructureDirective,
ToastBodyDirective,
];

@NgModule({
Expand Down
Loading

0 comments on commit b8b87d1

Please sign in to comment.