UNPKG

gatsby-remark-dictionary

Version:

Create a dictionary of word and phrases to be converted to highlighted values and links. define once and all phrases will be converted during build time.

159 lines (145 loc) 4.23 kB
import React, { useState, useEffect, createRef } from 'react'; import { InstantSearch, Index, Hits, Configure, Pagination, connectStateResults, } from 'react-instantsearch-dom'; import algoliasearch from 'algoliasearch/lite'; import config from '../../../config.js'; import styled from '@emotion/styled'; import { css } from '@emotion/core'; import { PoweredBy } from './styles'; import { Search } from 'styled-icons/fa-solid/Search'; import Input from './input'; import * as hitComps from './hitComps'; const SearchIcon = styled(Search)` width: 1em; pointer-events: none; `; const HitsWrapper = styled.div` display: ${props => (props.show ? `grid` : `none`)}; max-height: 80vh; overflow: scroll; z-index: 2; -webkit-overflow-scrolling: touch; position: absolute; right: 0; top: calc(100% + 0.5em); width: 80vw; max-width: 30em; box-shadow: 0 0 5px 0; padding: 0.7em 1em 0.4em; background: white; @media only screen and (max-width: 991px) { width: 400px; max-width: 400px; } @media only screen and (max-width: 767px) { width: 100%; max-width: 500px; } border-radius: ${props => props.theme.smallBorderRadius}; > * + * { padding-top: 1em !important; border-top: 2px solid ${props => props.theme.darkGray}; } li + li { margin-top: 0.7em; padding-top: 0.7em; border-top: 1px solid ${props => props.theme.lightGray}; } * { margin-top: 0; padding: 0; color: black !important; } ul { list-style: none; } mark { color: ${props => props.theme.lightBlue}; background: ${props => props.theme.darkBlue}; } header { display: flex; justify-content: space-between; margin-bottom: 0.3em; h3 { color: black; background: ${props => props.theme.gray}; padding: 0.1em 0.4em; border-radius: ${props => props.theme.smallBorderRadius}; } } h3 { color: black; margin: 0 0 0.5em; } h4 { color: black; margin-bottom: 0.3em; } `; const Root = styled.div` position: relative; display: grid; grid-gap: 1em; @media only screen and (max-width: 767px) { width: 100%; } `; const Results = connectStateResults( ({ searching, searchState: state, searchResults: res }) => (searching && `Searching...`) || (res && res.nbHits === 0 && `No results for '${state.query}'`) ); const useClickOutside = (ref, handler, events) => { if (!events) events = [`mousedown`, `touchstart`]; const detectClickOutside = event => ref && ref.current && !ref.current.contains(event.target) && handler(); useEffect(() => { for (const event of events) document.addEventListener(event, detectClickOutside); return () => { for (const event of events) document.removeEventListener(event, detectClickOutside); }; }); }; export default function SearchComponent({ indices, collapse, hitsAsGrid }) { const ref = createRef(); const [query, setQuery] = useState(``); const [focus, setFocus] = useState(false); const searchClient = algoliasearch( config.header.search.algoliaAppId, config.header.search.algoliaSearchKey ); useClickOutside(ref, () => setFocus(false)); const displayResult = query.length > 0 && focus ? 'showResults' : 'hideResults'; return ( <InstantSearch searchClient={searchClient} indexName={indices[0].name} onSearchStateChange={({ query }) => setQuery(query)} root={{ Root, props: { ref } }} > <Input onFocus={() => setFocus(true)} {...{ collapse, focus }} /> <HitsWrapper className={'hitWrapper ' + displayResult} show={query.length > 0 && focus} asGrid={hitsAsGrid} > {indices.map(({ name, title, hitComp, type }) => { return ( <Index key={name} indexName={name}> <Results /> <Hits hitComponent={hitComps[hitComp](() => setFocus(false))} /> </Index> ); })} <PoweredBy /> </HitsWrapper> <Configure hitsPerPage={5} /> </InstantSearch> ); }