Skip to content

AppSideNav: fix scrolling issue and being able to focus hidden links issue #2869

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: main
Choose a base branch
from

Conversation

shleewhite
Copy link
Contributor

@shleewhite shleewhite commented May 12, 2025

📌 Summary

If merged, this PR would do some cleanup of the AppSideNav component

  • only lock scrolling if the user is on mobile (when on desktop, scrolling shouldnt be prevented because the user can still interact with the rest of the page)
  • reset ability to scroll when close the AppSideNav with the escape key
  • prevent the user from tabbing to the body of the AppSideNav when it is minimized

There are also updates to tests to check for the correct behavior (+ 1 bonus fix for a Modal test):

  • add test to check the user cannot tab to the content when the AppSideNav is minimized
  • add checks to existing tests that overflow: hidden is set appropriately
  • add test that when a user resizes from mobile -> desktop, ability to scroll is returned
  • re-enable test for Modal because I figured out how to target the body element in tests

🧪 Tested in Atlas:

🔗 External links

Jira ticket: HDS-4827
Jira ticket: HDS-4858


👀 Component checklist

💬 Please consider using conventional comments when reviewing this PR.

Copy link

vercel bot commented May 12, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
hds-showcase ✅ Ready (Inspect) Visit Preview May 15, 2025 0:36am
hds-website ✅ Ready (Inspect) Visit Preview May 15, 2025 0:36am

@@ -27,7 +27,7 @@
/>
{{/if}}

<div class="hds-app-side-nav__wrapper-body">
<div class="hds-app-side-nav__wrapper-body hds-app-side-nav-hide-when-minimized">
Copy link
Contributor Author

@shleewhite shleewhite May 12, 2025

Choose a reason for hiding this comment

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

Note: I had to add this class because this is what the component is checking to decide what to add inert to. Previously, this was only getting added to the target for the portal (link).

Now it gets added when the consumer isn't using Portals too.

Copy link
Contributor

Choose a reason for hiding this comment

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

This seems a little confusing since the 2nd class is always present. Could you just use the existing hds-app-side-nav__wrapper-body class name instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

(Also BEM format would be "hds-app-side-nav--hide-when-minimized")

Copy link
Contributor Author

@shleewhite shleewhite May 13, 2025

Choose a reason for hiding this comment

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

Yeah.. I think this comes from SideNav. I can update it to be BEM format.

When the AppSideNav is inserted, it looks for elements with this class name to decide where to put inert.

I didn't want to change how it works for Portals, which is why I kept using the existing class.

The class is also used a lot in tests.

Copy link
Contributor

@KristinLBradley KristinLBradley May 13, 2025

Choose a reason for hiding this comment

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

It still seems a bit confusing to add an extra class that's always there instead of just using the existing class. Would be curious to know what other engineers think though. @hashicorp/hds-engineering @didoo

Maybe you could at least add a comment to explain why the extra class is there?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'll have a look tomorrow

@shleewhite shleewhite marked this pull request as ready for review May 13, 2025 11:44
@shleewhite shleewhite requested a review from a team as a code owner May 13, 2025 11:44
@shleewhite shleewhite changed the title AppSideNav: fix scrolling issue, AppSideNav: fix scrolling issue and being able to focus hidden links issue May 13, 2025
@shleewhite shleewhite added the release-candidate Publishes release candidates to npm label May 13, 2025
Copy link
Contributor

📦 RC Packages Published

Latest commit: 3dd5918

Published 1 packages

@hashicorp/[email protected]

yarn up -C @hashicorp/design-system-components@rc

@shleewhite shleewhite removed the release-candidate Publishes release candidates to npm label May 13, 2025

More specifically:

- `minimized → maximized` transition: the content appears with a fade-in effect, when the width animation is already completed (the width is maximized)
- `maximized → minimized` transition: the content disappears at once with no transition, before the width animation starts

Another option is to use the **`isMinimized` parameter**, which is useful in those cases where the content is so custom/specialized that it can’t just be faded in/out but needs to have a different kind of transition (eg. remain visible but change layout or respond to the width of the container). This value is passed down by the `<:header/body/footer>` named blocks as parameters, and can be used to build custom logic on the consumers’ side.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The component doesn't return named blocks or the isMinimized state to the consumer, so this is not true.

@@ -206,15 +206,13 @@ The App Side Nav component **automatically**:

Any other content in the App Side Nav needs to be **explicitly handled** by the consumers (in this way they have full control of the content they add, and they can customize the transition as they want/need).

