diff --git a/components/backdrop/README.md b/components/backdrop/README.md
index b284044a505..015c3396994 100644
--- a/components/backdrop/README.md
+++ b/components/backdrop/README.md
@@ -1,9 +1,11 @@
# Backdrops
-Use a backdrop to de-emphasize background elements and draw the user's attention to a dialog or other modal content.
+Use a backdrop to de-emphasize the content of an element or page.
## Backdrop [d2l-backdrop]
+Use a backdrop to de-emphasize background elements and draw the user's attention to a dialog or other modal content.
+
```html
+
+
Refresh Content
+
+
+
+ | Course |
+ Grade |
+ Hours Spent in Content |
+
+
+ | Math |
+ 85% |
+ 100 |
+
+
+ | Art |
+ 98% |
+ 10 |
+
+
+
+
+```
+
+### Properties:
+
+| Property | Type | Description |
+|--|--|--|
+| `shown` | Boolean | Used to control whether the loading backdrop is shown |
+
diff --git a/components/backdrop/loading-backdrop.js b/components/backdrop/loading-backdrop.js
new file mode 100644
index 00000000000..a7374d7a092
--- /dev/null
+++ b/components/backdrop/loading-backdrop.js
@@ -0,0 +1,131 @@
+import '../colors/colors.js';
+import { css, html, LitElement } from 'lit';
+
+const BACKDROP_DELAY_MS = 800;
+const BACKDROP_FADE_IN_DURATION_MS = 500;
+const BACKDROP_FADE_OUT_DURATION_MS = 200;
+const SPINNER_DELAY_MS = BACKDROP_DELAY_MS + BACKDROP_FADE_IN_DURATION_MS;
+const SPINNER_FADE_IN_DURATION_MS = 500;
+const SPINNER_FADE_OUT_DURATION_MS = 200;
+
+/**
+ * A component for displaying a semi-transparent backdrop and a loading spinner over the containing element
+ */
+class LoadingBackdrop extends LitElement {
+
+ static get properties() {
+ return {
+ /**
+ * Used to control whether the loading backdrop is shown
+ * @type {boolean}
+ */
+ shown: { type: Boolean },
+ _state: { type: String, reflect: true },
+ };
+ }
+
+ static get styles() {
+ return css`
+ :host, .backdrop, d2l-loading-spinner {
+ width: 0%;
+ height: 0%;
+ position: absolute;
+ }
+
+ .backdrop, d2l-loading-spinner {
+ opacity: 0;
+ }
+
+ :host {
+ z-index: 999;
+ top: 0px;
+ }
+
+ .backdrop {
+ background-color: var(--d2l-color-regolith);
+ }
+
+ d2l-loading-spinner {
+ top: 100px;
+ }
+
+ :host([_state="showing"]),
+ :host([_state="hiding"]),
+ d2l-loading-spinner[_state="showing"],
+ d2l-loading-spinner[_state="hiding"],
+ .backdrop[_state="showing"],
+ .backdrop[_state="hiding"] {
+ width: 100%;
+ height: 100%;
+ }
+
+ d2l-loading-spinner[_state="showing"] {
+ opacity: 1;
+ transition: opacity ${SPINNER_FADE_IN_DURATION_MS}ms ease-in ${SPINNER_DELAY_MS}ms;
+ }
+
+ .backdrop[_state="showing"] {
+ opacity: 0.7;
+ transition: opacity ${BACKDROP_FADE_IN_DURATION_MS}ms ease-in ${BACKDROP_DELAY_MS}ms;
+ }
+
+ d2l-loading-spinner[_state="hiding"] {
+ transition: opacity ${SPINNER_FADE_OUT_DURATION_MS}ms ease-out;
+ }
+
+ .backdrop[_state="hiding"] {
+ transition: opacity ${BACKDROP_FADE_OUT_DURATION_MS}ms ease-out;
+ }
+
+ @media (prefers-reduced-motion: reduce) {
+ * { transition: none}
+ }
+ `;
+ }
+
+ constructor() {
+ super();
+ this.shown = false;
+ this._state = null;
+ }
+
+ render() {
+ return html`
+
+
+ `;
+ }
+
+ willUpdate(changedProperties) {
+ if (changedProperties.has('shown')) {
+ if (this.shown) {
+ this._state = 'showing';
+ } else if (changedProperties.get('shown') !== undefined) {
+ this._state = 'hiding';
+
+ this._hideAfterFading();
+ }
+ }
+ }
+
+ _hideAfterFading() {
+ const backdrop = this.shadowRoot.querySelector('.backdrop');
+ const loadingSpinner = this.shadowRoot.querySelector('d2l-loading-spinner');
+
+ Promise.all([
+ new Promise(resolve => {
+ backdrop.addEventListener('transitionend', resolve, { once: true });
+ backdrop.addEventListener('transitioncancel', resolve, { once: true });
+ }),
+ new Promise(resolve => {
+ loadingSpinner.addEventListener('transitionend', resolve, { once: true });
+ loadingSpinner.addEventListener('transitioncancel', resolve, { once: true });
+ })
+ ]).then(() => {
+ this._state = null;
+ });
+ }
+
+}
+
+customElements.define('d2l-loading-backdrop', LoadingBackdrop);
diff --git a/components/table/table-wrapper.js b/components/table/table-wrapper.js
index b26422ca184..852f6315449 100644
--- a/components/table/table-wrapper.js
+++ b/components/table/table-wrapper.js
@@ -1,5 +1,6 @@
import '../colors/colors.js';
import '../scroll-wrapper/scroll-wrapper.js';
+import '../backdrop/loading-backdrop.js';
import { css, html, LitElement, nothing } from 'lit';
import { cssSizes } from '../inputs/input-checkbox.js';
import { getComposedParent } from '../../helpers/dom.js';
@@ -254,6 +255,7 @@ export const tableStyles = css`
[data-popover-count] {
z-index: 6 !important; /* if opened above, we want to stack on top of sticky table-controls */
}
+
`;
const SELECTORS = {
@@ -313,6 +315,10 @@ export class TableWrapper extends PageableMixin(SelectionMixin(LitElement)) {
reflect: true,
type: Boolean,
},
+ loading: {
+ reflect: true,
+ type: Boolean
+ },
};
}
@@ -382,6 +388,7 @@ export class TableWrapper extends PageableMixin(SelectionMixin(LitElement)) {
this._tableIntersectionObserver = null;
this._tableMutationObserver = null;
this._tableScrollers = {};
+ this._loading = false;
}
connectedCallback() {
@@ -410,7 +417,10 @@ export class TableWrapper extends PageableMixin(SelectionMixin(LitElement)) {
}
render() {
- const slot = html``;
+ const slot = html`
+
+
+ `;
const useScrollWrapper = this.stickyHeadersScrollWrapper || !this.stickyHeaders;
return html`