PosePicker is a radio!
This commit is contained in:
parent
117a802835
commit
752828b4b0
1 changed files with 110 additions and 25 deletions
|
@ -11,6 +11,7 @@ import {
|
||||||
PopoverArrow,
|
PopoverArrow,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
|
VisuallyHidden,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@chakra-ui/core";
|
} from "@chakra-ui/core";
|
||||||
|
|
||||||
|
@ -23,17 +24,18 @@ import twemojiSick from "../images/twemoji/sick.svg";
|
||||||
import twemojiMasc from "../images/twemoji/masc.svg";
|
import twemojiMasc from "../images/twemoji/masc.svg";
|
||||||
import twemojiFem from "../images/twemoji/fem.svg";
|
import twemojiFem from "../images/twemoji/fem.svg";
|
||||||
import { OutfitLayers } from "./OutfitPreview";
|
import { OutfitLayers } from "./OutfitPreview";
|
||||||
import { safeImageUrl } from "./util";
|
|
||||||
|
|
||||||
function PosePicker({ outfitState, onLockFocus, onUnlockFocus }) {
|
function PosePicker({ outfitState, onLockFocus, onUnlockFocus }) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const { speciesId, colorId } = outfitState;
|
const { speciesId, colorId } = outfitState;
|
||||||
const { loading, error, poses } = useAvailablePoses({
|
const { loading, error, poses, selectPose } = usePoses({
|
||||||
speciesId,
|
speciesId,
|
||||||
colorId,
|
colorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const checkedInputRef = React.useRef();
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -52,10 +54,11 @@ function PosePicker({ outfitState, onLockFocus, onUnlockFocus }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
placement="top-end"
|
placement="bottom-end"
|
||||||
usePortal
|
usePortal
|
||||||
onOpen={onLockFocus}
|
onOpen={onLockFocus}
|
||||||
onClose={onUnlockFocus}
|
onClose={onUnlockFocus}
|
||||||
|
initialFocusRef={checkedInputRef}
|
||||||
>
|
>
|
||||||
{({ isOpen }) => (
|
{({ isOpen }) => (
|
||||||
<>
|
<>
|
||||||
|
@ -92,7 +95,16 @@ function PosePicker({ outfitState, onLockFocus, onUnlockFocus }) {
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent>
|
<PopoverContent>
|
||||||
<Box p="4">
|
<Box p="4">
|
||||||
<table width="100%" borderSpacing="8px">
|
<table
|
||||||
|
width="100%"
|
||||||
|
borderSpacing="8px"
|
||||||
|
onChange={(e) => {
|
||||||
|
const [emotion, genderPresentation] = e.target.value.split(
|
||||||
|
"-"
|
||||||
|
);
|
||||||
|
selectPose({ emotion, genderPresentation });
|
||||||
|
}}
|
||||||
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th />
|
<th />
|
||||||
|
@ -161,12 +173,46 @@ function Cell({ children, as }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EMOTION_STRINGS = {
|
||||||
|
HAPPY: "Happy",
|
||||||
|
SAD: "Sad",
|
||||||
|
SICK: "Sick",
|
||||||
|
};
|
||||||
|
|
||||||
|
const GENDER_PRESENTATION_STRINGS = {
|
||||||
|
MASCULINE: "Masculine",
|
||||||
|
FEMININE: "Feminine",
|
||||||
|
};
|
||||||
|
|
||||||
function PoseButton({ pose, speciesId }) {
|
function PoseButton({ pose, speciesId }) {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
if (!pose) {
|
if (!pose) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const genderPresentationStr =
|
||||||
|
GENDER_PRESENTATION_STRINGS[pose.genderPresentation];
|
||||||
|
const emotionStr = EMOTION_STRINGS[pose.emotion];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Box
|
||||||
|
as="label"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
// HACK: We need the timeout to beat the popover's focus stealing!
|
||||||
|
const input = e.currentTarget.querySelector("input");
|
||||||
|
setTimeout(() => input.focus(), 0);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VisuallyHidden
|
||||||
|
as="input"
|
||||||
|
type="radio"
|
||||||
|
aria-label={`${emotionStr} and ${genderPresentationStr}`}
|
||||||
|
name="pose"
|
||||||
|
value={`${pose.emotion}-${pose.genderPresentation}`}
|
||||||
|
checked={pose.isSelected}
|
||||||
|
/>
|
||||||
<Box
|
<Box
|
||||||
rounded="full"
|
rounded="full"
|
||||||
boxShadow="md"
|
boxShadow="md"
|
||||||
|
@ -174,18 +220,49 @@ function PoseButton({ pose, speciesId }) {
|
||||||
width="50px"
|
width="50px"
|
||||||
height="50px"
|
height="50px"
|
||||||
title={window.location.hostname.includes("localhost") && `#${pose.id}`}
|
title={window.location.hostname.includes("localhost") && `#${pose.id}`}
|
||||||
|
position="relative"
|
||||||
|
className={css`
|
||||||
|
transform: scale(0.8);
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
input:checked + & {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
`}
|
||||||
>
|
>
|
||||||
<Button variant="unstyled" width="100%" height="100%">
|
|
||||||
<Box
|
<Box
|
||||||
width="100%"
|
rounded="full"
|
||||||
height="100%"
|
position="absolute"
|
||||||
|
top="0"
|
||||||
|
bottom="0"
|
||||||
|
left="0"
|
||||||
|
right="0"
|
||||||
|
zIndex="2"
|
||||||
|
className={css`
|
||||||
|
border: 0px solid ${theme.colors.green["600"]};
|
||||||
|
transition: border-width 0.2s;
|
||||||
|
|
||||||
|
input:checked + * & {
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus + * & {
|
||||||
|
border-width: 3px;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
width="50px"
|
||||||
|
height="50px"
|
||||||
transform={
|
transform={
|
||||||
transformsBySpeciesId[speciesId] || transformsBySpeciesId.default
|
transformsBySpeciesId[speciesId] || transformsBySpeciesId.default
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<OutfitLayers visibleLayers={getVisibleLayers(pose, [])} />
|
<OutfitLayers visibleLayers={getVisibleLayers(pose, [])} />
|
||||||
</Box>
|
</Box>
|
||||||
</Button>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -194,7 +271,12 @@ function EmojiImage({ src, "aria-label": ariaLabel }) {
|
||||||
return <Image src={src} aria-label={ariaLabel} width="16px" height="16px" />;
|
return <Image src={src} aria-label={ariaLabel} width="16px" height="16px" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useAvailablePoses({ speciesId, colorId }) {
|
function usePoses({ speciesId, colorId }) {
|
||||||
|
const [selectedPose, selectPose] = React.useState({
|
||||||
|
emotion: "HAPPY",
|
||||||
|
genderPresentation: "FEMININE",
|
||||||
|
});
|
||||||
|
|
||||||
const { loading, error, data } = useQuery(
|
const { loading, error, data } = useQuery(
|
||||||
gql`
|
gql`
|
||||||
query PosePicker($speciesId: ID!, $colorId: ID!) {
|
query PosePicker($speciesId: ID!, $colorId: ID!) {
|
||||||
|
@ -212,21 +294,24 @@ function useAvailablePoses({ speciesId, colorId }) {
|
||||||
);
|
);
|
||||||
|
|
||||||
const petAppearances = data?.petAppearances || [];
|
const petAppearances = data?.petAppearances || [];
|
||||||
const findAppearanceFor = (e, gp) =>
|
const buildPose = (e, gp) => ({
|
||||||
petAppearances.find(
|
...petAppearances.find(
|
||||||
(pa) => pa.emotion === e && pa.genderPresentation === gp
|
(pa) => pa.emotion === e && pa.genderPresentation === gp
|
||||||
);
|
),
|
||||||
|
isSelected:
|
||||||
|
selectedPose.emotion === e && selectedPose.genderPresentation === gp,
|
||||||
|
});
|
||||||
|
|
||||||
const poses = {
|
const poses = {
|
||||||
happyMasc: findAppearanceFor("HAPPY", "MASCULINE"),
|
happyMasc: buildPose("HAPPY", "MASCULINE"),
|
||||||
sadMasc: findAppearanceFor("SAD", "MASCULINE"),
|
sadMasc: buildPose("SAD", "MASCULINE"),
|
||||||
sickMasc: findAppearanceFor("SICK", "MASCULINE"),
|
sickMasc: buildPose("SICK", "MASCULINE"),
|
||||||
happyFem: findAppearanceFor("HAPPY", "FEMININE"),
|
happyFem: buildPose("HAPPY", "FEMININE"),
|
||||||
sadFem: findAppearanceFor("SAD", "FEMININE"),
|
sadFem: buildPose("SAD", "FEMININE"),
|
||||||
sickFem: findAppearanceFor("SICK", "FEMININE"),
|
sickFem: buildPose("SICK", "FEMININE"),
|
||||||
};
|
};
|
||||||
|
|
||||||
return { loading, error, poses };
|
return { loading, error, poses, selectPose };
|
||||||
}
|
}
|
||||||
|
|
||||||
const transformsBySpeciesId = {
|
const transformsBySpeciesId = {
|
||||||
|
|
Loading…
Reference in a new issue