From 7f3dfc8738adfa8cf230a86ced750abbc023dd3c Mon Sep 17 00:00:00 2001
From: Stacey Van Herk <13419300+svanherk@users.noreply.github.com>
Date: Fri, 1 May 2026 17:58:16 -0400
Subject: [PATCH 01/19] GAUD-9165 - Implement the d2l-page skeleton
---
components/page/README.md | 5 +
components/page/demo/page-component.js | 191 ++++++++++++++++++++
components/page/demo/page.html | 15 ++
components/page/demo/temp-nav-styles.js | 217 +++++++++++++++++++++++
components/page/page.js | 223 ++++++++++++++++++++++++
components/page/test/page.axe.js | 40 +++++
components/page/test/page.test.js | 10 ++
index.html | 1 +
lang/ar.js | 2 +
lang/ca.js | 2 +
lang/cy.js | 2 +
lang/da.js | 2 +
lang/de.js | 2 +
lang/en-gb.js | 2 +
lang/en.js | 2 +
lang/es-es.js | 2 +
lang/es.js | 2 +
lang/fr-fr.js | 2 +
lang/fr.js | 2 +
lang/haw.js | 2 +
lang/hi.js | 2 +
lang/ja.js | 2 +
lang/ko.js | 2 +
lang/mi.js | 2 +
lang/nl.js | 2 +
lang/pt.js | 2 +
lang/sv.js | 2 +
lang/th.js | 2 +
lang/tr.js | 2 +
lang/vi.js | 2 +
lang/zh-cn.js | 2 +
lang/zh-tw.js | 2 +
32 files changed, 750 insertions(+)
create mode 100644 components/page/README.md
create mode 100644 components/page/demo/page-component.js
create mode 100644 components/page/demo/page.html
create mode 100644 components/page/demo/temp-nav-styles.js
create mode 100644 components/page/page.js
create mode 100644 components/page/test/page.axe.js
create mode 100644 components/page/test/page.test.js
diff --git a/components/page/README.md b/components/page/README.md
new file mode 100644
index 00000000000..11d630c5402
--- /dev/null
+++ b/components/page/README.md
@@ -0,0 +1,5 @@
+# Page
+
+The `d2l-page` and page layout components are in progress.
+
+Please reach out before using them!
diff --git a/components/page/demo/page-component.js b/components/page/demo/page-component.js
new file mode 100644
index 00000000000..5aa62dc03e5
--- /dev/null
+++ b/components/page/demo/page-component.js
@@ -0,0 +1,191 @@
+import '../../collapsible-panel/collapsible-panel.js';
+import '../page.js';
+import { css, html, LitElement, nothing } from 'lit';
+import { navStyles } from './temp-nav-styles.js';
+import { selectStyles } from '../../inputs/input-select-styles.js';
+
+/**
+ * Component for d2l-page demos and tests
+ */
+class PageDemo extends LitElement {
+
+ static properties = {
+ demoMode: { type: Boolean, attribute: 'demo-mode' },
+ hasFooter: { type: Boolean, attribute: 'has-footer' },
+ hasSideNavPanel: { type: Boolean, attribute: 'has-side-nav-panel' },
+ hasSupportingPanel: { type: Boolean, attribute: 'has-supporting-panel' },
+ navType: { type: String, attribute: 'nav-type' },
+ widthType: { type: String, attribute: 'width-type' },
+ _allowThreePanels: { state: true }
+ };
+
+ static styles = [navStyles, selectStyles, css`
+ .demo-controls {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+ }
+ `];
+
+ constructor() {
+ super();
+ this._allowThreePanels = false; // Temp for dev/testing
+ this.demoMode = false;
+ this.hasFooter = false;
+ this.hasSideNavPanel = false;
+ this.hasSupportingPanel = false;
+ this.navType = 'full';
+ /** @type {'normal'|'wide'|'fullscreen'} */
+ this.widthType = 'normal';
+ }
+
+ render() {
+ return html`
+
+ ${this.navType === 'full' ? this.#renderFullNav() : this.#renderImmersiveNav()}
+ ${this.#renderSideNavPanel()}
+ ${this.#renderMainPanel()}
+ ${this.#renderSupportingPanel()}
+ ${this.#renderFooter()}
+
+ `;
+ }
+
+ #handleAllowThreePanelsChange(e) {
+ this._allowThreePanels = e.target.on;
+ if (!this._allowThreePanels && this.hasSideNavPanel && this.hasSupportingPanel) {
+ this.shadowRoot.querySelector('#switch-supporting-panel').on = false;
+ this.hasSupportingPanel = false;
+ }
+ }
+
+ #handleNavTypeChange(e) {
+ this.navType = e.target.on ? 'immersive' : 'full';
+ }
+
+ #handleVisibilityChange(e) {
+ const key = e.target.dataset.key;
+ this[key] = e.target.on;
+
+ if (this._allowThreePanels) return;
+ if (e.target.on && key === 'hasSideNavPanel' && this.hasSupportingPanel) {
+ this.shadowRoot.querySelector('#switch-supporting-panel').on = false;
+ this.hasSupportingPanel = false;
+ } else if (e.target.on && key === 'hasSupportingPanel' && this.hasSideNavPanel) {
+ this.shadowRoot.querySelector('#switch-side-nav-panel').on = false;
+ this.hasSideNavPanel = false;
+ }
+ }
+
+ #handleWidthTypeChange(e) {
+ this.widthType = e.target.value;
+ }
+
+ #renderFooter() {
+ return this.hasFooter ? html`
+
+ ${header}
+
+ ${sideNavPanel}
+ ${mainContent}
+ ${supportingPanel}
+
+ ${footer}
+
+ `;
+ }
+
+ #resizeObserver;
+
+ #handleHeaderSlotChange(e) {
+ const nodes = e.target.assignedNodes();
+ //this._headerIsSticky = nodes.some(node => node.tagName.toLowerCase() === 'd2l-page-header-immersive');
+ this._headerIsSticky = nodes.some(node => node.id === 'immersive-nav'); // temp until the official component exists
+ }
+
+ #handleSlotVisibilityChange(e) {
+ const key = e.target.name;
+ const nodes = e.target.assignedNodes();
+ this._slotVisibility = { ...this._slotVisibility, [key]: nodes.length !== 0 };
+ this.requestUpdate();
+ }
+
+ #renderFloatingButtons(footerContents) {
+ // Floating buttons needs to be wrapped as it spawns a sibling element that should be cleaned up as one by Lit
+ return html`
+
@@ -149,22 +165,7 @@ class PageDemo extends LitElement {
#renderMainPanel() {
return html`
- ${this.demoMode ? html`
-
-
-
-
-
-
-
-
-
-
- ` : nothing}
+ ${this.#renderDemoMainControls()}
I'm in the default slot of the d2l-page component!
End of Content
From 59495f89a5a7c9fab7f76af527098c15d50f743f Mon Sep 17 00:00:00 2001
From: Stacey Van Herk <13419300+svanherk@users.noreply.github.com>
Date: Tue, 5 May 2026 18:26:41 -0400
Subject: [PATCH 10/19] Set --d2l-page-header-height to 0 when header is not
sticky
---
components/page/page.js | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/components/page/page.js b/components/page/page.js
index ef417d34ef0..f31acd92714 100644
--- a/components/page/page.js
+++ b/components/page/page.js
@@ -63,14 +63,9 @@ class Page extends LocalizeCoreElement(LitElement) {
.side-nav-panel,
.supporting-panel {
- height: calc(100vh - var(--d2l-page-footer-height, 0));
+ height: calc(100vh - var(--d2l-page-header-height, 0) - var(--d2l-page-footer-height, 0));
overflow: clip auto;
position: sticky;
- top: 0;
- }
- .page.header-sticky .side-nav-panel,
- .page.header-sticky .supporting-panel {
- height: calc(100vh - var(--d2l-page-header-height, 0) - var(--d2l-page-footer-height, 0));
top: var(--d2l-page-header-height, 0);
}
@@ -109,7 +104,7 @@ class Page extends LocalizeCoreElement(LitElement) {
this.#resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
if (entry.target.classList.contains('header')) {
- const height = entry.target.offsetHeight;
+ const height = this._headerIsSticky ? entry.target.offsetHeight : 0;
this.style.setProperty('--d2l-page-header-height', `${height}px`);
} else if (entry.target.classList.contains('footer')) {
const height = entry.target.classList.contains('fixed-footer') ? entry.target.offsetHeight : 0;
From d04f7d67dd8c2a1312e05734468e27391d87f9d4 Mon Sep 17 00:00:00 2001
From: Stacey Van Herk <13419300+svanherk@users.noreply.github.com>
Date: Tue, 5 May 2026 18:31:41 -0400
Subject: [PATCH 11/19] requestUpdate not needed since we're replacing the
object
---
components/page/page.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/components/page/page.js b/components/page/page.js
index f31acd92714..4df1c36aaf4 100644
--- a/components/page/page.js
+++ b/components/page/page.js
@@ -194,7 +194,6 @@ class Page extends LocalizeCoreElement(LitElement) {
const key = e.target.name;
const nodes = e.target.assignedNodes();
this._slotVisibility = { ...this._slotVisibility, [key]: nodes.length !== 0 };
- this.requestUpdate();
}
#renderFloatingButtons(footerContents) {
From 551d5c72d87a45b8b7873d9a88f331a903ed540a Mon Sep 17 00:00:00 2001
From: Stacey Van Herk <13419300+svanherk@users.noreply.github.com>
Date: Tue, 5 May 2026 18:32:08 -0400
Subject: [PATCH 12/19] Move observe to connectedCallback
---
components/page/page.js | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/components/page/page.js b/components/page/page.js
index 4df1c36aaf4..2186d186b91 100644
--- a/components/page/page.js
+++ b/components/page/page.js
@@ -116,20 +116,18 @@ class Page extends LocalizeCoreElement(LitElement) {
connectedCallback() {
super.connectedCallback();
- }
-
- disconnectedCallback() {
- super.disconnectedCallback();
- this.#resizeObserver.disconnect();
- }
- firstUpdated() {
const header = this.shadowRoot.querySelector('.header');
const footer = this.shadowRoot.querySelector('.footer');
if (header) this.#resizeObserver.observe(header);
if (footer) this.#resizeObserver.observe(footer);
}
+ disconnectedCallback() {
+ super.disconnectedCallback();
+ this.#resizeObserver.disconnect();
+ }
+
render() {
const pageClasses = {
'page': true,
From 93af9b4ce58d70d2c27162bbfffe87cadf604714 Mon Sep 17 00:00:00 2001
From: Stacey Van Herk <13419300+svanherk@users.noreply.github.com>
Date: Tue, 5 May 2026 18:38:47 -0400
Subject: [PATCH 13/19] Pull render pieces out into their own functions
---
components/page/page.js | 82 ++++++++++++++++++++++-------------------
1 file changed, 44 insertions(+), 38 deletions(-)
diff --git a/components/page/page.js b/components/page/page.js
index 2186d186b91..52ba8a31f45 100644
--- a/components/page/page.js
+++ b/components/page/page.js
@@ -134,48 +134,15 @@ class Page extends LocalizeCoreElement(LitElement) {
'header-sticky': this._headerIsSticky
};
- const header = html`
- `;
-
- const mainContent = html`
-
-
- `;
-
- const sideNavPanel = html`
-
- ${this._slotVisibility['side-nav'] ? html`
` : nothing}
- `;
-
- const supportingPanel = html`
- ${this._slotVisibility['supporting'] ? html`
` : nothing}
-
`;
-
- const fixedFooter = this._slotVisibility['side-nav'] || this._slotVisibility['supporting'];
- const footerContainerClasses = { 'footer': true, 'fixed-footer': fixedFooter };
- const footerContents = html``;
- const footer = html`
- `;
-
return html`
- ${header}
+ ${this.#renderHeader()}
- ${sideNavPanel}
- ${mainContent}
- ${supportingPanel}
+ ${this.#renderSideNavPanel()}
+
+ ${this.#renderSupportingPanel()}
- ${footer}
+ ${this.#renderFooter()}
`;
}
@@ -205,6 +172,45 @@ class Page extends LocalizeCoreElement(LitElement) {
`;
}
+ #renderFooter() {
+ const fixedFooter = this._slotVisibility['side-nav'] || this._slotVisibility['supporting'];
+ const footerContainerClasses = { 'footer': true, 'fixed-footer': fixedFooter };
+ const footerContents = html``;
+ return html`
+
+ `;
+ }
+
+ #renderHeader() {
+ return html`
+
+ `;
+ }
+
+ #renderSideNavPanel() {
+ return html`
+
+ ${this._slotVisibility['side-nav'] ? html`
` : nothing}
+ `;
+ }
+
+ #renderSupportingPanel() {
+ return html`
+ ${this._slotVisibility['supporting'] ? html`
` : nothing}
+
+ `;
+ }
+
}
customElements.define('d2l-page', Page);
From e66ae7135deb38443fe6663ef9ee430e6bfd5a07 Mon Sep 17 00:00:00 2001
From: Stacey Van Herk <13419300+svanherk@users.noreply.github.com>
Date: Tue, 5 May 2026 18:48:20 -0400
Subject: [PATCH 14/19] Revert "Move observe to connectedCallback"
This reverts commit 551d5c72d87a45b8b7873d9a88f331a903ed540a.
---
components/page/page.js | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/components/page/page.js b/components/page/page.js
index 52ba8a31f45..b1ff21b7c48 100644
--- a/components/page/page.js
+++ b/components/page/page.js
@@ -116,11 +116,6 @@ class Page extends LocalizeCoreElement(LitElement) {
connectedCallback() {
super.connectedCallback();
-
- const header = this.shadowRoot.querySelector('.header');
- const footer = this.shadowRoot.querySelector('.footer');
- if (header) this.#resizeObserver.observe(header);
- if (footer) this.#resizeObserver.observe(footer);
}
disconnectedCallback() {
@@ -128,6 +123,13 @@ class Page extends LocalizeCoreElement(LitElement) {
this.#resizeObserver.disconnect();
}
+ firstUpdated() {
+ const header = this.shadowRoot.querySelector('.header');
+ const footer = this.shadowRoot.querySelector('.footer');
+ if (header) this.#resizeObserver.observe(header);
+ if (footer) this.#resizeObserver.observe(footer);
+ }
+
render() {
const pageClasses = {
'page': true,
From 919577eeb514be303647dfb881ba040fcf05adf5 Mon Sep 17 00:00:00 2001
From: Stacey Van Herk <13419300+svanherk@users.noreply.github.com>
Date: Tue, 5 May 2026 19:11:24 -0400
Subject: [PATCH 15/19] Save padding for the d2l-page-footer component
---
components/page/page.js | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/components/page/page.js b/components/page/page.js
index b1ff21b7c48..cc17e0fc0b5 100644
--- a/components/page/page.js
+++ b/components/page/page.js
@@ -86,9 +86,6 @@ class Page extends LocalizeCoreElement(LitElement) {
padding: 0.75rem 0;
position: fixed;
}
- .floating-footer {
- padding-block-end: 0.75rem;
- }
.footer-contents {
margin-inline: var(--d2l-page-margin-inline, 0);
max-width: var(--d2l-page-footer-max-width, 100%);
@@ -168,7 +165,7 @@ class Page extends LocalizeCoreElement(LitElement) {
return html`
-
+ ${footerContents}
`;
From d609d926db47a71cf421f8244acd0cfca232eead Mon Sep 17 00:00:00 2001
From: Stacey Van Herk <13419300+svanherk@users.noreply.github.com>
Date: Tue, 5 May 2026 19:11:54 -0400
Subject: [PATCH 16/19] Adjust description
Co-authored-by: Dave Lockhart
---
components/page/page.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/components/page/page.js b/components/page/page.js
index cc17e0fc0b5..9b3efef9acd 100644
--- a/components/page/page.js
+++ b/components/page/page.js
@@ -4,7 +4,7 @@ import { classMap } from 'lit/directives/class-map.js';
import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
/**
- * Page template with header, optional footer and optional navigation panel or supporting panel
+ * Component for laying out a page, with header, optional footer and optional navigation or supporting panels
* @slot - The main content of the page (expecting d2l-page-main)
* @slot header - The header content of the page (expecting d2l-page-header-*)
* @slot side-nav - The side navigation content of the page (expecting d2l-page-side-nav)
From 20a6859912289ff858a9f752ceec29ae639d134c Mon Sep 17 00:00:00 2001
From: Stacey Van Herk <13419300+svanherk@users.noreply.github.com>
Date: Tue, 5 May 2026 22:21:48 -0400
Subject: [PATCH 17/19] Re-arrange demo to make cleaner next PR
---
components/page/demo/page-component.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/components/page/demo/page-component.js b/components/page/demo/page-component.js
index 6483266d547..03fd59ddc2b 100644
--- a/components/page/demo/page-component.js
+++ b/components/page/demo/page-component.js
@@ -164,11 +164,11 @@ class PageDemo extends LitElement {
#renderMainPanel() {
return html`
-
- ${this.#renderDemoMainControls()}
+
I'm in the default slot of the d2l-page component!
+ ${this.#renderDemoMainControls()}
+
End of Content
-
End of Content
`;
}
From c86d89018c0088cdac5ac471c1ab10ea6a8ad286 Mon Sep 17 00:00:00 2001
From: Stacey Van Herk <13419300+svanherk@users.noreply.github.com>
Date: Tue, 5 May 2026 22:32:59 -0400
Subject: [PATCH 18/19] Footer needs z-index
---
components/page/page.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/components/page/page.js b/components/page/page.js
index 9b3efef9acd..1ad2ee4c90c 100644
--- a/components/page/page.js
+++ b/components/page/page.js
@@ -85,6 +85,7 @@ class Page extends LocalizeCoreElement(LitElement) {
inset: auto 0 0;
padding: 0.75rem 0;
position: fixed;
+ z-index: 10; /* To be over sticky content of our core components */
}
.footer-contents {
margin-inline: var(--d2l-page-margin-inline, 0);
From 1a9239338713c5612a5cec6f563f113d603d57cd Mon Sep 17 00:00:00 2001
From: Stacey Van Herk <13419300+svanherk@users.noreply.github.com>
Date: Wed, 6 May 2026 09:30:30 -0400
Subject: [PATCH 19/19] Add colors import, remove empty constructor
---
components/page/page.js | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/components/page/page.js b/components/page/page.js
index 1ad2ee4c90c..9157c2a78af 100644
--- a/components/page/page.js
+++ b/components/page/page.js
@@ -1,3 +1,4 @@
+import '../colors/colors.js';
import '../button/floating-buttons.js';
import { css, html, LitElement, nothing } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
@@ -112,10 +113,6 @@ class Page extends LocalizeCoreElement(LitElement) {
});
}
- connectedCallback() {
- super.connectedCallback();
- }
-
disconnectedCallback() {
super.disconnectedCallback();
this.#resizeObserver.disconnect();