@tastic/hud 0.17.0 → 0.18.0
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 +24 -13
- package/dist/{TriggerGauge-2OJOOF6I.mjs → TriggerGauge-GIHKGAZE.mjs} +5 -5
- package/dist/chrome/index.d.mts +20 -16
- package/dist/chrome/index.d.ts +20 -16
- package/dist/chrome/index.js +289 -210
- package/dist/chrome/index.mjs +11 -5
- package/dist/{chunk-AX4GC5KU.mjs → chunk-PUF6TABL.mjs} +60 -24
- package/dist/{chunk-27FIA26F.mjs → chunk-Z2WUUYTE.mjs} +156 -113
- package/dist/guide/index.d.mts +3 -0
- package/dist/guide/index.d.ts +3 -0
- package/dist/guide/index.js +122 -74
- package/dist/guide/index.mjs +16 -8
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +462 -383
- package/dist/index.mjs +12 -6
- package/package.json +10 -10
- package/src/BaseSettingsDialog.tsx +195 -142
- package/src/ConfirmDialog.tsx +8 -15
- package/src/DialogHeader.tsx +72 -0
- package/src/OverlaySurface.tsx +21 -13
- package/src/TriggerGauge.tsx +9 -9
- package/src/chrome/index.ts +3 -1
- package/src/guide/HowToPlayDialog.tsx +23 -9
- package/src/index.ts +3 -1
- package/src/overlayCard.ts +11 -34
- package/src/useCompactDialog.ts +23 -0
package/dist/index.mjs
CHANGED
|
@@ -17,16 +17,19 @@ import {
|
|
|
17
17
|
StatRow,
|
|
18
18
|
StatSection,
|
|
19
19
|
UpdateDialog,
|
|
20
|
-
getStaggeredWordDuration
|
|
21
|
-
|
|
20
|
+
getStaggeredWordDuration,
|
|
21
|
+
useCompactDialog
|
|
22
|
+
} from "./chunk-Z2WUUYTE.mjs";
|
|
22
23
|
import {
|
|
24
|
+
COMPACT_DIALOG_HEIGHT,
|
|
25
|
+
DialogHeader,
|
|
23
26
|
OverlayBackdrop,
|
|
24
27
|
OverlayCard,
|
|
25
|
-
|
|
28
|
+
isCompactDialogHeight,
|
|
26
29
|
overlayActionStyles,
|
|
27
30
|
overlayStyles,
|
|
28
31
|
useOverlayCardColors
|
|
29
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-PUF6TABL.mjs";
|
|
30
33
|
|
|
31
34
|
// src/AchievementUnlockList.tsx
|
|
32
35
|
import { getContrastColor, useAutoPaperTheme } from "@rific/auto-paper";
|
|
@@ -151,7 +154,7 @@ var styles2 = StyleSheet2.create({
|
|
|
151
154
|
|
|
152
155
|
// src/TriggerGaugeHost.tsx
|
|
153
156
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
154
|
-
var getTriggerGauge = () => import("./TriggerGauge-
|
|
157
|
+
var getTriggerGauge = () => import("./TriggerGauge-GIHKGAZE.mjs").then((mod) => ({ default: mod.TriggerGauge }));
|
|
155
158
|
function TriggerGaugeHost(props) {
|
|
156
159
|
return /* @__PURE__ */ jsx3(SkiaGate, { getComponent: getTriggerGauge, componentProps: props });
|
|
157
160
|
}
|
|
@@ -877,11 +880,13 @@ export {
|
|
|
877
880
|
ActivityStatSection,
|
|
878
881
|
BaseSettingsDialog,
|
|
879
882
|
BaseStatsScreen,
|
|
883
|
+
COMPACT_DIALOG_HEIGHT,
|
|
880
884
|
ConfirmDialog,
|
|
881
885
|
ContentGutter,
|
|
882
886
|
ControlSchemePicker,
|
|
883
887
|
CornerActionButtons,
|
|
884
888
|
CornerStatusBadges,
|
|
889
|
+
DialogHeader,
|
|
885
890
|
InlineColorPicker,
|
|
886
891
|
LABELED_DROPDOWN_POPOVER_WIDTH,
|
|
887
892
|
LOCKED_BADGE_COLOR,
|
|
@@ -906,12 +911,13 @@ export {
|
|
|
906
911
|
getCpuDifficultyPopoverId,
|
|
907
912
|
getInlineColorPickerContentSize,
|
|
908
913
|
getLabeledDropdownContentHeight,
|
|
909
|
-
getOverlayCardColors,
|
|
910
914
|
getStaggeredWordDuration,
|
|
915
|
+
isCompactDialogHeight,
|
|
911
916
|
loadSkiaWeb,
|
|
912
917
|
overlayActionStyles,
|
|
913
918
|
overlayStyles,
|
|
914
919
|
useAutoAlign,
|
|
920
|
+
useCompactDialog,
|
|
915
921
|
useOverlayCardColors,
|
|
916
922
|
usePopoverHost,
|
|
917
923
|
useQuitConfirmation,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tastic/hud",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Visual component kit for local-multiplayer React Native games: inline (non-modal) popovers, dropdowns, color pickers, gauges, ready buttons, and dialogs, built to stay scoped to one player's own zone on a shared screen",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -87,14 +87,14 @@
|
|
|
87
87
|
"@infinitetoken/eslint-config": "^0.2.0",
|
|
88
88
|
"@infinitetoken/jest-config": "^0.4.0",
|
|
89
89
|
"@infinitetoken/tsconfig": "^0.5.0",
|
|
90
|
-
"@rific/auto-paper": "^0.
|
|
91
|
-
"@rific/core": "^0.
|
|
92
|
-
"@rific/feedback-press": "^0.
|
|
90
|
+
"@rific/auto-paper": "^0.13.0",
|
|
91
|
+
"@rific/core": "^0.3.0",
|
|
92
|
+
"@rific/feedback-press": "^0.13.0",
|
|
93
93
|
"@rific/updater": "^0.6.0",
|
|
94
94
|
"@shopify/react-native-skia": "^2.6.2",
|
|
95
|
-
"@tastic/achievements": "^0.
|
|
96
|
-
"@tastic/core": "^0.
|
|
97
|
-
"@tastic/profile": "^0.
|
|
95
|
+
"@tastic/achievements": "^0.5.0",
|
|
96
|
+
"@tastic/core": "^0.13.0",
|
|
97
|
+
"@tastic/profile": "^0.12.0",
|
|
98
98
|
"@tastic/split-screen": "^0.5.0",
|
|
99
99
|
"@testing-library/dom": "^10.4.1",
|
|
100
100
|
"@testing-library/react": "^16.3.2",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"prettier": "^3.8.3",
|
|
109
109
|
"react": "^19.0.0",
|
|
110
110
|
"react-dom": "^19.0.0",
|
|
111
|
-
"react-native": "^0.
|
|
111
|
+
"react-native": "^0.86.3",
|
|
112
112
|
"react-native-paper": "^5.15.2",
|
|
113
113
|
"react-native-reanimated": "^4.5.1",
|
|
114
114
|
"react-native-safe-area-context": "^5.10.0",
|
|
@@ -116,10 +116,10 @@
|
|
|
116
116
|
"typescript": "^6.0.3"
|
|
117
117
|
},
|
|
118
118
|
"peerDependencies": {
|
|
119
|
-
"@rific/auto-paper": "
|
|
119
|
+
"@rific/auto-paper": "^0.13.0",
|
|
120
120
|
"@rific/feedback-press": ">=0.10.2",
|
|
121
121
|
"@rific/updater": ">=0.4.0",
|
|
122
|
-
"@shopify/react-native-skia": ">=
|
|
122
|
+
"@shopify/react-native-skia": ">=2.6.0",
|
|
123
123
|
"@tastic/achievements": ">=0.2.0",
|
|
124
124
|
"@tastic/core": ">=0.12.0",
|
|
125
125
|
"@tastic/profile": ">=0.4.0",
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { AutoAppearancePicker, Dialog, useAutoPaperTheme, useBlurModule, useThemeSettings } from '@rific/auto-paper'
|
|
1
|
+
import { AutoAppearancePicker, Dialog, FadingScrollView, useAutoPaperTheme, useBlurModule, useThemeSettings } from '@rific/auto-paper'
|
|
2
2
|
import { Button, SoundContext, TouchableRipple, useHapticSettings, useSoundSettings, useVibration } from '@rific/feedback-press'
|
|
3
3
|
import { useUpdater } from '@rific/updater'
|
|
4
|
-
import { useIsTouchPrimaryDevice, useRotation, type ViewRotation } from '@tastic/core'
|
|
4
|
+
import { rotateInsets, useIsTouchPrimaryDevice, useRotation, type ViewRotation } from '@tastic/core'
|
|
5
5
|
import { ReactNode, useContext, useState } from 'react'
|
|
6
|
-
import { Platform,
|
|
6
|
+
import { Platform, StyleSheet, useWindowDimensions, View } from 'react-native'
|
|
7
7
|
import { Icon, Portal, Text } from 'react-native-paper'
|
|
8
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
8
9
|
|
|
9
10
|
import { ConfirmDialog } from './ConfirmDialog'
|
|
11
|
+
import { DialogHeader, isCompactDialogHeight } from './DialogHeader'
|
|
12
|
+
import { overlayActionStyles, overlayStyles } from './overlayCard'
|
|
10
13
|
import { RotatedFrame } from './RotatedFrame'
|
|
11
14
|
import { useUpdateConfirmBridge } from './useUpdateConfirmBridge'
|
|
12
15
|
|
|
@@ -91,6 +94,18 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
|
|
|
91
94
|
const { colors } = useAutoPaperTheme()
|
|
92
95
|
const ambientRotation = useRotation()
|
|
93
96
|
const rotation = rotationOverride ?? ambientRotation
|
|
97
|
+
// The sheet lays out in a RotatedFrame turned by `rotation` (below), so its height is the window's
|
|
98
|
+
// width when turned a quarter: the same compact rule as every other dialog's header.
|
|
99
|
+
const window = useWindowDimensions()
|
|
100
|
+
const turned = rotation === 90 || rotation === -90
|
|
101
|
+
const compactHeader = isCompactDialogHeight(turned ? window.width : window.height)
|
|
102
|
+
// The overlay cards' width (ConfirmDialog, the guide, every app's result card): 24pt from the
|
|
103
|
+
// frame's edges, never under a notch, capped at their 360 (overlayStyles.card). auto-paper's Dialog
|
|
104
|
+
// would give the sheet a 32pt margin and a 400 cap, 16pt narrower than a card on a phone, which
|
|
105
|
+
// showed when Settings and a confirm opened one after the other. The card is centered, so the
|
|
106
|
+
// Dialog's own 32pt padding is simply overhung.
|
|
107
|
+
const insets = rotateInsets(useSafeAreaInsets(), rotation)
|
|
108
|
+
const sheetWidth = Math.min(overlayStyles.card.maxWidth, (turned ? window.height : window.width) - 2 * Math.max(insets.left, insets.right, SHEET_GUTTER))
|
|
94
109
|
// Also gates the Lock Orientation row below (alongside showLockOrientation itself) - true
|
|
95
110
|
// unconditionally on native (see the hook's own doc), so this only actually excludes a
|
|
96
111
|
// desktop/laptop browser, where a mouse-driven window has no physical orientation to lock
|
|
@@ -159,186 +174,205 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
|
|
|
159
174
|
return (
|
|
160
175
|
<Portal>
|
|
161
176
|
<RotatedFrame rotation={rotation} rotatedSafeArea portalHost passThrough>
|
|
162
|
-
<Dialog visible={visible} onDismiss={onDismiss} style={styles.dialog}>
|
|
163
|
-
<Dialog.Title>Settings</Dialog.Title>
|
|
177
|
+
<Dialog visible={visible} onDismiss={onDismiss} style={[styles.dialog, { width: sheetWidth }]}>
|
|
164
178
|
{/* react-native-paper's Dialog.ScrollArea has a fixed 24px marginBottom baked in (meant to
|
|
165
|
-
reserve room for a Dialog.Actions row below it) - overridden to 0
|
|
166
|
-
|
|
179
|
+
reserve room for a Dialog.Actions row below it) - overridden to 0: the Done row below
|
|
180
|
+
carries its own spacing. It is the sheet's first child, so it runs to the card's top edge. */}
|
|
167
181
|
<Dialog.ScrollArea style={styles.scrollArea}>
|
|
168
|
-
<
|
|
169
|
-
{/*
|
|
182
|
+
<FadingScrollView contentContainerStyle={styles.scrollContent} showsVerticalScrollIndicator={false}>
|
|
183
|
+
{/* The fleet's one dialog header and Done button (DialogHeader, overlayActionStyles' single
|
|
184
|
+
button), so the sheet reads as one family with ConfirmDialog, the guide and every app's
|
|
185
|
+
result cards (Jay, 2026-10-15). The header scrolls away with the rows, so a long sheet
|
|
186
|
+
gives them the whole card once scrolled; Done stays pinned below. Tapping outside still
|
|
187
|
+
closes it too. */}
|
|
188
|
+
<DialogHeader icon='cog' title='Settings' compact={compactHeader} />
|
|
189
|
+
<View style={styles.content}>
|
|
190
|
+
{/* Grouped tightly together (styles.toggleGroup's small internal gap, not the 24px gap
|
|
170
191
|
between sections below) - same bare-row shape for all of them, no segmented control
|
|
171
192
|
and no section heading (each row's own label already says what it is). Haptics only
|
|
172
193
|
joins on native - there's nothing for it to control on web. */}
|
|
173
|
-
|
|
174
|
-
|
|
194
|
+
<View style={styles.toggleGroup}>
|
|
195
|
+
{/* Omitted entirely for a portrait-only game (no lockOrientation/onLockOrientationChange
|
|
175
196
|
passed at all - nothing to lock), and hidden (not just disabled) on a desktop/laptop
|
|
176
197
|
browser even when they are - see isTouchPrimary's own doc above. Still shown on
|
|
177
198
|
native and on a touch-capable mobile browser, where a rotated window really does
|
|
178
199
|
reflect a rotated device. */}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
200
|
+
{showLockOrientation && (
|
|
201
|
+
<TouchableRipple onPress={() => onLockOrientationChange!(!lockOrientation)} style={styles.toggleButton} accessibilityLabel={`Lock orientation ${lockOrientation ? 'on' : 'off'}`}>
|
|
202
|
+
<View style={styles.toggleContent}>
|
|
203
|
+
<SettingIcon source={lockOrientation ? 'lock' : 'lock-open-variant-outline'} color={lockOrientation ? colors.tertiary : colors.onSurfaceVariant} containerColor={lockOrientation ? colors.tertiaryContainer : colors.surfaceVariant} />
|
|
204
|
+
<View style={styles.flexShrink}>
|
|
205
|
+
<Text variant='bodyLarge' style={{ color: colors.onSurface }}>
|
|
206
|
+
Lock Orientation
|
|
207
|
+
</Text>
|
|
208
|
+
<Text variant='bodySmall' numberOfLines={1} style={{ color: colors.onSurfaceVariant }}>
|
|
209
|
+
Pin current layout
|
|
210
|
+
</Text>
|
|
211
|
+
</View>
|
|
190
212
|
</View>
|
|
191
|
-
</
|
|
192
|
-
|
|
193
|
-
)}
|
|
213
|
+
</TouchableRipple>
|
|
214
|
+
)}
|
|
194
215
|
|
|
195
|
-
|
|
216
|
+
{/* iOS only - mirrors into UserDefaults for @tastic/edge-guard's own config plugin
|
|
196
217
|
swizzle to read. Guards both edges - the top (Notification Center/Control Center)
|
|
197
218
|
and the bottom (home-indicator swipe, incidentally Reachability too), per that
|
|
198
219
|
package's own doc. Off by default: a deliberate opt-in for whoever's actually hit an
|
|
199
220
|
accidental-swipe-near-an-edge problem mid-game and come looking for a fix, not a
|
|
200
221
|
surprise every player gets by default. */}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
222
|
+
{showEdgeGuard && (
|
|
223
|
+
<TouchableRipple onPress={() => onDeferBottomEdgeGestures!(!deferBottomEdgeGestures)} style={styles.toggleButton} accessibilityLabel={`Edge guard ${deferBottomEdgeGestures ? 'on' : 'off'}`}>
|
|
224
|
+
<View style={styles.toggleContent}>
|
|
225
|
+
<SettingIcon source={deferBottomEdgeGestures ? 'shield-check-outline' : 'shield-off-outline'} color={deferBottomEdgeGestures ? colors.tertiary : colors.onSurfaceVariant} containerColor={deferBottomEdgeGestures ? colors.tertiaryContainer : colors.surfaceVariant} />
|
|
226
|
+
<View style={styles.flexShrink}>
|
|
227
|
+
<Text variant='bodyLarge' style={{ color: colors.onSurface }}>
|
|
228
|
+
Edge Guard
|
|
229
|
+
</Text>
|
|
230
|
+
<Text variant='bodySmall' numberOfLines={1} style={{ color: colors.onSurfaceVariant }}>
|
|
231
|
+
Require second swipe
|
|
232
|
+
</Text>
|
|
233
|
+
</View>
|
|
212
234
|
</View>
|
|
213
|
-
</
|
|
214
|
-
|
|
215
|
-
)}
|
|
235
|
+
</TouchableRipple>
|
|
236
|
+
)}
|
|
216
237
|
|
|
217
|
-
|
|
238
|
+
{/* Side by side, not stacked - neither needs a full row's width (single-line label,
|
|
218
239
|
no description under it the way Lock Orientation has), so sharing one row reads
|
|
219
240
|
just as clearly and takes half the vertical space. toggleRow carries the same
|
|
220
241
|
edge-alignment negative margin toggleButton normally carries itself; the buttons
|
|
221
242
|
inside it use toggleButtonInRow (flex: 1) instead so the two don't double up on it.
|
|
222
243
|
The row itself only renders when at least one of the two will - an empty flex row
|
|
223
244
|
still eats its own gap/margin otherwise. */}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
+
{(showSound || showHaptics) && (
|
|
246
|
+
<View style={styles.toggleRow}>
|
|
247
|
+
{showSound && (
|
|
248
|
+
<TouchableRipple
|
|
249
|
+
soundDisabled
|
|
250
|
+
onPress={() => {
|
|
251
|
+
const enabled = !soundSettings.enabled
|
|
252
|
+
setSoundSettings({ enabled })
|
|
253
|
+
if (enabled) sound.selection?.()
|
|
254
|
+
}}
|
|
255
|
+
style={styles.toggleButtonInRow}
|
|
256
|
+
accessibilityLabel={`Sound ${soundSettings.enabled ? 'on' : 'off'}`}
|
|
257
|
+
>
|
|
258
|
+
<View style={styles.toggleContent}>
|
|
259
|
+
<SettingIcon source={soundSettings.enabled ? 'volume-high' : 'volume-off'} color={soundSettings.enabled ? colors.tertiary : colors.onSurfaceVariant} containerColor={soundSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant} />
|
|
260
|
+
<Text variant='bodyLarge' style={{ color: colors.onSurface }}>
|
|
261
|
+
Sound
|
|
262
|
+
</Text>
|
|
263
|
+
</View>
|
|
264
|
+
</TouchableRipple>
|
|
265
|
+
)}
|
|
245
266
|
|
|
246
|
-
|
|
267
|
+
{/* Already native-only (see showHaptics's own definition) - hideHaptics can hide
|
|
247
268
|
it there too, for a game that's deliberately silent on vibration. */}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
+
{showHaptics && (
|
|
270
|
+
<TouchableRipple
|
|
271
|
+
hapticDisabled
|
|
272
|
+
onPress={() => {
|
|
273
|
+
const vibrate = !hapticSettings.vibrate
|
|
274
|
+
setHapticSettings({ vibrate })
|
|
275
|
+
if (vibrate) forceHapticFeedback()
|
|
276
|
+
}}
|
|
277
|
+
style={styles.toggleButtonInRow}
|
|
278
|
+
accessibilityLabel={`Haptics ${hapticSettings.vibrate ? 'on' : 'off'}`}
|
|
279
|
+
>
|
|
280
|
+
<View style={styles.toggleContent}>
|
|
281
|
+
<SettingIcon source={hapticSettings.vibrate ? 'vibrate' : 'vibrate-off'} color={hapticSettings.vibrate ? colors.tertiary : colors.onSurfaceVariant} containerColor={hapticSettings.vibrate ? colors.tertiaryContainer : colors.surfaceVariant} />
|
|
282
|
+
<Text variant='bodyLarge' style={{ color: colors.onSurface }}>
|
|
283
|
+
Haptics
|
|
284
|
+
</Text>
|
|
285
|
+
</View>
|
|
286
|
+
</TouchableRipple>
|
|
287
|
+
)}
|
|
288
|
+
</View>
|
|
289
|
+
)}
|
|
269
290
|
|
|
270
|
-
|
|
291
|
+
{/* Not a toggle, but it reads like one of the rows above and belongs with them rather than
|
|
271
292
|
down in `children` (which sits below Appearance) - this is the "replay the intro"
|
|
272
293
|
entry point every game's own how-to-play flow shares. Dismisses this dialog first so
|
|
273
294
|
the guide's own overlay never opens on top of a still-visible Settings card. */}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
295
|
+
{onShowHowToPlay && (
|
|
296
|
+
<TouchableRipple
|
|
297
|
+
onPress={() => {
|
|
298
|
+
onDismiss()
|
|
299
|
+
onShowHowToPlay()
|
|
300
|
+
}}
|
|
301
|
+
style={styles.toggleButton}
|
|
302
|
+
accessibilityLabel='How to Play'
|
|
303
|
+
>
|
|
304
|
+
<View style={styles.toggleContent}>
|
|
305
|
+
<SettingIcon source='help-circle-outline' color={colors.secondary} containerColor={colors.secondaryContainer} />
|
|
306
|
+
<View style={styles.flexShrink}>
|
|
307
|
+
<Text variant='bodyLarge' style={{ color: colors.onSurface }}>
|
|
308
|
+
How to Play
|
|
309
|
+
</Text>
|
|
310
|
+
<Text variant='bodySmall' numberOfLines={1} style={{ color: colors.onSurfaceVariant }}>
|
|
311
|
+
Replay quick intro
|
|
312
|
+
</Text>
|
|
313
|
+
</View>
|
|
292
314
|
</View>
|
|
293
|
-
</
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
</View>
|
|
315
|
+
</TouchableRipple>
|
|
316
|
+
)}
|
|
317
|
+
</View>
|
|
297
318
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
319
|
+
{showAppearance && (
|
|
320
|
+
<View style={styles.section}>
|
|
321
|
+
<Text variant='labelMedium' style={[styles.sectionLabel, { color: colors.onSurfaceVariant }]}>
|
|
322
|
+
APPEARANCE
|
|
323
|
+
</Text>
|
|
324
|
+
<AutoAppearancePicker showLabels={false} />
|
|
325
|
+
{/* Blur (Jay, 2026-10-15): one fleet-wide switch for auto-paper's own `blur` setting,
|
|
305
326
|
which every blur-capable surface follows on its own (this sheet, and the
|
|
306
327
|
OverlayCard behind ConfirmDialog, UpdateDialog and the guide). Off, or wherever
|
|
307
328
|
blur can't happen, those surfaces paint the theme's raised surface instead. So the
|
|
308
329
|
row only shows where turning it on would change something: not on Android
|
|
309
330
|
(auto-paper's BlurView needs a blur target there, which these cards don't have) and
|
|
310
331
|
not in an app that never handed expo-blur to auto-paper's Provider. */}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
332
|
+
{showBlur && (
|
|
333
|
+
<TouchableRipple onPress={() => setThemeSettings({ blur: !themeSettings.blur })} style={styles.toggleButton} accessibilityLabel={`Blur ${themeSettings.blur ? 'on' : 'off'}`}>
|
|
334
|
+
<View style={styles.toggleContent}>
|
|
335
|
+
<SettingIcon source={themeSettings.blur ? 'blur' : 'blur-off'} color={themeSettings.blur ? colors.tertiary : colors.onSurfaceVariant} containerColor={themeSettings.blur ? colors.tertiaryContainer : colors.surfaceVariant} />
|
|
336
|
+
{/* The one-word label alone didn't say what blurs (Jay, 2026-10-15). One line
|
|
337
|
+
even on a 360pt phone (216pt of room). */}
|
|
338
|
+
<View style={styles.flexShrink}>
|
|
339
|
+
<Text variant='bodyLarge' style={{ color: colors.onSurface }}>
|
|
340
|
+
Blur
|
|
341
|
+
</Text>
|
|
342
|
+
<Text variant='bodySmall' numberOfLines={1} style={{ color: colors.onSurfaceVariant }}>
|
|
343
|
+
Frost behind dialogs
|
|
344
|
+
</Text>
|
|
345
|
+
</View>
|
|
346
|
+
</View>
|
|
347
|
+
</TouchableRipple>
|
|
348
|
+
)}
|
|
349
|
+
</View>
|
|
350
|
+
)}
|
|
323
351
|
|
|
324
|
-
|
|
352
|
+
{children}
|
|
325
353
|
|
|
326
|
-
|
|
354
|
+
{/* Native only (a web build has no OTA update concept at all, desktop or mobile browser
|
|
327
355
|
alike) - hideUpdateCheck can hide it there too, for a native app that isn't wired up
|
|
328
356
|
for EAS/OTA updates at all. */}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
357
|
+
{showUpdateCheck && (
|
|
358
|
+
<View style={styles.section}>
|
|
359
|
+
<Text variant='labelMedium' style={[styles.sectionLabel, { color: colors.onSurfaceVariant }]}>
|
|
360
|
+
VERSION {version}
|
|
361
|
+
{updateReady ? ' · UPDATE READY' : ''}
|
|
362
|
+
</Text>
|
|
363
|
+
<Button mode='outlined' onPress={check} loading={checking} disabled={checking}>
|
|
364
|
+
Check for Updates
|
|
365
|
+
</Button>
|
|
366
|
+
</View>
|
|
367
|
+
)}
|
|
368
|
+
</View>
|
|
369
|
+
</FadingScrollView>
|
|
341
370
|
</Dialog.ScrollArea>
|
|
371
|
+
<View style={[overlayActionStyles.actions, styles.footer]}>
|
|
372
|
+
<Button mode='contained' onPress={onDismiss} style={overlayActionStyles.actionButtonSingle} buttonColor={colors.primary} textColor={colors.onPrimary}>
|
|
373
|
+
Done
|
|
374
|
+
</Button>
|
|
375
|
+
</View>
|
|
342
376
|
</Dialog>
|
|
343
377
|
<ConfirmDialog visible={!!updateManifest} title='Update Available' message={updateManifestDate ? `Released ${updateManifestDate.toLocaleDateString()} at ${updateManifestDate.toLocaleTimeString()}` : ''} confirmLabel='Restart' cancelLabel='Later' icon='update' rotation={0} onConfirm={() => respondToUpdateConfirm(true)} onCancel={() => respondToUpdateConfirm(false)} />
|
|
344
378
|
<ConfirmDialog visible={!!infoMessage} title={infoMessage?.title ?? ''} message={infoMessage?.message ?? ''} confirmLabel='OK' icon='information-outline' rotation={0} onConfirm={() => setInfoMessage(null)} />
|
|
@@ -347,10 +381,13 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
|
|
|
347
381
|
)
|
|
348
382
|
}
|
|
349
383
|
|
|
384
|
+
// The backdrop gutter every overlay card keeps from the screen's edges.
|
|
385
|
+
const SHEET_GUTTER = 24
|
|
386
|
+
|
|
350
387
|
const styles = StyleSheet.create({
|
|
388
|
+
// The rows' sections, 24pt apart.
|
|
351
389
|
content: {
|
|
352
|
-
gap: 24
|
|
353
|
-
paddingVertical: 20
|
|
390
|
+
gap: 24
|
|
354
391
|
},
|
|
355
392
|
// Caps the card so it never grows past the screen - without this the dialog just keeps
|
|
356
393
|
// growing to fit its content and the overflow gets clipped by the screen edge, which is
|
|
@@ -362,6 +399,11 @@ const styles = StyleSheet.create({
|
|
|
362
399
|
flexShrink: {
|
|
363
400
|
flexShrink: 1
|
|
364
401
|
},
|
|
402
|
+
// The overlay cards' 24pt padding, as the sheet's own insets (its scroll area pads 24 sideways).
|
|
403
|
+
footer: {
|
|
404
|
+
paddingBottom: 24,
|
|
405
|
+
paddingHorizontal: 24
|
|
406
|
+
},
|
|
365
407
|
iconBadge: {
|
|
366
408
|
alignItems: 'center',
|
|
367
409
|
borderRadius: 10,
|
|
@@ -369,9 +411,20 @@ const styles = StyleSheet.create({
|
|
|
369
411
|
justifyContent: 'center',
|
|
370
412
|
width: 36
|
|
371
413
|
},
|
|
414
|
+
// No hairline dividers above and below the scroll area (Paper's MD2-era borders): none of the
|
|
415
|
+
// fleet's other dialogs draws one, so the sheet matches them (Jay, 2026-10-15).
|
|
372
416
|
scrollArea: {
|
|
417
|
+
borderBottomWidth: 0,
|
|
418
|
+
borderTopWidth: 0,
|
|
373
419
|
marginBottom: 0
|
|
374
420
|
},
|
|
421
|
+
// The header and the rows scroll together: the card's 24pt inset above the header, the overlay
|
|
422
|
+
// cards' 16pt gap between it and the rows, and 16pt below the last row before the Done row.
|
|
423
|
+
scrollContent: {
|
|
424
|
+
gap: 16,
|
|
425
|
+
paddingBottom: 16,
|
|
426
|
+
paddingTop: 24
|
|
427
|
+
},
|
|
375
428
|
section: {
|
|
376
429
|
gap: 12
|
|
377
430
|
},
|
package/src/ConfirmDialog.tsx
CHANGED
|
@@ -3,10 +3,12 @@ import { Button } from '@rific/feedback-press'
|
|
|
3
3
|
import { toRotationStyle, type ViewRotation } from '@tastic/core'
|
|
4
4
|
import { type ReactNode } from 'react'
|
|
5
5
|
import { StyleSheet, View } from 'react-native'
|
|
6
|
-
import {
|
|
6
|
+
import { Portal, Text } from 'react-native-paper'
|
|
7
7
|
|
|
8
|
+
import { DialogHeader } from './DialogHeader'
|
|
8
9
|
import { overlayActionStyles } from './overlayCard'
|
|
9
10
|
import { OverlayBackdrop, OverlayCard, useOverlayCardColors } from './OverlaySurface'
|
|
11
|
+
import { useCompactDialog } from './useCompactDialog'
|
|
10
12
|
|
|
11
13
|
export interface ConfirmDialogProps {
|
|
12
14
|
visible: boolean
|
|
@@ -20,8 +22,8 @@ export interface ConfirmDialogProps {
|
|
|
20
22
|
// BaseSettingsDialog's own rotation prop; defaults to 0 for a caller with no per-player zone to
|
|
21
23
|
// match (a portrait-only or single-player game has nothing to stay consistent with).
|
|
22
24
|
rotation?: ViewRotation
|
|
23
|
-
// Any react-native-paper Icon `source` name, shown
|
|
24
|
-
// question mark, since this component has no way to know why a caller is asking.
|
|
25
|
+
// Any react-native-paper Icon `source` name, shown in the header (DialogHeader) - defaults to a
|
|
26
|
+
// plain question mark, since this component has no way to know why a caller is asking.
|
|
25
27
|
icon?: string
|
|
26
28
|
// Whether the confirm button reads as destructive (the fleet's shared `danger` semantic role)
|
|
27
29
|
// instead of the default primary-colored affirmative action.
|
|
@@ -40,6 +42,7 @@ export interface ConfirmDialogProps {
|
|
|
40
42
|
export function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation = 0, icon = 'help-circle-outline', destructive = false }: ConfirmDialogProps) {
|
|
41
43
|
const { colors } = useAutoPaperTheme()
|
|
42
44
|
const { fg } = useOverlayCardColors()
|
|
45
|
+
const { compact, onLayout } = useCompactDialog(rotation)
|
|
43
46
|
if (!visible) return null
|
|
44
47
|
|
|
45
48
|
const accent = destructive ? colors.danger : colors.primary
|
|
@@ -48,12 +51,9 @@ export function ConfirmDialog({ visible, title, message, confirmLabel, cancelLab
|
|
|
48
51
|
|
|
49
52
|
return (
|
|
50
53
|
<Portal>
|
|
51
|
-
<OverlayBackdrop style={styles.overlay}>
|
|
54
|
+
<OverlayBackdrop style={styles.overlay} onLayout={onLayout}>
|
|
52
55
|
<OverlayCard style={[styles.card, toRotationStyle(rotation)]}>
|
|
53
|
-
<
|
|
54
|
-
<Text variant='headlineLarge' style={[styles.title, { color: accent }]}>
|
|
55
|
-
{title}
|
|
56
|
-
</Text>
|
|
56
|
+
<DialogHeader icon={icon} title={title} color={accent} compact={compact} />
|
|
57
57
|
<Text variant='bodyLarge' style={[styles.body, { color: fg }]}>
|
|
58
58
|
{message}
|
|
59
59
|
</Text>
|
|
@@ -91,12 +91,5 @@ const styles = StyleSheet.create({
|
|
|
91
91
|
// screen narrower than maxWidth's own 360.
|
|
92
92
|
overlay: {
|
|
93
93
|
paddingHorizontal: 24
|
|
94
|
-
},
|
|
95
|
-
// textAlign is load-bearing, not decorative, once `title` wraps to more than one line - centered
|
|
96
|
-
// within card's own alignItems: 'center' only centers the Text's block as a whole; each wrapped
|
|
97
|
-
// line inside that block still defaults to left-aligned without this.
|
|
98
|
-
title: {
|
|
99
|
-
fontWeight: 'bold',
|
|
100
|
-
textAlign: 'center'
|
|
101
94
|
}
|
|
102
95
|
})
|