Skip to content

Commit

Permalink
feat(design): convert toast component to standalone (#3132)
Browse files Browse the repository at this point in the history
  • Loading branch information
xelaint authored Oct 11, 2024
1 parent d2cfc71 commit 8ccd628
Show file tree
Hide file tree
Showing 20 changed files with 198 additions and 73 deletions.
4 changes: 2 additions & 2 deletions apps/design-land/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { DaffButtonModule } from '@daffodil/design/button';
import { DaffLinkSetModule } from '@daffodil/design/link-set';
import { DaffNavbarModule } from '@daffodil/design/navbar';
import { DaffSidebarModule } from '@daffodil/design/sidebar';
import { DaffToastModule } from '@daffodil/design/toast';
import { daffProvideToast } from '@daffodil/design/toast';
import { DaffThemeSwitchButtonModule } from '@daffodil/theme-switch';

import { DesignLandAppRoutingModule } from './app-routing.module';
Expand Down Expand Up @@ -41,11 +41,11 @@ import { DesignLandTemplateModule } from './core/template/template.module';
FontAwesomeModule,
DesignLandNavModule,
DesignLandTemplateModule,
DaffToastModule,
],
providers: [
DAFF_THEME_INITIALIZER,
provideHttpClient(withInterceptorsFromDi()),
daffProvideToast(),
],
})
export class AppModule { }
2 changes: 0 additions & 2 deletions apps/design-land/src/app/toast/toast.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';

import { DaffArticleModule } from '@daffodil/design/article';
import { DaffToastModule } from '@daffodil/design/toast';

import { DesignLandToastRoutingModule } from './toast-routing-module';
import { DesignLandToastComponent } from './toast.component';
Expand All @@ -22,7 +21,6 @@ import { DesignLandExampleViewerModule } from '../core/code-preview/container/ex
DesignLandArticleEncapsulatedModule,

DaffArticleModule,
DaffToastModule,
],
})
export class DesignLandToastModule {}
141 changes: 108 additions & 33 deletions libs/design/toast/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,117 @@ Toasts are small messages designed to mimic push notifications. They are used to
## Overview
Toasts should be used to display temporary messages about actions or events that occured or in need of attention, with no relation to content on a page. For messaging that provide context in close promixity to a piece of content within a page, use the [Notification](/libs/design/notification/README.md) component.

### Basic Toast
<design-land-article-encapsulated>
<design-land-example-viewer-container example="default-toast"></design-land-example-viewer-container>
</design-land-article-encapsulated>
## Basic Toast
<design-land-example-viewer-container example="default-toast"></design-land-example-viewer-container>

## Setting up the component
`daffProviderToast()` should be added as a provider either in your application's root component for global use or in a specific feature component.

