UNPKG

@newrelic/gatsby-theme-newrelic

Version:

[![Community Project header](https://github.com/newrelic/opensource-website/raw/master/src/images/categories/Community_Project.png)](https://opensource.newrelic.com/oss-category/#community-project)

230 lines (208 loc) 7.08 kB
import React, { useRef, useCallback, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { css } from '@emotion/react'; import { animated, useTransition } from 'react-spring'; import { useLocation } from '@reach/router'; import Footer from './Footer'; import Input from './Input'; import NoResults from './NoResults'; import useSearchQuery from './useSearchQuery'; import ResultList from './ResultList'; import ResultPreview from './ResultPreview'; import ScrollContainer from './ScrollContainer'; import useSearch from './useSearch'; import Portal from '../Portal'; import useThemeTranslation from '../../hooks/useThemeTranslation'; import useKeyPress from '../../hooks/useKeyPress'; import useScrollFreeze from '../../hooks/useScrollFreeze'; import usePrevious from '../../hooks/usePrevious'; const defaultFilters = [ { name: 'docs', isSelected: false }, { name: 'developer', isSelected: false }, { name: 'opensource', isSelected: false }, { name: 'quickstarts', isSelected: false }, ]; const defaultSearchByFilters = [ { name: 'title', isSelected: false }, { name: 'body', isSelected: false }, ]; const DEFAULT_FILTER_TYPES = [ { type: 'source', defaultFilters: defaultFilters }, { type: 'searchBy', defaultFilters: defaultSearchByFilters }, ]; const SearchModal = ({ onClose, isOpen }) => { const { pathname } = useLocation(); const didChangeRoute = pathname !== usePrevious(pathname, { initializeWithValue: true }); const { t } = useThemeTranslation(); const searchInput = useRef(); const [selectedIndex, setSelectedIndex] = useState(0); const [filters, setFilters] = useState(DEFAULT_FILTER_TYPES); const [searchTerm, setSearchTerm] = useSearchQuery(filters); const { status, results } = useSearch({ searchTerm, filters, }); const transitions = useTransition(isOpen, { config: { tension: 220, friction: 22 }, from: { opacity: 0, transform: 'scale(0.96)', }, enter: { opacity: 1, transform: 'scale(1)' }, leave: { opacity: 0, transform: 'scale(0.96)' }, }); useScrollFreeze(isOpen); useKeyPress('Escape', onClose, { ignoreTextInput: false }); useEffect(() => { if (isOpen && didChangeRoute) { onClose(); } }, [isOpen, didChangeRoute, onClose]); useEffect(() => { isOpen && searchInput?.current?.focus(); }, [isOpen]); useEffect(() => { setSelectedIndex(0); }, [searchTerm]); const handleSelectIndex = useCallback((idx) => { setSelectedIndex(idx); }, []); const selectedResult = results[selectedIndex]; const updateFilters = (filterName) => { setFilters((filters) => { return filters.map(({ defaultFilters, type }) => ({ defaultFilters: defaultFilters.map((filter) => { return filter.name === filterName ? { ...filter, isSelected: !filter.isSelected } : filter; }), type, })); }); }; return transitions( (style, item) => item && ( <Portal> <animated.div style={{ opacity: style.opacity }} css={css` position: fixed; top: 0; right: 0; bottom: 0; left: 0; padding: var(--site-content-padding); z-index: 900; background: var(--modal-wrapper-color); @media screen and (max-width: 760px) { padding: 0; } em { border-radius: 0.125rem; padding: 0.125rem 0.25rem; background: var(--system-text-secondary-inverted-light); font-style: normal; font-weight: bold; font-size: 85%; .dark-mode & { background: var( --system-background-selected-low-contrast-dark ); } } h2, h3, h4 { em { font-size: inherit; } } `} onClick={onClose} > <animated.div onClick={(e) => e.stopPropagation()} style={style} css={css` --horizontal-spacing: 1rem; z-index: 101; max-width: 1024px; width: 100%; margin: auto; box-shadow: var(--shadow-4); max-height: calc(100vh - 2 * var(--site-content-padding)); display: flex; flex-direction: column; position: relative; `} > <Input placeholder={t('searchInput.placeholder')} ref={searchInput} onChange={(e) => { setSearchTerm(e.target.value); }} onFilter={(filterName) => updateFilters(filterName)} value={searchTerm} onClear={() => setSearchTerm('')} onCancel={onClose} filters={filters} css={ searchTerm && css` input { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } ` } /> {searchTerm && ( <div css={css` display: grid; grid-template-columns: 1fr 1fr; flex-grow: 1; background-color: var(--modal-background-color); border-bottom-left-radius: 0.25rem; border-bottom-right-radius: 0.25rem; box-shadow: var(--shadow-6); border: 1px solid var(--border-color); border-top: none; overflow: hidden; @media screen and (max-width: 760px) { grid-template-columns: 1fr; } `} > {Boolean(results?.length) && ( <> <ScrollContainer> <ResultList results={results} selectedIndex={selectedIndex} onSelectIndex={handleSelectIndex} /> </ScrollContainer> <ResultPreview css={css``} result={selectedResult} /> <Footer /> </> )} {results.length === 0 && status === 'success' && ( <NoResults /> )} </div> )} </animated.div> </animated.div> </Portal> ) ); }; SearchModal.propTypes = { onClose: PropTypes.func.isRequired, isOpen: PropTypes.bool.isRequired, }; export default SearchModal;