From 4120c7aa88a149101ce8d11ad3012645981a3af0 Mon Sep 17 00:00:00 2001 From: Matchu Date: Mon, 4 Jan 2021 03:11:46 +0000 Subject: [PATCH] fix large-icon visual bug Looks like there was some kind of runtime conflict when running @emotion/css and @emotion/react at the same time in this app? Some styles would just get clobbered, making things look all weird. Here, I've removed our @emotion/css dependency, and use the `` utility element from `@emotion/react` instead. I'm not thrilled about the solution, but it seems okay for now... ...one other thing I tried was passing a `css` prop to Chakra elements, which seemed to work, but to clobber the element's own Emotion-based styles. I assumed that the Babel macro wouldn't help us, and wouldn't convert css props to className props for non-HTML elements... but I suppose I'm not sure! Anyway, I don't love this syntax... but I'm happy for the site to be working again. I wonder if we can find something better. --- package.json | 1 - src/app/HomePage.js | 68 +-- src/app/ItemPage.js | 212 +++++----- src/app/ItemTradesPage.js | 340 +++++++-------- src/app/PrivacyPolicyPage.js | 4 +- src/app/UserItemsPage.js | 300 +++++++------- src/app/WardrobePage/Item.js | 120 +++--- src/app/WardrobePage/ItemsPanel.js | 210 +++++----- src/app/WardrobePage/OutfitControls.js | 312 +++++++------- src/app/WardrobePage/PosePicker.js | 386 +++++++++--------- src/app/WardrobePage/SearchToolbar.js | 242 +++++------ .../WardrobePage/support/ItemSupportDrawer.js | 122 +++--- src/app/components/HangerSpinner.js | 124 +++--- src/app/components/ItemCard.js | 158 +++---- src/app/components/OutfitPreview.js | 199 ++++----- yarn.lock | 11 - 16 files changed, 1460 insertions(+), 1349 deletions(-) diff --git a/package.json b/package.json index 00f1fc7..1ea2991 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ "@chakra-ui/icons": "^1.0.2", "@chakra-ui/react": "^1.0.4", "@chakra-ui/theme-tools": "^1.0.2", - "@emotion/css": "^11.1.3", "@emotion/react": "^11.1.4", "@emotion/styled": "^11.0.0", "@loadable/component": "^5.12.0", diff --git a/src/app/HomePage.js b/src/app/HomePage.js index 4c9ea9c..c542b54 100644 --- a/src/app/HomePage.js +++ b/src/app/HomePage.js @@ -1,5 +1,5 @@ import React from "react"; -import { css } from "@emotion/css"; +import { ClassNames } from "@emotion/react"; import gql from "graphql-tag"; import { Box, @@ -213,37 +213,41 @@ function SubmitPetForm() { const buttonBgColorHover = useColorModeValue("green.700", "green.200"); return ( -
- - setPetName(e.target.value)} - isDisabled={loading} - placeholder="Enter a pet's name" - aria-label="Enter a pet's name" - borderColor={inputBorderColor} - _hover={{ borderColor: inputBorderColorHover }} - boxShadow="md" - width="14em" - className={css` - &::placeholder { - color: ${theme.colors.gray["500"]}; - } - `} - /> - - - -
+ + {({ css }) => ( +
+ + setPetName(e.target.value)} + isDisabled={loading} + placeholder="Enter a pet's name" + aria-label="Enter a pet's name" + borderColor={inputBorderColor} + _hover={{ borderColor: inputBorderColorHover }} + boxShadow="md" + width="14em" + className={css` + &::placeholder { + color: ${theme.colors.gray["500"]}; + } + `} + /> + + + +
+ )} +
); } diff --git a/src/app/ItemPage.js b/src/app/ItemPage.js index 70c93dc..a2e9536 100644 --- a/src/app/ItemPage.js +++ b/src/app/ItemPage.js @@ -1,5 +1,5 @@ import React from "react"; -import { css } from "@emotion/css"; +import { ClassNames } from "@emotion/react"; import { AspectRatio, Button, @@ -205,55 +205,62 @@ function ItemPageOwnButton({ itemId, isChecked }) { ); return ( - - { - if (e.target.checked) { - sendAddMutation().catch((e) => { - console.error(e); - toast({ - title: "We had trouble adding this to the items you own.", - description: "Check your internet connection, and try again.", - status: "error", - duration: 5000, - }); - }); - } else { - sendRemoveMutation().catch((e) => { - console.error(e); - toast({ - title: "We had trouble removing this from the items you own.", - description: "Check your internet connection, and try again.", - status: "error", - duration: 5000, - }); - }); - } - }} - /> - - + + {({ css }) => ( + + { + if (e.target.checked) { + sendAddMutation().catch((e) => { + console.error(e); + toast({ + title: "We had trouble adding this to the items you own.", + description: + "Check your internet connection, and try again.", + status: "error", + duration: 5000, + }); + }); + } else { + sendRemoveMutation().catch((e) => { + console.error(e); + toast({ + title: + "We had trouble removing this from the items you own.", + description: + "Check your internet connection, and try again.", + status: "error", + duration: 5000, + }); + }); + } + }} + /> + + + )} + ); } @@ -306,55 +313,62 @@ function ItemPageWantButton({ itemId, isChecked }) { ); return ( - - { - if (e.target.checked) { - sendAddMutation().catch((e) => { - console.error(e); - toast({ - title: "We had trouble adding this to the items you want.", - description: "Check your internet connection, and try again.", - status: "error", - duration: 5000, - }); - }); - } else { - sendRemoveMutation().catch((e) => { - console.error(e); - toast({ - title: "We had trouble removing this from the items you want.", - description: "Check your internet connection, and try again.", - status: "error", - duration: 5000, - }); - }); - } - }} - /> - - + + {({ css }) => ( + + { + if (e.target.checked) { + sendAddMutation().catch((e) => { + console.error(e); + toast({ + title: "We had trouble adding this to the items you want.", + description: + "Check your internet connection, and try again.", + status: "error", + duration: 5000, + }); + }); + } else { + sendRemoveMutation().catch((e) => { + console.error(e); + toast({ + title: + "We had trouble removing this from the items you want.", + description: + "Check your internet connection, and try again.", + status: "error", + duration: 5000, + }); + }); + } + }} + /> + + + )} + ); } diff --git a/src/app/ItemTradesPage.js b/src/app/ItemTradesPage.js index d42b230..879f151 100644 --- a/src/app/ItemTradesPage.js +++ b/src/app/ItemTradesPage.js @@ -1,5 +1,5 @@ import React from "react"; -import { css } from "@emotion/css"; +import { ClassNames } from "@emotion/react"; import { Box, Skeleton, useColorModeValue, useToken } from "@chakra-ui/react"; import gql from "graphql-tag"; import { useQuery } from "@apollo/client"; @@ -165,84 +165,88 @@ function ItemTradesTable({ }; return ( - - - - - {/* A small wording tweak to fit better on the xsmall screens! */} - Last active - Last edit - - {shouldShowCompareColumn && ( - - - {compareColumnLabel} - - Matches - - )} - - {userHeading} - - List - - - - {loading && ( - <> - - - - - - - )} - {!loading && - trades.length > 0 && - trades.map((trade) => ( - - ))} - {!loading && trades.length === 0 && ( - - - No trades yet! - + + {({ css }) => ( + + + + + {/* A small wording tweak to fit better on the xsmall screens! */} + Last active + Last edit + + {shouldShowCompareColumn && ( + + + {compareColumnLabel} + + Matches + + )} + + {userHeading} + + List + - )} - - + + {loading && ( + <> + + + + + + + )} + {!loading && + trades.length > 0 && + trades.map((trade) => ( + + ))} + {!loading && trades.length === 0 && ( + + + No trades yet! + + + )} + + + )} +
); } @@ -263,63 +267,67 @@ function ItemTradesTableRow({ ); return ( - - - {formatVagueDate(lastTradeActivity)} - - {shouldShowCompareColumn && ( - - {matchingItems.length > 0 ? ( - - {sortedMatchingItems.slice(0, 4).map((item) => ( - - - {item.name} - - - ))} - {matchingItems.length > 4 && ( - + {matchingItems.length - 4} more - )} - - ) : ( - <> - No matches - None - - )} - - )} - {username} - + + {({ css }) => ( - {listName} + + {formatVagueDate(lastTradeActivity)} + + {shouldShowCompareColumn && ( + + {matchingItems.length > 0 ? ( + + {sortedMatchingItems.slice(0, 4).map((item) => ( + + + {item.name} + + + ))} + {matchingItems.length > 4 && ( + + {matchingItems.length - 4} more + )} + + ) : ( + <> + No matches + None + + )} + + )} + {username} + + + {listName} + + - - + )} + ); } @@ -350,47 +358,51 @@ function ItemTradesTableCell({ children, as = "td", ...props }) { const borderRadiusCss = useToken("radii", "md"); return ( - + {({ css }) => ( + - {children} - + thead tr:first-of-type &:first-of-type { + border-top-left-radius: ${borderRadiusCss}; + } + thead tr:first-of-type &:last-of-type { + border-top-right-radius: ${borderRadiusCss}; + } + tbody tr:last-of-type &:first-of-type { + border-bottom-left-radius: ${borderRadiusCss}; + } + tbody tr:last-of-type &:last-of-type { + border-bottom-right-radius: ${borderRadiusCss}; + } + `} + {...props} + > + {children} + + )} + ); } diff --git a/src/app/PrivacyPolicyPage.js b/src/app/PrivacyPolicyPage.js index 50a05c7..c234e9b 100644 --- a/src/app/PrivacyPolicyPage.js +++ b/src/app/PrivacyPolicyPage.js @@ -1,5 +1,5 @@ import React from "react"; -import { css } from "@emotion/css"; +import { css } from "@emotion/react"; import { VStack } from "@chakra-ui/react"; import { Heading1, Heading2, Heading3 } from "./util"; @@ -11,7 +11,7 @@ function PrivacyPolicyPage() { - + + {({ css }) => ( - - {isCurrentUser ? "Your items" : `${data.user.username}'s items`} - - - {data.user.contactNeopetsUsername && ( - - - - {data.user.contactNeopetsUsername} - - - )} - {data.user.contactNeopetsUsername && ( - - - - Neomail - - - )} - - - - - - Support - - - - - {/* Usually I put "Own" before "Want", but this matches the natural - * order on the page: the _matches_ for things you want are things - * _this user_ owns, so they come first. I think it's also probably a - * more natural train of thought: you come to someone's list _wanting_ - * something, and _then_ thinking about what you can offer. */} - {!isCurrentUser && numItemsTheyOwnThatYouWant > 0 && ( - - - - {numItemsTheyOwnThatYouWant > 1 - ? `${numItemsTheyOwnThatYouWant} items you want` - : "1 item you want"} - - - )} - {!isCurrentUser && numItemsTheyWantThatYouOwn > 0 && ( - - - - {numItemsTheyWantThatYouOwn > 1 - ? `${numItemsTheyWantThatYouOwn} items you own` - : "1 item you own"} - - - )} - - - - - - - + + + + {isCurrentUser ? "Your items" : `${data.user.username}'s items`} + + + {data.user.contactNeopetsUsername && ( + + + + {data.user.contactNeopetsUsername} + + + )} + {data.user.contactNeopetsUsername && ( + + + + Neomail + + + )} + + + + + + Support + + + + + {/* Usually I put "Own" before "Want", but this matches the natural + * order on the page: the _matches_ for things you want are things + * _this user_ owns, so they come first. I think it's also probably a + * more natural train of thought: you come to someone's list _wanting_ + * something, and _then_ thinking about what you can offer. */} + {!isCurrentUser && numItemsTheyOwnThatYouWant > 0 && ( + + + + {numItemsTheyOwnThatYouWant > 1 + ? `${numItemsTheyOwnThatYouWant} items you want` + : "1 item you want"} + + + )} + {!isCurrentUser && numItemsTheyWantThatYouOwn > 0 && ( + + + + {numItemsTheyWantThatYouOwn > 1 + ? `${numItemsTheyWantThatYouOwn} items you own` + : "1 item you own"} + + + )} + + + + + + + - - {isCurrentUser && ( - - + + {isCurrentUser && ( + + + + )} + + {isCurrentUser + ? "Items you own" + : `Items ${data.user.username} owns`} + + + {listsOfOwnedItems.map((closetList) => ( + 1} + /> + ))} + - )} - - {isCurrentUser ? "Items you own" : `Items ${data.user.username} owns`} - - - {listsOfOwnedItems.map((closetList) => ( - 1} - /> - ))} - - - - {isCurrentUser ? "Items you want" : `Items ${data.user.username} wants`} - - - {listsOfWantedItems.map((closetList) => ( - 1} - /> - ))} - - + + {isCurrentUser + ? "Items you want" + : `Items ${data.user.username} wants`} + + + {listsOfWantedItems.map((closetList) => ( + 1} + /> + ))} + + + )} + ); } @@ -588,21 +596,25 @@ function MarkdownAndSafeHTML({ children }) { }); return ( - + {({ css }) => ( + + ol, + ul { + margin-left: 2em; + } + `} + /> + )} + ); } diff --git a/src/app/WardrobePage/Item.js b/src/app/WardrobePage/Item.js index c305c92..eaa4939 100644 --- a/src/app/WardrobePage/Item.js +++ b/src/app/WardrobePage/Item.js @@ -1,5 +1,5 @@ import React from "react"; -import { css, cx } from "@emotion/css"; +import { ClassNames } from "@emotion/react"; import { Box, Flex, @@ -166,35 +166,39 @@ function ItemContainer({ children, isDisabled = false }) { ); return ( - + {({ css, cx }) => ( + - {children} - + input:checked:focus + & { + border-color: ${focusCheckedBorderColor}; + } + `, + ])} + > + {children} + + )} + ); } @@ -242,39 +246,43 @@ function ItemActionButton({ icon, label, to, onClick }) { ); return ( - - + {({ css }) => ( + + + @media (hover: none) { + opacity: 1; + } + `} + /> + + )} + ); } diff --git a/src/app/WardrobePage/ItemsPanel.js b/src/app/WardrobePage/ItemsPanel.js index d6ee1ab..705fc10 100644 --- a/src/app/WardrobePage/ItemsPanel.js +++ b/src/app/WardrobePage/ItemsPanel.js @@ -1,5 +1,5 @@ import React from "react"; -import { css } from "@emotion/css"; +import { ClassNames } from "@emotion/react"; import { Box, Editable, @@ -34,50 +34,59 @@ function ItemsPanel({ outfitState, loading, dispatchToOutfit }) { const { zonesAndItems, incompatibleItems } = outfitState; return ( - - - - - - {loading ? ( - - ) : ( - - {zonesAndItems.map(({ zoneLabel, items }) => ( - - - - ))} - {incompatibleItems.length > 0 && ( - - - - } - items={incompatibleItems} - outfitState={outfitState} - dispatchToOutfit={dispatchToOutfit} - isDisabled + + {({ css }) => ( + + + + + + {loading ? ( + + ) : ( + + {zonesAndItems.map(({ zoneLabel, items }) => ( + + + + ))} + {incompatibleItems.length > 0 && ( + + + + } + items={incompatibleItems} + outfitState={outfitState} + dispatchToOutfit={dispatchToOutfit} + isDisabled + /> + )} + )} - - )} - - + + + )} + ); } @@ -125,59 +134,66 @@ function ItemZoneGroup({ ); return ( - - - {zoneLabel} - {afterHeader && {afterHeader}} - - - - {items.map((item) => { - const itemNameId = - zoneLabel.replace(/ /g, "-") + `-item-${item.id}-name`; - const itemNode = ( - - ); + + {({ css }) => ( + + + {zoneLabel} + {afterHeader && {afterHeader}} + + + + {items.map((item) => { + const itemNameId = + zoneLabel.replace(/ /g, "-") + `-item-${item.id}-name`; + const itemNode = ( + + ); - return ( - - {isDisabled ? ( - itemNode - ) : ( - - )} - - ); - })} - - - + return ( + + {isDisabled ? ( + itemNode + ) : ( + + )} + + ); + })} + + + + )} + ); } @@ -273,7 +289,7 @@ function OutfitHeading({ outfitState, dispatchToOutfit }) { * * See react-transition-group docs for more info! */ -const fadeOutAndRollUpTransition = { +const fadeOutAndRollUpTransition = (css) => ({ classNames: css` &-exit { opacity: 1; @@ -292,6 +308,6 @@ const fadeOutAndRollUpTransition = { onExit: (e) => { e.style.height = e.offsetHeight + "px"; }, -}; +}); export default ItemsPanel; diff --git a/src/app/WardrobePage/OutfitControls.js b/src/app/WardrobePage/OutfitControls.js index b7673ed..a64fdf7 100644 --- a/src/app/WardrobePage/OutfitControls.js +++ b/src/app/WardrobePage/OutfitControls.js @@ -1,5 +1,5 @@ import React from "react"; -import { css, cx } from "@emotion/css"; +import { ClassNames } from "@emotion/react"; import { Box, Button, @@ -84,120 +84,126 @@ function OutfitControls({ }; return ( - + {({ css, cx }) => ( + { - const opacity = parseFloat(getComputedStyle(e.currentTarget).opacity); - if (opacity < 0.5) { - // If the controls aren't visible right now, then clicks on them are - // probably accidental. Ignore them! (We prevent default to block - // built-in behaviors like link nav, and we stop propagation to block - // our own custom click handlers. I don't know if I can prevent the - // select clicks though?) - e.preventDefault(); - e.stopPropagation(); + @media (hover: hover) { + &:hover { + opacity: 1; + } + } + `, + focusIsLocked && "focus-is-locked" + )} + onClickCapture={(e) => { + const opacity = parseFloat( + getComputedStyle(e.currentTarget).opacity + ); + if (opacity < 0.5) { + // If the controls aren't visible right now, then clicks on them are + // probably accidental. Ignore them! (We prevent default to block + // built-in behaviors like link nav, and we stop propagation to block + // our own custom click handlers. I don't know if I can prevent the + // select clicks though?) + e.preventDefault(); + e.stopPropagation(); - // We also show the controls, by locking focus. We'll undo this when - // the user taps elsewhere (because it will trigger a blur event from - // our child components), in `maybeUnlockFocus`. - setFocusIsLocked(true); - } - }} - > - - } - aria-label="Leave this outfit" - d="inline-flex" // Not sure why requires this to style right! ^^` - /> - - {showAnimationControls && ( - - - - + // We also show the controls, by locking focus. We'll undo this when + // the user taps elsewhere (because it will trigger a blur event from + // our child components), in `maybeUnlockFocus`. + setFocusIsLocked(true); + } + }} + > + + } + aria-label="Leave this outfit" + d="inline-flex" // Not sure why requires this to style right! ^^` + /> + + {showAnimationControls && ( + + + + + + )} + + + + + + + + + + + {/** + * We try to center the species/color picker, but the left spacer will + * shrink more than the pose picker container if we run out of space! + */} + + + + + + + + + + )} - - - - - - - - - - - {/** - * We try to center the species/color picker, but the left spacer will - * shrink more than the pose picker container if we run out of space! - */} - - - - - - - - - - - + ); } @@ -277,55 +283,59 @@ function PlayPauseButton() { }, [blinkInState, setBlinkInState]); return ( - <> - - {blinkInState.type === "started" && ( - + + {({ css }) => ( + <> setBlinkInState({ type: "done" })} - // Don't disrupt the hover state of the controls! (And the button - // doesn't seem to click correctly, not sure why, but instead of - // debugging I'm adding this :p) - pointerEvents="none" - className={css` - @keyframes fade-in-out { - 0% { - opacity: 0; - } - - 10% { - opacity: 1; - } - - 90% { - opacity: 1; - } - - 100% { - opacity: 0; - } - } - - opacity: 0; - animation: fade-in-out 2s; - `} + marginTop="0.3rem" // to center-align with buttons (not sure on amt?) + ref={buttonRef} /> - + {blinkInState.type === "started" && ( + + setBlinkInState({ type: "done" })} + // Don't disrupt the hover state of the controls! (And the button + // doesn't seem to click correctly, not sure why, but instead of + // debugging I'm adding this :p) + pointerEvents="none" + className={css` + @keyframes fade-in-out { + 0% { + opacity: 0; + } + + 10% { + opacity: 1; + } + + 90% { + opacity: 1; + } + + 100% { + opacity: 0; + } + } + + opacity: 0; + animation: fade-in-out 2s; + `} + /> + + )} + )} - + ); } diff --git a/src/app/WardrobePage/PosePicker.js b/src/app/WardrobePage/PosePicker.js index b2ef66e..cc0ab56 100644 --- a/src/app/WardrobePage/PosePicker.js +++ b/src/app/WardrobePage/PosePicker.js @@ -1,7 +1,7 @@ import React from "react"; import gql from "graphql-tag"; import { useQuery } from "@apollo/client"; -import { css, cx } from "@emotion/css"; +import { ClassNames } from "@emotion/react"; import { Box, Button, @@ -107,97 +107,101 @@ function PosePicker({ }; return ( - - {({ isOpen }) => ( - <> - - - - - - - {isInSupportMode ? ( - - ) : ( - <> - - {numAvailablePoses <= 1 && ( - - - The empty picker is hidden for most users! -
- You can see it because you're a Support user. -
-
+ &.is-open { + border-width: 2px !important; + } + `, + isOpen && "is-open" + )} + > + + + + + + + {isInSupportMode ? ( + + ) : ( + <> + + {numAvailablePoses <= 1 && ( + + + The empty picker is hidden for most users! +
+ You can see it because you're a Support user. +
+
+ )} + )} - - )} - - - setIsInSupportMode(e.target.checked)} - /> + + + setIsInSupportMode(e.target.checked)} + /> + + - -
- -
-
- + +
+
+ + )} +
)} - + ); } @@ -343,110 +347,118 @@ function PoseOption({ ); return ( - { - // HACK: We need the timeout to beat the popover's focus stealing! - const input = e.currentTarget.querySelector("input"); - setTimeout(() => input.focus(), 0); - }} - {...otherProps} - > - - + + {({ css, cx }) => ( - {poseInfo.isAvailable ? ( - - - - ) : ( - - - - )} - - {label && ( - { + // HACK: We need the timeout to beat the popover's focus stealing! + const input = e.currentTarget.querySelector("input"); + setTimeout(() => input.focus(), 0); + }} + {...otherProps} > - {label} + + + + {poseInfo.isAvailable ? ( + + + + ) : ( + + + + )} + + {label && ( + + {label} + + )} )} - + ); } diff --git a/src/app/WardrobePage/SearchToolbar.js b/src/app/WardrobePage/SearchToolbar.js index 0e4d0ee..e66096d 100644 --- a/src/app/WardrobePage/SearchToolbar.js +++ b/src/app/WardrobePage/SearchToolbar.js @@ -12,7 +12,7 @@ import { useColorModeValue, } from "@chakra-ui/react"; import { CloseIcon, SearchIcon } from "@chakra-ui/icons"; -import { css, cx } from "@emotion/css"; +import { ClassNames } from "@emotion/react"; import Autosuggest from "react-autosuggest"; /** @@ -79,23 +79,27 @@ function SearchToolbar({ ({ containerProps, children }) => { const { className, ...otherContainerProps } = containerProps; return ( - + {({ css, cx }) => ( + + {children} + )} - > - {children} - + ); }, [] @@ -111,108 +115,116 @@ function SearchToolbar({ const focusBorderColor = useColorModeValue("green.600", "green.400"); return ( - - setSuggestions(getSuggestions(value, query, zoneLabels)) - } - onSuggestionsClearRequested={() => setSuggestions([])} - onSuggestionSelected={(e, { suggestion }) => { - const valueWithoutLastWord = query.value.match(/^(.*?)\s*\S+$/)[1]; - onChange({ - ...query, - value: valueWithoutLastWord, - filterToZoneLabel: suggestion.zoneLabel || query.filterToZoneLabel, - filterToItemKind: suggestion.itemKind || query.filterToItemKind, - }); - }} - getSuggestionValue={(zl) => zl} - highlightFirstSuggestion={true} - renderSuggestion={renderSuggestion} - renderSuggestionsContainer={renderSuggestionsContainer} - renderInputComponent={(props) => ( - - {queryFilterText ? ( - - - {queryFilterText} - - ) : ( - - - - )} - - {(query.value || queryFilterText) && ( - - } - color="gray.400" - variant="ghost" - colorScheme="green" - aria-label="Clear search" - onClick={() => { - onChange(null); - }} - // Big style hacks here! - height="calc(100% - 2px)" - marginRight="2px" - /> - - )} - - )} - inputProps={{ - // placeholder: "Search for items to add…", - "aria-label": "Search for items to add…", - focusBorderColor: focusBorderColor, - value: query.value || "", - ref: searchQueryRef, - minWidth: 0, - borderBottomRadius: suggestions.length > 0 ? "0" : "md", - // HACK: Chakra isn't noticing the InputLeftElement swapping out - // for the InputLeftAddon, so the styles aren't updating... - // Hard override! - className: css` - padding-left: ${queryFilterText ? "1rem" : "2.5rem"} !important; - border-bottom-left-radius: ${queryFilterText - ? "0" - : "0.25rem"} !important; - border-top-left-radius: ${queryFilterText - ? "0" - : "0.25rem"} !important; - `, - 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 }); + + {({ css }) => ( + + setSuggestions(getSuggestions(value, query, zoneLabels)) } - }, - onKeyDown: (e) => { - if (e.key === "Escape") { - if (suggestions.length > 0) { - setSuggestions([]); - return; - } - onChange(null); - e.target.blur(); - } else if (e.key === "ArrowDown") { - if (suggestions.length > 0) { - return; - } - onMoveFocusDownToResults(e); - } else if (e.key === "Backspace" && e.target.selectionStart === 0) { + onSuggestionsClearRequested={() => setSuggestions([])} + onSuggestionSelected={(e, { suggestion }) => { + const valueWithoutLastWord = query.value.match(/^(.*?)\s*\S+$/)[1]; onChange({ ...query, - filterToItemKind: null, - filterToZoneLabel: null, + value: valueWithoutLastWord, + filterToZoneLabel: + suggestion.zoneLabel || query.filterToZoneLabel, + filterToItemKind: suggestion.itemKind || query.filterToItemKind, }); - } - }, - }} - /> + }} + getSuggestionValue={(zl) => zl} + highlightFirstSuggestion={true} + renderSuggestion={renderSuggestion} + renderSuggestionsContainer={renderSuggestionsContainer} + renderInputComponent={(props) => ( + + {queryFilterText ? ( + + + {queryFilterText} + + ) : ( + + + + )} + + {(query.value || queryFilterText) && ( + + } + color="gray.400" + variant="ghost" + colorScheme="green" + aria-label="Clear search" + onClick={() => { + onChange(null); + }} + // Big style hacks here! + height="calc(100% - 2px)" + marginRight="2px" + /> + + )} + + )} + inputProps={{ + // placeholder: "Search for items to add…", + "aria-label": "Search for items to add…", + focusBorderColor: focusBorderColor, + value: query.value || "", + ref: searchQueryRef, + minWidth: 0, + borderBottomRadius: suggestions.length > 0 ? "0" : "md", + // HACK: Chakra isn't noticing the InputLeftElement swapping out + // for the InputLeftAddon, so the styles aren't updating... + // Hard override! + className: css` + padding-left: ${queryFilterText ? "1rem" : "2.5rem"} !important; + border-bottom-left-radius: ${queryFilterText + ? "0" + : "0.25rem"} !important; + border-top-left-radius: ${queryFilterText + ? "0" + : "0.25rem"} !important; + `, + 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; + } + onChange(null); + e.target.blur(); + } 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, + }); + } + }, + }} + /> + )} + ); } diff --git a/src/app/WardrobePage/support/ItemSupportDrawer.js b/src/app/WardrobePage/support/ItemSupportDrawer.js index 360c388..e7f6e73 100644 --- a/src/app/WardrobePage/support/ItemSupportDrawer.js +++ b/src/app/WardrobePage/support/ItemSupportDrawer.js @@ -1,7 +1,7 @@ import * as React from "react"; import gql from "graphql-tag"; import { useQuery, useMutation } from "@apollo/client"; -import { css } from "@emotion/css"; +import { ClassNames } from "@emotion/react"; import { Badge, Box, @@ -454,71 +454,75 @@ function ItemSupportAppearanceLayer({ const iconButtonColor = useColorModeValue("green.800", "gray.900"); return ( - - - + + {({ css }) => ( + + + - + + + + {itemLayer.zone.label} + Zone ID: {itemLayer.zone.id} + DTI ID: {itemLayer.id} + - - {itemLayer.zone.label} - Zone ID: {itemLayer.zone.id} - DTI ID: {itemLayer.id} - - + )} + ); } diff --git a/src/app/components/HangerSpinner.js b/src/app/components/HangerSpinner.js index a10aa08..4faf961 100644 --- a/src/app/components/HangerSpinner.js +++ b/src/app/components/HangerSpinner.js @@ -1,5 +1,5 @@ import * as React from "react"; -import { css } from "@emotion/css"; +import { ClassNames } from "@emotion/react"; import { Box, useColorModeValue } from "@chakra-ui/react"; import { createIcon } from "@chakra-ui/icons"; @@ -21,74 +21,76 @@ function HangerSpinner({ size = "md", ...props }) { const color = useColorModeValue("green.500", "green.300"); return ( - <> - + {({ css }) => ( + - - - + `} + {...props} + > + + + )} + ); } diff --git a/src/app/components/ItemCard.js b/src/app/components/ItemCard.js index 6279733..01df824 100644 --- a/src/app/components/ItemCard.js +++ b/src/app/components/ItemCard.js @@ -1,5 +1,5 @@ import React from "react"; -import { css } from "@emotion/css"; +import { ClassNames } from "@emotion/react"; import { Badge, Box, @@ -102,59 +102,63 @@ export function ItemThumbnail({ ); return ( - - + + {({ css }) => ( - - + width={size === "lg" ? "80px" : "50px"} + height={size === "lg" ? "80px" : "50px"} + transition="all 0.15s" + transformOrigin="center" + position="relative" + className={css([ + { + transform: "scale(0.8)", + }, + !isDisabled && + !isActive && { + [focusSelector]: { + opacity: "0.9", + transform: "scale(0.9)", + }, + }, + !isDisabled && + isActive && { + opacity: 1, + transform: "none", + }, + ])} + {...props} + > + + + + + )} + ); } @@ -166,30 +170,34 @@ function ItemName({ children, isDisabled, focusSelector, ...props }) { const theme = useTheme(); return ( - + {({ css }) => ( + - {children} - + {...props} + > + {children} + + )} + ); } diff --git a/src/app/components/OutfitPreview.js b/src/app/components/OutfitPreview.js index d7ddc38..4209fdd 100644 --- a/src/app/components/OutfitPreview.js +++ b/src/app/components/OutfitPreview.js @@ -1,7 +1,7 @@ import React from "react"; import { Box, DarkMode, Flex, Text } from "@chakra-ui/react"; import { WarningIcon } from "@chakra-ui/icons"; -import { css } from "@emotion/css"; +import { ClassNames } from "@emotion/react"; import { CSSTransition, TransitionGroup } from "react-transition-group"; import OutfitMovieLayer, { @@ -149,104 +149,113 @@ export function OutfitLayers({ }, [setCanvasSize]); return ( - - {placeholder && ( - - + {({ css }) => ( + + {placeholder && ( + + + {placeholder} + + + )} + + {visibleLayers.map((layer) => ( + + + {layer.canvasMovieLibraryUrl ? ( + + ) : ( + tags are always allowed through CORS), but + // this means we make the same request that the Download + // button makes, so it can use the cached version of this + // image instead of requesting it again with crossOrigin! + crossOrigin={getBestImageUrlForLayer(layer).crossOrigin} + alt="" + objectFit="contain" + maxWidth="100%" + maxHeight="100%" + /> + )} + + + ))} + + - {placeholder} - - - )} - - {visibleLayers.map((layer) => ( - - - {layer.canvasMovieLibraryUrl ? ( - - ) : ( + {spinnerVariant === "overlay" && ( + <> tags are always allowed through CORS), but - // this means we make the same request that the Download - // button makes, so it can use the cached version of this - // image instead of requesting it again with crossOrigin! - crossOrigin={getBestImageUrlForLayer(layer).crossOrigin} - alt="" - objectFit="contain" - maxWidth="100%" - maxHeight="100%" + position="absolute" + top="0" + left="0" + right="0" + bottom="0" + backgroundColor="gray.900" + opacity="0.7" /> - )} - - - ))} - - - {spinnerVariant === "overlay" && ( - <> - - {/* Against the dark overlay, use the Dark Mode spinner. */} - - - - - )} - {spinnerVariant === "corner" && ( - - )} - - + {/* Against the dark overlay, use the Dark Mode spinner. */} + + + + + )} + {spinnerVariant === "corner" && ( + + )} + + + )} + ); } diff --git a/yarn.lock b/yarn.lock index d2e040b..de813b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2750,17 +2750,6 @@ "@emotion/weak-memoize" "^0.2.5" stylis "^4.0.3" -"@emotion/css@^11.1.3": - version "11.1.3" - resolved "https://registry.yarnpkg.com/@emotion/css/-/css-11.1.3.tgz#9ed44478b19e5d281ccbbd46d74d123d59be793f" - integrity sha512-RSQP59qtCNTf5NWD6xM08xsQdCZmVYnX/panPYvB6LQAPKQB6GL49Njf0EMbS3CyDtrlWsBcmqBtysFvfWT3rA== - dependencies: - "@emotion/babel-plugin" "^11.0.0" - "@emotion/cache" "^11.1.3" - "@emotion/serialize" "^1.0.0" - "@emotion/sheet" "^1.0.0" - "@emotion/utils" "^1.0.0" - "@emotion/hash@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"