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 (
|
||||
<header
|
||||
className={`
|
||||
fixed top-0 left-0
|
||||
fixed bottom-0 lg:bottom-auto lg:top-0 left-0
|
||||
bg-neutral
|
||||
w-full
|
||||
z-30
|
||||
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
|
||||
`}
|
||||
>
|
||||
<div>
|
||||
<div className="m-auto" style={{ maxWidth: '1800px' }}>
|
||||
<div className="p-2 flex flex-row gap-2 justify-between text-neutral-content">
|
||||
<div className="flex flex-row items-center">
|
||||
<button
|
||||
|
@ -54,16 +54,20 @@ const Header = ({ app, setSearch }) => {
|
|||
>
|
||||
{app.primaryMenu ? <CloseIcon /> : <MenuIcon />}
|
||||
</button>
|
||||
<div className="hidden lg:block">
|
||||
<WordMark />
|
||||
</div>
|
||||
<div className="flex flex-row items-center lg:hidden pr-4">
|
||||
</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 btn-ghost hover:text-secondary-focus"
|
||||
>
|
||||
<SearchIcon />
|
||||
</button>
|
||||
<ThemePicker app={app} iconOnly />
|
||||
</div>
|
||||
<div className="hidden lg:flex flex-row items-center">
|
||||
<button
|
||||
|
|
|
@ -7,7 +7,7 @@ const Aside = ({ app, slug, mobileOnly = false, before = [], after = [] }) => (
|
|||
overflow-y-auto z-20
|
||||
bg-base-100 text-base-content
|
||||
${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:justify-center
|
||||
|
|
|
@ -2,19 +2,24 @@ import { useState, useRef } from 'react'
|
|||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import algoliasearch from 'algoliasearch/lite';
|
||||
import algoliasearch from 'algoliasearch/lite'
|
||||
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'
|
||||
|
||||
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
|
||||
// which means we must make the result links available in the input somehow
|
||||
// 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))
|
||||
|
||||
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 parsedHit = highlight({
|
||||
highlightProperty: snippet ? '_snippetResult' : '_highlightResult',
|
||||
attribute,
|
||||
hit,
|
||||
});
|
||||
})
|
||||
|
||||
return parsedHit.map((part, index) => part.isHighlighted
|
||||
? <mark className="text-base-content bg-secondary-focus bg-opacity-30" key={index}>{part.value}</mark>
|
||||
: <span key={index}>{part.value}</span>
|
||||
return parsedHit.map((part, index) =>
|
||||
part.isHighlighted ? (
|
||||
<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
|
||||
className={`
|
||||
px-2 py-1 ounded mt-1
|
||||
|
@ -54,34 +64,34 @@ const Hit = props => (
|
|||
sm:rounded
|
||||
lg:px-4 lg:py-2
|
||||
hover:bg-secondary hover:bg-opacity-10 hover:text-base-content
|
||||
${props.index === props.active
|
||||
? 'bg-secondary bg-opacity-30'
|
||||
: 'bg-base-300 bg-opacity-10'
|
||||
}
|
||||
${props.index === props.active ? 'bg-secondary bg-opacity-30' : 'bg-base-300 bg-opacity-10'}
|
||||
`}
|
||||
>
|
||||
<Link href={props.hit.page}>
|
||||
<a href={props.hit.page} className="flex flex-row justify-between gap-2">
|
||||
<span className="text-base sm:text-xl font-bold leading-5">
|
||||
{props.hit._highlightResult?.title
|
||||
? <CustomHighlight hit={props.hit} attribute='title' />
|
||||
: props.hit.title
|
||||
}
|
||||
{props.hit._highlightResult?.title ? (
|
||||
<CustomHighlight hit={props.hit} attribute="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 className="text-xs pt-0.5 sm:text-base sm:pt-1 font-bold uppercase">{props.hit.page.split('/')[1]}</span>
|
||||
</a>
|
||||
</Link>
|
||||
{props.hit._snippetResult?.body && (
|
||||
<Link href={props.hit.page}>
|
||||
<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>
|
||||
</Link>
|
||||
)}
|
||||
{props.hit?._highlightResult?.page && (
|
||||
<Link href={props.hit.page}>
|
||||
<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>
|
||||
</Link>
|
||||
)}
|
||||
|
@ -91,8 +101,8 @@ const Hit = props => (
|
|||
// We use this for trapping ctrl-c
|
||||
const handleInputKeydown = (evt, setSearch, setActive, active, router) => {
|
||||
if (evt.key === 'Escape') setSearch(false)
|
||||
if (evt.key === 'ArrowDown') setActive(act => act + 1)
|
||||
if (evt.key === 'ArrowUp') setActive(act => act - 1)
|
||||
if (evt.key === 'ArrowDown') setActive((act) => act + 1)
|
||||
if (evt.key === 'ArrowUp') setActive((act) => act - 1)
|
||||
if (evt.key === 'Enter') {
|
||||
// Trigger navigation
|
||||
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 router = useRouter()
|
||||
useHotkeys('ctrl+x', () => {
|
||||
|
@ -123,21 +132,31 @@ const SearchBox = props => {
|
|||
type="search"
|
||||
autoFocus={true}
|
||||
value={currentRefinement}
|
||||
onChange={event => refine(event.currentTarget.value)}
|
||||
onKeyDown={(evt) => handleInputKeydown(evt, setSearch, setActive, props.active, router)}
|
||||
onChange={(event) => refine(event.currentTarget.value)}
|
||||
onKeyDown={(evt) =>
|
||||
handleInputKeydown(evt, setSearch, setActive, props.active, router)
|
||||
}
|
||||
className="input lg:input-lg input-bordered input-neutral w-full pr-16"
|
||||
placeholder='Type to search'
|
||||
placeholder="Type to search"
|
||||
/>
|
||||
<button
|
||||
className="absolute right-0 top-0 rounded-l-none btn btn-neutral lg:btn-lg"
|
||||
onClick={() => props.setSearch(false)}
|
||||
>X</button>
|
||||
>
|
||||
X
|
||||
</button>
|
||||
</div>
|
||||
<label className="label hidden sm:block">
|
||||
<div className="label-text flex flex-row gap-4 justify-between">
|
||||
<div><b> Escape</b> to exit</div>
|
||||
<div><b> Up</b> or <b>Down</b> to select</div>
|
||||
<div><b> Enter</b> to navigate</div>
|
||||
<div>
|
||||
<b> Escape</b> to exit
|
||||
</div>
|
||||
<div>
|
||||
<b> Up</b> or <b>Down</b> to select
|
||||
</div>
|
||||
<div>
|
||||
<b> Enter</b> to navigate
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -145,26 +164,25 @@ const SearchBox = props => {
|
|||
className="overscroll-auto overflow-y-auto mt-2"
|
||||
style={{ maxHeight: 'calc(100vh - 10rem)' }}
|
||||
>
|
||||
{
|
||||
input.current
|
||||
&& input.current.value.length > 0
|
||||
&& <CustomHits hitComponent={Hit} {...props} input={input}/>
|
||||
}
|
||||
{input.current && input.current.value.length > 0 && (
|
||||
<CustomHits hitComponent={Hit} {...props} input={input} />
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
<div className={`
|
||||
<div
|
||||
className={`
|
||||
bg-neutral text-neutral-content
|
||||
z-20 w-full mx-auto
|
||||
lg:bg-base-100 lg:border-base-200
|
||||
fixed bottom-0 left-0 border-t-2
|
||||
lg:hidden
|
||||
`}>
|
||||
<div className='px-4 py-0 flex flex-row w-full lg:py-2'>
|
||||
<button
|
||||
className={`btn btn-ghost btn-block`}
|
||||
onClick={() => props.setSearch(false)}
|
||||
`}
|
||||
>
|
||||
<span className='px-2 pt-2 pb-2'>Close Search</span>
|
||||
<div className="px-4 py-0 flex flex-row w-full lg:py-2">
|
||||
<button className={`btn btn-ghost btn-block`} onClick={() => props.setSearch(false)}>
|
||||
<span className="px-2 pt-2 pb-2">
|
||||
<CloseIcon />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -172,17 +190,16 @@ const SearchBox = props => {
|
|||
)
|
||||
}
|
||||
|
||||
const CustomSearchBox = connectSearchBox(SearchBox);
|
||||
|
||||
const Search = props => {
|
||||
const CustomSearchBox = connectSearchBox(SearchBox)
|
||||
|
||||
const Search = (props) => {
|
||||
const [active, setActive] = useState(0)
|
||||
useHotkeys('esc', () => props.setSearch(false))
|
||||
useHotkeys('up', () => {
|
||||
if (active) setActive(act => act - 1)
|
||||
if (active) setActive((act) => act - 1)
|
||||
})
|
||||
useHotkeys('down', () => {
|
||||
setActive(act => act + 1)
|
||||
setActive((act) => act + 1)
|
||||
})
|
||||
useHotkeys('down', () => {
|
||||
console.log('enter', active)
|
||||
|
@ -191,7 +208,8 @@ const Search = props => {
|
|||
const stateProps = {
|
||||
setSearch: props.setSearch,
|
||||
setMenu: props.setMenu,
|
||||
active, setActive
|
||||
active,
|
||||
setActive,
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -2,11 +2,11 @@ import Logo from 'shared/components/logos/freesewing.js'
|
|||
import OsiLogo from 'shared/components/logos/osi.js'
|
||||
import CreativeCommonsLogo from 'shared/components/logos/cc.js'
|
||||
import CcByLogo from 'shared/components/logos/cc-by.js'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import DocsLink from 'shared/components/docs-link'
|
||||
import PinkedRibbon from 'shared/components/pinked-ribbon.js'
|
||||
import Ribbon from 'shared/components/ribbon.js'
|
||||
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 FacebookIcon from 'shared/components/icons/facebook.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'
|
||||
|
||||
// Classes
|
||||
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 freesewing = "px-1 text-lg font-bold block sm:inline"
|
||||
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 freesewing = 'px-1 text-lg font-bold block sm:inline'
|
||||
|
||||
// Keep these translations in the component because they're only used here
|
||||
const translations = {
|
||||
cc: {
|
||||
en: <span>
|
||||
Content on FreeSewing.org is available under <a
|
||||
className={link} href="https://creativecommons.org/licenses/by/4.0/"
|
||||
>a Creative Commons license</a>
|
||||
</span>,
|
||||
de: <span>
|
||||
Inhalte auf FreeSewing.org sind unter einer <a
|
||||
className={link} href="https://creativecommons.org/licenses/by/4.0/deed.de"
|
||||
>Creative
|
||||
Commons-Lizenz</a> verfügbar
|
||||
</span>,
|
||||
es: <span>
|
||||
El contenido de FreeSewing.org está disponible bajo <a
|
||||
className={link} href="https://creativecommons.org/licenses/by/4.0/deed.es"
|
||||
>una licencia Creative Commons</a>
|
||||
</span>,
|
||||
fr: <span>
|
||||
Le contenu de FreeSewing.org est sous
|
||||
<a className={link} href="https://creativecommons.org/licenses/by/4.0/deed.fr"
|
||||
>licence Creative Commons</a>
|
||||
</span>,
|
||||
nl: <span>
|
||||
De inhoud op FreeSewing.org is beschikbaar onder
|
||||
<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>
|
||||
</>,
|
||||
},
|
||||
cc: (
|
||||
<span>
|
||||
Content on FreeSewing.org is available under{' '}
|
||||
<a className={link} href="https://creativecommons.org/licenses/by/4.0/">
|
||||
a Creative Commons license
|
||||
</a>
|
||||
</span>
|
||||
),
|
||||
mit: (
|
||||
<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>
|
||||
),
|
||||
sponsors: (
|
||||
<>
|
||||
<span className={freesewing}>FreeSewing</span> is sponsored by these{' '}
|
||||
<span className={accent}>awesome companies</span>
|
||||
</>
|
||||
),
|
||||
}
|
||||
|
||||
const icon = { className: "w-20 h-20" }
|
||||
const icon = { className: 'w-8 lg:w-12 h-8 lg:h-12' }
|
||||
const social = {
|
||||
Discord: {
|
||||
icon: <DiscordIcon {...icon} />,
|
||||
href: 'https://discord.freesewing.org/'
|
||||
href: 'https://discord.freesewing.org/',
|
||||
},
|
||||
Instagram: {
|
||||
icon: <InstagramIcon {...icon} />,
|
||||
href: 'https://instagram.com/freesewing_org'
|
||||
href: 'https://instagram.com/freesewing_org',
|
||||
},
|
||||
Facebook: {
|
||||
icon: <FacebookIcon {...icon} />,
|
||||
href: 'https://www.facebook.com/groups/627769821272714/'
|
||||
href: 'https://www.facebook.com/groups/627769821272714/',
|
||||
},
|
||||
Github: {
|
||||
icon: <GithubIcon {...icon} />,
|
||||
href: 'https://github.com/freesewing'
|
||||
href: 'https://github.com/freesewing',
|
||||
},
|
||||
Reddit: {
|
||||
icon: <RedditIcon {...icon} />,
|
||||
href: 'https://www.reddit.com/r/freesewing/'
|
||||
href: 'https://www.reddit.com/r/freesewing/',
|
||||
},
|
||||
Twitter: {
|
||||
icon: <TwitterIcon {...icon} />,
|
||||
href: 'https://twitter.com/freesewing_org'
|
||||
}
|
||||
href: 'https://twitter.com/freesewing_org',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
const Footer = ({ app, full=false }) => {
|
||||
const { t } = useTranslation(['common', 'patrons'])
|
||||
|
||||
const Footer = ({ app }) => {
|
||||
return (
|
||||
<footer className="bg-neutral">
|
||||
<PinkedRibbon 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">
|
||||
|
||||
<Ribbon loading={app.loading} theme={app.theme} />
|
||||
<div className="grid grid-cols-1 lg:grid-cols-4 py-12 2xl:py-20 text-neutral-content px-4">
|
||||
{/* 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><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">
|
||||
<CcByLogo className="w-12 sm:w-16"/>
|
||||
<p className="text-neutral-content text-right basis-3/4">
|
||||
{translations.cc[app.locale]}
|
||||
<CcByLogo className="w-8 lg:w-12" />
|
||||
<p className="text-neutral-content text-right basis-4/5 lg:basis-3/4 leading-5">
|
||||
{translations.cc}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 justify-center items-center mt-4">
|
||||
<OsiLogo className="w-12 sm:w-16"/>
|
||||
<p className="text-neutral-content text-right basis-3/4">
|
||||
{translations.mit[app.locale]}
|
||||
<OsiLogo className="w-8 lg:w-12" />
|
||||
<p className="text-neutral-content text-right basis-4/5 lg:basis-3/4 leading-5">
|
||||
{translations.mit}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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 */}
|
||||
<div className="w-full sm:w-auto flex flex-row flex-wrap gap-8 items-center justify-center">
|
||||
{Object.keys(social).map(item => (
|
||||
<div className="w-full sm:w-auto flex flex-row flex-wrap gap-4 lg:gap-8 items-center justify-center">
|
||||
<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}>
|
||||
<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}
|
||||
</a>
|
||||
</Link>
|
||||
|
@ -197,49 +125,55 @@ const Footer = ({ app, full=false }) => {
|
|||
</div>
|
||||
{/* Sponsors */}
|
||||
<div className="border rounded-xl p-8 border-dashed border-base-100/25 mt-20">
|
||||
<p className="text-center text-neutral-content">
|
||||
{translations.sponsors[app.locale]}
|
||||
<p className="text-center text-neutral-content leading-5">
|
||||
{translations.sponsors}
|
||||
<br />
|
||||
</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/">
|
||||
<img src="/brands/algolia.svg" className="w-64" alt="Search powered by Algolia"/>
|
||||
<img
|
||||
src="/brands/algolia.svg"
|
||||
className="h-12 aspect-auto"
|
||||
alt="Search powered by Algolia"
|
||||
/>
|
||||
</a>
|
||||
<a title="Error handling by Bugsnag" href="https://www.bugsnag.com/">
|
||||
<img src="/brands/bugsnag.svg" className="h-32" alt="Error handling by bugsnag" />
|
||||
<img src="/brands/bugsnag.svg" className="h-12" alt="Error handling by bugsnag" />
|
||||
</a>
|
||||
<a title="Translation powered by Crowdin" href="https://www.crowdin.com/">
|
||||
<img src="/brands/crowdin.svg" className="w-64" alt="Translation powered by Crowdin" />
|
||||
<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" className="w-64 shadow-lg shadow-white/20" alt="Builds & Hosting by Vercel" />
|
||||
<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>
|
||||
<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>
|
||||
|
||||
{/* 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">
|
||||
<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>
|
||||
<p className="bold text-neutral-content text-normal lg:text-xl">
|
||||
{t('sloganCome')}
|
||||
<h5 className="lg:text-3xl mt-4">
|
||||
<WordMark />
|
||||
</h5>
|
||||
<p className="bold text-neutral-content text-normal lg:text-xl leading-5">
|
||||
Come for the sewing patterns
|
||||
<br />
|
||||
{t('sloganStay')}
|
||||
Stay for the community
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
||||
|
||||
|
|
|
@ -1,36 +1,24 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import FreeSewingIcon from 'shared/components/icons/freesewing.js'
|
||||
import Link from 'next/link'
|
||||
import ThemePicker from 'shared/components/theme-picker.js'
|
||||
import LocalePicker from 'shared/components/locale-picker.js'
|
||||
import CloseIcon from 'shared/components/icons/close.js'
|
||||
import MenuIcon from 'shared/components/icons/menu.js'
|
||||
import SearchIcon from 'shared/components/icons/search.js'
|
||||
|
||||
const Right = 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="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>
|
||||
)
|
||||
import Ribbon from 'shared/components/ribbon.js'
|
||||
import { WordMark } from 'shared/components/wordmark.js'
|
||||
|
||||
const Header = ({ app, setSearch }) => {
|
||||
|
||||
const [prevScrollPos, setPrevScrollPos] = useState(0)
|
||||
const [show, setShow] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const handleScroll = () => {
|
||||
const curScrollPos = (typeof window !== 'undefined') ? window.pageYOffset : 0
|
||||
const curScrollPos = typeof window !== 'undefined' ? window.pageYOffset : 0
|
||||
if (curScrollPos >= prevScrollPos) {
|
||||
if (show && curScrollPos > 20) setShow(false)
|
||||
}
|
||||
else setShow(true)
|
||||
} else setShow(true)
|
||||
setPrevScrollPos(curScrollPos)
|
||||
}
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
|
@ -38,89 +26,66 @@ const Header = ({ app, setSearch }) => {
|
|||
}
|
||||
}, [prevScrollPos, show])
|
||||
|
||||
|
||||
return (
|
||||
<header className={`
|
||||
fixed top-0 left-0
|
||||
<header
|
||||
className={`
|
||||
fixed bottom-0 lg:bottom-auto lg:top-0 left-0
|
||||
bg-neutral
|
||||
w-full
|
||||
z-30
|
||||
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
|
||||
${app.loading ? "theme-gradient loading" : ""}
|
||||
`}>
|
||||
<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
|
||||
className={`
|
||||
btn btn-sm
|
||||
text-neutral-content bg-transparent
|
||||
border border-transparent
|
||||
hover:bg-transparent hover:border-base-100
|
||||
md:hidden
|
||||
lg:hidden
|
||||
h-12
|
||||
`}
|
||||
onClick={app.togglePrimaryMenu}>
|
||||
{app.primaryMenu
|
||||
? (
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
onClick={app.togglePrimaryMenu}
|
||||
>
|
||||
{app.primaryMenu ? <CloseIcon /> : <MenuIcon />}
|
||||
</button>
|
||||
<div className="flex flex-row items-center md:hidden pr-4">
|
||||
<div className="hidden lg:block lg:pl-2">
|
||||
<WordMark />
|
||||
</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>
|
||||
<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">
|
||||
<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>
|
||||
<Ribbon loading={app.loading} theme={app.theme} />
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,14 +3,11 @@ import { useRouter } from 'next/router'
|
|||
import Aside from 'shared/components/navigation/aside'
|
||||
import ThemePicker from 'shared/components/theme-picker'
|
||||
import Breadcrumbs from 'shared/components/breadcrumbs.js'
|
||||
import { getCrumbs } from 'shared/utils.js'
|
||||
|
||||
const DefaultLayout = ({ app, title = false, crumbs = false, children = [] }) => {
|
||||
const router = useRouter()
|
||||
const slug = router.asPath.slice(1)
|
||||
const breadcrumbs = crumbs
|
||||
? crumbs
|
||||
: getCrumbs(app, slug, title)
|
||||
const breadcrumbs = crumbs ? crumbs : null //FIXME getCrumbs(app, slug, title)
|
||||
|
||||
return (
|
||||
<div className="m-auto flex flex-row justify-center">
|
||||
|
|
|
@ -4,9 +4,7 @@ const config = {
|
|||
index: 'canary_freesewing.dev',
|
||||
key: '589c7a7e4d9c95a4f12868581259bf3a', // Search-only API key
|
||||
},
|
||||
strapi: 'https://posts.freesewing.org',
|
||||
monorepo: 'https://github.com/freesewing/freesewing',
|
||||
}
|
||||
|
||||
export default config
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@ import path from 'path'
|
|||
import { readdirSync } from 'fs'
|
||||
import i18nConfig from './next-i18next.config.js'
|
||||
|
||||
const getDirectories = source =>
|
||||
const getDirectories = (source) =>
|
||||
readdirSync(source, { withFileTypes: true })
|
||||
.filter(dirent => dirent.isDirectory())
|
||||
.map(dirent => dirent.name)
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name)
|
||||
|
||||
const pkgs = getDirectories(path.resolve(`../`))
|
||||
|
||||
|
@ -16,24 +16,19 @@ const config = {
|
|||
i18n: i18nConfig.i18n,
|
||||
pageExtensions: ['js', 'mjs'],
|
||||
webpack: (config, options) => {
|
||||
|
||||
// Aliases
|
||||
config.resolve.alias.shared = path.resolve('../shared/')
|
||||
config.resolve.alias.site = path.resolve(`.`)
|
||||
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
|
||||
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
|
||||
}
|
||||
},
|
||||
}
|
||||
export default config
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// 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
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// 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
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Popover, Transition } from '@headlessui/react'
|
|||
import DownIcon from 'shared/components/icons/down'
|
||||
import Link from 'next/link'
|
||||
|
||||
const LocalePicker = ({ app, iconOnly = false }) => {
|
||||
const LocalePicker = ({ app, iconOnly = false, bottom = false }) => {
|
||||
const { t } = useTranslation(['locales'])
|
||||
const router = useRouter()
|
||||
|
||||
|
@ -23,11 +23,13 @@ const LocalePicker = ({ app, iconOnly = false }) => {
|
|||
{() => (
|
||||
<>
|
||||
<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 />
|
||||
<span className="ml-4 font-bold text-lg capitalize">{t(`common:language`)}</span>
|
||||
<DownIcon className={`ml-2 h-5 w-5`} aria-hidden="true" />
|
||||
{!iconOnly && (
|
||||
<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>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
|
@ -38,17 +40,16 @@ const LocalePicker = ({ app, iconOnly = false }) => {
|
|||
leaveFrom="opacity-100 translate-y-0"
|
||||
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="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) => (
|
||||
<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">
|
||||
<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>
|
||||
<a className="btn btn-primary">{t(locale)}</a>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useTranslation } from 'next-i18next'
|
|||
import { Popover, Transition } from '@headlessui/react'
|
||||
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'])
|
||||
|
||||
return (
|
||||
|
@ -13,11 +13,11 @@ const ThemePicker = ({ app, className, iconOnly = false }) => {
|
|||
{() => (
|
||||
<>
|
||||
<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 />
|
||||
{!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>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
|
@ -28,29 +28,21 @@ const ThemePicker = ({ app, className, iconOnly = false }) => {
|
|||
leaveFrom="opacity-100 translate-y-0"
|
||||
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="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) => (
|
||||
<button
|
||||
data-theme={theme}
|
||||
key={theme}
|
||||
onClick={() => app.setTheme(theme)}
|
||||
data-theme={theme}
|
||||
className="-m-3 flex rounded-lg p-2 transition duration-150 ease-in-out-50 hover:translate-x-2 hover:cursor-pointer"
|
||||
className="btn btn-primary"
|
||||
>
|
||||
<div className="w-full">
|
||||
<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>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -19,7 +19,7 @@ export const WordMark = () => (
|
|||
<Link href="/">
|
||||
<a
|
||||
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 />
|
||||
</a>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue