diff --git a/src/app/ItemPage.js b/src/app/ItemPage.js index 0e07eea..5d4edfb 100644 --- a/src/app/ItemPage.js +++ b/src/app/ItemPage.js @@ -19,6 +19,7 @@ import { Wrap, WrapItem, Flex, + usePrefersReducedMotion, } from "@chakra-ui/react"; import { CheckIcon, @@ -34,7 +35,7 @@ import { useQuery, useMutation } from "@apollo/client"; import { Link, useParams } from "react-router-dom"; import ItemPageLayout, { SubtleSkeleton } from "./ItemPageLayout"; -import { Delay, usePageTitle } from "./util"; +import { Delay, logAndCapture, usePageTitle } from "./util"; import { itemAppearanceFragment, petAppearanceFragment, @@ -813,25 +814,80 @@ function CustomizeMoreButton({ speciesId, colorId, pose, itemId }) { // The default background is good in light mode, but in dark mode it's a // very subtle transparent white... make it a semi-transparent black, for // better contrast against light-colored background items! - const backgroundColor = useColorModeValue(undefined, "blackAlpha.600"); - const backgroundColorHover = useColorModeValue(undefined, "blackAlpha.700"); + const backgroundColor = useColorModeValue(undefined, "blackAlpha.700"); + const backgroundColorHover = useColorModeValue(undefined, "blackAlpha.900"); return ( - - } - position="absolute" - top="2" - right="2" - size="sm" - background={backgroundColor} - _hover={{ backgroundColor: backgroundColorHover }} - _focus={{ backgroundColor: backgroundColorHover }} - boxShadow="sm" - /> - + + ); +} + +/** + * ExpandOnGroupHover starts at width=0, and expands to full width when a + * parent with role="group" gains hover or focus state. + */ +function ExpandOnGroupHover({ children, ...props }) { + const [measuredWidth, setMeasuredWidth] = React.useState(null); + const measurerRef = React.useRef(null); + const prefersReducedMotion = usePrefersReducedMotion(); + + React.useLayoutEffect(() => { + if (!measurerRef) { + // I don't think this is possible, but I'd like to know if it happens! + logAndCapture( + new Error( + `Measurer node not ready during effect. Transition won't be smooth.` + ) + ); + return; + } + + if (measuredWidth != null) { + // Skip re-measuring when we already have a measured width. This is + // mainly defensive, to prevent the possibility of loops, even though + // this algorithm should be stable! + return; + } + + const newMeasuredWidth = measurerRef.current.offsetWidth; + setMeasuredWidth(newMeasuredWidth); + }, [measuredWidth]); + + return ( + + + {children} + + ); }