@mideind/netskrafl-react 3.4.8 → 3.4.10

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
 
@@ -1037,6 +1037,14 @@ div.netskrafl-container.legacy-colors table.bag-content tr td {
1037
1037
  BASE CONTAINER STYLES
1038
1038
  ========================================================================== */
1039
1039
 
1040
+ .netskrafl-container div.netskrafl,
1041
+ .netskrafl-container div.gatadagsins {
1042
+ /* React wrapper around the Mithril UI. Stretch to the host's
1043
+ allocated slot so `.netskrafl-container` (height: 100%) resolves
1044
+ to a definite size. */
1045
+ height: 100%;
1046
+ }
1047
+
1040
1048
  div.netskrafl-container {
1041
1049
  /* The outer container fills its parent and paints the background */
1042
1050
  width: 100%;
@@ -1053,8 +1061,16 @@ div.netskrafl-container {
1053
1061
  padding-top: 4px;
1054
1062
  /* font-family: var(--primary-font); */
1055
1063
  width: 375px;
1056
- height: calc(100vh - 58px);
1057
- height: calc(100dvh - 58px);
1064
+ /* Fill the host-allocated slot. The previous fixed `100dvh - 58px`
1065
+ guessed the host's chrome height; that worked for hosts whose
1066
+ header was ~58px and broke elsewhere — clipping the bottom of the
1067
+ action buttons on phones like iPhone 13 portrait. Using `100%`
1068
+ lets the host's container chain (e.g. flex `h-full` inside an
1069
+ inset that fills `100vh - header`) own the height contract.
1070
+ The wrapper rules below propagate `height: 100%` from the
1071
+ outermost `.netskrafl` / `.gatadagsins` div so this resolves to a
1072
+ definite size. */
1073
+ height: 100%;
1058
1074
  overflow-x: hidden;
1059
1075
  overflow-y: visible;
1060
1076
  margin: 0 auto;
@@ -1216,10 +1232,15 @@ div.netskrafl-container {
1216
1232
  }
1217
1233
 
1218
1234
  .netskrafl-container .ui-tabs .ui-tabs-nav .ui-tabs-anchor.sp {
1219
- /* Single page version, mobile - 5 tabs must fit in 375px */
1220
- min-width: 64px;
1221
- padding-left: 4px;
1222
- 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;
1223
1244
  }
1224
1245
 
1225
1246
  .netskrafl-container .ui-tabs .ui-tabs-nav .ui-tabs-anchor.sp.narrow {
@@ -1530,6 +1551,19 @@ div.netskrafl-container {
1530
1551
  /* 0.96 = 360/375, scales 375px design to fit 360px viewport */
1531
1552
  transform-origin: center top;
1532
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
+ }
1533
1567
  }
1534
1568
 
1535
1569
  /* --------------------------------------------------------------------------
@@ -6556,21 +6590,20 @@ div.netskrafl-container input[type="checkbox"] {
6556
6590
  top: -4px;
6557
6591
  padding-right: 10px;
6558
6592
  }
6559
- .netskrafl-container div.player-btn {
6560
- display: inline-block;
6561
- position: relative;
6562
- top: 0;
6563
- left: 0;
6564
- padding-top: 4px;
6565
- width: 128px;
6566
- }
6593
+ .netskrafl-container div.player-btn,
6567
6594
  .netskrafl-container div.robot-btn {
6568
6595
  display: inline-block;
6569
6596
  position: relative;
6570
6597
  top: 0;
6571
6598
  left: 0;
6572
6599
  padding-top: 4px;
6573
- 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;
6574
6607
  }
6575
6608
  .netskrafl-container div.player-btn.left,
6576
6609
  .netskrafl-container div.robot-btn.left {
@@ -6584,9 +6617,48 @@ div.netskrafl-container input[type="checkbox"] {
6584
6617
  height: auto;
6585
6618
  display: flex;
6586
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;
6587
6657
  }
6588
6658
  .netskrafl-container div.fairplay {
6589
- top: 18px;
6659
+ /* Nudged down so the indicator does not overlap an ellipsis
6660
+ on a long left-player nick. */
6661
+ top: 26px;
6590
6662
  }
6591
6663
  .netskrafl-container span.fairplay-btn.large {
6592
6664
  /* This is shown in the board header */
@@ -6612,6 +6684,13 @@ div.netskrafl-container input[type="checkbox"] {
6612
6684
  .netskrafl-container div.board-area {
6613
6685
  top: 98px;
6614
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
+ }
6615
6694
  }
6616
6695
 
6617
6696
  /* iPhone 11 or larger: scale the UI to appear larger on the screen */
@@ -6658,10 +6737,12 @@ div.netskrafl-container input[type="checkbox"] {
6658
6737
  display: block;
6659
6738
  }
6660
6739
  .netskrafl-container .ui-tabs .ui-tabs-nav .ui-tabs-anchor.sp {
6661
- /* 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. */
6662
6743
  min-width: 82px;
6663
- padding-left: 12px;
6664
- padding-right: 12px;
6744
+ padding-left: 10px;
6745
+ padding-right: 10px;
6665
6746
  }
6666
6747
  .netskrafl-container div.tabbed-page {
6667
6748
  width: 100%;
@@ -6677,6 +6758,12 @@ div.netskrafl-container input[type="checkbox"] {
6677
6758
  .netskrafl-container p.help-center {
6678
6759
  text-align: center;
6679
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
+ }
6680
6767
  .netskrafl-container div.logowrapper {
6681
6768
  display: none;
6682
6769
  }
@@ -6684,7 +6771,9 @@ div.netskrafl-container input[type="checkbox"] {
6684
6771
  width: 100%;
6685
6772
  }
6686
6773
  .netskrafl-container div.fairplay {
6687
- top: 8px;
6774
+ /* Nudged down so the indicator does not overlap an ellipsis
6775
+ on a long left-player nick. */
6776
+ top: 36px;
6688
6777
  }
6689
6778
  .netskrafl-container div.login-btn-small {
6690
6779
  position: fixed;
@@ -6695,8 +6784,10 @@ div.netskrafl-container input[type="checkbox"] {
6695
6784
  .netskrafl-container div.logo {
6696
6785
  display: block;
6697
6786
  position: absolute;
6698
- top: 8px;
6699
- 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);
6700
6791
  width: 30px;
6701
6792
  z-index: 10;
6702
6793
  }
@@ -6715,6 +6806,17 @@ div.netskrafl-container input[type="checkbox"] {
6715
6806
  transform: scale(clamp(0.88, calc((100dvh - 16px) / 408px), 1));
6716
6807
  transform-origin: right top;
6717
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
+ }
6718
6820
  .netskrafl-container div.rightcol {
6719
6821
  width: 283px;
6720
6822
  top: 8px;
@@ -6788,7 +6890,7 @@ div.netskrafl-container input[type="checkbox"] {
6788
6890
  }
6789
6891
  .netskrafl-container div.player-btn,
6790
6892
  .netskrafl-container div.robot-btn {
6791
- width: 120px;
6893
+ width: auto;
6792
6894
  padding-top: 6px;
6793
6895
  }
6794
6896
  .netskrafl-container div.scoreleft,
@@ -7045,6 +7147,9 @@ div.netskrafl-container input[type="checkbox"] {
7045
7147
  }
7046
7148
  .netskrafl-container div.logo {
7047
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;
7048
7153
  }
7049
7154
  .netskrafl-container div.netskrafl-logo {
7050
7155
  /* Same size as Málstaður logo */
@@ -7679,12 +7784,34 @@ div.netskrafl-container input[type="checkbox"] {
7679
7784
  .netskrafl-container div.player-btn,
7680
7785
  .netskrafl-container div.robot-btn {
7681
7786
  position: relative;
7682
- display: flex;
7683
- 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;
7684
7795
  height: 28px;
7796
+ line-height: 28px;
7685
7797
  margin: 10px 0;
7686
7798
  padding: 0 4px;
7687
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;
7688
7815
  }
7689
7816
  .netskrafl-container div.player-btn {
7690
7817
  color: var(--player-btn-color);
@@ -8758,8 +8885,11 @@ div.gatadagsins-board-area.celebrate div.netskrafl-tile.netskrafl-racktile {
8758
8885
  /* Desktop score states */
8759
8886
 
8760
8887
  .netskrafl-container div.gata-dagsins-score.disabled {
8761
- color: var(--middle-shadow);
8762
- 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);
8763
8893
  border-style: solid;
8764
8894
  border-width: 3px;
8765
8895
  }
@@ -9254,12 +9384,12 @@ div.gatadagsins-board-area.celebrate div.netskrafl-tile.netskrafl-racktile {
9254
9384
 
9255
9385
  .netskrafl-container .mobile-status-item.left {
9256
9386
  align-items: flex-start;
9257
- padding-left: 16px;
9387
+ padding-left: 22px;
9258
9388
  }
9259
9389
 
9260
9390
  .netskrafl-container .mobile-status-item.right {
9261
9391
  align-items: flex-end;
9262
- padding-right: 16px;
9392
+ padding-right: 22px;
9263
9393
  }
9264
9394
 
9265
9395
  .netskrafl-container .mobile-status-item.best-possible {
@@ -9301,7 +9431,7 @@ div.gatadagsins-board-area.celebrate div.netskrafl-tile.netskrafl-racktile {
9301
9431
  flex-direction: row;
9302
9432
  align-items: center;
9303
9433
  justify-content: center;
9304
- flex: 1.5;
9434
+ flex: 1.6;
9305
9435
  position: relative;
9306
9436
  background-color: var(--tab-background);
9307
9437
  border: 1px solid var(--color-border);