1
0
Fork 0

chore(sites): Updates to headers

This commit is contained in:
joostdecock 2022-11-23 21:42:22 +01:00
parent 1fb689d779
commit 1e379d68ff
13 changed files with 281 additions and 378 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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