Skip to content

Commit

Permalink
Merge pull request #13 from JollyGrin/feat/deck-preview
Browse files Browse the repository at this point in the history
Feat/deck preview
  • Loading branch information
JollyGrin authored Sep 10, 2024
2 parents ad21150 + 6f62924 commit 6de3eab
Show file tree
Hide file tree
Showing 10 changed files with 287 additions and 85 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"next": "14.2.8",
"react": "^18",
"react-dom": "^18",
"react-hot-toast": "^2.4.1",
"react-icons": "^5.3.0"
},
"devDependencies": {
Expand Down
27 changes: 27 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/components/atoms/Modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import {
import { DialogDescription, DialogProps } from "@radix-ui/react-dialog";
import { ReactNode } from "react";
import { VisuallyHidden } from "styled-system/jsx";
import { Pretty } from "styled-system/types";

export const Modal = (props: {
wrapperProps: DialogProps;
wrapperProps: Pretty<DialogProps>;
trigger?: ReactNode;
title?: string;
content?: ReactNode;
Expand Down
81 changes: 0 additions & 81 deletions src/components/organisms/GameBoard/Footer/Decks.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { CardImage } from "@/components/atoms/card-view/card";
import { GameCard } from "@/types/card";
import { Box, Grid, HStack } from "styled-system/jsx";
import { button } from "styled-system/recipes";

import { PiHandWithdrawFill as IconHand } from "react-icons/pi";
import { useHover } from "@/utils/hooks/useHover";
import { useRef } from "react";
import { GameStateActions } from "../..";
import { GRIDS } from "../../constants";
import { actDrawDeckIndex } from "@/utils/actions";

export const DeckModalBody = ({
deckType,
...props
}: {
deckType?: "deck" | "atlas";
} & GameStateActions) => {
if (deckType === undefined) return null;

const deckTypeGridIndex = deckType === "deck" ? GRIDS.DECK : GRIDS.ATLAS_DECK;
const cards = props.gridItems[deckTypeGridIndex];

function returnToHand(cardIndex: number) {
if (deckType === undefined) return;
props.setGridItems(actDrawDeckIndex(props.gridItems, deckType, cardIndex));
}

return (
<Grid
gridTemplateColumns="repeat(auto-fill, minmax(300px, 1fr))"
gridTemplateRows="auto"
minW="75vw"
h="70vh"
overflowX="clip"
overflowY="scroll"
>
{cards?.map((card, index) => (
<Card
key={card.id}
card={card}
returnToHand={() => returnToHand(index)}
/>
))}
</Grid>
);
};

const Card = ({
card,
returnToHand,
}: {
card: GameCard;
returnToHand(): void;
}) => {
const hoverRef = useRef(null);
const isHovering = useHover(hoverRef);

return (
<Box
ref={hoverRef}
key={card.id + "discard"}
aspectRatio={8 / 11}
w="100%"
h="100%"
maxH="500px"
position="relative"
filter="saturate(1)"
_hover={{
filter: "saturate(1.75)",
}}
>
<CardImage img={card.img} minH={"0"} />
<button
onClick={returnToHand}
className={button()}
style={{
position: "absolute",
bottom: "50%",
right: "calc(50% - 50px)",
opacity: isHovering ? 1 : 0.1,
width: "100px",
transition: "all 0.25s ease",
}}
>
<HStack gap={0}>
<p>Return</p>
<IconHand size="2rem" style={{ fontSize: "2rem", color: "white" }} />
</HStack>
</button>
</Box>
);
};
112 changes: 112 additions & 0 deletions src/components/organisms/GameBoard/Footer/Decks/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { Grid, VStack } from "styled-system/jsx";
import {
GRIDS,
LAYOUT_HEIGHTS,
} from "@/components/organisms/GameBoard/constants";
import { GameStateActions } from "@/components/organisms/GameBoard";
import { actDrawAtlas, actDrawDeck, actShuffleDeck } from "@/utils/actions";
import { cva } from "styled-system/css/cva.mjs";
import { Modal } from "@/components/atoms/Modal";
import { useState } from "react";
import { DeckModalBody } from "./DeckPreviewModal";
import toast from "react-hot-toast";

export const DecksTray = (props: GameStateActions) => {
const [preview, setPreview] = useState<"deck" | "atlas">();

function draw(deck: GRIDS.DECK | GRIDS.ATLAS_DECK) {
deck === GRIDS.DECK
? props.setGridItems(actDrawDeck(props.gridItems))
: props.setGridItems(actDrawAtlas(props.gridItems));
}

function drawDeck() {
draw(GRIDS.DECK);
}
function drawAtlas() {
draw(GRIDS.ATLAS_DECK);
}

function shuffleDeck(deckType: typeof preview) {
if (deckType) props.setGridItems(actShuffleDeck(props.gridItems, deckType));
toast.success("Shuffled after closing");
}

const atlasRemainingCards = props.gridItems[GRIDS.ATLAS_DECK]?.length;
const deckRemainingCards = props.gridItems[GRIDS.DECK]?.length;

return (
<>
<Modal
wrapperProps={{
open: !!preview,
onOpenChange: () => {
// when closing the modal, delay shuffling the deck briefly
// avoids briefly showing new deck order before modal closes
const wasOpen = preview;
setPreview(undefined);
setTimeout(() => {
if (!!wasOpen) shuffleDeck(wasOpen);
}, 100);
},
}}
content={<DeckModalBody deckType={preview} {...props} />}
/>
<VStack
w="100%"
py="1rem"
style={{
height: LAYOUT_HEIGHTS.footer,
background:
"linear-gradient(90deg, rgba(131,58,180,0.15) 0%, rgba(253,29,29,0.15) 50%, rgba(252,176,69,0.35) 100%)",
}}
>
<Grid
onContextMenu={(e) => {
e.preventDefault();
setPreview("atlas");
}}
h="70px"
aspectRatio={3 / 2}
onClick={drawAtlas}
placeItems="center"
borderRadius="0.25rem"
cursor="pointer"
backgroundSize="cover"
style={{
backgroundImage: "url(/card-backs/m_atlas.png)",
}}
>
<p className={remainingCards()}>{atlasRemainingCards}</p>
</Grid>
<Grid
onContextMenu={(e) => {
e.preventDefault();
setPreview("deck");
}}
w="60px"
aspectRatio={2 / 3}
onClick={drawDeck}
placeItems="center"
borderRadius="0.25rem"
cursor="pointer"
backgroundSize="cover"
style={{
backgroundImage: "url(/card-backs/m_spells.png)",
}}
>
<p className={remainingCards()}>{deckRemainingCards}</p>
</Grid>
</VStack>
</>
);
};

const remainingCards = cva({
base: {
fontWeight: 700,
background: "rgba(255,255,255,0.7)",
padding: "0 0.25rem",
borderRadius: "0.25rem",
},
});
2 changes: 1 addition & 1 deletion src/components/organisms/GameBoard/Footer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const GameFooter = (props: GameStateActions) => {
overflowX: "auto",
}}
>
<Grid h="100%" gridTemplateColumns="repeat(2,150px) 1fr" gap={0}>
<Grid h="100%" gridTemplateColumns="130px 100px 1fr" gap={0}>
<GraveTray {...props} />
<DecksTray {...props} />
<DroppableGridItem id={gridIndex.toString()} gridIndex={gridIndex}>
Expand Down
2 changes: 2 additions & 0 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import "@/styles/globals.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type { AppProps } from "next/app";
import { Toaster } from "react-hot-toast";

const queryClient = new QueryClient();

export default function App({ Component, pageProps }: AppProps) {
return (
<QueryClientProvider client={queryClient}>
<Component {...pageProps} />
<Toaster />
</QueryClientProvider>
);
}
Loading

0 comments on commit 6de3eab

Please sign in to comment.