Skip to content

GAUD-9165 - Implement the d2l-page skeleton#6904

Merged
svanherk merged 19 commits into
mainfrom
GAUD-9165-Implement-the-d2l-page-skeleton
May 6, 2026
Merged

GAUD-9165 - Implement the d2l-page skeleton#6904
svanherk merged 19 commits into
mainfrom
GAUD-9165-Implement-the-d2l-page-skeleton

Conversation

@svanherk
Copy link
Copy Markdown
Contributor

@svanherk svanherk commented May 1, 2026

This adds d2l-page with the discussed slots. It uses the full body scroll demonstrated in #6875. The demo page is bare bones and will be fleshed out in the next PR.

Still tweaking a bit, experimenting with what we want vdiffs to look like, etc. but this should be good to go. Will add notes inline. I still want to give this one more go-through but I've been looking at it too long - will check Monday with fresh eyes.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Thanks for the PR! 🎉

We've deployed an automatic preview for this PR - you can see your changes here:

URL https://live.d2l.dev/prs/BrightspaceUI/core/pr-6904/

Note

The build needs to finish before your changes are deployed.
Changes to the PR will automatically update the instance.

class PageDemo extends LitElement {

static properties = {
demoMode: { type: Boolean, attribute: 'demo-mode' },
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was thinking I could also use this component for vdiff, so this is a bit heavy (being able to set controls with the switches and by a consumer, and hide the switches to look better in the vdiff. We can simplify this if that doesn't end up happening.


constructor() {
super();
this._allowThreePanels = false; // Temp for dev/testing
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really handy for dev, but will go away if we aren't supporting it. Likewise, we need to decide what happens if people try to use both - throw an error? Simply hide the supporting panel? Something else?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made a story to come back to this question: https://desire2learn.atlassian.net/browse/GAUD-9885


#renderSideNavPanel() {
return this.hasSideNavPanel ? html`
<div style="background-color: lavender; height: 1000px;" slot="side-nav">
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Colours here are temporary until the layout components are added and the demo is more fleshed out. Just thought it looked nice.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole file will go away - once the navigation pieces are in core, we can use them directly in #renderFullNav and #renderImmersiveNav and just add a few of these extra styles in page-component directly.

Disclaimer: I told Copilot to create these navs and then I updated them to use the CSS properties that the official nav pieces will use - didn't review the other temporary styles very closely.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still planning the tests and vdiffs, in coordination with the layout stuff

Comment thread components/page/page.js
--d2l-page-header-max-width: 1230px;
--d2l-page-content-max-width: 1230px;
--d2l-page-footer-max-width: 1230px;
--d2l-page-margin-inline: auto;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can maybe go away if we're not doing fancy nav stuff at different widths - we should revisit with the official nav stuff.

Comment thread components/page/page.js Outdated

main {
flex: 1;
min-width: 400px; /* TBD */
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open design question on whether this should be 400px or 600px, plus this will likely all adjust as panels start opening/closing/resizing. Kinda just a placeholder for now.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may want this min-width on the <div class="page"> actually. Otherwise if this panel requires horizontal scrolling, the nav & footer won't fill the full width:

Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting! I'll move it there for now, and then I think a lot of this logic is gonna have to be baked into the divider stuff anyways.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, just made this a bit more resilient and made the nav a bit more wrappy. I still think it'll eventually change, but it's the main panel that design specifically doesn't want sized smaller than 400px by the side panels. But then of course, if the page is smaller than that it'll need to adapt

Comment thread components/page/page.js
Comment on lines +199 to +200
//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
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open question on how we want to do this. I figured we'd just look for the actual component name so consumers can't easily set something and work around our choice (that full nav scrolls and immersive nav sticks). But maybe we want something more generic/flexible, and we have immersive-nav sprout a data attribute that this can look for?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My initial thought for solving this was to have the header dispatch an event with information in the detail that indicates what it would prefer. The page could also add stuff to that event detail if we needed cross-component communication.

But this also works for the time being!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I feel like you'll naturally revisit this with immersive-nav and we can test out what works best

Comment thread components/page/page.js
Comment on lines +203 to +208
#handleSlotVisibilityChange(e) {
const key = e.target.name;
const nodes = e.target.assignedNodes();
this._slotVisibility = { ...this._slotVisibility, [key]: nodes.length !== 0 };
this.requestUpdate();
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this when I was looking for like 8 slots and hiding/showing them accordingly, but now there's only three (footer, side-nav, supporting) so this might be overkill. But also, may became relevant in the d2l-page-main components (header, breadcrumbs, etc.) 🤷‍♀️ - thoughts?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah at first I was going to add a comment about how just having a few state properties would be easier to understand... but this is pretty elegant in terms of only needing a single slot change handler so I'm on the fence.

Comment thread components/page/page.js
}

#renderFloatingButtons(footerContents) {
// Floating buttons needs to be wrapped as it spawns a sibling element that should be cleaned up as one by Lit
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might not be an actual problem, because people shouldn't be changing their number of panels and footer type on the fly in their page like the demo page is. But it was infuriating and the lit-html error it caused was incredibly vague, and I'd like to handle it. May have also come up when handling the mobile view switching.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Floating buttons are the (second) worst.

@svanherk
Copy link
Copy Markdown
Contributor Author

svanherk commented May 1, 2026

Looks like collapsible-panel has a z-index of 11, and is covering the sticky nav.

primary-secondary set the header to a z-index of 14, but was hoping to avoid that. I'll play around.

image

@svanherk
Copy link
Copy Markdown
Contributor Author

svanherk commented May 4, 2026

Looks like collapsible-panel has a z-index of 11, and is covering the sticky nav.

primary-secondary set the header to a z-index of 14, but was hoping to avoid that. I'll play around.

Ok, I have a simple z-index setup and stacking contexts to avoid this. The collapsible-panel sticks behind the immersive navbar, but I'll handle that in a separate PR.

@svanherk svanherk marked this pull request as ready for review May 4, 2026 18:54
@svanherk svanherk requested a review from a team as a code owner May 4, 2026 18:54
Comment thread components/page/page.js Outdated
main {
flex: 1;
min-width: 400px; /* TBD */
overflow: clip;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not actually sure what overflow behaviour we want - clip so there's no way for the panels to overflow onto each other, and people need to make sure their content is responsive? Scroll? Default to one of these as the host style of the d2l-page-main, d2l-page-side-nav, etc. but have it overridable?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also not sure. I think we'd want auto instead of scroll since that puts the browser in control of whether scrollbars are visible.

My vote would be to leave it unset for main but clip auto for the nav/supporting.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will make things overflow for now, until we add the padding, because a lot of our components expand outside their bounds for hover styles

Comment thread components/page/demo/page-component.js Outdated
#handleAllowThreePanelsChange(e) {
this._allowThreePanels = e.target.on;
if (!this._allowThreePanels && this.hasSideNavPanel && this.hasSupportingPanel) {
this.shadowRoot.querySelector('#switch-supporting-panel').on = false;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This violates render() being a function of the properties. Setting this._allowThreePanels should trigger a re-render anyway -- can the on boolean just be calculated and set from there?

Comment thread components/page/demo/page-component.js Outdated

if (this._allowThreePanels) return;
if (e.target.on && key === 'hasSideNavPanel' && this.hasSupportingPanel) {
this.shadowRoot.querySelector('#switch-supporting-panel').on = false;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment here (and just below).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair, I think I did it here to be able to easily mass delete it at some point, but I can try moving it right into render

<div class="full-nav-header-right">
<button class="nav-icon-btn" title="Alerts">🔔</button>
<button class="nav-icon-btn" title="Settings">⚙️</button>
<button class="nav-icon-btn" title="Profile">👤</button>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are lovely! We should use them in the real nav. 😆

Comment thread components/page/demo/page-component.js
Comment thread components/page/demo/page-component.js Outdated

#renderMainPanel() {
return html`
<div style="height: 1000px;">
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the height got removed in a follow-up PR, but is this <div> even needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was basically using it to test scroll and as a placeholder for the d2l-page-main, although then I added the end content piece so it's not perfect. It will be gone by the end of the PR chain 😅

Comment thread components/page/page.js
Comment thread components/page/page.js Outdated
const footer = html`
<div class="${classMap(footerContainerClasses)}" ?hidden="${!this._slotVisibility['footer']}">
${fixedFooter ? footerContents : this.#renderFloatingButtons(footerContents)}
</div>`;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be tempted to put all these inside #renderFooter(), #renderSupporting(), etc. as they're only going to get more complex from here. But that can also wait!

Comment thread components/page/page.js
Comment on lines +199 to +200
//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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My initial thought for solving this was to have the header dispatch an event with information in the detail that indicates what it would prefer. The page could also add stuff to that event detail if we needed cross-component communication.

But this also works for the time being!

Comment thread components/page/page.js
Comment on lines +203 to +208
#handleSlotVisibilityChange(e) {
const key = e.target.name;
const nodes = e.target.assignedNodes();
this._slotVisibility = { ...this._slotVisibility, [key]: nodes.length !== 0 };
this.requestUpdate();
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah at first I was going to add a comment about how just having a few state properties would be easier to understand... but this is pretty elegant in terms of only needing a single slot change handler so I'm on the fence.

Comment thread components/page/page.js
}

#renderFloatingButtons(footerContents) {
// Floating buttons needs to be wrapped as it spawns a sibling element that should be cleaned up as one by Lit
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Floating buttons are the (second) worst.

Copy link
Copy Markdown
Member

@dlockhart dlockhart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obviously lots going to change, but I think this is in good enough shape to merge if you want to!

Comment thread components/page/page.js
.page.header-sticky .header {
position: sticky;
top: 0;
z-index: 15; /* To be over sticky content of our core components */
Copy link
Copy Markdown
Contributor Author

@svanherk svanherk May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to be 15, and the below doesn't need to be 10 - this is in the spirit of saving some room. I made a story about lowering collapsible-panel - if that is done, I think this could get down to like 7 or 8. But maybe we want a chart to keep track somewhere? It's currently something like:

  • Table sticky heads = 4
  • Scroll-wrapper = 5
  • Table and List controls = 6
  • Footer = 10 (Collapsible panels won't be sticky over the footer, unless the page is like 50px high)
  • Collapsible-panel = 11 (Probably can be much lower now)
  • Header = 15 (Can be lower)
  • Floating buttons, popover stuff, dialog, toast alerts = 997+

(Probably not perfect, going off memory of a few days ago)

@svanherk
Copy link
Copy Markdown
Contributor Author

svanherk commented May 6, 2026

Obviously lots going to change, but I think this is in good enough shape to merge if you want to!

Yeah I think it's all very adjustable as we flesh out more pieces, but getting it in would be very helpful for the PR chain!

@svanherk svanherk merged commit c6810a5 into main May 6, 2026
9 checks passed
@svanherk svanherk deleted the GAUD-9165-Implement-the-d2l-page-skeleton branch May 6, 2026 17:25
@d2l-github-release-tokens
Copy link
Copy Markdown

🎉 This PR is included in version 3.241.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants