1
0
Fork 0

wip(shared): Changes to layouts

This will break the org build, but we need to rip out sanity anyway so
I don't think it's worth obsessing over it now.

I've essentially changes the default layout and added a new navigation
component.
This commit is contained in:
joostdecock 2023-07-15 16:55:22 +02:00
parent 8b79de2bd6
commit 5a9f2f8d40
26 changed files with 318 additions and 172 deletions

View file

@ -16,6 +16,7 @@ import {
MeasieIcon, MeasieIcon,
PageIcon, PageIcon,
PlusIcon, PlusIcon,
RssIcon,
} from 'shared/components/icons.mjs' } from 'shared/components/icons.mjs'
import { HeaderWrapper } from 'shared/components/wrappers/header.mjs' import { HeaderWrapper } from 'shared/components/wrappers/header.mjs'
import { ModalThemePicker, ns as themeNs } from 'shared/components/modal/theme-picker.mjs' import { ModalThemePicker, ns as themeNs } from 'shared/components/modal/theme-picker.mjs'
@ -40,20 +41,20 @@ const NavIcons = ({ setModal, setSearch }) => {
<DesignIcon className={iconSize} /> <DesignIcon className={iconSize} />
</NavButton> </NavButton>
<NavButton <NavButton
href="/patterns" href="/docs"
label={t('header:patterns')} label={t('header:docs')}
color={colors[2]} color={colors[2]}
extraClasses="hidden lg:flex" extraClasses="hidden lg:flex"
> >
<PageIcon className={iconSize} /> <DocsIcon className={iconSize} />
</NavButton> </NavButton>
<NavButton <NavButton
href="/sets" href="/blog"
label={t('header:sets')} label={t('header:blog')}
color={colors[3]} color={colors[3]}
extraClasses="hidden lg:flex" extraClasses="hidden lg:flex"
> >
<MeasieIcon className={iconSize} /> <RssIcon className={iconSize} />
</NavButton> </NavButton>
<NavButton <NavButton
href="/showcase" href="/showcase"
@ -63,39 +64,44 @@ const NavIcons = ({ setModal, setSearch }) => {
> >
<ShowcaseIcon className={iconSize} /> <ShowcaseIcon className={iconSize} />
</NavButton> </NavButton>
<NavSpacer />
<NavButton <NavButton
href="/docs" href="/patterns"
label={t('header:docs')} label={t('header:patterns')}
color={colors[5]} color={colors[5]}
extraClasses="hidden lg:flex" extraClasses="hidden lg:flex"
> >
<DocsIcon className={iconSize} /> <PageIcon className={iconSize} />
</NavButton>
<NavButton
href="/sets"
label={t('header:sets')}
color={colors[6]}
extraClasses="hidden lg:flex"
>
<MeasieIcon className={iconSize} />
</NavButton>
<NavButton href="/account" label={t('header:account')} color={colors[7]}>
<UserIcon className={iconSize} />
</NavButton> </NavButton>
<NavSpacer /> <NavSpacer />
<NavButton <NavButton
onClick={() => setModal(<ModalThemePicker />)} onClick={() => setModal(<ModalThemePicker />)}
label={t('header:theme')} label={t('header:theme')}
color={colors[6]} color={colors[8]}
> >
<ThemeIcon className={iconSize} /> <ThemeIcon className={iconSize} />
</NavButton> </NavButton>
<NavButton <NavButton
onClick={() => setModal(<ModalLocalePicker />)} onClick={() => setModal(<ModalLocalePicker />)}
label={t('header:language')} label={t('header:language')}
color={colors[7]} color={colors[9]}
> >
<I18nIcon className={iconSize} /> <I18nIcon className={iconSize} />
</NavButton> </NavButton>
<NavButton onClick={() => setSearch(true)} label={t('header:search')} color={colors[8]}> <NavButton onClick={() => setSearch(true)} label={t('header:search')} color={colors[10]}>
<SearchIcon className={iconSize} /> <SearchIcon className={iconSize} />
</NavButton> </NavButton>
<NavSpacer />
<NavButton href="/account" label={t('header:account')} color={colors[9]}>
<UserIcon className={iconSize} />
</NavButton>
<NavButton href="/new" label={t('header:new')} color={colors[10]}>
<PlusIcon className={iconSize} />
</NavButton>
</> </>
) )
} }

View file

@ -1,10 +1,3 @@
import { AsideNavigation } from 'shared/components/navigation/aside.mjs'
export const ns = [] export const ns = []
export const BareLayout = (props) => ( export const BareLayout = (props) => props.children
<>
<AsideNavigation mobileOnly />
{props.children}
</>
)

View file

