feat(dev/shared): Ported PrevNext component to new nav structure
This commit is contained in:
parent
b89d0474ef
commit
0a8dd5f111
10 changed files with 145 additions and 133 deletions
|
@ -1,4 +1,5 @@
|
||||||
import { prebuildNavigation as pbn } from 'site/prebuild/navigation.mjs'
|
import { prebuildNavigation as pbn } from 'site/prebuild/navigation.mjs'
|
||||||
|
import { orderedSlugLut } from 'shared/hooks/use-navigation-helpers.mjs'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* prebuildNavvigation[locale] holds the navigation structure based on MDX content.
|
* prebuildNavvigation[locale] holds the navigation structure based on MDX content.
|
||||||
|
@ -77,15 +78,18 @@ const sitePages = () => {
|
||||||
return pages
|
return pages
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNavigation = (params = {}) => {
|
export const useNavigation = () => {
|
||||||
const { locale = 'en' } = params
|
// Dev is EN only
|
||||||
const nav = { ...pbn[locale], ...sitePages() }
|
const siteNav = { ...pbn.en, ...sitePages() }
|
||||||
// Make top-level documentation entries appear in b-list
|
// Make top-level documentation entries appear in b-list
|
||||||
for (const page of ['tutorials', 'guides', 'howtos', 'reference', 'training']) {
|
for (const page of ['tutorials', 'guides', 'howtos', 'reference', 'training']) {
|
||||||
nav[page].o = 1000
|
siteNav[page].o = 1000
|
||||||
nav[page].b = 1
|
siteNav[page].b = 1
|
||||||
}
|
}
|
||||||
nav.contact.h = 1
|
siteNav.contact.h = 1
|
||||||
|
|
||||||
return nav
|
return {
|
||||||
|
siteNav, // Site navigation
|
||||||
|
slugLut: orderedSlugLut(siteNav), // Slug lookup table
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { components } from 'shared/components/mdx/index.mjs'
|
||||||
import { MdxWrapper } from 'shared/components/wrappers/mdx.mjs'
|
import { MdxWrapper } from 'shared/components/wrappers/mdx.mjs'
|
||||||
import { Toc } from 'shared/components/mdx/toc.mjs'
|
import { Toc } from 'shared/components/mdx/toc.mjs'
|
||||||
import { MdxMetaData } from 'shared/components/mdx/meta.mjs'
|
import { MdxMetaData } from 'shared/components/mdx/meta.mjs'
|
||||||
|
import { PrevNext } from 'shared/components/prev-next.mjs'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This page is auto-generated by the prebuild script.
|
* This page is auto-generated by the prebuild script.
|
||||||
|
@ -62,7 +63,10 @@ const DocsPage = ({ page, slug }) => {
|
||||||
<Toc toc={frontmatter.toc} wrap />
|
<Toc toc={frontmatter.toc} wrap />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<MdxWrapper>{MDX}</MdxWrapper>
|
<div>
|
||||||
|
<MdxWrapper>{MDX}</MdxWrapper>
|
||||||
|
<PrevNext slug={slug} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { freeSewingConfig as conf } from 'shared/config/freesewing.config.mjs'
|
||||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||||
import { designs, tags } from 'shared/config/designs.mjs'
|
import { designs, tags } from 'shared/config/designs.mjs'
|
||||||
import { objUpdate } from 'shared/utils.mjs'
|
import { objUpdate } from 'shared/utils.mjs'
|
||||||
|
import { orderedSlugLut } from 'shared/hooks/use-navigation-helpers.mjs'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* prebuildNavvigation[locale] holds the navigation structure based on MDX content.
|
* prebuildNavvigation[locale] holds the navigation structure based on MDX content.
|
||||||
|
@ -156,21 +158,27 @@ const sitePages = (t = false, control = 99) => {
|
||||||
return pages
|
return pages
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNavigation = (params = {}, extra = []) => {
|
export const useNavigation = ({ ignoreControl = false }, extra = []) => {
|
||||||
const { locale = 'en', ignoreControl } = params
|
// Passing in the locale is not very DRY so let's just grab it from the router
|
||||||
|
const { locale } = useRouter()
|
||||||
|
// We need translation
|
||||||
const { t } = useTranslation(ns)
|
const { t } = useTranslation(ns)
|
||||||
const { account } = useAccount()
|
// We need the account if we take control into account
|
||||||
|
const { account } = ignoreControl ? useAccount() : { account: false }
|
||||||
|
|
||||||
const navigation = {
|
const siteNav = {
|
||||||
...pbn[locale],
|
...pbn[locale],
|
||||||
...sitePages(t, ignoreControl ? undefined : account.control),
|
...sitePages(t, ignoreControl ? undefined : account.control),
|
||||||
}
|
}
|
||||||
for (const [_path, _data] of extra) {
|
for (const [_path, _data] of extra) {
|
||||||
objUpdate(navigation, _path, _data)
|
objUpdate(siteNav, _path, _data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set order on docs key (from from prebuild navigation)
|
// Set order on docs key (from from prebuild siteNav)
|
||||||
navigation.docs.o = 30
|
siteNav.docs.o = 30
|
||||||
|
|
||||||
return navigation
|
return {
|
||||||
|
siteNav, // Site navigation
|
||||||
|
slugLut: orderedSlugLut(siteNav), // Slug lookup table
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ const getPage = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocsTitle = ({ slug, className = '', site = 'org' }) => {
|
export const DocsTitle = ({ slug, className = '', site = 'org' }) => {
|
||||||
const siteNav = useNavigation()
|
const { siteNav } = useNavigation()
|
||||||
const page = getPage[site](slug, siteNav)
|
const page = getPage[site](slug, siteNav)
|
||||||
|
|
||||||
return page ? <span className={className}>{page.t}</span> : null
|
return page ? <span className={className}>{page.t}</span> : null
|
||||||
|
|
|
@ -1,113 +0,0 @@
|
||||||
import get from 'lodash.get'
|
|
||||||
import orderBy from 'lodash.orderby'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import { LeftIcon, RightIcon } from 'shared/components/icons.mjs'
|
|
||||||
|
|
||||||
// helper method to order nav entries
|
|
||||||
const order = (obj) => orderBy(obj, ['o', 't'], ['asc', 'asc'])
|
|
||||||
|
|
||||||
// Helper method to filter out page nodes
|
|
||||||
const nodesOnly = (current) =>
|
|
||||||
Object.values(order(current)).filter((entry) => typeof entry === 'object')
|
|
||||||
|
|
||||||
// Helper method to get the siblings
|
|
||||||
const siblings = (app) =>
|
|
||||||
app.state.slug ? nodesOnly(get(app.state.nav, app.state.slug.split('/').slice(1, -1))) : []
|
|
||||||
|
|
||||||
// Helper method to get the current parent
|
|
||||||
const currentParent = (app) =>
|
|
||||||
app.state.slug ? [get(app.state.nav, app.state.slug.split('/').slice(1, -1))] : []
|
|
||||||
|
|
||||||
// Helper method to get the next parent
|
|
||||||
const nextParent = (app) => {
|
|
||||||
if (app.state.slug)
|
|
||||||
return app.state.slug.split('/').length < 4
|
|
||||||
? nodesOnly(app.state.nav)
|
|
||||||
: nodesOnly(get(app.state.nav, app.state.slug.split('/').slice(1, -2)))
|
|
||||||
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper method to get current node
|
|
||||||
const current = (app) => (app.state.slug ? get(app.state.nav, app.state.slug.split('/')) : null)
|
|
||||||
|
|
||||||
const previous = (app) => {
|
|
||||||
// Previous sibling (aside)
|
|
||||||
const aside = siblings(app)
|
|
||||||
if (aside.length > 0) {
|
|
||||||
let next = false
|
|
||||||
for (const node of aside.reverse()) {
|
|
||||||
if (next) return node
|
|
||||||
if (node?.s && node.s === app.state.slug) next = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Previous parent (up)
|
|
||||||
const up = currentParent(app)
|
|
||||||
if (up.length === 1) return up.pop()
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const next = (app) => {
|
|
||||||
// Next child (down)
|
|
||||||
const down = nodesOnly(current(app))
|
|
||||||
if (down.length > 0) return down[0]
|
|
||||||
|
|
||||||
// Next sibling (aside)
|
|
||||||
const aside = siblings(app)
|
|
||||||
if (aside.length > 0) {
|
|
||||||
let next = false
|
|
||||||
for (const node of aside) {
|
|
||||||
if (next) return node
|
|
||||||
if (node?.s && node.s === app.state.slug) next = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next parent (up)
|
|
||||||
const up = nextParent(app)
|
|
||||||
if (up.length > 0) {
|
|
||||||
let next = false
|
|
||||||
for (const node of up) {
|
|
||||||
if (next) return node
|
|
||||||
if (node?.s && node.s === app.state.slug.slice(0, node.s.length)) next = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderPrevious = (node) =>
|
|
||||||
node ? (
|
|
||||||
<div className="flex flex-row gap-2 items-center">
|
|
||||||
<LeftIcon className="w-6 h-6 shrink-0" />
|
|
||||||
<Link href={'/' + node.s} className="text-secondary break-words">
|
|
||||||
{node.t}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span></span>
|
|
||||||
)
|
|
||||||
|
|
||||||
const renderNext = (node) =>
|
|
||||||
node ? (
|
|
||||||
<div className="flex flex-row gap-2 items-center justify-end">
|
|
||||||
<Link href={'/' + node.s} className="text-right break-words">
|
|
||||||
{node.t}
|
|
||||||
</Link>
|
|
||||||
<RightIcon className="w-6 h-6 shrink-0" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span></span>
|
|
||||||
)
|
|
||||||
|
|
||||||
export const PrevNext = ({ app }) => {
|
|
||||||
return (
|
|
||||||
<div className="grid grid-cols-2 gap-4 border-t mt-12 py-2">
|
|
||||||
{renderPrevious(previous(app))}
|
|
||||||
{renderNext(next(app))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
//<pre>{JSON.stringify(app.state.nav, null ,2)}</pre>
|
|
||||||
//<pre>{JSON.stringify(app.state.slug, null ,2)}</pre>
|
|
|
@ -4,6 +4,7 @@ import { useContext } from 'react'
|
||||||
import { NavigationContext } from 'shared/context/navigation-context.mjs'
|
import { NavigationContext } from 'shared/context/navigation-context.mjs'
|
||||||
import { useNavigation } from 'site/hooks/use-navigation.mjs'
|
import { useNavigation } from 'site/hooks/use-navigation.mjs'
|
||||||
import { BulletIcon, RightIcon } from 'shared/components/icons.mjs'
|
import { BulletIcon, RightIcon } from 'shared/components/icons.mjs'
|
||||||
|
import { pageHasChildren } from 'shared/utils.mjs'
|
||||||
|
|
||||||
const getRoot = {
|
const getRoot = {
|
||||||
dev: (root, nav) => {
|
dev: (root, nav) => {
|
||||||
|
@ -31,7 +32,7 @@ const RenderTree = ({ tree, recurse, depth = 1, level = 0, lead = [] }) => (
|
||||||
* Does this have children?
|
* Does this have children?
|
||||||
*/
|
*/
|
||||||
const hasChildren =
|
const hasChildren =
|
||||||
recurse && (!depth || level < depth) && Object.keys(tree[key]).join('').length > 5
|
recurse && (!depth || level < depth) && pageHasChildren(tree[key])
|
||||||
? tree[key].s.replaceAll('/', '')
|
? tree[key].s.replaceAll('/', '')
|
||||||
: false
|
: false
|
||||||
|
|
||||||
|
@ -72,7 +73,7 @@ export const ReadMore = ({
|
||||||
ignoreControl,
|
ignoreControl,
|
||||||
}) => {
|
}) => {
|
||||||
const { slug } = useContext(NavigationContext)
|
const { slug } = useContext(NavigationContext)
|
||||||
const siteNav = useNavigation({ ignoreControl })
|
const { siteNav } = useNavigation({ ignoreControl })
|
||||||
|
|
||||||
// Deal with recurse not being a number
|
// Deal with recurse not being a number
|
||||||
if (recurse && recurse !== true) {
|
if (recurse && recurse !== true) {
|
||||||
|
|
65
sites/shared/components/prev-next.mjs
Normal file
65
sites/shared/components/prev-next.mjs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Dependencies
|
||||||
|
import get from 'lodash.get'
|
||||||
|
// Hooks
|
||||||
|
import { useNavigation } from 'site/hooks/use-navigation.mjs'
|
||||||
|
// Components
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { LeftIcon, RightIcon } from 'shared/components/icons.mjs'
|
||||||
|
|
||||||
|
const linkClasses =
|
||||||
|
'flex flex-row gap-2 items-center w-full bg-secondary py-4 px-2 ' +
|
||||||
|
'rounded-lg border-secondary bg-opacity-10 border border-solid ' +
|
||||||
|
'hover:bg-opacity-100 hover:text-secondary-content'
|
||||||
|
|
||||||
|
const PrevPage = ({ t, s }) =>
|
||||||
|
s ? (
|
||||||
|
<Link className={`${linkClasses} justify-start`} href={'/' + s}>
|
||||||
|
<LeftIcon className="w-6 h-6 shrink-0" />
|
||||||
|
<span className="text-left break-words font-bold">{t}</span>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span></span>
|
||||||
|
)
|
||||||
|
|
||||||
|
const NextPage = ({ t, s }) =>
|
||||||
|
s ? (
|
||||||
|
<Link className={`${linkClasses} justify-end`} href={'/' + s}>
|
||||||
|
<span className="text-right break-words font-bold">{t}</span>
|
||||||
|
<RightIcon className="w-6 h-6 shrink-0" />
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span></span>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const PrevNext = ({ slug }) => {
|
||||||
|
// Grab site navigation and slug lookup table from the useNavigatin hook
|
||||||
|
const { siteNav, slugLut } = useNavigation()
|
||||||
|
|
||||||
|
// Lookup the current slug in the LUT
|
||||||
|
const index = slugLut.indexOf(slug)
|
||||||
|
|
||||||
|
// Add 1 for the next page, unless it's the last page
|
||||||
|
const iNext = index === slugLut.length ? 0 : index + 1
|
||||||
|
|
||||||
|
// Subtract 1 for the previous page, unless it's the first page
|
||||||
|
const iPrev = index === 0 ? slugLut.length - 1 : index - 1
|
||||||
|
|
||||||
|
// Get the next page from the siteNav object
|
||||||
|
const next = get(siteNav, slugLut[iNext].split('/'))
|
||||||
|
|
||||||
|
// Get the previous page from the siteNav object
|
||||||
|
const prev = get(siteNav, slugLut[iPrev].split('/'))
|
||||||
|
|
||||||
|
// Return content
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'flex flex-col-reverse gap-2 lg:grid lg:grid-cols-2 lg:gap-8 w-full' +
|
||||||
|
'items-start pt-6 mt-6 border-t-2 border-solid border-r-0 border-l-0 border-b-0'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PrevPage t={prev.t} s={prev.s} />
|
||||||
|
<NextPage t={next.t} s={next.s} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -66,7 +66,7 @@ export const NavigationContextProvider = ({ children }) => {
|
||||||
})
|
})
|
||||||
const [extraPages, setExtraPages] = useState([])
|
const [extraPages, setExtraPages] = useState([])
|
||||||
|
|
||||||
const siteNav = useNavigation({ path: value.path, locale: value.locale }, extraPages)
|
const { siteNav } = useNavigation({ path: value.path, locale: value.locale }, extraPages)
|
||||||
const navState = buildNavState(value, siteNav)
|
const navState = buildNavState(value, siteNav)
|
||||||
|
|
||||||
const addPages = (extra) => {
|
const addPages = (extra) => {
|
||||||
|
|
36
sites/shared/hooks/use-navigation-helpers.mjs
Normal file
36
sites/shared/hooks/use-navigation-helpers.mjs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { pageHasChildren } from 'shared/utils.mjs'
|
||||||
|
import orderBy from 'lodash.orderby'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A method to recursively add the ordered slugs to the LUT
|
||||||
|
*/
|
||||||
|
const flattenOrderedChildPages = (nav) => {
|
||||||
|
const slugs = []
|
||||||
|
for (const page of orderBy(nav, ['o', 't'], ['asc', 'asc'])) {
|
||||||
|
if (page.s) {
|
||||||
|
slugs.push(page.s)
|
||||||
|
if (pageHasChildren(page)) slugs.push(...flattenOrderedChildPages(page))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return slugs
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This builds the slugLut (slug look up table) which makes it trivial to
|
||||||
|
* build the PrevNext component as it builds a flat list of all pages in
|
||||||
|
* the order they are naturally presented to the reader. So if you have
|
||||||
|
* a page's slug, you merely need to look it up in the list and return the
|
||||||
|
* next entry (or previous)
|
||||||
|
*/
|
||||||
|
export const orderedSlugLut = (nav) => {
|
||||||
|
const slugs = []
|
||||||
|
for (const page of orderBy(nav, ['o', 't'], ['asc', 'asc'])) {
|
||||||
|
if (page.s) {
|
||||||
|
slugs.push(page.s)
|
||||||
|
if (pageHasChildren(page)) slugs.push(...flattenOrderedChildPages(page))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return slugs
|
||||||
|
}
|
|
@ -304,3 +304,10 @@ export const hasRequiredMeasurements = (Design, measies = {}, DesignIsMeasuremen
|
||||||
|
|
||||||
return [missing.length === 0, missing]
|
return [missing.length === 0, missing]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This expects a object from the nav tree and will filter out the know 1-char keys
|
||||||
|
* and then check if there are any left. If there are, those are child-pages.
|
||||||
|
*/
|
||||||
|
export const pageHasChildren = (page) =>
|
||||||
|
Object.keys(page).filter((key) => !['t', 's', 'o', 'b', 'h'].includes(key)).length > 0
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue