@tastic/hud 0.8.2 → 0.8.4
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/dist/index.d.mts +44 -8
- package/dist/index.d.ts +44 -8
- package/dist/index.js +308 -277
- package/dist/index.mjs +228 -200
- package/package.json +5 -4
- package/src/BaseSettingsDialog.tsx +16 -51
- package/src/BaseStatsScreen.tsx +2 -2
- package/src/ConfirmDialog.tsx +27 -37
- package/src/UpdateDialog.tsx +28 -0
- package/src/index.ts +2 -0
- package/src/overlayCard.ts +48 -0
- package/src/useUpdateConfirmBridge.ts +30 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tastic/hud",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
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",
|
|
@@ -73,11 +73,12 @@
|
|
|
73
73
|
"@infinitetoken/eslint-config": "^0.2.0",
|
|
74
74
|
"@infinitetoken/jest-config": "^0.3.0",
|
|
75
75
|
"@infinitetoken/tsconfig": "^0.4.1",
|
|
76
|
-
"@rific/auto-paper": "^0.
|
|
77
|
-
"@rific/
|
|
76
|
+
"@rific/auto-paper": "^0.11.0",
|
|
77
|
+
"@rific/core": "^0.1.1",
|
|
78
|
+
"@rific/feedback-press": "^0.11.1",
|
|
78
79
|
"@rific/updater": "^0.6.0",
|
|
79
80
|
"@shopify/react-native-skia": "^2.6.2",
|
|
80
|
-
"@tastic/core": "^0.
|
|
81
|
+
"@tastic/core": "^0.4.1",
|
|
81
82
|
"@testing-library/dom": "^10.4.1",
|
|
82
83
|
"@testing-library/react": "^16.3.2",
|
|
83
84
|
"@types/jest": "^30.0.0",
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { AutoAppearancePicker, Dialog, useAutoPaperTheme } 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 } from '@tastic/core'
|
|
4
|
+
import { toRotationStyle, useIsTouchPrimaryDevice, useRotation, type ViewRotation } from '@tastic/core'
|
|
5
5
|
import { ReactNode, useContext, useState } from 'react'
|
|
6
6
|
import { Platform, ScrollView, StyleSheet, View } from 'react-native'
|
|
7
|
-
import { Icon,
|
|
7
|
+
import { Icon, Text } from 'react-native-paper'
|
|
8
|
+
|
|
9
|
+
import { ConfirmDialog } from './ConfirmDialog'
|
|
10
|
+
import { useUpdateConfirmBridge } from './useUpdateConfirmBridge'
|
|
8
11
|
|
|
9
12
|
interface SettingIconProps {
|
|
10
13
|
source: string
|
|
@@ -31,7 +34,7 @@ export interface BaseSettingsDialogProps {
|
|
|
31
34
|
// dialog), so it just rotates its own content in place. Defaults to a live ambient read via
|
|
32
35
|
// useRotation() when omitted; pass this only when a caller's own reading needs to differ from the
|
|
33
36
|
// ambient one (rare — most callers should simply omit it).
|
|
34
|
-
rotation?:
|
|
37
|
+
rotation?: ViewRotation
|
|
35
38
|
// Each app's own OTA version (e.g. release.otaVersion) — not something this package can read
|
|
36
39
|
// itself, since every consuming app tracks its own release.ts independently. Accepts a number too
|
|
37
40
|
// since that's what release.otaVersion actually is in most apps (a bare incrementing counter, not
|
|
@@ -78,7 +81,7 @@ export interface BaseSettingsDialogProps {
|
|
|
78
81
|
// backup flow, or anything else genuinely specific to it) renders those below Appearance, not by
|
|
79
82
|
// forking this component.
|
|
80
83
|
export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, children }: BaseSettingsDialogProps) {
|
|
81
|
-
const {
|
|
84
|
+
const { colors } = useAutoPaperTheme()
|
|
82
85
|
const ambientRotation = useRotation()
|
|
83
86
|
const rotation = rotationOverride ?? ambientRotation
|
|
84
87
|
// Also gates the Lock Orientation row below (alongside showLockOrientation itself) — true
|
|
@@ -103,13 +106,18 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
|
|
|
103
106
|
// autoCheck: false — every consuming app's own root layout already runs the background check via
|
|
104
107
|
// its own useUpdater() instance; a second instance with autoCheck's default (true) would set up a
|
|
105
108
|
// second AppState listener and double every foreground-resume update check. This instance only
|
|
106
|
-
// ever checks on an explicit tap of the button below.
|
|
109
|
+
// ever checks on an explicit tap of the button below. Deliberately its own useUpdater() instance
|
|
110
|
+
// rather than rendering UpdateDialog directly here, for the same reason — UpdateDialog owns a
|
|
111
|
+
// second AppState listener of its own.
|
|
112
|
+
const { manifest: updateManifest, onConfirm: onUpdateConfirm, respond: respondToUpdateConfirm } = useUpdateConfirmBridge()
|
|
107
113
|
const { check, checking, updateReady } = useUpdater({
|
|
108
114
|
autoCheck: false,
|
|
109
115
|
autoPrompt: false,
|
|
116
|
+
onConfirm: onUpdateConfirm,
|
|
110
117
|
onError: onUpdateError,
|
|
111
118
|
onInfo: (title, message) => setInfoMessage({ title, message })
|
|
112
119
|
})
|
|
120
|
+
const updateManifestDate = updateManifest ? new Date(updateManifest.createdAt) : null
|
|
113
121
|
|
|
114
122
|
const showLockOrientation = isTouchPrimary && lockOrientation !== undefined && onLockOrientationChange !== undefined
|
|
115
123
|
const showEdgeGuard = Platform.OS === 'ios' && deferBottomEdgeGestures !== undefined && onDeferBottomEdgeGestures !== undefined
|
|
@@ -118,15 +126,9 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
|
|
|
118
126
|
const showAppearance = !hideAppearance
|
|
119
127
|
const showUpdateCheck = Platform.OS !== 'web' && !hideUpdateCheck
|
|
120
128
|
|
|
121
|
-
// High-contrast retro look: literal black/white by appearance, not auto-paper's own (slightly
|
|
122
|
-
// tinted) background role.
|
|
123
|
-
const fg = dark ? '#FFFFFF' : '#000000'
|
|
124
|
-
const cardBg = dark ? '#111111' : '#F2F2F2'
|
|
125
|
-
const cardBorder = dark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.2)'
|
|
126
|
-
|
|
127
129
|
return (
|
|
128
130
|
<>
|
|
129
|
-
<Dialog visible={visible} onDismiss={onDismiss} style={[styles.dialog, rotation
|
|
131
|
+
<Dialog visible={visible} onDismiss={onDismiss} style={[styles.dialog, toRotationStyle(rotation)]}>
|
|
130
132
|
<Dialog.Title>Settings</Dialog.Title>
|
|
131
133
|
{/* react-native-paper's Dialog.ScrollArea has a fixed 24px marginBottom baked in (meant to
|
|
132
134
|
reserve room for a Dialog.Actions row below it) — overridden to 0 since this dialog has
|
|
@@ -263,24 +265,8 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
|
|
|
263
265
|
</ScrollView>
|
|
264
266
|
</Dialog.ScrollArea>
|
|
265
267
|
</Dialog>
|
|
266
|
-
{
|
|
267
|
-
|
|
268
|
-
<View style={styles.overlay}>
|
|
269
|
-
<View style={[styles.overlayCard, { backgroundColor: cardBg, borderColor: cardBorder }, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }]}>
|
|
270
|
-
<Icon source='information-outline' size={64} color={colors.secondary} />
|
|
271
|
-
<Text variant='headlineLarge' style={[styles.overlayTitle, { color: colors.secondary }]}>
|
|
272
|
-
{infoMessage.title}
|
|
273
|
-
</Text>
|
|
274
|
-
<Text variant='bodyLarge' style={[styles.overlayBody, { color: fg }]}>
|
|
275
|
-
{infoMessage.message}
|
|
276
|
-
</Text>
|
|
277
|
-
<Button mode='contained' onPress={() => setInfoMessage(null)} style={styles.overlayButton} buttonColor={colors.primary} textColor={colors.onPrimary}>
|
|
278
|
-
OK
|
|
279
|
-
</Button>
|
|
280
|
-
</View>
|
|
281
|
-
</View>
|
|
282
|
-
</Portal>
|
|
283
|
-
)}
|
|
268
|
+
<ConfirmDialog visible={!!updateManifest} title='Update Available' message={updateManifestDate ? `Released ${updateManifestDate.toLocaleDateString()} at ${updateManifestDate.toLocaleTimeString()}` : ''} confirmLabel='Restart' cancelLabel='Later' icon='update' rotation={rotation} onConfirm={() => respondToUpdateConfirm(true)} onCancel={() => respondToUpdateConfirm(false)} />
|
|
269
|
+
<ConfirmDialog visible={!!infoMessage} title={infoMessage?.title ?? ''} message={infoMessage?.message ?? ''} confirmLabel='OK' icon='information-outline' rotation={rotation} onConfirm={() => setInfoMessage(null)} />
|
|
284
270
|
</>
|
|
285
271
|
)
|
|
286
272
|
}
|
|
@@ -307,27 +293,6 @@ const styles = StyleSheet.create({
|
|
|
307
293
|
justifyContent: 'center',
|
|
308
294
|
width: 36
|
|
309
295
|
},
|
|
310
|
-
overlay: {
|
|
311
|
-
alignItems: 'center',
|
|
312
|
-
backgroundColor: 'rgba(0,0,0,0.72)',
|
|
313
|
-
bottom: 0,
|
|
314
|
-
justifyContent: 'center',
|
|
315
|
-
left: 0,
|
|
316
|
-
position: 'absolute',
|
|
317
|
-
right: 0,
|
|
318
|
-
top: 0
|
|
319
|
-
},
|
|
320
|
-
overlayBody: { textAlign: 'center' },
|
|
321
|
-
overlayButton: { width: 160 },
|
|
322
|
-
overlayCard: {
|
|
323
|
-
alignItems: 'center',
|
|
324
|
-
borderRadius: 20,
|
|
325
|
-
borderWidth: 1,
|
|
326
|
-
gap: 16,
|
|
327
|
-
maxWidth: 360,
|
|
328
|
-
padding: 32
|
|
329
|
-
},
|
|
330
|
-
overlayTitle: { fontWeight: 'bold' },
|
|
331
296
|
scrollArea: {
|
|
332
297
|
marginBottom: 0
|
|
333
298
|
},
|
package/src/BaseStatsScreen.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useAutoPaperTheme } from '@rific/auto-paper'
|
|
2
2
|
import { Button, IconButton } from '@rific/feedback-press'
|
|
3
|
-
import { useRotation } from '@tastic/core'
|
|
3
|
+
import { useRotation, type ViewRotation } from '@tastic/core'
|
|
4
4
|
import { ReactNode, useState } from 'react'
|
|
5
5
|
import { ScrollView, StyleSheet, View } from 'react-native'
|
|
6
6
|
import { Text } from 'react-native-paper'
|
|
@@ -20,7 +20,7 @@ interface Props {
|
|
|
20
20
|
// read via useRotation() when omitted — note this does NOT make the header/scrollable content
|
|
21
21
|
// below rotate; the caller's own screen still needs its own FakeLandscapeView wrap for that
|
|
22
22
|
// (this component has no transform on its own main body).
|
|
23
|
-
rotation?:
|
|
23
|
+
rotation?: ViewRotation
|
|
24
24
|
title?: string
|
|
25
25
|
// 'headlineSmall' (this screen's own default, below) — was originally 'displaySmall' (the full
|
|
26
26
|
// display tier's own smallest step), then 'headlineLarge', both still read as oversized for a
|
package/src/ConfirmDialog.tsx
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import { useAutoPaperTheme } from '@rific/auto-paper'
|
|
2
2
|
import { Button } from '@rific/feedback-press'
|
|
3
|
+
import { toRotationStyle, type ViewRotation } from '@tastic/core'
|
|
4
|
+
import { type ReactNode } from 'react'
|
|
3
5
|
import { StyleSheet, View } from 'react-native'
|
|
4
6
|
import { Icon, Portal, Text } from 'react-native-paper'
|
|
5
7
|
|
|
8
|
+
import { getOverlayCardColors, overlayStyles } from './overlayCard'
|
|
9
|
+
|
|
6
10
|
export interface ConfirmDialogProps {
|
|
7
11
|
visible: boolean
|
|
8
12
|
title: string
|
|
9
|
-
message:
|
|
13
|
+
message: ReactNode
|
|
10
14
|
confirmLabel: string
|
|
11
|
-
cancelLabel
|
|
15
|
+
cancelLabel?: string
|
|
12
16
|
onConfirm: () => void
|
|
13
|
-
onCancel
|
|
14
|
-
// Live physical-hold rotation (see @tastic/
|
|
17
|
+
onCancel?: () => void
|
|
18
|
+
// Live physical-hold rotation (see @tastic/core's getViewRotation) - same convention as
|
|
15
19
|
// BaseSettingsDialog's own rotation prop; defaults to 0 for a caller with no per-player zone to
|
|
16
20
|
// match (a portrait-only or single-player game has nothing to stay consistent with).
|
|
17
|
-
rotation?:
|
|
21
|
+
rotation?: ViewRotation
|
|
18
22
|
// Any react-native-paper Icon `source` name, shown above the title - defaults to a plain
|
|
19
23
|
// question mark, since this component has no way to know why a caller is asking.
|
|
20
24
|
icon?: string
|
|
@@ -35,18 +39,14 @@ export function ConfirmDialog({ visible, title, message, confirmLabel, cancelLab
|
|
|
35
39
|
const { dark, colors } = useAutoPaperTheme()
|
|
36
40
|
if (!visible) return null
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
// tinted) background role - same convention BaseSettingsDialog's own overlay card uses.
|
|
40
|
-
const fg = dark ? '#FFFFFF' : '#000000'
|
|
41
|
-
const cardBg = dark ? '#111111' : '#F2F2F2'
|
|
42
|
-
const cardBorder = dark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.2)'
|
|
42
|
+
const { fg, cardBg, cardBorder } = getOverlayCardColors(dark)
|
|
43
43
|
const accent = destructive ? colors.danger : colors.primary
|
|
44
44
|
const onAccent = destructive ? colors.onDanger : colors.onPrimary
|
|
45
45
|
|
|
46
46
|
return (
|
|
47
47
|
<Portal>
|
|
48
|
-
<View style={styles.overlay}>
|
|
49
|
-
<View style={[styles.card, { backgroundColor: cardBg, borderColor: cardBorder }, rotation
|
|
48
|
+
<View style={[overlayStyles.overlay, styles.overlay]}>
|
|
49
|
+
<View style={[overlayStyles.card, styles.card, { backgroundColor: cardBg, borderColor: cardBorder }, toRotationStyle(rotation)]}>
|
|
50
50
|
<Icon source={icon} size={64} color={accent} />
|
|
51
51
|
<Text variant='headlineLarge' style={[styles.title, { color: accent }]}>
|
|
52
52
|
{title}
|
|
@@ -55,9 +55,11 @@ export function ConfirmDialog({ visible, title, message, confirmLabel, cancelLab
|
|
|
55
55
|
{message}
|
|
56
56
|
</Text>
|
|
57
57
|
<View style={styles.actions}>
|
|
58
|
-
|
|
59
|
-
{
|
|
60
|
-
|
|
58
|
+
{cancelLabel !== undefined && onCancel !== undefined && (
|
|
59
|
+
<Button mode='outlined' onPress={onCancel} style={styles.actionButton}>
|
|
60
|
+
{cancelLabel}
|
|
61
|
+
</Button>
|
|
62
|
+
)}
|
|
61
63
|
<Button mode='contained' onPress={onConfirm} style={styles.actionButton} buttonColor={accent} textColor={onAccent}>
|
|
62
64
|
{confirmLabel}
|
|
63
65
|
</Button>
|
|
@@ -93,32 +95,20 @@ const styles = StyleSheet.create({
|
|
|
93
95
|
body: {
|
|
94
96
|
textAlign: 'center'
|
|
95
97
|
},
|
|
98
|
+
// Additive on top of overlayCard's own shared `card` shape (borderRadius/borderWidth/gap/maxWidth/
|
|
99
|
+
// padding) — claims its full allowance instead of just maxWidth alone, which only caps how wide the
|
|
100
|
+
// card is allowed to get - without an explicit width, a flex-centered child sizes to its own content
|
|
101
|
+
// instead of stretching, so the card (and therefore actions below) can end up narrower than the
|
|
102
|
+
// screen actually has room for, needlessly forcing the action row to wrap sooner than it has to.
|
|
96
103
|
card: {
|
|
97
|
-
alignItems: 'center',
|
|
98
|
-
borderRadius: 20,
|
|
99
|
-
borderWidth: 1,
|
|
100
|
-
gap: 16,
|
|
101
|
-
maxWidth: 360,
|
|
102
|
-
padding: 32,
|
|
103
|
-
// Claims its full allowance instead of just maxWidth alone, which only caps how wide the card
|
|
104
|
-
// is allowed to get - without an explicit width, a flex-centered child sizes to its own content
|
|
105
|
-
// instead of stretching, so the card (and therefore actions below) can end up narrower than the
|
|
106
|
-
// screen actually has room for, needlessly forcing the action row to wrap sooner than it has to.
|
|
107
104
|
width: '100%'
|
|
108
105
|
},
|
|
106
|
+
// Additive on top of overlayCard's own shared `overlay` backdrop — gives the card room to breathe
|
|
107
|
+
// from the screen edges now that it reliably stretches to fill its full width allowance (see
|
|
108
|
+
// card's own width comment) - without this, width: '100%' would run the card edge-to-edge on a
|
|
109
|
+
// screen narrower than maxWidth's own 360.
|
|
109
110
|
overlay: {
|
|
110
|
-
|
|
111
|
-
backgroundColor: 'rgba(0,0,0,0.72)',
|
|
112
|
-
bottom: 0,
|
|
113
|
-
justifyContent: 'center',
|
|
114
|
-
left: 0,
|
|
115
|
-
// Gives the card room to breathe from the screen edges now that it reliably stretches to fill
|
|
116
|
-
// its full width allowance (see card's own width comment) - without this, width: '100%' would
|
|
117
|
-
// run the card edge-to-edge on a screen narrower than maxWidth's own 360.
|
|
118
|
-
paddingHorizontal: 24,
|
|
119
|
-
position: 'absolute',
|
|
120
|
-
right: 0,
|
|
121
|
-
top: 0
|
|
111
|
+
paddingHorizontal: 24
|
|
122
112
|
},
|
|
123
113
|
// textAlign is load-bearing, not decorative, once `title` wraps to more than one line - centered
|
|
124
114
|
// within card's own alignItems: 'center' only centers the Text's block as a whole; each wrapped
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useUpdater } from '@rific/updater'
|
|
2
|
+
import { type ViewRotation } from '@tastic/core'
|
|
3
|
+
|
|
4
|
+
import { ConfirmDialog } from './ConfirmDialog'
|
|
5
|
+
import { useUpdateConfirmBridge } from './useUpdateConfirmBridge'
|
|
6
|
+
|
|
7
|
+
export interface UpdateDialogProps {
|
|
8
|
+
// Same live physical-hold rotation convention as ConfirmDialog/BaseSettingsDialog's own rotation
|
|
9
|
+
// prop - defaults to 0 for a caller with no per-player zone to match.
|
|
10
|
+
rotation?: ViewRotation
|
|
11
|
+
autoCheck?: boolean
|
|
12
|
+
autoPrompt?: boolean
|
|
13
|
+
onError?: (message: string) => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Drop-in background updater: mount once (e.g. an app's root layout) and it owns its own
|
|
17
|
+
// useUpdater() instance end to end, rendering a themed ConfirmDialog instead of useUpdater's
|
|
18
|
+
// default Alert-based prompt whenever an update is found. BaseSettingsDialog's own manual "Check
|
|
19
|
+
// for Updates" button deliberately does NOT render this component - see its own comment on why
|
|
20
|
+
// that flow keeps a second, separate useUpdater() instance instead of reusing this one.
|
|
21
|
+
export function UpdateDialog({ rotation = 0, autoCheck = true, autoPrompt = true, onError }: UpdateDialogProps) {
|
|
22
|
+
const { manifest, onConfirm, respond } = useUpdateConfirmBridge()
|
|
23
|
+
useUpdater({ autoCheck, autoPrompt, onConfirm, onError })
|
|
24
|
+
|
|
25
|
+
const date = manifest ? new Date(manifest.createdAt) : null
|
|
26
|
+
|
|
27
|
+
return <ConfirmDialog visible={!!manifest} title='Update Available' message={date ? `Released ${date.toLocaleDateString()} at ${date.toLocaleTimeString()}` : ''} confirmLabel='Restart' cancelLabel='Later' icon='update' rotation={rotation} onConfirm={() => respond(true)} onCancel={() => respond(false)} />
|
|
28
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { MONO_FONT } from './fonts'
|
|
|
10
10
|
export { InlineColorPicker } from './InlineColorPicker'
|
|
11
11
|
export { getLabeledDropdownContentHeight, LABELED_DROPDOWN_POPOVER_WIDTH, LabeledDropdown, type LabeledDropdownOption } from './LabeledDropdown'
|
|
12
12
|
export { loadSkiaWeb } from './loadSkiaWeb'
|
|
13
|
+
export { getOverlayCardColors, type OverlayCardColors, overlayStyles } from './overlayCard'
|
|
13
14
|
export { PopoverBody } from './PopoverBody'
|
|
14
15
|
export { PressAwayOverlay } from './PressAwayOverlay'
|
|
15
16
|
export { ReadyButton } from './ReadyButton'
|
|
@@ -27,5 +28,6 @@ export { StatSection } from './StatSection'
|
|
|
27
28
|
// TriggerGaugeHost is the only supported way to render this.
|
|
28
29
|
export type { TriggerGaugeProps } from './TriggerGauge'
|
|
29
30
|
export { default as TriggerGaugeHost } from './TriggerGaugeHost'
|
|
31
|
+
export { UpdateDialog, type UpdateDialogProps } from './UpdateDialog'
|
|
30
32
|
export { type PopoverAlign, type PopoverRotation, type PopoverVerticalAlign, useAutoAlign } from './useAutoAlign'
|
|
31
33
|
export { type PopoverHost, usePopoverHost } from './usePopoverHost'
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native'
|
|
2
|
+
|
|
3
|
+
export interface OverlayCardColors {
|
|
4
|
+
fg: string
|
|
5
|
+
fgMuted: string
|
|
6
|
+
cardBg: string
|
|
7
|
+
cardBorder: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// The literal-black/white-by-appearance "retro" formula this package's own BaseSettingsDialog and
|
|
11
|
+
// ConfirmDialog each independently re-derived — deliberately NOT auto-paper's own (seed-color-tinted)
|
|
12
|
+
// background role, see either component's own "High-contrast retro look" comment for why. Every
|
|
13
|
+
// fleet app's own Game-Over/Round-Over/Match-Over/Quit-confirmation overlay re-derives this same
|
|
14
|
+
// three-or-four-line formula again, which is what this export exists to replace.
|
|
15
|
+
export function getOverlayCardColors(dark: boolean): OverlayCardColors {
|
|
16
|
+
return {
|
|
17
|
+
fg: dark ? '#FFFFFF' : '#000000',
|
|
18
|
+
fgMuted: dark ? 'rgba(255,255,255,0.4)' : 'rgba(0,0,0,0.4)',
|
|
19
|
+
cardBg: dark ? '#111111' : '#F2F2F2',
|
|
20
|
+
cardBorder: dark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.2)'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Only the shell shape every overlay card shares (backdrop + card geometry) — deliberately not the
|
|
25
|
+
// button/title/body styling, which already legitimately varies by consumer (BaseSettingsDialog's
|
|
26
|
+
// single centered OK button vs. ConfirmDialog's two-button flexBasis-wrap row vs. each app's own
|
|
27
|
+
// score/badge/zone content). A consumer applies `{backgroundColor: cardBg, borderColor: cardBorder}`
|
|
28
|
+
// from getOverlayCardColors on top of `card` itself, same as every existing call site already does.
|
|
29
|
+
export const overlayStyles = StyleSheet.create({
|
|
30
|
+
card: {
|
|
31
|
+
alignItems: 'center',
|
|
32
|
+
borderRadius: 20,
|
|
33
|
+
borderWidth: 1,
|
|
34
|
+
gap: 16,
|
|
35
|
+
maxWidth: 360,
|
|
36
|
+
padding: 32
|
|
37
|
+
},
|
|
38
|
+
overlay: {
|
|
39
|
+
alignItems: 'center',
|
|
40
|
+
backgroundColor: 'rgba(0,0,0,0.72)',
|
|
41
|
+
bottom: 0,
|
|
42
|
+
justifyContent: 'center',
|
|
43
|
+
left: 0,
|
|
44
|
+
position: 'absolute',
|
|
45
|
+
right: 0,
|
|
46
|
+
top: 0
|
|
47
|
+
}
|
|
48
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { UpdateManifest } from '@rific/updater'
|
|
2
|
+
import { useCallback, useRef, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
// Bridges @rific/updater's async onConfirm: (manifest) => Promise<boolean> into plain render
|
|
5
|
+
// state a ConfirmDialog can drive: `manifest` is the pending update to show (or null when there's
|
|
6
|
+
// nothing to confirm), and `respond` both resolves the promise onConfirm handed back to useUpdater
|
|
7
|
+
// and clears `manifest` so the dialog closes. Internal - not exported from index.ts. Shared by
|
|
8
|
+
// UpdateDialog and BaseSettingsDialog's own manual-check flow, so each only owns its own
|
|
9
|
+
// useUpdater() instance (see BaseSettingsDialog's comment on why that instance can't be shared).
|
|
10
|
+
export function useUpdateConfirmBridge() {
|
|
11
|
+
const [manifest, setManifest] = useState<UpdateManifest | null>(null)
|
|
12
|
+
const resolveRef = useRef<((confirmed: boolean) => void) | null>(null)
|
|
13
|
+
|
|
14
|
+
const onConfirm = useCallback(
|
|
15
|
+
(next: UpdateManifest) =>
|
|
16
|
+
new Promise<boolean>((resolve) => {
|
|
17
|
+
resolveRef.current = resolve
|
|
18
|
+
setManifest(next)
|
|
19
|
+
}),
|
|
20
|
+
[]
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
const respond = useCallback((confirmed: boolean) => {
|
|
24
|
+
setManifest(null)
|
|
25
|
+
resolveRef.current?.(confirmed)
|
|
26
|
+
resolveRef.current = null
|
|
27
|
+
}, [])
|
|
28
|
+
|
|
29
|
+
return { manifest, onConfirm, respond }
|
|
30
|
+
}
|