Add Advanced Search toggle button

It shows all of the suggestion options!

I feel like the state stuff on this isn't super robust, I hope it works okay!
This commit is contained in:
Emi Matchu 2021-01-21 17:23:34 -08:00
parent 5799c725ff
commit add7fc0cb6

View file

@ -9,9 +9,15 @@ import {
InputLeftAddon, InputLeftAddon,
InputLeftElement, InputLeftElement,
InputRightElement, InputRightElement,
Tooltip,
useColorModeValue, useColorModeValue,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { CloseIcon, SearchIcon } from "@chakra-ui/icons"; import {
ChevronDownIcon,
ChevronUpIcon,
CloseIcon,
SearchIcon,
} from "@chakra-ui/icons";
import { ClassNames } from "@emotion/react"; import { ClassNames } from "@emotion/react";
import Autosuggest from "react-autosuggest"; import Autosuggest from "react-autosuggest";
@ -48,6 +54,7 @@ function SearchToolbar({
boxShadow = null, boxShadow = null,
}) { }) {
const [suggestions, setSuggestions] = React.useState([]); const [suggestions, setSuggestions] = React.useState([]);
const [advancedSearchIsOpen, setAdvancedSearchIsOpen] = React.useState(false);
const { isLoggedIn } = useCurrentUser(); const { isLoggedIn } = useCurrentUser();
// NOTE: This query should always load ~instantly, from the client cache. // NOTE: This query should always load ~instantly, from the client cache.
@ -103,8 +110,9 @@ function SearchToolbar({
{...otherContainerProps} {...otherContainerProps}
borderBottomRadius="md" borderBottomRadius="md"
boxShadow="md" boxShadow="md"
overflow="hidden" overflow="auto"
transition="all 0.4s" transition="all 0.4s"
maxHeight="48"
className={cx( className={cx(
className, className,
css` css`
@ -115,12 +123,22 @@ function SearchToolbar({
)} )}
> >
{children} {children}
{!children && advancedSearchIsOpen && (
<Box
padding="4"
fontSize="sm"
fontStyle="italic"
textAlign="center"
>
No more filters available!
</Box>
)}
</Box> </Box>
)} )}
</ClassNames> </ClassNames>
); );
}, },
[] [advancedSearchIsOpen]
); );
// When we change the query filters, clear out the suggestions. // When we change the query filters, clear out the suggestions.
@ -148,11 +166,23 @@ function SearchToolbar({
); );
} }
const allSuggestions = getSuggestions(null, query, zoneLabels, isLoggedIn, {
showAll: true,
});
// Once you remove the final suggestion available, close Advanced Search. We
// have placeholder text available, sure, but this feels more natural!
React.useEffect(() => {
if (allSuggestions.length === 0) {
setAdvancedSearchIsOpen(false);
}
}, [allSuggestions.length]);
const focusBorderColor = useColorModeValue("green.600", "green.400"); const focusBorderColor = useColorModeValue("green.600", "green.400");
return ( return (
<Autosuggest <Autosuggest
suggestions={suggestions} suggestions={advancedSearchIsOpen ? allSuggestions : suggestions}
onSuggestionsFetchRequested={({ value }) => { onSuggestionsFetchRequested={({ value }) => {
// HACK: I'm not sure why, but apparently this gets called with value // HACK: I'm not sure why, but apparently this gets called with value
// set to the _chosen suggestion_ after choosing it? Has that // set to the _chosen suggestion_ after choosing it? Has that
@ -162,7 +192,8 @@ function SearchToolbar({
} }
}} }}
onSuggestionSelected={(e, { suggestion }) => { onSuggestionSelected={(e, { suggestion }) => {
const valueWithoutLastWord = query.value.match(/^(.*?)\s*\S+$/)[1]; const valueWithoutLastWord =
query.value.match(/^(.*?)\s*\S+$/)?.[1] || query.value;
onChange({ onChange({
...query, ...query,
value: valueWithoutLastWord, value: valueWithoutLastWord,
@ -194,24 +225,45 @@ function SearchToolbar({
autoFocus={autoFocus} autoFocus={autoFocus}
{...inputProps} {...inputProps}
/> />
{!searchQueryIsEmpty(query) && ( <InputRightElement
<InputRightElement> width="auto"
justifyContent="flex-end"
paddingRight="2px"
paddingY="2px"
>
{!searchQueryIsEmpty(query) && (
<Tooltip label="Clear">
<IconButton
icon={<CloseIcon fontSize="0.6em" />}
color="gray.400"
variant="ghost"
height="100%"
marginLeft="1"
aria-label="Clear search"
onClick={() => {
setSuggestions([]);
onChange(emptySearchQuery);
}}
/>
</Tooltip>
)}
<Tooltip label="Advanced search">
<IconButton <IconButton
icon={<CloseIcon />} icon={
advancedSearchIsOpen ? (
<ChevronUpIcon fontSize="1.5em" />
) : (
<ChevronDownIcon fontSize="1.5em" />
)
}
color="gray.400" color="gray.400"
variant="ghost" variant="ghost"
colorScheme="green" height="100%"
aria-label="Clear search" aria-label="Open advanced search"
onClick={() => { onClick={() => setAdvancedSearchIsOpen((isOpen) => !isOpen)}
setSuggestions([]);
onChange(emptySearchQuery);
}}
// Big style hacks here!
height="calc(100% - 2px)"
marginRight="2px"
/> />
</InputRightElement> </Tooltip>
)} </InputRightElement>
</InputGroup> </InputGroup>
)} )}
inputProps={{ inputProps={{
@ -256,46 +308,64 @@ function SearchToolbar({
); );
} }
function getSuggestions(value, query, zoneLabels, isLoggedIn) { function getSuggestions(
if (!value) { value,
query,
zoneLabels,
isLoggedIn,
{ showAll = false } = {}
) {
if (!value && !showAll) {
return []; return [];
} }
const words = value.split(/\s+/); const words = (value || "").split(/\s+/);
const lastWord = words[words.length - 1]; const lastWord = words[words.length - 1];
if (lastWord.length < 2) { if (lastWord.length < 2 && !showAll) {
return []; return [];
} }
const suggestions = []; const suggestions = [];
if (query.filterToItemKind == null) { if (query.filterToItemKind == null) {
if (wordMatches("NC", lastWord) || wordMatches("Neocash", lastWord)) { if (
wordMatches("NC", lastWord) ||
wordMatches("Neocash", lastWord) ||
showAll
) {
suggestions.push({ itemKind: "NC", text: "Neocash items" }); suggestions.push({ itemKind: "NC", text: "Neocash items" });
} }
if (wordMatches("NP", lastWord) || wordMatches("Neopoints", lastWord)) { if (
wordMatches("NP", lastWord) ||
wordMatches("Neopoints", lastWord) ||
showAll
) {
suggestions.push({ itemKind: "NP", text: "Neopoint items" }); suggestions.push({ itemKind: "NP", text: "Neopoint items" });
} }
if (wordMatches("PB", lastWord) || wordMatches("Paintbrush", lastWord)) { if (
wordMatches("PB", lastWord) ||
wordMatches("Paintbrush", lastWord) ||
showAll
) {
suggestions.push({ itemKind: "PB", text: "Paintbrush items" }); suggestions.push({ itemKind: "PB", text: "Paintbrush items" });
} }
} }
if (isLoggedIn && query.filterToCurrentUserOwnsOrWants == null) { if (isLoggedIn && query.filterToCurrentUserOwnsOrWants == null) {
if (wordMatches("Items you own", lastWord)) { if (wordMatches("Items you own", lastWord) || showAll) {
suggestions.push({ userOwnsOrWants: "OWNS", text: "Items you own" }); suggestions.push({ userOwnsOrWants: "OWNS", text: "Items you own" });
} }
if (wordMatches("Items you want", lastWord)) { if (wordMatches("Items you want", lastWord) || showAll) {
suggestions.push({ userOwnsOrWants: "WANTS", text: "Items you want" }); suggestions.push({ userOwnsOrWants: "WANTS", text: "Items you want" });
} }
} }
if (query.filterToZoneLabel == null) { if (query.filterToZoneLabel == null) {
for (const zoneLabel of zoneLabels) { for (const zoneLabel of zoneLabels) {
if (wordMatches(zoneLabel, lastWord)) { if (wordMatches(zoneLabel, lastWord) || showAll) {
suggestions.push({ zoneLabel, text: `Zone: ${zoneLabel}` }); suggestions.push({ zoneLabel, text: `Zone: ${zoneLabel}` });
} }
} }