From 5d2fe252ec3d088bbe59f13e8ed3e65e28d7f18a Mon Sep 17 00:00:00 2001 From: DrMint <29893320+DrMint@users.noreply.github.com> Date: Thu, 11 May 2023 00:41:36 +0200 Subject: [PATCH] Focus on search input when opening search popup --- src/components/Containers/Popup.tsx | 11 ++++- src/components/Inputs/TextInput.tsx | 58 +++++++++++++-------------- src/components/Panels/SearchPopup.tsx | 32 ++++++++++----- 3 files changed, 58 insertions(+), 43 deletions(-) diff --git a/src/components/Containers/Popup.tsx b/src/components/Containers/Popup.tsx index ef475b3..d3fe162 100644 --- a/src/components/Containers/Popup.tsx +++ b/src/components/Containers/Popup.tsx @@ -11,6 +11,7 @@ import { Button } from "components/Inputs/Button"; */ interface Props { + onOpen?: () => void; onCloseRequest?: () => void; isVisible: boolean; children: React.ReactNode; @@ -23,6 +24,7 @@ interface Props { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ export const Popup = ({ + onOpen, onCloseRequest, isVisible, children, @@ -47,13 +49,18 @@ export const Popup = ({ if (isVisible) { setHidden(false); // We delay the visiblity of the element so that the opening animation is played - timeouts.push(setTimeout(() => setActuallyVisible(true), 100)); + timeouts.push( + setTimeout(() => { + setActuallyVisible(true); + onOpen?.(); + }, 100) + ); } else { setActuallyVisible(false); timeouts.push(setTimeout(() => setHidden(true), 600)); } return () => timeouts.forEach(clearTimeout); - }, [isVisible]); + }, [isVisible, onOpen]); return isHidden ? ( <> diff --git a/src/components/Inputs/TextInput.tsx b/src/components/Inputs/TextInput.tsx index 460b8af..f5e814d 100644 --- a/src/components/Inputs/TextInput.tsx +++ b/src/components/Inputs/TextInput.tsx @@ -1,3 +1,4 @@ +import { forwardRef } from "react"; import { Ico } from "components/Ico"; import { cIf, cJoin } from "helpers/className"; import { isDefinedAndNotEmpty } from "helpers/asserts"; @@ -18,34 +19,31 @@ interface Props { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ -export const TextInput = ({ - value, - onChange, - className, - name, - placeholder, - disabled = false, -}: Props): JSX.Element => ( -
- { - onChange(event.target.value); - }} - /> - {isDefinedAndNotEmpty(value) && ( -
- !disabled && onChange("")} - /> -
- )} -
+export const TextInput = forwardRef( + ({ value, onChange, className, name, placeholder, disabled = false }, ref) => ( +
+ { + onChange(event.target.value); + }} + /> + {isDefinedAndNotEmpty(value) && ( +
+ !disabled && onChange("")} + /> +
+ )} +
+ ) ); +TextInput.displayName = "TextInput"; diff --git a/src/components/Panels/SearchPopup.tsx b/src/components/Panels/SearchPopup.tsx index 78a0952..8f5bbcf 100644 --- a/src/components/Panels/SearchPopup.tsx +++ b/src/components/Panels/SearchPopup.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useCallback, useRef, useState } from "react"; import { MaterialSymbol } from "material-symbols"; import { Popup } from "components/Containers/Popup"; import { sendAnalytics } from "helpers/analytics"; @@ -55,13 +55,13 @@ export const SearchPopup = (): JSX.Element => { const { format } = useFormat(); const [multiResult, setMultiResult] = useState({}); - useEffect(() => { + const fetchSearchResults = useCallback((q: string) => { const fetchMultiResult = async () => { const searchResults = ( await meiliMultiSearch([ { indexUid: MeiliIndices.LIBRARY_ITEM, - q: query, + q, limit: SEARCH_LIMIT, attributesToRetrieve: [ "title", @@ -80,7 +80,7 @@ export const SearchPopup = (): JSX.Element => { }, { indexUid: MeiliIndices.CONTENT, - q: query, + q, limit: SEARCH_LIMIT, attributesToRetrieve: ["translations", "id", "slug", "categories", "type", "thumbnail"], attributesToHighlight: ["translations"], @@ -88,7 +88,7 @@ export const SearchPopup = (): JSX.Element => { }, { indexUid: MeiliIndices.VIDEOS, - q: query, + q, limit: SEARCH_LIMIT, attributesToRetrieve: [ "title", @@ -104,7 +104,7 @@ export const SearchPopup = (): JSX.Element => { }, { indexUid: MeiliIndices.POST, - q: query, + q, limit: SEARCH_LIMIT, attributesToRetrieve: ["translations", "thumbnail", "slug", "date", "categories"], attributesToHighlight: [ @@ -117,7 +117,7 @@ export const SearchPopup = (): JSX.Element => { }, { indexUid: MeiliIndices.WEAPON, - q: query, + q, limit: SEARCH_LIMIT, attributesToHighlight: ["translations.description", "translations.names"], attributesToCrop: ["translations.description"], @@ -125,7 +125,7 @@ export const SearchPopup = (): JSX.Element => { }, { indexUid: MeiliIndices.WIKI_PAGE, - q: query, + q, limit: SEARCH_LIMIT, attributesToHighlight: [ "translations.title", @@ -184,12 +184,16 @@ export const SearchPopup = (): JSX.Element => { setMultiResult(result); }; - if (query === "") { + if (q === "") { setMultiResult({}); } else { fetchMultiResult(); } - }, [query]); + + setQuery(q); + }, []); + + const searchInputRef = useRef(null); return ( { setSearchOpened(false); sendAnalytics("Search", "Close search"); }} + onOpen={() => searchInputRef.current?.focus()} fillViewport>

{format("search")}

- +
{isDefined(multiResult.libraryItems) && (