One possible way to do it is to use the **`hds-app-side-nav-hide-when-minimized` class**. This is a special class that can be applied to a DOM element so that it **automatically** fades in/out when the App Side Nav changes its “minimization” state.
One possible way to do it is to use the **`hds-app-side-nav--hide-when-minimized` class**. This is a special class that can be applied to a DOM element so that it **automatically** fades in/out when the App Side Nav changes its “minimization” state.
Copy link
Contributor

Choose a reason for hiding this comment

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

But this class is always present now, isn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes... Ok I have looked into it more and I believe we can safely simplify this logic. I will push up a refactor shortly.

Copy link
Contributor

@KristinLBradley KristinLBradley left a comment

Choose a reason for hiding this comment

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

Looking good overall but I had a few questions still (see comments)

@shleewhite shleewhite added this to the [email protected] milestone May 14, 2025
@shleewhite shleewhite requested a review from a team May 14, 2025 16:39
@shleewhite shleewhite force-pushed the hds-4827/app-side-nav-overflow-bug branch from 1a2188c to ffb7563 Compare May 14, 2025 21:14
Copy link
Contributor

@didoo didoo left a comment

Choose a reason for hiding this comment

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

@shleewhite left a bunch of comments/questions, but overall the PR is so much better and clean with your latest changes! 🙌

@@ -27,7 +27,7 @@
/>
{{/if}}

<div class="hds-app-side-nav__wrapper-body">
<div class="hds-app-side-nav__wrapper-body" {{did-insert this.didInsertNavWrapperBody}}>
Copy link
Contributor

Choose a reason for hiding this comment

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

[suggestion] my understanding is that we're moving away from ember-render-modifiers in favour of custom modifiers (see Dylan's PR https://github.com/hashicorp/design-system/pull/2856/files for example); maybe something we can do here as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

or is it because you didn't want to have to do also the migration of the did-insert applied to the root element of this component? in which case makes sense, and I have no objection to keep it "old school" for now :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hahah I can update to use the new modifier pattern! Might as well since I'm touching the file.

@@ -213,6 +216,11 @@ export default class HdsAppSideNav extends Component<HdsAppSideNavSignature> {

this.synchronizeInert();

if (this._isDesktop) {
// make sure scrolling is enabled if the user resizes the window from mobile to desktop
Copy link
Contributor

Choose a reason for hiding this comment

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

[praise] 👏 👏 👏

element.removeAttribute('inert');
}
});
if (this._isMinimized) {
Copy link
Contributor

Choose a reason for hiding this comment

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

[question/suggestion?] MDN says that "The inert [...] attribute is a Boolean attribute". Does it mean this could be converted to this._navWrapperBody?.setAttribute('inert', this._isMinimized);? (in case, test that it works, I didn't)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tested in chrome and it treated inert="false" as inert still, will leave it as is for now.

@@ -249,7 +248,7 @@ module('Integration | Component | hds/modal/index', function (hooks) {
await click('#test-interactive');
assert.true(this.showModal);
await click('button.hds-modal__dismiss');
assert.dom('body', 'body').isFocused();
assert.dom('body', document).isFocused();
Copy link
Contributor

Choose a reason for hiding this comment

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

😮

await render(hbs`<Hds::AppSideNav
id='test-app-side-nav'
@isCollapsible={{true}}
@onDesktopViewportChange={{this.onDesktopViewportChange}}
Copy link
Contributor

@didoo didoo May 14, 2025

Choose a reason for hiding this comment

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

[question] could it be that this.onDesktopViewportChange is undefined for this test, and you have to add its definition like in the other tests above? or I'm missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I can remove it. It is unneeded for this test.

@isCollapsible={{true}}
@onDesktopViewportChange={{this.onDesktopViewportChange}}
>
<span id='test-app-side-nav-body' />
Copy link
Contributor

Choose a reason for hiding this comment

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

[question] what is this span#test-app-side-nav-body used for? 🤔

(I see other places where is not used in the tests assertions/declarations; maybe there's a reason for that? otherwise maybe we can remove them?)

Copy link
Contributor Author

@shleewhite shleewhite May 15, 2025

Choose a reason for hiding this comment

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

I can most of remove them... They were initially used in tests along with <span id='hds-app-side-nav-hide-when-minimized' /> to show that inert was only added to the correct elements.

Im leaving ones where we test that the content in the yielded block is rendered correctly and a test that makes sure that the content in the block is hidden when the nav is minimized.

assert.dom('body', document).doesNotHaveStyle('overflow');
});

test('when collapsed, the content in the AppSideNav is not focusable', async function (assert) {
Copy link
Contributor

Choose a reason for hiding this comment

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

[question] I don't understand this test: how do you know if the content in the AppSideNav is focusable or not, if there's nothing focusable (like a button) inside it? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh my gosh, that is right. I will update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs-website Content updates to the documentation website packages/components showcase
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants