@mideind/netskrafl-react 3.4.9 → 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 +53 -4
- package/dist/esm/css/netskrafl.css +142 -28
- package/package.json +1 -1
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
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
6680
|
-
padding-right:
|
|
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
|
-
|
|
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
|
-
|
|
6715
|
-
|
|
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:
|
|
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
|
-
|
|
7699
|
-
|
|
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
|
-
|
|
8778
|
-
|
|
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:
|
|
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:
|
|
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.
|
|
9434
|
+
flex: 1.6;
|
|
9321
9435
|
position: relative;
|
|
9322
9436
|
background-color: var(--tab-background);
|
|
9323
9437
|
border: 1px solid var(--color-border);
|