diff --git a/components/page/demo/page-component.js b/components/page/demo/page-component.js index 0fd3c8907a9..c6c9d7e4428 100644 --- a/components/page/demo/page-component.js +++ b/components/page/demo/page-component.js @@ -1,8 +1,14 @@ +import '../../button/button-icon.js'; import '../../button/button-subtle.js'; +import '../../button/button.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 '../../filter/filter.js'; +import '../../filter/filter-dimension-set.js'; +import '../../filter/filter-dimension-set-value.js'; import '../../icons/icon.js'; import '../../inputs/input-checkbox.js'; import '../../inputs/input-date.js'; @@ -14,8 +20,13 @@ import '../../list/list-item.js'; import '../../list/list-item-content.js'; import '../../list/list-item-nav.js'; import '../../selection/selection-action.js'; +import '../../switch/switch-visibility.js'; +import '../../switch/switch.js'; import '../../table/table-controls.js'; import '../page.js'; +import '../page-main.js'; +import '../page-side-nav.js'; +import '../page-supporting.js'; import { css, html, LitElement, nothing } from 'lit'; import { navStyles } from './temp-nav-styles.js'; import { selectStyles } from '../../inputs/input-select-styles.js'; @@ -29,11 +40,15 @@ class PageDemo extends LitElement { static properties = { demoMode: { type: Boolean, attribute: 'demo-mode' }, hasFooter: { type: Boolean, attribute: 'has-footer' }, + hasMainHeader: { type: Boolean, attribute: 'has-main-header' }, + hasSideNavHeader: { type: Boolean, attribute: 'has-side-nav-header' }, hasSideNavPanel: { type: Boolean, attribute: 'has-side-nav-panel' }, + hasSupportingHeader: { type: Boolean, attribute: 'has-supporting-header' }, hasSupportingPanel: { type: Boolean, attribute: 'has-supporting-panel' }, navType: { type: String, attribute: 'nav-type' }, widthType: { type: String, attribute: 'width-type' }, - _allowThreePanels: { state: true } + _allowThreePanels: { state: true }, + _demoDialogOpened: { state: true } }; static styles = [navStyles, selectStyles, tableStyles, css` @@ -42,6 +57,9 @@ class PageDemo extends LitElement { flex-wrap: wrap; gap: 0.75rem; } + d2l-demo-page-settings { + margin-block: 0.6rem; + } `]; constructor() { @@ -49,11 +67,15 @@ class PageDemo extends LitElement { this._allowThreePanels = false; // Temp for dev/testing this.demoMode = false; this.hasFooter = false; + this.hasMainHeader = false; + this.hasSideNavHeader = false; this.hasSideNavPanel = false; + this.hasSupportingHeader = false; this.hasSupportingPanel = false; this.navType = 'full'; /** @type {'normal'|'wide'|'fullscreen'} */ this.widthType = 'normal'; + this._demoDialogOpened = false; } render() { @@ -75,6 +97,14 @@ class PageDemo extends LitElement { } } + #handleDialogClose() { + this._demoDialogOpened = false; + } + + #handleDialogOpen() { + this._demoDialogOpened = true; + } + #handleNavTypeChange(e) { this.navType = e.target.on ? 'immersive' : 'full'; } @@ -107,6 +137,7 @@ class PageDemo extends LitElement { + @@ -114,6 +145,19 @@ class PageDemo extends LitElement { ` : nothing; } + #renderDemoSideNavControls() { + return this.demoMode ? html` + + ` : nothing; + } + + #renderDemoSupportingControls() { + return this.demoMode ? html` + + + ` : nothing; + } + #renderFooter() { return this.hasFooter ? html`
@@ -181,7 +225,20 @@ class PageDemo extends LitElement { #renderMainPanel() { return html` -
+ + ${this.hasMainHeader ? html` + + + + + + + + + + New Assignment + ` : nothing } + ${this.#renderDemoMainControls()}

I'm in the default slot of the d2l-page component!

@@ -333,13 +390,29 @@ class PageDemo extends LitElement {
End of Content
-
+ +
+ + + + Allow late submissions +
+ Create + Cancel +
+ `; } #renderSideNavPanel() { return this.hasSideNavPanel ? html` -
+ + ${this.hasSideNavHeader ? html` + + + + ` : nothing} + ${this.#renderDemoSideNavControls()}

I'm in the side-nav slot of the d2l-page component!

@@ -433,14 +506,19 @@ class PageDemo extends LitElement {
End of Content
-
+ ` : nothing; } #renderSupportingPanel() { return this.hasSupportingPanel ? html` -
- + + ${this.hasSupportingHeader ? html` + + + + ` : nothing} + ${this.#renderDemoSupportingControls()}

I'm in the supporting slot of the d2l-page component!

@@ -489,7 +567,7 @@ class PageDemo extends LitElement {
End of Content
-
+ ` : nothing; } } diff --git a/components/page/page-main.js b/components/page/page-main.js new file mode 100644 index 00000000000..ad82de676ea --- /dev/null +++ b/components/page/page-main.js @@ -0,0 +1,24 @@ +import { css, html, LitElement } from 'lit'; +import { PagePanelMixin, pagePanelStyles } from './page-panel-mixin.js'; + +/** + * Component to be placed in the main default slot of d2l-page, providing a panel with optional header + * @slot - The main content of the main page panel + * @slot header-start - Optional start content of the main page header + * @slot header-end - Optional end content of the main page header + */ +class PageMain extends PagePanelMixin(LitElement) { + + static styles = [pagePanelStyles, css` + .panel-header { + top: var(--d2l-page-header-height, 0); + } + `]; + + render() { + return this._renderPanel(html``); + } + +} + +customElements.define('d2l-page-main', PageMain); diff --git a/components/page/page-panel-mixin.js b/components/page/page-panel-mixin.js new file mode 100644 index 00000000000..5efa230b071 --- /dev/null +++ b/components/page/page-panel-mixin.js @@ -0,0 +1,74 @@ +import '../colors/colors.js'; +import { css, html } from 'lit'; +import { classMap } from 'lit/directives/class-map.js'; + +export const pagePanelStyles = css` + .panel-header:not([hidden]) { + align-items: center; + display: flex; + flex-direction: row; + justify-content: space-between; + } + .panel-header { + background-color: white; + border-bottom: 1px solid var(--d2l-color-mica); + height: 70px; + overflow: hidden; + padding: 0 30px; + position: sticky; + top: 0; + z-index: 14; /* To be over sticky content of our core components, but not over the header shadow */ + } + + .header-start, + .header-end { + display: flex; + gap: 6px; + } + + .panel { + padding: 30px; + } +`; + +export const PagePanelMixin = superclass => class extends superclass { + + static properties = { + _hasHeader: { state: true }, + _slotVisibility: { state: true } + }; + + constructor() { + super(); + this._hasHeader = false; + this._slotVisibility = {}; + } + + _renderPanel(content) { + const panelClasses = { + 'panel': true, + 'header-sticky': this._hasHeader + }; + const header = html` +
+
+
+
`; + + return html` +
+ ${header} +
${content}
+
+ `; + } + + #handleSlotVisibilityChange(e) { + const key = e.target.name; + const nodes = e.target.assignedNodes(); + this._slotVisibility = { ...this._slotVisibility, [key]: nodes.length !== 0 }; + + this._hasHeader = this._slotVisibility['header-start'] || this._slotVisibility['header-end']; + } + +}; diff --git a/components/page/page-side-nav.js b/components/page/page-side-nav.js new file mode 100644 index 00000000000..169745cacb4 --- /dev/null +++ b/components/page/page-side-nav.js @@ -0,0 +1,19 @@ +import { html, LitElement } from 'lit'; +import { PagePanelMixin, pagePanelStyles } from './page-panel-mixin.js'; + +/** + * Component to be placed in the side-nav slot of d2l-page, providing a panel with optional header + * @slot - The main content of the side-nav page panel + * @slot header-start - Optional start content of the side-nav page header + * @slot header-end - Optional end content of the side-nav page header + */ +class PageSideNav extends PagePanelMixin(LitElement) { + + static styles = [pagePanelStyles]; + + render() { + return this._renderPanel(html``); + } +} + +customElements.define('d2l-page-side-nav', PageSideNav); diff --git a/components/page/page-supporting.js b/components/page/page-supporting.js new file mode 100644 index 00000000000..cb71f045119 --- /dev/null +++ b/components/page/page-supporting.js @@ -0,0 +1,19 @@ +import { html, LitElement } from 'lit'; +import { PagePanelMixin, pagePanelStyles } from './page-panel-mixin.js'; + +/** + * Component to be placed in the supporting slot of d2l-page, providing a panel with optional header + * @slot - The main content of the supporting page panel + * @slot header-start - Optional start content of the supporting page header + * @slot header-end - Optional end content of the supporting page header + */ +class PageSupporting extends PagePanelMixin(LitElement) { + + static styles = [pagePanelStyles]; + + render() { + return this._renderPanel(html``); + } +} + +customElements.define('d2l-page-supporting', PageSupporting); diff --git a/components/page/page.js b/components/page/page.js index 9157c2a78af..7c1aa97f0c0 100644 --- a/components/page/page.js +++ b/components/page/page.js @@ -44,10 +44,14 @@ class Page extends LocalizeCoreElement(LitElement) { --d2l-page-footer-max-width: 100%; } + .header { + position: relative; + z-index: 15; /* To be over sticky content of our core components */ + } + .page.header-sticky .header { position: sticky; top: 0; - z-index: 15; /* To be over sticky content of our core components */ } .content { diff --git a/components/page/test/panels.axe.js b/components/page/test/panels.axe.js new file mode 100644 index 00000000000..1a5f96d8bd4 --- /dev/null +++ b/components/page/test/panels.axe.js @@ -0,0 +1,74 @@ +import '../page-main.js'; +import '../page-side-nav.js'; +import '../page-supporting.js'; +import { expect, fixture, html } from '@brightspace-ui/testing'; + +describe('panels', () => { + + describe('page-main', () => { + it('no-header', async() => { + const elem = await fixture(html` + +
Content
+
+ `); + await expect(elem).to.be.accessible(); + }); + + it('with-header', async() => { + const elem = await fixture(html` + +
Start
+
End
+
Content
+
+ `); + await expect(elem).to.be.accessible(); + }); + }); + + describe('page-side-nav', () => { + it('no-header', async() => { + const elem = await fixture(html` + +
Content
+
+ `); + await expect(elem).to.be.accessible(); + }); + + it('with-header', async() => { + const elem = await fixture(html` + +
Start
+
End
+
Content
+
+ `); + await expect(elem).to.be.accessible(); + }); + }); + + describe('page-supporting', () => { + it('no-header', async() => { + const elem = await fixture(html` + +
Content
+
+ `); + await expect(elem).to.be.accessible(); + }); + + it('with-header', async() => { + const elem = await fixture(html` + +
Start
+
End
+
Content
+
+ `); + await expect(elem).to.be.accessible(); + }); + }); + +}); diff --git a/components/page/test/panels.test.js b/components/page/test/panels.test.js new file mode 100644 index 00000000000..040c17e062f --- /dev/null +++ b/components/page/test/panels.test.js @@ -0,0 +1,26 @@ +import '../page-main.js'; +import '../page-side-nav.js'; +import '../page-supporting.js'; +import { runConstructor } from '@brightspace-ui/testing'; + +describe('panels', () => { + + describe('page-main', () => { + it('should construct', () => { + runConstructor('d2l-page-main'); + }); + }); + + describe('page-side-nav', () => { + it('should construct', () => { + runConstructor('d2l-page-side-nav'); + }); + }); + + describe('page-supporting', () => { + it('should construct', () => { + runConstructor('d2l-page-supporting'); + }); + }); + +});