Skip to content

Commit b779051

Browse files
committed
feat: more features and docs
- docs on premoves - add canDragPiece and squareStyles props - prevent text highlighting on notation by default - export all utils for consumers to use (docs coming later)
1 parent cb58b2a commit b779051

9 files changed

Lines changed: 361 additions & 9 deletions

File tree

docs/B_HowToUse.mdx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import { Canvas, Meta } from '@storybook/blocks';
33
import * as DefaultStories from './stories/Default.stories';
44
import * as PlayVsRandomStories from './stories/PlayVsRandom.stories';
55
import * as SparePiecesStories from './stories/SparePieces.stories';
6+
import * as PremovesStories from './stories/Premoves.stories';
67

78
import * as AllowDraggingStories from './stories/options/AllowDragging.stories';
89
import * as AllowDragOffBoardStories from './stories/options/AllowDragOffBoard.stories';
910
import * as AlphaNotationStyleStories from './stories/options/AlphaNotationStyle.stories';
1011
import * as AnimationDurationInMsStories from './stories/options/AnimationDurationInMs.stories';
1112
import * as BoardOrientationStories from './stories/options/BoardOrientation.stories';
1213
import * as BoardStyleStories from './stories/options/BoardStyle.stories';
14+
import * as CanDragPieceStories from './stories/options/CanDragPiece.stories';
1315
import * as ChessboardColumnsStories from './stories/options/ChessboardColumns.stories';
1416
import * as ChessboardRowsStories from './stories/options/ChessboardRows.stories';
1517
import * as DarkSquareNotationStyleStories from './stories/options/DarkSquareNotationStyle.stories';
@@ -30,6 +32,7 @@ import * as PositionStories from './stories/options/Position.stories';
3032
import * as ShowAnimationsStories from './stories/options/ShowAnimations.stories';
3133
import * as ShowNotationStories from './stories/options/ShowNotation.stories';
3234
import * as SquareStyleStories from './stories/options/SquareStyle.stories';
35+
import * as SquareStylesStories from './stories/options/SquareStyles.stories';
3336

3437
<Meta title="How to use" />
3538

