From 16e539f41a665aa6fcaba94e8c9045a478424495 Mon Sep 17 00:00:00 2001 From: Clinton Date: Mon, 25 Oct 2021 16:19:22 -0700 Subject: [PATCH] chore: add shadowed nav item, only make item clickable if no children --- .../components/NavItem.js | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 src/@newrelic/gatsby-theme-newrelic/components/NavItem.js diff --git a/src/@newrelic/gatsby-theme-newrelic/components/NavItem.js b/src/@newrelic/gatsby-theme-newrelic/components/NavItem.js new file mode 100644 index 00000000000..f6294394b36 --- /dev/null +++ b/src/@newrelic/gatsby-theme-newrelic/components/NavItem.js @@ -0,0 +1,181 @@ +import React, { useEffect, useLayoutEffect, useState, useMemo } from 'react'; +import PropTypes from 'prop-types'; +import { css } from '@emotion/react'; +import { graphql, useStaticQuery } from 'gatsby'; +import { useLocation } from '@reach/router'; + +import { + NavLink, + TextHighlight, + usePrevious, + useLocale, + useNavigation, + stripTrailingSlash, +} from '@newrelic/gatsby-theme-newrelic'; + +const useIsomorphicLayoutEffect = + typeof window !== 'undefined' ? useLayoutEffect : useEffect; + +const NavItem = ({ + page, + __parent: parent, + __depth: depth = 0, + __root: root, +}) => { + const locale = useLocale(); + const location = useLocation(); + const { searchTerm } = useNavigation(); + const matchesSearch = searchTerm + ? matchesSearchTerm(page, searchTerm) + : false; + const pathname = stripTrailingSlash(location.pathname).replace( + new RegExp(`\\/${locale.locale}(?=\\/)`), + '' + ); + const containsCurrentPage = useMemo(() => containsPage(page, pathname), [ + page, + pathname, + ]); + const isCurrentPage = page.url === pathname; + const shouldExpand = isCurrentPage || containsCurrentPage; + const hasChangedPage = pathname !== usePrevious(pathname); + const [isExpanded, setIsExpanded] = useState(shouldExpand); + const toggle = (expanded) => !expanded; + + const { + site: { + layout: { mobileBreakpoint }, + }, + } = useStaticQuery(graphql` + query { + site { + layout { + mobileBreakpoint + } + } + } + `); + + useEffect(() => { + if (hasChangedPage) { + setIsExpanded(shouldExpand); + } + }, [hasChangedPage, shouldExpand]); + + useEffect(() => { + if (matchesSearch && !shouldExpand) { + setIsExpanded(true); + } + }, [matchesSearch, shouldExpand, searchTerm]); + + useIsomorphicLayoutEffect(() => { + if (!searchTerm) { + setIsExpanded(shouldExpand); + } + }, [searchTerm, shouldExpand]); + + return ( +
+ 0 ? null : page.url} + icon={page.icon} + isExpanded={isExpanded} + expandable={page.pages?.length > 0} + onClick={() => { + setIsExpanded(() => !isExpanded); + }} + onToggle={() => setIsExpanded(toggle)} + mobileBreakpoint={mobileBreakpoint} + css={css` + padding-left: ${root?.icon + ? 'calc(var(--icon-size) + var(--icon-spacing))' + : 'var(--nav-link-padding)'}; + + ${mobileBreakpoint && + css` + @media screen and (max-width: ${mobileBreakpoint}) { + --border-width: 4px; + + padding-left: ${getMobilePadding({ parent, depth })}; + } + `} + `} + > + {searchTerm ? ( + + ) : ( + page.title + )} + + + {isExpanded && + page.pages?.map((child) => ( + + ))} +
+ ); +}; + +const page = PropTypes.shape({ + title: PropTypes.string.isRequired, + icon: PropTypes.string, + url: PropTypes.string, + pages: PropTypes.arrayOf(PropTypes.object), +}); + +NavItem.propTypes = { + __parent: page, + __depth: PropTypes.number, + page: page.isRequired, + __root: page, +}; + +const getMobilePadding = ({ parent, depth }) => { + if (parent == null) { + return 'calc(var(--nav-link-padding) - var(--border-width))'; + } + + return parent?.icon + ? `calc(${depth} * var(--nav-link-padding) + var(--icon-size) + var(--icon-spacing) - var(--border-width))` + : `calc(${depth + 1} * var(--nav-link-padding) - var(--border-width))`; +}; + +const containsPage = (page, url) => { + if (page.url === url) { + return true; + } + + if (page.pages == null || page.pages.length === 0) { + return false; + } + + return page.pages.some((child) => containsPage(child, url)); +}; + +const matchesSearchTerm = (page, searchTerm) => + new RegExp(searchTerm, 'i').test(page.title) || + (page.pages || []).some((child) => matchesSearchTerm(child, searchTerm)); + +export default NavItem;