diff --git a/components/collapsible-panel/collapsible-panel.js b/components/collapsible-panel/collapsible-panel.js
index 8ace4071b6d..043c1ba26fc 100644
--- a/components/collapsible-panel/collapsible-panel.js
+++ b/components/collapsible-panel/collapsible-panel.js
@@ -173,15 +173,17 @@ class CollapsiblePanel extends SkeletonMixin(FocusMixin(LitElement)) {
border-radius: 0;
outline-offset: -2px;
}
- .d2l-collapsible-panel.scrolled .d2l-collapsible-panel-header {
+ .d2l-collapsible-panel .d2l-collapsible-panel-header {
background-color: var(--d2l-theme-background-color-base);
- box-shadow: 0 8px 12px -9px rgba(0, 0, 0, 0.3);
position: sticky;
- top: 0;
+ top: var(--d2l-sticky-offset, 0px);
z-index: 11; /* must be greater greater than list-items with open dropdowns or tooltips */
}
+ .d2l-collapsible-panel.scrolled .d2l-collapsible-panel-header {
+ box-shadow: 0 8px 12px -9px rgba(0, 0, 0, 0.3);
+ }
.d2l-collapsible-panel.focused.scrolled .d2l-collapsible-panel-header {
- top: 2px;
+ top: calc(var(--d2l-sticky-offset, 0px) + 2px);
}
.d2l-collapsible-panel-title {
flex: 1;
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..468ebe83e2c
--- /dev/null
+++ b/components/page/demo/page-component.js
@@ -0,0 +1,528 @@
+import '../../button/button.js';
+import '../../button/button-subtle.js';
+import '../../collapsible-panel/collapsible-panel.js';
+import '../../collapsible-panel/collapsible-panel-group.js';
+import '../../collapsible-panel/collapsible-panel-summary-item.js';
+import '../../demo/demo-page-settings.js';
+import '../../dialog/dialog.js';
+import '../../inputs/input-checkbox.js';
+import '../../inputs/input-date.js';
+import '../../inputs/input-number.js';
+import '../../inputs/input-text.js';
+import '../../list/list.js';
+import '../../list/list-controls.js';
+import '../../list/list-item.js';
+import '../../list/list-item-content.js';
+import '../../list/list-item-nav.js';
+import '../../selection/selection-action.js';
+import '../../table/table-controls.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';
+import { tableStyles } from '../../table/table-wrapper.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, tableStyles, 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;
+ }
+ }
+
+ // TO DO: Don't commit this! Fix the dialog and add this in the main header
+ #handleDialogBrokenClick() {
+ this.shadowRoot.querySelector('#broken-dialog').opened = true;
+ }
+
+ #handleDialogNativeClick() {
+ this.shadowRoot.querySelector('#native-dialog').opened = true;
+ }
+
+ #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`
+
+ I'm in the footer slot of the d2l-page component!
+
+ ` : nothing;
+ }
+
+ #renderFullNav() {
+ // Update with navigation components once available
+ return html`
+
+ `;
+ }
+
+ #renderImmersiveNav() {
+ // Update with navigation components once available
+ return html`
+
+
+
+
+
+ Assignment 1 - Introduction to Economics
+
+
+
+
+
+
+
+
+ `;
+ }
+
+ #renderMainPanel() {
+ return html`
+
+
I'm in the default slot of the d2l-page component!
+ ${this.demoMode ? html`
+
+
+
+
+
+
+
+
+
+
+ ` : nothing}
+
+
List with Sticky Controls (extend-separators)
+
+
+
+
+
+
+
+
+
+ Introduction to Economics
+ Chapter 1 — Fundamentals
+ Due: May 15, 2026
+
+
+
+
+ Supply and Demand
+ Chapter 2 — Market Forces
+ Due: May 22, 2026
+
+
+
+
+ Market Equilibrium
+ Chapter 3 — Price Discovery
+ Due: May 29, 2026
+
+
+
+
+
List with Sticky Controls (no extend-separators)
+
+
+
+
+
+
+
+ Assignment 1: Research Proposal
+ Weight: 20%
+ Submissions: 14/30
+
+
+
+
+ Assignment 2: Literature Review
+ Weight: 30%
+ Submissions: 8/30
+
+
+
+
+ Assignment 3: Final Paper
+ Weight: 50%
+ Submissions: 0/30
+
+
+
+
+
Table with Sticky Controls
+
+
+
+
+
+
+
+
+ | Course |
+ Enrolled |
+ Completion Rate |
+ Avg Grade |
+
+
+
+
+ | Introduction to Biology |
+ 145 |
+ 87% |
+ B+ |
+
+
+ | Advanced Chemistry |
+ 62 |
+ 79% |
+ B |
+
+
+ | World History |
+ 98 |
+ 92% |
+ A- |
+
+
+
+
+
+
Sticky Table
+
+
+
+
+ | Student |
+ Assignment 1 |
+ Assignment 2 |
+ Final Exam |
+ Total |
+
+
+
+
+ | Alice Johnson |
+ 92 |
+ 88 |
+ 95 |
+ 91.7 |
+
+
+ | Bob Smith |
+ 85 |
+ 79 |
+ 82 |
+ 82.0 |
+
+
+ | Carol Davis |
+ 78 |
+ 91 |
+ 87 |
+ 85.3 |
+
+
+ | David Lee |
+ 95 |
+ 93 |
+ 90 |
+ 92.7 |
+
+
+ | Emily Chen |
+ 88 |
+ 84 |
+ 91 |
+ 87.7 |
+
+
+
+
+
+
+
+
+
+ Allow late submissions
+
+ Add
+ Cancel
+
+
+
+
+
+
+ Allow late submissions
+
+ Add
+ Cancel
+
+
+ End of Content
+ `;
+ }
+
+ #renderSideNavPanel() {
+ return this.hasSideNavPanel ? html`
+
+
I'm in the side-nav slot of the d2l-page component!
+
+
+
+ Course Overview
+
+
+
+
+ Unit 1: Foundations
+ 3 items
+
+
+
+
+ Reading: Core Concepts
+
+
+
+
+ Discussion: Key Takeaways
+
+
+
+
+ Quiz: Chapter 1
+
+
+
+
+
+
+ Unit 2: Applications
+ 4 items
+
+
+
+
+ Case Study Analysis
+
+
+
+
+ Group Project
+
+
+
+
+ Lab: Data Collection
+
+
+
+
+ Reflection Journal
+
+
+
+
+
+
+ Unit 3: Research Methods
+ 5 items
+
+
+
+
+ Introduction to Research Design
+
+
+
+
+ Qualitative vs Quantitative
+
+
+
+
+ Survey Design Workshop
+
+
+
+
+ Ethics in Research
+
+
+
+
+ Quiz: Research Methods
+
+
+
+
+
+
End of Content
+
+ ` : nothing;
+ }
+
+ #renderSupportingPanel() {
+ return this.hasSupportingPanel ? html`
+
+
+
I'm in the supporting slot of the d2l-page component!
+
+
+
+
+
+
+ Has start date
+ Has end date
+
+
+
+
+
+
+
+
+
+
+ Include in final grade calculation
+
+
+
+
+
+
+
+
+ Allow learners to retract submissions
+ Notify instructor on submission
+
+
+
+
+
+
End of Content
+
+ ` : nothing;
+ }
+}
+
+customElements.define('d2l-page-demo', PageDemo);
diff --git a/components/page/demo/page.html b/components/page/demo/page.html
new file mode 100644
index 00000000000..f31638807b9
--- /dev/null
+++ b/components/page/demo/page.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/page/demo/temp-nav-styles.js b/components/page/demo/temp-nav-styles.js
new file mode 100644
index 00000000000..98d89148ef5
--- /dev/null
+++ b/components/page/demo/temp-nav-styles.js
@@ -0,0 +1,217 @@
+/**
+ * Temporary navigation demo styles
+ * These can be removed once we have official navigation components to use
+ */
+
+import { css } from 'lit';
+
+export const navStyles = css`
+
+ /* Shared Styles */
+ .nav-shadow {
+ background-color: rgba(0, 0, 0, 0.02);
+ bottom: -4px;
+ display: block;
+ height: 4px;
+ pointer-events: none;
+ position: absolute;
+ width: 100%;
+ z-index: 1;
+ }
+ .nav-band {
+ background: linear-gradient(180deg, var(--d2l-color-celestine) 1.5rem, #ffffff 0%);
+ min-height: 4px;
+ }
+ .nav-icon-btn {
+ align-items: center;
+ background: none;
+ border: none;
+ border-radius: 4px;
+ color: var(--d2l-color-ferrite);
+ cursor: pointer;
+ display: inline-flex;
+ font-size: 0.8rem;
+ gap: 4px;
+ justify-content: center;
+ min-height: 42px;
+ min-width: 42px;
+ padding: 6px;
+ }
+ .nav-icon-btn:hover,
+ .nav-icon-btn:focus-visible {
+ background-color: var(--d2l-color-gypsum);
+ color: var(--d2l-color-celestine);
+ }
+
+ /* Full Nav Styles */
+ .full-nav-wrapper {
+ position: relative;
+ }
+ .full-nav-header {
+ align-items: center;
+ display: flex;
+ height: 90px;
+ margin-inline: var(--d2l-page-margin-inline);
+ max-width: var(--d2l-page-header-max-width);
+ padding: 0 30px;
+ }
+ @media (max-width: 767px) {
+ .full-nav-header {
+ height: 72px;
+ }
+ }
+ @media (max-width: 615px) {
+ .full-nav-header {
+ padding: 0 15px;
+ }
+ }
+ .full-nav-header-left {
+ align-items: center;
+ display: flex;
+ flex: 0 1 auto;
+ gap: 12px;
+ height: 100%;
+ }
+ .full-nav-header-spacer {
+ flex: 1 1 auto;
+ min-width: 30px;
+ }
+ @media (max-width: 615px) {
+ .full-nav-header-spacer {
+ min-width: 15px;
+ }
+ }
+ .full-nav-header-right {
+ align-items: center;
+ display: flex;
+ flex: 0 0 auto;
+ gap: 6px;
+ height: 100%;
+ }
+ .full-nav-logo {
+ background-color: var(--d2l-color-celestine);
+ border-radius: 4px;
+ color: white;
+ font-size: 0.8rem;
+ font-weight: 700;
+ padding: 8px 14px;
+ }
+ .full-nav-separator {
+ background-color: var(--d2l-color-mica);
+ height: 26px;
+ margin: 0 6px;
+ width: 1px;
+ }
+ .full-nav-footer {
+ border-bottom: 1px solid rgba(124, 134, 149, 0.18);
+ border-top: 1px solid rgba(124, 134, 149, 0.18);
+ }
+ .full-nav-footer-inner {
+ align-items: center;
+ display: flex;
+ gap: 4px;
+ margin-inline: var(--d2l-page-margin-inline);
+ max-width: var(--d2l-page-header-max-width);
+ padding: 0 30px;
+ }
+ @media (max-width: 615px) {
+ .full-nav-footer-inner {
+ padding: 0 15px;
+ }
+ }
+ .full-nav-footer-link {
+ border-bottom: 4px solid transparent;
+ color: var(--d2l-color-ferrite);
+ display: inline-block;
+ font-size: 0.7rem;
+ padding: 8px 12px;
+ text-decoration: none;
+ }
+ .full-nav-footer-link:hover,
+ .full-nav-footer-link:focus-visible {
+ border-bottom-color: var(--d2l-color-celestine);
+ color: var(--d2l-color-celestine);
+ }
+
+ /* Immersive Nav Styles */
+ .immersive-wrapper {
+ background-color: white;
+ border-bottom: 1px solid var(--d2l-color-mica);
+ position: relative;
+ }
+ .immersive-container {
+ align-items: center;
+ display: flex;
+ height: 3.1rem;
+ justify-content: space-between;
+ margin-inline: var(--d2l-page-margin-inline);
+ max-width: var(--d2l-page-header-max-width);
+ overflow: hidden;
+ padding: 0 30px;
+ }
+ @media (max-width: 929px) {
+ .immersive-container {
+ padding: 0 24px;
+ }
+ }
+ @media (max-width: 767px) {
+ .immersive-container {
+ padding: 0 18px;
+ }
+ }
+ @media (max-width: 615px) {
+ .immersive-container {
+ height: 2.8rem;
+ }
+ }
+ .immersive-left {
+ align-items: center;
+ color: var(--d2l-color-tungsten);
+ display: flex;
+ flex: 0 0 auto;
+ font-size: 0.8rem;
+ letter-spacing: 0.2px;
+ }
+ .immersive-back-link {
+ align-items: center;
+ color: var(--d2l-color-tungsten);
+ display: inline-flex;
+ gap: 4px;
+ text-decoration: none;
+ }
+ .immersive-back-link:hover,
+ .immersive-back-link:focus-visible {
+ color: var(--d2l-color-celestine);
+ }
+ .immersive-back-icon {
+ font-size: 1rem;
+ }
+ .immersive-middle {
+ border-inline-end: 1px solid var(--d2l-color-gypsum);
+ border-inline-start: 1px solid var(--d2l-color-gypsum);
+ flex: 0 1 auto;
+ font-size: 0.8rem;
+ margin: 0 24px;
+ min-width: 0;
+ overflow: hidden;
+ padding: 0 24px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width: 100%;
+ }
+ @media (max-width: 615px) {
+ .immersive-middle {
+ margin: 0 18px;
+ padding: 0 18px;
+ }
+ }
+ .immersive-right {
+ align-items: center;
+ display: flex;
+ flex: 0 0 auto;
+ gap: 8px;
+ }
+ .immersive-right .nav-icon-btn {
+ font-size: 0.7rem;
+ }
+`;
diff --git a/components/page/page.js b/components/page/page.js
new file mode 100644
index 00000000000..ee9fb5da102
--- /dev/null
+++ b/components/page/page.js
@@ -0,0 +1,230 @@
+import '../button/floating-buttons.js';
+import { css, html, LitElement, nothing } from 'lit';
+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
+ * @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)
+ * @slot supporting - The supporting content of the page (expecting d2l-page-supporting)
+ * @slot footer - The footer content of the page (expecting d2l-page-footer)
+ */
+class Page extends LocalizeCoreElement(LitElement) {
+
+ static properties = {
+ /**
+ * Width type of the page and its underlying pieces
+ * @type {'normal'|'wide'|'fullscreen'}
+ */
+ widthType: { type: String, attribute: 'width-type' },
+ _headerIsSticky: { state: true },
+ _slotVisibility: { state: true }
+ };
+
+ static styles = css`
+ :host {
+ --d2l-page-header-max-width: 1230px;
+ --d2l-page-content-max-width: 1230px;
+ --d2l-page-footer-max-width: 1230px;
+ --d2l-page-margin-inline: auto;
+ }
+
+ :host([width-type="wide"]) {
+ --d2l-page-header-max-width: 1440px;
+ --d2l-page-content-max-width: 1440px;
+ --d2l-page-footer-max-width: 1440px;
+ }
+
+ :host([width-type="fullscreen"]) {
+ --d2l-page-header-max-width: 100%;
+ --d2l-page-content-max-width: 100%;
+ --d2l-page-footer-max-width: 100%;
+ }
+
+ .header {
+ background-color: white;
+ z-index: 2; /* To be over divider and main contents */
+ }
+ .page.header-sticky .header {
+ position: sticky;
+ top: 0;
+ }
+
+ .content {
+ display: flex;
+ margin-inline: var(--d2l-page-margin-inline, 0);
+ max-width: var(--d2l-page-content-max-width, 100%);
+ padding-bottom: var(--d2l-page-footer-height, 0); /* Reserve space for fixed footer */
+ }
+
+ main {
+ flex: 1;
+ min-width: 400px; /* TBD */
+ overflow: clip;
+ position: relative;
+ z-index: 0;
+ }
+ .page.header-sticky main {
+ --d2l-sticky-offset: var(--d2l-page-header-height, 0px);
+ }
+
+ .side-nav-panel,
+ .supporting-panel {
+ height: calc(100vh - var(--d2l-page-footer-height, 0));
+ overflow: clip auto;
+ position: sticky;
+ top: 0;
+ z-index: 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);
+ }
+
+ .divider {
+ background-color: var(--d2l-color-gypsum);
+ flex: none;
+ width: 4px;
+ z-index: 1;
+ }
+
+ .footer:not([hidden]),
+ .floating-buttons-container {
+ display: inline;
+ }
+ .fixed-footer {
+ background-color: white;
+ box-shadow: 0 -2px 4px rgba(32, 33, 34, 0.2); /* ferrite */
+ inset: auto 0 0;
+ padding: 0.75rem 0;
+ position: fixed;
+ z-index: 1; /* To be over divider */
+ }
+ .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%);
+ }
+ `;
+
+ constructor() {
+ super();
+
+ this.widthType = 'normal';
+ this._headerIsSticky = false;
+ this._slotVisibility = {};
+ this.#resizeObserver = new ResizeObserver(entries => {
+ for (const entry of entries) {
+ if (entry.target.classList.contains('header')) {
+ const height = entry.target.offsetHeight;
+ 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;
+ this.style.setProperty('--d2l-page-footer-height', `${height}px`);
+ }
+ }
+ });
+ }
+
+ 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);
+ }
+
+ render() {
+ const pageClasses = {
+ 'page': true,
+ '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}
+
+ ${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`
+
+
+
+
+
+ `;
+ }
+
+}
+
+customElements.define('d2l-page', Page);
diff --git a/components/page/test/page.axe.js b/components/page/test/page.axe.js
new file mode 100644
index 00000000000..0cb23386687
--- /dev/null
+++ b/components/page/test/page.axe.js
@@ -0,0 +1,40 @@
+import '../page.js';
+import { expect, fixture, html } from '@brightspace-ui/testing';
+
+describe('page', () => {
+
+ it('single panel', async() => {
+ const elem = await fixture(html`
+
+ Header
+ Content
+ Footer
+
+ `);
+ await expect(elem).to.be.accessible();
+ });
+
+ it('with side-nav panel', async() => {
+ const elem = await fixture(html`
+
+ Header
+ Content
+ Side Nav
+ Footer
+
+ `);
+ await expect(elem).to.be.accessible();
+ });
+
+ it('with supporting panel', async() => {
+ const elem = await fixture(html`
+
+ Header
+ Content
+ Supporting
+ Footer
+
+ `);
+ await expect(elem).to.be.accessible();
+ });
+});
diff --git a/components/page/test/page.test.js b/components/page/test/page.test.js
new file mode 100644
index 00000000000..2b8bce6f0e7
--- /dev/null
+++ b/components/page/test/page.test.js
@@ -0,0 +1,10 @@
+import '../page.js';
+import { runConstructor } from '@brightspace-ui/testing';
+
+describe('page', () => {
+
+ it('should construct', () => {
+ runConstructor('d2l-page');
+ });
+
+});
diff --git a/components/selection/selection-controls.js b/components/selection/selection-controls.js
index d164e68ea46..c10344e3c6e 100644
--- a/components/selection/selection-controls.js
+++ b/components/selection/selection-controls.js
@@ -50,7 +50,7 @@ export class SelectionControls extends PageableSubscriberMixin(SelectionObserver
:host {
display: block;
position: sticky;
- top: 0;
+ top: var(--d2l-sticky-offset, 0px);
}
:host([no-sticky]) {
position: static;
diff --git a/components/table/table-wrapper.js b/components/table/table-wrapper.js
index 780249f5348..b59c1ab2c36 100644
--- a/components/table/table-wrapper.js
+++ b/components/table/table-wrapper.js
@@ -241,7 +241,7 @@ export const tableStyles = css`
d2l-table-wrapper[sticky-headers][sticky-headers-scroll-wrapper] .d2l-table > thead {
display: block;
position: sticky;
- top: calc(var(--d2l-table-sticky-top, 0px) + var(--d2l-table-border-radius-sticky-offset, 0px));
+ top: calc(var(--d2l-sticky-offset, 0px) + var(--d2l-table-sticky-top, 0px) + var(--d2l-table-border-radius-sticky-offset, 0px));
z-index: 2;
}
@@ -382,7 +382,7 @@ export class TableWrapper extends PropertyRequiredMixin(PageableMixin(SelectionM
}
.d2l-sticky-headers-backdrop {
position: sticky;
- top: calc(var(--d2l-table-sticky-top, 0px) + var(--d2l-table-border-radius));
+ top: calc(var(--d2l-sticky-offset, 0px) + var(--d2l-table-sticky-top, 0px) + var(--d2l-table-border-radius));
width: 100%;
z-index: 2; /* Must sit under d2l-table sticky-headers but over sticky columns and regular cells */
}
@@ -832,7 +832,7 @@ export class TableWrapper extends PropertyRequiredMixin(PageableMixin(SelectionM
const stickyRows = Array.from(this._table.querySelectorAll(SELECTORS.headers));
stickyRows.forEach(r => {
- const thTop = hasStickyControls ? `${rowTop}px` : `calc(${rowTop}px + var(--d2l-table-border-radius-sticky-offset, 0px))`;
+ const thTop = hasStickyControls ? `calc(var(--d2l-sticky-offset, 0px) + ${rowTop}px)` : `calc(var(--d2l-sticky-offset, 0px) + ${rowTop}px + var(--d2l-table-border-radius-sticky-offset, 0px))`;
const ths = Array.from(r.querySelectorAll('th,td'));
ths.forEach(th => th.style.top = thTop);
diff --git a/index.html b/index.html
index fdc60242b71..517691d84f9 100644
--- a/index.html
+++ b/index.html
@@ -153,6 +153,7 @@ Components
d2l-more-less
d2l-object-property-list
d2l-offscreen
+ d2l-page
d2l-progress
d2l-overflow-group
d2l-pager-load-more
diff --git a/lang/ar.js b/lang/ar.js
index e76318e0c36..fe4a4c0d3a6 100644
--- a/lang/ar.js
+++ b/lang/ar.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "المزيد",
"components.object-property-list.item-placeholder-text": "عنصر نائب",
"components.overflow-group.moreActions": "مزيد من الإجراءات",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} مادة واحد}
diff --git a/lang/ca.js b/lang/ca.js
index 569d4558a69..7b465df428a 100644
--- a/lang/ca.js
+++ b/lang/ca.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "more",
"components.object-property-list.item-placeholder-text": "Placeholder Item",
"components.overflow-group.moreActions": "More Actions",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} item}
diff --git a/lang/cy.js b/lang/cy.js
index 4a2343d3c01..80f1dedb897 100644
--- a/lang/cy.js
+++ b/lang/cy.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "mwy",
"components.object-property-list.item-placeholder-text": "Eitem Dalfan",
"components.overflow-group.moreActions": "Rhagor o Gamau Gweithredu",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} eitem}
diff --git a/lang/da.js b/lang/da.js
index 5d1b1c5e6df..37c60f9326d 100644
--- a/lang/da.js
+++ b/lang/da.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "flere",
"components.object-property-list.item-placeholder-text": "Pladsholder-element",
"components.overflow-group.moreActions": "Flere handlinger",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} element}
diff --git a/lang/de.js b/lang/de.js
index 89d29dc533b..80e362ab3a0 100644
--- a/lang/de.js
+++ b/lang/de.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "mehr",
"components.object-property-list.item-placeholder-text": "Platzhalterelement",
"components.overflow-group.moreActions": "Weitere Aktionen",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} Element}
diff --git a/lang/en-gb.js b/lang/en-gb.js
index 4830e2e5336..26ca60cb2f9 100644
--- a/lang/en-gb.js
+++ b/lang/en-gb.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "more",
"components.object-property-list.item-placeholder-text": "Placeholder Item",
"components.overflow-group.moreActions": "More Actions",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} item}
diff --git a/lang/en.js b/lang/en.js
index 569d4558a69..7b465df428a 100644
--- a/lang/en.js
+++ b/lang/en.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "more",
"components.object-property-list.item-placeholder-text": "Placeholder Item",
"components.overflow-group.moreActions": "More Actions",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} item}
diff --git a/lang/es-es.js b/lang/es-es.js
index fff3d714103..cfb60447b5a 100644
--- a/lang/es-es.js
+++ b/lang/es-es.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "más",
"components.object-property-list.item-placeholder-text": "Elemento de marcador de posición",
"components.overflow-group.moreActions": "Más acciones",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} elemento}
diff --git a/lang/es.js b/lang/es.js
index 22356ee0d91..8a848f05bb4 100644
--- a/lang/es.js
+++ b/lang/es.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "más",
"components.object-property-list.item-placeholder-text": "Elemento de marcador de posición",
"components.overflow-group.moreActions": "Más acciones",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} elemento}
diff --git a/lang/fr-fr.js b/lang/fr-fr.js
index 6b15ef59e1e..a87745a48b3 100644
--- a/lang/fr-fr.js
+++ b/lang/fr-fr.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "plus",
"components.object-property-list.item-placeholder-text": "Élément d’espace réservé",
"components.overflow-group.moreActions": "Plus d’actions",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} élément}
diff --git a/lang/fr.js b/lang/fr.js
index 0f3cc881a93..f8979e193f2 100644
--- a/lang/fr.js
+++ b/lang/fr.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "plus",
"components.object-property-list.item-placeholder-text": "Élément de paramètre fictif",
"components.overflow-group.moreActions": "Plus d’actions",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} élément}
diff --git a/lang/haw.js b/lang/haw.js
index de2d20a99b9..846b1a0ee09 100644
--- a/lang/haw.js
+++ b/lang/haw.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "hou aku",
"components.object-property-list.item-placeholder-text": "Mea Paʻa Wahi",
"components.overflow-group.moreActions": "Nā Hana Hou",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} mea}
diff --git a/lang/hi.js b/lang/hi.js
index d4a5947e1f7..16303fc1082 100644
--- a/lang/hi.js
+++ b/lang/hi.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "अधिक",
"components.object-property-list.item-placeholder-text": "प्लेसहोल्डर आइटम",
"components.overflow-group.moreActions": "अधिक क्रियाएँ",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} आइटम}
diff --git a/lang/ja.js b/lang/ja.js
index c8195d72ca8..57ee858f7fb 100644
--- a/lang/ja.js
+++ b/lang/ja.js
@@ -147,6 +147,8 @@ export default {
"components.more-less.more": "増やす",
"components.object-property-list.item-placeholder-text": "プレースホルダの項目",
"components.overflow-group.moreActions": "その他のアクション",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
other {{countFormatted} 個の項目}
diff --git a/lang/ko.js b/lang/ko.js
index f4bcf45798a..a32f2ebbc33 100644
--- a/lang/ko.js
+++ b/lang/ko.js
@@ -147,6 +147,8 @@ export default {
"components.more-less.more": "더 보기",
"components.object-property-list.item-placeholder-text": "자리표시자 항목",
"components.overflow-group.moreActions": "추가 작업",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
other {해당 항목 수 {countFormatted}개}
diff --git a/lang/mi.js b/lang/mi.js
index 322c2cb3d8a..7dbfbef6103 100644
--- a/lang/mi.js
+++ b/lang/mi.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "ētahi atu",
"components.object-property-list.item-placeholder-text": "Tūemi Puriwāhi",
"components.overflow-group.moreActions": "Ētahi atu Hohenga",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} Tūemi}
diff --git a/lang/nl.js b/lang/nl.js
index 3d3e9401311..35ca1a3fe25 100644
--- a/lang/nl.js
+++ b/lang/nl.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "meer",
"components.object-property-list.item-placeholder-text": "Item tijdelijke aanduiding",
"components.overflow-group.moreActions": "Meer acties",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} item}
diff --git a/lang/pt.js b/lang/pt.js
index 9da99f9b552..26ede4348b6 100644
--- a/lang/pt.js
+++ b/lang/pt.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "mais",
"components.object-property-list.item-placeholder-text": "Item de espaço reservado",
"components.overflow-group.moreActions": "Mais ações",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} item}
diff --git a/lang/sv.js b/lang/sv.js
index 7d107505040..1e9f65dd9b1 100644
--- a/lang/sv.js
+++ b/lang/sv.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "mer",
"components.object-property-list.item-placeholder-text": "Platshållarobjekt",
"components.overflow-group.moreActions": "Fler åtgärder",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} objekt}
diff --git a/lang/th.js b/lang/th.js
index 69e39a595ce..899f502c5a6 100644
--- a/lang/th.js
+++ b/lang/th.js
@@ -148,6 +148,8 @@ export default {
"components.more-less.more": "เพิ่มเติม",
"components.object-property-list.item-placeholder-text": "รายการตัวแทน",
"components.overflow-group.moreActions": "การดำเนินการเพิ่มเติม",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
other {{countFormatted} รายการ}
diff --git a/lang/tr.js b/lang/tr.js
index 9dfe17ae411..e090ada5e4d 100644
--- a/lang/tr.js
+++ b/lang/tr.js
@@ -152,6 +152,8 @@ export default {
"components.more-less.more": "daha fazla",
"components.object-property-list.item-placeholder-text": "Yer Tutucu Öğesi",
"components.overflow-group.moreActions": "Daha Fazla Eylem",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
one {{countFormatted} öğe}
diff --git a/lang/vi.js b/lang/vi.js
index 4d038ec17f0..587270ad2de 100644
--- a/lang/vi.js
+++ b/lang/vi.js
@@ -146,6 +146,8 @@ export default {
"components.more-less.more": "thêm",
"components.object-property-list.item-placeholder-text": "Mục giữ chỗ",
"components.overflow-group.moreActions": "Thêm các Tác vụ",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
other {{countFormatted} mục}
diff --git a/lang/zh-cn.js b/lang/zh-cn.js
index 3658b14a471..0129e96669a 100644
--- a/lang/zh-cn.js
+++ b/lang/zh-cn.js
@@ -147,6 +147,8 @@ export default {
"components.more-less.more": "更多",
"components.object-property-list.item-placeholder-text": "占位符项目",
"components.overflow-group.moreActions": "更多操作",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
other {{countFormatted} 项}
diff --git a/lang/zh-tw.js b/lang/zh-tw.js
index ac27fda0215..54d8b207006 100644
--- a/lang/zh-tw.js
+++ b/lang/zh-tw.js
@@ -148,6 +148,8 @@ export default {
"components.more-less.more": "較多",
"components.object-property-list.item-placeholder-text": "預留位置項目",
"components.overflow-group.moreActions": "其他動作",
+ "components.page.header-nav-label": "Main",
+ "components.page.side-nav-label": "Side",
"components.pageable.info":
`{count, plural,
other {{countFormatted} 個項目}