Skip to content

Instantly share code, notes, and snippets.

@nathan815
Last active January 5, 2023 02:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nathan815/fa927764dd3b0c47174e1805fe1b5577 to your computer and use it in GitHub Desktop.
Save nathan815/fa927764dd3b0c47174e1805fe1b5577 to your computer and use it in GitHub Desktop.
React Playing Card component
import React from 'react';
import { Flex } from '@chakra-ui/react';
import { PlayingCard, ALL_RANKS, ALL_SUITS } from './PlayingCard';
export function AllCards() {
return (
<Flex flexWrap="wrap" gap={5} mt={20} padding={10}>
{ALL_RANKS.map((rank) =>
ALL_SUITS.map((suit, idx) => (
<PlayingCard key={idx} suit={suit} rank={rank} width="22%" minWidth="200px" />
))
)}
</Flex>
);
}
@import url('https://fonts.googleapis.com/css2?family=Lobster+Two:wght@400;700&display=swap');
.playing-card {
user-select: none;
aspect-ratio: 0.7;
background-color: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
align-content: center;
position: relative;
border-radius: 5%;
}
.chakra-ui-dark .playing-card {
border: 1px solid #111;
box-shadow: 2px 3px 8px #111;
}
.chakra-ui-light .playing-card {
border: 1px solid #bbb;
box-shadow: 2px 3px 8px #ddd;
}
.playing-card.black {
color: #000;
}
.playing-card.red {
color: #cb2d2d;
}
.playing-card .card-rank {
font-size: 10vw ;
font-weight: bold;
font-family: 'Lobster Two', serif;
line-height: 1;
margin-bottom: 20px;
pointer-events: none;
}
.playing-card .card-suit {
position: absolute;
}
.playing-card .card-suit.card-suit-1 {
left: 15px;
top: 15px;
}
.playing-card .card-suit.card-suit-2 {
right: 15px;
bottom: 15px;
transform: rotate(180deg);
}
import { Box, BoxProps } from '@chakra-ui/react';
import React, { useEffect, useRef, useState } from 'react';
import {
BsSuitClubFill,
BsSuitHeartFill,
BsSuitDiamondFill,
BsSuitSpadeFill,
} from 'react-icons/bs';
import { IconType } from 'react-icons';
import './PlayingCard.css';
import { debounce } from 'lodash';
export type CardSuit = 'diamonds' | 'clubs' | 'hearts' | 'spades';
export const ALL_SUITS: CardSuit[] = ['diamonds', 'clubs', 'hearts', 'spades'];
const RANK_NAMES = {
1: { singular: 'Ace', plural: 'Aces' },
2: { singular: 'Two', plural: 'Twos' },
3: { singular: 'Three', plural: 'Threes' },
4: { singular: 'Four', plural: 'Fours' },
5: { singular: 'Five', plural: 'Fives' },
6: { singular: 'Six', plural: 'Sixes' },
7: { singular: 'Seven', plural: 'Sevens' },
8: { singular: 'Eight', plural: 'Eights' },
9: { singular: 'Nine', plural: 'Nines' },
10: { singular: 'Ten', plural: 'Tens' },
11: { singular: 'Jack', plural: 'Jacks' },
12: { singular: 'Queen', plural: 'Queens' },
13: { singular: 'King', plural: 'Kings' },
};
export type CardRankNumber = keyof typeof RANK_NAMES;
export const ALL_RANKS = Object.keys(RANK_NAMES).map((n) => parseInt(n)) as CardRankNumber[];
export function cardRankName(
rank: CardRankNumber,
plural = false
): string | undefined {
const name = RANK_NAMES[rank];
if (name) {
return plural ? name.plural : name.singular;
}
}
export function cardRankShortName(rank: CardRankNumber): string {
return (
{
1: 'A',
11: 'J',
12: 'Q',
13: 'K',
}[rank] || String(rank)
);
}
const suits: { [key in CardSuit]: [IconType, string] } = {
hearts: [BsSuitHeartFill, 'red'],
diamonds: [BsSuitDiamondFill, 'red'],
clubs: [BsSuitClubFill, 'black'],
spades: [BsSuitSpadeFill, 'black'],
};
interface PlayingCardProps {
suit: CardSuit;
rank: CardRankNumber;
}
export function PlayingCard(props: PlayingCardProps & BoxProps) {
const { suit, rank, ...restProps } = props;
const rankName = cardRankName(rank);
const rankLetter = cardRankShortName(rank);
const cardDiv = useRef<HTMLDivElement>(null);
const [SuitIcon, color] = suits[suit];
const [width, setWidth] = useState(0);
useEffect(() => {
const updateWidth = debounce(() => {
if (cardDiv?.current?.offsetWidth) {
setWidth(cardDiv?.current?.offsetWidth);
}
}, 100);
updateWidth();
window.addEventListener('resize', updateWidth);
return () => window.removeEventListener('reisze', updateWidth);
}, []);
return (
<Box
background="white"
width="100%"
className={`playing-card ${color}`}
title={`${rankName} of ${suit} Playing Card`}
ref={cardDiv}
{...restProps}
>
<div className="card-suit card-suit-1">
<SuitIcon size="5.5em" />
</div>
<div className="card-rank" style={{ fontSize: width / 1.8 }}>
{rankLetter}
</div>
<div className="card-suit card-suit-2">
<SuitIcon size="5.5em" />
</div>
</Box>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment