diff --git a/src/app/WardrobePage/SearchFooter.js b/src/app/WardrobePage/SearchFooter.js new file mode 100644 index 0000000..2c6e2df --- /dev/null +++ b/src/app/WardrobePage/SearchFooter.js @@ -0,0 +1,46 @@ +import React from "react"; +import * as Sentry from "@sentry/react"; +import { Box, Flex } from "@chakra-ui/react"; +import SearchToolbar, { emptySearchQuery } from "./SearchToolbar"; +import { MajorErrorMessage, TestErrorSender, useLocalStorage } from "../util"; + +/** + * SearchFooter appears on large screens only, to let you search for new items + * while still keeping the rest of the item screen open! + */ +function SearchFooter() { + const [canUseSearchFooter, setCanUseSearchFooter] = useLocalStorage( + "DTIFeatureFlagCanUseSearchFooter", + false + ); + + React.useEffect(() => { + if (window.location.search.includes("feature-flag-can-use-search-footer")) { + setCanUseSearchFooter(true); + } + }, [setCanUseSearchFooter]); + + const [query, setQuery] = React.useState(emptySearchQuery); + + // TODO: Show the new footer to other users, too! + if (!canUseSearchFooter) { + return null; + } + + return ( + + + + + + Add new items: + + + + + + + ); +} + +export default SearchFooter; diff --git a/src/app/WardrobePage/SearchToolbar.js b/src/app/WardrobePage/SearchToolbar.js index 46e53ab..f30cd2b 100644 --- a/src/app/WardrobePage/SearchToolbar.js +++ b/src/app/WardrobePage/SearchToolbar.js @@ -53,6 +53,7 @@ function SearchToolbar({ showItemsLabel = false, background = null, boxShadow = null, + ...props }) { const [suggestions, setSuggestions] = React.useState([]); const [advancedSearchIsOpen, setAdvancedSearchIsOpen] = React.useState(false); @@ -182,139 +183,144 @@ function SearchToolbar({ 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 - // always happened? Idk? Let's just, gate around it, I guess? - if (typeof value === "string") { - setSuggestions(getSuggestions(value, query, zoneLabels, isLoggedIn)); - } - }} - onSuggestionSelected={(e, { suggestion }) => { - onChange({ - ...query, - // If the suggestion was from typing, remove the last word of the - // query value. Or, if it was from Advanced Search, leave it alone! - value: advancedSearchIsOpen - ? query.value - : removeLastWord(query.value), - filterToZoneLabel: suggestion.zoneLabel || query.filterToZoneLabel, - filterToItemKind: suggestion.itemKind || query.filterToItemKind, - filterToCurrentUserOwnsOrWants: - suggestion.userOwnsOrWants || query.filterToCurrentUserOwnsOrWants, - }); - }} - getSuggestionValue={(zl) => zl} - alwaysRenderSuggestions={true} - renderSuggestion={renderSuggestion} - renderSuggestionsContainer={renderSuggestionsContainer} - renderInputComponent={(inputProps) => ( - - {queryFilterText ? ( - - - {queryFilterText} - - ) : ( - - - - )} - - - {!searchQueryIsEmpty(query) && ( - + + { + // HACK: I'm not sure why, but apparently this gets called with value + // set to the _chosen suggestion_ after choosing it? Has that + // always happened? Idk? Let's just, gate around it, I guess? + if (typeof value === "string") { + setSuggestions( + getSuggestions(value, query, zoneLabels, isLoggedIn) + ); + } + }} + onSuggestionSelected={(e, { suggestion }) => { + onChange({ + ...query, + // If the suggestion was from typing, remove the last word of the + // query value. Or, if it was from Advanced Search, leave it alone! + value: advancedSearchIsOpen + ? query.value + : removeLastWord(query.value), + filterToZoneLabel: suggestion.zoneLabel || query.filterToZoneLabel, + filterToItemKind: suggestion.itemKind || query.filterToItemKind, + filterToCurrentUserOwnsOrWants: + suggestion.userOwnsOrWants || + query.filterToCurrentUserOwnsOrWants, + }); + }} + getSuggestionValue={(zl) => zl} + alwaysRenderSuggestions={true} + renderSuggestion={renderSuggestion} + renderSuggestionsContainer={renderSuggestionsContainer} + renderInputComponent={(inputProps) => ( + + {queryFilterText ? ( + + + {queryFilterText} + + ) : ( + + + + )} + + + {!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" height="100%" - marginLeft="1" - aria-label="Clear search" - onClick={() => { - setSuggestions([]); - onChange(emptySearchQuery); - }} + aria-label="Open advanced search" + onClick={() => setAdvancedSearchIsOpen((isOpen) => !isOpen)} /> - )} - - - ) : ( - - ) - } - color="gray.400" - variant="ghost" - height="100%" - aria-label="Open advanced search" - onClick={() => setAdvancedSearchIsOpen((isOpen) => !isOpen)} - /> - - - - )} - inputProps={{ - placeholder: "Search all items…", - focusBorderColor: focusBorderColor, - value: query.value || "", - ref: searchQueryRef, - minWidth: 0, - "data-test-id": "item-search-input", - onChange: (e, { newValue, method }) => { - // The Autosuggest tries to change the _entire_ value of the element - // when navigating suggestions, which isn't actually what we want. - // Only accept value changes that are typed by the user! - if (method === "type") { - onChange({ ...query, value: newValue }); - } - }, - onKeyDown: (e) => { - if (e.key === "Escape") { - if (suggestions.length > 0) { - setSuggestions([]); - return; + + + )} + inputProps={{ + placeholder: "Search all items…", + focusBorderColor: focusBorderColor, + value: query.value || "", + ref: searchQueryRef, + minWidth: 0, + "data-test-id": "item-search-input", + onChange: (e, { newValue, method }) => { + // The Autosuggest tries to change the _entire_ value of the element + // when navigating suggestions, which isn't actually what we want. + // Only accept value changes that are typed by the user! + if (method === "type") { + onChange({ ...query, value: newValue }); } - onChange(emptySearchQuery); - e.target.blur(); - } else if (e.key === "Enter") { - // Pressing Enter doesn't actually submit because it's all on - // debounce, but it can be a declaration that the query is done, so - // filter suggestions should go away! - if (suggestions.length > 0) { - setSuggestions([]); - return; + }, + onKeyDown: (e) => { + if (e.key === "Escape") { + if (suggestions.length > 0) { + setSuggestions([]); + return; + } + onChange(emptySearchQuery); + e.target.blur(); + } else if (e.key === "Enter") { + // Pressing Enter doesn't actually submit because it's all on + // debounce, but it can be a declaration that the query is done, so + // filter suggestions should go away! + if (suggestions.length > 0) { + setSuggestions([]); + return; + } + } else if (e.key === "ArrowDown") { + if (suggestions.length > 0) { + return; + } + onMoveFocusDownToResults(e); + } else if (e.key === "Backspace" && e.target.selectionStart === 0) { + onChange({ + ...query, + filterToItemKind: null, + filterToZoneLabel: null, + filterToCurrentUserOwnsOrWants: null, + }); } - } else if (e.key === "ArrowDown") { - if (suggestions.length > 0) { - return; - } - onMoveFocusDownToResults(e); - } else if (e.key === "Backspace" && e.target.selectionStart === 0) { - onChange({ - ...query, - filterToItemKind: null, - filterToZoneLabel: null, - filterToCurrentUserOwnsOrWants: null, - }); - } - }, - }} - /> + }, + }} + /> + ); } diff --git a/src/app/WardrobePage/WardrobePageLayout.js b/src/app/WardrobePage/WardrobePageLayout.js index f5dc687..e205f4d 100644 --- a/src/app/WardrobePage/WardrobePageLayout.js +++ b/src/app/WardrobePage/WardrobePageLayout.js @@ -1,8 +1,15 @@ import React from "react"; -import { Box, Grid, useColorModeValue } from "@chakra-ui/react"; +import { Box, Grid, useColorModeValue, useToken } from "@chakra-ui/react"; +import { useCommonStyles } from "../util"; -function WardrobePageLayout({ previewAndControls, itemsAndSearch }) { +function WardrobePageLayout({ + previewAndControls = null, + itemsAndMaybeSearchPanel = null, + searchFooter = null, +}) { const itemsAndSearchBackground = useColorModeValue("white", "gray.900"); + const searchBackground = useCommonStyles().bodyBackground; + const searchShadowColorValue = useToken("colors", "gray.400"); return ( {previewAndControls} - - {itemsAndSearch} + + {itemsAndMaybeSearchPanel} + + + {searchFooter} diff --git a/src/app/WardrobePage/index.js b/src/app/WardrobePage/index.js index 561e06f..572fa81 100644 --- a/src/app/WardrobePage/index.js +++ b/src/app/WardrobePage/index.js @@ -4,6 +4,7 @@ import { useToast } from "@chakra-ui/react"; import { loadable } from "../util"; import ItemsAndSearchPanels from "./ItemsAndSearchPanels"; +import SearchFooter from "./SearchFooter"; import SupportOnly from "./support/SupportOnly"; import useOutfitSaving from "./useOutfitSaving"; import useOutfitState, { OutfitStateContext } from "./useOutfitState"; @@ -104,7 +105,7 @@ function WardrobePage() { dispatchToOutfit={dispatchToOutfit} /> } - itemsAndSearch={ + itemsAndMaybeSearchPanel={ } + searchFooter={} /> );