stack-the-deck (#1)
Allow for redrawing or explicitly selecting a card for replacement. Co-authored-by: Gavin McDonald <gavinmcdoh@gmail.com> Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import ToolTip from '@/components/ToolTip';
|
||||
import StackTheDeck from '@/components/StackTheDeck';
|
||||
import tarokkaCards from '@/constants/tarokkaCards';
|
||||
import getCardInfo from '@/tools/getCardInfo';
|
||||
import getURL from '@/tools/getURL';
|
||||
@@ -15,9 +17,21 @@ type CardProps = {
|
||||
position: Layout;
|
||||
settings: Settings;
|
||||
flipAction: () => void;
|
||||
redrawAction: () => void;
|
||||
selectAction: () => void;
|
||||
};
|
||||
|
||||
export default function Card({ dm, card, position, settings, flipAction }: CardProps) {
|
||||
export default function Card({
|
||||
dm,
|
||||
card,
|
||||
position,
|
||||
settings,
|
||||
flipAction,
|
||||
redrawAction,
|
||||
selectAction,
|
||||
}: CardProps) {
|
||||
const [tooltip, setTooltip] = useState<React.ReactNode>(null);
|
||||
|
||||
const { aria, flipped } = card;
|
||||
|
||||
const handleClick = () => {
|
||||
@@ -42,22 +56,39 @@ export default function Card({ dm, card, position, settings, flipAction }: CardP
|
||||
};
|
||||
|
||||
return (
|
||||
<ToolTip content={getTooltip()}>
|
||||
<ToolTip content={tooltip || getTooltip()}>
|
||||
<div
|
||||
className={`relative h-[21vh] w-[15vh] perspective transition-transform duration-200 hover:scale-150 z-0 hover:z-10 ${dm ? 'cursor-pointer' : ''} `}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div
|
||||
className={`transition-transform duration-500 transform-style-preserve-3d ${flipped ? 'rotate-y-180' : ''}`}
|
||||
className={`absolute inset-0 transition-transform duration-500 transform-style-preserve-3d ${flipped ? 'rotate-y-180' : ''}`}
|
||||
>
|
||||
<div className="absolute group inset-0 backface-hidden">
|
||||
<div className="absolute inset-0 group backface-hidden">
|
||||
{dm && (
|
||||
<>
|
||||
<img src={getURL(card, settings)} alt={aria} className="absolute rounded-lg" />
|
||||
<img
|
||||
src={getURL(cardBack as TarokkaGameCard, settings)}
|
||||
alt=""
|
||||
className={`absolute rounded-lg see-through`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<img
|
||||
src={getURL(cardBack as TarokkaGameCard, settings)}
|
||||
alt="Card Back"
|
||||
className={`rounded-lg ${settings.cardStyle === 'grayscale' ? 'border border-yellow-500/25 hover:drop-shadow-[0_0_3px_#ffd700/50]' : ''}`}
|
||||
className={`absolute rounded-lg ${dm ? 'transition duration-500 group-hover:opacity-0' : ''} ${settings.cardStyle === 'grayscale' ? 'border border-yellow-500/25 group-hover:drop-shadow-[0_0_3px_#ffd700/50]' : ''}`}
|
||||
/>
|
||||
{dm && !flipped && (
|
||||
<StackTheDeck
|
||||
onRedraw={redrawAction}
|
||||
onSelect={() => selectAction()}
|
||||
onHover={setTooltip}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="absolute group inset-0 backface-hidden rotate-y-180">
|
||||
<div className="absolute inset-0 backface-hidden rotate-y-180">
|
||||
<img
|
||||
src={getURL(card, settings)}
|
||||
alt={aria}
|
||||
|
||||
73
components/CardSelect.tsx
Normal file
73
components/CardSelect.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
'use client';
|
||||
|
||||
import { CircleX } from 'lucide-react';
|
||||
import TarokkaDeck from '@/lib/TarokkaDeck';
|
||||
import getURL from '@/tools/getURL';
|
||||
|
||||
import { Deck, Settings, TarokkaGameCard } from '@/types';
|
||||
|
||||
const tarokkaDeck = new TarokkaDeck();
|
||||
|
||||
type CardSelectProps = {
|
||||
closeAction: () => void;
|
||||
selectAction: (cardID: string) => void;
|
||||
hand: TarokkaGameCard[];
|
||||
settings: Settings;
|
||||
show: Deck | null;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function CardSelect({
|
||||
closeAction,
|
||||
selectAction,
|
||||
hand,
|
||||
settings,
|
||||
show,
|
||||
className = '',
|
||||
}: CardSelectProps) {
|
||||
const handIDs = hand.map(({ id }) => id);
|
||||
|
||||
const handleClose = (event: React.MouseEvent<HTMLElement>) => {
|
||||
if (event.target === event.currentTarget) {
|
||||
closeAction();
|
||||
}
|
||||
};
|
||||
|
||||
if (!show) return null;
|
||||
|
||||
const cards = show === 'high' ? tarokkaDeck.getHigh() : tarokkaDeck.getLow();
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={handleClose}
|
||||
className={`fixed inset-0 flex justify-center items-center p-4 bg-black/20 backdrop-blur-sm z-40 ${className}`}
|
||||
>
|
||||
<button
|
||||
className={`fixed top-4 right-4 p-2 transition-all duration-250 text-yellow-400 hover:text-yellow-300 hover:drop-shadow-[0_0_3px_#ffd700] cursor-pointer`}
|
||||
onClick={closeAction}
|
||||
>
|
||||
<CircleX className="w-6 h-6" />
|
||||
</button>
|
||||
<div
|
||||
onClick={handleClose}
|
||||
className={`flex flex-wrap justify-center items-center gap-3 h-dvh w-2/3 overflow-scroll scrollbar-hide p-4`}
|
||||
>
|
||||
{cards
|
||||
.filter(({ id }) => !handIDs.includes(id))
|
||||
.map((card) => (
|
||||
<div
|
||||
key={card.id}
|
||||
className={`relative h-[21vh] w-[15vh] perspective transition-transform duration-200 hover:scale-150 z-0 hover:z-10`}
|
||||
onClick={() => selectAction(card.id)}
|
||||
>
|
||||
<img
|
||||
src={getURL(card, settings)}
|
||||
alt={card.aria}
|
||||
className="rounded-lg border border-yellow-500/25 hover:drop-shadow-[0_0_3px_#ffd700/50]"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ export default function Scrim({ children, clickAction, show = true, className =
|
||||
clickAction(event);
|
||||
}
|
||||
};
|
||||
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
|
||||
48
components/StackTheDeck.tsx
Normal file
48
components/StackTheDeck.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { GalleryHorizontalEnd, RefreshCw } from 'lucide-react';
|
||||
|
||||
interface StackTheDeckProps {
|
||||
onRedraw: () => void;
|
||||
onSelect: () => void;
|
||||
onHover: (state: React.ReactNode) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function StackTheDeck({
|
||||
onRedraw,
|
||||
onSelect,
|
||||
onHover,
|
||||
className = '',
|
||||
}: StackTheDeckProps) {
|
||||
const curryHandleClick = (action: () => void) => (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
action();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`absolute top-0 right-0 flex flex-col items-center justify-center bg-black/50 rounded-tr-lg rounded-bl-lg ${className}`}
|
||||
>
|
||||
<button
|
||||
onMouseEnter={() => onHover(<p className="text-yellow-400">Redraw</p>)}
|
||||
onMouseLeave={() => onHover(null)}
|
||||
onTouchStart={() => onHover(<p className="text-yellow-400">Redraw</p>)}
|
||||
onTouchEnd={() => onHover(null)}
|
||||
className={`p-1 transition-all duration-250 text-yellow-400 hover:text-yellow-300 hover:drop-shadow-[0_0_3px_#ffd700] cursor-pointer`}
|
||||
onClick={curryHandleClick(onRedraw)}
|
||||
>
|
||||
<RefreshCw className="w-3 h-3" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onMouseEnter={() => onHover(<p className="text-yellow-400">Select</p>)}
|
||||
onMouseLeave={() => onHover(null)}
|
||||
onTouchStart={() => onHover(<p className="text-yellow-400">Select</p>)}
|
||||
onTouchEnd={() => onHover(null)}
|
||||
className={`p-1 transition-all duration-250 text-yellow-400 hover:text-yellow-300 hover:drop-shadow-[0_0_3px_#ffd700] cursor-pointer`}
|
||||
onClick={curryHandleClick(onSelect)}
|
||||
>
|
||||
<GalleryHorizontalEnd className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -15,8 +15,8 @@ type TooltipProps = {
|
||||
export default function Tooltip({
|
||||
children,
|
||||
content,
|
||||
delay = 500,
|
||||
mobileDelay = 500,
|
||||
delay = 250,
|
||||
mobileDelay = 250,
|
||||
offsetX = 20,
|
||||
offsetY = 20,
|
||||
edgeBuffer = 10,
|
||||
|
||||
Reference in New Issue
Block a user