1
0
Fork 0

feat(dev/shared): Ported PrevNext component to new nav structure

This commit is contained in:
joostdecock 2023-07-14 09:23:37 +02:00
parent b89d0474ef
commit 0a8dd5f111
10 changed files with 145 additions and 133 deletions

View file

@ -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
}
} }

View file

@ -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>
) )

View file

@ -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
}
} }

View file

@ -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

View file

@ -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>

View file

@ -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) {

View 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>
)
}

View file

@ -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) => {

View 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
}

View file

@ -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