diff --git a/src/App.tsx b/src/App.tsx index b717aafa..7ea16072 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -23,6 +23,7 @@ interface Collection { }; name: string; group?: string; + groupName?: string; leadParagraph?: string; }[]; } @@ -70,6 +71,7 @@ const App = (): JSX.Element => { id: item?.sys.id, name: item?.name, group: item?.group ? slugify(item.group) : null, + groupName: item?.group ? item.group : null, leadParagraph: item?.leadParagraph }) ); diff --git a/src/pages/codeStandards/CodeStandards.tsx b/src/pages/codeStandards/CodeStandards.tsx index 9ff96670..98e797c8 100644 --- a/src/pages/codeStandards/CodeStandards.tsx +++ b/src/pages/codeStandards/CodeStandards.tsx @@ -1,6 +1,5 @@ import { useState, useEffect, useContext } from 'react'; -import { useLocation, useParams } from 'react-router-dom'; -import { NavItemSecondary } from '@boston-scientific/anatomy-react'; +import { useParams } from 'react-router-dom'; import { NavItemTertiary } from '@boston-scientific/anatomy-react'; import { IdLookupContext } from 'App'; import Markdown from 'shared/components/Markdown'; @@ -11,11 +10,10 @@ import useHashScroll from 'shared/hooks/useHashScroll'; import useHeadings from 'shared/hooks/useHeadings'; import PageTemplate from 'shared/components/PageTemplate'; import Layout from 'shared/components/Layout'; +import useNavItems from 'shared/hooks/useNavItems'; const CodeStandards = (): JSX.Element => { - const location = useLocation(); const params = useParams(); - const [navItems, setNavItems] = useState([] as NavItemSecondary[]); const [codeStandardData, setCodeStandardData] = useState( {} as GetCodeStandardQuery['codeStandard'] ); @@ -34,44 +32,13 @@ const CodeStandards = (): JSX.Element => { console.error(error); } + const navItems = useNavItems(idLookup.codeStandards, 'code-standards'); + useEffect(() => { if (data?.codeStandard) { setCodeStandardData(data.codeStandard); } - const basePath = location.pathname.slice(0, location.pathname.lastIndexOf('/')); - const pathPrefix = basePath + '/'; - const navItems = [ - { - text: 'General', - to: pathPrefix + 'general' - }, - { - text: 'Accessibility', - to: pathPrefix + 'accessibility' - }, - { - text: 'HTML', - to: pathPrefix + 'html' - }, - { - text: 'CSS', - to: pathPrefix + 'css' - }, - { - text: 'JavaScript', - to: pathPrefix + 'javascript' - }, - { - text: 'DevOps', - to: pathPrefix + 'devops' - }, - { - text: 'Automated code quality tools', - to: pathPrefix + 'automated-code-quality-tools' - } - ]; - setNavItems(navItems); - }, [data, idLookup, location]); + }, [data]); useTitle({ titlePrefix: `${codeStandardData?.name} - Code Standards` }); useHashScroll(!!codeStandardData?.content); diff --git a/src/pages/components/Components.tsx b/src/pages/components/Components.tsx index 8598dd93..8e0fe1b8 100644 --- a/src/pages/components/Components.tsx +++ b/src/pages/components/Components.tsx @@ -1,6 +1,4 @@ import { useState, useEffect, useContext, Fragment } from 'react'; -import { useLocation } from 'react-router-dom'; -import { NavItemSecondary } from '@boston-scientific/anatomy-react'; import { NavItemTertiary } from '@boston-scientific/anatomy-react'; import Markdown from 'shared/components/Markdown'; import { GetComponentQuery } from 'shared/types/contentful'; @@ -11,17 +9,19 @@ import PageTemplate from 'shared/components/PageTemplate'; import Layout from 'shared/components/Layout'; import { ComponentContext } from './ComponentsController'; import Preview from 'pages/components/variants/Preview'; +import { IdLookupContext } from 'App'; +import useNavItems from 'shared/hooks/useNavItems'; const Components = (): JSX.Element => { - const location = useLocation(); - - const [navItems, setNavItems] = useState([] as NavItemSecondary[]); const [componentData, setComponentData] = useState( {} as GetComponentQuery['component'] ); const [headings, setHeadings] = useState([]); const data = useContext(ComponentContext); + const idLookup = useContext(IdLookupContext); + + const navItems = useNavItems(idLookup.components, 'components'); useEffect(() => { if (data) { @@ -29,152 +29,6 @@ const Components = (): JSX.Element => { } }, [data]); - useEffect(() => { - // TODO: ADS-380 get rid of .replace() - const basePath = location.pathname - .slice(0, location.pathname.lastIndexOf('/')) - .replace('/form-controls', '') - .replace('/navigation', '') - .replace('/cards', ''); - setNavItems([ - { - text: 'Accordion', - to: basePath + '/accordion' - }, - { - text: 'Button', - to: basePath + '/button' - }, - { - text: 'Callout', - to: basePath + '/callout' - }, - { - text: 'Cards', - children: [ - { - text: 'Content card', - to: basePath + '/cards/content-card' - }, - { - text: 'Card group', - to: basePath + '/cards/card-group' - }, - { - text: 'Product card', - to: basePath + '/cards/product-card' - } - ] - }, - { - text: 'Dropdown menu', - to: basePath + '/dropdown-menu' - }, - { - text: 'Form controls', - children: [ - { - text: 'Form', - to: basePath + '/form-controls/form' - }, - { - text: 'Fieldset', - to: basePath + '/form-controls/fieldset' - }, - { - text: 'Checkbox', - to: basePath + '/form-controls/checkbox' - }, - { - text: 'Radio group', - to: basePath + '/form-controls/radio-group' - }, - { - text: 'Select', - to: basePath + '/form-controls/select' - }, - { - text: 'Text input', - to: basePath + '/form-controls/text-input' - }, - { - text: 'Textarea', - to: basePath + '/form-controls/textarea' - } - ] - }, - { - text: 'Image', - to: basePath + '/image' - }, - { - text: 'Link', - to: basePath + '/link' - }, - { - text: 'Modal', - to: basePath + '/modal' - }, - { - text: 'Navigation', - children: [ - { - text: 'Breadcrumbs', - to: basePath + '/navigation/breadcrumbs' - }, - { - text: 'Primary navigation', - to: basePath + '/navigation/primary-navigation' - }, - { - text: 'Secondary navigation', - to: basePath + '/navigation/secondary-navigation' - }, - { - text: 'Tertiary navigation', - to: basePath + '/navigation/tertiary-navigation' - }, - { - text: 'Wizard navigation', - to: basePath + '/navigation/wizard-navigation' - }, - { - text: 'Footer', - to: basePath + '/navigation/footer' - }, - { - text: 'Search', - to: basePath + '/navigation/search' - }, - { - text: 'Skip link', - to: basePath + '/navigation/skip-link' - } - ] - }, - { - text: 'Pagination', - to: basePath + '/pagination' - }, - { - text: 'Ribbon', - to: basePath + '/ribbon' - }, - { - text: 'Stoplight', - to: basePath + '/stoplight' - }, - { - text: 'Tabs', - to: basePath + '/tabs' - }, - { - text: 'Tag', - to: basePath + '/tag' - } - ]); - }, [location]); - const nameForTitle = componentData?.name || ''; useTitle({ titlePrefix: `${nameForTitle} - Components` }); diff --git a/src/pages/contentGuidelines/ContentGuidelines.tsx b/src/pages/contentGuidelines/ContentGuidelines.tsx index c8998ce5..2ac894ec 100644 --- a/src/pages/contentGuidelines/ContentGuidelines.tsx +++ b/src/pages/contentGuidelines/ContentGuidelines.tsx @@ -1,6 +1,5 @@ import { useState, useEffect, useContext } from 'react'; import { useLocation, useParams } from 'react-router-dom'; -import { NavItemSecondary } from '@boston-scientific/anatomy-react'; import { NavItemTertiary } from '@boston-scientific/anatomy-react'; import { IdLookupContext } from 'App'; import Markdown from 'shared/components/Markdown'; @@ -11,11 +10,11 @@ import useHashScroll from 'shared/hooks/useHashScroll'; import useHeadings from 'shared/hooks/useHeadings'; import PageTemplate from 'shared/components/PageTemplate'; import Layout from 'shared/components/Layout'; +import useNavItems from 'shared/hooks/useNavItems'; const ContentGuidelines = (): JSX.Element => { const params = useParams(); const location = useLocation(); - const [navItems, setNavItems] = useState([] as NavItemSecondary[]); const [contentGuidelineData, setContentGuidelineData] = useState( {} as GetContentGuidelineQuery['contentGuideline'] ); @@ -34,16 +33,12 @@ const ContentGuidelines = (): JSX.Element => { console.error(error); } + const navItems = useNavItems(idLookup.contentGuidelines, 'content'); + useEffect(() => { if (data?.contentGuideline) { setContentGuidelineData(data.contentGuideline); } - const basePath = location.pathname.slice(0, location.pathname.lastIndexOf('/')); - const navItems = Object.keys(idLookup.contentGuidelines).map((entry) => ({ - text: idLookup.contentGuidelines[entry].name, - to: basePath + '/' + entry - })); - setNavItems(navItems); }, [data, idLookup, location]); useTitle({ titlePrefix: `${contentGuidelineData?.name} - Content` }); diff --git a/src/pages/foundations/Foundations.tsx b/src/pages/foundations/Foundations.tsx index 17a64085..ed2f8c12 100644 --- a/src/pages/foundations/Foundations.tsx +++ b/src/pages/foundations/Foundations.tsx @@ -1,6 +1,5 @@ import { useState, useEffect, useContext } from 'react'; -import { useLocation, useParams } from 'react-router-dom'; -import { NavItemSecondary } from '@boston-scientific/anatomy-react'; +import { useParams } from 'react-router-dom'; import { NavItemTertiary } from '@boston-scientific/anatomy-react'; import { IdLookupContext } from 'App'; import Markdown from 'shared/components/Markdown'; @@ -11,12 +10,11 @@ import useHashScroll from 'shared/hooks/useHashScroll'; import useHeadings from 'shared/hooks/useHeadings'; import PageTemplate from 'shared/components/PageTemplate'; import Layout from 'shared/components/Layout'; +import useNavItems from 'shared/hooks/useNavItems'; const Foundations = (): JSX.Element => { const params = useParams(); - const location = useLocation(); const idLookup: IdLookup = useContext(IdLookupContext); - const [navItems, setNavItems] = useState([] as NavItemSecondary[]); const [foundationData, setFoundationData] = useState( {} as GetFoundationQuery['foundation'] ); @@ -33,64 +31,14 @@ const Foundations = (): JSX.Element => { console.error(error); } + const navItems = useNavItems(idLookup.foundations, 'foundations'); + useEffect(() => { if (data?.foundation) { setFoundationData(data.foundation); } }, [data]); - useEffect(() => { - // TODO: ADS-380 get rid of .replace() - const basePath = location.pathname.slice(0, location.pathname.lastIndexOf('/')).replace('/iconography', ''); - setNavItems([ - { - text: 'Accessibility', - to: basePath + '/accessibility' - }, - { - text: 'Anti-patterns', - to: basePath + '/anti-patterns' - }, - { - text: 'Color', - to: basePath + '/color' - }, - { - text: 'Grid', - to: basePath + '/grid' - }, - { - text: 'Icons', - children: [ - { - text: 'Decorative icons', - to: basePath + '/iconography/decorative-icons' - }, - { - text: 'System icons', - to: basePath + '/iconography/system-icons' - } - ] - }, - { - text: 'Spacing', - to: basePath + '/spacing' - }, - { - text: 'Tokens', - to: basePath + '/tokens' - }, - { - text: 'Typography', - to: basePath + '/typography' - }, - { - text: 'Web sustainability', - to: basePath + '/web-sustainability' - } - ]); - }, [location]); - useTitle({ titlePrefix: `${foundationData?.name} - Foundations` }); useHashScroll(!!foundationData?.content); diff --git a/src/pages/resources/Resources.tsx b/src/pages/resources/Resources.tsx index 2bb37521..11851a63 100644 --- a/src/pages/resources/Resources.tsx +++ b/src/pages/resources/Resources.tsx @@ -1,6 +1,5 @@ import { useState, useEffect, useContext } from 'react'; -import { useLocation, useParams } from 'react-router-dom'; -import { NavItemSecondary } from '@boston-scientific/anatomy-react'; +import { useParams } from 'react-router-dom'; import { NavItemTertiary } from '@boston-scientific/anatomy-react'; import { IdLookupContext } from 'App'; import Markdown from 'shared/components/Markdown'; @@ -11,12 +10,11 @@ import useHashScroll from 'shared/hooks/useHashScroll'; import useHeadings from 'shared/hooks/useHeadings'; import PageTemplate from 'shared/components/PageTemplate'; import Layout from 'shared/components/Layout'; +import useNavItems from 'shared/hooks/useNavItems'; const Resources = (): JSX.Element => { const params = useParams(); - const location = useLocation(); const idLookup: IdLookup = useContext(IdLookupContext); - const [navItems, setNavItems] = useState([] as NavItemSecondary[]); const [resourceData, setResourceData] = useState({} as GetResourceQuery['resource']); const [headings, setHeadings] = useState([]); @@ -31,68 +29,14 @@ const Resources = (): JSX.Element => { console.error(error); } + const navItems = useNavItems(idLookup.resources, 'resources'); + useEffect(() => { if (data?.resource) { setResourceData(data.resource); } }, [data]); - useEffect(() => { - // TODO: ADS-380 get rid of .replace() - const basePath = location.pathname.slice(0, location.pathname.lastIndexOf('/')).replace('/designers', ''); - setNavItems([ - { - text: 'About Anatomy', - to: basePath + '/about-anatomy' - }, - { - text: 'Accessibility statement', - to: basePath + '/accessibility-statement' - }, - { - text: 'Community', - to: basePath + '/community' - }, - { - text: 'Contributing', - to: basePath + '/contributing' - }, - { - text: 'Designers', - children: [ - { - text: 'App design guidelines', - to: basePath + '/designers/app-design-guidelines' - }, - { - text: 'Libraries', - to: basePath + '/designers/libraries' - }, - { - text: 'Icon guidelines', - to: basePath + '/designers/icon-guidelines' - }, - { - text: 'Tools and links', - to: basePath + '/designers/tools-and-links' - } - ] - }, - { - text: 'Developers', - to: basePath + '/developers' - }, - { - text: 'SEO', - to: basePath + '/seo' - }, - { - text: 'Release notes', - to: basePath + '/release-notes' - } - ]); - }, [location]); - useTitle({ titlePrefix: `${resourceData?.name} - Resources` }); useHashScroll(!!resourceData?.content); diff --git a/src/shared/hooks/useNavItems.tsx b/src/shared/hooks/useNavItems.tsx new file mode 100644 index 00000000..8f2d54be --- /dev/null +++ b/src/shared/hooks/useNavItems.tsx @@ -0,0 +1,54 @@ +import { NavItemSecondary } from '@boston-scientific/anatomy-react'; +import { useEffect, useState } from 'react'; +import { useLocation } from 'react-router'; +import { slugify } from 'shared/helpers'; +import { IdLookupEntry, IdLookupProperties } from 'shared/types/docs'; + +const useNavItems = (lookupEntries: IdLookupEntry, pathEnd: string) => { + const [navItems, setNavItems] = useState([] as NavItemSecondary[]); + const location = useLocation(); + + useEffect(() => { + const pathParts = location.pathname.split('/'); + const basePath = pathParts.slice(0, pathParts.indexOf(pathEnd) + 1).join('/'); + + const groupedItems: { [key: string]: IdLookupProperties[] } = { + _ungrouped: [] + }; + + Object.keys(lookupEntries).forEach((entry) => { + const item = lookupEntries[entry]; + if (item.groupName) { + if (groupedItems[item.groupName]) { + groupedItems[item.groupName].push(item); + } else { + groupedItems[item.groupName] = [item]; + } + } else { + groupedItems._ungrouped.push(item); + } + }); + + const navItems: NavItemSecondary[] = groupedItems._ungrouped.map((item) => ({ + text: item.name, + to: basePath + '/' + slugify(item.name) + })); + + delete groupedItems['_ungrouped']; + + const groups = Object.keys(groupedItems).map((entry) => ({ + text: entry, + children: groupedItems[entry].map((item) => ({ + text: item.name, + to: basePath + '/' + item.group + '/' + slugify(item.name) + })) + })); + + const sortedItems = navItems.concat(groups).sort((a, z) => (a.text > z.text ? 1 : -1)); + setNavItems(sortedItems); + }, [location.pathname, lookupEntries, pathEnd]); + + return navItems; +}; + +export default useNavItems; diff --git a/src/shared/types/docs.ts b/src/shared/types/docs.ts index f867068a..b95a2228 100644 --- a/src/shared/types/docs.ts +++ b/src/shared/types/docs.ts @@ -2,6 +2,7 @@ export interface IdLookupProperties { id: string; name: string; group: string | null; + groupName: string | null; leadParagraph?: string; }