@ -1,26 +1,43 @@
// Hooks
import { useContext } from 'react' import { useContext } from 'react'
// Components import { useNavigation } from 'site/hooks/use-navigation.mjs'
import { AsideNavigation, ns as navNs } from 'shared/components/navigation/aside.mjs' // Context
import { Breadcrumbs } from 'shared/components/breadcrumbs.mjs'
import { NavigationContext } from 'shared/context/navigation-context.mjs' import { NavigationContext } from 'shared/context/navigation-context.mjs'
// Components
//import { AsideNavigation, ns as navNs } from 'shared/components/navigation/aside.mjs'
//import { Breadcrumbs } from 'shared/components/breadcrumbs.mjs'
//import { NavigationContext } from 'shared/context/navigation-context.mjs'
export const ns = navNs import { BaseLayout, BaseLayoutLeft, BaseLayoutWide } from 'shared/components/base-layout.mjs'
import { NavLinks, Breadcrumbs, MainSections } from 'shared/components/navigation/sitenav.mjs'
export const DefaultLayout = ({ children = [], pageTitle = false }) => { export const ns = [] //navNs
const { crumbs } = useContext(NavigationContext)
export const DefaultLayout = ({ children = [], slug, pageTitle = false }) => {
const { siteNav } = useNavigation({ ignoreControl: true })
return ( return (
<div className="grid grid-cols-4 mx-auto justify-center place-items-stretch"> <BaseLayout>
<AsideNavigation /> <BaseLayoutLeft>
<section className="col-span-4 lg:col-span-3 py-8 lg:py-24 px-4 lg:pl-8 bg-base-50"> {slug ? (
<>
<MainSections {...{ siteNav, slug }} />
<NavLinks {...{ siteNav, slug }} />
</>
) : (
<p>Slug not passed to layout</p>
)}
</BaseLayoutLeft>
<BaseLayoutWide>
{pageTitle && ( {pageTitle && (
<div className="xl:pl-4"> <div className="xl:pl-4">
<Breadcrumbs crumbs={crumbs} title={pageTitle} /> {slug && <Breadcrumbs {...{ siteNav, slug }} />}
<h1 className="break-words">{pageTitle}</h1> <h1 className="break-words">{pageTitle}</h1>
</div> </div>
)} )}
<div className="xl:pl-4">{children}</div> <div className="xl:pl-4">{children}</div>
</section> </BaseLayoutWide>
</div> </BaseLayout>
) )
} }

View file

@ -0,0 +1,69 @@
// Hooks
import { useNavigation } from 'site/hooks/use-navigation.mjs'
// Components
import Head from 'next/head'
import {
BaseLayout,
BaseLayoutLeft,
BaseLayoutProse,
BaseLayoutRight,
} from 'shared/components/base-layout.mjs'
import { NavLinks, Breadcrumbs, MainSections } from 'shared/components/navigation/sitenav.mjs'
import { Toc } from 'shared/components/mdx/toc.mjs'
import { MdxMetaData } from 'shared/components/mdx/meta.mjs'
import { PrevNext } from 'shared/components/prev-next.mjs'
export const ns = [] //navNs
export const DocsLayout = ({ children = [], slug, frontmatter }) => {
const { siteNav } = useNavigation({ ignoreControl: true })
return (
<>
<Head>
<meta property="og:title" content={frontmatter.title} key="title" />
<meta property="og:type" content="article" key="type" />
<meta property="og:description" content={``} key="type" />
<meta property="og:article:author" content="Joost De Cock" key="author" />
<meta
property="og:image"
content={`https://canary.backend.freesewing.org/og-img/en/org/${slug}}`}
key="image"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:url" content={`https://freesewing.org/${slug}`} key="url" />
<meta property="og:locale" content="en" key="locale" />
<meta property="og:site_name" content="freesewing.org" key="site" />
<title>{frontmatter.title} - FreeSewing.org</title>
</Head>
<BaseLayout>
<BaseLayoutLeft>
<MainSections {...{ siteNav, slug }} />
<NavLinks {...{ siteNav, slug }} />
</BaseLayoutLeft>
<BaseLayoutProse>
<div className="w-full">
<Breadcrumbs {...{ siteNav, slug }} />
<h1 className="break-words searchme">{frontmatter.title}</h1>
<div className="block xl:hidden">
<Toc toc={frontmatter.toc} wrap />
</div>
</div>
{children}
<PrevNext slug={slug} noPrev={slug === 'docs'} />
</BaseLayoutProse>
<BaseLayoutRight>
<MdxMetaData frontmatter={frontmatter} slug={slug} locale="en" />
<div className="hidden xl:block">
<Toc toc={frontmatter.toc} wrap />
</div>
</BaseLayoutRight>
</BaseLayout>
</>
)
}

View file

@ -1,4 +0,0 @@
export const ns = []
// This layout adds nothing
export const NoLayout = ({ children = [] }) => children

View file

