@tastic/hud 0.8.3 → 0.8.5
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 +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +206 -160
- package/dist/index.mjs +185 -140
- package/package.json +4 -3
- package/src/BaseSettingsDialog.tsx +12 -27
- package/src/ConfirmDialog.tsx +38 -16
- package/src/UpdateDialog.tsx +28 -0
- package/src/index.ts +1 -0
- package/src/useUpdateConfirmBridge.ts +30 -0
package/src/ConfirmDialog.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useAutoPaperTheme } from '@rific/auto-paper'
|
|
2
2
|
import { Button } from '@rific/feedback-press'
|
|
3
3
|
import { toRotationStyle, type ViewRotation } from '@tastic/core'
|
|
4
|
+
import { type ReactNode } from 'react'
|
|
4
5
|
import { StyleSheet, View } from 'react-native'
|
|
5
6
|
import { Icon, Portal, Text } from 'react-native-paper'
|
|
6
7
|
|
|
@@ -9,11 +10,11 @@ import { getOverlayCardColors, overlayStyles } from './overlayCard'
|
|
|
9
10
|
export interface ConfirmDialogProps {
|
|
10
11
|
visible: boolean
|
|
11
12
|
title: string
|
|
12
|
-
message:
|
|
13
|
+
message: ReactNode
|
|
13
14
|
confirmLabel: string
|
|
14
|
-
cancelLabel
|
|
15
|
+
cancelLabel?: string
|
|
15
16
|
onConfirm: () => void
|
|
16
|
-
onCancel
|
|
17
|
+
onCancel?: () => void
|
|
17
18
|
// Live physical-hold rotation (see @tastic/core's getViewRotation) - same convention as
|
|
18
19
|
// BaseSettingsDialog's own rotation prop; defaults to 0 for a caller with no per-player zone to
|
|
19
20
|
// match (a portrait-only or single-player game has nothing to stay consistent with).
|
|
@@ -41,6 +42,7 @@ export function ConfirmDialog({ visible, title, message, confirmLabel, cancelLab
|
|
|
41
42
|
const { fg, cardBg, cardBorder } = getOverlayCardColors(dark)
|
|
42
43
|
const accent = destructive ? colors.danger : colors.primary
|
|
43
44
|
const onAccent = destructive ? colors.onDanger : colors.onPrimary
|
|
45
|
+
const showCancel = cancelLabel !== undefined && onCancel !== undefined
|
|
44
46
|
|
|
45
47
|
return (
|
|
46
48
|
<Portal>
|
|
@@ -54,10 +56,12 @@ export function ConfirmDialog({ visible, title, message, confirmLabel, cancelLab
|
|
|
54
56
|
{message}
|
|
55
57
|
</Text>
|
|
56
58
|
<View style={styles.actions}>
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
{showCancel && (
|
|
60
|
+
<Button mode='outlined' onPress={onCancel} style={styles.actionButton}>
|
|
61
|
+
{cancelLabel}
|
|
62
|
+
</Button>
|
|
63
|
+
)}
|
|
64
|
+
<Button mode='contained' onPress={onConfirm} style={showCancel ? styles.actionButton : styles.actionButtonSingle} buttonColor={accent} textColor={onAccent}>
|
|
61
65
|
{confirmLabel}
|
|
62
66
|
</Button>
|
|
63
67
|
</View>
|
|
@@ -68,25 +72,43 @@ export function ConfirmDialog({ visible, title, message, confirmLabel, cancelLab
|
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
const styles = StyleSheet.create({
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
+
// minWidth (not flexBasis) is each button's real floor - whenever both fit side by side, flexGrow
|
|
76
|
+
// still splits any leftover width evenly beyond that floor, same visual result as a plain flex: 1
|
|
77
|
+
// on a roomy screen, but a button can never be squeezed narrower than 128, only wrap to its own
|
|
78
|
+
// row instead (see actions' flexWrap below). This used to be flexBasis: 128, which measured
|
|
79
|
+
// correctly on web but rendered every button roughly SQUARE on iOS - confirmed live (see the
|
|
80
|
+
// "Quit Match?" Cancel/Quit buttons and the single-button "OK" dialogs both coming out ~128pt
|
|
81
|
+
// tall instead of wide). Root cause: react-native-paper's Button renders its Surface as two
|
|
82
|
+
// nested views on iOS only (Surface.tsx's SurfaceIOS, for shadow rendering) - only a curated
|
|
83
|
+
// allowlist of style keys (flex/flexGrow/flexShrink/width/height/position/... - notably NOT
|
|
84
|
+
// flexBasis) reaches the outer view, which is the real flex item inside this row; every other
|
|
85
|
+
// key, flexBasis included, falls through to an inner view whose own unspecified flexDirection
|
|
86
|
+
// defaults to column. flexGrow: 1 landed on the (row-context) outer view and grew width
|
|
87
|
+
// correctly; flexBasis: 128 landed on the (column-context) inner view instead, where a flexBasis
|
|
88
|
+
// sets HEIGHT, not width. minWidth isn't axis-relative like flexBasis, so it constrains width
|
|
89
|
+
// correctly regardless of which of the two views it ends up on. react-native-paper's Button also
|
|
75
90
|
// hardcodes numberOfLines={1} on its label with no override, so a button squeezed under its
|
|
76
91
|
// label's natural width silently ellipsizes ("Cancel" -> "Can...") instead of wrapping - assumed
|
|
77
92
|
// fixable by just giving the row more width, until confirmed live that these two-word labels
|
|
78
93
|
// (Cancel/Reset/New Game) can still lose the truncation fight even with the extra room card's own
|
|
79
|
-
// width fix below provides, on a narrow enough screen.
|
|
80
|
-
// width there's no side-by-side arrangement that fits both buttons at their natural label size,
|
|
81
|
-
// so the layout has to give (stack) rather than the label (truncate).
|
|
94
|
+
// width fix below provides, on a narrow enough screen.
|
|
82
95
|
actionButton: {
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
flexGrow: 1,
|
|
97
|
+
minWidth: 128
|
|
98
|
+
},
|
|
99
|
+
// A lone confirm button (no cancel - BaseSettingsDialog's info-only "OK" dialogs, e.g. the
|
|
100
|
+
// update-check "no update found" message) has nothing left to share actions' width with, so
|
|
101
|
+
// actionButton's own flexGrow: 1 would stretch it across the entire row instead of reading as a
|
|
102
|
+
// normal button. Same 128 floor, no flexGrow, so it stays at that natural size instead of filling
|
|
103
|
+
// the card - centered by actions' own justifyContent below.
|
|
104
|
+
actionButtonSingle: {
|
|
105
|
+
minWidth: 128
|
|
85
106
|
},
|
|
86
107
|
actions: {
|
|
87
108
|
flexDirection: 'row',
|
|
88
109
|
flexWrap: 'wrap',
|
|
89
110
|
gap: 12,
|
|
111
|
+
justifyContent: 'center',
|
|
90
112
|
width: '100%'
|
|
91
113
|
},
|
|
92
114
|
body: {
|
|
@@ -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
|
@@ -28,5 +28,6 @@ export { StatSection } from './StatSection'
|
|
|
28
28
|
// TriggerGaugeHost is the only supported way to render this.
|
|
29
29
|
export type { TriggerGaugeProps } from './TriggerGauge'
|
|
30
30
|
export { default as TriggerGaugeHost } from './TriggerGaugeHost'
|
|
31
|
+
export { UpdateDialog, type UpdateDialogProps } from './UpdateDialog'
|
|
31
32
|
export { type PopoverAlign, type PopoverRotation, type PopoverVerticalAlign, useAutoAlign } from './useAutoAlign'
|
|
32
33
|
export { type PopoverHost, usePopoverHost } from './usePopoverHost'
|
|
@@ -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
|
+
}
|