UNPKG

@navinc/base-react-components

Version:
237 lines (233 loc) 10.4 kB
var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useEffect, useRef, useState } from 'react'; import styled from 'styled-components'; import { LoadingDots } from './loading-dots.js'; import { Icon } from './icon.js'; import { Err, Errors, Field, FieldWrapper, Helper, Input, Label } from './form-elements/shared.js'; import { Copy } from './copy.js'; import { focusWithoutScroll, capitalize, noop } from '@navinc/utils'; import { useDebouncedCallback } from 'use-debounce'; const id = (id) => id; const SearchInputContainer = styled.div.withConfig({ displayName: "brc-sc-SearchInputContainer", componentId: "brc-sc-1ogiirv" }) ` width: 100%; `; const ResultsContainer = styled.div.withConfig({ displayName: "brc-sc-ResultsContainer", componentId: "brc-sc-1mckfdl" }) ` position: relative; z-index: 2; & > ${Helper} { margin-left: ${({ theme }) => theme.gu(2)}; } `; const ResultContainer = styled.div.withConfig({ displayName: "brc-sc-ResultContainer", componentId: "brc-sc-ztsn0h" }) ` cursor: pointer; outline: none; padding: 16px; border-bottom: 1px solid ${({ theme }) => theme.navNeutral300}; ${({ isActive, theme }) => isActive && `background-color: ${theme.navNeutral100}; border: 4px solid ${theme.navStatusPositive500};`} &:hover { background-color: ${({ theme }) => theme.navNeutral100}; } &:focus { outline: ${({ theme }) => theme.focusOutline}; } `; const Results = styled.div.withConfig({ displayName: "brc-sc-Results", componentId: "brc-sc-1625sii" }) ` background-color: ${({ theme }) => theme.navNeutralLight}; border: solid 1px ${({ theme }) => theme.navNeutral300}; border-top: none; border-radius: 0 0 8px 8px; position: ${({ shouldPositionResultsRelative }) => (shouldPositionResultsRelative ? 'relative' : 'absolute')}; top: 0; overflow-y: auto; max-height: ${({ resultsMaxHeight }) => resultsMaxHeight || '300px'}; width: 100%; /* prettier-ignore */ ${ResultContainer}:last-child { border-bottom: none; } `; const StyledFieldWrapper = styled(FieldWrapper).withConfig({ displayName: "brc-sc-StyledFieldWrapper", componentId: "brc-sc-t4nnx1" }) ` & ${Icon}, & ${LoadingDots} { position: absolute; right: 16px; top: 50%; transform: translateY(-50%); } & ${Icon} { width: 24px; } & ${LoadingDots} { color: ${({ theme }) => theme.navPrimary}; width: 56px; } & ${Input} { text-overflow: ellipsis; padding-right: 40px; ${({ shouldShowDropDown }) => shouldShowDropDown && 'border-radius: 4px 4px 0 0;'} } `; const StyledNoResults = styled.div.withConfig({ displayName: "brc-sc-StyledNoResults", componentId: "brc-sc-1amzcra" }) ` align-items: center; display: flex; flex-direction: column; margin-bottom: 24px; margin-top: 24px; `; const NoResultsIcon = styled(Icon).withConfig({ displayName: "brc-sc-NoResultsIcon", componentId: "brc-sc-px8svc" }) ` color: ${({ theme }) => theme.navNeutral400}; margin-bottom: 8px; `; const DefaultNoResults = ({ query }) => (_jsxs(StyledNoResults, { children: [_jsx(NoResultsIcon, { name: "actions/circle-faq" }), _jsxs(Copy, { children: ["No results for \"", query, "\""] })] })); const _SearchInput = (_a) => { var { autoFocus, className, errors = [], helperText, isLoading, isInvalid, label, lede, name, NoResults, onBlur = noop, onChange = noop, onFocus = noop, Result, required, results = [], resultsMaxHeight, resultToQuery = id, search, shouldPositionResultsRelative, touched, type, value } = _a, inputProps = __rest(_a, ["autoFocus", "className", "errors", "helperText", "isLoading", "isInvalid", "label", "lede", "name", "NoResults", "onBlur", "onChange", "onFocus", "Result", "required", "results", "resultsMaxHeight", "resultToQuery", "search", "shouldPositionResultsRelative", "touched", "type", "value"]); const [focusedResultIndex, setFocusedResultIndex] = useState(-1); const [canBeOpen, setCanBeOpen] = useState(false); const [query, setQuery] = useState(''); const [lastSearchedQuery, setLastSearchedQuery] = useState(''); const inputRef = useRef(null); const resultsRef = useRef(null); const shouldShowDropDown = !!(query.length && canBeOpen && query === lastSearchedQuery && !isLoading); const shouldShowResults = !!(shouldShowDropDown && results.length); const shouldShowNoResults = !!(shouldShowDropDown && !results.length); const RenderNoResults = NoResults || DefaultNoResults; const isVisited = touched || !!value; useEffect(() => { if (!value) { return; } const newQuery = resultToQuery(value); if (!newQuery) { return; } setQuery(newQuery); }, [resultToQuery, value]); useEffect(() => { if (autoFocus && inputRef.current) { focusWithoutScroll(inputRef.current); } }, [autoFocus]); const searchDebounced = useDebouncedCallback((query) => { setLastSearchedQuery(query); search(query); setFocusedResultIndex(-1); }, 500); const handleChange = ({ target }) => { var _a; const newQuery = (_a = target.value) !== null && _a !== void 0 ? _a : ''; setQuery(newQuery); onChange({ target: { name, value: null, }, }); if (newQuery.length > 0) { searchDebounced(newQuery); setCanBeOpen(true); } }; const handleInputFocus = (event) => { setCanBeOpen(true); onFocus(event); }; const focusResult = (index) => { var _a, _b, _c; const ref = (_b = (_a = resultsRef.current) === null || _a === void 0 ? void 0 : _a.children) === null || _b === void 0 ? void 0 : _b[index]; if (ref) { setFocusedResultIndex(index); ref.focus(); (_c = inputRef.current) === null || _c === void 0 ? void 0 : _c.focus(); } }; const goToNextResult = () => { const newIndex = focusedResultIndex + 1; if (newIndex < results.length) { focusResult(newIndex); } }; const goToPreviousResult = () => { const newIndex = focusedResultIndex - 1; if (newIndex > -2) { focusResult(newIndex); } }; const closeResults = () => { setCanBeOpen(false); setFocusedResultIndex(-1); }; const onSelectResult = (clickedResult) => { const result = clickedResult || results[focusedResultIndex]; closeResults(); onChange({ target: { name, value: result, }, }); }; const handleBlurOnInput = (event) => { if (!resultsRef.current || (resultsRef.current && !resultsRef.current.contains(event.relatedTarget))) { closeResults(); onBlur(event); } }; const handleKeyDownOnInput = (event) => { switch (event.keyCode) { case 40: // Down Arrow if (shouldShowResults) { event.preventDefault(); goToNextResult(); } else { setCanBeOpen(true); } break; case 9: // Tab if (shouldShowResults) { event.preventDefault(); if (event.shiftKey) { goToPreviousResult(); } else { goToNextResult(); } } break; case 38: // Up Arrow if (shouldShowResults) { event.preventDefault(); goToPreviousResult(); } break; case 27: // Escape if (focusedResultIndex > -1) { setFocusedResultIndex(-1); } else { setQuery(''); } break; case 13: // Enter if (focusedResultIndex > -1) { event.preventDefault(); onSelectResult(); } break; } }; return (_jsxs(SearchInputContainer, { className: className, children: [_jsxs(StyledFieldWrapper, { shouldShowDropDown: shouldShowDropDown, children: [lede && _jsx(Copy, { bold: true, children: lede }), _jsxs(Field, { isVisited: isVisited, children: [_jsx(Input, Object.assign({ autoComplete: "off", "data-testid": "search-input:input", name: name, onBlur: handleBlurOnInput, onChange: handleChange, onFocus: handleInputFocus, onKeyDown: handleKeyDownOnInput, type: type, ref: inputRef, required: required, value: query, isInvalid: isInvalid }, inputProps)), isLoading ? _jsx(LoadingDots, {}) : _jsx(Icon, { name: "system/search" }), _jsx(Label, { required: required, children: capitalize(label) })] })] }), _jsxs(ResultsContainer, { children: [shouldShowDropDown && (_jsxs(Results, { resultsMaxHeight: resultsMaxHeight, ref: resultsRef, shouldPositionResultsRelative: shouldPositionResultsRelative, children: [shouldShowNoResults && _jsx(RenderNoResults, { query: query }), shouldShowResults && results.map((result, index) => (_jsx(ResultContainer, { isActive: focusedResultIndex === index, onMouseDown: () => onSelectResult(result), tabIndex: 0, children: _jsx(Result, { result: result }) }, JSON.stringify(result))))] })), helperText && _jsx(Helper, { hasSpaceForHelper: true, helperText: helperText }), _jsx(Errors, { hasSpaceForErrors: true, children: !!errors.length && errors.map((err, i) => _jsx(Err, { children: err }, `err-${i}`)) })] })] })); }; export const SearchInput = styled(_SearchInput) ``; //# sourceMappingURL=search-input.js.map