-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2a052af
commit 8c4d3ef
Showing
11 changed files
with
4,778 additions
and
4,894 deletions.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
import '@algolia/autocomplete-theme-classic'; | ||
import type {SearchClient} from 'algoliasearch/lite'; | ||
import type {BaseItem} from '@algolia/autocomplete-core'; | ||
import type {AutocompleteOptions} from '@algolia/autocomplete-js'; | ||
|
||
import { | ||
createElement, | ||
Fragment, | ||
useEffect, | ||
useMemo, | ||
useRef, | ||
useState, | ||
} from 'react'; | ||
import {createRoot, Root} from 'react-dom/client'; | ||
|
||
import {useSearchBox} from 'react-instantsearch'; | ||
import {autocomplete} from '@algolia/autocomplete-js'; | ||
import {createLocalStorageRecentSearchesPlugin} from '@algolia/autocomplete-plugin-recent-searches'; | ||
import {createQuerySuggestionsPlugin} from '@algolia/autocomplete-plugin-query-suggestions'; | ||
import {debounce} from '@algolia/autocomplete-shared'; | ||
|
||
import { | ||
INSTANT_SEARCH_INDEX_NAME, | ||
INSTANT_SEARCH_QUERY_SUGGESTIONS, | ||
} from 'src/constants'; // Only import necessary constants | ||
|
||
type AutocompleteProps = Partial<AutocompleteOptions<BaseItem>> & { | ||
searchClient: SearchClient; | ||
className?: string; | ||
}; | ||
|
||
type SetInstantSearchUiStateOptions = { | ||
query: string; | ||
}; | ||
|
||
export function Autocomplete({ | ||
searchClient, | ||
className, | ||
...autocompleteProps | ||
}: AutocompleteProps) { | ||
// Refs for DOM elements and the root for rendering the panel | ||
const autocompleteContainer = useRef<HTMLDivElement>(null); | ||
const panelRootRef = useRef<Root | null>(null); | ||
const rootRef = useRef<HTMLElement | null>(null); | ||
|
||
// Hooks for managing state from Algolia's useSearchBox and usePagination | ||
const {query, refine: setQuery} = useSearchBox(); | ||
|
||
// Local state to manage instant search UI state with debounced updates | ||
const [instantSearchUiState, setInstantSearchUiState] = | ||
useState<SetInstantSearchUiStateOptions>({query}); | ||
const debouncedSetInstantSearchUiState = debounce( | ||
setInstantSearchUiState, | ||
500 | ||
); | ||
|
||
// Update the query in Algolia's useSearchBox when the instant search UI state changes | ||
useEffect(() => { | ||
setQuery(instantSearchUiState.query); | ||
}, [instantSearchUiState, setQuery]); | ||
|
||
// Memoize the creation of plugins for recent searches and query suggestions | ||
const plugins = useMemo(() => { | ||
const recentSearches = createLocalStorageRecentSearchesPlugin({ | ||
key: 'instantsearch', | ||
limit: 3, | ||
transformSource({source}) { | ||
return { | ||
...source, | ||
onSelect({item}) { | ||
setInstantSearchUiState({query: item.label}); | ||
}, | ||
}; | ||
}, | ||
}); | ||
|
||
const querySuggestions = createQuerySuggestionsPlugin({ | ||
searchClient, | ||
indexName: INSTANT_SEARCH_QUERY_SUGGESTIONS, | ||
getSearchParams() { | ||
return recentSearches.data!.getAlgoliaSearchParams({ | ||
hitsPerPage: 6, | ||
}); | ||
}, | ||
transformSource({source}) { | ||
return { | ||
...source, | ||
sourceId: 'querySuggestionsPlugin', | ||
onSelect({item}) { | ||
// Update the instant search state with the selected suggestion | ||
setInstantSearchUiState({query: item.name}); | ||
|
||
// Update the input field with the selected suggestion | ||
const inputElement = document.querySelector('.aa-Input'); | ||
if (inputElement) { | ||
inputElement.value = item.name; | ||
} | ||
|
||
// Perform a search on the main index and add the query to recent searches | ||
searchClient.search([{ | ||
indexName: INSTANT_SEARCH_INDEX_NAME, | ||
query: item.name, | ||
params: {hitsPerPage: 10}, | ||
}]).then(() => { | ||
recentSearches.data!.addItem({id: item.name, label: item.name}); | ||
}).catch(err => { | ||
console.error('Search failed:', err); | ||
}); | ||
|
||
debouncedSetInstantSearchUiState({query: item.name}); | ||
}, | ||
templates: { | ||
item({item}) { | ||
return ( | ||
<div className="aa-ItemWrapper"> | ||
<div className="aa-ItemContent"> | ||
<div className="aa-ItemTitle"> | ||
{item.name} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}, | ||
}, | ||
}; | ||
}, | ||
}); | ||
|
||
return [recentSearches, querySuggestions]; | ||
}, [searchClient, debouncedSetInstantSearchUiState]); | ||
|
||
// Initialize the autocomplete instance with the specified plugins and options | ||
useEffect(() => { | ||
if (!autocompleteContainer.current) { | ||
return; | ||
} | ||
const autocompleteInstance = autocomplete({ | ||
...autocompleteProps, | ||
container: autocompleteContainer.current, | ||
initialState: {query}, | ||
insights: true, | ||
plugins, | ||
onSubmit({state}) { | ||
// Perform a search on form submission | ||
setInstantSearchUiState({query: state.query}); | ||
|
||
searchClient.search([{ | ||
indexName: INSTANT_SEARCH_INDEX_NAME, | ||
query: state.query, | ||
params: {hitsPerPage: 10}, | ||
}]).catch(err => { | ||
console.error('Search failed:', err); | ||
}); | ||
}, | ||
onStateChange({prevState, state}) { | ||
// Update the instant search UI state on query change | ||
if (prevState.query !== state.query) { | ||
debouncedSetInstantSearchUiState({query: state.query}); | ||
} | ||
}, | ||
renderer: { | ||
createElement, Fragment, render: () => { | ||
} | ||
}, | ||
render({children}, root) { | ||
// Ensure the root is mounted correctly for rendering | ||
if (!panelRootRef.current || rootRef.current !== root) { | ||
rootRef.current = root; | ||
panelRootRef.current?.unmount(); | ||
panelRootRef.current = createRoot(root); | ||
} | ||
|
||
panelRootRef.current.render(children); | ||
}, | ||
}); | ||
|
||
return () => autocompleteInstance.destroy(); | ||
}, [plugins, searchClient, autocompleteProps, query, debouncedSetInstantSearchUiState]); | ||
|
||
return ( | ||
<div className={className}> | ||
<div ref={autocompleteContainer}/> | ||
</div> | ||
); | ||
} | ||
|
||
export default Autocomplete; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import * as React from 'react'; | ||
import {useInstantSearch} from 'react-instantsearch'; | ||
|
||
type EmptyQueryBoundaryProps = { | ||
children: React.ReactNode; | ||
fallback: React.ReactNode; | ||
}; | ||
|
||
const EmptyQueryBoundary: React.FC<EmptyQueryBoundaryProps> = ({children, fallback}) => { | ||
const {indexUiState} = useInstantSearch(); | ||
|
||
// Render the fallback if the query is empty or too short | ||
if (!indexUiState.query || indexUiState.query.length <= 1) { | ||
return ( | ||
<> | ||
{fallback} | ||
<div hidden>{children}</div> | ||
</> | ||
); | ||
} | ||
|
||
// Render children if the query is valid | ||
return <>{children}</>; | ||
}; | ||
|
||
export default EmptyQueryBoundary; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import React from 'react'; | ||
import { Snippet } from 'react-instantsearch'; | ||
import { useRouter } from 'next/router'; | ||
|
||
type HitProps = { | ||
hit: { | ||
id: string; | ||
name: string; | ||
publisher_id: string; | ||
total_install: number; | ||
version: string; | ||
}; | ||
}; | ||
|
||
const Hit: React.FC<HitProps> = ({ hit }) => { | ||
const router = useRouter(); | ||
|
||
const handleClick = () => { | ||
router.push(`/nodes/${hit.id}`); | ||
}; | ||
|
||
return ( | ||
<div | ||
className="flex flex-col bg-gray-800 rounded-lg shadow cursor-pointer h-full dark:border-gray-700 lg:p-4" | ||
onClick={handleClick} | ||
> | ||
<div className="flex flex-col px-4"> | ||
<h6 className="mb-2 text-base font-bold tracking-tight text-white break-words"> | ||
<Snippet hit={hit} attribute="name" /> | ||
</h6> | ||
|
||
{hit.version && ( | ||
<p className="mb-1 text-xs tracking-tight text-white"> | ||
<span>v{hit.version}</span> | ||
</p> | ||
)} | ||
|
||
<p className="mb-1 text-xs font-light text-white text-nowrap mt-2"> | ||
{hit.publisher_id} | ||
</p> | ||
|
||
<div className="flex items-center flex-start align-center gap-1 mt-2"> | ||
{hit.total_install != 0 && ( | ||
<p className="flex justify-center text-center align-center"> | ||
<svg | ||
className="w-4 h-4 text-white" | ||
aria-hidden="true" | ||
xmlns="http://www.w3.org/2000/svg" | ||
width="24" | ||
height="24" | ||
fill="none" | ||
viewBox="0 0 24 24" | ||
> | ||
<path | ||
stroke="currentColor" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
strokeWidth="2" | ||
d="M12 13V4M7 14H5a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1h-2m-1-5-4 5-4-5m9 8h.01" | ||
/> | ||
</svg> | ||
<p className="ml-1 text-xs font-bold text-white"> | ||
{hit.total_install} | ||
</p> | ||
</p> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default Hit; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './Autocomplete'; | ||
export * from './EmptyQueryBoundary' |
Oops, something went wrong.