@react-chess-tools/react-chess-puzzle 1.0.2 → 1.0.3

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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # @react-chess-tools/react-chess-puzzle
2
2
 
3
+ ## 1.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [93e1029]
8
+ - @react-chess-tools/react-chess-game@1.0.2
9
+
3
10
  ## 1.0.2
4
11
 
5
12
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-chess-tools/react-chess-puzzle",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "A lightweight, customizable React component library for rendering and interacting with chess puzzles.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -42,7 +42,7 @@
42
42
  "license": "MIT",
43
43
  "dependencies": {
44
44
  "@radix-ui/react-slot": "^1.2.4",
45
- "@react-chess-tools/react-chess-game": "1.0.1",
45
+ "@react-chess-tools/react-chess-game": "1.0.2",
46
46
  "chess.js": "^1.4.0",
47
47
  "lodash": "^4.17.21"
48
48
  },
@@ -18,6 +18,105 @@ const puzzles = [
18
18
  },
19
19
  ];
20
20
 
21
+ // ============================================================================
22
+ // Shared Story Styles
23
+ // ============================================================================
24
+ const storyStyles = {
25
+ container: {
26
+ display: "flex",
27
+ flexDirection: "column" as const,
28
+ alignItems: "center",
29
+ gap: "20px",
30
+ padding: "24px",
31
+ backgroundColor: "#f8f9fa",
32
+ borderRadius: "12px",
33
+ maxWidth: "500px",
34
+ margin: "0 auto",
35
+ },
36
+ header: {
37
+ display: "flex",
38
+ flexDirection: "column" as const,
39
+ alignItems: "center",
40
+ gap: "8px",
41
+ },
42
+ title: {
43
+ fontSize: "22px",
44
+ fontWeight: 700,
45
+ color: "#2c3e50",
46
+ margin: 0,
47
+ textAlign: "center" as const,
48
+ },
49
+ subtitle: {
50
+ fontSize: "14px",
51
+ color: "#6c757d",
52
+ margin: 0,
53
+ textAlign: "center" as const,
54
+ },
55
+ boardWrapper: {
56
+ backgroundColor: "#fff",
57
+ padding: "16px",
58
+ borderRadius: "10px",
59
+ boxShadow: "0 2px 12px rgba(0,0,0,0.08)",
60
+ },
61
+ controlsSection: {
62
+ display: "flex",
63
+ gap: "10px",
64
+ justifyContent: "center",
65
+ flexWrap: "wrap" as const,
66
+ },
67
+ button: {
68
+ padding: "10px 20px",
69
+ fontSize: "14px",
70
+ fontWeight: 600,
71
+ cursor: "pointer",
72
+ border: "none",
73
+ borderRadius: "8px",
74
+ backgroundColor: "#fff",
75
+ boxShadow: "0 2px 6px rgba(0,0,0,0.08)",
76
+ color: "#495057",
77
+ transition: "all 0.2s ease",
78
+ } as React.CSSProperties,
79
+ buttonPrimary: {
80
+ backgroundColor: "#4dabf7",
81
+ color: "#fff",
82
+ boxShadow: "0 2px 6px rgba(77, 171, 247, 0.3)",
83
+ } as React.CSSProperties,
84
+ buttonSuccess: {
85
+ backgroundColor: "#51cf66",
86
+ color: "#fff",
87
+ boxShadow: "0 2px 6px rgba(81, 207, 102, 0.3)",
88
+ } as React.CSSProperties,
89
+ hintButton: {
90
+ padding: "8px 16px",
91
+ fontSize: "13px",
92
+ fontWeight: 500,
93
+ cursor: "pointer",
94
+ border: "1px dashed #adb5bd",
95
+ borderRadius: "8px",
96
+ backgroundColor: "transparent",
97
+ color: "#868e96",
98
+ } as React.CSSProperties,
99
+ infoBox: {
100
+ padding: "12px 16px",
101
+ backgroundColor: "#e7f5ff",
102
+ borderRadius: "8px",
103
+ fontSize: "13px",
104
+ color: "#1864ab",
105
+ textAlign: "center" as const,
106
+ },
107
+ statusBadge: {
108
+ display: "inline-flex",
109
+ alignItems: "center",
110
+ gap: "6px",
111
+ padding: "6px 14px",
112
+ fontSize: "12px",
113
+ fontWeight: 600,
114
+ backgroundColor: "#e9ecef",
115
+ borderRadius: "20px",
116
+ color: "#495057",
117
+ },
118
+ };
119
+
21
120
  // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
