1
0
Fork 0
freesewing/sites/dev/components/search.js

223 lines
6.5 KiB
JavaScript
Raw Normal View History

2022-01-20 09:07:38 +01:00
import { useState, useRef } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/router'
2022-11-23 21:42:22 +01:00
import algoliasearch from 'algoliasearch/lite'
import { useHotkeys } from 'react-hotkeys-hook'
2022-11-23 21:42:22 +01:00
import {
InstantSearch,
connectHits,
connectHighlight,
connectSearchBox,
} from 'react-instantsearch-dom'
import CloseIcon from 'shared/components/icons/close.js'
2022-06-17 12:02:09 +02:00
import config from 'site/algolia.config.mjs'
const searchClient = algoliasearch(config.algolia.app, config.algolia.key)
2022-11-23 21:42:22 +01:00
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
2022-11-23 21:42:22 +01:00
const links = props.hits.map((hit) => hit.page)
props.input.current.setAttribute('data-links', JSON.stringify(links))
return props.hits.map((hit, index) => (
<Hit
key={hit.page}
{...props}
hit={hit}
index={index}
len={props.hits.length}
activeLink={links[props.active]}
/>
))
}
2022-11-23 21:42:22 +01:00
const CustomHits = connectHits(Hits)
2022-11-23 21:42:22 +01:00
const Highlight = ({ highlight, attribute, hit, snippet = false }) => {
const parsedHit = highlight({
2021-12-31 10:15:51 +01:00
highlightProperty: snippet ? '_snippetResult' : '_highlightResult',
attribute,
hit,
2022-11-23 21:42:22 +01:00
})
2022-11-23 21:42:22 +01:00
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>
)
)
}
2022-11-23 21:42:22 +01:00
const CustomHighlight = connectHighlight(Highlight)
2022-11-23 21:42:22 +01:00
const Hit = (props) => (
<div
className={`
2021-12-31 10:15:51 +01:00
px-2 py-1 ounded mt-1
text-base text-base-content
sm:rounded
lg:px-4 lg:py-2
hover:bg-secondary hover:bg-opacity-10 hover:text-base-content
2022-11-23 21:42:22 +01:00
${props.index === props.active ? 'bg-secondary bg-opacity-30' : 'bg-base-300 bg-opacity-10'}
`}
>
<Link href={props.hit.page}>
2021-12-31 10:15:51 +01:00
<a href={props.hit.page} className="flex flex-row justify-between gap-2">
<span className="text-base sm:text-xl font-bold leading-5">
2022-11-23 21:42:22 +01:00
{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]}
2021-12-31 10:15:51 +01:00
</span>
</a>
</Link>
2022-01-20 09:07:38 +01:00
{props.hit._snippetResult?.body && (
2021-12-31 10:15:51 +01:00
<Link href={props.hit.page}>
<a href={props.hit.page} className="text-sm sm:text-base block py-1">
2022-11-23 21:42:22 +01:00
<CustomHighlight hit={props.hit} attribute="body" snippet />
2021-12-31 10:15:51 +01:00
</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">
2022-11-23 21:42:22 +01:00
<CustomHighlight hit={props.hit} attribute="page" />
2021-12-31 10:15:51 +01:00
</a>
</Link>
)}
</div>
)
// We use this for trapping ctrl-c
const handleInputKeydown = (evt, setSearch, setActive, active, router) => {
if (evt.key === 'Escape') setSearch(false)
2022-11-23 21:42:22 +01:00
if (evt.key === 'ArrowDown') setActive((act) => act + 1)
if (evt.key === 'ArrowUp') setActive((act) => act - 1)
if (evt.key === 'Enter') {
// Trigger navigation
2022-01-20 09:07:38 +01:00
if (evt.target?.dataset?.links) {
router.push(JSON.parse(evt.target.dataset.links)[active])
setSearch(false)
}
}
}
2022-11-23 21:42:22 +01:00
const SearchBox = (props) => {
const input = useRef(null)
const router = useRouter()
useHotkeys('ctrl+x', () => {
input.current.value = ''
})
if (input.current && props.active < 0) input.current.focus()
2022-01-20 09:07:38 +01:00
const { currentRefinement, refine, setSearch, setActive } = props
return (
2021-12-31 10:15:51 +01:00
<div className="py-8">
<form noValidate action="" role="search" onSubmit={(evt) => evt.preventDefault()}>
<div className="form-control">
<div className="relative">
<input
ref={input}
type="search"
autoFocus={true}
value={currentRefinement}
2022-11-23 21:42:22 +01:00
onChange={(event) => refine(event.currentTarget.value)}
onKeyDown={(evt) =>
handleInputKeydown(evt, setSearch, setActive, props.active, router)
}
2021-12-31 10:15:51 +01:00
className="input lg:input-lg input-bordered input-neutral w-full pr-16"
2022-11-23 21:42:22 +01:00
placeholder="Type to search"
/>
<button
2021-12-31 10:15:51 +01:00
className="absolute right-0 top-0 rounded-l-none btn btn-neutral lg:btn-lg"
onClick={() => props.setSearch(false)}
2022-11-23 21:42:22 +01:00
>
X
</button>
</div>
2021-12-31 10:15:51 +01:00
<label className="label hidden sm:block">
<div className="label-text flex flex-row gap-4 justify-between">
2022-11-23 21:42:22 +01:00
<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>
2021-12-31 10:15:51 +01:00
</div>
</label>
</div>
<div
2021-12-31 10:15:51 +01:00
className="overscroll-auto overflow-y-auto mt-2"
2022-11-23 21:42:22 +01:00
style={{ maxHeight: 'calc(100vh - 10rem)' }}
>
2022-11-23 21:42:22 +01:00
{input.current && input.current.value.length > 0 && (
<CustomHits hitComponent={Hit} {...props} input={input} />
)}
</div>
</form>
2022-11-23 21:42:22 +01:00
<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
2022-11-23 21:42:22 +01:00
`}
>
<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>
</div>
)
}
2022-11-23 21:42:22 +01:00
const CustomSearchBox = connectSearchBox(SearchBox)
2022-11-23 21:42:22 +01:00
const Search = (props) => {
const [active, setActive] = useState(0)
useHotkeys('esc', () => props.setSearch(false))
useHotkeys('up', () => {
2022-11-23 21:42:22 +01:00
if (active) setActive((act) => act - 1)
})
useHotkeys('down', () => {
2022-11-23 21:42:22 +01:00
setActive((act) => act + 1)
})
useHotkeys('down', () => {
console.log('enter', active)
})
const stateProps = {
setSearch: props.setSearch,
setMenu: props.setMenu,
2022-11-23 21:42:22 +01:00
active,
setActive,
}
return (
2021-12-31 10:15:51 +01:00
<InstantSearch indexName={config.algolia.index} searchClient={searchClient}>
2022-11-23 21:42:22 +01:00
<CustomSearchBox {...stateProps} />
2021-12-31 10:15:51 +01:00
</InstantSearch>
)
}
export default Search