```ts
import { daffProvideToast } from '@daffodil/design/toast';

@NgModule({
providers: [
daffProvideToast(),
]
)}

export class AppModule {}
```
### Configurations
Toast can be configured by using the `DaffToastService`.
The following is an example of a toast with a duration:
```ts
constructor(private toastService: DaffToastService) {}

open() {
this.toast = this.toastService.open({
title: 'Update Complete',
message: 'This page has been updated to the newest version.',
},
{
duration: 5000,
});
import {
ChangeDetectionStrategy,
Component,
} from '@angular/core';

import {
DaffToast
DaffToastService,
} from '@daffodil/design/toast';

@Component({
selector: 'custom-toast',
templateUrl: './custom-toast.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
})
export class CustomToastComponent {
private toast: DaffToast;

constructor(private toastService: DaffToastService) {}

open() {
this.toast = this.toastService.open({
title: 'Update Complete',
message: 'This page has been updated to the newest version.',
},
{
duration: 5000,
});
}
}
```
The following is an example of a toast with actions:
```ts
open() {
this.toast = this.toastService.open({
title: 'Update Available',
message: 'A new version of this page is available.',
actions: [
{ content: 'Update', color: 'theme-contrast', size: 'sm', eventEmitter: this.update },
{ content: 'Remind me later', type: 'flat', size: 'sm', eventEmitter: this.closeToast },
]
});
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
OnInit,
} from '@angular/core';

import { DAFF_BUTTON_COMPONENTS } from '@daffodil/design/button';
import {
DaffToast,
DaffToastAction,
DaffToastService,
} from '@daffodil/design/toast';

@Component({
selector: 'action-toast',
templateUrl: './action-toast.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
DAFF_BUTTON_COMPONENTS,
],
})
export class ActionToastComponent implements OnInit {
private toast: DaffToast;

constructor(private toastService: DaffToastService) {}

update = new EventEmitter<DaffToastAction>();

closeToast = new EventEmitter<DaffToastAction>();

open() {
this.toast = this.toastService.open({
title: 'Update Available',
message: 'A new version of this page is available.',
actions: [
{ content: 'Update', color: 'theme-contrast', size: 'sm', eventEmitter: this.update },
{ content: 'Remind me later', type: 'flat', size: 'sm', eventEmitter: this.closeToast },
]
});
}

ngOnInit() {
this.update.subscribe(() => {
});

this.closeToast.subscribe(() => {
this.toastService.close(this.toast);
});
}
}
```
Expand All @@ -55,35 +130,35 @@ The following configurations are available in the `DaffToastService`:
The `actions` configurations are based on the properties of the `DaffButtonComponent` (view [Button Documentation](/libs/design/button/README.md)) with the addition of `data` and `eventEmitter`.
### Dismissal
## Dismissal
A toast can be dismissed via a timed duration, a close button, or the `ESC` key.
##### Timed duration
### Timed duration
A toast with actions will persist until one of the actions have been interacted with, or is dismissed by the close button or the `ESC` key. Actionable toasts should be persistent, but a duration is allowed to be set. If duration must be set, make sure it's long enough for users to engage with the actions.
By default, a toast without actions will be dismissed after `5000ms`. This can be updated by setting `duration` through the `DaffToastService`.
#### Toast with custom duration
<design-land-example-viewer-container example="toast-with-custom-duration"></design-land-example-viewer-container>
##### Close button
### Close button
The close button is shown by default but can be hidden by setting `dismissible: false` through the `DaffToastService`.
##### Escape Key
### Escape Key
A toast can be dismissed by using the `ESC` key if it has actions and is focus trapped.
### Stacking
## Stacking
A maximum of three toasts can be shown at a time. Toasts are stacked vertically, with the most recent toast displayed on top.
### Statuses
## Statuses
The status color of a toast can be updated by using the `status` property.
Supported statuses: `warn | danger | success`
#### Toast with statuses
### Toast with statuses
<design-land-example-viewer-container example="toast-status"></design-land-example-viewer-container>
### Positions
## Positions
| Property | Value | Default |
| ------------ | ------------------------ | ------- |
Expand All @@ -106,10 +181,10 @@ providers: [
The position of a toast on a mobile device will always be on the bottom center.
#### Toast with configurable positions
### Toast with configurable positions
<design-land-example-viewer-container example="toast-positions"></design-land-example-viewer-container>
### Accessibility
## Accessibility
By default, toasts use a `role="status"` to announce messages. It's the equivalent of `aria-live="polite"`, which does not interrupt a user's current activity and waits until they are idle to make the announcement. When a toast has actions, a `role="alertdialog"` is used. The toast will be focus trapped and focus immediately moves to the actions.
Avoid setting a duration on toasts with actions because they will disappear automatically, making it difficult for users to interact with the actions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
<select [formControl]="statusControl">
<option value="success">Success</option>
<option value="warn">Warn</option>
<option value="error">Error</option>
<option value="danger">Danger</option>
</select>
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ import {
} from '@daffodil/design/toast';

const status: Record<string, DaffToastData> = {
error: {
danger: {
title: 'Server error',
message: 'There is a server error.',
},
success: {
title: 'Update complete',
Expand Down Expand Up @@ -59,7 +60,7 @@ export class ToastStatusComponent {
...status[this.statusControl.value],
},
{
duration: this.statusControl.value === 'error' ? undefined : 5000,
duration: this.statusControl.value === 'danger' ? undefined : 5000,
},
);
}
Expand Down
1 change: 1 addition & 0 deletions libs/design/toast/src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export * from './toast/toast.component';
export * from './toast-actions/toast-actions.directive';
export * from './toast-title/toast-title.directive';
export * from './toast-message/toast-message.directive';
export * from './toast/toast-provider';
12 changes: 5 additions & 7 deletions libs/design/toast/src/service/toast.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
DaffFocusStackService,
DaffPrefixSuffixModule,
} from '@daffodil/design';
import { DaffButtonModule } from '@daffodil/design/button';

import { DaffToastPositionService } from './position.service';
import { DaffToastService } from './toast.service';
Expand All @@ -32,22 +31,20 @@ describe('@daffodil/design/toast | DaffToastService', () => {
TestBed.configureTestingModule({
imports: [
DaffPrefixSuffixModule,
DaffButtonModule,
FontAwesomeModule,
PortalModule,
OverlayModule,
NoopAnimationsModule,
],
providers: [
DaffToastPositionService,
],
declarations: [

DaffToastComponent,
DaffToastActionsDirective,
DaffToastTitleDirective,
DaffToastMessageDirective,
DaffToastTemplateComponent,
],
providers: [
DaffToastPositionService,
],
});

const overlay = TestBed.inject(Overlay);
Expand All @@ -73,6 +70,7 @@ describe('@daffodil/design/toast | DaffToastService', () => {
TestBed.inject(BreakpointObserver),
TestBed.inject(DaffToastPositionService),
TestBed.inject(DaffFocusStackService),
null,
);
});

Expand Down
8 changes: 4 additions & 4 deletions libs/design/toast/src/service/toast.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import {
EventEmitter,
Inject,
Injectable,
Injector,
OnDestroy,
Optional,
SkipSelf,
} from '@angular/core';
import {
EMPTY,
interval,
merge,
of,
Subscription,
Expand Down Expand Up @@ -49,9 +49,8 @@ import {
DaffToastConfiguration,
} from '../toast/toast-config';
import { DaffToastTemplateComponent } from '../toast/toast-template.component';
import { DaffToastModule } from '../toast.module';

@Injectable({ providedIn: DaffToastModule })
@Injectable()
export class DaffToastService implements OnDestroy {

private _sub: Subscription;
Expand All @@ -69,6 +68,7 @@ export class DaffToastService implements OnDestroy {
private mediaQuery: BreakpointObserver,
private toastPosition: DaffToastPositionService,
private focusStack: DaffFocusStackService,
private injector: Injector,
) {
this._sub = this.mediaQuery.observe(DaffBreakpoints.MOBILE).pipe(
filter(() => this._overlayRef !== undefined),
Expand All @@ -84,7 +84,7 @@ export class DaffToastService implements OnDestroy {
private _attachToastTemplate(
overlayRef: OverlayRef,
): ComponentRef<DaffToastTemplateComponent> {
const template = overlayRef.attach(new ComponentPortal(DaffToastTemplateComponent));
const template = overlayRef.attach(new ComponentPortal(DaffToastTemplateComponent, null, this.injector));
return template;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import { DaffToastActionsDirective } from './toast-actions.directive';
template: `
<div daffToastActions><button>Click me!</button></div>
`,
standalone: true,
imports: [
DaffToastActionsDirective,
],
})
class WrapperComponent {}

Expand All @@ -25,8 +29,7 @@ describe('DaffToastActionsDirective', () => {

beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [
DaffToastActionsDirective,
imports: [
WrapperComponent,
],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {

@Directive({
selector: '[daffToastActions]',
standalone: true,
})

export class DaffToastActionsDirective {
Expand Down
Loading

0 comments on commit 8ccd628

Please sign in to comment.