Add navigation scroll to techdocs
Currently the active navigation item might be hidden behind nested items or out of view on load. This change adds a techdocs transformer that scrolls any active item into view and expands any nested active items. Signed-off-by: Crevil <bjoern.soerensen@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Scroll techdocs navigation into focus and expand any nested navigation items.
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
rewriteDocLinks,
|
||||
simplifyMkdocsFooter,
|
||||
scrollIntoAnchor,
|
||||
scrollIntoNavigation,
|
||||
transform as transformer,
|
||||
copyToClipboard,
|
||||
useSanitizerTransformer,
|
||||
@@ -166,6 +167,7 @@ export const useTechDocsReaderDom = (
|
||||
async (transformedElement: Element) =>
|
||||
transformer(transformedElement, [
|
||||
scrollIntoAnchor(),
|
||||
scrollIntoNavigation(),
|
||||
copyToClipboard(theme),
|
||||
addLinkClickListener({
|
||||
baseUrl: window.location.origin,
|
||||
|
||||
@@ -26,4 +26,5 @@ export * from './removeMkdocsHeader';
|
||||
export * from './simplifyMkdocsFooter';
|
||||
export * from './onCssReady';
|
||||
export * from './scrollIntoAnchor';
|
||||
export * from './scrollIntoNavigation';
|
||||
export * from './transformer';
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { scrollIntoNavigation } from '.';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
describe('scrollIntoNavigation', () => {
|
||||
const transformer = scrollIntoNavigation();
|
||||
const dom = { querySelectorAll: jest.fn().mockReturnValue([]) };
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('scroll to active navigation item', async () => {
|
||||
const scrollNavIntoView1 = jest.fn();
|
||||
const scrollNavIntoView2 = jest.fn();
|
||||
|
||||
dom.querySelectorAll.mockReturnValue([
|
||||
{
|
||||
scrollIntoView: scrollNavIntoView1,
|
||||
querySelector: jest.fn(),
|
||||
click: jest.fn(),
|
||||
},
|
||||
{
|
||||
scrollIntoView: scrollNavIntoView2,
|
||||
querySelector: jest.fn(),
|
||||
click: jest.fn(),
|
||||
},
|
||||
]);
|
||||
|
||||
transformer(dom as unknown as Element);
|
||||
jest.advanceTimersByTime(200);
|
||||
|
||||
expect(dom.querySelectorAll).toHaveBeenCalledWith(
|
||||
expect.stringMatching('li.md-nav__item--active'),
|
||||
);
|
||||
expect(scrollNavIntoView1).not.toHaveBeenCalled();
|
||||
expect(scrollNavIntoView2).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('expand active navigation items', async () => {
|
||||
const navItemClick1 = jest.fn();
|
||||
const navItemClick2 = jest.fn();
|
||||
|
||||
dom.querySelectorAll.mockReturnValue([
|
||||
{
|
||||
scrollIntoView: jest.fn(),
|
||||
querySelector: jest.fn().mockReturnValue({ click: navItemClick1 }),
|
||||
},
|
||||
{
|
||||
scrollIntoView: jest.fn(),
|
||||
querySelector: jest.fn().mockReturnValue({ click: navItemClick2 }),
|
||||
},
|
||||
]);
|
||||
|
||||
transformer(dom as unknown as Element);
|
||||
jest.advanceTimersByTime(200);
|
||||
|
||||
expect(dom.querySelectorAll).toHaveBeenCalledWith(
|
||||
expect.stringMatching('li.md-nav__item--active'),
|
||||
);
|
||||
expect(navItemClick1).toHaveBeenCalledWith();
|
||||
expect(navItemClick2).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Transformer } from './transformer';
|
||||
|
||||
export const scrollIntoNavigation = (): Transformer => {
|
||||
return dom => {
|
||||
setTimeout(() => {
|
||||
const activeNavItems = dom?.querySelectorAll(`li.md-nav__item--active`);
|
||||
if (activeNavItems.length !== 0) {
|
||||
// expand all navigation items that are active
|
||||
activeNavItems.forEach(activeNavItem => {
|
||||
activeNavItem?.querySelector('input')?.click();
|
||||
});
|
||||
// scroll to the last active navigation item
|
||||
activeNavItems[activeNavItems.length - 1].scrollIntoView();
|
||||
}
|
||||
}, 200);
|
||||
return dom;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user