diff --git a/src/app/WardrobePage/SearchToolbar.js b/src/app/WardrobePage/SearchToolbar.js index 626af13..d348049 100644 --- a/src/app/WardrobePage/SearchToolbar.js +++ b/src/app/WardrobePage/SearchToolbar.js @@ -9,9 +9,15 @@ import { InputLeftAddon, InputLeftElement, InputRightElement, + Tooltip, useColorModeValue, } 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 Autosuggest from "react-autosuggest"; @@ -48,6 +54,7 @@ function SearchToolbar({ boxShadow = null, }) { const [suggestions, setSuggestions] = React.useState([]); + const [advancedSearchIsOpen, setAdvancedSearchIsOpen] = React.useState(false); const { isLoggedIn } = useCurrentUser(); // NOTE: This query should always load ~instantly, from the client cache. @@ -103,8 +110,9 @@ function SearchToolbar({ {...otherContainerProps} borderBottomRadius="md" boxShadow="md" - overflow="hidden" + overflow="auto" transition="all 0.4s" + maxHeight="48" className={cx( className, css` @@ -115,12 +123,22 @@ function SearchToolbar({ )} > {children} + {!children && advancedSearchIsOpen && ( + + No more filters available! + + )} )} ); }, - [] + [advancedSearchIsOpen] ); // 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"); return ( { // HACK: I'm not sure why, but apparently this gets called with value // set to the _chosen suggestion_ after choosing it? Has that @@ -162,7 +192,8 @@ function SearchToolbar({ } }} onSuggestionSelected={(e, { suggestion }) => { - const valueWithoutLastWord = query.value.match(/^(.*?)\s*\S+$/)[1]; + const valueWithoutLastWord = + query.value.match(/^(.*?)\s*\S+$/)?.[1] || query.value; onChange({ ...query, value: valueWithoutLastWord, @@ -194,24 +225,45 @@ function SearchToolbar({ autoFocus={autoFocus} {...inputProps} /> - {!searchQueryIsEmpty(query) && ( - + + {!searchQueryIsEmpty(query) && ( + + } + color="gray.400" + variant="ghost" + height="100%" + marginLeft="1" + aria-label="Clear search" + onClick={() => { + setSuggestions([]); + onChange(emptySearchQuery); + }} + /> + + )} + } + icon={ + advancedSearchIsOpen ? ( + + ) : ( + + ) + } color="gray.400" variant="ghost" - colorScheme="green" - aria-label="Clear search" - onClick={() => { - setSuggestions([]); - onChange(emptySearchQuery); - }} - // Big style hacks here! - height="calc(100% - 2px)" - marginRight="2px" + height="100%" + aria-label="Open advanced search" + onClick={() => setAdvancedSearchIsOpen((isOpen) => !isOpen)} /> - - )} + + )} inputProps={{ @@ -256,46 +308,64 @@ function SearchToolbar({ ); } -function getSuggestions(value, query, zoneLabels, isLoggedIn) { - if (!value) { +function getSuggestions( + value, + query, + zoneLabels, + isLoggedIn, + { showAll = false } = {} +) { + if (!value && !showAll) { return []; } - const words = value.split(/\s+/); + const words = (value || "").split(/\s+/); const lastWord = words[words.length - 1]; - if (lastWord.length < 2) { + if (lastWord.length < 2 && !showAll) { return []; } const suggestions = []; 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" }); } - if (wordMatches("NP", lastWord) || wordMatches("Neopoints", lastWord)) { + if ( + wordMatches("NP", lastWord) || + wordMatches("Neopoints", lastWord) || + showAll + ) { 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" }); } } 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" }); } - if (wordMatches("Items you want", lastWord)) { + if (wordMatches("Items you want", lastWord) || showAll) { suggestions.push({ userOwnsOrWants: "WANTS", text: "Items you want" }); } } if (query.filterToZoneLabel == null) { for (const zoneLabel of zoneLabels) { - if (wordMatches(zoneLabel, lastWord)) { + if (wordMatches(zoneLabel, lastWord) || showAll) { suggestions.push({ zoneLabel, text: `Zone: ${zoneLabel}` }); } }