deckfx
v1.0.1
Published
A high-performance, interaction-rich React library for building card and board games
Downloads
274
Maintainers
Readme
DeckFX Demos
Interactive demo showcase for the DeckFX library
🎮 Live Demo • 📦 npm Package • 💻 Examples
This repository contains interactive demonstrations of the DeckFX library - a high-performance, interaction-rich React library for building card and board games.
DeckFX provides production-ready components for 3D card rendering, layout management, and dice rolling. Built with React, Three.js, and Framer Motion for smooth animations and interactions.
Note: This repository uses the published
deckfxnpm package. For the library source code, see the npm package.
Features
- 🎴 3D Card Rendering - Realistic card physics with tilt, glare, and flip animations
- 🎯 Layout Engine - Multiple layout modes (fan, grid, stack, cascade, messy)
- 🎲 3D Dice System - Support for D4, D6, D8, D10, D12, and D20 with physics
- 🖱️ Drag & Drop - Built-in drag interactions with snap-to-origin
- ✅ Async Validation - Server-side validation for all card interactions (drag, drop, reorder, draw)
- 🎨 Customizable - Full control over card appearance, animations, and interactions
- 📱 TypeScript - Fully typed with comprehensive type definitions
- ⚡ Performance - Optimized for smooth 60fps animations
Installation
npm install deckfx framer-motion three @react-three/fiber @react-three/drei lucide-reactQuick Start
import { Card, CardZone, DiceRoller } from 'deckfx';
function App() {
return (
<CardZone layout="fan">
<Card faceUp={true} width={120} height={170}>
<div>Ace of Spades</div>
</Card>
</CardZone>
);
}Examples
Check out the live demo to see DeckFX in action:
- TCG Battle - Trading card game battlefield with drag & drop
- Hand Physics - Interactive card hand with hover effects
- Inventory Grid - Grid-based card inventory system
- Solitaire - Classic solitaire game implementation
- RPG Adventure - Role-playing game card system
- War - Card game with deck management
- Blackjack - Casino card game
- Dice Roller - 3D dice with physics simulation
Core Components
Card
The fundamental building block. Handles 3D tilt, glare effects, flipping animations, and drag interactions.
<Card
faceUp={true}
imageUrl="/card-art.jpg"
width={200}
height={300}
showGlare={true}
maxTilt={15}
drag
dragSnapToOrigin
onClick={() => console.log('clicked')}
/>Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| faceUp | boolean | true | Whether the card face is visible |
| width | number | 200 | Card width in pixels |
| height | number | 300 | Card height in pixels |
| imageUrl | string | - | Background image for the card |
| showGlare | boolean | true | Enable holographic glare effect |
| maxTilt | number | 15 | Maximum 3D tilt angle in degrees |
| rotation | number | 0 | Z-axis rotation in degrees |
| drag | boolean | false | Enable drag interactions |
| onValidateDragStart | (cardId: string, cardData?: any) => Promise<ValidationResult> | - | Async validator for drag start |
| onValidateDragEnd | (cardId: string, fromZone: string, toZone: string, cardData?: any) => Promise<ValidationResult> | - | Async validator for drag end |
| onValidationError | (error: string) => void | - | Callback for validation errors |
| cardId | string | - | Card identifier for validation |
| cardData | any | - | Additional data to pass to validators |
| onFlip | (newFaceUp: boolean) => void | - | Callback when flip animation starts |
| onFlipComplete | (faceUp: boolean) => void | - | Callback when flip animation completes |
| flipDuration | number | - | Flip animation duration in ms |
| flipDirection | 'horizontal' \| 'vertical' | 'horizontal' | Direction of flip animation |
| selectable | boolean | false | Whether card can be selected |
| selected | boolean | false | Controlled selected state |
| onSelect | (cardId: string, event?: MouseEvent) => void | - | Callback when card is selected |
| counters | Counter[] | - | Array of counters to display on card |
| renderCounter | (counter: Counter) => ReactNode | - | Custom counter renderer |
| stackId | string | - | ID of the stack this card belongs to |
| stackOrder | number | - | Order within stack (0 = bottom) |
| isStackTop | boolean | - | Whether card is on top of stack |
CardZone
A layout engine that automatically positions child cards.
<CardZone
layout="fan"
fanAngle={15}
fanArcHeight={20}
>
{cards.map(card => <Card key={card.id} {...card} />)}
</CardZone>Layouts:
stack- Cards piled on top (deck)grid- Standard grid (inventory)linear-horizontal- Row of cardslinear-vertical- Column of cardsmessy- Random rotation (discard pile)fan- Arc layout (player hand)cascade-vertical- Solitaire-style cascadecascade-horizontal- Horizontal cascade
Capacity Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| maxCards | number | - | Maximum number of cards allowed |
| currentCount | number | - | Manual count override (auto-calculated if not provided) |
| showCapacity | boolean | false | Show capacity badge |
| capacityWarningThreshold | number | 0.8 | Threshold (0-1) for warning color |
| onCapacityReached | () => void | - | Callback when capacity is reached |
| dropZoneValid | boolean | - | External control for drop zone validity |
| onDragOver | (isOver: boolean) => void | - | Callback when dragging over zone |
DiceRoller
A 3D dice engine supporting D4, D6, D8, D10, D12, and D20.
import { DiceRoller, DieState, DieConfig } from 'deckfx';
const [dice, setDice] = useState<DieState[]>([
{ id: 1, type: 'd20', value: 1, target: 20, isRolling: false }
]);
const config: DieConfig = {
color: '#ffffff',
duration: 2000,
size: 1,
spinForce: 5
};
<DiceRoller
dice={dice}
config={config}
zoom={15}
onRollComplete={(id) => {
setDice(prev => prev.map(d =>
d.id === id ? { ...d, isRolling: false } : d
));
}}
/>DeckSearchModal
A pre-built modal for searching through card collections.
<DeckSearchModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Search Deck"
cards={deckCards}
searchProperty="title"
onCardSelect={(card) => handleSelect(card)}
renderCard={(card) => <Card {...card} />}
/>Deck
A visual representation of a card deck with draw functionality and validation support.
<Deck
count={52}
onDraw={() => {
// Handle card draw
}}
onValidateDraw={async () => {
// Validate if draw is allowed
return { valid: true };
}}
onValidationError={(error) => {
// Handle validation errors
console.error(error);
}}
/>Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| count | number | - | Number of cards in the deck |
| onDraw | () => void | - | Callback when deck is clicked |
| thickness | number | 1 | Visual thickness per card |
| onValidateDraw | () => Promise<ValidationResult> | - | Async validator for draw operation |
| onValidationError | (error: string) => void | - | Callback for validation errors |
Hand
A specialized component for player hands with hover effects, reordering, and validation.
<Hand
spacing={-40}
arcHeight={30}
maxFanAngle={15}
sortable={true}
onReorder={(fromIndex, toIndex) => {
// Handle reorder
}}
onValidateReorder={async (cardId, fromIndex, toIndex) => {
// Validate if reorder is allowed
return { valid: true };
}}
onValidationError={(error) => {
// Handle validation errors
console.error(error);
}}
>
{handCards.map(card => <Card key={card.id} {...card} />)}
</Hand>Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| spacing | number | -50 | Card spacing (negative for overlap) |
| arcHeight | number | 30 | Height of the arc |
| maxFanAngle | number | 15 | Maximum rotation angle |
| hoverScale | number | 1.2 | Scale on hover |
| hoverYOffset | number | -100 | Vertical offset on hover |
| straightenOnHover | boolean | true | Straighten card on hover |
| sortable | boolean | false | Enable card reordering |
| onReorder | (fromIndex: number, toIndex: number) => void | - | Callback when cards are reordered |
| onValidateReorder | (cardId: string, fromIndex: number, toIndex: number) => Promise<ValidationResult> | - | Async validator for reorder operation |
| onValidationError | (error: string) => void | - | Callback for validation errors |
TargetingArrow
An SVG arrow for targeting interactions (like Hearthstone).
<TargetingArrow
start={{ x: 100, y: 100 }}
end={{ x: 300, y: 200 }}
color="#ff0000"
/>Card Flip API
Programmatic control over card flip animations.
import { Card, CardRef } from 'deckfx';
const cardRef = useRef<CardRef>(null);
<Card
ref={cardRef}
faceUp={isFaceUp}
onFlip={(newFaceUp) => console.log('Flipping to', newFaceUp)}
onFlipComplete={(faceUp) => console.log('Flip complete')}
flipDuration={500}
flipDirection="horizontal"
/>
// Programmatic flip
cardRef.current?.flipTo(true);Card Selection
Context-based selection management with multi-select support.
import { SelectionProvider, useCardSelection } from 'deckfx';
<SelectionProvider
selectionMode="multi"
onSelectionChange={(selectedIds) => console.log(selectedIds)}
>
<Card cardId="card-1" selectable>Card 1</Card>
<Card cardId="card-2" selectable>Card 2</Card>
</SelectionProvider>
// Hook usage
const { selectedIds, selectCard, clearSelection } = useCardSelection();Card Counters
Display counters and tokens on cards.
<Card
counters={[
{ id: 'damage', type: 'damage', value: 3, color: 'red', position: 'top-right' },
{ id: 'mana', type: 'mana', value: 2, color: 'blue', position: 'top-left' }
]}
/>Zone Capacity
Add capacity limits and visual feedback to zones.
<CardZone
zoneId="hand"
maxCards={7}
showCapacity={true}
capacityWarningThreshold={0.8}
onCapacityReached={() => console.log('Zone full!')}
>
{cards.map(card => <Card key={card.id} {...card} />)}
</CardZone>Card Stacking
Group cards into stacks with drag and peek functionality.
import { CardStack } from 'deckfx';
<CardStack stackId="stack-1" peekOffset={20}>
<Card cardId="card-1" stackId="stack-1" stackOrder={0} isStackTop />
<Card cardId="card-2" stackId="stack-1" stackOrder={1} />
<Card cardId="card-3" stackId="stack-1" stackOrder={2} />
</CardStack>Validation
DeckFX provides async validation for all card interactions, allowing you to validate actions against server-side rules, game state, or API endpoints.
Validation Context (Recommended)
For applications with multiple components, use the ValidationProvider to centralize validation configuration:
import { ValidationProvider, Card, CardZone } from 'deckfx';
function GameBoard() {
return (
<ValidationProvider
onValidateDragEnd={async (cardId, fromZone, toZone) => {
// Centralized validation logic
const response = await fetch('/api/validate-move', {
method: 'POST',
body: JSON.stringify({ cardId, fromZone, toZone })
});
const data = await response.json();
return { valid: data.allowed, error: data.error };
}}
onValidationError={(error) => {
showToast(error);
}}
>
<CardZone zoneId="hand" layout="fan">
<Card cardId="card-1" drag>Card 1</Card>
</CardZone>
<CardZone zoneId="board" layout="grid">
{/* Drop zone */}
</CardZone>
</ValidationProvider>
);
}Prop-Based Validation (Legacy)
You can also pass validators directly to components:
import { Card, ValidationError, useValidationError } from 'deckfx';
function GameBoard() {
const { error, showError, ErrorComponent } = useValidationError();
return (
<>
<Card
drag
cardId={card.id}
onValidateDragEnd={async (cardId, fromZone, toZone) => {
// Server-side validation
const response = await fetch('/api/validate-move', {
method: 'POST',
body: JSON.stringify({ cardId, fromZone, toZone })
});
const data = await response.json();
return { valid: data.allowed, error: data.error };
}}
onValidationError={showError}
/>
{ErrorComponent}
</>
);
}Zone Identification
Use the zoneId prop on CardZone to identify zones for drag and drop validation:
<CardZone zoneId="hand" layout="fan">
<Card drag cardId="card-1">Card 1</Card>
</CardZone>
<CardZone zoneId="board" layout="grid">
{/* Drop target */}
</CardZone>The zoneId is automatically set as a data-zone-id attribute, which is used by the drag validation system to identify source and target zones.
Validation Types
- Drag Start - Validate before a card can be picked up
- Drag End - Validate before a card can be dropped (snaps back on failure)
- Reorder - Validate before cards can be reordered in a hand
- Draw - Validate before a card can be drawn from a deck
Validation Result
All validators return a Promise<ValidationResult>:
type ValidationResult = {
valid: boolean;
error?: string;
};For detailed validation documentation, see the full documentation.
Peer Dependencies
DeckFX requires the following peer dependencies:
{
"react": "^18.0.0",
"react-dom": "^18.0.0",
"framer-motion": "^11.0.0",
"three": "^0.160.0",
"@react-three/fiber": "^8.0.0",
"@react-three/drei": "^9.0.0"
}TypeScript
DeckFX is written in TypeScript and exports all types:
import type {
DieType, // 'd4' | 'd6' | 'd8' | 'd10' | 'd12' | 'd20'
DieState, // { id, type, value, target, isRolling }
DieConfig, // { color, duration, size, spinForce }
ValidationResult, // { valid: boolean; error?: string }
DragStartValidator, // (cardId: string, cardData?: any) => Promise<ValidationResult>
DragEndValidator, // (cardId: string, fromZone: string, toZone: string, cardData?: any) => Promise<ValidationResult>
ReorderValidator, // (cardId: string, fromIndex: number, toIndex: number) => Promise<ValidationResult>
DrawValidator, // () => Promise<ValidationResult>
ValidationConfig, // Configuration for ValidationProvider
ZoneLayout, // Layout modes for CardZone
CardRef, // Ref type for Card component
FlipDirection, // 'horizontal' | 'vertical'
Counter, // Counter type for card counters
CounterPosition, // Counter position type
SelectionMode, // 'single' | 'multi' | 'none'
SelectionContextValue // Selection context value type
} from 'deckfx';Running the Demos Locally
# Install dependencies
npm install
# Run development server
npm run dev
# Build for production (GitHub Pages)
npm run build:demo
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Run linting
npm run lintContributing
Contributions to the demos are welcome! If you'd like to add a new demo or improve existing ones:
- Fork the repository
- Create your feature branch (
git checkout -b feature/new-demo) - Commit your changes (
git commit -m 'Add new poker demo') - Push to the branch (
git push origin feature/new-demo) - Open a Pull Request
For contributions to the DeckFX library itself, please visit the npm package repository.
License
MIT License - see LICENSE file for details.
