chore(sites): Updates to headers
This commit is contained in:
parent
1fb689d779
commit
1e379d68ff
13 changed files with 281 additions and 378 deletions
|
@ -31,16 +31,16 @@ const Header = ({ app, setSearch }) => {
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className={`
|
className={`
|
||||||
fixed top-0 left-0
|
fixed bottom-0 lg:bottom-auto lg:top-0 left-0
|
||||||
bg-neutral
|
bg-neutral
|
||||||
w-full
|
w-full
|
||||||
z-30
|
z-30
|
||||||
transition-transform
|
transition-transform
|
||||||
${show ? '' : 'fixed top-0 left-0 -translate-y-20'}
|
${show ? '' : 'fixed bottom-0 lg:top-0 left-0 translate-y-20 lg:-translate-y-20'}
|
||||||
drop-shadow-xl
|
drop-shadow-xl
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<div>
|
<div className="m-auto" style={{ maxWidth: '1800px' }}>
|
||||||
<div className="p-2 flex flex-row gap-2 justify-between text-neutral-content">
|
<div className="p-2 flex flex-row gap-2 justify-between text-neutral-content">
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
<button
|
<button
|
||||||
|
@ -54,16 +54,20 @@ const Header = ({ app, setSearch }) => {
|
||||||
>
|
>
|
||||||
{app.primaryMenu ? <CloseIcon /> : <MenuIcon />}
|
{app.primaryMenu ? <CloseIcon /> : <MenuIcon />}
|
||||||
</button>
|
</button>
|
||||||
|
<div className="hidden lg:block">
|
||||||
|
<WordMark />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row items-center lg:hidden">
|
||||||
<WordMark />
|
<WordMark />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center lg:hidden pr-4">
|
<div className="flex flex-row items-center lg:hidden pr-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSearch(true)}
|
onClick={() => setSearch(true)}
|
||||||
className="btn btn-sm btn-ghost hover:text-secondary-focus"
|
className="btn btn-sm btn-ghost hover:text-secondary-focus"
|
||||||
>
|
>
|
||||||
<SearchIcon />
|
<SearchIcon />
|
||||||
</button>
|
</button>
|
||||||
<ThemePicker app={app} iconOnly />
|
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden lg:flex flex-row items-center">
|
<div className="hidden lg:flex flex-row items-center">
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -7,7 +7,7 @@ const Aside = ({ app, slug, mobileOnly = false, before = [], after = [] }) => (
|
||||||
overflow-y-auto z-20
|
overflow-y-auto z-20
|
||||||
bg-base-100 text-base-content
|
bg-base-100 text-base-content
|
||||||
${app.primaryMenu ? '' : 'translate-x-[-120%]'} transition-transform
|
${app.primaryMenu ? '' : 'translate-x-[-120%]'} transition-transform
|
||||||
px-6 py-20 shrink-0
|
px-6 pb-20 pt-8 shrink-0
|
||||||
|
|
||||||
lg:sticky lg:relative lg:transform-none
|
lg:sticky lg:relative lg:transform-none
|
||||||
lg:justify-center
|
lg:justify-center
|
||||||
|
|
|
@ -2,19 +2,24 @@ import { useState, useRef } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
import algoliasearch from 'algoliasearch/lite';
|
import algoliasearch from 'algoliasearch/lite'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { InstantSearch, connectHits, connectHighlight, connectSearchBox } from 'react-instantsearch-dom'
|
import {
|
||||||
|
InstantSearch,
|
||||||
|
connectHits,
|
||||||
|
connectHighlight,
|
||||||
|
connectSearchBox,
|
||||||
|
} from 'react-instantsearch-dom'
|
||||||
|
import CloseIcon from 'shared/components/icons/close.js'
|
||||||
import config from 'site/algolia.config.mjs'
|
import config from 'site/algolia.config.mjs'
|
||||||
|
|
||||||
const searchClient = algoliasearch(config.algolia.app, config.algolia.key)
|
const searchClient = algoliasearch(config.algolia.app, config.algolia.key)
|
||||||
|
|
||||||
const Hits = props => {
|
const Hits = (props) => {
|
||||||
|
|
||||||
// When we hit enter in the text field, we want to navigate to the result
|
// When we hit enter in the text field, we want to navigate to the result
|
||||||
// which means we must make the result links available in the input somehow
|
// which means we must make the result links available in the input somehow
|
||||||
// so let's stuff them in a data attribute
|
// so let's stuff them in a data attribute
|
||||||
const links = props.hits.map(hit => hit.page)
|
const links = props.hits.map((hit) => hit.page)
|
||||||
props.input.current.setAttribute('data-links', JSON.stringify(links))
|
props.input.current.setAttribute('data-links', JSON.stringify(links))
|
||||||
|
|
||||||
return props.hits.map((hit, index) => (
|
return props.hits.map((hit, index) => (
|
||||||
|
@ -29,24 +34,29 @@ const Hits = props => {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomHits = connectHits(Hits);
|
const CustomHits = connectHits(Hits)
|
||||||
|
|
||||||
const Highlight = ({ highlight, attribute, hit, snippet=false }) => {
|
const Highlight = ({ highlight, attribute, hit, snippet = false }) => {
|
||||||
const parsedHit = highlight({
|
const parsedHit = highlight({
|
||||||
highlightProperty: snippet ? '_snippetResult' : '_highlightResult',
|
highlightProperty: snippet ? '_snippetResult' : '_highlightResult',
|
||||||
attribute,
|
attribute,
|
||||||
hit,
|
hit,
|
||||||
});
|
})
|
||||||
|
|
||||||
return parsedHit.map((part, index) => part.isHighlighted
|
return parsedHit.map((part, index) =>
|
||||||
? <mark className="text-base-content bg-secondary-focus bg-opacity-30" key={index}>{part.value}</mark>
|
part.isHighlighted ? (
|
||||||
: <span key={index}>{part.value}</span>
|
<mark className="text-base-content bg-secondary-focus bg-opacity-30" key={index}>
|
||||||
|
{part.value}
|
||||||
|
</mark>
|
||||||
|
) : (
|
||||||
|
<span key={index}>{part.value}</span>
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomHighlight = connectHighlight(Highlight);
|
const CustomHighlight = connectHighlight(Highlight)
|
||||||
|
|
||||||
const Hit = props => (
|
const Hit = (props) => (
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
px-2 py-1 ounded mt-1
|
px-2 py-1 ounded mt-1
|
||||||
|
@ -54,34 +64,34 @@ const Hit = props => (
|
||||||
sm:rounded
|
sm:rounded
|
||||||
lg:px-4 lg:py-2
|
lg:px-4 lg:py-2
|
||||||
hover:bg-secondary hover:bg-opacity-10 hover:text-base-content
|
hover:bg-secondary hover:bg-opacity-10 hover:text-base-content
|
||||||
${props.index === props.active
|
${props.index === props.active ? 'bg-secondary bg-opacity-30' : 'bg-base-300 bg-opacity-10'}
|
||||||
? 'bg-secondary bg-opacity-30'
|
|
||||||
: 'bg-base-300 bg-opacity-10'
|
|
||||||
}
|
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<Link href={props.hit.page}>
|
<Link href={props.hit.page}>
|
||||||
<a href={props.hit.page} className="flex flex-row justify-between gap-2">
|
<a href={props.hit.page} className="flex flex-row justify-between gap-2">
|
||||||
<span className="text-base sm:text-xl font-bold leading-5">
|
<span className="text-base sm:text-xl font-bold leading-5">
|
||||||
{props.hit._highlightResult?.title
|
{props.hit._highlightResult?.title ? (
|
||||||
? <CustomHighlight hit={props.hit} attribute='title' />
|
<CustomHighlight hit={props.hit} attribute="title" />
|
||||||
: props.hit.title
|
) : (
|
||||||
}
|
props.hit.title
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs pt-0.5 sm:text-base sm:pt-1 font-bold uppercase">
|
||||||
|
{props.hit.page.split('/')[1]}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs pt-0.5 sm:text-base sm:pt-1 font-bold uppercase">{props.hit.page.split('/')[1]}</span>
|
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
{props.hit._snippetResult?.body && (
|
{props.hit._snippetResult?.body && (
|
||||||
<Link href={props.hit.page}>
|
<Link href={props.hit.page}>
|
||||||
<a href={props.hit.page} className="text-sm sm:text-base block py-1">
|
<a href={props.hit.page} className="text-sm sm:text-base block py-1">
|
||||||
<CustomHighlight hit={props.hit} attribute='body' snippet />
|
<CustomHighlight hit={props.hit} attribute="body" snippet />
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{props.hit?._highlightResult?.page && (
|
{props.hit?._highlightResult?.page && (
|
||||||
<Link href={props.hit.page}>
|
<Link href={props.hit.page}>
|
||||||
<a href={props.hit.page} className="text-xs sm:text-sm block opacity-70">
|
<a href={props.hit.page} className="text-xs sm:text-sm block opacity-70">
|
||||||
<CustomHighlight hit={props.hit} attribute='page' />
|
<CustomHighlight hit={props.hit} attribute="page" />
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
@ -91,8 +101,8 @@ const Hit = props => (
|
||||||
// We use this for trapping ctrl-c
|
// We use this for trapping ctrl-c
|
||||||
const handleInputKeydown = (evt, setSearch, setActive, active, router) => {
|
const handleInputKeydown = (evt, setSearch, setActive, active, router) => {
|
||||||
if (evt.key === 'Escape') setSearch(false)
|
if (evt.key === 'Escape') setSearch(false)
|
||||||
if (evt.key === 'ArrowDown') setActive(act => act + 1)
|
if (evt.key === 'ArrowDown') setActive((act) => act + 1)
|
||||||
if (evt.key === 'ArrowUp') setActive(act => act - 1)
|
if (evt.key === 'ArrowUp') setActive((act) => act - 1)
|
||||||
if (evt.key === 'Enter') {
|
if (evt.key === 'Enter') {
|
||||||
// Trigger navigation
|
// Trigger navigation
|
||||||
if (evt.target?.dataset?.links) {
|
if (evt.target?.dataset?.links) {
|
||||||
|
@ -102,8 +112,7 @@ const handleInputKeydown = (evt, setSearch, setActive, active, router) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchBox = props => {
|
const SearchBox = (props) => {
|
||||||
|
|
||||||
const input = useRef(null)
|
const input = useRef(null)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
useHotkeys('ctrl+x', () => {
|
useHotkeys('ctrl+x', () => {
|
||||||
|
@ -123,48 +132,57 @@ const SearchBox = props => {
|
||||||
type="search"
|
type="search"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
value={currentRefinement}
|
value={currentRefinement}
|
||||||
onChange={event => refine(event.currentTarget.value)}
|
onChange={(event) => refine(event.currentTarget.value)}
|
||||||
onKeyDown={(evt) => handleInputKeydown(evt, setSearch, setActive, props.active, router)}
|
onKeyDown={(evt) =>
|
||||||
|
handleInputKeydown(evt, setSearch, setActive, props.active, router)
|
||||||
|
}
|
||||||
className="input lg:input-lg input-bordered input-neutral w-full pr-16"
|
className="input lg:input-lg input-bordered input-neutral w-full pr-16"
|
||||||
placeholder='Type to search'
|
placeholder="Type to search"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className="absolute right-0 top-0 rounded-l-none btn btn-neutral lg:btn-lg"
|
className="absolute right-0 top-0 rounded-l-none btn btn-neutral lg:btn-lg"
|
||||||
onClick={() => props.setSearch(false)}
|
onClick={() => props.setSearch(false)}
|
||||||
>X</button>
|
>
|
||||||
|
X
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<label className="label hidden sm:block">
|
<label className="label hidden sm:block">
|
||||||
<div className="label-text flex flex-row gap-4 justify-between">
|
<div className="label-text flex flex-row gap-4 justify-between">
|
||||||
<div><b> Escape</b> to exit</div>
|
<div>
|
||||||
<div><b> Up</b> or <b>Down</b> to select</div>
|
<b> Escape</b> to exit
|
||||||
<div><b> Enter</b> to navigate</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<b> Up</b> or <b>Down</b> to select
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<b> Enter</b> to navigate
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="overscroll-auto overflow-y-auto mt-2"
|
className="overscroll-auto overflow-y-auto mt-2"
|
||||||
style={{maxHeight: 'calc(100vh - 10rem)'}}
|
style={{ maxHeight: 'calc(100vh - 10rem)' }}
|
||||||
>
|
>
|
||||||
{
|
{input.current && input.current.value.length > 0 && (
|
||||||
input.current
|
<CustomHits hitComponent={Hit} {...props} input={input} />
|
||||||
&& input.current.value.length > 0
|
)}
|
||||||
&& <CustomHits hitComponent={Hit} {...props} input={input}/>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div className={`
|
<div
|
||||||
|
className={`
|
||||||
bg-neutral text-neutral-content
|
bg-neutral text-neutral-content
|
||||||
z-20 w-full mx-auto
|
z-20 w-full mx-auto
|
||||||
lg:bg-base-100 lg:border-base-200
|
lg:bg-base-100 lg:border-base-200
|
||||||
fixed bottom-0 left-0 border-t-2
|
fixed bottom-0 left-0 border-t-2
|
||||||
lg:hidden
|
lg:hidden
|
||||||
`}>
|
`}
|
||||||
<div className='px-4 py-0 flex flex-row w-full lg:py-2'>
|
>
|
||||||
<button
|
<div className="px-4 py-0 flex flex-row w-full lg:py-2">
|
||||||
className={`btn btn-ghost btn-block`}
|
<button className={`btn btn-ghost btn-block`} onClick={() => props.setSearch(false)}>
|
||||||
onClick={() => props.setSearch(false)}
|
<span className="px-2 pt-2 pb-2">
|
||||||
>
|
<CloseIcon />
|
||||||
<span className='px-2 pt-2 pb-2'>Close Search</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -172,17 +190,16 @@ const SearchBox = props => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomSearchBox = connectSearchBox(SearchBox);
|
const CustomSearchBox = connectSearchBox(SearchBox)
|
||||||
|
|
||||||
const Search = props => {
|
|
||||||
|
|
||||||
|
const Search = (props) => {
|
||||||
const [active, setActive] = useState(0)
|
const [active, setActive] = useState(0)
|
||||||
useHotkeys('esc', () => props.setSearch(false))
|
useHotkeys('esc', () => props.setSearch(false))
|
||||||
useHotkeys('up', () => {
|
useHotkeys('up', () => {
|
||||||
if (active) setActive(act => act - 1)
|
if (active) setActive((act) => act - 1)
|
||||||
})
|
})
|
||||||
useHotkeys('down', () => {
|
useHotkeys('down', () => {
|
||||||
setActive(act => act + 1)
|
setActive((act) => act + 1)
|
||||||
})
|
})
|
||||||
useHotkeys('down', () => {
|
useHotkeys('down', () => {
|
||||||
console.log('enter', active)
|
console.log('enter', active)
|
||||||
|
@ -191,12 +208,13 @@ const Search = props => {
|
||||||
const stateProps = {
|
const stateProps = {
|
||||||
setSearch: props.setSearch,
|
setSearch: props.setSearch,
|
||||||
setMenu: props.setMenu,
|
setMenu: props.setMenu,
|
||||||
active, setActive
|
active,
|
||||||
|
setActive,
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InstantSearch indexName={config.algolia.index} searchClient={searchClient}>
|
<InstantSearch indexName={config.algolia.index} searchClient={searchClient}>
|
||||||
<CustomSearchBox {...stateProps}/>
|
<CustomSearchBox {...stateProps} />
|
||||||
</InstantSearch>
|
</InstantSearch>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@ import Logo from 'shared/components/logos/freesewing.js'
|
||||||
import OsiLogo from 'shared/components/logos/osi.js'
|
import OsiLogo from 'shared/components/logos/osi.js'
|
||||||
import CreativeCommonsLogo from 'shared/components/logos/cc.js'
|
import CreativeCommonsLogo from 'shared/components/logos/cc.js'
|
||||||
import CcByLogo from 'shared/components/logos/cc-by.js'
|
import CcByLogo from 'shared/components/logos/cc-by.js'
|
||||||
import { useTranslation } from 'next-i18next'
|
import Ribbon from 'shared/components/ribbon.js'
|
||||||
import DocsLink from 'shared/components/docs-link'
|
|
||||||
import PinkedRibbon from 'shared/components/pinked-ribbon.js'
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
import { WordMark } from 'shared/components/wordmark.js'
|
||||||
|
|
||||||
|
import HelpIcon from 'shared/components/icons/help.js'
|
||||||
import DiscordIcon from 'shared/components/icons/discord.js'
|
import DiscordIcon from 'shared/components/icons/discord.js'
|
||||||
import FacebookIcon from 'shared/components/icons/facebook.js'
|
import FacebookIcon from 'shared/components/icons/facebook.js'
|
||||||
import GithubIcon from 'shared/components/icons/github.js'
|
import GithubIcon from 'shared/components/icons/github.js'
|
||||||
|
@ -15,181 +15,109 @@ import RedditIcon from 'shared/components/icons/reddit.js'
|
||||||
import TwitterIcon from 'shared/components/icons/twitter.js'
|
import TwitterIcon from 'shared/components/icons/twitter.js'
|
||||||
|
|
||||||
// Classes
|
// Classes
|
||||||
const link = "text-secondary font-bold hover:pointer hover:underline px-1"
|
const link = 'text-secondary font-bold hover:pointer hover:underline px-1'
|
||||||
const accent = "text-accent font-bold text-lg px-1 block sm:inline"
|
const accent = 'text-accent font-bold text-lg px-1 block sm:inline'
|
||||||
const freesewing = "px-1 text-lg font-bold block sm:inline"
|
const freesewing = 'px-1 text-lg font-bold block sm:inline'
|
||||||
|
|
||||||
// Keep these translations in the component because they're only used here
|
// Keep these translations in the component because they're only used here
|
||||||
const translations = {
|
const translations = {
|
||||||
cc: {
|
cc: (
|
||||||
en: <span>
|
<span>
|
||||||
Content on FreeSewing.org is available under <a
|
Content on FreeSewing.org is available under{' '}
|
||||||
className={link} href="https://creativecommons.org/licenses/by/4.0/"
|
<a className={link} href="https://creativecommons.org/licenses/by/4.0/">
|
||||||
>a Creative Commons license</a>
|
a Creative Commons license
|
||||||
</span>,
|
</a>
|
||||||
de: <span>
|
</span>
|
||||||
Inhalte auf FreeSewing.org sind unter einer <a
|
),
|
||||||
className={link} href="https://creativecommons.org/licenses/by/4.0/deed.de"
|
mit: (
|
||||||
>Creative
|
<span>
|
||||||
Commons-Lizenz</a> verfügbar
|
The FreeSewing source code is{' '}
|
||||||
</span>,
|
<a href="https://github.com/freesewing/freesewing" className={link}>
|
||||||
es: <span>
|
available on Github
|
||||||
El contenido de FreeSewing.org está disponible bajo <a
|
</a>{' '}
|
||||||
className={link} href="https://creativecommons.org/licenses/by/4.0/deed.es"
|
under{' '}
|
||||||
>una licencia Creative Commons</a>
|
<a href="https://opensource.org/licenses/MIT" className={link}>
|
||||||
</span>,
|
the MIT license
|
||||||
fr: <span>
|
</a>
|
||||||
Le contenu de FreeSewing.org est sous
|
</span>
|
||||||
<a className={link} href="https://creativecommons.org/licenses/by/4.0/deed.fr"
|
),
|
||||||
>licence Creative Commons</a>
|
sponsors: (
|
||||||
</span>,
|
<>
|
||||||
nl: <span>
|
<span className={freesewing}>FreeSewing</span> is sponsored by these{' '}
|
||||||
De inhoud op FreeSewing.org is beschikbaar onder
|
<span className={accent}>awesome companies</span>
|
||||||
<a className={link} href="https://creativecommons.org/licenses/by/4.0/deed.nl"
|
</>
|
||||||
>een Creative Commons licentie</a>
|
),
|
||||||
</span>,
|
|
||||||
},
|
|
||||||
mit: {
|
|
||||||
en: <span>
|
|
||||||
The FreeSewing source code is <a
|
|
||||||
href="https://github.com/freesewing/freesewing" className={link}
|
|
||||||
>available on Github</a> under <a href="https://opensource.org/licenses/MIT"
|
|
||||||
className={link}>the MIT license</a>
|
|
||||||
</span>,
|
|
||||||
de: <span>
|
|
||||||
Der FreeSewing-Quellcode ist <a
|
|
||||||
href="https://github.com/freesewing/freesewing" className={link}
|
|
||||||
>auf Github verfügbar</a> unter <a href="https://opensource.org/licenses/MIT"
|
|
||||||
className={link}>der MIT-Lizenz</a>
|
|
||||||
</span>,
|
|
||||||
es: <span>
|
|
||||||
El código fuente de FreeSewing está <a
|
|
||||||
href="https://github.com/freesewing/freesewing" className={link}
|
|
||||||
>disponible en Github</a> bajo <a href="https://opensource.org/licenses/MIT"
|
|
||||||
className={link}>la licencia MIT</a>
|
|
||||||
</span>,
|
|
||||||
fr: <span>
|
|
||||||
Le code source de FreeSewing est <a
|
|
||||||
href="https://github.com/freesewing/freesewing" className={link}
|
|
||||||
>disponible sur Github</a> sous <a href="https://opensource.org/licenses/MIT"
|
|
||||||
className={link}>la licence MIT</a>
|
|
||||||
</span>,
|
|
||||||
nl: <span>
|
|
||||||
De FreeSewing broncode is <a
|
|
||||||
href="https://github.com/freesewing/freesewing" className={link}
|
|
||||||
>beschikbaar op Github</a> onder <a href="https://opensource.org/licenses/MIT"
|
|
||||||
className={link}>de MIT licentie</a>
|
|
||||||
</span>,
|
|
||||||
},
|
|
||||||
sponsors: {
|
|
||||||
en: <>
|
|
||||||
<span className={freesewing}>FreeSewing</span> is sponsored by these <span
|
|
||||||
className={accent}>awesome companies</span>
|
|
||||||
</>,
|
|
||||||
de: <>
|
|
||||||
<span className={freesewing}>FreeSewing</span> wird von diesen <span
|
|
||||||
className={accent}>großartigen Unternehmen</span> gesponsert
|
|
||||||
</>,
|
|
||||||
es: <>
|
|
||||||
<span className={freesewing}>FreeSewing</span> está patrocinado por estas <span
|
|
||||||
className={accent}>increíbles empresas</span>
|
|
||||||
</>,
|
|
||||||
fr: <>
|
|
||||||
<span className={freesewing}>FreeSewing</span> est sponsorisé par ces <span
|
|
||||||
className={accent}>entreprises géniales</span>
|
|
||||||
</>,
|
|
||||||
nl: <>
|
|
||||||
<span className={freesewing}>FreeSewing</span> wordt gesponsord door deze <span
|
|
||||||
className={accent}>geweldige bedrijven</span>
|
|
||||||
</>,
|
|
||||||
},
|
|
||||||
msf: {
|
|
||||||
en: <>
|
|
||||||
All <span className={freesewing}>FreeSewing</span> revenue goes to <span
|
|
||||||
className={accent}>Doctors Without Borders</span>
|
|
||||||
</>,
|
|
||||||
de: <>
|
|
||||||
Alle <span className={freesewing}>FreeSewing</span>-Einnahmen gehen an <span
|
|
||||||
className={accent}>Ärzte ohne Grenzen</span>
|
|
||||||
</>,
|
|
||||||
es: <>
|
|
||||||
Todos los ingresos de <span className={freesewing}>FreeSewing</span> van a <span
|
|
||||||
className={accent}>Médicos sin Fronteras</span>
|
|
||||||
</>,
|
|
||||||
fr: <>
|
|
||||||
Tous les revenus de <span className={freesewing}>FreeSewing</span> vont à <span
|
|
||||||
className={accent}>Médecins Sans Frontières</span>
|
|
||||||
</>,
|
|
||||||
nl: <>
|
|
||||||
Alle inkomsten van <span className={freesewing}>FreeSewing</span> gaan naar <span
|
|
||||||
className={accent}>Artsen Zonder Grenzen</span>
|
|
||||||
</>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const icon = { className: "w-20 h-20" }
|
const icon = { className: 'w-8 lg:w-12 h-8 lg:h-12' }
|
||||||
const social = {
|
const social = {
|
||||||
Discord: {
|
Discord: {
|
||||||
icon: <DiscordIcon {...icon}/>,
|
icon: <DiscordIcon {...icon} />,
|
||||||
href: 'https://discord.freesewing.org/'
|
href: 'https://discord.freesewing.org/',
|
||||||
},
|
},
|
||||||
Instagram: {
|
Instagram: {
|
||||||
icon: <InstagramIcon {...icon}/>,
|
icon: <InstagramIcon {...icon} />,
|
||||||
href: 'https://instagram.com/freesewing_org'
|
href: 'https://instagram.com/freesewing_org',
|
||||||
},
|
},
|
||||||
Facebook: {
|
Facebook: {
|
||||||
icon: <FacebookIcon {...icon}/>,
|
icon: <FacebookIcon {...icon} />,
|
||||||
href: 'https://www.facebook.com/groups/627769821272714/'
|
href: 'https://www.facebook.com/groups/627769821272714/',
|
||||||
},
|
},
|
||||||
Github: {
|
Github: {
|
||||||
icon: <GithubIcon {...icon} />,
|
icon: <GithubIcon {...icon} />,
|
||||||
href: 'https://github.com/freesewing'
|
href: 'https://github.com/freesewing',
|
||||||
},
|
},
|
||||||
Reddit: {
|
Reddit: {
|
||||||
icon: <RedditIcon {...icon} />,
|
icon: <RedditIcon {...icon} />,
|
||||||
href: 'https://www.reddit.com/r/freesewing/'
|
href: 'https://www.reddit.com/r/freesewing/',
|
||||||
},
|
},
|
||||||
Twitter: {
|
Twitter: {
|
||||||
icon: <TwitterIcon {...icon} />,
|
icon: <TwitterIcon {...icon} />,
|
||||||
href: 'https://twitter.com/freesewing_org'
|
href: 'https://twitter.com/freesewing_org',
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Footer = ({ app }) => {
|
||||||
const Footer = ({ app, full=false }) => {
|
|
||||||
const { t } = useTranslation(['common', 'patrons'])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="bg-neutral">
|
<footer className="bg-neutral">
|
||||||
<PinkedRibbon loading={app.loading} theme={app.theme} />
|
<Ribbon loading={app.loading} theme={app.theme} />
|
||||||
<div className="px-8 py-20 2xl:py-40 flex flex-row gap-8 flex-wrap 2xl:flex-nowrap justify-around text-neutral-content">
|
<div className="grid grid-cols-1 lg:grid-cols-4 py-12 2xl:py-20 text-neutral-content px-4">
|
||||||
|
|
||||||
{/* First col - CC & MIT */}
|
{/* First col - CC & MIT */}
|
||||||
<div className="max-w-md mb-20 order-1 mt-20 2xl:mt-0">
|
<div className="mb-20 order-1 mt-20 2xl:mt-0 2xl:mb-0">
|
||||||
<div className="max-w-md m-auto">
|
<div className="max-w-md m-auto">
|
||||||
<div><CreativeCommonsLogo className="w-64 sm:w-80 m-auto"/></div>
|
<div>
|
||||||
|
<CreativeCommonsLogo className="w-64 m-auto" />
|
||||||
|
</div>
|
||||||
<div className="flex flex-row gap-2 justify-center items-center mt-8">
|
<div className="flex flex-row gap-2 justify-center items-center mt-8">
|
||||||
<CcByLogo className="w-12 sm:w-16"/>
|
<CcByLogo className="w-8 lg:w-12" />
|
||||||
<p className="text-neutral-content text-right basis-3/4">
|
<p className="text-neutral-content text-right basis-4/5 lg:basis-3/4 leading-5">
|
||||||
{translations.cc[app.locale]}
|
{translations.cc}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row gap-2 justify-center items-center mt-4">
|
<div className="flex flex-row gap-2 justify-center items-center mt-4">
|
||||||
<OsiLogo className="w-12 sm:w-16"/>
|
<OsiLogo className="w-8 lg:w-12" />
|
||||||
<p className="text-neutral-content text-right basis-3/4">
|
<p className="text-neutral-content text-right basis-4/5 lg:basis-3/4 leading-5">
|
||||||
{translations.mit[app.locale]}
|
{translations.mit}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Second col - Social & Sponsors */}
|
{/* Second col - Social & Sponsors */}
|
||||||
<div className="w-full 2xl:w-fit -order-2 2xl:order-2">
|
<div className="lg:col-span-2 -order-2 2xl:order-2 px-4 lg:px-0">
|
||||||
{/* Social icons */}
|
{/* Social icons */}
|
||||||
<div className="w-full sm:w-auto flex flex-row flex-wrap gap-8 items-center justify-center">
|
<div className="w-full sm:w-auto flex flex-row flex-wrap gap-4 lg:gap-8 items-center justify-center">
|
||||||
{Object.keys(social).map(item => (
|
<Link href="/contact">
|
||||||
|
<a
|
||||||
|
className="hover:text-secondary hover:-mt-2 transition-all"
|
||||||
|
title="Contact information"
|
||||||
|
>
|
||||||
|
<HelpIcon {...icon} />
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
{Object.keys(social).map((item) => (
|
||||||
<Link key={item} href={social[item].href}>
|
<Link key={item} href={social[item].href}>
|
||||||
<a className="hover:text-secondary-focus hover:-mt-2 transition-all" title={item}>
|
<a className="hover:text-secondary hover:-mt-2 transition-all" title={item}>
|
||||||
{social[item].icon}
|
{social[item].icon}
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -197,49 +125,55 @@ const Footer = ({ app, full=false }) => {
|
||||||
</div>
|
</div>
|
||||||
{/* Sponsors */}
|
{/* Sponsors */}
|
||||||
<div className="border rounded-xl p-8 border-dashed border-base-100/25 mt-20">
|
<div className="border rounded-xl p-8 border-dashed border-base-100/25 mt-20">
|
||||||
<p className="text-center text-neutral-content">
|
<p className="text-center text-neutral-content leading-5">
|
||||||
{translations.sponsors[app.locale]}
|
{translations.sponsors}
|
||||||
<br />
|
<br />
|
||||||
</p>
|
</p>
|
||||||
<div className="p-4 flex flex-row gap-4 xl:gap-8 flex-wrap justify-center items-center text-neutral-content shrink">
|
<div className="py-4 flex flex-row gap-8 flex-wrap 2xl:flex-nowrap justify-around text-neutral-content">
|
||||||
<a title="Search powered by Algolia" href="https://www.algolia.com/">
|
<a title="Search powered by Algolia" href="https://www.algolia.com/">
|
||||||
<img src="/brands/algolia.svg" className="w-64" alt="Search powered by Algolia"/>
|
<img
|
||||||
</a>
|
src="/brands/algolia.svg"
|
||||||
<a title="Error handling by Bugsnag" href="https://www.bugsnag.com/">
|
className="h-12 aspect-auto"
|
||||||
<img src="/brands/bugsnag.svg" className="h-32" alt="Error handling by bugsnag" />
|
alt="Search powered by Algolia"
|
||||||
</a>
|
/>
|
||||||
<a title="Translation powered by Crowdin" href="https://www.crowdin.com/">
|
</a>
|
||||||
<img src="/brands/crowdin.svg" className="w-64" alt="Translation powered by Crowdin" />
|
<a title="Error handling by Bugsnag" href="https://www.bugsnag.com/">
|
||||||
</a>
|
<img src="/brands/bugsnag.svg" className="h-12" alt="Error handling by bugsnag" />
|
||||||
<a title="Builds & hosting by Vercel" href="https://www.vercel.com/?utm_source=freesewing&utm_campaign=oss">
|
</a>
|
||||||
<img src="/brands/vercel.svg" className="w-64 shadow-lg shadow-white/20" alt="Builds & Hosting by Vercel" />
|
<a title="Translation powered by Crowdin" href="https://www.crowdin.com/">
|
||||||
</a>
|
<img
|
||||||
|
src="/brands/crowdin.svg"
|
||||||
|
alt="Translation powered by Crowdin"
|
||||||
|
className="h-12"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
title="Builds & hosting by Vercel"
|
||||||
|
href="https://www.vercel.com/?utm_source=freesewing&utm_campaign=oss"
|
||||||
|
>
|
||||||
|
<img src="/brands/vercel.svg" alt="Builds & Hosting by Vercel" className="h-12" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<p className="text-center text-neutral-content mt-4 border rounded-xl p-8 border-dashed border-base-100/25">
|
|
||||||
{translations.msf[app.locale]}
|
|
||||||
<br />
|
|
||||||
[ <DocsLink slug="docs/various/pledge" /> ]
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Col 3 - Logo & Slogan */}
|
{/* Col 3 - Logo & Slogan */}
|
||||||
<div className="w-full 4xl:w-auto xl:max-w-md mb-20 text-center order-3 mt-20 2xl:mt-0">
|
<div className="w-full 4xl:w-auto xl:max-w-md mb-8 text-center order-3 mt-0 lg:mt-20 2xl:mt-0 2xl:mb-0">
|
||||||
<div className="max-w-md m-auto">
|
<div className="max-w-md m-auto">
|
||||||
<Logo stroke="none" size={164} className="w-40 lg:w-64 m-auto m-auto" />
|
<Logo stroke="none" size={164} className="w-40 lg:w-64 m-auto m-auto" />
|
||||||
<h5 className="text-neutral-content lg:text-3xl mt-4">FreeSewing</h5>
|
<h5 className="lg:text-3xl mt-4">
|
||||||
<p className="bold text-neutral-content text-normal lg:text-xl">
|
<WordMark />
|
||||||
{t('sloganCome')}
|
</h5>
|
||||||
|
<p className="bold text-neutral-content text-normal lg:text-xl leading-5">
|
||||||
|
Come for the sewing patterns
|
||||||
<br />
|
<br />
|
||||||
{t('sloganStay')}
|
Stay for the community
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Footer
|
export default Footer
|
||||||
|
|
||||||
|
|
|
@ -1,36 +1,24 @@
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import FreeSewingIcon from 'shared/components/icons/freesewing.js'
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import ThemePicker from 'shared/components/theme-picker.js'
|
import ThemePicker from 'shared/components/theme-picker.js'
|
||||||
import LocalePicker from 'shared/components/locale-picker.js'
|
import LocalePicker from 'shared/components/locale-picker.js'
|
||||||
import CloseIcon from 'shared/components/icons/close.js'
|
import CloseIcon from 'shared/components/icons/close.js'
|
||||||
import MenuIcon from 'shared/components/icons/menu.js'
|
import MenuIcon from 'shared/components/icons/menu.js'
|
||||||
import SearchIcon from 'shared/components/icons/search.js'
|
import SearchIcon from 'shared/components/icons/search.js'
|
||||||
|
import Ribbon from 'shared/components/ribbon.js'
|
||||||
const Right = props => (
|
import { WordMark } from 'shared/components/wordmark.js'
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
const Left = props => (
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
|
|
||||||
const Header = ({ app, setSearch }) => {
|
const Header = ({ app, setSearch }) => {
|
||||||
|
|
||||||
const [prevScrollPos, setPrevScrollPos] = useState(0)
|
const [prevScrollPos, setPrevScrollPos] = useState(0)
|
||||||
const [show, setShow] = useState(true)
|
const [show, setShow] = useState(true)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
const curScrollPos = (typeof window !== 'undefined') ? window.pageYOffset : 0
|
const curScrollPos = typeof window !== 'undefined' ? window.pageYOffset : 0
|
||||||
if (curScrollPos >= prevScrollPos) {
|
if (curScrollPos >= prevScrollPos) {
|
||||||
if (show && curScrollPos > 20) setShow(false)
|
if (show && curScrollPos > 20) setShow(false)
|
||||||
}
|
} else setShow(true)
|
||||||
else setShow(true)
|
|
||||||
setPrevScrollPos(curScrollPos)
|
setPrevScrollPos(curScrollPos)
|
||||||
}
|
}
|
||||||
window.addEventListener('scroll', handleScroll)
|
window.addEventListener('scroll', handleScroll)
|
||||||
|
@ -38,90 +26,67 @@ const Header = ({ app, setSearch }) => {
|
||||||
}
|
}
|
||||||
}, [prevScrollPos, show])
|
}, [prevScrollPos, show])
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className={`
|
<header
|
||||||
fixed top-0 left-0
|
className={`
|
||||||
bg-neutral
|
fixed bottom-0 lg:bottom-auto lg:top-0 left-0
|
||||||
w-full
|
bg-neutral
|
||||||
z-30
|
w-full
|
||||||
transition-transform
|
z-30
|
||||||
${show ? '': 'fixed top-0 left-0 -translate-y-20'}
|
transition-transform
|
||||||
drop-shadow-xl
|
${show ? '' : 'fixed bottom-0 lg:top-0 left-0 translate-y-20 lg:-translate-y-20'}
|
||||||
${app.loading ? "theme-gradient loading" : ""}
|
drop-shadow-xl
|
||||||
`}>
|
`}
|
||||||
<div className="max-w-6xl m-auto">
|
>
|
||||||
<div className="p-2 flex flex-row gap-2 justify-between text-neutral-content">
|
<div className="m-auto" style={{ maxWidth: '1800px' }}>
|
||||||
|
<div className="p-2 flex flex-row gap-2 justify-between text-neutral-content items-center">
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
<button
|
<button
|
||||||
className={`
|
className={`
|
||||||
btn btn-sm
|
btn btn-sm
|
||||||
text-neutral-content bg-transparent
|
text-neutral-content bg-transparent
|
||||||
border border-transparent
|
lg:hidden
|
||||||
hover:bg-transparent hover:border-base-100
|
|
||||||
md:hidden
|
|
||||||
h-12
|
h-12
|
||||||
`}
|
`}
|
||||||
onClick={app.togglePrimaryMenu}>
|
onClick={app.togglePrimaryMenu}
|
||||||
{app.primaryMenu
|
>
|
||||||
? (
|
{app.primaryMenu ? <CloseIcon /> : <MenuIcon />}
|
||||||
<>
|
|
||||||
<CloseIcon />
|
|
||||||
<span className="opacity-50 pl-2 flex flex-row items-center gap-1">
|
|
||||||
<Left />
|
|
||||||
swipe
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<MenuIcon />
|
|
||||||
<span className="opacity-50 pl-2 flex flex-row items-center gap-1">
|
|
||||||
<Right />
|
|
||||||
swipe
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</button>
|
</button>
|
||||||
<div className="flex flex-row items-center md:hidden pr-4">
|
<div className="hidden lg:block lg:pl-2">
|
||||||
<button onClick={() => setSearch(true)} className="btn btn-sm">
|
<WordMark />
|
||||||
<SearchIcon />
|
|
||||||
</button>
|
|
||||||
<ThemePicker app={app} iconOnly />
|
|
||||||
<LocalePicker app={app} iconOnly />
|
|
||||||
</div>
|
|
||||||
<button className={`
|
|
||||||
btn btn-sm h-12
|
|
||||||
hidden md:flex
|
|
||||||
flex-row gap-1 mr-4 w-64 px-2
|
|
||||||
bg-base-100 text-base-content
|
|
||||||
hover:bg-base-100 hover:text-base-content
|
|
||||||
justify-between
|
|
||||||
`} onClick={() => setSearch(true)}>
|
|
||||||
<div className="flex flex-row items-center gap-2">
|
|
||||||
<SearchIcon />
|
|
||||||
<span className="normal-case font-normal">Quick Search...</span>
|
|
||||||
</div>
|
|
||||||
<span className="normal-case">Ctrl K</span>
|
|
||||||
</button>
|
|
||||||
<div className="hidden md:flex md:flex-row gap-2">
|
|
||||||
<Link href="/">
|
|
||||||
<a className="flex flex-column items-center">
|
|
||||||
<FreeSewingIcon className="h-10 w-10"/>
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
<Link href="/">
|
|
||||||
<a role="button" className="btn btn-link btn-sm text-neutral-content h-12 font-normal lowercase text-2xl">
|
|
||||||
<span className="font-black px-1 normal-case">FreeSewing</span>.org
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className="hidden md:flex flex-row items-center">
|
|
||||||
<ThemePicker app={app} />
|
|
||||||
<LocalePicker app={app} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-row items-center lg:hidden">
|
||||||
|
<WordMark />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row items-center lg:hidden pr-2">
|
||||||
|
<button onClick={() => setSearch(true)} className="btn btn-sm">
|
||||||
|
<SearchIcon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="hidden lg:flex lg:flex-row gap-2 grow"></div>
|
||||||
|
<div className="hidden lg:flex flex-row items-center">
|
||||||
|
<ThemePicker app={app} />
|
||||||
|
<LocalePicker app={app} />
|
||||||
|
<button
|
||||||
|
className={`
|
||||||
|
btn btn-ghost btn-sm h-12
|
||||||
|
hidden lg:flex
|
||||||
|
flex-row gap-4
|
||||||
|
justify-between
|
||||||
|
hover:text-secondary-focus
|
||||||
|
hover:bg-transparent
|
||||||
|
`}
|
||||||
|
onClick={() => setSearch(true)}
|
||||||
|
>
|
||||||
|
<SearchIcon />
|
||||||
|
<span className="normal-case text-base font-medium">Ctrl K</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</div>
|
||||||
|
<Ribbon loading={app.loading} theme={app.theme} />
|
||||||
|
</header>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,18 +3,15 @@ import { useRouter } from 'next/router'
|
||||||
import Aside from 'shared/components/navigation/aside'
|
import Aside from 'shared/components/navigation/aside'
|
||||||
import ThemePicker from 'shared/components/theme-picker'
|
import ThemePicker from 'shared/components/theme-picker'
|
||||||
import Breadcrumbs from 'shared/components/breadcrumbs.js'
|
import Breadcrumbs from 'shared/components/breadcrumbs.js'
|
||||||
import { getCrumbs } from 'shared/utils.js'
|
|
||||||
|
|
||||||
const DefaultLayout = ({ app, title=false, crumbs=false, children=[] }) => {
|
const DefaultLayout = ({ app, title = false, crumbs = false, children = [] }) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const slug = router.asPath.slice(1)
|
const slug = router.asPath.slice(1)
|
||||||
const breadcrumbs = crumbs
|
const breadcrumbs = crumbs ? crumbs : null //FIXME getCrumbs(app, slug, title)
|
||||||
? crumbs
|
|
||||||
: getCrumbs(app, slug, title)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="m-auto flex flex-row justify-center">
|
<div className="m-auto flex flex-row justify-center">
|
||||||
<Aside app={app} slug={slug} before={<ThemePicker app={app} className="block sm:hidden"/>}/>
|
<Aside app={app} slug={slug} before={<ThemePicker app={app} className="block sm:hidden" />} />
|
||||||
<section className="py-28 md:py-36 max-w-7xl px-6 xl:pl-8 2xl:pl-16">
|
<section className="py-28 md:py-36 max-w-7xl px-6 xl:pl-8 2xl:pl-16">
|
||||||
<div>
|
<div>
|
||||||
{title && (
|
{title && (
|
||||||
|
|
|
@ -4,9 +4,7 @@ const config = {
|
||||||
index: 'canary_freesewing.dev',
|
index: 'canary_freesewing.dev',
|
||||||
key: '589c7a7e4d9c95a4f12868581259bf3a', // Search-only API key
|
key: '589c7a7e4d9c95a4f12868581259bf3a', // Search-only API key
|
||||||
},
|
},
|
||||||
strapi: 'https://posts.freesewing.org',
|
|
||||||
monorepo: 'https://github.com/freesewing/freesewing',
|
monorepo: 'https://github.com/freesewing/freesewing',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default config
|
export default config
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ import path from 'path'
|
||||||
import { readdirSync } from 'fs'
|
import { readdirSync } from 'fs'
|
||||||
import i18nConfig from './next-i18next.config.js'
|
import i18nConfig from './next-i18next.config.js'
|
||||||
|
|
||||||
const getDirectories = source =>
|
const getDirectories = (source) =>
|
||||||
readdirSync(source, { withFileTypes: true })
|
readdirSync(source, { withFileTypes: true })
|
||||||
.filter(dirent => dirent.isDirectory())
|
.filter((dirent) => dirent.isDirectory())
|
||||||
.map(dirent => dirent.name)
|
.map((dirent) => dirent.name)
|
||||||
|
|
||||||
const pkgs = getDirectories(path.resolve(`../`))
|
const pkgs = getDirectories(path.resolve(`../`))
|
||||||
|
|
||||||
|
@ -14,26 +14,21 @@ const config = {
|
||||||
externalDir: true,
|
externalDir: true,
|
||||||
},
|
},
|
||||||
i18n: i18nConfig.i18n,
|
i18n: i18nConfig.i18n,
|
||||||
pageExtensions: [ 'js', 'mjs' ],
|
pageExtensions: ['js', 'mjs'],
|
||||||
webpack: (config, options) => {
|
webpack: (config, options) => {
|
||||||
|
|
||||||
// Aliases
|
// Aliases
|
||||||
config.resolve.alias.shared = path.resolve('../shared/')
|
config.resolve.alias.shared = path.resolve('../shared/')
|
||||||
config.resolve.alias.site = path.resolve(`.`)
|
config.resolve.alias.site = path.resolve(`.`)
|
||||||
config.resolve.alias.pkgs = path.resolve(`../../packages/`)
|
config.resolve.alias.pkgs = path.resolve(`../../packages/`)
|
||||||
|
|
||||||
// Suppress warnings about importing version from package.json
|
|
||||||
// We'll deal with it in v3 of FreeSewing
|
|
||||||
config.ignoreWarnings = [
|
|
||||||
/only default export is available soon/
|
|
||||||
]
|
|
||||||
|
|
||||||
// This forces webpack to load the code from source, rather than compiled bundle
|
// This forces webpack to load the code from source, rather than compiled bundle
|
||||||
for (const pkg of pkgs) {
|
for (const pkg of pkgs) {
|
||||||
config.resolve.alias[`@freesewing/${pkg}$`] = path.resolve(`../../packages/${pkg}/src/index.js`)
|
config.resolve.alias[`@freesewing/${pkg}$`] = path.resolve(
|
||||||
|
`../../packages/${pkg}/src/index.js`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
export default config
|
export default config
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
// Can't seem to make this work as ESM
|
// Can't seem to make this work as ESM
|
||||||
const config = require('../freesewing.shared/config/postcss.config.js')
|
const config = require('../shared/config/postcss.config.js')
|
||||||
|
|
||||||
module.exports = config
|
module.exports = config
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Can't seem to make this work as ESM
|
// Can't seem to make this work as ESM
|
||||||
const config = require('../freesewing.shared/config/tailwind.config.js')
|
const config = require('../shared/config/tailwind.config.js')
|
||||||
|
|
||||||
module.exports = config
|
module.exports = config
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Popover, Transition } from '@headlessui/react'
|
||||||
import DownIcon from 'shared/components/icons/down'
|
import DownIcon from 'shared/components/icons/down'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
|
||||||
const LocalePicker = ({ app, iconOnly = false }) => {
|
const LocalePicker = ({ app, iconOnly = false, bottom = false }) => {
|
||||||
const { t } = useTranslation(['locales'])
|
const { t } = useTranslation(['locales'])
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
@ -23,11 +23,13 @@ const LocalePicker = ({ app, iconOnly = false }) => {
|
||||||
{() => (
|
{() => (
|
||||||
<>
|
<>
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
className={`group inline-flex items-center px-3 py-2 text-base font-medium text-neural-content hover:bg-neutral-focus rounded-lg px-4`}
|
className={`group border-0 inline-flex items-center px-3 py-2 text-base font-medium text-neural-content rounded-lg px-4 hover:text-secondary-focus`}
|
||||||
>
|
>
|
||||||
<LocaleIcon />
|
<LocaleIcon />
|
||||||
<span className="ml-4 font-bold text-lg capitalize">{t(`common:language`)}</span>
|
{!iconOnly && (
|
||||||
<DownIcon className={`ml-2 h-5 w-5`} aria-hidden="true" />
|
<span className="ml-4 font-medium capitalize">{t(`common:language`)}</span>
|
||||||
|
)}
|
||||||
|
<DownIcon className={`ml-2 h-5 w-5 ${bottom ? 'rotate-180' : ''}`} aria-hidden="true" />
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
<Transition
|
<Transition
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
|
@ -38,17 +40,16 @@ const LocalePicker = ({ app, iconOnly = false }) => {
|
||||||
leaveFrom="opacity-100 translate-y-0"
|
leaveFrom="opacity-100 translate-y-0"
|
||||||
leaveTo="opacity-0 translate-y-1"
|
leaveTo="opacity-0 translate-y-1"
|
||||||
>
|
>
|
||||||
<Popover.Panel className="absolute z-10 mt-3 w-screen max-w-sm transform px-4 sm:px-0 lg:max-w-xl right-0">
|
<Popover.Panel
|
||||||
|
className={`absolute z-10 mb-3 w-64 transform px-4 sm:px-0 lg:max-w-xl right-0 ${
|
||||||
|
iconOnly ? 'translate-x-4' : ''
|
||||||
|
} ${bottom ? 'bottom-10' : 'top-12'}`}
|
||||||
|
>
|
||||||
<div className="overflow-hidden rounded-lg shadow-lg">
|
<div className="overflow-hidden rounded-lg shadow-lg">
|
||||||
<div className="relative grid gap-8 bg-base-100 p-7 lg:grid-cols-1">
|
<div className="relative grid gap-2 bg-base-100 p-4 grid-cols-1">
|
||||||
{router.locales.map((locale) => (
|
{router.locales.map((locale) => (
|
||||||
<Link href={`${locale}/${router.asPath}`} key={locale}>
|
<Link href={`${locale}/${router.asPath}`} key={locale}>
|
||||||
<a className="-m-3 flex rounded-lg p-2 transition duration-150 ease-in-out-50 hover:translate-x-2 hover:cursor-pointer">
|
<a className="btn btn-primary">{t(locale)}</a>
|
||||||
<div className="ml-4">
|
|
||||||
<p className="text-lg font-medium text-base text-left">{t(locale)}</p>
|
|
||||||
<p className="text-base left -mt-4">{text[locale]}</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { useTranslation } from 'next-i18next'
|
||||||
import { Popover, Transition } from '@headlessui/react'
|
import { Popover, Transition } from '@headlessui/react'
|
||||||
import DownIcon from 'shared/components/icons/down'
|
import DownIcon from 'shared/components/icons/down'
|
||||||
|
|
||||||
const ThemePicker = ({ app, className, iconOnly = false }) => {
|
const ThemePicker = ({ app, className, iconOnly = false, bottom = false }) => {
|
||||||
const { t } = useTranslation(['themes', 'common'])
|
const { t } = useTranslation(['themes', 'common'])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -13,11 +13,11 @@ const ThemePicker = ({ app, className, iconOnly = false }) => {
|
||||||
{() => (
|
{() => (
|
||||||
<>
|
<>
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
className={`group inline-flex items-center px-3 py-2 text-base font-medium text-neural-content rounded-lg px-4 hover:text-secondary-focus`}
|
className={`group border-0 inline-flex items-center px-3 py-2 text-base font-medium text-neural-content rounded-lg px-4 hover:text-secondary-focus`}
|
||||||
>
|
>
|
||||||
<ThemeIcon />
|
<ThemeIcon />
|
||||||
{!iconOnly && <span className="ml-4 font-medium capitalize">{t(`common:theme`)}</span>}
|
{!iconOnly && <span className="ml-4 font-medium capitalize">{t(`common:theme`)}</span>}
|
||||||
<DownIcon className={`ml-2 h-5 w-5`} aria-hidden="true" />
|
<DownIcon className={`ml-2 h-5 w-5 ${bottom ? 'rotate-180' : ''}`} aria-hidden="true" />
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
<Transition
|
<Transition
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
|
@ -28,29 +28,21 @@ const ThemePicker = ({ app, className, iconOnly = false }) => {
|
||||||
leaveFrom="opacity-100 translate-y-0"
|
leaveFrom="opacity-100 translate-y-0"
|
||||||
leaveTo="opacity-0 translate-y-1"
|
leaveTo="opacity-0 translate-y-1"
|
||||||
>
|
>
|
||||||
<Popover.Panel className="absolute z-10 mt-3 w-screen max-w-sm transform px-4 sm:px-0 lg:max-w-xl right-0">
|
<Popover.Panel
|
||||||
|
className={`absolute z-10 mb-3 w-64 transform px-4 sm:px-0 lg:max-w-xl right-0 ${
|
||||||
|
iconOnly ? 'translate-x-4' : ''
|
||||||
|
} ${bottom ? 'bottom-10' : 'top-12'}`}
|
||||||
|
>
|
||||||
<div className="overflow-hidden rounded-lg shadow-lg">
|
<div className="overflow-hidden rounded-lg shadow-lg">
|
||||||
<div className="relative grid gap-8 bg-base-100 p-7 lg:grid-cols-1">
|
<div className="relative grid gap-2 bg-base-100 p-4 grid-cols-1">
|
||||||
{Object.keys(themes).map((theme) => (
|
{Object.keys(themes).map((theme) => (
|
||||||
<button
|
<button
|
||||||
|
data-theme={theme}
|
||||||
key={theme}
|
key={theme}
|
||||||
onClick={() => app.setTheme(theme)}
|
onClick={() => app.setTheme(theme)}
|
||||||
data-theme={theme}
|
className="btn btn-primary"
|
||||||
className="-m-3 flex rounded-lg p-2 transition duration-150 ease-in-out-50 hover:translate-x-2 hover:cursor-pointer"
|
|
||||||
>
|
>
|
||||||
<div className="w-full">
|
{t(`${theme}Theme`)}
|
||||||
<div className="px-2">
|
|
||||||
<p className="text-xl font-medium text-base text-left mb-0 pb-0">
|
|
||||||
{t(`${theme}Theme`)}
|
|
||||||
</p>
|
|
||||||
<div className="theme-gradient h-1 w-full mb-2"></div>
|
|
||||||
<p className="text-base text-left -mt-4 shadow">
|
|
||||||
{t('common:sloganCome')}
|
|
||||||
<span className="px-2">|</span>
|
|
||||||
{t('common:sloganStay')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,7 +19,7 @@ export const WordMark = () => (
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
<a
|
<a
|
||||||
role="button"
|
role="button"
|
||||||
className="btn btn-ghost btn-sm normal-case text-2xl hover:bg-transparent font-bold"
|
className="btn btn-ghost btn-sm normal-case text-2xl hover:bg-transparent font-bold px-0 -mt-1"
|
||||||
>
|
>
|
||||||
<InnerWordMark />
|
<InnerWordMark />
|
||||||
</a>
|
</a>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue