Merge branch 'main' into feature/content-picker

This commit is contained in:
Jesper Møller Jensen
2022-08-09 10:17:44 +02:00
21 changed files with 4655 additions and 443 deletions

View File

@@ -2,6 +2,9 @@ module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-a11y'],
framework: '@storybook/web-components',
features: {
previewMdx2: true,
},
core: {
builder: '@storybook/builder-vite',
},

View File

@@ -2,6 +2,15 @@
body {
padding: 0px !important;
}
pre {
font-family: monospace;
font-size: 14px;
background-color: var(--uui-color-background);
padding: 1.5em;
border-radius: 3px;
line-height: 1.3em;
}
</style>
<script>

File diff suppressed because it is too large Load Diff

View File

@@ -40,6 +40,7 @@
"openapi-typescript-fetch": "^1.1.3",
"router-slot": "^1.5.5",
"rxjs": "^7.5.6",
"uuid": "^8.3.2"
"@umbraco-ui/uui-modal": "file:umbraco-ui-uui-modal-0.0.0.tgz",
"@umbraco-ui/uui-modal-container": "file:umbraco-ui-uui-modal-container-0.0.0.tgz",
"@umbraco-ui/uui-modal-dialog": "file:umbraco-ui-uui-modal-dialog-0.0.0.tgz",
@@ -47,15 +48,18 @@
},
"devDependencies": {
"@babel/core": "^7.18.10",
"@mdx-js/react": "^2.1.2",
"@open-wc/testing": "^3.1.6",
"@storybook/addon-a11y": "^6.5.9",
"@storybook/addon-actions": "^6.5.9",
"@storybook/addon-essentials": "^6.5.9",
"@storybook/addon-links": "^6.5.9",
"@storybook/builder-vite": "^0.2.2",
"@storybook/mdx2-csf": "^0.0.3",
"@storybook/web-components": "^6.5.9",
"@types/chai": "^4.3.1",
"@types/mocha": "^9.1.1",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.32.0",
"@typescript-eslint/parser": "^5.32.0",
"@web/dev-server-esbuild": "^0.3.1",

View File

@@ -3,8 +3,8 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html, LitElement } from 'lit';
import { UmbContextProviderMixin } from '../core/context';
import { UmbNotificationService } from '../core/services/notification';
import { UmbModalService } from '../core/services/modal/modal.service';
import { UmbNotificationService } from '../core/services/notification.service';
import { UmbDataTypeStore } from '../core/stores/data-type.store';
import { UmbNodeStore } from '../core/stores/node.store';

View File

