diff --git a/app/javascript/wardrobe-2020/WardrobePage/OutfitControls.js b/app/javascript/wardrobe-2020/WardrobePage/OutfitControls.js
index b931c400..836b20c0 100644
--- a/app/javascript/wardrobe-2020/WardrobePage/OutfitControls.js
+++ b/app/javascript/wardrobe-2020/WardrobePage/OutfitControls.js
@@ -11,6 +11,9 @@ import {
HStack,
IconButton,
ListItem,
+ Menu,
+ MenuItem,
+ MenuList,
Popover,
PopoverArrow,
PopoverBody,
@@ -106,156 +109,197 @@ 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();
+
+ // 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);
}
- `,
- 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);
- }
- }}
- onContextMenuCapture={() => {
- if (!toast.isActive("outfit-controls-context-menu-hint")) {
- toast({
- id: "outfit-controls-context-menu-hint",
- title:
- "By the way, to save this image, use the Download button!",
- description: "It's in the top right of the preview area.",
- duration: 10000,
- isClosable: true,
- });
- }
- }}
- data-test-id="wardrobe-outfit-controls"
- >
-
-
-
-
-
- {showAnimationControls && }
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
- {outfitState.speciesId && outfitState.colorId && (
-
- {/**
- * 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!
- */}
-
-
-
-
-
-
-
-
+ {showAnimationControls && }
+
+
+
+
+
-
+
- )}
-
+
+
+
+
+
+
+
+
+
+ {outfitState.speciesId && outfitState.colorId && (
+
+ {/**
+ * 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!
+ */}
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
)}
);
}
+function OutfitControlsContextMenu({ outfitState, children }) {
+ // NOTE: We track these separately, rather than in one atomic state object,
+ // because I want to still keep the menu in the right position when it's
+ // animating itself closed!
+ const [isOpen, setIsOpen] = React.useState(false);
+ const [position, setPosition] = React.useState({ x: 0, y: 0 });
+
+ const { visibleLayers } = useOutfitAppearance(outfitState);
+ const [downloadImageUrl, prepareDownload] =
+ useDownloadableImage(visibleLayers);
+
+ return (
+ {
+ setIsOpen(true);
+ setPosition({ x: e.pageX, y: e.pageY });
+ e.preventDefault();
+ }}
+ >
+ {children}
+
+
+ );
+}
+
function OutfitHTML5Badge({ appearance }) {
const petIsUsingHTML5 =
appearance.petAppearance?.layers.every(layerUsesHTML5);