@brightspace-ui/core 2.34.0 → 2.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/backdrop/backdrop.js +5 -3
- package/components/empty-state/README.md +100 -0
- package/components/empty-state/demo/empty-state.html +54 -0
- package/components/empty-state/empty-state-illustrated-button.js +71 -0
- package/components/empty-state/empty-state-illustrated-link.js +57 -0
- package/components/empty-state/empty-state-illustrated-mixin.js +126 -0
- package/components/empty-state/empty-state-simple-button.js +82 -0
- package/components/empty-state/empty-state-simple-link.js +74 -0
- package/components/empty-state/empty-state-styles.js +84 -0
- package/components/empty-state/images/assembly-line.svg +133 -0
- package/components/empty-state/images/blueprint.svg +67 -0
- package/components/empty-state/images/calendar.svg +37 -0
- package/components/empty-state/images/cat-computer.svg +60 -0
- package/components/empty-state/images/checklist.svg +33 -0
- package/components/empty-state/images/data-tracking.svg +91 -0
- package/components/empty-state/images/desert-road.svg +134 -0
- package/components/empty-state/images/fish-hook.svg +143 -0
- package/components/empty-state/images/oven.svg +75 -0
- package/components/empty-state/images/pipeline.svg +126 -0
- package/components/empty-state/images/race.svg +199 -0
- package/components/empty-state/images/rockets.svg +698 -0
- package/components/empty-state/images/tumbleweed.svg +34 -0
- package/components/list/README.md +4 -6
- package/components/paging/README.md +1 -2
- package/components/tag-list/tag-list.js +3 -1
- package/custom-elements.json +225 -0
- package/generated/empty-state/assembly-line.js +135 -0
- package/generated/empty-state/blueprint.js +69 -0
- package/generated/empty-state/calendar.js +39 -0
- package/generated/empty-state/cat-computer.js +62 -0
- package/generated/empty-state/checklist.js +35 -0
- package/generated/empty-state/data-tracking.js +93 -0
- package/generated/empty-state/desert-road.js +136 -0
- package/generated/empty-state/fish-hook.js +145 -0
- package/generated/empty-state/oven.js +77 -0
- package/generated/empty-state/pipeline.js +128 -0
- package/generated/empty-state/presetIllustrationLoader.js +32 -0
- package/generated/empty-state/race.js +201 -0
- package/generated/empty-state/rockets.js +700 -0
- package/generated/empty-state/tumbleweed.js +36 -0
- package/package.json +3 -2
|
@@ -3,6 +3,8 @@ import { css, html, LitElement } from 'lit';
|
|
|
3
3
|
import { cssEscape, getComposedChildren, getComposedParent, isVisible } from '../../helpers/dom.js';
|
|
4
4
|
import { getUniqueId } from '../../helpers/uniqueId.js';
|
|
5
5
|
|
|
6
|
+
export const BACKDROP_ROLE = 'd2l-backdrop-role';
|
|
7
|
+
|
|
6
8
|
const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
7
9
|
|
|
8
10
|
const scrollKeys = [];
|
|
@@ -159,7 +161,7 @@ function hideAccessible(target) {
|
|
|
159
161
|
if (child.hasAttribute('d2l-backdrop-hidden')) continue;
|
|
160
162
|
|
|
161
163
|
const role = child.getAttribute('role');
|
|
162
|
-
if (role) child.setAttribute(
|
|
164
|
+
if (role) child.setAttribute(BACKDROP_ROLE, role);
|
|
163
165
|
child.setAttribute('role', 'presentation');
|
|
164
166
|
|
|
165
167
|
if (child.nodeName === 'FORM' || child.nodeName === 'A') {
|
|
@@ -200,10 +202,10 @@ export function preventBodyScroll() {
|
|
|
200
202
|
function showAccessible(elems) {
|
|
201
203
|
for (let i = 0; i < elems.length; i++) {
|
|
202
204
|
const elem = elems[i];
|
|
203
|
-
const role = elem.getAttribute(
|
|
205
|
+
const role = elem.getAttribute(BACKDROP_ROLE);
|
|
204
206
|
if (role) {
|
|
205
207
|
elem.setAttribute('role', role);
|
|
206
|
-
elem.removeAttribute(
|
|
208
|
+
elem.removeAttribute(BACKDROP_ROLE);
|
|
207
209
|
} else {
|
|
208
210
|
elem.removeAttribute('role');
|
|
209
211
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Empty State
|
|
2
|
+
Empty states are used when there is no data available to be displayed, or when a search or filter returns no results.
|
|
3
|
+
|
|
4
|
+
<!-- docs: demo align:start -->
|
|
5
|
+
```html
|
|
6
|
+
<script type="module">
|
|
7
|
+
import '@brightspace-ui/core/components/empty-state/empty-state-simple-button.js';
|
|
8
|
+
import '@brightspace-ui/core/components/empty-state/empty-state-illustrated-button.js';
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<d2l-empty-state-simple-button description="There are no assignments to display." action-text="Create an Assignment"></d2l-empty-state-simple-button>
|
|
12
|
+
<d2l-empty-state-illustrated-button illustration-name="desert-road" title-text="No Learning Paths Yet" description="Get started by clicking below to create your first learning path." action-text="Create Learning Paths"> </d2l-empty-state-illustrated-button>
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Best Practices
|
|
17
|
+
|
|
18
|
+
<!-- docs: start best practices -->
|
|
19
|
+
<!-- docs: start dos -->
|
|
20
|
+
* Do make it clear that there is no data available to be displayed
|
|
21
|
+
* Do include guidance on next steps if available, either as short instructions or as Call to Actions
|
|
22
|
+
* Do use a link for navigation and a button for actions
|
|
23
|
+
* Do replace the entire content with its empty state for accessibility
|
|
24
|
+
<!-- docs: end dos -->
|
|
25
|
+
|
|
26
|
+
<!-- docs: start donts -->
|
|
27
|
+
* Don’t use an empty state as a default state while data is loading
|
|
28
|
+
* Don't leave a section completely empty, or use a skeleton loading screen in place of an empty state component
|
|
29
|
+
* Avoid causing users to believe that they have hit a dead-end when they have not
|
|
30
|
+
<!-- docs: end donts -->
|
|
31
|
+
<!-- docs: end best practices -->
|
|
32
|
+
|
|
33
|
+
## Empty State Simple Button [d2l-empty-state-simple-button]
|
|
34
|
+
|
|
35
|
+
The `d2l-empty-state-simple-button` component is an empty state component that displays a description and action button.
|
|
36
|
+
|
|
37
|
+
<!-- docs: demo live name:d2l-empty-state-simple-button -->
|
|
38
|
+
```html
|
|
39
|
+
<script type="module">
|
|
40
|
+
import '@brightspace-ui/core/components/empty-state/empty-state-simple-button.js';
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<d2l-empty-state-simple-button description="There are no assignments to display." action-text="Create an Assignment"></d2l-empty-state-simple-button>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Empty State Simple Link [d2l-empty-state-simple-link]
|
|
47
|
+
|
|
48
|
+
The `d2l-empty-state-simple-link` component is an empty state component that displays a description and action link.
|
|
49
|
+
|
|
50
|
+
<!-- docs: demo live name:d2l-empty-state-simple-link -->
|
|
51
|
+
```html
|
|
52
|
+
<script type="module">
|
|
53
|
+
import '@brightspace-ui/core/components/empty-state/empty-state-simple-link.js';
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<d2l-empty-state-simple-link description="There are no assignments to display." action-text="Create an Assignment" action-href='https://d2l.com'></d2l-empty-state-simple-link>
|
|
57
|
+
```
|
|
58
|
+
## Empty State Illustrated Button [d2l-empty-state-illustrated-button]
|
|
59
|
+
|
|
60
|
+
The `d2l-empty-state-illustrated-button` component is an empty state component that displays a title and description with an illustration and action button. The `illustration-name` property can be set to use one of the preset illustrations or a custom SVG illustration can be added in the default slot.
|
|
61
|
+
|
|
62
|
+
<!-- docs: demo live name:d2l-empty-state-illustrated-button -->
|
|
63
|
+
```html
|
|
64
|
+
<script type="module">
|
|
65
|
+
import '@brightspace-ui/core/components/empty-state/empty-state-illustrated-button.js';
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<d2l-empty-state-illustrated-button illustration-name="desert-road" title-text="No Learning Paths Yet" description="Get started by clicking below to create your first learning path." action-text="Create Learning Paths"></d2l-empty-state-illustrated-button>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Empty State Illustrated Link [d2l-empty-state-illustrated-link]
|
|
72
|
+
|
|
73
|
+
The `d2l-empty-state-illustrated-link` component is an empty state component that displays a title and description with an illustration and action link. The `illustration-name` property can be set to use one of the preset illustrations or a custom SVG illustration can be added in the default slot.
|
|
74
|
+
|
|
75
|
+
<!-- docs: demo live name:d2l-empty-state-illustrated-link -->
|
|
76
|
+
```html
|
|
77
|
+
<script type="module">
|
|
78
|
+
import '@brightspace-ui/core/components/empty-state/empty-state-illustrated-link.js';
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<d2l-empty-state-illustrated-link illustration-name="desert-road" title-text="No Learning Paths Yet" description="Get started by clicking below to create your first learning path." action-text="Create Learning Paths" action-href='https://d2l.com'></d2l-empty-state-illustrated-link>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Preset Empty State Illustrations
|
|
85
|
+
|
|
86
|
+
| Illustration | Name |
|
|
87
|
+
| :---: | :--- |
|
|
88
|
+
|  | assembly-line |
|
|
89
|
+
|  | blueprint |
|
|
90
|
+
|  | calendar |
|
|
91
|
+
|  | cat-computer |
|
|
92
|
+
|  | checklist |
|
|
93
|
+
|  | data-tracking |
|
|
94
|
+
|  | desert-road |
|
|
95
|
+
|  | fish-hook |
|
|
96
|
+
|  | oven |
|
|
97
|
+
|  | pipeline |
|
|
98
|
+
|  | race |
|
|
99
|
+
|  | rockets |
|
|
100
|
+
|  | tumbleweed |
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<link rel="stylesheet" href="../../demo/styles.css" type="text/css">
|
|
7
|
+
<script type="module">
|
|
8
|
+
import '../../demo/demo-page.js';
|
|
9
|
+
import '../empty-state-simple-button.js';
|
|
10
|
+
import '../empty-state-simple-link.js';
|
|
11
|
+
import '../empty-state-illustrated-button.js';
|
|
12
|
+
import '../empty-state-illustrated-link.js';
|
|
13
|
+
</script>
|
|
14
|
+
</head>
|
|
15
|
+
<body unresolved>
|
|
16
|
+
|
|
17
|
+
<d2l-demo-page page-title="d2l-empty-state">
|
|
18
|
+
|
|
19
|
+
<h2>Empty State Simple Button</h2>
|
|
20
|
+
|
|
21
|
+
<d2l-demo-snippet>
|
|
22
|
+
<template>
|
|
23
|
+
<d2l-empty-state-simple-button description="There are no assignments to display." action-text="Create New Assignment"></d2l-empty-state-simple-button>
|
|
24
|
+
</template>
|
|
25
|
+
</d2l-demo-snippet>
|
|
26
|
+
|
|
27
|
+
<h2>Empty State Simple Link</h2>
|
|
28
|
+
|
|
29
|
+
<d2l-demo-snippet>
|
|
30
|
+
<template>
|
|
31
|
+
<d2l-empty-state-simple-link description="There are no assignments to display." action-text="Create New Assignment" action-href="https://www.d2l.com/"></d2l-empty-state-simple-link>
|
|
32
|
+
</template>
|
|
33
|
+
</d2l-demo-snippet>
|
|
34
|
+
|
|
35
|
+
<h2>Empty State Illustrated Button</h2>
|
|
36
|
+
|
|
37
|
+
<d2l-demo-snippet>
|
|
38
|
+
<template>
|
|
39
|
+
<d2l-empty-state-illustrated-button illustration-name="fish-hook" title-text="No Learning Paths Yet" description="Get started by clicking below to create your first learning path." action-text="Create Learning Paths"></d2l-empty-state-illustrated-button>
|
|
40
|
+
</template>
|
|
41
|
+
</d2l-demo-snippet>
|
|
42
|
+
|
|
43
|
+
<h2>Empty State Illustrated Link</h2>
|
|
44
|
+
|
|
45
|
+
<d2l-demo-snippet>
|
|
46
|
+
<template>
|
|
47
|
+
<d2l-empty-state-illustrated-link illustration-name="desert-road" title-text="No Learning Paths Yet" description="Get started by clicking below to create your first learning path." action-text="Create Learning Paths" action-href="https://www.d2l.com/"></d2l-empty-state-illustrated-link>
|
|
48
|
+
</template>
|
|
49
|
+
</d2l-demo-snippet>
|
|
50
|
+
|
|
51
|
+
</d2l-demo-page>
|
|
52
|
+
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import '../button/button.js';
|
|
2
|
+
import '../button/button-subtle.js';
|
|
3
|
+
import { html, LitElement, nothing } from 'lit';
|
|
4
|
+
import { classMap } from 'lit/directives/class-map.js';
|
|
5
|
+
import { EmptyStateIllustratedMixin } from './empty-state-illustrated-mixin.js';
|
|
6
|
+
import { runAsync } from '../../directives/run-async/run-async.js';
|
|
7
|
+
import { styleMap } from 'lit/directives/style-map.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The `d2l-empty-state-illustrated-button` component is an empty state component that displays an illustration and action button. The illustration property can be set to use one of the preset illustrations or a custom SVG illustration can be added in the default slot.
|
|
11
|
+
* @fires d2l-empty-state-action - Dispatched when the action button is clicked
|
|
12
|
+
* @slot - Custom SVG content if `illustration-name` property is not set
|
|
13
|
+
*/
|
|
14
|
+
class EmptyStateIllustratedButton extends EmptyStateIllustratedMixin(LitElement) {
|
|
15
|
+
|
|
16
|
+
static get properties() {
|
|
17
|
+
return {
|
|
18
|
+
/**
|
|
19
|
+
* This will change the action button to use a primary button instead of the default subtle button
|
|
20
|
+
* @type {boolean}
|
|
21
|
+
*/
|
|
22
|
+
primary: { type: Boolean },
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
super();
|
|
28
|
+
this._illustratedComponentType = 'button';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
render() {
|
|
32
|
+
const illustrationContainerStyle = this.getIllustrationContainerStyle();
|
|
33
|
+
const titleClass = this.getTitleClass();
|
|
34
|
+
|
|
35
|
+
let actionButton = nothing;
|
|
36
|
+
if (this.actionText) {
|
|
37
|
+
actionButton = this.primary
|
|
38
|
+
? html`<d2l-button
|
|
39
|
+
class="d2l-empty-state-action"
|
|
40
|
+
@click=${this._handleActionClick}
|
|
41
|
+
primary>${this.actionText}
|
|
42
|
+
</d2l-button>`
|
|
43
|
+
: html`<d2l-button-subtle
|
|
44
|
+
class="d2l-empty-state-action"
|
|
45
|
+
@click=${this._handleActionClick}
|
|
46
|
+
text=${this.actionText}>
|
|
47
|
+
</d2l-button-subtle>`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return html`
|
|
51
|
+
${this.illustrationName
|
|
52
|
+
? html`
|
|
53
|
+
<div style="${styleMap(illustrationContainerStyle)}">
|
|
54
|
+
${runAsync(this.illustrationName, () => this.getIllustration(this.illustrationName), { success: (illustration) => illustration }, { pendingState: false })}
|
|
55
|
+
</div>`
|
|
56
|
+
: html`<slot></slot>`}
|
|
57
|
+
|
|
58
|
+
<p class="${classMap(titleClass)}">${this.titleText}</p>
|
|
59
|
+
<p class="d2l-body-compact d2l-empty-state-description">${this.description}</p>
|
|
60
|
+
${actionButton}
|
|
61
|
+
`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_handleActionClick(e) {
|
|
65
|
+
e.stopPropagation();
|
|
66
|
+
this.dispatchEvent(new CustomEvent('d2l-empty-state-action'));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
customElements.define('d2l-empty-state-illustrated-button', EmptyStateIllustratedButton);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { html, LitElement, nothing } from 'lit';
|
|
2
|
+
import { classMap } from 'lit/directives/class-map.js';
|
|
3
|
+
import { EmptyStateIllustratedMixin } from './empty-state-illustrated-mixin.js';
|
|
4
|
+
import { linkStyles } from '../link/link.js';
|
|
5
|
+
import { runAsync } from '../../directives/run-async/run-async.js';
|
|
6
|
+
import { styleMap } from 'lit/directives/style-map.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The `d2l-empty-state-illustrated-link` component is an empty state component that displays an illustration and action link. The illustration property can be set to use one of the preset illustrations or a custom SVG illustration can be added in the default slot.
|
|
10
|
+
* @slot - Custom SVG content if `illustration-name` property is not set
|
|
11
|
+
*/
|
|
12
|
+
class EmptyStateIllustratedLink extends EmptyStateIllustratedMixin(LitElement) {
|
|
13
|
+
|
|
14
|
+
static get properties() {
|
|
15
|
+
return {
|
|
16
|
+
/**
|
|
17
|
+
* The action URL or URL fragment of the link
|
|
18
|
+
* @type {string}
|
|
19
|
+
*/
|
|
20
|
+
actionHref: { type: String, attribute: 'action-href' },
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static get styles() {
|
|
25
|
+
return [super.styles, linkStyles];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
constructor() {
|
|
29
|
+
super();
|
|
30
|
+
this._illustratedComponentType = 'link';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
render() {
|
|
34
|
+
const illustrationContainerStyle = this.getIllustrationContainerStyle();
|
|
35
|
+
const titleClass = this.getTitleClass();
|
|
36
|
+
|
|
37
|
+
const actionLink = this.actionText && this.actionHref
|
|
38
|
+
? html`<a class="d2l-body-compact d2l-empty-state-action d2l-link" href=${this.actionHref}>${this.actionText}</a>`
|
|
39
|
+
: nothing;
|
|
40
|
+
|
|
41
|
+
return html`
|
|
42
|
+
${this.illustrationName
|
|
43
|
+
? html`
|
|
44
|
+
<div style="${styleMap(illustrationContainerStyle)}">
|
|
45
|
+
${runAsync(this.illustrationName, () => this.getIllustration(this.illustrationName), { success: (illustration) => illustration }, { pendingState: false })}
|
|
46
|
+
</div>`
|
|
47
|
+
: html`<slot></slot>`}
|
|
48
|
+
|
|
49
|
+
<p class="${classMap(titleClass)}">${this.titleText}</p>
|
|
50
|
+
<p class="d2l-body-compact d2l-empty-state-description">${this.description}</p>
|
|
51
|
+
${actionLink}
|
|
52
|
+
`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
customElements.define('d2l-empty-state-illustrated-link', EmptyStateIllustratedLink);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { emptyStateIllustratedStyles, emptyStateStyles } from './empty-state-styles.js';
|
|
2
|
+
import { html, nothing } from 'lit';
|
|
3
|
+
import { bodyCompactStyles } from '../typography/styles.js';
|
|
4
|
+
import { loadSvg } from '../../generated/empty-state/presetIllustrationLoader.js';
|
|
5
|
+
import ResizeObserver from 'resize-observer-polyfill/dist/ResizeObserver.es.js';
|
|
6
|
+
import { RtlMixin } from '../../mixins/rtl-mixin.js';
|
|
7
|
+
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
8
|
+
|
|
9
|
+
const illustrationAspectRatio = 500 / 330;
|
|
10
|
+
|
|
11
|
+
export const EmptyStateIllustratedMixin = superclass => class extends RtlMixin(superclass) {
|
|
12
|
+
|
|
13
|
+
static get properties() {
|
|
14
|
+
return {
|
|
15
|
+
/**
|
|
16
|
+
* The action text to be used in the subtle button
|
|
17
|
+
* @type {string}
|
|
18
|
+
*/
|
|
19
|
+
actionText: { type: String, attribute: 'action-text' },
|
|
20
|
+
/**
|
|
21
|
+
* REQUIRED: A description giving details about the empty state
|
|
22
|
+
* @type {string}
|
|
23
|
+
*/
|
|
24
|
+
description: { type: String },
|
|
25
|
+
/**
|
|
26
|
+
* The name of the preset image you would like to display in the component
|
|
27
|
+
* @type {string}
|
|
28
|
+
*/
|
|
29
|
+
illustrationName: { type: String, attribute: 'illustration-name' },
|
|
30
|
+
/**
|
|
31
|
+
* REQUIRED: A title for the empty state
|
|
32
|
+
* @type {string}
|
|
33
|
+
*/
|
|
34
|
+
titleText: { type: String, attribute: 'title-text' },
|
|
35
|
+
_contentHeight: { state: true },
|
|
36
|
+
_illustratedComponentType: { state: true },
|
|
37
|
+
_titleSmall: { state: true }
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static get styles() {
|
|
42
|
+
return [bodyCompactStyles, emptyStateStyles, emptyStateIllustratedStyles];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
constructor() {
|
|
46
|
+
super();
|
|
47
|
+
|
|
48
|
+
this._contentHeight = 330;
|
|
49
|
+
this._missingDescriptionErrorHasBeenThrown = false;
|
|
50
|
+
this._missingTitleTextErrorHasBeenThrown = false;
|
|
51
|
+
this._resizeObserver = new ResizeObserver(this._onResize.bind(this));
|
|
52
|
+
this._titleSmall = false;
|
|
53
|
+
this._validatingAttributesTimeout = null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
connectedCallback() {
|
|
57
|
+
super.connectedCallback();
|
|
58
|
+
this._resizeObserver.observe(this);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
disconnectedCallback() {
|
|
62
|
+
super.disconnectedCallback();
|
|
63
|
+
this._resizeObserver.disconnect();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
firstUpdated(changedProperties) {
|
|
67
|
+
super.firstUpdated(changedProperties);
|
|
68
|
+
this._validateAttributes();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async getIllustration(illustrationName) {
|
|
72
|
+
if (!illustrationName) return;
|
|
73
|
+
|
|
74
|
+
const svg = await loadSvg(illustrationName);
|
|
75
|
+
if (!svg) setTimeout(() => {
|
|
76
|
+
throw new Error(`<d2l-empty-state-illustrated-${this._illustratedComponentType}>: Unable to retrieve requested illustration.`);
|
|
77
|
+
});
|
|
78
|
+
return svg ? html`${unsafeSVG(svg.val)}` : nothing;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getIllustrationContainerStyle() {
|
|
82
|
+
return {
|
|
83
|
+
height: `${this._contentHeight}px`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getTitleClass() {
|
|
88
|
+
return {
|
|
89
|
+
'd2l-empty-state-title': true,
|
|
90
|
+
'd2l-empty-state-title-small': this._titleSmall,
|
|
91
|
+
'd2l-empty-state-title-large': !this._titleSmall,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
_onResize(entries) {
|
|
96
|
+
if (!entries || entries.length === 0) return;
|
|
97
|
+
const entry = entries[0];
|
|
98
|
+
this._contentHeight = Math.min(entry.contentRect.right / illustrationAspectRatio, 330);
|
|
99
|
+
this._titleSmall = entry.contentRect.right <= 615;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_validateAttributes() {
|
|
103
|
+
clearTimeout(this._validatingAttributesTimeout);
|
|
104
|
+
// don't error immediately in case it doesn't get set immediately
|
|
105
|
+
this._validatingAttributesTimeout = setTimeout(() => {
|
|
106
|
+
this._validatingAttributesTimeout = null;
|
|
107
|
+
const hasTitleText = (typeof this.titleText === 'string') && this.titleText.length > 0;
|
|
108
|
+
const hasDescription = (typeof this.description === 'string') && this.description.length > 0;
|
|
109
|
+
|
|
110
|
+
if (!hasTitleText && !this._missingTitleTextErrorHasBeenThrown) {
|
|
111
|
+
this._missingTitleTextErrorHasBeenThrown = true;
|
|
112
|
+
setTimeout(() => {
|
|
113
|
+
throw new Error(`<d2l-empty-state-illustrated-${this._illustratedComponentType}>: missing required "titleText" attribute.`);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!hasDescription && !this._missingDescriptionErrorHasBeenThrown) {
|
|
118
|
+
this._missingDescriptionErrorHasBeenThrown = true;
|
|
119
|
+
setTimeout(() => {
|
|
120
|
+
throw new Error(`<d2l-empty-state-illustrated-${this._illustratedComponentType}>: missing required "description" attribute.`);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}, 3000);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import '../button/button-subtle.js';
|
|
2
|
+
import { emptyStateSimpleStyles, emptyStateStyles } from './empty-state-styles.js';
|
|
3
|
+
import { html, LitElement, nothing } from 'lit';
|
|
4
|
+
import { bodyCompactStyles } from '../typography/styles.js';
|
|
5
|
+
import { RtlMixin } from '../../mixins/rtl-mixin.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The `d2l-empty-state-simple-button` component is an empty state component that displays a description and action button.
|
|
9
|
+
* @fires d2l-empty-state-action - Dispatched when the action button is clicked
|
|
10
|
+
*/
|
|
11
|
+
class EmptyStateSimpleButton extends RtlMixin(LitElement) {
|
|
12
|
+
|
|
13
|
+
static get properties() {
|
|
14
|
+
return {
|
|
15
|
+
/**
|
|
16
|
+
* The action text to be used in the subtle button
|
|
17
|
+
* @type {string}
|
|
18
|
+
*/
|
|
19
|
+
actionText: { type: String, attribute: 'action-text' },
|
|
20
|
+
/**
|
|
21
|
+
* REQUIRED: A description giving details about the empty state
|
|
22
|
+
* @type {string}
|
|
23
|
+
*/
|
|
24
|
+
description: { type: String },
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static get styles() {
|
|
29
|
+
return [bodyCompactStyles, emptyStateStyles, emptyStateSimpleStyles];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
constructor() {
|
|
33
|
+
super();
|
|
34
|
+
this._missingDescriptionErrorHasBeenThrown = false;
|
|
35
|
+
this._validatingDescriptionTimeout = null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
firstUpdated(changedProperties) {
|
|
39
|
+
super.firstUpdated(changedProperties);
|
|
40
|
+
this._validateDescription();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
render() {
|
|
44
|
+
const actionButton = this.actionText
|
|
45
|
+
? html`
|
|
46
|
+
<d2l-button-subtle
|
|
47
|
+
class="d2l-empty-state-action"
|
|
48
|
+
@click=${this._handleActionClick}
|
|
49
|
+
h-align="text"
|
|
50
|
+
text=${this.actionText}
|
|
51
|
+
slim>
|
|
52
|
+
</d2l-button-subtle>`
|
|
53
|
+
: nothing;
|
|
54
|
+
|
|
55
|
+
return html`
|
|
56
|
+
<p class="d2l-body-compact d2l-empty-state-description">${this.description}</p>
|
|
57
|
+
${actionButton}
|
|
58
|
+
`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_handleActionClick(e) {
|
|
62
|
+
e.stopPropagation();
|
|
63
|
+
this.dispatchEvent(new CustomEvent('d2l-empty-state-action'));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_validateDescription() {
|
|
67
|
+
clearTimeout(this._validatingDescriptionTimeout);
|
|
68
|
+
// don't error immediately in case it doesn't get set immediately
|
|
69
|
+
this._validatingDescriptionTimeout = setTimeout(() => {
|
|
70
|
+
this._validatingDescriptionTimeout = null;
|
|
71
|
+
const hasDescription = (typeof this.description === 'string') && this.description.length > 0;
|
|
72
|
+
|
|
73
|
+
if (!hasDescription && !this._missingDescriptionErrorHasBeenThrown) {
|
|
74
|
+
this._missingDescriptionErrorHasBeenThrown = true;
|
|
75
|
+
setTimeout(() => { throw new Error('<d2l-empty-state-simple-button>: missing required "description" attribute.'); });
|
|
76
|
+
}
|
|
77
|
+
}, 3000);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
customElements.define('d2l-empty-state-simple-button', EmptyStateSimpleButton);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { emptyStateSimpleStyles, emptyStateStyles } from './empty-state-styles.js';
|
|
2
|
+
import { html, LitElement, nothing } from 'lit';
|
|
3
|
+
import { bodyCompactStyles } from '../typography/styles.js';
|
|
4
|
+
import { linkStyles } from '../link/link.js';
|
|
5
|
+
import { RtlMixin } from '../../mixins/rtl-mixin.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The `d2l-empty-state-simple-link` component is an empty state component that displays a description and action link.
|
|
9
|
+
*/
|
|
10
|
+
class EmptyStateSimpleLink extends RtlMixin(LitElement) {
|
|
11
|
+
|
|
12
|
+
static get properties() {
|
|
13
|
+
return {
|
|
14
|
+
/**
|
|
15
|
+
* The action URL or URL fragment of the link
|
|
16
|
+
* @type {string}
|
|
17
|
+
*/
|
|
18
|
+
actionHref: { type: String, attribute: 'action-href' },
|
|
19
|
+
/**
|
|
20
|
+
* The action text to be used in the link
|
|
21
|
+
* @type {string}
|
|
22
|
+
*/
|
|
23
|
+
actionText: { type: String, attribute: 'action-text' },
|
|
24
|
+
/**
|
|
25
|
+
* REQUIRED: A description giving details about the empty state
|
|
26
|
+
* @type {string}
|
|
27
|
+
*/
|
|
28
|
+
description: { type: String }
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static get styles() {
|
|
33
|
+
return [bodyCompactStyles, emptyStateStyles, emptyStateSimpleStyles, linkStyles];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
constructor() {
|
|
37
|
+
super();
|
|
38
|
+
this._missingDescriptionErrorHasBeenThrown = false;
|
|
39
|
+
this._validatingDescriptionTimeout = null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
firstUpdated(changedProperties) {
|
|
43
|
+
super.firstUpdated(changedProperties);
|
|
44
|
+
this._validateDescription();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
render() {
|
|
48
|
+
const actionLink = this.actionText && this.actionHref
|
|
49
|
+
? html`<a class="d2l-body-compact d2l-link" href=${this.actionHref}>${this.actionText}</a>`
|
|
50
|
+
: nothing;
|
|
51
|
+
|
|
52
|
+
return html`
|
|
53
|
+
<p class="d2l-body-compact d2l-empty-state-description">${this.description}</p>
|
|
54
|
+
${actionLink}
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_validateDescription() {
|
|
59
|
+
clearTimeout(this._validatingDescriptionTimeout);
|
|
60
|
+
// don't error immediately in case it doesn't get set immediately
|
|
61
|
+
this._validatingDescriptionTimeout = setTimeout(() => {
|
|
62
|
+
this._validatingDescriptionTimeout = null;
|
|
63
|
+
const hasDescription = (typeof this.description === 'string') && this.description.length > 0;
|
|
64
|
+
|
|
65
|
+
if (!hasDescription && !this._missingDescriptionErrorHasBeenThrown) {
|
|
66
|
+
this._missingDescriptionErrorHasBeenThrown = true;
|
|
67
|
+
setTimeout(() => { throw new Error('<d2l-empty-state-simple-link>: missing required "description" attribute.'); });
|
|
68
|
+
}
|
|
69
|
+
}, 3000);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
customElements.define('d2l-empty-state-simple-link', EmptyStateSimpleLink);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import '../colors/colors.js';
|
|
2
|
+
import { css } from 'lit';
|
|
3
|
+
|
|
4
|
+
export const emptyStateStyles = css`
|
|
5
|
+
|
|
6
|
+
:host {
|
|
7
|
+
border: 1px solid var(--d2l-color-mica);
|
|
8
|
+
border-radius: 0.3rem;
|
|
9
|
+
display: block;
|
|
10
|
+
padding: 1.2rem 1.5rem;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
:host([hidden]) {
|
|
14
|
+
display: none;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
export const emptyStateSimpleStyles = css`
|
|
20
|
+
|
|
21
|
+
:host([dir="rtl"]) .d2l-empty-state-description {
|
|
22
|
+
padding-left: 0.5rem;
|
|
23
|
+
padding-right: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.d2l-empty-state-description {
|
|
27
|
+
display: inline;
|
|
28
|
+
padding-right: 0.5rem;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.d2l-empty-state-action {
|
|
32
|
+
vertical-align: top;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
export const emptyStateIllustratedStyles = css`
|
|
38
|
+
|
|
39
|
+
:host {
|
|
40
|
+
text-align: center;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.d2l-empty-state-action {
|
|
44
|
+
margin-top: 0.5rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.d2l-empty-state-description {
|
|
48
|
+
margin: 0 auto 0.3rem;
|
|
49
|
+
max-width: 500px;
|
|
50
|
+
width: 100%;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.d2l-empty-state-title {
|
|
54
|
+
margin-bottom: 0.9rem;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.d2l-empty-state-title-large {
|
|
58
|
+
font-size: 1.5rem;
|
|
59
|
+
line-height: 1.8rem;
|
|
60
|
+
margin: 1rem 0 1.5rem 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.d2l-empty-state-title-small {
|
|
64
|
+
font-size: 1rem;
|
|
65
|
+
font-weight: 700;
|
|
66
|
+
line-height: 1.5rem;
|
|
67
|
+
margin-top: 0.5rem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
::slotted(*) {
|
|
71
|
+
display: none;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
::slotted(svg:first-child) {
|
|
75
|
+
display: inline-block;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
svg {
|
|
79
|
+
height: 100%;
|
|
80
|
+
max-width: 500px;
|
|
81
|
+
width: 100%;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
`;
|