Created
August 25, 2022 16:07
-
-
Save peterbe/095658ee05b806de365cbfd40b7f45f9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/components/Search.tsx b/components/Search.tsx | |
index 5bffca38715..80a07dd66af 100644 | |
--- a/components/Search.tsx | |
+++ b/components/Search.tsx | |
@@ -9,6 +9,8 @@ import { useTranslation } from 'components/hooks/useTranslation' | |
import { sendEvent, EventType } from 'components/lib/events' | |
import { useMainContext } from './context/MainContext' | |
import { DEFAULT_VERSION, useVersion } from 'components/hooks/useVersion' | |
+import { useRecentSearches } from 'components/hooks/useRecentSearches' | |
+import type { RecentSearch } from 'components/hooks/useRecentSearches' | |
import { useQuery } from 'components/hooks/useQuery' | |
import { Link } from 'components/Link' | |
import { useLanguages } from './context/LanguagesContext' | |
@@ -86,11 +88,13 @@ export function Search({ | |
revalidateOnReconnect: false, | |
} | |
) | |
+ const { recentSearches, addRecentSearch } = useRecentSearches() | |
const [previousResults, setPreviousResults] = useState<SearchResult[] | undefined>() | |
useEffect(() => { | |
if (results) { | |
setPreviousResults(results) | |
+ addRecentSearch(query, results.length) | |
} else if (!query) { | |
setPreviousResults(undefined) | |
} | |
@@ -156,6 +160,9 @@ export function Search({ | |
// Close panel if overlay is clicked | |
function closeSearch() { | |
setLocalQuery('') | |
+ if (inputRef.current) { | |
+ inputRef.current.blur() | |
+ } | |
} | |
// Prevent the page from refreshing when you "submit" the form | |
@@ -166,6 +173,11 @@ export function Search({ | |
} | |
} | |
+ const [inputFocussed, setInputFocussed] = useState(false) | |
+ | |
+ const showRecentSearches = inputFocussed && recentSearches.length > 0 | |
+ const showResults = Boolean(query || showRecentSearches) | |
+ | |
const SearchResults = ( | |
<> | |
<div | |
@@ -176,20 +188,30 @@ export function Search({ | |
'pt-9 color-bg-default color-shadow-medium position-absolute top-0 right-0', | |
styles.resultsContainer, | |
isHeaderSearch && styles.resultsContainerHeader, | |
- query ? 'd-block' : 'd-none', | |
- query && styles.resultsContainerOpen | |
+ showResults ? 'd-block' : 'd-none', | |
+ showResults && styles.resultsContainerOpen | |
)} | |
> | |
- <ShowSearchResults | |
- anchorRef={inputRef} | |
- isHeaderSearch={isHeaderSearch} | |
- isMobileSearch={isMobileSearch} | |
- isLoading={isLoading} | |
- results={previousResults} | |
- closeSearch={closeSearch} | |
- debug={debug} | |
- query={query} | |
- /> | |
+ {query ? ( | |
+ <ShowSearchResults | |
+ anchorRef={inputRef} | |
+ isHeaderSearch={isHeaderSearch} | |
+ isMobileSearch={isMobileSearch} | |
+ isLoading={isLoading} | |
+ results={previousResults} | |
+ closeSearch={closeSearch} | |
+ debug={debug} | |
+ query={query} | |
+ /> | |
+ ) : showRecentSearches ? ( | |
+ <ShowRecentSearches | |
+ anchorRef={inputRef} | |
+ isHeaderSearch={isHeaderSearch} | |
+ isMobileSearch={isMobileSearch} | |
+ searches={recentSearches} | |
+ closeSearch={closeSearch} | |
+ /> | |
+ ) : null} | |
</div> | |
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} | |
<div | |
@@ -220,8 +242,8 @@ export function Search({ | |
variant === 'expanded' && 'py-3', | |
isHeaderSearch && styles.searchInputHeader, | |
!isHeaderSearch && 'width-full', | |
- isHeaderSearch && query && styles.searchInputExpanded, | |
- isHeaderSearch && query && 'position-absolute top-0 right-0' | |
+ isHeaderSearch && showResults && styles.searchInputExpanded, | |
+ isHeaderSearch && showResults && 'position-absolute top-0 right-0' | |
)} | |
style={{ | |
background: `var(--color-canvas-default) url("/assets/images/octicons/search-${iconSize}.svg") no-repeat ${ | |
@@ -237,6 +259,16 @@ export function Search({ | |
maxLength={512} | |
onChange={onSearch} | |
value={localQuery} | |
+ onFocus={() => { | |
+ console.log('Focussed!') | |
+ | |
+ setInputFocussed(true) | |
+ }} | |
+ onBlur={() => { | |
+ console.log('Blurred') | |
+ | |
+ setInputFocussed(false) | |
+ }} | |
/> | |
<button className="d-none" type="submit" title="Submit the search query." hidden /> | |
</form> | |
@@ -520,3 +552,113 @@ function ShowSearchResults({ | |
</p> | |
) | |
} | |
+ | |
+function ShowRecentSearches({ | |
+ anchorRef, | |
+ isHeaderSearch, | |
+ isMobileSearch, | |
+ searches, | |
+ closeSearch, | |
+}: { | |
+ anchorRef: RefObject<HTMLElement> | |
+ isHeaderSearch: boolean | |
+ isMobileSearch: boolean | |
+ | |
+ searches: RecentSearch[] | |
+ closeSearch: () => void | |
+}) { | |
+ const { clearRecentSearches } = useRecentSearches() | |
+ | |
+ const ActionListResults = ( | |
+ <div | |
+ data-testid="search-results" | |
+ className={cx( | |
+ 'mt-3', | |
+ isHeaderSearch && styles.headerSearchResults, | |
+ isHeaderSearch && 'overflow-auto' | |
+ )} | |
+ > | |
+ <ActionList | |
+ items={searches.map(({ locale, version, query, results }) => { | |
+ let href = `/${locale}` | |
+ if (version !== DEFAULT_VERSION) { | |
+ href += `/${version}` | |
+ } | |
+ href += `?${new URLSearchParams({ query }).toString()}` | |
+ return { | |
+ key: locale + version + query, | |
+ text: query, | |
+ renderItem: () => ( | |
+ <ActionList.Item as="div"> | |
+ <div | |
+ data-testid="recent-search" | |
+ className={cx('list-style-none', styles.resultsContainer)} | |
+ > | |
+ <div className={cx('py-2 px-3')}> | |
+ <Link href={href} className="no-underline color-fg-default"> | |
+ <i>{query}</i> | |
+ </Link>{' '} | |
+ found {results} | |
+ </div> | |
+ </div> | |
+ </ActionList.Item> | |
+ ), | |
+ } | |
+ })} | |
+ /> | |
+ <button | |
+ className="btn btn-outline float-right" | |
+ onClick={() => { | |
+ clearRecentSearches() | |
+ }} | |
+ > | |
+ clear | |
+ </button> | |
+ </div> | |
+ ) | |
+ | |
+ console.log('Rendering Overlay') | |
+ | |
+ return ( | |
+ <div> | |
+ {!isHeaderSearch && !isMobileSearch ? ( | |
+ <> | |
+ <Overlay | |
+ initialFocusRef={anchorRef} | |
+ returnFocusRef={anchorRef} | |
+ ignoreClickRefs={[anchorRef]} | |
+ onEscape={() => closeSearch()} | |
+ onClickOutside={() => closeSearch()} | |
+ aria-labelledby="title" | |
+ sx={ | |
+ isHeaderSearch | |
+ ? { | |
+ background: 'none', | |
+ boxShadow: 'none', | |
+ position: 'static', | |
+ overflowY: 'auto', | |
+ maxHeight: '80vh', | |
+ maxWidth: '96%', | |
+ margin: '1.5em 2em 0 0.5em', | |
+ scrollbarWidth: 'none', | |
+ } | |
+ : window.innerWidth < 1012 | |
+ ? { | |
+ marginTop: '28rem', | |
+ marginLeft: '5rem', | |
+ } | |
+ : { | |
+ marginTop: '15rem', | |
+ marginLeft: '5rem', | |
+ } | |
+ } | |
+ > | |
+ {ActionListResults} | |
+ </Overlay> | |
+ </> | |
+ ) : ( | |
+ ActionListResults | |
+ )} | |
+ </div> | |
+ ) | |
+} | |
diff --git a/components/hooks/useRecentSearches.ts b/components/hooks/useRecentSearches.ts | |
new file mode 100644 | |
index 00000000000..655939eaf9f | |
--- /dev/null | |
+++ b/components/hooks/useRecentSearches.ts | |
@@ -0,0 +1,128 @@ | |
+import { useEffect, useState } from 'react' | |
+import { useRouter } from 'next/router' | |
+ | |
+import { DEFAULT_VERSION } from './useVersion' | |
+ | |
+export interface RecentSearch { | |
+ query: string | |
+ results: number | |
+ version: string | |
+ locale: string | |
+ date: Date | |
+} | |
+ | |
+type RecentSearchesInfo = { | |
+ recentSearches: RecentSearch[] | |
+ addRecentSearch: (query: string, results: number) => void | |
+ clearRecentSearches: () => void | |
+} | |
+ | |
+const STORAGE_KEY = 'recentsearches' | |
+ | |
+function store(struct: RecentSearch[]) { | |
+ const storage = process.env.NODE_ENV === 'development' ? sessionStorage : localStorage | |
+ try { | |
+ storage.setItem(STORAGE_KEY, JSON.stringify(struct)) | |
+ } catch (err) { | |
+ console.warn('Unable to storage in local storage', err) | |
+ } | |
+} | |
+ | |
+function load(): RecentSearch[] { | |
+ const storage = process.env.NODE_ENV === 'development' ? sessionStorage : localStorage | |
+ try { | |
+ return JSON.parse(storage.getItem(STORAGE_KEY) || '[]') | |
+ } catch (err) { | |
+ console.warn('Unable to storage in local storage', err) | |
+ return [] | |
+ } | |
+} | |
+ | |
+export const useRecentSearches = (): RecentSearchesInfo => { | |
+ const router = useRouter() | |
+ | |
+ const [recentSearches, setRecentSearches] = useState<RecentSearch[]>([]) | |
+ | |
+ useEffect(() => { | |
+ const fromStorage = load() | |
+ if (fromStorage.length > 0) { | |
+ setRecentSearches(fromStorage) | |
+ } | |
+ }, []) | |
+ | |
+ useEffect(() => { | |
+ if (recentSearches.length > 0) { | |
+ console.log('STORE IN STORAGE', recentSearches) | |
+ store(recentSearches) | |
+ } | |
+ }, [recentSearches]) | |
+ | |
+ function addRecentSearch(query: string, results: number) { | |
+ if (!results) return | |
+ const versionId = router.query.versionId || DEFAULT_VERSION | |
+ | |
+ if (!versionId || typeof versionId !== 'string') { | |
+ console.log('no versionId', router.query) | |
+ | |
+ return | |
+ } | |
+ const { locale } = router | |
+ if (!locale) return | |
+ | |
+ // // If the most recently added one is the same and within the same time | |
+ // // do nothing. | |
+ // if (recentSearches.length > 0) { | |
+ // const lastSearch = recentSearches[0] | |
+ // if ( | |
+ // lastSearch.query === query && | |
+ // lastSearch.results === results && | |
+ // lastSearch.version === versionId && | |
+ // lastSearch.locale === locale | |
+ // ) { | |
+ // // console.log('New recent query not new', query) | |
+ | |
+ // return | |
+ // } | |
+ // } | |
+ | |
+ setRecentSearches((prevState) => { | |
+ const thisSearch = { | |
+ query, | |
+ results, | |
+ version: versionId, | |
+ locale, | |
+ date: new Date(), | |
+ } | |
+ | |
+ const merged = [ | |
+ thisSearch, | |
+ ...prevState.filter((previousSearch, i) => { | |
+ return !i && query.startsWith(previousSearch.query) | |
+ }), | |
+ ] | |
+ console.log( | |
+ 'MERGED', | |
+ merged.map((x) => x.query) | |
+ ) | |
+ | |
+ return merged.slice(0, 10) | |
+ }) | |
+ } | |
+ | |
+ function clearRecentSearches() { | |
+ console.log('CLEARING') | |
+ | |
+ setRecentSearches([]) | |
+ store([]) | |
+ } | |
+ return { | |
+ recentSearches: recentSearches.filter((search) => { | |
+ return ( | |
+ search.version === (router.query.versionId || DEFAULT_VERSION) && | |
+ search.locale === router.locale | |
+ ) | |
+ }), | |
+ addRecentSearch, | |
+ clearRecentSearches, | |
+ } | |
+} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment