@react-chess-tools/react-chess-game 0.5.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/index.cjs +775 -0
- package/dist/index.cjs.map +1 -0
- package/dist/{index.d.mts → index.d.cts} +118 -12
- package/dist/index.d.ts +264 -0
- package/dist/{index.mjs → index.js} +171 -38
- package/dist/index.js.map +1 -0
- package/package.json +18 -9
- package/src/components/ChessGame/Theme.stories.tsx +242 -0
- package/src/components/ChessGame/ThemePresets.stories.tsx +144 -0
- package/src/components/ChessGame/parts/Board.tsx +6 -4
- package/src/components/ChessGame/parts/Root.tsx +11 -1
- package/src/docs/Theming.mdx +281 -0
- package/src/hooks/useChessGame.ts +23 -7
- package/src/index.ts +19 -0
- package/src/theme/__tests__/context.test.tsx +75 -0
- package/src/theme/__tests__/defaults.test.ts +61 -0
- package/src/theme/__tests__/utils.test.ts +106 -0
- package/src/theme/context.tsx +37 -0
- package/src/theme/defaults.ts +22 -0
- package/src/theme/index.ts +36 -0
- package/src/theme/presets.ts +41 -0
- package/src/theme/types.ts +56 -0
- package/src/theme/utils.ts +47 -0
- package/src/utils/__tests__/board.test.ts +118 -0
- package/src/utils/board.ts +18 -9
- package/src/utils/chess.ts +25 -5
- package/coverage/clover.xml +0 -6
- package/coverage/coverage-final.json +0 -1
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -101
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -196
- package/coverage/lcov.info +0 -0
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import React, { useState } from "react";
|
|
3
|
+
import { ChessGame } from "./index";
|
|
4
|
+
import { defaultGameTheme, themes } from "../../theme";
|
|
5
|
+
import type { ChessGameTheme, PartialChessGameTheme } from "../../theme/types";
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "react-chess-game/Theme/Playground",
|
|
9
|
+
component: ChessGame.Root,
|
|
10
|
+
tags: ["theme"],
|
|
11
|
+
decorators: [
|
|
12
|
+
(Story) => (
|
|
13
|
+
<div style={{ maxWidth: "900px" }}>
|
|
14
|
+
<Story />
|
|
15
|
+
</div>
|
|
16
|
+
),
|
|
17
|
+
],
|
|
18
|
+
} satisfies Meta<typeof ChessGame.Root>;
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
|
|
22
|
+
// Color picker component
|
|
23
|
+
const ColorInput: React.FC<{
|
|
24
|
+
label: string;
|
|
25
|
+
value: string;
|
|
26
|
+
onChange: (value: string) => void;
|
|
27
|
+
}> = ({ label, value, onChange }) => {
|
|
28
|
+
// Extract hex from rgba for color picker
|
|
29
|
+
const rgbaToHex = (rgba: string): string => {
|
|
30
|
+
const match = rgba.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
31
|
+
if (match) {
|
|
32
|
+
const r = parseInt(match[1]).toString(16).padStart(2, "0");
|
|
33
|
+
const g = parseInt(match[2]).toString(16).padStart(2, "0");
|
|
34
|
+
const b = parseInt(match[3]).toString(16).padStart(2, "0");
|
|
35
|
+
return `#${r}${g}${b}`;
|
|
36
|
+
}
|
|
37
|
+
return value.startsWith("#") ? value : "#000000";
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const hexToRgba = (hex: string, alpha: number = 0.5): string => {
|
|
41
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
42
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
43
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
44
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div
|
|
49
|
+
style={{
|
|
50
|
+
display: "flex",
|
|
51
|
+
alignItems: "center",
|
|
52
|
+
gap: "8px",
|
|
53
|
+
marginBottom: "8px",
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
<input
|
|
57
|
+
type="color"
|
|
58
|
+
value={rgbaToHex(value)}
|
|
59
|
+
onChange={(e) => onChange(hexToRgba(e.target.value))}
|
|
60
|
+
style={{ width: "40px", height: "30px", cursor: "pointer" }}
|
|
61
|
+
/>
|
|
62
|
+
<span style={{ fontSize: "12px", minWidth: "100px" }}>{label}</span>
|
|
63
|
+
<input
|
|
64
|
+
type="text"
|
|
65
|
+
value={value}
|
|
66
|
+
onChange={(e) => onChange(e.target.value)}
|
|
67
|
+
style={{ fontSize: "11px", width: "180px", padding: "4px" }}
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Background color picker (for board squares)
|
|
74
|
+
const BgColorInput: React.FC<{
|
|
75
|
+
label: string;
|
|
76
|
+
value: string;
|
|
77
|
+
onChange: (value: string) => void;
|
|
78
|
+
}> = ({ label, value, onChange }) => {
|
|
79
|
+
return (
|
|
80
|
+
<div
|
|
81
|
+
style={{
|
|
82
|
+
display: "flex",
|
|
83
|
+
alignItems: "center",
|
|
84
|
+
gap: "8px",
|
|
85
|
+
marginBottom: "8px",
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
<input
|
|
89
|
+
type="color"
|
|
90
|
+
value={value}
|
|
91
|
+
onChange={(e) => onChange(e.target.value)}
|
|
92
|
+
style={{ width: "40px", height: "30px", cursor: "pointer" }}
|
|
93
|
+
/>
|
|
94
|
+
<span style={{ fontSize: "12px", minWidth: "100px" }}>{label}</span>
|
|
95
|
+
<input
|
|
96
|
+
type="text"
|
|
97
|
+
value={value}
|
|
98
|
+
onChange={(e) => onChange(e.target.value)}
|
|
99
|
+
style={{ fontSize: "11px", width: "180px", padding: "4px" }}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const Playground = () => {
|
|
106
|
+
const [theme, setTheme] = useState<ChessGameTheme>(defaultGameTheme);
|
|
107
|
+
const [copied, setCopied] = useState(false);
|
|
108
|
+
|
|
109
|
+
const updateTheme = (path: string[], value: string) => {
|
|
110
|
+
setTheme((prev) => {
|
|
111
|
+
const newTheme = JSON.parse(JSON.stringify(prev));
|
|
112
|
+
let current: Record<string, unknown> = newTheme;
|
|
113
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
114
|
+
current = current[path[i]] as Record<string, unknown>;
|
|
115
|
+
}
|
|
116
|
+
if (path[path.length - 2] === "board") {
|
|
117
|
+
current[path[path.length - 1]] = { backgroundColor: value };
|
|
118
|
+
} else {
|
|
119
|
+
current[path[path.length - 1]] = value;
|
|
120
|
+
}
|
|
121
|
+
return newTheme;
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const copyTheme = () => {
|
|
126
|
+
const themeCode = `const myTheme: PartialChessGameTheme = ${JSON.stringify(theme, null, 2)};`;
|
|
127
|
+
navigator.clipboard.writeText(themeCode);
|
|
128
|
+
setCopied(true);
|
|
129
|
+
setTimeout(() => setCopied(false), 2000);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const loadPreset = (preset: ChessGameTheme) => {
|
|
133
|
+
setTheme(preset);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<div style={{ display: "flex", gap: "24px", flexWrap: "wrap" }}>
|
|
138
|
+
<div style={{ flex: "1", minWidth: "300px" }}>
|
|
139
|
+
<h3 style={{ marginBottom: "16px" }}>Theme Editor</h3>
|
|
140
|
+
|
|
141
|
+
<div style={{ marginBottom: "16px" }}>
|
|
142
|
+
<strong>Load Preset:</strong>
|
|
143
|
+
<div style={{ display: "flex", gap: "8px", marginTop: "8px" }}>
|
|
144
|
+
<button onClick={() => loadPreset(themes.default)}>Default</button>
|
|
145
|
+
<button onClick={() => loadPreset(themes.lichess)}>Lichess</button>
|
|
146
|
+
<button onClick={() => loadPreset(themes.chessCom)}>
|
|
147
|
+
Chess.com
|
|
148
|
+
</button>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<div style={{ marginBottom: "16px" }}>
|
|
153
|
+
<strong>Board Colors</strong>
|
|
154
|
+
<div style={{ marginTop: "8px" }}>
|
|
155
|
+
<BgColorInput
|
|
156
|
+
label="Light Square"
|
|
157
|
+
value={
|
|
158
|
+
(theme.board.lightSquare as { backgroundColor: string })
|
|
159
|
+
.backgroundColor
|
|
160
|
+
}
|
|
161
|
+
onChange={(v) => updateTheme(["board", "lightSquare"], v)}
|
|
162
|
+
/>
|
|
163
|
+
<BgColorInput
|
|
164
|
+
label="Dark Square"
|
|
165
|
+
value={
|
|
166
|
+
(theme.board.darkSquare as { backgroundColor: string })
|
|
167
|
+
.backgroundColor
|
|
168
|
+
}
|
|
169
|
+
onChange={(v) => updateTheme(["board", "darkSquare"], v)}
|
|
170
|
+
/>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<div style={{ marginBottom: "16px" }}>
|
|
175
|
+
<strong>State Colors</strong>
|
|
176
|
+
<div style={{ marginTop: "8px" }}>
|
|
177
|
+
<ColorInput
|
|
178
|
+
label="Last Move"
|
|
179
|
+
value={theme.state.lastMove}
|
|
180
|
+
onChange={(v) => updateTheme(["state", "lastMove"], v)}
|
|
181
|
+
/>
|
|
182
|
+
<ColorInput
|
|
183
|
+
label="Active Square"
|
|
184
|
+
value={theme.state.activeSquare}
|
|
185
|
+
onChange={(v) => updateTheme(["state", "activeSquare"], v)}
|
|
186
|
+
/>
|
|
187
|
+
<ColorInput
|
|
188
|
+
label="Check"
|
|
189
|
+
value={theme.state.check}
|
|
190
|
+
onChange={(v) => updateTheme(["state", "check"], v)}
|
|
191
|
+
/>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<div style={{ marginBottom: "16px" }}>
|
|
196
|
+
<strong>Indicator Colors</strong>
|
|
197
|
+
<div style={{ marginTop: "8px" }}>
|
|
198
|
+
<ColorInput
|
|
199
|
+
label="Move Dot"
|
|
200
|
+
value={theme.indicators.move}
|
|
201
|
+
onChange={(v) => updateTheme(["indicators", "move"], v)}
|
|
202
|
+
/>
|
|
203
|
+
<ColorInput
|
|
204
|
+
label="Capture Ring"
|
|
205
|
+
value={theme.indicators.capture}
|
|
206
|
+
onChange={(v) => updateTheme(["indicators", "capture"], v)}
|
|
207
|
+
/>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<button
|
|
212
|
+
onClick={copyTheme}
|
|
213
|
+
style={{
|
|
214
|
+
padding: "8px 16px",
|
|
215
|
+
backgroundColor: copied ? "#4caf50" : "#2196f3",
|
|
216
|
+
color: "white",
|
|
217
|
+
border: "none",
|
|
218
|
+
borderRadius: "4px",
|
|
219
|
+
cursor: "pointer",
|
|
220
|
+
}}
|
|
221
|
+
>
|
|
222
|
+
{copied ? "Copied!" : "Copy Theme Code"}
|
|
223
|
+
</button>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<div style={{ flex: "1", minWidth: "350px" }}>
|
|
227
|
+
<h3 style={{ marginBottom: "16px" }}>Preview</h3>
|
|
228
|
+
<div style={{ maxWidth: "400px" }}>
|
|
229
|
+
<ChessGame.Root
|
|
230
|
+
fen="r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR w KQkq - 4 4"
|
|
231
|
+
theme={theme}
|
|
232
|
+
>
|
|
233
|
+
<ChessGame.Board />
|
|
234
|
+
</ChessGame.Root>
|
|
235
|
+
</div>
|
|
236
|
+
<p style={{ fontSize: "12px", color: "#666", marginTop: "8px" }}>
|
|
237
|
+
Click on a piece to see move indicators. The position shows a check.
|
|
238
|
+
</p>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { Meta } from "@storybook/react";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { ChessGame } from "./index";
|
|
4
|
+
import { themes } from "../../theme";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "react-chess-game/Theme/Presets",
|
|
8
|
+
component: ChessGame.Root,
|
|
9
|
+
tags: ["theme", "presets"],
|
|
10
|
+
decorators: [
|
|
11
|
+
(Story) => (
|
|
12
|
+
<div style={{ maxWidth: "1200px" }}>
|
|
13
|
+
<Story />
|
|
14
|
+
</div>
|
|
15
|
+
),
|
|
16
|
+
],
|
|
17
|
+
} satisfies Meta<typeof ChessGame.Root>;
|
|
18
|
+
|
|
19
|
+
export default meta;
|
|
20
|
+
|
|
21
|
+
// Position with a move played (to show lastMove highlight)
|
|
22
|
+
const POSITION_WITH_MOVE =
|
|
23
|
+
"rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2";
|
|
24
|
+
|
|
25
|
+
export const DefaultTheme = () => (
|
|
26
|
+
<div style={{ maxWidth: "500px" }}>
|
|
27
|
+
<h3>Default Theme</h3>
|
|
28
|
+
<p style={{ fontSize: "14px", color: "#666", marginBottom: "16px" }}>
|
|
29
|
+
The original colors matching the classic chessboard look.
|
|
30
|
+
</p>
|
|
31
|
+
<ChessGame.Root theme={themes.default}>
|
|
32
|
+
<ChessGame.Board />
|
|
33
|
+
</ChessGame.Root>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export const LichessTheme = () => (
|
|
38
|
+
<div style={{ maxWidth: "500px" }}>
|
|
39
|
+
<h3>Lichess Theme</h3>
|
|
40
|
+
<p style={{ fontSize: "14px", color: "#666", marginBottom: "16px" }}>
|
|
41
|
+
Green highlights inspired by Lichess.org style.
|
|
42
|
+
</p>
|
|
43
|
+
<ChessGame.Root theme={themes.lichess}>
|
|
44
|
+
<ChessGame.Board />
|
|
45
|
+
</ChessGame.Root>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
export const ChessComTheme = () => (
|
|
50
|
+
<div style={{ maxWidth: "500px" }}>
|
|
51
|
+
<h3>Chess.com Theme</h3>
|
|
52
|
+
<p style={{ fontSize: "14px", color: "#666", marginBottom: "16px" }}>
|
|
53
|
+
Green board with yellow highlights inspired by Chess.com.
|
|
54
|
+
</p>
|
|
55
|
+
<ChessGame.Root theme={themes.chessCom}>
|
|
56
|
+
<ChessGame.Board />
|
|
57
|
+
</ChessGame.Root>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
export const CustomThemeExample = () => {
|
|
62
|
+
// Example of a custom dark theme
|
|
63
|
+
const darkTheme = {
|
|
64
|
+
board: {
|
|
65
|
+
lightSquare: { backgroundColor: "#4a4a4a" },
|
|
66
|
+
darkSquare: { backgroundColor: "#2d2d2d" },
|
|
67
|
+
},
|
|
68
|
+
state: {
|
|
69
|
+
lastMove: "rgba(100, 150, 255, 0.5)",
|
|
70
|
+
check: "rgba(255, 50, 50, 0.6)",
|
|
71
|
+
activeSquare: "rgba(100, 150, 255, 0.5)",
|
|
72
|
+
dropSquare: { backgroundColor: "rgba(100, 150, 255, 0.3)" },
|
|
73
|
+
},
|
|
74
|
+
indicators: {
|
|
75
|
+
move: "rgba(200, 200, 200, 0.2)",
|
|
76
|
+
capture: "rgba(255, 100, 100, 0.3)",
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div style={{ maxWidth: "500px" }}>
|
|
82
|
+
<h3>Custom Dark Theme Example</h3>
|
|
83
|
+
<p style={{ fontSize: "14px", color: "#666", marginBottom: "16px" }}>
|
|
84
|
+
Example of a fully custom theme with dark colors and blue highlights.
|
|
85
|
+
</p>
|
|
86
|
+
<ChessGame.Root theme={darkTheme}>
|
|
87
|
+
<ChessGame.Board />
|
|
88
|
+
</ChessGame.Root>
|
|
89
|
+
<details style={{ marginTop: "16px" }}>
|
|
90
|
+
<summary style={{ cursor: "pointer", fontSize: "14px" }}>
|
|
91
|
+
View theme code
|
|
92
|
+
</summary>
|
|
93
|
+
<pre
|
|
94
|
+
style={{
|
|
95
|
+
fontSize: "11px",
|
|
96
|
+
background: "#f5f5f5",
|
|
97
|
+
padding: "12px",
|
|
98
|
+
overflow: "auto",
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
{JSON.stringify(darkTheme, null, 2)}
|
|
102
|
+
</pre>
|
|
103
|
+
</details>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const PartialThemeOverride = () => {
|
|
109
|
+
// Only override specific colors
|
|
110
|
+
const partialTheme = {
|
|
111
|
+
state: {
|
|
112
|
+
lastMove: "rgba(147, 112, 219, 0.5)", // Purple
|
|
113
|
+
check: "rgba(255, 165, 0, 0.6)", // Orange
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div style={{ maxWidth: "500px" }}>
|
|
119
|
+
<h3>Partial Theme Override</h3>
|
|
120
|
+
<p style={{ fontSize: "14px", color: "#666", marginBottom: "16px" }}>
|
|
121
|
+
Only override specific colors (purple last move, orange check). Other
|
|
122
|
+
colors use defaults.
|
|
123
|
+
</p>
|
|
124
|
+
<ChessGame.Root fen={POSITION_WITH_MOVE} theme={partialTheme}>
|
|
125
|
+
<ChessGame.Board />
|
|
126
|
+
</ChessGame.Root>
|
|
127
|
+
<details style={{ marginTop: "16px" }}>
|
|
128
|
+
<summary style={{ cursor: "pointer", fontSize: "14px" }}>
|
|
129
|
+
View theme code
|
|
130
|
+
</summary>
|
|
131
|
+
<pre
|
|
132
|
+
style={{
|
|
133
|
+
fontSize: "11px",
|
|
134
|
+
background: "#f5f5f5",
|
|
135
|
+
padding: "12px",
|
|
136
|
+
overflow: "auto",
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
{JSON.stringify(partialTheme, null, 2)}
|
|
140
|
+
</pre>
|
|
141
|
+
</details>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "../../../utils/board";
|
|
13
13
|
import { isLegalMove, requiresPromotion } from "../../../utils/chess";
|
|
14
14
|
import { useChessGameContext } from "../../../hooks/useChessGameContext";
|
|
15
|
+
import { useChessGameTheme } from "../../../theme/context";
|
|
15
16
|
|
|
16
17
|
export interface ChessGameProps {
|
|
17
18
|
options?: ChessboardOptions;
|
|
@@ -19,6 +20,7 @@ export interface ChessGameProps {
|
|
|
19
20
|
|
|
20
21
|
export const Board: React.FC<ChessGameProps> = ({ options = {} }) => {
|
|
21
22
|
const gameContext = useChessGameContext();
|
|
23
|
+
const theme = useChessGameTheme();
|
|
22
24
|
|
|
23
25
|
if (!gameContext) {
|
|
24
26
|
throw new Error("ChessGameContext not found");
|
|
@@ -121,18 +123,18 @@ export const Board: React.FC<ChessGameProps> = ({ options = {} }) => {
|
|
|
121
123
|
}, [promotionMove, squareWidth, orientation]);
|
|
122
124
|
|
|
123
125
|
const baseOptions: ChessboardOptions = {
|
|
124
|
-
squareStyles: getCustomSquareStyles(game, info, activeSquare),
|
|
126
|
+
squareStyles: getCustomSquareStyles(game, info, activeSquare, theme),
|
|
125
127
|
boardOrientation: orientation === "b" ? "black" : "white",
|
|
126
128
|
position: currentFen,
|
|
127
129
|
showNotation: true,
|
|
128
130
|
showAnimations: isLatestMove,
|
|
131
|
+
lightSquareStyle: theme.board.lightSquare,
|
|
132
|
+
darkSquareStyle: theme.board.darkSquare,
|
|
129
133
|
canDragPiece: ({ piece }) => {
|
|
130
134
|
if (isGameOver) return false;
|
|
131
135
|
return piece.pieceType[0] === turn;
|
|
132
136
|
},
|
|
133
|
-
dropSquareStyle:
|
|
134
|
-
backgroundColor: "rgba(255, 255, 0, 0.4)",
|
|
135
|
-
},
|
|
137
|
+
dropSquareStyle: theme.state.dropSquare,
|
|
136
138
|
onPieceDrag: ({ piece, square }) => {
|
|
137
139
|
if (piece.pieceType[0] === turn) {
|
|
138
140
|
setActiveSquare(square as Square);
|
|
@@ -2,21 +2,31 @@ import React from "react";
|
|
|
2
2
|
import { Color } from "chess.js";
|
|
3
3
|
import { useChessGame } from "../../../hooks/useChessGame";
|
|
4
4
|
import { ChessGameContext } from "../../../hooks/useChessGameContext";
|
|
5
|
+
import { ThemeProvider } from "../../../theme/context";
|
|
6
|
+
import { mergeTheme } from "../../../theme/utils";
|
|
7
|
+
import type { PartialChessGameTheme } from "../../../theme/types";
|
|
5
8
|
|
|
6
9
|
export interface RootProps {
|
|
7
10
|
fen?: string;
|
|
8
11
|
orientation?: Color;
|
|
12
|
+
/** Optional theme configuration. Supports partial themes - only override the colors you need. */
|
|
13
|
+
theme?: PartialChessGameTheme;
|
|
9
14
|
}
|
|
10
15
|
|
|
11
16
|
export const Root: React.FC<React.PropsWithChildren<RootProps>> = ({
|
|
12
17
|
fen,
|
|
13
18
|
orientation,
|
|
19
|
+
theme,
|
|
14
20
|
children,
|
|
15
21
|
}) => {
|
|
16
22
|
const context = useChessGame({ fen, orientation });
|
|
23
|
+
|
|
24
|
+
// Merge partial theme with defaults
|
|
25
|
+
const mergedTheme = React.useMemo(() => mergeTheme(theme), [theme]);
|
|
26
|
+
|
|
17
27
|
return (
|
|
18
28
|
<ChessGameContext.Provider value={context}>
|
|
19
|
-
{children}
|
|
29
|
+
<ThemeProvider theme={mergedTheme}>{children}</ThemeProvider>
|
|
20
30
|
</ChessGameContext.Provider>
|
|
21
31
|
);
|
|
22
32
|
};
|