@mideind/netskrafl-react 2.0.1 → 2.1.1

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/README.md CHANGED
@@ -1,63 +1,138 @@
1
1
 
2
2
  # netskrafl-react
3
3
 
4
- ## A React front end for Netskrafl
4
+ A React front end for Netskrafl, packaged as a React component library.
5
5
 
6
- This is a web front end for the Netskrafl game server, packaged as a React component.
6
+ ## Components
7
7
 
8
- The component is written in TypeScript. It assumes that the containing application
9
- (a) performs user authentication and (b) determines whether users have full access to the
10
- Netskrafl functionality (typically by being paying subscribers) or limited/free access.
11
- Information about (a) and (b) is passed to the component via its props.
8
+ This library provides two main components:
12
9
 
13
- The code for the Netskrafl backend server can be found on GitHub
14
- at [https://github.com/mideind/Netskrafl](https://github.com/mideind/Netskrafl).
15
- The backend is written in Python, using the Flask web framework.
16
- The `netskrafl-react` component talks to the Netskrafl server via a
17
- JSON API over HTTPS, and uses Firebase for real-time updates.
10
+ - **Netskrafl** - The full multiplayer crossword game
11
+ - **GataDagsins** - A daily crossword riddle ("Riddle of the Day")
18
12
 
19
- The source code for `netskrafl-react` is a bit unusual in that it was originally
20
- written for the [Mithril](https://mithril.js.org) UI framework. The Mithril code
21
- has been wrapped in a React component. Modifying the component thus requires
22
- some familiarity with Mithril, as well as React. However, Mithril is fairly
23
- straightforward and arguably simpler than React, so this should not be a major
24
- obstacle.
13
+ Both components are responsive and mobile-friendly.
25
14
 
26
15
  ## Installation
27
16
 
28
- To install the Netskrafl React component via `npm`, run the following command:
17
+ ```bash
18
+ npm install @mideind/netskrafl-react
19
+ ```
29
20
 
21
+ Requires React 18+. This package is distributed as ESM only.
22
+
23
+ ## Usage
24
+
25
+ Import the component and its styles:
26
+
27
+ ```tsx
28
+ import { Netskrafl } from '@mideind/netskrafl-react';
29
+ import '@mideind/netskrafl-react/style.css';
30
+
31
+ function App() {
32
+ return (
33
+ <Netskrafl
34
+ state={{
35
+ userEmail: 'user@example.com',
36
+ userFullName: 'Example User',
37
+ userId: 'unique-user-id',
38
+ firebaseToken: 'firebase-auth-token',
39
+ subscriber: true,
40
+ locale: 'is',
41
+ urlPrefix: 'https://netskrafl.is',
42
+ }}
43
+ />
44
+ );
45
+ }
30
46
  ```
31
- npm install @mideind/netskrafl-react
47
+
48
+ For Gáta Dagsins:
49
+
50
+ ```tsx
51
+ import { GataDagsins } from '@mideind/netskrafl-react';
52
+ import '@mideind/netskrafl-react/style.css';
53
+
54
+ function App() {
55
+ return (
56
+ <GataDagsins
57
+ state={{
58
+ userEmail: 'user@example.com',
59
+ userFullName: 'Example User',
60
+ userId: 'unique-user-id',
61
+ firebaseToken: 'firebase-auth-token',
62
+ subscriber: true,
63
+ locale: 'is',
64
+ urlPrefix: 'https://netskrafl.is',
65
+ }}
66
+ />
67
+ );
68
+ }
32
69
  ```
33
70
 
34
- ## Development
71
+ ### Props
35
72
 
36
- During development, you can run the rollup bundler in watch mode by running:
73
+ | Prop | Type | Description |
74
+ |------|------|-------------|
75
+ | `userEmail` | string | User's email address |
76
+ | `userFullName` | string | User's display name |
77
+ | `userId` | string | Unique user identifier |
78
+ | `firebaseToken` | string | Firebase authentication token for real-time updates |
79
+ | `subscriber` | boolean | Whether user has full access (subscriber) or limited/free access |
80
+ | `locale` | string | UI language (`'is'` for Icelandic, `'en'` for English) |
81
+ | `urlPrefix` | string | Base URL of the Netskrafl backend server |
37
82
 
38
- ```bash
39
- npm run watch
40
- ```
83
+ ## Features
41
84
 
42
- You can also use Storybook as a testing container by running:
85
+ - Drag-and-drop tile placement
86
+ - Click-to-select tile placement
87
+ - Keyboard tile placement (type letters to place tiles)
88
+ - Real-time game updates via Firebase
89
+ - Mobile-responsive design with touch support
90
+ - Pinch-to-zoom on mobile devices
43
91
 
44
- ```bash
45
- npm run storybook
46
- ```
92
+ ## Architecture
93
+
94
+ The components are written in TypeScript. They assume that the containing
95
+ application handles user authentication and subscription status.
47
96
 
48
- To build the component for production, run:
97
+ The code uses a hybrid React-Mithril architecture: the components are React
98
+ wrappers around [Mithril](https://mithril.js.org) UI code. Modifying the
99
+ components requires familiarity with both frameworks, though Mithril is
100
+ straightforward and arguably simpler than React.
101
+
102
+ The components communicate with the Netskrafl backend server via a JSON API
103
+ over HTTPS, and use Firebase for real-time updates.
104
+
105
+ ## Backend
106
+
107
+ The Netskrafl backend server code is available at
108
+ [github.com/mideind/Netskrafl](https://github.com/mideind/Netskrafl).
109
+ The backend is written in Python using the Flask web framework.
110
+
111
+ ## Development
49
112
 
50
113
  ```bash
114
+ # Install dependencies
115
+ npm install
116
+
117
+ # Run rollup in watch mode
118
+ npm run watch
119
+
120
+ # Run Storybook for development/testing
121
+ npm run storybook
122
+
123
+ # Build for production
51
124
  npm run rollup
52
- ```
53
125
 
54
- See the `package.json` file for specifics.
126
+ # Type-check
127
+ npx tsc --noEmit
128
+
129
+ # Lint
130
+ npx @biomejs/biome check src/
131
+ ```
55
132
 
56
- ## License and copyright
133
+ ## License
57
134
 
58
- Copyright © 2025 Miðeind ehf. The original author of `netskrafl-react`
59
- is *Vilhjálmur Þorsteinsson*.
135
+ Copyright © 2025 Miðeind ehf. Original author: Vilhjálmur Þorsteinsson.
60
136
 
61
- This code is released under the
62
- [CC-BY-NC 4.0 license](https://creativecommons.org/licenses/by-nc/4.0/).
137
+ Released under the [CC-BY-NC 4.0 license](https://creativecommons.org/licenses/by-nc/4.0/).
63
138
  See the LICENSE file for details.
@@ -3206,22 +3206,27 @@ div.netskrafl-racktile {
3206
3206
  0% {
3207
3207
  color: white;
3208
3208
  background-color: var(--cancel-button);
3209
+ border-color: var(--cancel-button);
3209
3210
  }
3210
3211
  45% {
3211
3212
  color: white;
3212
3213
  background-color: var(--cancel-button);
3214
+ border-color: var(--cancel-button);
3213
3215
  }
3214
3216
  50% {
3215
3217
  color: black;
3216
3218
  background-color: white;
3219
+ border-color: white;
3217
3220
  }
3218
3221
  95% {
3219
3222
  color: black;
3220
3223
  background-color: white;
3224
+ border-color: white;
3221
3225
  }
3222
3226
  100% {
3223
3227
  color: white;
3224
3228
  background-color: var(--cancel-button);
3229
+ border-color: var(--cancel-button);
3225
3230
  }
3226
3231
  }
3227
3232
 
@@ -3229,22 +3234,27 @@ div.netskrafl-racktile {
3229
3234
  0% {
3230
3235
  color: white;
3231
3236
  background-color: var(--cancel-button);
3237
+ border-color: var(--cancel-button);
3232
3238
  }
3233
3239
  45% {
3234
3240
  color: white;
3235
3241
  background-color: var(--cancel-button);
3242
+ border-color: var(--cancel-button);
3236
3243
  }
3237
3244
  50% {
3238
3245
  color: black;
3239
3246
  background-color: white;
3247
+ border-color: white;
3240
3248
  }
3241
3249
  95% {
3242
3250
  color: black;
3243
3251
  background-color: white;
3252
+ border-color: white;
3244
3253
  }
3245
3254
  100% {
3246
3255
  color: white;
3247
3256
  background-color: var(--cancel-button);
3257
+ border-color: var(--cancel-button);
3248
3258
  }
3249
3259
  }
3250
3260
 
@@ -3252,22 +3262,27 @@ div.netskrafl-racktile {
3252
3262
  0% {
3253
3263
  color: white;
3254
3264
  background-color: var(--cancel-button);
3265
+ border-color: var(--cancel-button);
3255
3266
  }
3256
3267
  45% {
3257
3268
  color: white;
3258
3269
  background-color: var(--cancel-button);
3270
+ border-color: var(--cancel-button);
3259
3271
  }
3260
3272
  50% {
3261
3273
  color: var(--blank-tile);
3262
3274
  background-color: white;
3275
+ border-color: white;
3263
3276
  }
3264
3277
  95% {
3265
3278
  color: var(--blank-tile);
3266
3279
  background-color: white;
3280
+ border-color: white;
3267
3281
  }
3268
3282
  100% {
3269
3283
  color: white;
3270
3284
  background-color: var(--cancel-button);
3285
+ border-color: var(--cancel-button);
3271
3286
  }
3272
3287
  }
3273
3288
 
@@ -3275,22 +3290,65 @@ div.netskrafl-racktile {
3275
3290
  0% {
3276
3291
  color: white;
3277
3292
  background-color: var(--cancel-button);
3293
+ border-color: var(--cancel-button);
3278
3294
  }
3279
3295
  45% {
3280
3296
  color: white;
3281
3297
  background-color: var(--cancel-button);
3298
+ border-color: var(--cancel-button);
3282
3299
  }
3283
3300
  50% {
3284
3301
  color: var(--blank-tile);
3285
3302
  background-color: white;
3303
+ border-color: white;
3286
3304
  }
3287
3305
  95% {
3288
3306
  color: var(--blank-tile);
3289
3307
  background-color: white;
3308
+ border-color: white;
3290
3309
  }
3291
3310
  100% {
3292
3311
  color: white;
3293
3312
  background-color: var(--cancel-button);
3313
+ border-color: var(--cancel-button);
3314
+ }
3315
+ }
3316
+
3317
+ /* Text-only animation for tiles (background handled by parent td) */
3318
+
3319
+ @keyframes selBlinkText {
3320
+ 0% {
3321
+ color: white;
3322
+ }
3323
+ 45% {
3324
+ color: white;
3325
+ }
3326
+ 50% {
3327
+ color: black;
3328
+ }
3329
+ 95% {
3330
+ color: black;
3331
+ }
3332
+ 100% {
3333
+ color: white;
3334
+ }
3335
+ }
3336
+
3337
+ @keyframes selBlankText {
3338
+ 0% {
3339
+ color: white;
3340
+ }
3341
+ 45% {
3342
+ color: white;
3343
+ }
3344
+ 50% {
3345
+ color: var(--blank-tile);
3346
+ }
3347
+ 95% {
3348
+ color: var(--blank-tile);
3349
+ }
3350
+ 100% {
3351
+ color: white;
3294
3352
  }
3295
3353
  }
3296
3354
 
@@ -3367,19 +3425,83 @@ div.netskrafl-racktile {
3367
3425
  }
3368
3426
 
3369
3427
  .netskrafl-container div.sel {
3428
+ background-color: transparent;
3429
+ color: inherit;
3430
+ }
3431
+
3432
+ div.netskrafl-blanktile.sel {
3433
+ background-color: transparent;
3434
+ color: inherit;
3435
+ }
3436
+
3437
+ /* Make the parent td also blink when it contains a selected tile */
3438
+
3439
+ .netskrafl-container div.rack td:has(> div.sel),
3440
+ .netskrafl-container div.board td:has(> div.sel),
3441
+ .netskrafl-container table.board td:has(> div.sel) {
3370
3442
  animation: selBlink 1.2s infinite;
3371
3443
  -webkit-animation: selBlink 1.2s infinite;
3372
3444
  }
3373
3445
 
3374
- div.netskrafl-blanktile.sel {
3375
- animation: selBlank 1.2s infinite;
3376
- -webkit-animation: selBlank 1.2s infinite;
3446
+ /* Make the parent td match the background of an exchange-selected tile */
3447
+
3448
+ div.rack td:has(> div.netskrafl-xchgsel) {
3449
+ background-color: var(--human-color);
3450
+ border-color: var(--human-color);
3377
3451
  }
3378
3452
 
3379
3453
  .netskrafl-container td.sel {
3380
3454
  outline: var(--cancel-button) solid 3px;
3381
3455
  }
3382
3456
 
3457
+ /* Container focus styling - remove default outline */
3458
+
3459
+ .netskrafl-container:focus {
3460
+ outline: none;
3461
+ }
3462
+
3463
+ /* Keyboard tile placement target indicator */
3464
+
3465
+ .netskrafl-container td.keyboard-target {
3466
+ position: relative;
3467
+ background-color: rgba(102, 178, 102, 0.4); /* Light green highlight */
3468
+ }
3469
+
3470
+ .netskrafl-container td.keyboard-target::after {
3471
+ font-family: "Glyphicons Regular";
3472
+ content: "\E212"; /* right-arrow */
3473
+ position: absolute;
3474
+ top: calc(50% + 1px);
3475
+ left: 50%;
3476
+ transform: translate(-50%, -50%);
3477
+ font-size: 15px;
3478
+ color: var(--cancel-button);
3479
+ opacity: 0.8;
3480
+ animation: pulse 1.5s infinite;
3481
+ }
3482
+
3483
+ .netskrafl-container td.keyboard-target.vertical::after {
3484
+ content: "\E213"; /* down-arrow */
3485
+ }
3486
+
3487
+ @keyframes pulse {
3488
+ 0% {
3489
+ opacity: 0.4;
3490
+ }
3491
+ 25% {
3492
+ opacity: 0.8;
3493
+ }
3494
+ 50% {
3495
+ opacity: 1;
3496
+ }
3497
+ 75% {
3498
+ opacity: 0.8;
3499
+ }
3500
+ 100% {
3501
+ opacity: 0.4;
3502
+ }
3503
+ }
3504
+
3383
3505
  .netskrafl-container div.xchg {
3384
3506
  cursor: pointer;
3385
3507
  }
@@ -7585,6 +7707,12 @@ div.netskrafl-container input[type="checkbox"] {
7585
7707
  overflow-y: hidden;
7586
7708
  }
7587
7709
 
7710
+ /* Container focus styling - remove default outline */
7711
+
7712
+ .netskrafl-container:focus {
7713
+ outline: none;
7714
+ }
7715
+
7588
7716
  .netskrafl-container div.gatadagsins-main {
7589
7717
  display: flex;
7590
7718
  position: relative;