-
Notifications
You must be signed in to change notification settings - Fork 155
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
fix: AppLayout horizontal scroll #2839
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2839 +/- ##
=======================================
Coverage 96.20% 96.21%
=======================================
Files 761 761
Lines 21447 21521 +74
Branches 6940 6968 +28
=======================================
+ Hits 20633 20706 +73
- Misses 806 807 +1
Partials 8 8 ☔ View full report in Codecov by Sentry. |
@@ -154,7 +154,8 @@ describe('Visual refresh toolbar only', () => { | |||
}) | |||
); | |||
|
|||
test('first opened drawer should be closed when active drawers can not be shrunk to accommodate it (1400px)', () => { | |||
test( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized that this test suite didn't run earlier due to an extra wrap with an arrow function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks
@@ -227,6 +223,14 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef<AppLayoutProps.Ref, AppLa | |||
const navigationFocusControl = useFocusControl(navigationOpen); | |||
const splitPanelFocusControl = useSplitPanelFocusControl([splitPanelPreferences, splitPanelOpen]); | |||
|
|||
const onNavigationToggle = useCallback( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this handler below because it relies on navigationFocusControl, which is declared earlier
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use our useStableCallback
util.
There is no promise that onNavigationChange
is a stable callback, so your code does not really do what you expect.
To an extent, the same applies to other useCallback
additions here. Make sure their dependencies are really stable (drawers
array, for example is not)
@@ -77,7 +77,7 @@ describe.each(['classic', 'refresh', 'refresh-toolbar'] as const)('%s', theme => | |||
}); | |||
|
|||
describe('Visual refresh toolbar only', () => { | |||
function setupTest(testFn: (page: BasePageObject) => Promise<void>) { | |||
function setupTest(testFn: (page: BasePageObject, browser: WebdriverIO.Browser) => Promise<void>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This typing is not cross-version compatible. You will make Andrei's life upgrading webdriverio version easier, if you use a more portable version
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what exactly is not portable? the type WebdriverIO.Browser
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this type is not even imported here in this file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because this type is globally available: https://webdriver.io/docs/api/globals/
what exactly do you want me to change to make it forward compatible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
return; | ||
} | ||
const throttledOnPageResize = throttle(onPageResize, 500); | ||
window.addEventListener('resize', throttledOnPageResize); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might wonder why I didn't use useResizeObserver—the reason is that this approach is easier to mock and test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a potential for future issues.
Also, this makes app layout depending on user interactions – open and then resize renders a different UI than opening on a small screen initially. We should not allow such deviations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is your suggestion here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do something similar to computeLayout
function. The decision for drawers visibility should be a pure function too.
…' into fix/app-layout-horizontal-scroll
function setupTest(testFn: (page: BasePageObject) => Promise<void>) { | ||
class PageObject extends BasePageObject { | ||
getBrowser() { | ||
return this.browser; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the browser
property is protected in the BasePageObject
class and it needs to be public here. get browser
does not work as typescript considers it as overriding and throws TS2611
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is by design, to limit accidental use of the browser
field directly and stick to high level methods.
For this task, you could introduce page.hasHorizontalScroll
utility here.
If you think browser
should not be private, we can simply expose it in browser test tools after discussion with our team
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed
@@ -154,7 +154,8 @@ describe('Visual refresh toolbar only', () => { | |||
}) | |||
); | |||
|
|||
test('first opened drawer should be closed when active drawers can not be shrunk to accommodate it (1400px)', () => { | |||
test( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks
return; | ||
} | ||
const throttledOnPageResize = throttle(onPageResize, 500); | ||
window.addEventListener('resize', throttledOnPageResize); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a potential for future issues.
Also, this makes app layout depending on user interactions – open and then resize renders a different UI than opening on a small screen initially. We should not allow such deviations.
const hasHorizontalScroll = () => | ||
page.getBrowser().execute(() => document.body.scrollWidth - document.body.clientWidth > 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NB, we have another similar check here:
components/src/app-layout/__integ__/mobile.test.ts
Lines 52 to 53 in 50a9c9e
const [documentWidth, windowWidth] = await page.getViewportWidths(); | |
expect(documentWidth).toEqual(windowWidth); |
After merging this, we should follow up and unify detection logic. (Discussed it DM, your version is better, because it properly counts for the scrollbar widths)
expect(globalDrawersWrapper.findDrawerById('global-drawer-2')!.isActive()).toBe(true); | ||
|
||
act(() => { | ||
Object.defineProperty(HTMLElement.prototype, 'scrollWidth', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- If we check only the
document
values, can we only override these values? - Why do I not see a reset of this value after the test?
const totalActiveDrawersMinSize = getActiveDrawersTotalMinSize(); | ||
const activeNavigationWidth = navigationOpen ? navigationWidth : 0; | ||
// collapsed content width is $space-layout-content-horizontal * 2 = 48px | ||
const minContentVisibleWidth = 48; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There’s no way to calculate this directly from the DOM tree since $space-layout-content-horizontal
is applied in the grid-template to .
area, specifically in this line: https://github.com/cloudscape-design/components/blob/main/src/app-layout/visual-refresh-toolbar/skeleton/styles.scss#L56
If you have a better idea, feel free to share it in this thread.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have this constant:
const contentPadding = 2 * 24; // space-xl |
I suggest to reuse it somehow
@@ -116,27 +111,15 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef<AppLayoutProps.Ref, AppLa | |||
// and compare a given number with the new drawer id min size | |||
|
|||
// the total size of all global drawers resized to their min size | |||
let totalActiveDrawersMinSize = activeGlobalDrawersIds | |||
.map( | |||
activeDrawerId => combinedDrawers.find(drawer => drawer.id === activeDrawerId)?.defaultSize ?? MIN_DRAWER_SIZE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realized that this calculation works incorrectly, as it returns min size only if defaultSize
is not defined, but it should compare them and return min of them. Fixed, see below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All good overall, only minor comments
@@ -227,6 +223,14 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef<AppLayoutProps.Ref, AppLa | |||
const navigationFocusControl = useFocusControl(navigationOpen); | |||
const splitPanelFocusControl = useSplitPanelFocusControl([splitPanelPreferences, splitPanelOpen]); | |||
|
|||
const onNavigationToggle = useCallback( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use our useStableCallback
util.
There is no promise that onNavigationChange
is a stable callback, so your code does not really do what you expect.
To an extent, the same applies to other useCallback
additions here. Make sure their dependencies are really stable (drawers
array, for example is not)
const totalActiveDrawersMinSize = getActiveDrawersTotalMinSize(); | ||
const activeNavigationWidth = navigationOpen ? navigationWidth : 0; | ||
// collapsed content width is $space-layout-content-horizontal * 2 = 48px | ||
const minContentVisibleWidth = 48; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have this constant:
const contentPadding = 2 * 24; // space-xl |
I suggest to reuse it somehow
return; | ||
} | ||
|
||
closeFirstDrawer(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't we want to have an iteration here, "close until hasHorizontalScroll is false"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, we don't, because closing a drawer triggers a change in the dependency array, which leads to recalculations until the horizontal scrollbar is no longer present
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not instantly obvious, but okay
Tried. It leads to unexpected results |
Description
When 3 drawers are open on a page with the app layout toolbar, resizing the page eventually causes a horizontal scrollbar to appear (see the picture below), even though all drawers are resized to their minimum size. A solution for that is to check for the horizontal scroll on resize and close the drawers following the priority: navigation bar first, then the first opened drawer.
Related links, issue #, if available: n/a
How has this been tested?
Review checklist
The following items are to be evaluated by the author(s) and the reviewer(s).
Correctness
CONTRIBUTING.md
.CONTRIBUTING.md
.Security
checkSafeUrl
function.Testing
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.