22
121
  const meta = {
23
122
  title: "react-chess-puzzle/Components/Puzzle",
@@ -29,15 +128,8 @@ const meta = {
29
128
  },
30
129
  parameters: {
31
130
  actions: { argTypesRegex: "^_on.*" },
131
+ layout: "centered",
32
132
  },
33
- decorators: [
34
- (Story) => (
35
- <div style={{ width: "400px" }}>
36
- {/* 👇 Decorators in Storybook also accept a function. Replace <Story/> with Story() to enable it */}
37
- <Story />
38
- </div>
39
- ),
40
- ],
41
133
  } satisfies Meta<typeof ChessPuzzle.Root>;
42
134
 
43
135
  export default meta;
@@ -48,49 +140,70 @@ export const Example = (args: RootProps) => {
48
140
  const [puzzleIndex, setPuzzleIndex] = React.useState(0);
49
141
  const puzzle = puzzles[puzzleIndex];
50
142
  return (
51
- <div>
52
- <ChessPuzzle.Root {...args} puzzle={puzzle}>
53
- <ChessPuzzle.Board />
54
- <ChessPuzzle.Reset asChild>
55
- <button>restart</button>
56
- </ChessPuzzle.Reset>
57
- <ChessPuzzle.Reset
58
- asChild
59
- puzzle={puzzles[(puzzleIndex + 1) % puzzles.length]}
60
- onReset={() => setPuzzleIndex((puzzleIndex + 1) % puzzles.length)}
61
- >
62
- <button>next</button>
63
- </ChessPuzzle.Reset>
64
- <ChessPuzzle.Hint>hint</ChessPuzzle.Hint>
65
- </ChessPuzzle.Root>
66
- </div>
143
+ <ChessPuzzle.Root {...args} puzzle={puzzle}>
144
+ <div style={storyStyles.container}>
145
+ <div style={storyStyles.header}>
146
+ <h3 style={storyStyles.title}>Chess Puzzle</h3>
147
+ <p style={storyStyles.subtitle}>Find the best move sequence</p>
148
+ <span style={storyStyles.statusBadge}>
149
+ Puzzle {puzzleIndex + 1} of {puzzles.length}
150
+ </span>
151
+ </div>
152
+ <div style={storyStyles.boardWrapper}>
153
+ <ChessPuzzle.Board />
154
+ </div>
155
+ <div style={storyStyles.controlsSection}>
156
+ <ChessPuzzle.Reset asChild>
157
+ <button style={storyStyles.button}>Restart</button>
158
+ </ChessPuzzle.Reset>
159
+ <ChessPuzzle.Reset
160
+ asChild
161
+ puzzle={puzzles[(puzzleIndex + 1) % puzzles.length]}
162
+ onReset={() => setPuzzleIndex((puzzleIndex + 1) % puzzles.length)}
163
+ >
164
+ <button
165
+ style={{ ...storyStyles.button, ...storyStyles.buttonPrimary }}
166
+ >
167
+ Next Puzzle
168
+ </button>
169
+ </ChessPuzzle.Reset>
170
+ <ChessPuzzle.Hint style={storyStyles.hintButton}>
171
+ 💡 Hint
172
+ </ChessPuzzle.Hint>
173
+ </div>
174
+ </div>
175
+ </ChessPuzzle.Root>
67
176
  );
68
177
  };
69
178
 