@ -17,10 +17,12 @@ export const SanityPageWrapper = ({
author = {}, author = {},
page = {}, page = {},
namespaces = ['common'], namespaces = ['common'],
slug,
}) => { }) => {
const { t } = useTranslation(namespaces) const { t } = useTranslation(namespaces)
console.log({ slug })
return ( return (
<PageWrapper title={post.title} {...page}> <PageWrapper title={post.title} {...page} slug={slug}>
<Head> <Head>
<meta property="og:type" content="article" key="type" /> <meta property="og:type" content="article" key="type" />
<meta property="og:description" content={post.intro || post.title} key="description" /> <meta property="og:description" content={post.intro || post.title} key="description" />

View file

@ -14,8 +14,17 @@ import { useRouter } from 'next/router'
* - home page => no navvigation shown * - home page => no navvigation shown
* - /contact => Added below * - /contact => Added below
* *
* Note: Set 'h' to truthy to not show a top-level entry as a section * Remember Mc_Shifton:
* Note: Set 'c' to set the control level to hide things from users * Note: Set 'm' to truthy to show this as a main section in the side-navigation (optional)
* Note: Set 'c' to set the control level to hide things from users (optional)
* Note: Set 's' to the slug (optional insofar as it's not a real page (a spacer for the header))
* Note: Set '_' to never show the page in the site navigation (like the tags pages)
* Note: Set 'h' to indicate this is a top-level page that should be hidden from the side-nav (like search)
* Note: Set 'i' when something should be included as top-level in the collapse side-navigation (optional)
* Note: Set 'f' to add the page to the footer
* Note: Set 't' to the title
* Note: Set 'o' to set the order (optional)
* Note: Set 'n' to mark this as a noisy entry that should always be closed unless active (like blog)
*/ */
export const ns = ['account', 'sections', 'design', 'tags', 'designs'] export const ns = ['account', 'sections', 'design', 'tags', 'designs']
@ -26,55 +35,59 @@ const sitePages = (t = false, control = 99) => {
const pages = { const pages = {
// Top-level pages that are the sections menu // Top-level pages that are the sections menu
designs: { designs: {
t: t('sections:designs'), m: 1,
s: 'designs', s: 'designs',
o: 10, t: t('sections:designs'),
n: 1,
tags: { tags: {
t: t('design:tags'), _: 1,
s: 'designs/tags', s: 'designs/tags',
h: 1, t: t('design:tags'),
o: 'aaa', o: 'aaa',
}, },
}, },
patterns: { patterns: {
t: t('sections:patterns'), m: 1,
s: 'patterns', s: 'patterns',
o: 14, t: t('sections:patterns'),
}, },
sets: { sets: {
t: t('sections:sets'), m: 1,
s: 'sets', s: 'sets',
o: 16, t: t('sections:sets'),
}, },
community: { community: {
t: t('sections:community'), m: 1,
s: 'community', s: 'community',
o: 40, t: t('sections:community'),
}, },
account: { account: {
t: t('sections:account'), m: 1,
s: 'account', s: 'account',
o: 99, t: t('sections:account'),
n: 1,
}, },
// Top-level pages that are not in the sections menu // Top-level pages that are not in the sections menu
apikeys: { apikeys: {
t: t('apikeys'), _: 1,
s: 'apikeys', s: 'apikeys',
h: 1, h: 1,
t: t('apikeys'),
}, },
curate: { curate: {
t: t('curate'),
s: 'curate', s: 'curate',
h: 1, h: 1,
t: t('curate'),
sets: { sets: {
t: t('curateSets'), t: t('curateSets'),
s: 'curate/sets', s: 'curate/sets',
}, },
}, },
new: { new: {
t: t('new'), m: 1,
s: 'new', s: 'new',
h: 1, h: 1,
t: t('new'),
pattern: { pattern: {
t: t('patternNew'), t: t('patternNew'),
s: 'new/pattern', s: 'new/pattern',
@ -87,56 +100,54 @@ const sitePages = (t = false, control = 99) => {
}, },
}, },
profile: { profile: {
t: t('yourProfile'),
s: 'profile', s: 'profile',
h: 1, h: 1,
t: t('yourProfile'),
}, },
translation: { translation: {
t: t('translation'),
s: 'translation', s: 'translation',
h: 1, h: 1,
t: t('translation'),
join: { join: {
t: t('translation:joinATranslationTeam'), t: t('translation:joinATranslationTeam'),
s: 'translation', s: 'translation',
h: 1,
}, },
'suggest-language': { 'suggest-language': {
t: t('translation:suggestLanguage'), t: t('translation:suggestLanguage'),
s: 'translation', s: 'translation',
h: 1,
}, },
}, },
sitemap: { sitemap: {
t: t('sitemap'),
s: 'sitemap', s: 'sitemap',
h: 1, h: 1,
t: t('sitemap'),
}, },
// Not translated, this is a developer page // Not translated, this is a developer page
typography: { typography: {
t: 'Typography',
s: 'typography', s: 'typography',
h: 1, h: 1,
t: 'Typography',
}, },
} }
for (const section in conf.account.fields) { for (const section in conf.account.fields) {
for (const [field, controlScore] of Object.entries(conf.account.fields[section])) { for (const [field, controlScore] of Object.entries(conf.account.fields[section])) {
if (Number(control) >= controlScore) if (Number(control) >= controlScore)
pages.account[field] = { pages.account[field] = {
t: t(`account:${field}`),
s: `account/${field}`, s: `account/${field}`,
t: t(`account:${field}`),
} }
} }
} }
if (Number(control) >= conf.account.fields.developer.apikeys) if (Number(control) >= conf.account.fields.developer.apikeys)
pages.new.apikey = { pages.new.apikey = {
t: t('newApikey'),
s: 'new/apikey', s: 'new/apikey',
t: t('newApikey'),
o: 30, o: 30,
} }
pages.account.reload = { pages.account.reload = {
t: t(`account:reload`),
s: `account/reload`, s: `account/reload`,
t: t(`account:reload`),
} }
for (const design in designs) { for (const design in designs) {
// pages.designs[design] = { // pages.designs[design] = {
@ -144,21 +155,22 @@ const sitePages = (t = false, control = 99) => {
// s: `designs/${design}`, // s: `designs/${design}`,
// } // }
pages.new.pattern[design] = { pages.new.pattern[design] = {
t: t(`account:generateANewThing`, { thing: t(`designs:${design}.t`) }),
s: `new/${design}`, s: `new/${design}`,
t: t(`account:generateANewThing`, { thing: t(`designs:${design}.t`) }),
} }
} }
for (const tag of tags) { for (const tag of tags) {
pages.designs.tags[tag] = { pages.designs.tags[tag] = {
t: t(`tags:${tag}`),
s: `designs/tags/${tag}`, s: `designs/tags/${tag}`,
t: t(`tags:${tag}`),
} }
} }
return pages return pages
} }
export const useNavigation = ({ ignoreControl = false }, extra = []) => { export const useNavigation = (param = {}, extra = []) => {
const { ignoreControl = false } = param
// Passing in the locale is not very DRY so let's just grab it from the router // Passing in the locale is not very DRY so let's just grab it from the router
const { locale } = useRouter() const { locale } = useRouter()
// We need translation // We need translation
@ -174,8 +186,23 @@ export const useNavigation = ({ ignoreControl = false }, extra = []) => {
objUpdate(siteNav, _path, _data) objUpdate(siteNav, _path, _data)
} }
// Set order on docs key (from from prebuild siteNav) // Apply some tweaks
siteNav.docs.o = 30 siteNav.blog.m = 1
siteNav.blog.n = 1
siteNav.showcase.m = 1
siteNav.showcase.n = 1
siteNav.docs.m = 1
// Set order on main sections
siteNav.designs.o = 10
siteNav.docs.o = 20
siteNav.blog.o = 30
siteNav.showcase.o = 40
siteNav.community.o = 50
siteNav.patterns.o = 60
siteNav.sets.o = 70
siteNav.account.o = 80
siteNav.new.o = 90
return { return {
siteNav, // Site navigation siteNav, // Site navigation

View file

@ -11,6 +11,7 @@ import { Spinner } from 'shared/components/spinner.mjs'
import { components } from 'shared/components/mdx/index.mjs' 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 { DocsLayout } from 'site/components/layouts/docs.mjs'
/* /*
* PLEASE READ THIS BEFORE YOU TRY TO REFACTOR THIS PAGE * PLEASE READ THIS BEFORE YOU TRY TO REFACTOR THIS PAGE
@ -61,7 +62,11 @@ const HeadInfo = ({ frontmatter, locale, slug }) => (
) )
export const Page = ({ page, frontmatter, slug, locale, MDX }) => ( export const Page = ({ page, frontmatter, slug, locale, MDX }) => (
<PageWrapper {...page} title={frontmatter.title}> <PageWrapper
{...page}
title={frontmatter.title}
layout={(props) => <DocsLayout {...props} {...{ slug, frontmatter }} />}
>
<HeadInfo {...{ frontmatter, locale, slug }} /> <HeadInfo {...{ frontmatter, locale, slug }} />
<div className="flex flex-row-reverse flex-wrap xl:flex-nowrap justify-end"> <div className="flex flex-row-reverse flex-wrap xl:flex-nowrap justify-end">
{frontmatter.toc && frontmatter.toc.length > 0 && ( {frontmatter.toc && frontmatter.toc.length > 0 && (

View file

@ -4,7 +4,6 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
// Components // Components
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
import { BareLayout as Layout } from 'site/components/layouts/bare.mjs'
import { TranslationStatus } from 'site/components/crowdin/status.mjs' import { TranslationStatus } from 'site/components/crowdin/status.mjs'
import { Translators } from 'site/components/crowdin/translators.mjs' import { Translators } from 'site/components/crowdin/translators.mjs'
import { Popout } from 'shared/components/popout.mjs' import { Popout } from 'shared/components/popout.mjs'
@ -20,26 +19,29 @@ const TranslationPage = ({ page }) => {
const title = t('translation:translation') const title = t('translation:translation')
return ( return (
<PageWrapper {...page} layout={Layout}> <PageWrapper {...page} title={title}>
<div className="max-w-4xl mx-auto p-4 mt-4"> <div className="max-w-2xl">
<Breadcrumbs crumbs={[{ s: 'translation', t: title }]} title={title} />
<h1>{title}</h1>
<p>{t('translation:proudlyMultilingual')}</p> <p>{t('translation:proudlyMultilingual')}</p>
<div className="max-w-2xl">
<Popout tip> <Popout tip>
<h5>{t('translation:getInvolved')}</h5> <h5>{t('translation:getInvolved')}</h5>
<p>{t('translation:teamEffort')}</p> <p>{t('translation:teamEffort')}</p>
<p>
<Link href="/translation/join" className="btn btn-accent mr-2"> <Link href="/translation/join" className="btn btn-accent mr-2">
{t('translation:joinTheTeam')} {t('translation:joinTheTeam')}
</Link> </Link>
</p>
<p>
<a <a
href="https://freesewing.dev/guides/translation" href="https://freesewing.dev/guides/translation"
className="btn btn-accent btn-outline" className="btn btn-accent btn-outline"
> >
{t('translation:seeTranslationGuide')} {t('translation:seeTranslationGuide')}
</a> </a>
</p>
</Popout> </Popout>
</div>
<h2 id="status">Translation Status</h2> <h2 id="status">Translation Status</h2>
<TranslationStatus /> <TranslationStatus />
@ -91,6 +93,7 @@ const TranslationPage = ({ page }) => {
<b>{t('locales:uk')}</b> <b>{t('locales:uk')}</b>
</li> </li>
</ul> </ul>
<div className="max-w-3xl">
<Popout tip> <Popout tip>
<h5>{t('translation:addLanguage1')}</h5> <h5>{t('translation:addLanguage1')}</h5>
<p> <p>
@ -98,17 +101,22 @@ const TranslationPage = ({ page }) => {
<br /> <br />
{t('translation:addLanguage3')} {t('translation:addLanguage3')}
</p> </p>
<p>
<Link href="/translation/suggest-language" className="btn btn-accent mr-2"> <Link href="/translation/suggest-language" className="btn btn-accent mr-2">
{t('translation:suggestLanguage')} {t('translation:suggestLanguage')}
</Link> </Link>
</p>
<p>
<a <a
href="https://freesewing.dev/guides/translation" href="https://freesewing.dev/guides/translation"
className="btn btn-accent btn-outline" className="btn btn-accent btn-outline"
> >
{t('translation:seeTranslationGuide')} {t('translation:seeTranslationGuide')}
</a> </a>
</p>
</Popout> </Popout>
</div> </div>
</div>
</PageWrapper> </PageWrapper>
) )
} }

View file

@ -5,8 +5,6 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
// Components // Components
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
import { BareLayout as Layout } from 'site/components/layouts/bare.mjs'
import { Breadcrumbs } from 'shared/components/breadcrumbs.mjs'
// Translation namespaces used on this page // Translation namespaces used on this page
const namespaces = [...new Set(pageNs), 'translation', 'locales'] const namespaces = [...new Set(pageNs), 'translation', 'locales']
@ -31,17 +29,7 @@ const TranslationJoinPage = ({ page }) => {
const title = t('translation:joinATranslationTeam') const title = t('translation:joinATranslationTeam')
return ( return (
<PageWrapper {...page} layout={Layout}> <PageWrapper {...page} title={title}>
<div className="max-w-4xl mx-auto p-4 mt-4">
<Breadcrumbs
crumbs={[
{ s: 'translation', t: t('translation:translation') },
{ s: 'translation/join', t: title },
]}
title={title}
/>
<h1>{title}</h1>
<p> <p>
{t('translation:joinIntro')} {t('translation:joinIntro')}
<br /> <br />
@ -50,7 +38,6 @@ const TranslationJoinPage = ({ page }) => {
<DynamicAuthWrapper> <DynamicAuthWrapper>
<DynamicForm /> <DynamicForm />
</DynamicAuthWrapper> </DynamicAuthWrapper>
</div>
</PageWrapper> </PageWrapper>
) )
} }

View file

@ -5,8 +5,6 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
// Components // Components
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
import { BareLayout as Layout } from 'site/components/layouts/bare.mjs'
import { Breadcrumbs } from 'shared/components/breadcrumbs.mjs'
// Translation namespaces used on this page // Translation namespaces used on this page
const namespaces = [...new Set(pageNs), 'translation', 'locales'] const namespaces = [...new Set(pageNs), 'translation', 'locales']
@ -32,17 +30,8 @@ const SuggestLanguagePage = ({ page }) => {
const title = t('translation:suggestLanguage') const title = t('translation:suggestLanguage')
return ( return (
<PageWrapper {...page} layout={Layout}> <PageWrapper {...page} title={title}>
<div className="max-w-4xl mx-auto p-4 mt-4"> <div className="max-w-2xl">
<Breadcrumbs
crumbs={[
{ s: 'translation', t: t('translation:translation') },
{ s: 'translation/join', t: title },
]}
title={title}
/>
<h1>{title}</h1>
<p> <p>
{t('translation:suggestIntro')} {t('translation:suggestIntro')}
<br /> <br />

View file

@ -32,7 +32,7 @@ const DynamicBio = dynamic(
const WelcomeBioPage = ({ page }) => ( const WelcomeBioPage = ({ page }) => (
<PageWrapper {...page} layout={BareLayout} footer={false}> <PageWrapper {...page} layout={BareLayout} footer={false}>
<DynamicAuthWrapper> <DynamicAuthWrapper>
<div className="m-auto max-w-lg text-center lg:mt-24 p-8"> <div className="m-auto max-w-lg text-center lg:mt-4 p-8">
<DynamicBio title welcome /> <DynamicBio title welcome />
</div> </div>
</DynamicAuthWrapper> </DynamicAuthWrapper>

View file

@ -32,7 +32,7 @@ const DynamicCompare = dynamic(
const WelcomeComparePage = ({ page }) => ( const WelcomeComparePage = ({ page }) => (
<PageWrapper {...page} layout={BareLayout} footer={false}> <PageWrapper {...page} layout={BareLayout} footer={false}>
<DynamicAuthWrapper> <DynamicAuthWrapper>
<div className="m-auto max-w-lg text-center lg:mt-24 p-8"> <div className="m-auto max-w-lg text-center lg:mt-4 p-8">
<DynamicCompare title welcome /> <DynamicCompare title welcome />
</div> </div>
</DynamicAuthWrapper> </DynamicAuthWrapper>

View file

@ -37,7 +37,7 @@ const WelcomeImgPage = ({ page }) => {
return ( return (
<PageWrapper {...page} title={t('imgTitle')} layout={BareLayout} footer={false}> <PageWrapper {...page} title={t('imgTitle')} layout={BareLayout} footer={false}>
<DynamicAuthWrapper> <DynamicAuthWrapper>
<div className="m-auto max-w-lg text-center lg:mt-24 p-8"> <div className="m-auto max-w-lg text-center lg:mt-4 p-8">
<DynamicImg title welcome /> <DynamicImg title welcome />
</div> </div>
</DynamicAuthWrapper> </DynamicAuthWrapper>

View file

@ -32,7 +32,7 @@ const DynamicControl = dynamic(
const WelcomePage = ({ page }) => ( const WelcomePage = ({ page }) => (
<PageWrapper {...page} layout={BareLayout} footer={false}> <PageWrapper {...page} layout={BareLayout} footer={false}>
<DynamicAuthWrapper> <DynamicAuthWrapper>
<div className="m-auto max-w-lg text-center lg:mt-24 p-8"> <div className="m-auto max-w-lg text-center lg:mt-4 p-8">
<DynamicControl title welcome /> <DynamicControl title welcome />
</div> </div>
</DynamicAuthWrapper> </DynamicAuthWrapper>

View file

@ -38,7 +38,7 @@ const WelcomeNewsletterPage = ({ page }) => {
return ( return (
<PageWrapper {...page} title={t('title')} layout={BareLayout} footer={false}> <PageWrapper {...page} title={t('title')} layout={BareLayout} footer={false}>
<DynamicAuthWrapper> <DynamicAuthWrapper>
<div className="m-auto max-w-lg text-center lg:mt-24 p-8"> <div className="m-auto max-w-lg text-center lg:mt-4 p-8">
<DynamicNewsletter title welcome /> <DynamicNewsletter title welcome />
</div> </div>
</DynamicAuthWrapper> </DynamicAuthWrapper>

View file

@ -32,7 +32,7 @@ const DynamicImperial = dynamic(
const WelcomeUnitsPage = ({ page }) => ( const WelcomeUnitsPage = ({ page }) => (
<PageWrapper {...page} layout={BareLayout} footer={false}> <PageWrapper {...page} layout={BareLayout} footer={false}>
<DynamicAuthWrapper> <DynamicAuthWrapper>
<div className="m-auto max-w-lg text-center lg:mt-24 p-8"> <div className="m-auto max-w-lg text-center lg:mt-4 p-8">
<DynamicImperial title welcome /> <DynamicImperial title welcome />
</div> </div>
</DynamicAuthWrapper> </DynamicAuthWrapper>

View file

@ -37,7 +37,7 @@ const WelcomeUsernamePage = ({ page }) => {
return ( return (
<PageWrapper {...page} title={t('title')} layout={BareLayout} footer={false}> <PageWrapper {...page} title={t('title')} layout={BareLayout} footer={false}>
<DynamicAuthWrapper> <DynamicAuthWrapper>
<div className="m-auto max-w-lg text-center lg:mt-24 p-8"> <div className="m-auto max-w-lg text-center lg:mt-4 p-8">
<DynamicUsername title welcome /> <DynamicUsername title welcome />
</div> </div>
</DynamicAuthWrapper> </DynamicAuthWrapper>

View file

@ -0,0 +1,36 @@
/*
* The default full-page FreeSewing layout
*/
export const BaseLayout = ({ children = [] }) => (
<div className="flex flex-row items-start mt-8 w-full justify-between 2xl:px-36 xl:px-12 px-4">
{children}
</div>
)
/*
* The left column of the default layout
*/
export const BaseLayoutLeft = ({ children = [] }) => (
<div className="max-w-96 w-1/4 mt-8 hidden lg:block shrink-0">{children}</div>
)
/*
* The right column of the default layout
*/
export const BaseLayoutRight = ({ children = [] }) => (
<div className="max-w-96 w-1/4 mt-8 hidden xl:block">{children}</div>
)
/*
* The main column for prose (text like docs and so on)
*/
export const BaseLayoutProse = ({ children = [] }) => (
<div className="grow w-full m-auto max-w-prose mt-0 mb-8">{children}</div>
)
/*
* The central column for wide content (no max-width)
*/
export const BaseLayoutWide = ({ children = [] }) => (
<div className="grow w-full m-auto mt-0 mb-8 grow">{children}</div>
)

View file

@ -9,11 +9,13 @@ import { Pattern, PatternXray } from '@freesewing/react-components'
// Get code from children // Get code from children
export const asText = (reactEl) => { export const asText = (reactEl) => {
if (reactEl) {
if (typeof reactEl.props.children === 'string') return reactEl.props.children if (typeof reactEl.props.children === 'string') return reactEl.props.children
if (Array.isArray(reactEl.props.children)) { if (Array.isArray(reactEl.props.children)) {
return reactEl.props.children.map((el) => (typeof el === 'string' ? el : asText(el))).join('') return reactEl.props.children.map((el) => (typeof el === 'string' ? el : asText(el))).join('')
} }
if (typeof reactEl.props.children === 'object') return asText(reactEl.props.children) if (typeof reactEl.props.children === 'object') return asText(reactEl.props.children)
}
return '' return ''
} }

View file

@ -17,6 +17,7 @@ import {
FreeSewingIcon, FreeSewingIcon,
HeartIcon, HeartIcon,
BulletIcon, BulletIcon,
PlusIcon,
GitHubIcon, GitHubIcon,
} from 'shared/components/icons.mjs' } from 'shared/components/icons.mjs'
import { Breadcrumbs } from 'shared/components/breadcrumbs.mjs' import { Breadcrumbs } from 'shared/components/breadcrumbs.mjs'
@ -43,6 +44,7 @@ export const icons = {
community: (className = '') => <CommunityIcon className={className} />, community: (className = '') => <CommunityIcon className={className} />,
sets: (className = '') => <MeasieIcon className={className} />, sets: (className = '') => <MeasieIcon className={className} />,
patterns: (className = '') => <PageIcon className={className} />, patterns: (className = '') => <PageIcon className={className} />,
new: (className = '') => <PlusIcon className={className} />,
// Lab // Lab
code: (className = '') => <GitHubIcon className={className} />, code: (className = '') => <GitHubIcon className={className} />,

View file

@ -4,7 +4,7 @@ showcase: Showcase
showcaseAbout: Examples and inspiration from the FreeSewing community using our designs showcaseAbout: Examples and inspiration from the FreeSewing community using our designs
docs: Documentation docs: Documentation
docsAbout: In-depth documenation for all our designs, our website, and much more docsAbout: In-depth documenation for all our designs, our website, and much more
account: Account account: Your Account
accountAbout: Manage your account settings and preferences, and your presonal data accountAbout: Manage your account settings and preferences, and your presonal data
designs: Designs designs: Designs
designsAbout: Our library of designs that you can turn into made-to-measure patterns with a few clicks designsAbout: Our library of designs that you can turn into made-to-measure patterns with a few clicks
@ -24,12 +24,12 @@ referenceAbout: Reference holds technical descriptions of the underlying technol
trainingAbout: Training materials are the most in-depth and strive to teach you a new skill. trainingAbout: Training materials are the most in-depth and strive to teach you a new skill.
mainSections: Main sections mainSections: Main sections
currentSection: Current section currentSection: Current section
sets: Measurements Sets sets: Your Measurements Sets
patterns: Patterns patterns: Your Patterns
curate: Curate curate: Curate
curateSets: Curate Sets curateSets: Curate Sets
code: Code code: Code
patternsAbout: Lists the patterns that you have stored in your FreeSewing account patternsAbout: Lists the patterns that you have stored in your FreeSewing account
setsAbout: Lists the measurements sets that you have stored in your FreeSewing account setsAbout: Lists the measurements sets that you have stored in your FreeSewing account
codeAbout: Here you can find (links to) the Freesewing source code codeAbout: Here you can find (links to) the Freesewing source code
new: New...

View file

@ -22,7 +22,7 @@ import { icons } from 'shared/components/navigation/primary.mjs'
*/ */
const onlyValidChildren = (tree, hIsOk = false) => const onlyValidChildren = (tree, hIsOk = false) =>
orderBy(tree, ['o', 't'], ['asc', 'asc']).filter( orderBy(tree, ['o', 't'], ['asc', 'asc']).filter(
(entry) => typeof entry === 'object' && entry.t !== 'spacer' && !entry.h && !entry.m (entry) => typeof entry === 'object' && entry.t !== 'spacer' && !entry.h
) )
/* /*
@ -39,7 +39,7 @@ const onlyMainSections = (tree) =>
orderBy(tree, ['o', 't'], ['asc', 'asc']).filter((entry) => entry.m) orderBy(tree, ['o', 't'], ['asc', 'asc']).filter((entry) => entry.m)
const SectionLink = ({ skey, tree, slug }) => const SectionLink = ({ skey, tree, slug }) =>
tree[skey].s === slug ? ( tree[skey]._ ? null : tree[skey].s === slug ? ( // Underscore means always hide
<> <>
<span className="pl-2 border-l-2 py-2 block w-full border-secondary bg-opacity-10"> <span className="pl-2 border-l-2 py-2 block w-full border-secondary bg-opacity-10">
{tree[skey].t} {tree[skey].t}
@ -189,7 +189,7 @@ export const NavLinks = ({ slug, siteNav, ignoreControl = false }) => (
{onlyValidChildren(siteNav).map((page, i) => ( {onlyValidChildren(siteNav).map((page, i) => (
<li key={i} className="w-full"> <li key={i} className="w-full">
<MainLink s={page.s} t={page.t} slug={slug} /> <MainLink s={page.s} t={page.t} slug={slug} />
{pageHasChildren(page) && <Section {...{ tree: page, slug }} />} {pageHasChildren(page) && !page.n && <Section {...{ tree: page, slug }} />}
</li> </li>
))} ))}
</ul> </ul>

View file

@ -31,7 +31,7 @@ const NextPage = ({ t, s }) =>
<span></span> <span></span>
) )
export const PrevNext = ({ slug }) => { export const PrevNext = ({ slug, noPrev = false }) => {
// Grab site navigation and slug lookup table from the useNavigatin hook // Grab site navigation and slug lookup table from the useNavigatin hook
const { siteNav, slugLut } = useNavigation() const { siteNav, slugLut } = useNavigation()
@ -42,13 +42,13 @@ export const PrevNext = ({ slug }) => {
const iNext = index === slugLut.length - 1 ? 0 : index + 1 const iNext = index === slugLut.length - 1 ? 0 : index + 1
// Subtract 1 for the previous page, unless it's the first page // Subtract 1 for the previous page, unless it's the first page
const iPrev = index === 0 ? slugLut.length - 1 : index - 1 const iPrev = noPrev ? false : index === 0 ? slugLut.length - 1 : index - 1
// Get the next page from the siteNav object // Get the next page from the siteNav object
const next = get(siteNav, slugLut[iNext].split('/')) const next = get(siteNav, slugLut[iNext].split('/'))
// Get the previous page from the siteNav object // Get the previous page from the siteNav object
const prev = get(siteNav, slugLut[iPrev].split('/')) const prev = noPrev ? false : get(siteNav, slugLut[iPrev].split('/'))
// Return content // Return content
return ( return (
@ -58,7 +58,7 @@ export const PrevNext = ({ slug }) => {
'items-start pt-6 mt-6 border-t-2 border-solid border-r-0 border-l-0 border-b-0' '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} /> {noPrev ? <span /> : <PrevPage t={prev.t} s={prev.s} />}
<NextPage t={next.t} s={next.s} /> <NextPage t={next.t} s={next.s} />
</div> </div>
) )

View file

@ -12,6 +12,7 @@ export const LayoutWrapper = ({
setSearch, setSearch,
noSearch = false, noSearch = false,
header = false, header = false,
footer = true,
}) => { }) => {
const ChosenHeader = header ? header : Header const ChosenHeader = header ? header : Header
@ -70,7 +71,7 @@ export const LayoutWrapper = ({
<div className="fixed top-0 left-0 w-full min-h-screen bg-neutral z-20 bg-opacity-70"></div> <div className="fixed top-0 left-0 w-full min-h-screen bg-neutral z-20 bg-opacity-70"></div>
</> </>
)} )}
<Footer /> {footer && <Footer />}
</div> </div>
) )
} }

View file

@ -30,11 +30,17 @@ export const PageWrapper = (props) => {
// Title is typically set in props.t but check props.title too // Title is typically set in props.t but check props.title too
const pageTitle = props.t ? props.t : props.title ? props.title : null const pageTitle = props.t ? props.t : props.title ? props.title : null
/*
* Slug should come from page props.path not from context
* which won't be available in SSR
*/
const slug = path.join('/')
/* /*
* Contexts * Contexts
*/ */
const { modalContent } = useContext(ModalContext) const { modalContent } = useContext(ModalContext)
const { setNavigation, slug } = useContext(NavigationContext) const { setNavigation } = useContext(NavigationContext)
/* /*
* This forces a re-render upon initial bootstrap of the app * This forces a re-render upon initial bootstrap of the app
@ -73,7 +79,7 @@ export const PageWrapper = (props) => {
//const [search, setSearch] = useState(false) //const [search, setSearch] = useState(false)
// Helper object to pass props down (keeps things DRY) // Helper object to pass props down (keeps things DRY)
const childProps = { footer, header, pageTitle } const childProps = { footer, header, pageTitle, slug }
// Make layout prop into a (uppercase) component // Make layout prop into a (uppercase) component
const Layout = layout const Layout = layout