diff --git a/src/ItemList.css b/src/ItemList.css new file mode 100644 index 0000000..a8c39d7 --- /dev/null +++ b/src/ItemList.css @@ -0,0 +1,12 @@ +.item-list-row-exit { + opacity: 1; + height: auto; +} + +.item-list-row-exit-active { + opacity: 0; + height: 0 !important; + margin-top: 0 !important; + margin-bottom: 0 !important; + transition: all 0.5s; +} diff --git a/src/ItemList.js b/src/ItemList.js index 7080802..3412f52 100644 --- a/src/ItemList.js +++ b/src/ItemList.js @@ -1,19 +1,42 @@ import React from "react"; -import { Box, Image, PseudoBox, Stack, Skeleton } from "@chakra-ui/core"; +import { CSSTransition, TransitionGroup } from "react-transition-group"; +import { + Box, + Flex, + IconButton, + Image, + PseudoBox, + Stack, + Skeleton, + Tooltip, +} from "@chakra-ui/core"; -function ItemList({ items, wornItemIds, dispatchToOutfit }) { +import "./ItemList.css"; + +function ItemList({ items, outfitState, dispatchToOutfit }) { return ( - - {items.map((item) => ( - - - - ))} - + + + {items.map((item) => ( + { + e.style.height = e.offsetHeight + "px"; + }} + > + + + + + ))} + + ); } @@ -29,7 +52,12 @@ function ItemListSkeleton({ count }) { ); } -function Item({ item, isWorn, dispatchToOutfit }) { +function Item({ item, outfitState, dispatchToOutfit }) { + const { wornItemIds, allItemIds } = outfitState; + + const isWorn = wornItemIds.includes(item.id); + const isInOutfit = allItemIds.includes(item.id); + return ( {item.name} + + {isInOutfit && ( + + { + e.stopPropagation(); + dispatchToOutfit({ type: "removeItem", itemId: item.id }); + }} + opacity="0" + transitionProperty="opacity color" + transitionDuration="0.2s" + _groupHover={{ + opacity: 1, + transitionDuration: "0.5s", + }} + _hover={{ + opacity: 1, + color: "gray.800", + backgroundColor: "gray.200", + }} + _focus={{ + opacity: 1, + color: "gray.800", + backgroundColor: "gray.200", + }} + /> + + )} ); } diff --git a/src/ItemsPanel.css b/src/ItemsPanel.css new file mode 100644 index 0000000..cd5fa7b --- /dev/null +++ b/src/ItemsPanel.css @@ -0,0 +1,12 @@ +.items-panel-zone-exit { + opacity: 1; + height: auto; +} + +.items-panel-zone-exit-active { + opacity: 0; + height: 0 !important; + margin-top: 0 !important; + margin-bottom: 0 !important; + transition: all 0.5s; +} diff --git a/src/ItemsPanel.js b/src/ItemsPanel.js new file mode 100644 index 0000000..6429243 --- /dev/null +++ b/src/ItemsPanel.js @@ -0,0 +1,115 @@ +import React from "react"; +import { + Box, + Editable, + EditablePreview, + EditableInput, + Flex, + IconButton, + PseudoBox, + Skeleton, +} from "@chakra-ui/core"; +import { CSSTransition, TransitionGroup } from "react-transition-group"; + +import { Delay, Heading1, Heading2 } from "./util"; +import ItemList, { ItemListSkeleton } from "./ItemList"; + +import "./ItemsPanel.css"; + +function ItemsPanel({ outfitState, loading, dispatchToOutfit }) { + const { zonesAndItems, wornItemIds } = outfitState; + + return ( + + + + {loading && + [1, 2, 3].map((i) => ( + + + + + + + ))} + {!loading && ( + + {zonesAndItems.map(({ zone, items }) => ( + { + e.style.height = e.offsetHeight + "px"; + }} + > + + {zone.label} + + + + ))} + + )} + + + ); +} + +function OutfitHeading({ outfitState, dispatchToOutfit }) { + return ( + + + + + dispatchToOutfit({ type: "rename", outfitName: value }) + } + > + {({ isEditing, onRequestEdit }) => ( + <> + + + {!isEditing && ( + + )} + + )} + + + + + ); +} + +function OutfitNameEditButton({ onRequestEdit }) { + return ( + + + + ); +} + +export default ItemsPanel; diff --git a/src/SearchPanel.js b/src/SearchPanel.js index b47332f..9408a6c 100644 --- a/src/SearchPanel.js +++ b/src/SearchPanel.js @@ -94,7 +94,7 @@ function SearchResults({ query, outfitState, dispatchToOutfit }) { return ( ); diff --git a/src/WardrobePage.js b/src/WardrobePage.js index 440f6c0..5a5ab29 100644 --- a/src/WardrobePage.js +++ b/src/WardrobePage.js @@ -1,9 +1,6 @@ import React from "react"; import { Box, - Editable, - EditablePreview, - EditableInput, Grid, Icon, IconButton, @@ -11,14 +8,10 @@ import { InputGroup, InputLeftElement, InputRightElement, - PseudoBox, - Skeleton, - Stack, useToast, } from "@chakra-ui/core"; -import { Delay, Heading1, Heading2 } from "./util"; -import ItemList, { ItemListSkeleton } from "./ItemList"; +import ItemsPanel from "./ItemsPanel"; import OutfitPreview from "./OutfitPreview"; import SearchPanel from "./SearchPanel"; import useOutfitState from "./useOutfitState.js"; @@ -129,90 +122,4 @@ function SearchToolbar({ query, onChange }) { ); } -function ItemsPanel({ outfitState, loading, dispatchToOutfit }) { - const { zonesAndItems, wornItemIds } = outfitState; - - return ( - - - - {loading && - [1, 2, 3].map((i) => ( - - - - - - - ))} - {!loading && - zonesAndItems.map(({ zone, items }) => ( - - {zone.label} - i.id) - .filter((id) => wornItemIds.includes(id))} - dispatchToOutfit={dispatchToOutfit} - /> - - ))} - - - ); -} - -function OutfitHeading({ outfitState, dispatchToOutfit }) { - return ( - - - - - dispatchToOutfit({ type: "rename", outfitName: value }) - } - > - {({ isEditing, onRequestEdit }) => ( - <> - - - {!isEditing && ( - - )} - - )} - - - - - ); -} - -function OutfitNameEditButton({ onRequestEdit }) { - return ( - - - - ); -} - export default WardrobePage; diff --git a/src/useOutfitState.js b/src/useOutfitState.js index 51b115a..76412a7 100644 --- a/src/useOutfitState.js +++ b/src/useOutfitState.js @@ -133,6 +133,15 @@ const outfitStateReducer = (apolloClient) => (baseState, action) => { wornItemIds.delete(itemId); closetedItemIds.add(itemId); }); + case "removeItem": + return produce(baseState, (state) => { + const { wornItemIds, closetedItemIds } = state; + const { itemId } = action; + + // Remove this item from both the worn set and the closet. + wornItemIds.delete(itemId); + closetedItemIds.delete(itemId); + }); default: throw new Error(`unexpected action ${action}`); }