@mideind/netskrafl-react 3.4.9 → 3.4.11

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
@@ -26,6 +26,11 @@ If using Next.js, add the package to `transpilePackages` in `next.config.ts`:
26
26
  transpilePackages: ['@mideind/netskrafl-react'],
27
27
  ```
28
28
 
29
+ The components use React hooks (`useEffect`, `useRef`), so under the
30
+ Next.js App Router they must be rendered from a Client Component —
31
+ either mark your wrapper file with `'use client'` at the top, or
32
+ import them from a wrapper that does.
33
+
29
34
  ## Usage
30
35
 
31
36
  Import the component, its styles, and optionally the props type:
@@ -53,7 +58,7 @@ function App() {
53
58
  databaseUrl: 'https://your-netskrafl.firebaseio.com',
54
59
  measurementId: '...',
55
60
  // Optional
56
- loginMethod: 'myapp',
61
+ loginMethod: 'myapp', // e.g. 'malstadur'
57
62
  subscriptionUrl: '/subscribe',
58
63
  },
59
64
  tokenExpired: async () => {
@@ -119,6 +124,53 @@ All fields are optional (the state is `Partial<GlobalState>`), but the
119
124
  components need at minimum `serverUrl`, `userEmail`, `token`, and the
120
125
  Firebase configuration fields to function.
121
126
 
127
+ ## Host integration and layout
128
+
129
+ The component fills the height of its parent slot — `.netskrafl-inner`
130
+ (and the `.netskrafl` / `.gatadagsins` wrappers) all use `height: 100%`.
131
+ The host application must therefore hand it a parent with a definite
132
+ height; otherwise the component collapses to zero height and renders
133
+ nothing.
134
+
135
+ A typical Next.js App Router layout that satisfies this contract:
136
+
137
+ ```tsx
138
+ // app/layout.tsx
139
+ <body className='h-screen'>
140
+ <div className='flex h-screen flex-col'>
141
+ <SiteHeader />
142
+ <main className='flex-1 overflow-y-auto'>
143
+ {children}
144
+ </main>
145
+ </div>
146
+ </body>
147
+
148
+ // app/netskrafl/page.tsx
149
+ export default function Page() {
150
+ return (
151
+ <div className='flex flex-1 flex-col'>
152
+ <div className='flex h-full grow justify-center'>
153
+ <NetskraflClientWrapper />
154
+ </div>
155
+ </div>
156
+ );
157
+ }
158
+ ```
159
+
160
+ The `flex-1` / `h-full` chain propagates the viewport height down to
161
+ `.netskrafl-inner` so it sizes itself to exactly the slot the host
162
+ allocates — no clipping at the bottom of the action buttons, no
163
+ unwanted vertical scrolling inside the host's content area.
164
+
165
+ ### Tunable CSS variables
166
+
167
+ The host stylesheet may set the following on `:root` (or any ancestor
168
+ of the component) to fine-tune internal layout:
169
+
170
+ | Variable | Default | Purpose |
171
+ |----------|---------|---------|
172
+ | `--header-size` | `40px` | Height of the host's header bar; subtracted from the move-list and tab-panel heights so they don't overflow the host slot on mobile portrait. |
173
+
122
174
  ## Features
123
175
 
124
176
  - Drag-and-drop tile placement with touch support
@@ -158,9 +210,6 @@ npm install
158
210
  # Run rollup in watch mode
159
211
  npm run watch
160
212
 
161
- # Run Storybook for development/testing
162
- npm run storybook
163
-
164
213
  # Build for production
165
214
  npm run rollup
166
215
 
@@ -1232,10 +1232,15 @@ div.netskrafl-container {
1232
1232
  }
1233
1233
 
1234
1234
  .netskrafl-container .ui-tabs .ui-tabs-nav .ui-tabs-anchor.sp {
1235
- /* Single page version, mobile - 5 tabs must fit in 375px */
1236
- min-width: 64px;
1237
- padding-left: 4px;
1238
- padding-right: 4px;
1235
+ /* Single page version, mobile - 5 tabs must fit in the available width.
1236
+ Tightened to make room for Málstaður's immersive chrome slice
1237
+ (a 40x40 quarter-disk at the viewport top-left), which on mobile
1238
+ pushes the lobby tab bar right by ~44px. Landscape and desktop
1239
+ media queries below override these back since the tab legend is
1240
+ shown there. */
1241
+ min-width: 58px;
1242
+ padding-left: 2px;
1243
+ padding-right: 2px;
1239
1244
  }
1240
1245
 
1241
1246
  .netskrafl-container .ui-tabs .ui-tabs-nav .ui-tabs-anchor.sp.narrow {
@@ -1546,6 +1551,19 @@ div.netskrafl-container {
1546
1551
  /* 0.96 = 360/375, scales 375px design to fit 360px viewport */
1547
1552
  transform-origin: center top;
1548
1553
  }
1554
+ /* Gáta dagsins lays content right at both edges, so center-origin
1555
+ scaling leaves a visible 7.5px gap on the left and still clips
1556
+ on the right when the 375px design overflows the viewport. Anchor
1557
+ the scale at the left edge instead — Netskrafl's natural left
1558
+ margins on .board-area mask the same offset, so we leave it
1559
+ alone there. (Selector targets the id on the Gáta dagsins
1560
+ .netskrafl-inner so the postcss prefix `.netskrafl-container`
1561
+ still resolves correctly — `.gatadagsins` is *outside*
1562
+ `.netskrafl-container`, so a `.gatadagsins …` selector wouldn't
1563
+ match after the prefix is applied.) */
1564
+ .netskrafl-container div.netskrafl-inner#gatadagsins-container {
1565
+ transform-origin: left top;
1566
+ }
1549
1567
  }
1550
1568
 
1551
1569
  /* --------------------------------------------------------------------------
@@ -6572,21 +6590,20 @@ div.netskrafl-container input[type="checkbox"] {
6572
6590
  top: -4px;
6573
6591
  padding-right: 10px;
6574
6592
  }
6575
- .netskrafl-container div.player-btn {
6576
- display: inline-block;
6577
- position: relative;
6578
- top: 0;
6579
- left: 0;
6580
- padding-top: 4px;
6581
- width: 128px;
6582
- }
6593
+ .netskrafl-container div.player-btn,
6583
6594
  .netskrafl-container div.robot-btn {
6584
6595
  display: inline-block;
6585
6596
  position: relative;
6586
6597
  top: 0;
6587
6598
  left: 0;
6588
6599
  padding-top: 4px;
6589
- width: 128px;
6600
+ /* Without an absolute width the inline-block grew to fit the
6601
+ player name, so the default text-overflow: ellipsis never
6602
+ kicked in. Cap at the parent width (border-box so the side
6603
+ paddings don't push past it) to restore the "…" truncation
6604
+ for long nicks. */
6605
+ max-width: 100%;
6606
+ box-sizing: border-box;
6590
6607
  }
6591
6608
  .netskrafl-container div.player-btn.left,
6592
6609
  .netskrafl-container div.robot-btn.left {
@@ -6600,9 +6617,48 @@ div.netskrafl-container input[type="checkbox"] {
6600
6617
  height: auto;
6601
6618
  display: flex;
6602
6619
  flex-direction: row;
6620
+ /* Reserve top-left 40x40 area for Málstaður's immersive
6621
+ chrome slice (quarter-disk with sidebar trigger). The
6622
+ border-box keeps the padding inside the 100% width so the
6623
+ right edge of the heading does not get clipped. */
6624
+ padding-left: 32px;
6625
+ box-sizing: border-box;
6626
+ }
6627
+ .netskrafl-container div.logowrapper {
6628
+ /* Slight rebalance with playerwrapper (was 11:89) gives the
6629
+ home glyph a bit more breathing room next to the chrome
6630
+ slice; justify-content: center adds even margins around the
6631
+ glyph itself. */
6632
+ flex: 14;
6633
+ justify-content: center;
6634
+ }
6635
+ /* Broaden the tap target so the whole logowrapper area routes home,
6636
+ not just the glyph itself. */
6637
+ .netskrafl-container div.logowrapper div.header-logo,
6638
+ .netskrafl-container div.logowrapper a.backlink {
6639
+ display: flex;
6640
+ align-items: center;
6641
+ justify-content: center;
6642
+ width: 100%;
6643
+ height: 100%;
6644
+ }
6645
+ .netskrafl-container div.playerwrapper {
6646
+ flex: 86;
6647
+ /* Flex items default to min-width: auto, which is min-content —
6648
+ a long unbreakable nick (e.g. "WWWW…") forces the wrapper and
6649
+ the player halves wide enough to show the whole string,
6650
+ defeating the player-btn's max-width: 100% + ellipsis. Allow
6651
+ the flex chain to shrink below content. */
6652
+ min-width: 0;
6653
+ }
6654
+ .netskrafl-container div.leftplayer,
6655
+ .netskrafl-container div.rightplayer {
6656
+ min-width: 0;
6603
6657
  }
6604
6658
  .netskrafl-container div.fairplay {
6605
- top: 18px;
6659
+ /* Nudged down so the indicator does not overlap an ellipsis
6660
+ on a long left-player nick. */
6661
+ top: 26px;
6606
6662
  }
6607
6663
  .netskrafl-container span.fairplay-btn.large {
6608
6664
  /* This is shown in the board header */
@@ -6628,6 +6684,13 @@ div.netskrafl-container input[type="checkbox"] {
6628
6684
  .netskrafl-container div.board-area {
6629
6685
  top: 98px;
6630
6686
  }
6687
+ /* Reserve top-left 40x40 area for Málstaður's immersive chrome slice
6688
+ (quarter-disk with sidebar trigger). Shift the lobby tab bar right;
6689
+ the .sp tab dimensions are tightened in the default rule above so
6690
+ all five still fit on one row. */
6691
+ .netskrafl-container div#main-tabs > ul.ui-tabs-nav {
6692
+ padding-left: 44px;
6693
+ }
6631
6694
  }
6632
6695
 
6633
6696
  /* iPhone 11 or larger: scale the UI to appear larger on the screen */
@@ -6674,10 +6737,12 @@ div.netskrafl-container input[type="checkbox"] {
6674
6737
  display: block;
6675
6738
  }
6676
6739
  .netskrafl-container .ui-tabs .ui-tabs-nav .ui-tabs-anchor.sp {
6677
- /* Override tab width */
6740
+ /* Override tab width. Padding tightened from 12px to 10px to keep
6741
+ all five tabs on one row alongside the 44px left shift for the
6742
+ Málstaður immersive chrome slice. */
6678
6743
  min-width: 82px;
6679
- padding-left: 12px;
6680
- padding-right: 12px;
6744
+ padding-left: 10px;
6745
+ padding-right: 10px;
6681
6746
  }
6682
6747
  .netskrafl-container div.tabbed-page {
6683
6748
  width: 100%;
@@ -6693,6 +6758,12 @@ div.netskrafl-container input[type="checkbox"] {
6693
6758
  .netskrafl-container p.help-center {
6694
6759
  text-align: center;
6695
6760
  }
6761
+ .netskrafl-container div.heading {
6762
+ /* Landscape hides the logowrapper, so the Málstaður chrome
6763
+ slice does not overlap any heading element — reset the
6764
+ padding-left applied in mobile portrait. */
6765
+ padding-left: 0;
6766
+ }
6696
6767
  .netskrafl-container div.logowrapper {
6697
6768
  display: none;
6698
6769
  }
@@ -6700,7 +6771,9 @@ div.netskrafl-container input[type="checkbox"] {
6700
6771
  width: 100%;
6701
6772
  }
6702
6773
  .netskrafl-container div.fairplay {
6703
- top: 8px;
6774
+ /* Nudged down so the indicator does not overlap an ellipsis
6775
+ on a long left-player nick. */
6776
+ top: 36px;
6704
6777
  }
6705
6778
  .netskrafl-container div.login-btn-small {
6706
6779
  position: fixed;
@@ -6711,8 +6784,10 @@ div.netskrafl-container input[type="checkbox"] {
6711
6784
  .netskrafl-container div.logo {
6712
6785
  display: block;
6713
6786
  position: absolute;
6714
- top: 8px;
6715
- left: calc(8px - (100cqw - 667px) / 2);
6787
+ /* Nudged down (8px -> 50px) and right (+4px) to clear the
6788
+ Málstaður immersive chrome slice in the top-left corner. */
6789
+ top: 50px;
6790
+ left: calc(12px - (100cqw - 667px) / 2);
6716
6791
  width: 30px;
6717
6792
  z-index: 10;
6718
6793
  }
@@ -6731,6 +6806,17 @@ div.netskrafl-container input[type="checkbox"] {
6731
6806
  transform: scale(clamp(0.88, calc((100dvh - 16px) / 408px), 1));
6732
6807
  transform-origin: right top;
6733
6808
  }
6809
+ /* Gáta dagsins centers the board inside a flex column and has
6810
+ enough room in landscape, so skip the iPhone SE scale-down that
6811
+ the Netskrafl game view needs. */
6812
+ .netskrafl-container div.gatadagsins-container div.board {
6813
+ transform: none;
6814
+ }
6815
+ /* Breathing room between the top header (date nav + status) and the
6816
+ board in Gáta dagsins landscape. */
6817
+ .netskrafl-container div.gatadagsins-board-area {
6818
+ margin-top: 12px;
6819
+ }
6734
6820
  .netskrafl-container div.rightcol {
6735
6821
  width: 283px;
6736
6822
  top: 8px;
@@ -6804,7 +6890,7 @@ div.netskrafl-container input[type="checkbox"] {
6804
6890
  }
6805
6891
  .netskrafl-container div.player-btn,
6806
6892
  .netskrafl-container div.robot-btn {
6807
- width: 120px;
6893
+ width: auto;
6808
6894
  padding-top: 6px;
6809
6895
  }
6810
6896
  .netskrafl-container div.scoreleft,
@@ -7061,6 +7147,9 @@ div.netskrafl-container input[type="checkbox"] {
7061
7147
  }
7062
7148
  .netskrafl-container div.logo {
7063
7149
  display: block;
7150
+ /* Pushed below the Málstaður immersive chrome tab (40x40,
7151
+ top-left) so the home glyph does not sit underneath it. */
7152
+ top: 54px;
7064
7153
  }
7065
7154
  .netskrafl-container div.netskrafl-logo {
7066
7155
  /* Same size as Málstaður logo */
@@ -7695,12 +7784,34 @@ div.netskrafl-container input[type="checkbox"] {
7695
7784
  .netskrafl-container div.player-btn,
7696
7785
  .netskrafl-container div.robot-btn {
7697
7786
  position: relative;
7698
- display: flex;
7699
- align-items: center;
7787
+ /* Use inline-block (not flex) so text-overflow: ellipsis from
7788
+ the default rule actually applies to the player nick. Inside
7789
+ a flex container, a bare text node becomes an anonymous flex
7790
+ item and the ellipsis property doesn't reach it; inline-block
7791
+ keeps the nick as inline content where ellipsis works.
7792
+ Vertical centering is already handled by the parent .player
7793
+ (display: flex; align-items: center). */
7794
+ display: inline-block;
7700
7795
  height: 28px;
7796
+ line-height: 28px;
7701
7797
  margin: 10px 0;
7702
7798
  padding: 0 4px;
7703
7799
  width: auto;
7800
+ /* Cap at parent width so long player names truncate with the
7801
+ ellipsis from the default rule rather than overflowing the
7802
+ player slot. min-width: 0 lets this flex item shrink past
7803
+ its content's min-content width (otherwise a long unbreakable
7804
+ nick keeps the button wide and ellipsis never kicks in). */
7805
+ max-width: 100%;
7806
+ min-width: 0;
7807
+ box-sizing: border-box;
7808
+ }
7809
+ /* See mobile rule for rationale: allow the flex chain that wraps
7810
+ the player names to shrink past long unbreakable strings. */
7811
+ .netskrafl-container div.playerwrapper,
7812
+ .netskrafl-container div.leftplayer,
7813
+ .netskrafl-container div.rightplayer {
7814
+ min-width: 0;
7704
7815
  }
7705
7816
  .netskrafl-container div.player-btn {
7706
7817
  color: var(--player-btn-color);
@@ -8774,8 +8885,11 @@ div.gatadagsins-board-area.celebrate div.netskrafl-tile.netskrafl-racktile {
8774
8885
  /* Desktop score states */
8775
8886
 
8776
8887
  .netskrafl-container div.gata-dagsins-score.disabled {
8777
- color: var(--middle-shadow);
8778
- border-color: var(--middle-shadow);
8888
+ /* --middle-shadow (#ccc) on the cream legacy background reads as
8889
+ nearly invisible; --blank-tile (#999 default, #777 legacy) gives
8890
+ a more legible contrast while staying a muted gray. */
8891
+ color: var(--blank-tile);
8892
+ border-color: var(--blank-tile);
8779
8893
  border-style: solid;
8780
8894
  border-width: 3px;
8781
8895
  }
@@ -9270,12 +9384,12 @@ div.gatadagsins-board-area.celebrate div.netskrafl-tile.netskrafl-racktile {
9270
9384
 
9271
9385
  .netskrafl-container .mobile-status-item.left {
9272
9386
  align-items: flex-start;
9273
- padding-left: 16px;
9387
+ padding-left: 22px;
9274
9388
  }
9275
9389
 
9276
9390
  .netskrafl-container .mobile-status-item.right {
9277
9391
  align-items: flex-end;
9278
- padding-right: 16px;
9392
+ padding-right: 22px;
9279
9393
  }
9280
9394
 
9281
9395
  .netskrafl-container .mobile-status-item.best-possible {
@@ -9317,7 +9431,7 @@ div.gatadagsins-board-area.celebrate div.netskrafl-tile.netskrafl-racktile {
9317
9431
  flex-direction: row;
9318
9432
  align-items: center;
9319
9433
  justify-content: center;
9320
- flex: 1.5;
9434
+ flex: 1.6;
9321
9435
  position: relative;
9322
9436
  background-color: var(--tab-background);
9323
9437
  border: 1px solid var(--color-border);