Skip to content

Commit

Permalink
feat(JumpLinks): support history.replaceState in JumpLinks (11471(pat…
Browse files Browse the repository at this point in the history
…ternfly#11471))

* feat(JumpLinks): add support for using history.replaceState

* docs(JumpLinks): update react-core component demo document, and react-integration demo-app demo

* test(JumpLinks): add integration tests for replaceState and pushState
  • Loading branch information
amorelli committed Jan 25, 2025
1 parent edc04fa commit ed9d9ba
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 4 deletions.
9 changes: 8 additions & 1 deletion packages/react-core/src/components/JumpLinks/JumpLinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export interface JumpLinksProps extends Omit<React.HTMLProps<HTMLElement>, 'labe
children?: React.ReactNode;
/** Offset to add to `scrollPosition`, potentially for a masthead which content scrolls under. */
offset?: number;
/** Use history.replaceState() instead of history.pushState() */
isReplaceState?: boolean;
/** When to collapse/expand at different breakpoints */
expandable?: {
default?: 'expandable' | 'nonExpandable';
Expand Down Expand Up @@ -80,6 +82,7 @@ function isResponsive(jumpLinks: HTMLElement) {
export const JumpLinks: React.FunctionComponent<JumpLinksProps> = ({
isCentered,
isVertical,
isReplaceState,
children,
label,
'aria-label': ariaLabel = typeof label === 'string' ? label : null,
Expand Down Expand Up @@ -207,7 +210,11 @@ export const JumpLinks: React.FunctionComponent<JumpLinksProps> = ({
scrollableElement.scrollTo(0, newScrollItem.offsetTop - offset);
}
newScrollItem.focus();
window.history.pushState('', '', (ev.currentTarget as HTMLAnchorElement).href);
if (isReplaceState) {
window.history.replaceState('', '', (ev.currentTarget as HTMLAnchorElement).href);
} else {
window.history.pushState('', '', (ev.currentTarget as HTMLAnchorElement).href);
}
ev.preventDefault();
setActiveIndex(itemIndex);
}
Expand Down
11 changes: 10 additions & 1 deletion packages/react-core/src/demos/JumpLinks.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ ScrollspyH2 = () => {
const headings = [1, 2, 3, 4, 5];

const [isVertical, setIsVertical] = React.useState(true);
const [isReplaceState, setIsReplaceState] = React.useState(false);
const [offsetHeight, setOffsetHeight] = React.useState(10);
const offsetForPadding = 10;
let masthead;
Expand All @@ -59,7 +60,7 @@ ScrollspyH2 = () => {
jumpLinksHeaderHeight && setOffsetHeight(masthead.offsetHeight + jumpLinksHeaderHeight + offsetForPadding);
}


}, [isVertical]);

getResizeObserver(
Expand Down Expand Up @@ -88,6 +89,12 @@ ScrollspyH2 = () => {
isChecked={isVertical}
onChange={(_event, check) => setIsVertical(check)}
/>
<Switch
id="is-replace-state"
label="Replace state of browser history stack"
isChecked={isReplaceState}
onChange={(_event, check) => setIsReplaceState}(check)}
/>
</PageSection>
<PageSection padding={{ default: 'noPadding' }}>
<Sidebar hasGutter orientation={!isVertical && 'stack'}>
Expand All @@ -96,6 +103,7 @@ ScrollspyH2 = () => {
<JumpLinks
isVertical={isVertical}
isCentered={!isVertical}
isReplaceState={isReplaceState}
label="Jump to section"
scrollableSelector=".pf-v6-c-page__main-container"
offset={offsetHeight}
Expand Down Expand Up @@ -157,4 +165,5 @@ This demo shows how jump links can be used in combination with a drawer.
This demo uses a `scrollableRef` prop on the JumpLinks component, which is a React ref to the `DrawerContent` component.

```js isFullscreen file="./examples/JumpLinks/JumpLinksWithDrawer.js"

```
26 changes: 26 additions & 0 deletions packages/react-integration/cypress/integration/jumplinks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,30 @@ describe('Jump Link Demo Test', () => {
expect(el.scrollTop()).to.not.eq(0);
});
});

it("Should use replaceState to update the browser history state", () => {
cy.visit('http://localhost:3000/jump-links-demo-nav-link', {
onBeforeLoad(win) {
cy.spy(win.history, 'replaceState').as('replaceState');
},
});
cy.get('#is-replace-state').should('be.checked');
cy.get('a[href*="#heading-3"]').click();
cy.get('@replaceState')
.should('have.been.calledOnce');
});

it("Should use pushState to update the browser history state", () => {
cy.visit('http://localhost:3000/jump-links-demo-nav-link', {
onBeforeLoad(win) {
cy.spy(win.history, 'pushState').as('pushState');
},
});
cy.get('#is-replace-state').should('be.checked');
cy.get('#is-replace-state').click({ force: true });
cy.get('a[href*="#heading-5"]').click();
cy.get('@pushState')
.should('have.been.calledOnce');
});

});
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { Title, JumpLinks, JumpLinksItem, PageSection, PageGroup } from '@patternfly/react-core';
import { useState } from 'react';
import { Title, JumpLinks, JumpLinksItem, PageSection, PageGroup, Switch } from '@patternfly/react-core';

export const JumpLinksDemo = () => {
const [isReplaceState, setIsReplaceState] = useState(true);
const headings = [1, 2, 3, 4, 5];

return (
<>
<PageSection stickyOnBreakpoint={{ default: 'top' }}>
<JumpLinks isCentered label="Jump to section" scrollableSelector="#scrollable-element">
<Switch
id="is-replace-state"
label="Replace state of browser history stack"
isChecked={isReplaceState}
onChange={(_event, check) => setIsReplaceState(check)}
/>
<JumpLinks isCentered label="Jump to section" scrollableSelector="#scrollable-element" isReplaceState={isReplaceState}>
{headings.map((i) => (
<JumpLinksItem key={i} id={`#heading-${i}`} href={`#heading-${i}`}>
{`Heading ${i}`}
Expand Down

0 comments on commit ed9d9ba

Please sign in to comment.