@brightspace-ui/core 3.277.0 → 3.279.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.
@@ -1,10 +1,10 @@
1
+ import './backdrop-stale-overlay.js';
1
2
  import '../colors/colors.js';
2
3
  import '../loading-spinner/loading-spinner.js';
3
- import './backdrop-dirty-overlay.js';
4
- import { css, html, LitElement, nothing } from 'lit';
4
+ import { css, html, LitElement, nothing, unsafeCSS } from 'lit';
5
+ import { freshness, FreshnessMixin } from '../../mixins/freshness/freshness-mixin.js';
5
6
  import { getComposedChildren, getComposedParent } from '../../helpers/dom.js';
6
7
  import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
7
-
8
8
  import { PropertyRequiredMixin } from '../../mixins/property-required/property-required-mixin.js';
9
9
  import { styleMap } from 'lit/directives/style-map.js';
10
10
 
@@ -12,7 +12,6 @@ const BACKDROP_DELAY_MS = 800;
12
12
  const FADE_DURATION_MS = 500;
13
13
  const SPINNER_DELAY_MS = FADE_DURATION_MS;
14
14
  const LOADING_ANNOUNCEMENT_DELAY = 1000;
15
-
16
15
  const LOADING_SPINNER_SIZE = 50;
17
16
 
18
17
  const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
@@ -20,56 +19,31 @@ const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
20
19
  /**
21
20
  * A component for displaying a semi-transparent backdrop and a loading spinner over the containing element
22
21
  */
