diff --git a/src/components/organisms/GameBoard/Footer/index.tsx b/src/components/organisms/GameBoard/Footer/index.tsx index bd62624..815ab18 100644 --- a/src/components/organisms/GameBoard/Footer/index.tsx +++ b/src/components/organisms/GameBoard/Footer/index.tsx @@ -28,7 +28,13 @@ import { CountersTray } from "./Counters"; /** * HAND - Drag and Drop tray of all the cards in your hand * */ -export const GameFooter = (props: GameStateActions & PlayerDataProps) => { +export const GameFooter = ({ + setHoverCard, + ...props +}: GameStateActions & + PlayerDataProps & { + setHoverCard(slug: string): void; + }) => { const gridIndex = GRIDS.HAND; const cardsInHand = props.gridItems[gridIndex] ?? []; @@ -97,6 +103,7 @@ export const GameFooter = (props: GameStateActions & PlayerDataProps) => { gridIndex={gridIndex} cardIndex={index} gameStateActions={props} + setHoverCard={setHoverCard} /> ))} @@ -112,12 +119,21 @@ const HandCard = ({ gridIndex, cardIndex: index, gameStateActions, + ...props }: { card: GameCard; gridIndex: number; cardIndex: number; gameStateActions: GameStateActions; + setHoverCard(slug?: string): void; }) => { + function over() { + if (props.setHoverCard) props.setHoverCard(card.img); + } + function out() { + if (props.setHoverCard) props.setHoverCard(); + } + const [preview, setPreview] = useState(false); const deckType = card.type === "site" ? "atlas" : "deck"; @@ -139,6 +155,8 @@ const HandCard = ({ e.preventDefault(); setPreview(true); }} + onMouseOver={over} + onMouseOut={out} style={{ position: "relative", width: "100%", diff --git a/src/components/organisms/GameBoard/Layout.tsx b/src/components/organisms/GameBoard/Layout.tsx index 29ae701..cf0d0fc 100644 --- a/src/components/organisms/GameBoard/Layout.tsx +++ b/src/components/organisms/GameBoard/Layout.tsx @@ -1,21 +1,76 @@ -import { ReactNode } from "react"; +import { DragEvent, ReactNode, useState } from "react"; import { Box, Grid } from "styled-system/jsx"; import { grid } from "styled-system/patterns"; -import { LAYOUT_HEIGHTS } from "./constants"; +import { getCardImage, LAYOUT_HEIGHTS } from "./constants"; import { GameFooter } from "./Footer"; import { GameStateActions } from "."; import { Auras } from "./Auras"; import { PlayerDataProps } from "@/types/card"; import { GameHeader } from "./Header"; +import { useMediaQuery } from "@/utils/hooks/useMediaQuery"; const { nav, body, footer } = LAYOUT_HEIGHTS; export const GameLayout = ( props: GameStateActions & - PlayerDataProps & { isReversed?: boolean; children: ReactNode }, + PlayerDataProps & { + isReversed?: boolean; + activeCardSlug?: string; + setHoverCard(slug: string): void; + children: ReactNode; + }, ) => { + const matches = useMediaQuery("(min-width: 1200px)"); + const [trans, setTrans] = useState(""); + + const handleDrag = (e: DragEvent) => { + e.preventDefault(); // Prevent default drag behavior + + if (e.clientX !== 0 && e.clientY !== 0) { + const transform = `translate(${e.clientX - 150}px, ${e.clientY - 150}px)`; + setTrans(transform ?? ""); + } + }; + return ( - + + { + const img = new Image(); + img.src = ""; + e.dataTransfer.setDragImage(img, 0, 0); + }} + onDrag={handleDrag} // Directly update position for smoother dragging + cursor="grab" + style={{ + transform: trans, + display: matches ? "block" : "none", + }} + > + {matches && props.activeCardSlug && ( + card + )} + {matches && props.activeCardSlug === undefined && ( + + )} + diff --git a/src/components/organisms/GameBoard/SortItemWrapper.tsx b/src/components/organisms/GameBoard/SortItemWrapper.tsx index 00df1ec..de978f2 100644 --- a/src/components/organisms/GameBoard/SortItemWrapper.tsx +++ b/src/components/organisms/GameBoard/SortItemWrapper.tsx @@ -28,7 +28,15 @@ export const SortItemWrapper = ({ gridIndex: number; cardIndex: number; amountOfCards?: number; + setHoverImage?(slug?: string): void; } & GameStateActions) => { + function over() { + if (props.setHoverImage) props.setHoverImage(card.img); + } + function out() { + if (props.setHoverImage) props.setHoverImage(); + } + const { query } = useRouter(); const name = query?.name as string; @@ -78,6 +86,8 @@ export const SortItemWrapper = ({ e.stopPropagation(); toggleTap(); }} + onMouseOver={over} + onMouseOut={out} data-testid={"sortable-item-wrapper-" + card.id} style={{ height: heightCalc() + 7 + "px", diff --git a/src/components/organisms/GameBoard/index.tsx b/src/components/organisms/GameBoard/index.tsx index 2d5df10..76396cb 100644 --- a/src/components/organisms/GameBoard/index.tsx +++ b/src/components/organisms/GameBoard/index.tsx @@ -36,6 +36,8 @@ export const GameBoard = ({ setGridItems, }); + const [hoverCard, setHoverCard] = useState(); + const [gridHover, setGridHover] = useState(undefined); const { key, ...options } = LOCALSTORAGE_KEYS.SETTINGS.DISPLAY.toggle; const [isDisplay] = useLocalStorage(key, false, options); @@ -47,6 +49,8 @@ export const GameBoard = ({ gridItems={gridItems} setGridItems={setGridItems} isReversed={isReversed} + activeCardSlug={hoverCard} + setHoverCard={setHoverCard} {...playerDataProps} > {(isReversed @@ -105,6 +109,7 @@ export const GameBoard = ({ diff --git a/src/utils/hooks/useMediaQuery.ts b/src/utils/hooks/useMediaQuery.ts new file mode 100644 index 0000000..6eeaab7 --- /dev/null +++ b/src/utils/hooks/useMediaQuery.ts @@ -0,0 +1,61 @@ +import { useState } from "react"; + +import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect"; + +type UseMediaQueryOptions = { + defaultValue?: boolean; + initializeWithValue?: boolean; +}; + +const IS_SERVER = typeof window === "undefined"; + +export function useMediaQuery( + query: string, + { + defaultValue = false, + initializeWithValue = true, + }: UseMediaQueryOptions = {}, +): boolean { + const getMatches = (query: string): boolean => { + if (IS_SERVER) { + return defaultValue; + } + return window.matchMedia(query).matches; + }; + + const [matches, setMatches] = useState(() => { + if (initializeWithValue) { + return getMatches(query); + } + return defaultValue; + }); + + // Handles the change event of the media query. + function handleChange() { + setMatches(getMatches(query)); + } + + useIsomorphicLayoutEffect(() => { + const matchMedia = window.matchMedia(query); + + // Triggered at the first client-side load and if query changes + handleChange(); + + // Use deprecated `addListener` and `removeListener` to support Safari < 14 (#135) + if (matchMedia.addListener) { + matchMedia.addListener(handleChange); + } else { + matchMedia.addEventListener("change", handleChange); + } + + return () => { + if (matchMedia.removeListener) { + matchMedia.removeListener(handleChange); + } else { + matchMedia.removeEventListener("change", handleChange); + } + }; + }, [query]); + + return matches; +}