@@ -55,6 +58,7 @@ This page will show you how to use React Chessboard. It includes some basic and
5558
- [`options.animationDurationInMs`](#optionsanimationdurationinms)
5659
- [`options.boardOrientation`](#optionsboardorientation)
5760
- [`options.boardStyle`](#optionsboardstyle)
61+
- [`options.canDragPiece`](#optionscandragpiece)
5862
- [`options.chessboardColumns`](#optionschessboardcolumns)
5963
- [`options.chessboardRows`](#optionschessboardrows)
6064
- [`options.darkSquareNotationStyle`](#optionsdarksquarenotationstyle)
@@ -75,6 +79,7 @@ This page will show you how to use React Chessboard. It includes some basic and
7579
- [`options.showAnimations`](#optionsshowanimations)
7680
- [`options.showNotation`](#optionsshownotation)
7781
- [`options.squareStyle`](#optionssquarestyle)
82+
- [`options.squareStyles`](#optionssquarestyles)
7883

7984
## Basic and common examples
8085

@@ -122,7 +127,9 @@ TODO
122127

123128
### Premoves
124129

125-
TODO
130+
This example shows you how can you implement premoves with the component. Premoves are when you make a move and then before your opponent makes their move, you make a move to be played automatically after your opponent's move.
131+
132+
<Canvas of={PremovesStories.Premoves} />
126133

127134
### Promotion piece selection
128135

@@ -174,6 +181,7 @@ If you wish to display different styles of notation on different coloured square
174181
position: "absolute",
175182
bottom: 1,
176183
right: 4,
184+
userSelect: 'none',
177185
}
178186
```
179187

@@ -227,6 +235,18 @@ Controls the styling of the entire chessboard container.
227235

228236
<Canvas of={BoardStyleStories.BoardStyle} />
229237

238+
### `options.canDragPiece`
239+
240+
Controls whether a piece can be dragged.
241+
242+
**Default value:** `undefined`
243+
244+
**TypeScript type:** `({ isSparePiece: boolean, piece: { pieceType: string }, square: string | null }) => boolean`
245+
246+
**Standard use case:** Restricting piece dragging to certain pieces or squares.
247+
248+
<Canvas of={CanDragPieceStories.CanDragPiece} />
249+
230250
### `options.chessboardColumns`
231251

232252
Controls the number of columns on the chessboard. If you set either of the row or column options above `9`, you will need to use the `positionObject` notation for the `position` prop, as `fen` notation only supports single digit columns.
@@ -363,6 +383,7 @@ If you wish to display different styles of notation on different coloured square
363383
position: "absolute",
364384
top: 2,
365385
left: 2,
386+
userSelect: 'none',
366387
}
367388
```
368389

@@ -526,6 +547,18 @@ Controls the styling of all squares on the board, regardless of their colour.
526547

527548
<Canvas of={SquareStyleStories.SquareStyle} />
528549

550+
### `options.squareStyles`
551+
552+
Controls the styling of individual squares on the board. This style will go over the top of any existing styles set with the [`squareStyle`](#optionssquarestyle) prop. This allows you to achieve effects like a background color over light and dark squares without needing to know whether the square is light or dark.
553+
554+
**Default value:** `{}`
555+
556+
**TypeScript type:** `Record<string, React.CSSProperties>`
557+
558+
**Standard use case:** Customizing the appearance of specific squares such as for right clicks on squares or premoves.
559+
560+
<Canvas of={SquareStylesStories.SquareStyles} />
561+
529562
<div
530563
style={{
531564
textAlign: 'center',

docs/stories/Premoves.stories.tsx

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
import { Chess } from 'chess.js';
3+
import { useState, useRef, useEffect } from 'react';
4+
5+
import defaultMeta from './Default.stories';
6+
import { Chessboard, fenStringToPositionObject } from '../../src';
7+
import { PieceDropHandlerArgs, PieceHandlerArgs } from '../../src/types';
8+
9+
const meta: Meta<typeof Chessboard> = {
10+
...defaultMeta,
11+
title: 'stories/Premoves',
12+
} satisfies Meta<typeof Chessboard>;
13+
14+
export default meta;
15+
16+
type Story = StoryObj<typeof meta>;
17+
18+
export const Premoves: Story = {
19+
render: () => {
20+
const [chessGame, setChessGame] = useState(new Chess());
21+
const [premoves, setPremoves] = useState<PieceDropHandlerArgs[]>([]);
22+
const [showAnimations, setShowAnimations] = useState(true);
23+
const chessGameRef = useRef(chessGame);
24+
const premovesRef = useRef<PieceDropHandlerArgs[]>([]);
25+
26+
// update the ref when the game state changes
27+
useEffect(() => {
28+
chessGameRef.current = chessGame;
29+
}, [chessGame]);
30+
31+
// make a random "CPU" move
32+
function makeRandomMove() {
33+
// get all possible moves
34+
const possibleMoves = chessGameRef.current.moves();
35+
36+
// exit if the game is over
37+
if (chessGameRef.current.isGameOver()) {
38+
return;
39+
}
40+
41+
// make a random move
42+
const randomMove =
43+
possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
44+
chessGameRef.current.move(randomMove);
45+
setChessGame(new Chess(chessGameRef.current.fen()));
46+
47+
// if there is a premove, remove it from the list and make it once animation is complete
48+
if (premovesRef.current.length > 0) {
49+
const nextPlayerPremove = premovesRef.current[0];
50+
premovesRef.current.splice(0, 1);
51+
52+
// wait for CPU move animation to complete
53+
setTimeout(() => {
54+
// execute the premove
55+
const premoveSuccessful = onPieceDrop(nextPlayerPremove);
56+
57+
// if the premove was not successful, clear all premoves
58+
if (!premoveSuccessful) {
59+
premovesRef.current = [];
60+
}
61+
62+
// update the premoves state
63+
setPremoves([...premovesRef.current]);
64+
65+
// disable animations while clearing premoves
66+
setShowAnimations(false);
67+
68+
// re-enable animations after a short delay
69+
setTimeout(() => {
70+
setShowAnimations(true);
71+
}, 50);
72+
}, 300);
73+
}
74+
}
75+
76+
// handle piece drop
77+
function onPieceDrop({
78+
sourceSquare,
79+
targetSquare,
80+
piece,
81+
}: PieceDropHandlerArgs) {
82+
// type narrow targetSquare potentially being null (e.g. if dropped off board) or user dropping piece onto same square
83+
if (!targetSquare || sourceSquare === targetSquare) {
84+
return false;
85+
}
86+
87+
// check if a premove (piece isn't the color of the current player's turn)
88+
const pieceColor = piece.pieceType[0]; // 'w' or 'b'
89+
if (chessGameRef.current.turn() !== pieceColor) {
90+
premovesRef.current.push({ sourceSquare, targetSquare, piece });
91+
setPremoves([...premovesRef.current]);
92+
// return early to stop processing the move and return true to not animate the move
93+
return true;
94+
}
95+
96+
// try to make the move
97+
try {
98+
chessGameRef.current.move({
99+
from: sourceSquare,
100+
to: targetSquare,
101+
promotion: 'q', // always promote to a queen for example simplicity
102+
});
103+
104+
// update the game state
105+
setChessGame(new Chess(chessGameRef.current.fen()));
106+
107+
// make random cpu move after a slightly longer delay to allow user to premove
108+
setTimeout(makeRandomMove, 3000);
109+
110+
// return true if the move was successful
111+
return true;
112+
} catch {
113+
// return false if the move was not successful
114+
return false;
115+
}
116+
}
117+
118+
// clear all premoves on right click
119+
function onSquareRightClick() {
120+
premovesRef.current = [];
121+
setPremoves([...premovesRef.current]);
122+
123+
// disable animations while clearing premoves
124+
setShowAnimations(false);
125+
126+
// re-enable animations after a short delay
127+
setTimeout(() => {
128+
setShowAnimations(true);
129+
}, 50);
130+
}
131+
132+
// only allow white pieces to be dragged
133+
function canDragPiece({ piece }: PieceHandlerArgs) {
134+
return piece.pieceType[0] === 'w';
135+
}
136+
137+
// create a position object from the fen string to split the premoves from the game state
138+
const position = fenStringToPositionObject(chessGame.fen(), 8, 8);
139+
const squareStyles: Record<string, React.CSSProperties> = {};
140+
141+
// add premoves to the position object to show them on the board
142+
for (const premove of premoves) {
143+
delete position[premove.sourceSquare];
144+
position[premove.targetSquare!] = {
145+
pieceType: premove.piece.pieceType,
146+
};
147+
squareStyles[premove.sourceSquare] = {
148+
backgroundColor: 'rgba(255,0,0,0.2)',
149+
};
150+
squareStyles[premove.targetSquare!] = {
151+
backgroundColor: 'rgba(255,0,0,0.2)',
152+
};
153+
}
154+
155+
// set the chessboard options
156+
const chessboardOptions = {
157+
canDragPiece,
158+
onPieceDrop,
159+
onSquareRightClick,
160+
position,
161+
showAnimations,
162+
squareStyles,
163+
};
164+
165+
// render the chessboard
166+
return <Chessboard options={chessboardOptions} />;
167+
},
168+
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
3+
import defaultMeta from '../Default.stories';
4+
import { Chessboard } from '../../../src';
5+
import { PieceHandlerArgs } from '../../../src/types';
6+
7+
const meta: Meta<typeof Chessboard> = {
8+
...defaultMeta,
9+
title: 'stories/Options/AllowDragging',
10+
} satisfies Meta<typeof Chessboard>;
11+
12+
export default meta;
13+
type Story = StoryObj<typeof meta>;
14+
15+
export const CanDragPiece: Story = {
16+
render: () => {
17+
function canDragPiece({ piece }: PieceHandlerArgs) {
18+
return piece.pieceType[0] === 'w';
19+
}
20+
21+
// chessboard options
22+
const chessboardOptions = {
23+
canDragPiece,
24+
};
25+
26+
// render
27+
return (
28+
<div
29+
style={{
30+
display: 'flex',
31+
flexDirection: 'column',
32+
gap: '1rem',
33+
alignItems: 'center',
34+
}}
35+
>
36+
<Chessboard options={chessboardOptions} />
37+
38+
<p style={{ fontSize: '0.8rem', color: '#666' }}>
39+
Only white pieces can be dragged
40+
</p>
41+
</div>
42+
);
43+
},
44+
};
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
import { useState } from 'react';
3+
4+
import defaultMeta from '../Default.stories';
5+
import { Chessboard } from '../../../src';
6+
import { SquareHandlerArgs } from '../../../src/types';
7+
8+
const meta: Meta<typeof Chessboard> = {
9+
...defaultMeta,
10+
title: 'stories/Options/SquareStyle',
11+
} satisfies Meta<typeof Chessboard>;
12+
13+
export default meta;
14+
type Story = StoryObj<typeof meta>;
15+
16+
export const SquareStyles: Story = {
17+
render: () => {
18+
const [squareStyles, setSquareStyles] = useState<
19+
Record<string, React.CSSProperties>
20+
>({
21+
e4: {
22+
backgroundColor: 'rgba(255,0,0,0.2)',
23+
},
24+
});
25+
26+
function onSquareClick() {
27+
setSquareStyles({});
28+
}
29+
30+
// add or remove a style when a square is right clicked
31+
function onSquareRightClick(args: SquareHandlerArgs) {
32+
setSquareStyles((prev) => {
33+
const newSquareStyles = { ...prev };
34+
if (newSquareStyles[args.square]) {
35+
delete newSquareStyles[args.square];
36+
} else {
37+
newSquareStyles[args.square] = {
38+
backgroundColor: 'rgba(255,0,0,0.2)',
39+
};
40+
}
41+
return newSquareStyles;
42+
});
43+
}
44+
45+
// chessboard options
46+
const chessboardOptions = {
47+
onSquareClick,
48+
onSquareRightClick,
49+
squareStyles,
50+
};
51+
52+
// render
53+
return (
54+
<div
55+
style={{
56+
display: 'flex',
57+
flexDirection: 'column',
58+
gap: '1rem',
59+
alignItems: 'center',
60+
}}
61+
>
62+
<Chessboard options={chessboardOptions} />
63+
64+
<p style={{ fontSize: '0.8rem', color: '#666' }}>
65+
Right click on a square to add or remove a red background. Left click
66+
to remove all red backgrounds.
67+
</p>
68+
</div>
69+
);
70+
},
71+
};

0 commit comments

Comments
 (0)