70
179
  export const WithOrientation = (args: RootProps) => {
71
- const [puzzleIndex, setPuzzleIndex] = React.useState(0);
72
180
  const puzzle = {
73
181
  fen: "4kbnr/2p1pp1p/pp4p1/5b2/8/2NB1N2/PP3PPP/RKB4R b k - 0 1",
74
182
  makeFirstMove: false,
75
183
  moves: ["Bxd3"],
76
184
  };
77
185
  return (
78
- <div>
79
- <ChessPuzzle.Root {...args} puzzle={puzzle}>
80
- <ChessPuzzle.Board options={{ boardOrientation: "black" }} />
81
- <ChessPuzzle.Reset asChild>
82
- <button>restart</button>
83
- </ChessPuzzle.Reset>
84
- <ChessPuzzle.Reset
85
- asChild
86
- puzzle={puzzle}
87
- onReset={() => setPuzzleIndex((puzzleIndex + 1) % puzzles.length)}
88
- >
89
- <button>next</button>
90
- </ChessPuzzle.Reset>
91
- <ChessPuzzle.Hint>hint</ChessPuzzle.Hint>
92
- </ChessPuzzle.Root>
93
- </div>
186
+ <ChessPuzzle.Root {...args} puzzle={puzzle}>
187
+ <div style={storyStyles.container}>
188
+ <div style={storyStyles.header}>
189
+ <h3 style={storyStyles.title}>Black to Move</h3>
190
+ <p style={storyStyles.subtitle}>
191
+ Board oriented from Black's perspective
192
+ </p>
193
+ </div>
194
+ <div style={storyStyles.boardWrapper}>
195
+ <ChessPuzzle.Board options={{ boardOrientation: "black" }} />
196
+ </div>
197
+ <div style={storyStyles.controlsSection}>
198
+ <ChessPuzzle.Reset asChild>
199
+ <button style={storyStyles.button}>Restart</button>
200
+ </ChessPuzzle.Reset>
201
+ <ChessPuzzle.Hint style={storyStyles.hintButton}>
202
+ 💡 Hint
203
+ </ChessPuzzle.Hint>
204
+ </div>
205
+ </div>
206
+ </ChessPuzzle.Root>
94
207
  );
95
208
  };
96
209
 
@@ -101,14 +214,31 @@ export const Underpromotion = (args: RootProps) => {
101
214
  makeFirstMove: true,
102
215
  };
103
216
  return (
104
- <div>
105
- <ChessPuzzle.Root {...args} puzzle={puzzle}>
106
- <ChessPuzzle.Board />
107
- <ChessPuzzle.Reset asChild>
108
- <button>done! Restart</button>
109
- </ChessPuzzle.Reset>
110
- </ChessPuzzle.Root>
111
- </div>
217
+ <ChessPuzzle.Root {...args} puzzle={puzzle}>
218
+ <div style={storyStyles.container}>
219
+ <div style={storyStyles.header}>
220
+ <h3 style={storyStyles.title}>Underpromotion Challenge</h3>
221
+ <p style={storyStyles.subtitle}>
222
+ Promote to a knight instead of a queen
223
+ </p>
224
+ </div>
225
+ <div style={storyStyles.boardWrapper}>
226
+ <ChessPuzzle.Board />
227
+ </div>
228
+ <div style={storyStyles.controlsSection}>
229
+ <ChessPuzzle.Reset asChild>
230
+ <button
231
+ style={{ ...storyStyles.button, ...storyStyles.buttonSuccess }}
232
+ >
233
+ ✓ Solved! Restart
234
+ </button>
235
+ </ChessPuzzle.Reset>
236
+ </div>
237
+ <div style={storyStyles.infoBox}>
238
+ Sometimes promoting to a knight is better than a queen!
239
+ </div>
240
+ </div>
241
+ </ChessPuzzle.Root>
112
242
  );
113
243
  };
114
244
 
@@ -116,7 +246,18 @@ export const WithSounds = (args: RootProps) => {
116
246
  return (
117
247
  <ChessPuzzle.Root {...args} puzzle={puzzles[0]}>
118
248
  <ChessGame.Sounds />
119
- <ChessPuzzle.Board />
249
+ <div style={storyStyles.container}>
250
+ <div style={storyStyles.header}>
251
+ <h3 style={storyStyles.title}>Puzzle with Sound</h3>
252
+ <p style={storyStyles.subtitle}>Audio feedback on every move</p>
253
+ </div>
254
+ <div style={storyStyles.boardWrapper}>
255
+ <ChessPuzzle.Board />
256
+ </div>
257
+ <p style={{ fontSize: "12px", color: "#868e96", textAlign: "center" }}>
258
+ Move pieces to hear different sounds
259
+ </p>
260
+ </div>
120
261
  </ChessPuzzle.Root>
121
262
  );
122
263
  };
@@ -133,7 +274,145 @@ export const WithKeyboardControls = (args: RootProps) => {
133
274
  d: (context) => context.methods.goToNextMove(),
134
275
  }}
