import { SearchIcon } from "@chakra-ui/icons"; import { Input, InputGroup, InputLeftElement, InputProps, InputRightElement, useDisclosure, Button, IconButton, } from "@chakra-ui/react"; import { useContext, createContext, FunctionComponent, useEffect, useRef, ChangeEventHandler, FormEventHandler, } from "react"; import testIds from "./testIds"; import { useAnalytics } from "../../contexts/Analytics"; import { clickEvent, eventName } from "../../contexts/Analytics/util"; import { useCatalogSearch } from "../../hooks/useCatalogSearch"; import { useSearch } from "../../hooks/useSearch"; import { Form } from "../Form"; export interface SearchBarProps extends Omit { "data-event"?: string; defaultQuery?: string; hasButton?: boolean; value?: string; onChange?: ChangeEventHandler; onSubmit?: FormEventHandler; } const SearchBarState = createContext< { dataEvent?: string; query: string; isOpen: boolean } | undefined >(undefined); export const useSearchBarState = () => { const state = useContext(SearchBarState); if (!state) { throw new Error("This component must be a child of a "); } return state; }; /** * Exposes a Search component that provides a default search implementation. This behavior can be overridden by defining `value`, `onChange`, and `onSubmit` props. * Additionally, it's behavior can be extended with `` and `` * ```tsx * // Minimal use-case * import { SearchBar } from "components/SearchBar"; * * * // With extended behavior * import { SearchBar, SearchOverlay, SearchSuggestions } from "components/SearchBar"; * * * * * * ``` */ export const SearchBar: FunctionComponent = ({ children, "data-event": dataEvent, hasButton, onSubmit, value, onChange, ...inputProps }) => { const disclosure = useDisclosure(); const inputRef = useRef(null); const searchAPI = useCatalogSearch(); const catalog = useSearch(); const { trackCustomEvent } = useAnalytics(); const roundedCatalogLength = Math.floor((catalog.length ?? 0) / 100) * 100; const placeholder = `Search ${ roundedCatalogLength > 0 ? `${roundedCatalogLength}+ ` : "" }construct libraries`; useEffect(() => { // Handle closing disclosures when user clicks outside of input. // We cannot rely on the input's onBlur due to left & right elements (icon / button) triggering it const clickListener = (e: MouseEvent) => { if (!inputRef.current || !e.target) { return; } if (!inputRef.current.contains(e.target as Node)) { disclosure.onClose(); } }; // Closes disclosures when Esc key is pressed const kbdListener = (e: KeyboardEvent) => { if (e.key === "Escape") { inputRef.current?.blur?.(); disclosure.onClose(); } }; window.addEventListener("keyup", kbdListener); window.addEventListener("click", clickListener); return () => { window.removeEventListener("keyup", kbdListener); window.removeEventListener("click", clickListener); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return (
{hasButton && ( )} { disclosure.onOpen(); if (dataEvent) { trackCustomEvent( clickEvent({ name: eventName(dataEvent, "Input") }) ); } }} placeholder={placeholder} pr={hasButton ? { base: "none", md: "9rem" } : undefined} ref={inputRef} value={value ?? searchAPI.query} {...inputProps} /> {hasButton ? ( ) : ( } type="submit" variant="ghost" > )} {children}
); };