23
- class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitElement)) {
22
+ class BackdropLoading extends PropertyRequiredMixin(FreshnessMixin(LocalizeCoreElement(LitElement))) {
24
23
 
25
24
  static properties = {
26
- /**
27
- * The state of data in the element being overlaid. Set to 'clean' when the data represents the user's latest selections, 'dirty' when the data does not represent the user's latest selections, and 'loading' if the data is being actively refreshed
28
- * @type {'clean'|'dirty'|'loading'}
29
- */
30
- dataState: {
31
- reflect: true,
32
- type: String
33
- },
34
25
  /**
35
26
  * Used to identify content that the backdrop should make inert
36
27
  * @type {string}
37
28
  */
38
29
  for: { type: String, required: true },
39
- /**
40
- * The text displayed on the dirty state overlay when the 'dirty' dataState is set.
41
- * @type {string}
42
- */
43
- dirtyText: {
44
- reflect: true,
45
- attribute: 'dirty-text',
46
- type: String
47
- },
48
- /**
49
- * The text displayed on the button of the dirty state overlay when the 'dirty' dataState is set.
50
- * @type {string}
51
- */
52
- dirtyButtonText: {
53
- reflect: true,
54
- attribute: 'dirty-button-text',
55
- type: String
56
- },
57
- _state: { type: String, reflect: true },
58
- _spinnerTop: { state: true },
59
- _ariaContent: { state: true }
30
+ _ariaContent: { state: true },
31
+ _loadingSpinnerTop: { state: true },
32
+ _staleOverlayTop: { state: true },
33
+ _state: { type: String, reflect: true }
60
34
  };
61
35
 
62
36
  static styles = [css`
63
- #visible {
37
+ .backdrop-container {
64
38
  display: none;
65
39
  }
66
40
 
67
41
  :host([_state="showing"]),
68
42
  :host([_state="shown"]),
69
43
  :host([_state="hiding"]),
70
- :host([_state="showing"]) #visible,
71
- :host([_state="shown"]) #visible,
72
- :host([_state="hiding"]) #visible {
44
+ :host([_state="showing"]) .backdrop-container,
45
+ :host([_state="shown"]) .backdrop-container,
46
+ :host([_state="hiding"]) .backdrop-container {
73
47
  display: flex;
74
48
  inset: 0;
75
49
  justify-content: center;
@@ -99,13 +73,13 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
99
73
  opacity: 1;
100
74
  transition: opacity ${FADE_DURATION_MS}ms ease-in ${SPINNER_DELAY_MS}ms;
101
75
  }
102
- :host([_state="shown"][dataState="dirty"]) d2l-loading-spinner,
76
+ :host([_state="shown"][freshness="${unsafeCSS(freshness.stale)}"]) d2l-loading-spinner,
103
77
  :host([_state="hiding"]) d2l-loading-spinner {
104
78
  opacity: 0;
105
79
  transition: opacity ${FADE_DURATION_MS}ms ease-out;
106
80
  }
107
81
 
108
- d2l-backdrop-dirty-overlay {
82
+ d2l-backdrop-stale-overlay {
109
83
  background-color: var(--d2l-theme-backdrop-dialog-color);
110
84
  height: fit-content;
111
85
  justify-content: center;
@@ -114,12 +88,12 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
114
88
  top: 0;
115
89
  z-index: 1000;
116
90
  }
117
- :host([_state="shown"]) d2l-backdrop-dirty-overlay {
91
+ :host([_state="shown"]) d2l-backdrop-stale-overlay {
118
92
  opacity: 1;
119
93
  transition: opacity ${FADE_DURATION_MS}ms ease-in;
120
94
  }
121
- :host([_state="shown"][dataState="loading"]) d2l-backdrop-dirty-overlay,
122
- :host([_state="hiding"]) d2l-backdrop-dirty-overlay {
95
+ :host([_state="shown"][freshness="${unsafeCSS(freshness.loading)}"]) d2l-backdrop-stale-overlay,
96
+ :host([_state="hiding"]) d2l-backdrop-stale-overlay {
123
97
  opacity: 0;
124
98
  transition: opacity ${FADE_DURATION_MS}ms ease-out;
125
99
  }
@@ -131,46 +105,46 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
131
105
 
132
106
  constructor() {
133
107
  super();
134
- this.dataState = 'clean';
135
- this._state = 'hidden';
136
- this._spinnerTop = 0;
137
- this._dirtyDialogTop = 0;
138
108
  this._ariaContent = '';
109
+ this._loadingSpinnerTop = 0;
110
+ this._staleOverlayTop = 0;
111
+ this._state = 'hidden';
139
112
  }
140
113
 
141
114
  render() {
142
115
  const backdropVisible = this._state !== 'hidden';
143
116
 
117
+ const backdropContainer = backdropVisible ? html`<div class="backdrop-container">
118
+ <div class="backdrop" @transitionend="${this.#handleTransitionEnd}" @transitioncancel="${this.#handleTransitionEnd}"></div>
119
+ <d2l-loading-spinner style="${styleMap({ top: `${this._loadingSpinnerTop}px` })}" size="${LOADING_SPINNER_SIZE}"></d2l-loading-spinner>
120
+ </div>` : nothing;
121
+
122
+ const backdropStaleOverlay = backdropVisible ? html`<d2l-backdrop-stale-overlay
123
+ button-text="${this.freshnessStaleButtonText}"
124
+ text="${this.freshnessStaleText}"
125
+ ?inert="${this.freshness !== freshness.stale}"
126
+ style="${styleMap({ top: `${this._staleOverlayTop}px` })}">
127
+ </d2l-backdrop-stale-overlay>` : nothing;
128
+
144
129
  return html`
145
- ${backdropVisible ?
146
- html`<div id="visible">
147
- <div class="backdrop" @transitionend="${this.#handleTransitionEnd}" @transitioncancel="${this.#handleTransitionEnd}"></div>
148
- <d2l-loading-spinner style=${styleMap({ top: `${this._spinnerTop}px` })} size="${LOADING_SPINNER_SIZE}"></d2l-loading-spinner>
149
- </div>` : nothing
150
- }
151
- <div aria-live="polite" id="d2l-live-region">
152
- ${backdropVisible ?
153
- html`<d2l-backdrop-dirty-overlay
154
- style=${styleMap({ top: `${this._dirtyDialogTop}px` })}
155
- description="${this.dirtyText}"
156
- action="${this.dirtyButtonText}"
157
- ?inert=${this.dataState !== 'dirty'}
158
- ></d2l-backdrop-dirty-overlay>` : nothing }
159
- <d2l-offscreen>
160
- ${this._ariaContent}
161
- </d2l-offscreen>
130
+ ${backdropContainer}
131
+ <div aria-live="polite" class="backdrop-stale-container">
132
+ ${backdropStaleOverlay}
133
+ <d2l-offscreen>${this._ariaContent}</d2l-offscreen>
162
134
  </div>
163
135
  `;
164
136
  }
137
+
165
138
  updated(changedProperties) {
166
- if (changedProperties.get('_state') && changedProperties.get('_state') === 'hidden')
167
- {
168
- this.#centerLoadingSpinnerAndDialog();
139
+ super.updated(changedProperties);
140
+
141
+ if (changedProperties.get('_state') && changedProperties.get('_state') === 'hidden') {
142
+ this.#updatePosition();
169
143
  }
170
144
 
171
145
  if (changedProperties.has('_state')) {
172
146
  if (this._state === 'showing') {
173
- if (this.dataState === 'loading') {
147
+ if (this.freshness === freshness.loading) {
174
148
  setTimeout(() => {
175
149
  if (this._state === 'showing') this._state = 'shown';
176
150
  }, BACKDROP_DELAY_MS);
@@ -180,26 +154,27 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
180
154
  }
181
155
  }
182
156
  }
157
+
183
158
  willUpdate(changedProperties) {
184
- if (changedProperties.has('dataState') && changedProperties.get('dataState') !== undefined) {
185
- this.#clearLiveArea();
159
+ super.willUpdate(changedProperties);
186
160
 
187
- const oldState = changedProperties.get('dataState');
188
- const newState = this.dataState;
161
+ if (changedProperties.has('freshness') && changedProperties.get('freshness') !== undefined) {
162
+ this.#clearLiveRegionContent();
189
163
 
190
- // Calculate announcements
191
- if (newState === 'loading') {
192
- this.#setLiveArea(this.localize('components.backdrop-loading.loadingAnnouncement'), { delay: LOADING_ANNOUNCEMENT_DELAY });
193
- } else if (oldState === 'loading' && newState === 'clean') {
194
- this.#setLiveArea(this.localize('components.backdrop-loading.loadingCompleteAnnouncement'));
164
+ const oldState = changedProperties.get('freshness');
165
+ const newState = this.freshness;
166
+
167
+ if (newState === freshness.loading) {
168
+ this.#setLiveRegionContent(this.localize('components.backdrop-loading.loadingAnnouncement'), { delay: LOADING_ANNOUNCEMENT_DELAY });
169
+ } else if (oldState === freshness.loading && newState === freshness.fresh) {
170
+ this.#setLiveRegionContent(this.localize('components.backdrop-loading.loadingCompleteAnnouncement'));
195
171
  }
196
172
 
197
- // Update backdrop
198
- if (oldState === 'clean') {
173
+ if (oldState === freshness.fresh) {
199
174
  this.#show();
200
- } else if (newState === 'clean') {
175
+ } else if (newState === freshness.fresh) {
201
176
  this.#fade();
202
- } else if (oldState === 'loading' && newState === 'dirty') {
177
+ } else if (oldState === freshness.loading && newState === freshness.stale) {
203
178
  setTimeout(() => {
204
179
  if (this._state === 'showing') this._state = 'shown';
205
180
  }, BACKDROP_DELAY_MS);
@@ -207,52 +182,19 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
207
182
  }
208
183
  }
209
184
 
210
- async #centerLoadingSpinnerAndDialog() {
211
- if (this._state === 'hidden') { return; }
212
-
213
- const loadingSpinner = this.shadowRoot.querySelector('d2l-loading-spinner');
214
- if (!loadingSpinner) { return; }
215
-
216
- const boundingRect = this.shadowRoot.querySelector('#visible').getBoundingClientRect();
217
-
218
- // Calculate the centerpoint of the visible portion of the element
219
- const upperVisibleBound = Math.max(0, boundingRect.top);
220
- const lowerVisibleBound = Math.min(window.innerHeight, boundingRect.bottom);
221
- const visibleHeight = lowerVisibleBound - upperVisibleBound;
222
- const centeringOffset = (visibleHeight / 4);
223
-
224
- // Calculate if an offset is required to move to the top of the viewport before centering
225
- const topOffset = Math.max(0, -boundingRect.top); // measures the distance below the top of the viewport, which is negative if the element starts above the viewport
226
-
227
- // Adjust for the size of the spinner
228
- const spinnerSizeOffset = LOADING_SPINNER_SIZE / 2;
229
-
230
- this._spinnerTop = centeringOffset + topOffset - spinnerSizeOffset;
185
+ #announcementTimeoutId;
231
186
 
232
- // Adjust for the size of the dirty dialog
233
- const dirtyOverlay = this.shadowRoot.querySelector('d2l-backdrop-dirty-overlay');
234
- if (dirtyOverlay) {
235
- await this.shadowRoot.querySelector('d2l-empty-state-action-button')?.getUpdateComplete();
236
- const dirtyDialogSizeOffset = dirtyOverlay.getBoundingClientRect().height / 2;
237
-
238
- this._dirtyDialogTop = centeringOffset + topOffset - dirtyDialogSizeOffset;
239
- }
240
- }
241
-
242
- #clearLiveArea() {
187
+ #clearLiveRegionContent() {
243
188
  this._ariaContent = '';
244
189
 
245
- if (this.announcementTimeout) {
246
- clearTimeout(this.announcementTimeout);
247
- }
248
-
249
- this.announcementTimeout = null;
190
+ if (this.#announcementTimeoutId) clearTimeout(this.#announcementTimeoutId);
191
+ this.#announcementTimeoutId = null;
250
192
  }
251
193
 
252
194
  #fade() {
253
195
  let hideImmediately = reduceMotion || this._state === 'showing';
254
196
  if (this._state === 'shown') {
255
- const currentOpacity = getComputedStyle(this.shadowRoot.querySelector('#d2l-live-region')).opacity;
197
+ const currentOpacity = getComputedStyle(this.shadowRoot.querySelector('.backdrop-stale-container')).opacity;
256
198
  hideImmediately ||= (currentOpacity === '0');
257
199
  }
258
200
 
@@ -266,14 +208,10 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
266
208
  #getBackdropTarget() {
267
209
  const parent = getComposedParent(this);
268
210
 
269
- const targetedChildren = getComposedChildren(
270
- parent,
271
- (elem) => elem.id === this.for,
272
- false
273
- );
274
-
275
- if (targetedChildren.length === 0) { throw new Error(`Backdrop cannot find sibling identified by 'for' property with value ${this.for}`);}
276
-
211
+ const targetedChildren = getComposedChildren(parent, (elem) => elem.id === this.for, false);
212
+ if (targetedChildren.length === 0) {
213
+ throw new Error(`Backdrop cannot find sibling identified by 'for' property with value ${this.for}`);
214
+ }
277
215
  return targetedChildren[0];
278
216
  }
279
217
 
@@ -287,24 +225,53 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
287
225
  this._state = 'hidden';
288
226
 
289
227
  const containingBlock = this.#getBackdropTarget();
290
-
291
228
  if (containingBlock.dataset.initiallyInert !== '1') containingBlock.removeAttribute('inert');
292
229
  }
293
230
 
294
- #setLiveArea(content, { delay } = {}) {
295
- this.announcementTimeout = setTimeout(() => this._ariaContent = content, delay || 0);
231
+ #setLiveRegionContent(content, { delay } = {}) {
232
+ this.#announcementTimeoutId = setTimeout(() => this._ariaContent = content, delay || 0);
296
233
  }
297
234
 
298
235
  #show() {
299
236
  this._state = reduceMotion ? 'shown' : 'showing';
300
237
 
301
238
  const containingBlock = this.#getBackdropTarget();
302
-
303
239
  if (containingBlock.getAttribute('inert') !== null) containingBlock.dataset.initiallyInert = '1';
304
-
305
240
  containingBlock.setAttribute('inert', 'inert');
306
241
  }
307
242
 
243
+ async #updatePosition() {
244
+ if (this._state === 'hidden') return;
245
+
246
+ const loadingSpinner = this.shadowRoot.querySelector('d2l-loading-spinner');
247
+ if (!loadingSpinner) return;
248
+
249
+ const backdropContainerRect = this.shadowRoot.querySelector('.backdrop-container').getBoundingClientRect();
250
+
251
+ // Calculate the centerpoint of the visible portion of the element
252
+ const upperVisibleBound = Math.max(0, backdropContainerRect.top);
253
+ const lowerVisibleBound = Math.min(window.innerHeight, backdropContainerRect.bottom);
254
+ const visibleHeight = lowerVisibleBound - upperVisibleBound;
255
+ const centeringOffset = (visibleHeight / 4);
256
+
257
+ // Calculate if an offset is required to move to the top of the viewport before centering
258
+ const topOffset = Math.max(0, -backdropContainerRect.top); // measures the distance below the top of the viewport, which is negative if the element starts above the viewport
259
+
260
+ // Adjust for the size of the spinner
261
+ const spinnerSizeOffset = LOADING_SPINNER_SIZE / 2;
262
+
263
+ this._loadingSpinnerTop = centeringOffset + topOffset - spinnerSizeOffset;
264
+
265
+ // Adjust for the size of the stale overlay
266
+ const staleOverlay = this.shadowRoot.querySelector('d2l-backdrop-stale-overlay');
267
+ if (staleOverlay) {
268
+ await this.shadowRoot.querySelector('d2l-empty-state-action-button')?.getUpdateComplete();
269
+ const staleOverlaySizeOffset = staleOverlay.getBoundingClientRect().height / 2;
270
+
271
+ this._staleOverlayTop = centeringOffset + topOffset - staleOverlaySizeOffset;
272
+ }
273
+ }
274
+
308
275
  }
309
276
 
310
- customElements.define('d2l-backdrop-loading', LoadingBackdrop);
277
+ customElements.define('d2l-backdrop-loading', BackdropLoading);
@@ -0,0 +1,58 @@
1
+ import '../button/button-subtle.js';
2
+ import { css, html, LitElement } from 'lit';
3
+ import { bodyCompactStyles } from '../typography/styles.js';
4
+ import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
5
+
6
+ /**
7
+ * A component to render as an overlay over another element with stale data.
8
+ */
9
+ class BackdropStaleOverlay extends LocalizeCoreElement(LitElement) {
10
+
11
+ static properties = {
12
+ /**
13
+ * The action button text
14
+ * @type {string}
15
+ */
16
+ buttonText: { type: String, attribute: 'button-text' },
17
+ /**
18
+ * The text displayed on the overlay
19
+ * @type {string}
20
+ */
21
+ text: { type: String }
22
+ };
23
+
24
+ static styles = [bodyCompactStyles, css`
25
+ :host {
26
+ align-items: center;
27
+ border: 1px solid var(--d2l-color-mica);
28
+ border-radius: 0.3rem;
29
+ column-gap: 0.75rem;
30
+ display: flex;
31
+ flex-wrap: wrap;
32
+ padding: 1.2rem 1.5rem;
33
+ }
34
+ `];
35
+
36
+ render() {
37
+ const message = this.text || this.localize('components.backdrop-stale-overlay.message');
38
+ const buttonText = this.buttonText || this.localize('intl-common:actions:reload');;
39
+
40
+ return html`
41
+ <p class="d2l-body-compact">${message}</p>
42
+ <d2l-button-subtle
43
+ @click="${this.#handleActionClick}"
44
+ text="${buttonText}">
45
+ </d2l-button-subtle>
46
+ `;
47
+ }
48
+
49
+ #handleActionClick(e) {
50
+ e.stopPropagation();
51
+
52
+ /** Dispatched when the action button on the overlay is clicked */
53
+ this.dispatchEvent(new CustomEvent('d2l-backdrop-stale-overlay-action', { bubbles: true, composed: true }));
54
+ }
55
+
56
+ }
57
+
58
+ customElements.define('d2l-backdrop-stale-overlay', BackdropStaleOverlay);
@@ -16,26 +16,46 @@ class PanelStateController {
16
16
  this.#host = host;
17
17
  this.#panels = {};
18
18
  for (const [key, config] of Object.entries(panelConfigs)) {
19
- this.#panels[key] = { collapsed: false, size: 0, minSize: config.minSize, maxSize: config.minSize };
19
+ this.#panels[key] = {
20
+ animate: false,
21
+ collapsed: false,
22
+ size: 0,
23
+ minSize: config.minSize,
24
+ maxSize: config.minSize
25
+ };
20
26
  }
21
27
  host.addController(this);
22
28
  }
23
29
 
30
+ getAnimate(key) { return this.#panels[key].animate; }
24
31
  getCollapsed(key) { return this.#panels[key].collapsed; }
25
32
  getMaxSize(key) { return this.#panels[key].maxSize; }
26
33
  getMinSize(key) { return this.#panels[key].minSize; }
27
- getSize(key) { return this.#panels[key].size; }
34
+ getSize(key) {
35
+ const panel = this.#panels[key];
36
+ // TO DO: Factor in dragging
37
+ return panel.collapsed ? 0 : panel.size;
38
+ }
28
39
 
29
- resize(key, requestedSize) {
40
+ resize(key, requestedSize, animate = false) {
30
41
  const panel = this.#panels[key];
31
42
  // Clamp requested size to min and max bounds
32
43
  panel.size = Math.max(panel.minSize, Math.min(requestedSize, panel.maxSize));
44
+ panel.animate = animate;
33
45
  this.#host.requestUpdate();
34
46
  }
35
47
 
36
48
  setCollapsed(key, collapsed) {
37
49
  const panel = this.#panels[key];
38
50
  panel.collapsed = collapsed;
51
+ panel.animate = true;
52
+ this.#host.requestUpdate();
53
+ }
54
+
55
+ setDragSize(key) {
56
+ const panel = this.#panels[key];
57
+ // TO DO: Handle Dragging
58
+ panel.animate = false;
39
59
  this.#host.requestUpdate();
40
60
  }
41
61
 
@@ -155,6 +175,27 @@ class Page extends ProviderMixin(LocalizeCoreElement(LitElement)) {
155
175
  z-index: 15; /* To be over d2l-page-* panel headers */
156
176
  }
157
177
 
178
+ .side-nav .divider[collapsed] {
179
+ margin-inline-start: 18px;
180
+ }
181
+ .supporting .divider[collapsed] {
182
+ margin-inline-end: 18px;
183
+ }
184
+ .side-nav-panel.collapsed,
185
+ .supporting-panel.collapsed {
186
+ visibility: hidden;
187
+ }
188
+ @media (prefers-reduced-motion: no-preference) {
189
+ .side-nav-panel.animate,
190
+ .supporting-panel.animate {
191
+ transition: width 400ms cubic-bezier(0, 0.7, 0.5, 1);
192
+ }
193
+ .side-nav-panel.animate.collapsed,
194
+ .supporting-panel.animate.collapsed {
195
+ transition: width 400ms cubic-bezier(0, 0.7, 0.5, 1), visibility 0s 400ms;
196
+ }
197
+ }
198
+
158
199
  .footer:not([hidden]),
159
200
  .floating-buttons-container {
160
201
  display: inline;
@@ -285,7 +326,7 @@ class Page extends ProviderMixin(LocalizeCoreElement(LitElement)) {
285
326
 
286
327
  #handleDividerResize(e) {
287
328
  const panelKey = e.target.dataset.panelKey;
288
- this._panelState.resize(panelKey, e.detail.requestedSize);
329
+ this._panelState.resize(panelKey, e.detail.requestedSize, true);
289
330
  };
290
331
 
291
332
  #handleDividerToggle(e) {
@@ -363,10 +404,15 @@ class Page extends ProviderMixin(LocalizeCoreElement(LitElement)) {
363
404
  }
364
405
 
365
406
  #renderSideNavPanel() {
407
+ const classes = {
408
+ 'side-nav-panel': true,
409
+ 'animate': this._panelState.getAnimate('side-nav'),
410
+ 'collapsed': this._panelState.getCollapsed('side-nav')
411
+ };
366
412
  return html`
367
413
  <nav class="side-nav" aria-label="${this.localize('components.page.side-nav-label')}">
368
414
  <div
369
- class="side-nav-panel"
415
+ class="${classMap(classes)}"
370
416
  style=${styleMap({ width: `${this._panelState.getSize('side-nav')}px` })}
371
417
  ?hidden="${!this._slotVisibility['side-nav']}">
372
418
  <slot name="side-nav" @slotchange="${this.#handleSlotVisibilityChange}"></slot>
@@ -378,12 +424,17 @@ class Page extends ProviderMixin(LocalizeCoreElement(LitElement)) {
378
424
  }
379
425
 
380
426
  #renderSupportingPanel() {
427
+ const classes = {
428
+ 'supporting-panel': true,
429
+ 'animate': this._panelState.getAnimate('supporting'),
430
+ 'collapsed': this._panelState.getCollapsed('supporting')
431
+ };
381
432
  return html`
382
433
  <aside class="supporting" aria-label="${this.localize('components.page.supporting-label')}">
383
434
  ${!this._slotVisibility['supporting'] ? nothing :
384
435
  this.#renderDivider('supporting', this.localize('components.page.supporting-divider-label'), 'end')}
385
436
  <div
386
- class="supporting-panel"
437
+ class="${classMap(classes)}"
387
438
  style=${styleMap({ width: `${this._panelState.getSize('supporting')}px` })}
388
439
  ?hidden="${!this._slotVisibility['supporting']}">
389
440
  <slot name="supporting" @slotchange="${this.#handleSlotVisibilityChange}"></slot>
@@ -18,7 +18,7 @@ import '../../inputs/input-radio.js';
18
18
  import '../../inputs/input-radio-group.js';
19
19
 
20
20
  import { css, html, nothing } from 'lit';
21
- import { tableStyles, TableWrapper } from '../table-wrapper.js';
21
+ import { tableFreshness, tableStyles, TableWrapper } from '../table-wrapper.js';
22
22
  import { DemoPassthroughMixin } from '../../demo/demo-passthrough-mixin.js';
23
23
  import { ifDefined } from 'lit/directives/if-defined.js';
24
24
 
@@ -83,12 +83,12 @@ class TestTable extends DemoPassthroughMixin(TableWrapper, 'd2l-table-wrapper')
83
83
  this._data = data();
84
84
  this._sortField = undefined;
85
85
  this._sortDesc = false;
86
- this.dataState = 'clean';
86
+ this.freshness = tableFreshness.fresh;
87
87
  }
88
88
 
89
89
  render() {
90
90
  return html`
91
- <d2l-table-wrapper item-count="${ifDefined(this.paging ? 500 : undefined)}" dirty-text="This text indicates we're in the dirty state" dirty-button-text="Refresh">
91
+ <d2l-table-wrapper item-count="${ifDefined(this.paging ? 500 : undefined)}" freshness-stale-text="This table data is out of date." freshness-stale-button-text="Reload Me!">
92
92
  <d2l-table-controls slot="controls" ?no-sticky="${!this.stickyControls}" select-all-pages-allowed>
93
93
  <d2l-selection-action
94
94
  text="Sticky controls"
@@ -100,10 +100,10 @@ class TestTable extends DemoPassthroughMixin(TableWrapper, 'd2l-table-wrapper')
100
100
  icon="tier1:${this.stickyHeaders ? 'check' : 'close-default'}"
101
101
  @d2l-selection-action-click="${this._toggleStickyHeaders}">
102
102
  </d2l-selection-action>
103
- <d2l-input-radio-group label="Date State" horizontal label-hidden name="dataState" @change=${this._handleDataStateChange}>
104
- <d2l-input-radio label="Clean" value="clean" ?checked=${this.dataState === 'clean'}></d2l-input-radio>
105
- <d2l-input-radio label="Dirty" value="dirty" ?checked=${this.dataState === 'dirty'}></d2l-input-radio>
106
- <d2l-input-radio label="Loading" value="loading" ?checked=${this.dataState === 'loading'}></d2l-input-radio>
103
+ <d2l-input-radio-group label="Freshness" horizontal label-hidden name="freshness" @change=${this._handleFreshnessChange}>
104
+ <d2l-input-radio label="Fresh" value="${tableFreshness.fresh}" ?checked=${this.freshness === tableFreshness.fresh}></d2l-input-radio>
105
+ <d2l-input-radio label="Stale" value="${tableFreshness.stale}" ?checked=${this.freshness === tableFreshness.stale}></d2l-input-radio>
106
+ <d2l-input-radio label="Loading" value="${tableFreshness.loading}" ?checked=${this.freshness === tableFreshness.loading}></d2l-input-radio>
107
107
  </d2l-input-radio-group>
108
108
  </d2l-table-controls>
109
109
 
@@ -170,8 +170,8 @@ class TestTable extends DemoPassthroughMixin(TableWrapper, 'd2l-table-wrapper')
170
170
  `;
171
171
  }
172
172
 
173
- _handleDataStateChange(e) {
174
- this.dataState = e.detail.value;
173
+ _handleFreshnessChange(e) {
174
+ this.freshness = e.detail.value;
175
175
  }
176
176
 
177
177
  async _handlePagerLoadMore(e) {