135
276
  />
136
- <ChessPuzzle.Board />
277
+ <div style={storyStyles.container}>
278
+ <div style={storyStyles.header}>
279
+ <h3 style={storyStyles.title}>Keyboard Navigation</h3>
280
+ <p style={storyStyles.subtitle}>Use keyboard shortcuts to navigate</p>
281
+ </div>
282
+ <div style={storyStyles.boardWrapper}>
283
+ <ChessPuzzle.Board />
284
+ </div>
285
+ <div
286
+ style={{
287
+ display: "grid",
288
+ gridTemplateColumns: "repeat(3, auto)",
289
+ gap: "8px",
290
+ justifyContent: "center",
291
+ marginTop: "12px",
292
+ }}
293
+ >
294
+ <div
295
+ style={{
296
+ display: "flex",
297
+ alignItems: "center",
298
+ gap: "6px",
299
+ fontSize: "12px",
300
+ color: "#495057",
301
+ }}
302
+ >
303
+ <kbd
304
+ style={{
305
+ padding: "2px 8px",
306
+ backgroundColor: "#e9ecef",
307
+ border: "1px solid #ced4da",
308
+ borderRadius: "4px",
309
+ fontFamily: "monospace",
310
+ fontSize: "11px",
311
+ fontWeight: 600,
312
+ }}
313
+ >
314
+ W
315
+ </kbd>{" "}
316
+ Start
317
+ </div>
318
+ <div
319
+ style={{
320
+ display: "flex",
321
+ alignItems: "center",
322
+ gap: "6px",
323
+ fontSize: "12px",
324
+ color: "#495057",
325
+ }}
326
+ >
327
+ <kbd
328
+ style={{
329
+ padding: "2px 8px",
330
+ backgroundColor: "#e9ecef",
331
+ border: "1px solid #ced4da",
332
+ borderRadius: "4px",
333
+ fontFamily: "monospace",
334
+ fontSize: "11px",
335
+ fontWeight: 600,
336
+ }}
337
+ >
338
+ A
339
+ </kbd>{" "}
340
+ Previous
341
+ </div>
342
+ <div
343
+ style={{
344
+ display: "flex",
345
+ alignItems: "center",
346
+ gap: "6px",
347
+ fontSize: "12px",
348
+ color: "#495057",
349
+ }}
350
+ >
351
+ <kbd
352
+ style={{
353
+ padding: "2px 8px",
354
+ backgroundColor: "#e9ecef",
355
+ border: "1px solid #ced4da",
356
+ borderRadius: "4px",
357
+ fontFamily: "monospace",
358
+ fontSize: "11px",
359
+ fontWeight: 600,
360
+ }}
361
+ >
362
+ F
363
+ </kbd>{" "}
364
+ Flip
365
+ </div>
366
+ <div
367
+ style={{
368
+ display: "flex",
369
+ alignItems: "center",
370
+ gap: "6px",
371
+ fontSize: "12px",
372
+ color: "#495057",
373
+ }}
374
+ >
375
+ <kbd
376
+ style={{
377
+ padding: "2px 8px",
378
+ backgroundColor: "#e9ecef",
379
+ border: "1px solid #ced4da",
380
+ borderRadius: "4px",
381
+ fontFamily: "monospace",
382
+ fontSize: "11px",
383
+ fontWeight: 600,
384
+ }}
385
+ >
386
+ S
387
+ </kbd>{" "}
388
+ End
389
+ </div>
390
+ <div
391
+ style={{
392
+ display: "flex",
393
+ alignItems: "center",
394
+ gap: "6px",
395
+ fontSize: "12px",
396
+ color: "#495057",
397
+ }}
398
+ >
399
+ <kbd
400
+ style={{
401
+ padding: "2px 8px",
402
+ backgroundColor: "#e9ecef",
403
+ border: "1px solid #ced4da",
404
+ borderRadius: "4px",
405
+ fontFamily: "monospace",
406
+ fontSize: "11px",
407
+ fontWeight: 600,
408
+ }}
409
+ >
410
+ D
411
+ </kbd>{" "}
412
+ Next
413
+ </div>
414
+ </div>
415
+ </div>
137
416
  </ChessPuzzle.Root>
138
417
  );
139
418
  };