@@ -4,7 +4,7 @@ import { customElement, state } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
import { Subscription } from 'rxjs';
import { UmbContextConsumerMixin } from '../../core/context';
import { UmbNotificationService } from '../../core/services/notification.service';
import type { UmbNotificationService, UmbNotificationHandler } from '../../core/services/notification';
@customElement('umb-backoffice-notification-container')
export class UmbBackofficeNotificationContainer extends UmbContextConsumerMixin(LitElement) {
@@ -24,7 +24,7 @@ export class UmbBackofficeNotificationContainer extends UmbContextConsumerMixin(
];
@state()
private _notifications: any[] = [];
private _notifications: UmbNotificationHandler[] = [];
private _notificationService?: UmbNotificationService;
private _notificationSubscription?: Subscription;
@@ -41,11 +41,13 @@ export class UmbBackofficeNotificationContainer extends UmbContextConsumerMixin(
private _useNotifications() {
this._notificationSubscription?.unsubscribe();
this._notificationService?.notifications.subscribe((notifications: Array<any>) => {
this._notificationService?.notifications.subscribe((notifications: Array<UmbNotificationHandler>) => {
this._notifications = notifications;
});
}
// TODO: listen to close event and remove notification from store.
private _closedNotificationHandler(notificationHandler: UmbNotificationHandler) {
notificationHandler.close();
}
disconnectedCallback(): void {
@@ -55,12 +57,15 @@ export class UmbBackofficeNotificationContainer extends UmbContextConsumerMixin(
render() {
return html`
<uui-toast-notification-container auto-close="7000" bottom-up id="notifications">
<uui-toast-notification-container bottom-up id="notifications">
${repeat(
this._notifications,
(notification) => notification.key,
(notification) => html` <uui-toast-notification color="positive">
<uui-toast-notification-layout .headline=${notification.headline}> </uui-toast-notification-layout>
(notification: UmbNotificationHandler) => notification.key,
(notification) => html`<uui-toast-notification
color="${notification.color}"
.autoClose="${notification.duration}"
@closed="${() => this._closedNotificationHandler(notification)}">
${notification.element}
</uui-toast-notification>`
)}
</uui-toast-notification-container>

View File

@@ -5,7 +5,7 @@ import { UmbContextConsumerMixin } from '../../core/context';
import { UmbNodeStore } from '../../core/stores/node.store';
import { map, Subscription } from 'rxjs';
import { DocumentNode } from '../../mocks/data/content.data';
import { UmbNotificationService } from '../../core/services/notification.service';
import type { UmbNotificationService } from '../../core/services/notification';
import { UmbExtensionManifest, UmbExtensionManifestEditorView, UmbExtensionRegistry } from '../../core/extension';
import { IRoutingInfo, RouterSlot } from 'router-slot';
@@ -13,6 +13,7 @@ import { IRoutingInfo, RouterSlot } from 'router-slot';
// TODO: Make this dynamic, use load-extensions method to loop over extensions for this node.
import '../editor-views/editor-view-node-edit.element';
import '../editor-views/editor-view-node-info.element';
import { UmbNotificationDefaultData } from '../../core/services/notification/layouts/default';
@customElement('umb-node-editor')
export class UmbNodeEditor extends UmbContextConsumerMixin(LitElement) {
@@ -156,7 +157,8 @@ export class UmbNodeEditor extends UmbContextConsumerMixin(LitElement) {
// TODO: What if store is not present, what if node is not loaded....
if (this._node) {
this._nodeStore?.save([this._node]).then(() => {
this._notificationService?.peek('Document saved');
const data: UmbNotificationDefaultData = { message: 'Document Saved' };
this._notificationService?.peek('positive', { data });
});
}
}

View File

@@ -1,7 +1,8 @@
import { html, LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { UmbContextConsumerMixin } from '../../core/context';
import { UmbNotificationService } from '../../core/services/notification.service';
import type { UmbNotificationDefaultData } from '../../core/services/notification/layouts/default';
import type { UmbNotificationService } from '../../core/services/notification';
import type { UmbPropertyAction } from './property-action/property-action.model';
@customElement('umb-property-action-copy')
@@ -26,7 +27,8 @@ export default class UmbPropertyActionCopyElement extends UmbContextConsumerMixi
}
private _handleLabelClick () {
this._notificationService?.peek('Copied to clipboard');
const data: UmbNotificationDefaultData = { message: 'Copied to clipboard' };
this._notificationService?.peek('positive', { data });
// TODO: how do we want to close the menu? Testing an event based approach
this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }));
}

View File

@@ -1,7 +1,8 @@
import { html, LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';
import { UmbContextConsumerMixin } from '../../core/context';
import { UmbNotificationService } from '../../core/services/notification.service';
import type { UmbNotificationDefaultData } from '../../core/services/notification/layouts/default';
import type { UmbNotificationService } from '../../core/services/notification';
@customElement('umb-property-editor-context-example')
export default class UmbPropertyEditorContextExample extends UmbContextConsumerMixin(LitElement) {
@@ -15,7 +16,8 @@ export default class UmbPropertyEditorContextExample extends UmbContextConsumerM
});
}
private _onClick = () => {
this._notificationService?.peek('Hello from property editor');
const data: UmbNotificationDefaultData = { message: 'Hello from property editor' };
this._notificationService?.peek('positive', { data });
};
render() {

View File

@@ -1,18 +0,0 @@
import { BehaviorSubject, Observable } from 'rxjs';
type TempNotificationObject = {
headline: string;
key: string;
};
export class UmbNotificationService {
private _notifications: BehaviorSubject<Array<TempNotificationObject>> = new BehaviorSubject(
<Array<TempNotificationObject>>[]
);
public readonly notifications: Observable<Array<TempNotificationObject>> = this._notifications.asObservable();
// TODO: this is just a quick solution to get notifications in POC. (suppose to get much more complex data set for this, enabling description, actions and event custom elements).
peek(headline: string) {
this._notifications.next([...this._notifications.getValue(), { headline: headline, key: Date.now().toString() }]);
}
}

View File

@@ -0,0 +1,2 @@
export * from './notification.service';
export * from './notification-handler';

View File

@@ -0,0 +1 @@
export * from './notification-layout-default.element';

View File

@@ -0,0 +1,29 @@
import { html, LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { UUITextStyles } from '@umbraco-ui/uui-css';
import type { UmbNotificationHandler } from '../../';
import { ifDefined } from 'lit-html/directives/if-defined.js';
export interface UmbNotificationDefaultData {
message: string;
headline?: string;
}
@customElement('umb-notification-layout-default')
export class UmbNotificationLayoutDefaultElement extends LitElement {
static styles = [UUITextStyles];
@property({ attribute: false })
notificationHandler!: UmbNotificationHandler;
@property({ type: Object })
data!: UmbNotificationDefaultData;
render() {
return html`
<uui-toast-notification-layout id="layout" headline="${ifDefined(this.data.headline)}" class="uui-text">
<div id="message">${this.data.message}</div>
</uui-toast-notification-layout>
`;
}
}

View File

@@ -0,0 +1,22 @@
import { Meta, Story } from '@storybook/web-components';
import { html } from 'lit';
import { UmbNotificationLayoutDefaultElement, UmbNotificationDefaultData } from '.';
export default {
title: 'API/Notifications/Layouts/Default',
component: 'umb-notification-layout-default',
id: 'notification-layout-default',
} as Meta;
const data: UmbNotificationDefaultData = {
headline: 'Headline',
message: 'This is a default notification',
};
const Template: Story<UmbNotificationLayoutDefaultElement> = () => html`
<uui-toast-notification .open=${true}>
<umb-notification-layout-default .data=${data}></umb-notification-layout-default>
</uui-toast-notification>
`;
export const Default = Template.bind({});

View File

@@ -0,0 +1,56 @@
import { fixture, expect, html } from '@open-wc/testing';
import { UmbNotificationHandler } from '../../';
import type { UmbNotificationLayoutDefaultElement, UmbNotificationDefaultData } from '.';
import '.';
import { UUIToastNotificationLayoutElement } from '@umbraco-ui/uui';
describe('UmbNotificationLayoutDefault', () => {
let element: UmbNotificationLayoutDefaultElement;
const data: UmbNotificationDefaultData = {
headline: 'Notification Headline',
message: 'Notification message',
};
const options = { elementName: 'umb-notification-layout-default', data };
let notificationHandler: UmbNotificationHandler;
beforeEach(async () => {
notificationHandler = new UmbNotificationHandler(options);
element = await fixture(
html`<umb-notification-layout-default
.notificationHandler=${notificationHandler}
.data=${options.data}></umb-notification-layout-default>`
);
});
describe('Public API', () => {
describe('properties', () => {
it('has a notificationHandler property', () => {
expect(element).to.have.property('notificationHandler');
});
it('has a data property', () => {
expect(element).to.have.property('data');
});
});
});
describe('Data options', () => {
describe('Headline', () => {
it('sets headline on uui notification layout', () => {
const uuiNotificationLayout: UUIToastNotificationLayoutElement | null =
element.renderRoot.querySelector('#layout');
expect(uuiNotificationLayout?.getAttribute('headline')).to.equal('Notification Headline');
});
});
describe('message', () => {
it('renders the message', () => {
const messageElement: HTMLElement | null = element.renderRoot.querySelector('#message');
expect(messageElement?.innerText).to.equal('Notification message');
});
});
});
});

View File

@@ -0,0 +1,121 @@
import { assert, expect } from '@open-wc/testing';
import { validate as uuidValidate } from 'uuid';
import { UmbNotificationHandler } from './notification-handler';
import type { UmbNotificationDefaultData } from './layouts/default';
import type { UmbNotificationOptions } from './';
describe('UCPNotificationHandler', () => {
let notificationHandler: UmbNotificationHandler;
beforeEach(async () => {
const options: UmbNotificationOptions<UmbNotificationDefaultData> = {};
notificationHandler = new UmbNotificationHandler(options);
});
describe('Public API', () => {
describe('properties', () => {
it('has a key property', () => {
expect(notificationHandler).to.have.property('key');
expect(uuidValidate(notificationHandler.key)).to.be.true;
});
it('has an element property', () => {
expect(notificationHandler).to.have.property('element');
});
it('has an color property', () => {
expect(notificationHandler).to.have.property('color');
});
it('has an duration property', () => {
expect(notificationHandler).to.have.property('duration');
});
it('sets a default duration to 6000 ms', () => {
expect(notificationHandler.duration).to.equal(6000);
});
});
describe('methods', () => {
it('has a close method', () => {
expect(notificationHandler).to.have.property('close').that.is.a('function');
});
it('has a onClose method', () => {
expect(notificationHandler).to.have.property('onClose').that.is.a('function');
});
it('returns result from close method in onClose promise', () => {
notificationHandler.close('result value');
const onClose = notificationHandler.onClose();
expect(onClose).that.is.a('promise');
expect(onClose).to.be.not.null;
if (onClose !== null) {
return onClose.then((result) => {
expect(result).to.equal('result value');
});
}
return assert.fail('onClose should not have returned ´null´');
});
});
});
describe('Layout', () => {
describe('Default Layout', () => {
let defaultLayoutNotification: UmbNotificationHandler;
beforeEach(async () => {
const options: UmbNotificationOptions<UmbNotificationDefaultData> = {
color: 'positive',
data: {
message: 'Notification default layout message',
},
};
defaultLayoutNotification = new UmbNotificationHandler(options);
});
it('creates a default layout if a custom element name havnt been specified', () => {
expect(defaultLayoutNotification.element.tagName).to.equal('UMB-NOTIFICATION-LAYOUT-DEFAULT');
});
it('it sets notificationHandler on custom element', () => {
expect(defaultLayoutNotification.element.notificationHandler).to.equal(defaultLayoutNotification);
});
it('it sets data on custom element', () => {
expect(defaultLayoutNotification.element.data.message).to.equal('Notification default layout message');
});
});
describe('Custom Layout', () => {
let customLayoutNotification: UmbNotificationHandler;
beforeEach(async () => {
const options: UmbNotificationOptions<UmbNotificationDefaultData> = {
elementName: 'umb-notification-test-element',
color: 'positive',
data: {
message: 'Notification custom layout message',
},
};
customLayoutNotification = new UmbNotificationHandler(options);
});
it('creates a custom element', () => {
expect(customLayoutNotification.element.tagName).to.equal('UMB-NOTIFICATION-TEST-ELEMENT');
});
it('it sets notificationHandler on custom element', () => {
expect(customLayoutNotification.element.notificationHandler).to.equal(customLayoutNotification);
});
it('it sets data on custom element', () => {
expect(customLayoutNotification.element.data.message).to.equal('Notification custom layout message');
});
});
});
});

View File

@@ -0,0 +1,71 @@
import { v4 as uuidv4 } from 'uuid';
import type { UmbNotificationOptions, UmbNotificationData, UmbNotificationColor } from './';
import './layouts/default';
/**
* @export
* @class UmbNotificationHandler
*/
export class UmbNotificationHandler {
private _closeResolver: any;
private _closePromise: Promise<any>;
private _elementName?: string;
private _data: UmbNotificationData;
private _defaultColor: UmbNotificationColor = 'default';
private _defaultDuration = 6000;
private _defaultLayout = 'umb-notification-layout-default';
public key: string;
public element: any;
public color: UmbNotificationColor;
public duration: number | null;
/**
* Creates an instance of UmbNotificationHandler.
* @param {UmbNotificationOptions} options
* @memberof UmbNotificationHandler
*/
constructor(options: UmbNotificationOptions<UmbNotificationData>) {
this.key = uuidv4();
this.color = options.color || this._defaultColor;
this.duration = options.duration !== undefined ? options.duration : this._defaultDuration;
this._elementName = options.elementName || this._defaultLayout;
this._data = options.data;
this._closePromise = new Promise((res) => {
this._closeResolver = res;
});
this._createLayoutElement();
}
/**
* @private
* @memberof UmbNotificationHandler
*/
private _createLayoutElement() {
if (!this._elementName) return;
this.element = document.createElement(this._elementName);
this.element.data = this._data;
this.element.notificationHandler = this;
}
/**
* @param {...any} args
* @memberof UmbNotificationHandler
*/
public close(...args: any) {
this._closeResolver(...args);
}
/**
* @return {*}
* @memberof UmbNotificationHandler
*/
public onClose(): Promise<any> {
return this._closePromise;
}
}

View File

@@ -0,0 +1,77 @@
import { BehaviorSubject, Observable } from 'rxjs';
import { UmbNotificationHandler } from './';
export type UmbNotificationData = any;
/**
* @export
* @interface UmbNotificationOptions
* @template UmbNotificationData
*/
export interface UmbNotificationOptions<UmbNotificationData> {
color?: UmbNotificationColor;
duration?: number | null;
elementName?: string;
data?: UmbNotificationData;
}
export type UmbNotificationColor = '' | 'default' | 'positive' | 'warning' | 'danger';
export class UmbNotificationService {
private _notifications: BehaviorSubject<Array<UmbNotificationHandler>> = new BehaviorSubject(
<Array<UmbNotificationHandler>>[]
);
public readonly notifications: Observable<Array<UmbNotificationHandler>> = this._notifications.asObservable();
/**
* @private
* @param {UmbNotificationOptions<UmbNotificationData>} options
* @return {*} {UmbNotificationHandler}
* @memberof UmbNotificationService
*/
private _open(options: UmbNotificationOptions<UmbNotificationData>): UmbNotificationHandler {
const notificationHandler = new UmbNotificationHandler(options);
notificationHandler.onClose().then(() => this._close(notificationHandler.key));
this._notifications.next([...this._notifications.getValue(), notificationHandler]);
return notificationHandler;
}
/**
* @private
* @param {string} key
* @memberof UmbNotificationService
*/
private _close(key: string) {
this._notifications.next(this._notifications.getValue().filter((notification) => notification.key !== key));
}
/**
* Opens a notification that automatically goes away after 6 sek.
* @param {UmbNotificationColor} color
* @param {UmbNotificationOptions<UmbNotificationData>} options
* @return {*}
* @memberof UmbNotificationService
*/
public peek(
color: UmbNotificationColor,
options: UmbNotificationOptions<UmbNotificationData>
): UmbNotificationHandler {
return this._open({ ...options, color });
}
/**
* Opens a notification that stays on the screen until dismissed by the user or custom code
* @param {UmbNotificationColor} color
* @param {UmbNotificationOptions<UmbNotificationData>} options
* @return {*}
* @memberof UmbNotificationService
*/
public stay(
color: UmbNotificationColor,
options: UmbNotificationOptions<UmbNotificationData>
): UmbNotificationHandler {
return this._open({ ...options, color, duration: null });
}
}

View File

@@ -0,0 +1,173 @@
import { Meta } from '@storybook/addon-docs';
<Meta title="API/Notifications/Intro" />
# Notifications
Notifications appear in the bottom right corner of the Backoffice. There are two types of notifications: "Peek" and "Stay".
**Peek notifications**
Goes away automatically and should be used as feedback on user actions.
**Stay notifications**
Stays on the screen until dismissed by the user or custom code. Stay notification should be used when you need user feedback or want to control when the notification disappears.
## Basic usage
### Consume UmbNotificationService from an element
The UmbNotification service can be used to open notifications.
```ts
import { html, LitElement } from 'lit';
import { UmbContextConsumerMixin } from './core/context';
import type { UmbNotificationService } from './core/services/notification';
class MyElement extends UmbContextConsumerMixin(LitElement) {
private _notificationService?: UmbNotificationService;
constructor () {
super();
this.consumeContext('umbNotificationService', (notificationService) => {
this._notificationService = notificationService;
// notificationService is now ready to be used
});
}
}
```
### Open a notification
A notification is opened by calling one of the helper methods on the UmbNotificationService. The methods will return an instance of UmbNotificationHandler.
```ts
import { html, LitElement } from 'lit';
import { state } from 'lit/decorators.js';
import { UmbContextConsumerMixin } from './core/context';
import type { UmbNotificationService, UmbNotificationDefaultData } from './core/services/notification';
class MyElement extends UmbContextConsumerMixin(LitElement) {
private _notificationService?: UmbNotificationService;
constructor () {
super();
this.consumeContext('umbNotificationService', (notificationService) => {
this._notificationService = notificationService;
// notificationService is now ready to be used
});
}
private _handleClick () {
const data: UmbNotificationDefaultData = { headline: 'Look at this', message: 'Something good happened' };
const notificationHandler = this._notificationService?.peek('positive', { data });
notificationHandler.onClose().then(() => {
// if you need any logic when the notification is closed you can run it here
});
}
render() {
return html`<button @click="${this._handleClick}">Open Notification</button>`;
}
}
```
## Advanced usage: creating custom layouts
The default layout will cover most cases, but there might be situations where we want a more complex layout. You can create a new Custom Element to use as the layout.
### Custom layout element
```ts
import { html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import { UUITextStyles } from '@umbraco-ui/uui-css';
import type { UmbNotificationHandler } from './core/services/notification';
export interface UmbNotificationCustomData {
headline: string;
user: {
name: string;
}
}
export class UmbNotificationLayoutCustom extends LitElement {
static styles = [
UUITextStyles
];
@property({ attribute: false })
public notificationHandler: UmbNotificationHandler;
@property({ type: Object })
public data: UmbNotificationCustomData;
private _handleConfirm () {
this.notificationHandler.close(true);
}
render() {
return html`
<uui-toast-notification-layout headline="${this.data.headline}" class="uui-text">
${this.data.user.name}
<uui-button slot="actions" @click="${this._handleConfirm}" label="Confirm">Confirm</uui-button>
</uui-toast-notification-layout>
`;
}
}
```
### Open notification with custom layout
```ts
import { html, LitElement } from 'lit';
import { UmbContextInjectMixin } from './core/context';
import type { UmbNotificationService, UmbNotificationOptions } from './core/services/notification';
import type { UmbNotificationCustomData } from './custom-notification-layout';
class MyElement extends LitElement {
private _notificationService?: UmbNotificationService;
constructor () {
super();
this.consumeContext('umbNotificationService', (notificationService) => {
this._notificationService = notificationService;
// notificationService is now ready to be used
});
}
private _handleClick () {
const options: UmbNotificationOptions<UmbNotificationCustomData> = {
elementName: 'umb-notification-layout-custom',
data: {
headline: 'Attention',
user: { name: 'Peter Parker' }
}
};
const notificationHandler = this._notificationService?.stay('default', options);
notificationHandler.onClose().then((result) => {
if (result) {
console.log('He agreed!');
}
});
}
render() {
return html`<button @click="${this._handleClick}">Open Notification</button>`;
}
}
```
## Best practices
* Keep messages in notifications short and friendly.
* Only use headlines when you need extra attention to the notification
* If a custom notification layout is only used in one module keep the files layout files local to that module.
* If a custom notification will be used across the project. Create it as a layout in the notification folder and add a helper method to the UmbNotificationService.

View File

@@ -0,0 +1,89 @@
import { Meta, Story } from '@storybook/web-components';
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { UmbNotificationService, UmbNotificationOptions, UmbNotificationColor } from '.';
import type { UmbNotificationDefaultData } from './layouts/default';
import { UmbContextConsumerMixin } from '../../context';
import '../../context/context-provider.element';
import '../../../backoffice/components/backoffice-notification-container.element';
import './layouts/default';
export default {
title: 'API/Notifications/Overview',
component: 'ucp-notification-layout-default',
decorators: [
(story) =>
html`<umb-context-provider key="umbNotificationService" .value=${new UmbNotificationService()}>
${story()}
</umb-context-provider>`,
],
} as Meta;
@customElement('story-notification-default-example')
class StoryNotificationDefaultExampleElement extends UmbContextConsumerMixin(LitElement) {
private _notificationService?: UmbNotificationService;
connectedCallback(): void {
super.connectedCallback();
this.consumeContext('umbNotificationService', (notificationService) => {
this._notificationService = notificationService;
console.log('notification service ready');
});
}
private _handleNotification = (color: UmbNotificationColor) => {
const options: UmbNotificationOptions<UmbNotificationDefaultData> = {
data: {
headline: 'Headline',
message: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
},
};
this._notificationService?.peek(color, options);
};
render() {
return html`
<uui-button @click="${() => this._handleNotification('default')}" label="Default"></uui-button>
<uui-button
@click="${() => this._handleNotification('positive')}"
label="Positive"
look="primary"
color="positive"></uui-button>
<uui-button
@click="${() => this._handleNotification('warning')}"
label="Warning"
look="primary"
color="warning"></uui-button>
<uui-button
@click="${() => this._handleNotification('danger')}"
label="Danger"
look="primary"
color="danger"></uui-button>
<umb-backoffice-notification-container></umb-backoffice-notification-container>
`;
}
}
const Template: Story = () => html`<story-notification-default-example></story-notification-default-example>`;
export const Default = Template.bind({});
Default.parameters = {
docs: {
source: {
language: 'js',
code: `
const options: UmbNotificationOptions<UmbNotificationDefaultData> = {
data: {
headline: 'Headline',
message: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
}
};
this._notificationService?.peek('positive', options);
`,
},
},
};

View File

@@ -0,0 +1,80 @@
import { expect } from '@open-wc/testing';
import { UmbNotificationService, UmbNotificationHandler } from './';
describe('UCPNotificationService', () => {
let notificationService: UmbNotificationService;
beforeEach(async () => {
notificationService = new UmbNotificationService();
});
describe('Public API', () => {
describe('properties', () => {
it('has a dialog property', () => {
expect(notificationService).to.have.property('notifications');
});
});
describe('methods', () => {
it('has a peek method', () => {
expect(notificationService).to.have.property('peek').that.is.a('function');
});
it('has a stay method', () => {
expect(notificationService).to.have.property('stay').that.is.a('function');
});
});
});
describe('peek', () => {
let peekNotificationHandler: UmbNotificationHandler | undefined = undefined;
beforeEach(async () => {
const peekOptions = {
data: { headline: 'Peek notification headline', message: 'Peek notification message' },
};
peekNotificationHandler = notificationService.peek('positive', peekOptions);
});
it('it sets notification color', () => {
expect(peekNotificationHandler?.color).to.equal('positive');
});
it('should set peek data on the notification element', () => {
const data = peekNotificationHandler?.element.data;
expect(data.headline).to.equal('Peek notification headline');
expect(data.message).to.equal('Peek notification message');
});
it('it sets duration to 6000 ms', () => {
expect(peekNotificationHandler?.duration).to.equal(6000);
});
});
describe('stay', () => {
let stayNotificationHandler: UmbNotificationHandler | undefined = undefined;
beforeEach(async () => {
const stayOptions = {
data: { headline: 'Stay notification headline', message: 'Stay notification message' },
};
stayNotificationHandler = notificationService.stay('danger', stayOptions);
});
it('it sets notification color', () => {
expect(stayNotificationHandler?.color).to.equal('danger');
});
it('should set stay data on the notification element', () => {
const data = stayNotificationHandler?.element.data;
expect(data.headline).to.equal('Stay notification headline');
expect(data.message).to.equal('Stay notification message');
});
it('it sets the duration to null', () => {
expect(stayNotificationHandler?.duration).to.equal(null);
});
});
});