diff --git a/packages/react/src/__tests__/__snapshots__/storyshots.test.js.snap b/packages/react/src/__tests__/__snapshots__/storyshots.test.js.snap index 8f0a2492b60..5dbc86a662d 100644 --- a/packages/react/src/__tests__/__snapshots__/storyshots.test.js.snap +++ b/packages/react/src/__tests__/__snapshots__/storyshots.test.js.snap @@ -40118,7 +40118,9 @@ exports[`Storyshots Components|Dotcom shell Default (footer language only) 1`] = } } > - + - + - + - +
@@ -71861,7 +71869,9 @@ exports[`Storyshots Components|Dotcom shell With micro footer (language only) 1` } } > - + - + - + - + - + {
- +
diff --git a/packages/react/src/components/DotcomShell/__stories__/data/content.js b/packages/react/src/components/DotcomShell/__stories__/data/content.js index d5905861b28..9b3d6a6d077 100644 --- a/packages/react/src/components/DotcomShell/__stories__/data/content.js +++ b/packages/react/src/components/DotcomShell/__stories__/data/content.js @@ -35,6 +35,7 @@ import logoMicrosoft from '../../../../../../storybook-images/assets/logos/logo- import logoRabobank from '../../../../../../storybook-images/assets/logos/logo-rabobank.png'; import logoUsBank from '../../../../../../storybook-images/assets/logos/logo-usbank.png'; +import PropTypes from 'prop-types'; import React from 'react'; /** @@ -42,9 +43,12 @@ import React from 'react'; * * @returns {*} JSX for Learn template */ -const Content = () => ( +const Content = ({ withL1 }) => ( <> - + ( ); +Content.propTypes = { + /** + * `true` if content is rendered with an L1 on the page + */ + withL1: PropTypes.bool, +}; + export default Content; diff --git a/packages/react/src/components/Masthead/Masthead.js b/packages/react/src/components/Masthead/Masthead.js index 1411b02ceda..f79a8282b99 100644 --- a/packages/react/src/components/Masthead/Masthead.js +++ b/packages/react/src/components/Masthead/Masthead.js @@ -148,28 +148,30 @@ const Masthead = ({ [`${prefix}--masthead__header--search-active`]: isSearchActive, }); + const [scrollOffset, setScrollOffset] = useState(window.scrollY); + useEffect(() => { /** * Sets sticky masthead. If both L0 and L1 are present, L1 will be sticky. * */ - const hideTopnavThreshold = 0.25; const handleScroll = root.addEventListener('scroll', () => { /** - * L0 will hide/show only in the top 25% of the viewport. + * L0 will hide on scroll down, show on scroll up * */ if (mastheadL1Ref.current != null) { - setIsMastheadSticky( - root.pageYOffset > root.innerHeight * hideTopnavThreshold - ); + const prevOffset = scrollOffset; + const currOffset = window.scrollY; + setIsMastheadSticky(currOffset > prevOffset); + setScrollOffset(currOffset); } }); return () => { root.removeEventListener('scroll', () => handleScroll); }; - }, []); + }, [scrollOffset]); if (navigation) { switch (typeof navigation) { diff --git a/packages/react/src/components/TableOfContents/TOCDesktop.js b/packages/react/src/components/TableOfContents/TOCDesktop.js index a96431e2d97..4c5d084bd13 100644 --- a/packages/react/src/components/TableOfContents/TOCDesktop.js +++ b/packages/react/src/components/TableOfContents/TOCDesktop.js @@ -58,7 +58,10 @@ const TOCDesktop = ({ menuItems, selectedId }) => { const handleOnClick = (e, id) => { e.preventDefault(); const selector = `a[name="${id}"]`; - smoothScroll(null, selector); + const masthead = e.target.ownerDocument.querySelector( + `.${prefix}--masthead` + ); + smoothScroll(null, selector, masthead?.offsetHeight); triggerFocus(selector); }; diff --git a/packages/react/src/components/TableOfContents/TableOfContents.js b/packages/react/src/components/TableOfContents/TableOfContents.js index a33df6eb5fa..899d8893f6b 100644 --- a/packages/react/src/components/TableOfContents/TableOfContents.js +++ b/packages/react/src/components/TableOfContents/TableOfContents.js @@ -125,7 +125,7 @@ const TableOfContents = ({ .filter((elem, index, arr) => elem.height === null ? arr[index - 1].position < arr[index - 1].height - : elem.position - 50 > -elem.height + : elem.position - 50 - stickyOffset > -elem.height ); // Sets last section as active at the end of page in case there is not enough height for it to dynamically activate @@ -164,11 +164,10 @@ const TableOfContents = ({ * @param {Array} menuItems array of Items * @returns {Array} filtered array of items */ - const validateMenuItems = menuItems => { - return menuItems.filter( - item => item.title.trim().length > 0 && item.id.trim().length > 0 + const validateMenuItems = menuItems => + menuItems.filter( + item => item?.title?.trim().length && item?.id?.trim().length ); - }; /** * Props for TOCDesktop and TOCMobile diff --git a/packages/styles/scss/components/masthead/_masthead.scss b/packages/styles/scss/components/masthead/_masthead.scss index a8df785574e..412d6a5969b 100755 --- a/packages/styles/scss/components/masthead/_masthead.scss +++ b/packages/styles/scss/components/masthead/_masthead.scss @@ -213,25 +213,26 @@ $search-transition-timing: 95ms; } .#{$prefix}--masthead--sticky.#{$prefix}--masthead--sticky__l1 { + top: -98px; + @include carbon--breakpoint-up(800px) { top: -48px; } } - .#{$prefix}--masthead--sticky__l1 + .#{$prefix}--dotcom-shell { - @include carbon--breakpoint-up(800px) { - .#{$prefix}--tableofcontents__sidebar { - top: 98px; - } - } + .#{$prefix}--masthead--sticky__l1 + + .#{$prefix}--dotcom-shell + .#{$prefix}--tableofcontents__sidebar { + top: 98px; } .#{$prefix}--masthead--sticky__l1.#{$prefix}--masthead--sticky - + .#{$prefix}--dotcom-shell { + + .#{$prefix}--dotcom-shell .#{$prefix}--tableofcontents__sidebar { - @include carbon--breakpoint-up(800px) { - top: 48px; - } + top: 0; + + @include carbon--breakpoint-up(800px) { + top: 48px; } } diff --git a/packages/web-components/src/components/dotcom-shell/dotcom-shell-composite.ts b/packages/web-components/src/components/dotcom-shell/dotcom-shell-composite.ts index 71ff0c1f651..96b4491ee43 100644 --- a/packages/web-components/src/components/dotcom-shell/dotcom-shell-composite.ts +++ b/packages/web-components/src/components/dotcom-shell/dotcom-shell-composite.ts @@ -26,6 +26,7 @@ import { FOOTER_SIZE } from '../footer/footer'; import '../footer/footer-composite'; import './dotcom-shell'; import styles from './dotcom-shell-composite.scss'; +import DDSTableOfContents from '../table-of-contents/table-of-contents'; const { prefix } = settings; const { stablePrefix: ddsPrefix } = ddsSettings; @@ -85,6 +86,11 @@ class DDSDotcomShellComposite extends LitElement { */ private _masthead?: HTMLElement; + /** + * The tableOfContents element. + */ + private _tableOfContents?: DDSTableOfContents; + /** * The tableOfContents inner navBar or sideBar depending on layout. */ @@ -178,10 +184,20 @@ class DDSDotcomShellComposite extends LitElement { this._masthead!.style.top = `${mastheadTop}px`; } } else if (l1Element) { - this._masthead!.style.top = `-${Math.min( - this._masthead!.offsetHeight - l1Element.offsetHeight, - Math.abs(mastheadTop) - )}px`; + const toc = this._tableOfContents; + const stickyOffset = Number(toc?.getAttribute('stickyOffset')); + if (window.scrollY < this._lastScrollPosition) { + // scrolling up + this._masthead!.style.top = '0'; + toc!.stickyOffset = stickyOffset + l1Element.offsetHeight; + } else { + // scrolling down + this._masthead!.style.top = `-${Math.min( + this._masthead!.offsetHeight - l1Element.offsetHeight, + Math.abs(mastheadTop) + )}px`; + toc!.stickyOffset = Math.max(stickyOffset - l1Element.offsetHeight, stickyOffset); + } } else if (this._tableOfContentsLayout === 'horizontal') { this._tableOfContentsInnerBar!.style.top = `${Math.max(Math.min(tocPosition, this._masthead!.offsetHeight), 0)}px`; this._masthead!.style.top = `${mastheadTop}px`; @@ -579,7 +595,8 @@ class DDSDotcomShellComposite extends LitElement { super.update(changedProperties); if (!this._tableOfContentsInnerBar) { - const toc = document.querySelector(`${ddsPrefix}-table-of-contents`); + this._tableOfContents = document.querySelector(`${ddsPrefix}-table-of-contents`) as DDSTableOfContents; + const toc = this._tableOfContents; if (toc?.getAttribute('toc-layout') === 'horizontal') { this._tableOfContentsInnerBar = toc?.shadowRoot?.querySelector(`.${prefix}--tableofcontents__navbar`) as HTMLElement; this._tableOfContentsLayout = 'horizontal'; diff --git a/packages/web-components/src/components/table-of-contents/table-of-contents.ts b/packages/web-components/src/components/table-of-contents/table-of-contents.ts index 4b22ede196e..8945a272858 100644 --- a/packages/web-components/src/components/table-of-contents/table-of-contents.ts +++ b/packages/web-components/src/components/table-of-contents/table-of-contents.ts @@ -275,7 +275,9 @@ class DDSTableOfContents extends HostListenerMixin(StableSelectorMixin(LitElemen position: elem.getBoundingClientRect().y, })) .filter((elem, index, arr) => - elem.height === null ? arr[index - 1].position < arr[index - 1].height! : elem.position - 50 > -elem.height + elem.height === null + ? arr[index - 1].position < arr[index - 1].height! + : elem.position - 50 - this.stickyOffset > -elem.height ); // Sets last section as active at the end of page in case there is not enough height for it to dynamically activate @@ -659,7 +661,7 @@ class DDSTableOfContents extends HostListenerMixin(StableSelectorMixin(LitElemen : ``}