diff --git a/src/ItemList.js b/src/ItemList.js index 1a9d451..d38bf8b 100644 --- a/src/ItemList.js +++ b/src/ItemList.js @@ -1,12 +1,10 @@ import React from "react"; import { css } from "emotion"; -import { CSSTransition, TransitionGroup } from "react-transition-group"; import { Box, Flex, IconButton, Image, - PseudoBox, Skeleton, Tooltip, useTheme, @@ -14,33 +12,6 @@ import { import "./ItemList.css"; -function ItemList({ items, outfitState, dispatchToOutfit }) { - return ( - - - {items.map((item) => ( - { - e.style.height = e.offsetHeight + "px"; - }} - > - - - - - ))} - - - ); -} - export function ItemListContainer({ children }) { return {children}; } @@ -55,7 +26,7 @@ export function ItemListSkeleton({ count }) { ); } -export function Item({ item, outfitState, dispatchToOutfit }) { +export function Item({ item, itemNameId, outfitState, dispatchToOutfit }) { const { allItemIds } = outfitState; const isInOutfit = allItemIds.includes(item.id); const theme = useTheme(); @@ -64,7 +35,7 @@ export function Item({ item, outfitState, dispatchToOutfit }) { - {item.name} + {item.name} {isInOutfit && ( @@ -175,7 +146,7 @@ function ItemThumbnail({ src }) { ); } -function ItemName({ children }) { +function ItemName({ children, ...props }) { const theme = useTheme(); return ( @@ -194,10 +165,9 @@ function ItemName({ children }) { font-weight: ${theme.fontWeights.bold}; } `} + {...props} > {children} ); } - -export default ItemList; diff --git a/src/ItemsPanel.js b/src/ItemsPanel.js index 178c02e..5541e81 100644 --- a/src/ItemsPanel.js +++ b/src/ItemsPanel.js @@ -21,7 +21,7 @@ function ItemsPanel({ outfitState, loading, dispatchToOutfit }) { const { zonesAndItems } = outfitState; return ( - + diff --git a/src/SearchPanel.js b/src/SearchPanel.js index 1381d4d..9ef441a 100644 --- a/src/SearchPanel.js +++ b/src/SearchPanel.js @@ -1,17 +1,18 @@ import React from "react"; import gql from "graphql-tag"; -import { Box, Text } from "@chakra-ui/core"; +import { Box, Text, VisuallyHidden } from "@chakra-ui/core"; import { useQuery } from "@apollo/react-hooks"; import { Delay, Heading1, useDebounce } from "./util"; -import ItemList, { ItemListSkeleton } from "./ItemList"; +import { ItemListContainer, ItemListSkeleton, Item } from "./ItemList"; import { itemAppearanceFragment } from "./OutfitPreview"; function SearchPanel({ query, outfitState, dispatchToOutfit, - getScrollParent, + firstSearchResultRef, + onMoveFocusUpToQuery, }) { return ( @@ -20,13 +21,20 @@ function SearchPanel({ query={query} outfitState={outfitState} dispatchToOutfit={dispatchToOutfit} - getScrollParent={getScrollParent} + firstSearchResultRef={firstSearchResultRef} + onMoveFocusUpToQuery={onMoveFocusUpToQuery} /> ); } -function SearchResults({ query, outfitState, dispatchToOutfit }) { +function SearchResults({ + query, + outfitState, + dispatchToOutfit, + firstSearchResultRef, + onMoveFocusUpToQuery, +}) { const { speciesId, colorId } = outfitState; const debouncedQuery = useDebounce(query, 300, { waitForFirstPause: true }); @@ -159,19 +167,74 @@ function SearchResults({ query, outfitState, dispatchToOutfit }) { ); } + const onChange = (e) => { + const itemId = e.target.value; + const willBeWorn = e.target.checked; + if (willBeWorn) { + dispatchToOutfit({ type: "wearItem", itemId }); + } else { + dispatchToOutfit({ type: "unwearItem", itemId }); + } + }; + + const goToPrevItem = (e) => { + const prevLabel = e.target.closest("label").previousSibling; + if (prevLabel) { + prevLabel.querySelector("input[type=checkbox]").focus(); + e.preventDefault(); + } else { + // If we're at the top of the list, move back up to the search box! + onMoveFocusUpToQuery(e); + } + }; + + const goToNextItem = (e) => { + const nextLabel = e.target.closest("label").nextSibling; + if (nextLabel) { + nextLabel.querySelector("input[type=checkbox]").focus(); + e.preventDefault(); + } + }; + + console.log(firstSearchResultRef); return ( - + + {items.map((item, index) => ( + + ))} + {items && loading && } ); } -function ScrollTracker({ children, query, threshold, onScrolledToBottom }) { +function ScrollTracker({ children, threshold, onScrolledToBottom }) { const containerRef = React.useRef(); const scrollParent = React.useRef(); @@ -209,7 +272,7 @@ function ScrollTracker({ children, query, threshold, onScrolledToBottom }) { }; }, [onScroll]); - return {children}; + return
{children}
; } export default SearchPanel; diff --git a/src/WardrobePage.js b/src/WardrobePage.js index fbae50d..d9ae9e3 100644 --- a/src/WardrobePage.js +++ b/src/WardrobePage.js @@ -21,6 +21,8 @@ function WardrobePage() { const [searchQuery, setSearchQuery] = React.useState(""); const toast = useToast(); const searchContainerRef = React.useRef(); + const searchQueryRef = React.useRef(); + const firstSearchResultRef = React.useRef(); React.useEffect(() => { if (error) { @@ -72,13 +74,24 @@ function WardrobePage() {
- + { + if (firstSearchResultRef.current) { + firstSearchResultRef.current.focus(); + e.preventDefault(); + } + }} + /> {searchQuery ? ( { + if (searchQueryRef.current) { + searchQueryRef.current.focus(); + e.preventDefault(); + } + }} />
) : ( - + @@ -118,11 +148,14 @@ function SearchToolbar({ query, onChange }) { focusBorderColor="green.600" color="green.800" value={query} + ref={queryRef} onChange={(e) => onChange(e.target.value)} onKeyDown={(e) => { if (e.key === "Escape") { onChange(""); e.target.blur(); + } else if (e.key === "ArrowDown") { + onMoveFocusDownToResults(e); } }} />