@@ -147,39 +426,72 @@ const multiMatePuzzle = {
147
426
 
148
427
  export const MultiMatePuzzle = (args: RootProps) => {
149
428
  return (
150
- <div>
151
- <p style={{ marginBottom: "1rem", fontSize: "0.875rem" }}>
152
- <strong>solveOnCheckmate=true (default):</strong> Any checkmate move
153
- solves the puzzle. Try Qc8# (queen mate), Qf8# (queen mate), Rb8# (rook
154
- mate), or the canonical Ra8#.
155
- </p>
156
- <ChessPuzzle.Root {...args} puzzle={multiMatePuzzle}>
157
- <ChessPuzzle.Board />
158
- <ChessPuzzle.Reset asChild>
159
- <button>restart</button>
160
- </ChessPuzzle.Reset>
161
- </ChessPuzzle.Root>
162
- </div>
429
+ <ChessPuzzle.Root {...args} puzzle={multiMatePuzzle}>
430
+ <div style={storyStyles.container}>
431
+ <div style={storyStyles.header}>
432
+ <h3 style={storyStyles.title}>Flexible Checkmate</h3>
433
+ <p style={storyStyles.subtitle}>
434
+ Any checkmate move solves the puzzle
435
+ </p>
436
+ </div>
437
+ <div
438
+ style={{
439
+ ...storyStyles.infoBox,
440
+ backgroundColor: "#d3f9d8",
441
+ color: "#2b8a3e",
442
+ }}
443
+ >
444
+ <strong>solveOnCheckmate=true (default)</strong>
445
+ <br />
446
+ Try Qc8#, Qf8#, Rb8#, or the canonical Ra8#
447
+ </div>
448
+ <div style={storyStyles.boardWrapper}>
449
+ <ChessPuzzle.Board />
450
+ </div>
451
+ <div style={storyStyles.controlsSection}>
452
+ <ChessPuzzle.Reset asChild>
453
+ <button style={storyStyles.button}>Restart</button>
454
+ </ChessPuzzle.Reset>
455
+ </div>
456
+ </div>
457
+ </ChessPuzzle.Root>
163
458
  );
164
459
  };
165
460
 
166
461
  export const MultiMatePuzzleStrict = (args: RootProps) => {
167
462
  return (
168
- <div>
169
- <p style={{ marginBottom: "1rem", fontSize: "0.875rem" }}>
170
- <strong>solveOnCheckmate=false:</strong> Only the canonical solution
171
- (a7a8#) is accepted. Alternative mates like Qc8# will fail the puzzle.
172
- </p>
173
- <ChessPuzzle.Root
174
- {...args}
175
- puzzle={multiMatePuzzle}
176
- solveOnCheckmate={false}
177
- >
178
- <ChessPuzzle.Board />
179
- <ChessPuzzle.Reset asChild>
180
- <button>restart</button>
181
- </ChessPuzzle.Reset>
182
- </ChessPuzzle.Root>
183
- </div>
463
+ <ChessPuzzle.Root
464
+ {...args}
465
+ puzzle={multiMatePuzzle}
466
+ solveOnCheckmate={false}
467
+ >
468
+ <div style={storyStyles.container}>
469
+ <div style={storyStyles.header}>
470
+ <h3 style={storyStyles.title}>Strict Checkmate</h3>
471
+ <p style={storyStyles.subtitle}>
472
+ Only the canonical solution is accepted
473
+ </p>
474
+ </div>
475
+ <div
476
+ style={{
477
+ ...storyStyles.infoBox,
478
+ backgroundColor: "#ffe3e3",
479
+ color: "#c92a2a",
480
+ }}
481
+ >
482
+ <strong>solveOnCheckmate=false</strong>
483
+ <br />
484
+ Only Ra8# is accepted. Alternative mates like Qc8# will fail!
485
+ </div>
486
+ <div style={storyStyles.boardWrapper}>
487
+ <ChessPuzzle.Board />
488
+ </div>
489
+ <div style={storyStyles.controlsSection}>
490
+ <ChessPuzzle.Reset asChild>
491
+ <button style={storyStyles.button}>Restart</button>
492
+ </ChessPuzzle.Reset>
493
+ </div>
494
+ </div>
495
+ </ChessPuzzle.Root>
184
496
  );
185
497
  };