@tastic/hud 0.8.1 → 0.8.3
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 +34 -8
- package/dist/index.d.ts +34 -8
- package/dist/index.js +237 -234
- package/dist/index.mjs +179 -178
- package/package.json +2 -2
- package/src/BaseSettingsDialog.tsx +8 -28
- package/src/BaseStatsScreen.tsx +52 -94
- package/src/ConfirmDialog.tsx +38 -25
- package/src/index.ts +1 -0
- package/src/overlayCard.ts +48 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tastic/hud",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
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",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"@rific/feedback-press": "^0.10.8",
|
|
78
78
|
"@rific/updater": "^0.6.0",
|
|
79
79
|
"@shopify/react-native-skia": "^2.6.2",
|
|
80
|
-
"@tastic/core": "^0.
|
|
80
|
+
"@tastic/core": "^0.4.1",
|
|
81
81
|
"@testing-library/dom": "^10.4.1",
|
|
82
82
|
"@testing-library/react": "^16.3.2",
|
|
83
83
|
"@types/jest": "^30.0.0",
|
|
@@ -1,11 +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
7
|
import { Icon, Portal, Text } from 'react-native-paper'
|
|
8
8
|
|
|
9
|
+
import { getOverlayCardColors, overlayStyles } from './overlayCard'
|
|
10
|
+
|
|
9
11
|
interface SettingIconProps {
|
|
10
12
|
source: string
|
|
11
13
|
color: string
|
|
@@ -31,7 +33,7 @@ export interface BaseSettingsDialogProps {
|
|
|
31
33
|
// dialog), so it just rotates its own content in place. Defaults to a live ambient read via
|
|
32
34
|
// useRotation() when omitted; pass this only when a caller's own reading needs to differ from the
|
|
33
35
|
// ambient one (rare — most callers should simply omit it).
|
|
34
|
-
rotation?:
|
|
36
|
+
rotation?: ViewRotation
|
|
35
37
|
// Each app's own OTA version (e.g. release.otaVersion) — not something this package can read
|
|
36
38
|
// itself, since every consuming app tracks its own release.ts independently. Accepts a number too
|
|
37
39
|
// since that's what release.otaVersion actually is in most apps (a bare incrementing counter, not
|
|
@@ -118,15 +120,11 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
|
|
|
118
120
|
const showAppearance = !hideAppearance
|
|
119
121
|
const showUpdateCheck = Platform.OS !== 'web' && !hideUpdateCheck
|
|
120
122
|
|
|
121
|
-
|
|
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)'
|
|
123
|
+
const { fg, cardBg, cardBorder } = getOverlayCardColors(dark)
|
|
126
124
|
|
|
127
125
|
return (
|
|
128
126
|
<>
|
|
129
|
-
<Dialog visible={visible} onDismiss={onDismiss} style={[styles.dialog, rotation
|
|
127
|
+
<Dialog visible={visible} onDismiss={onDismiss} style={[styles.dialog, toRotationStyle(rotation)]}>
|
|
130
128
|
<Dialog.Title>Settings</Dialog.Title>
|
|
131
129
|
{/* react-native-paper's Dialog.ScrollArea has a fixed 24px marginBottom baked in (meant to
|
|
132
130
|
reserve room for a Dialog.Actions row below it) — overridden to 0 since this dialog has
|
|
@@ -265,8 +263,8 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
|
|
|
265
263
|
</Dialog>
|
|
266
264
|
{infoMessage && (
|
|
267
265
|
<Portal>
|
|
268
|
-
<View style={
|
|
269
|
-
<View style={[
|
|
266
|
+
<View style={overlayStyles.overlay}>
|
|
267
|
+
<View style={[overlayStyles.card, { backgroundColor: cardBg, borderColor: cardBorder }, toRotationStyle(rotation)]}>
|
|
270
268
|
<Icon source='information-outline' size={64} color={colors.secondary} />
|
|
271
269
|
<Text variant='headlineLarge' style={[styles.overlayTitle, { color: colors.secondary }]}>
|
|
272
270
|
{infoMessage.title}
|
|
@@ -307,26 +305,8 @@ const styles = StyleSheet.create({
|
|
|
307
305
|
justifyContent: 'center',
|
|
308
306
|
width: 36
|
|
309
307
|
},
|
|
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
308
|
overlayBody: { textAlign: 'center' },
|
|
321
309
|
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
310
|
overlayTitle: { fontWeight: 'bold' },
|
|
331
311
|
scrollArea: {
|
|
332
312
|
marginBottom: 0
|
package/src/BaseStatsScreen.tsx
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
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
|
-
import {
|
|
6
|
+
import { Text } from 'react-native-paper'
|
|
7
|
+
|
|
8
|
+
import { ConfirmDialog } from './ConfirmDialog'
|
|
7
9
|
|
|
8
10
|
// react-native-paper's own MD3 Text variant names — mirrored locally rather than importing its
|
|
9
11
|
// internal VariantProp/MD3TypescaleKey (not exported from the package's public entry point) just
|
|
@@ -12,12 +14,13 @@ type MD3TextVariant = 'displayLarge' | 'displayMedium' | 'displaySmall' | 'headl
|
|
|
12
14
|
|
|
13
15
|
interface Props {
|
|
14
16
|
// Explicit override for the live physical-hold rotation (see @tastic/core's useRotation) — only
|
|
15
|
-
// affects this component's own Portal-rendered reset-confirm
|
|
16
|
-
// content) is unaffected by whichever screen's own
|
|
17
|
-
// to rotate itself. Defaults to a live ambient
|
|
18
|
-
// does NOT make the header/scrollable content
|
|
19
|
-
//
|
|
20
|
-
|
|
17
|
+
// affects this component's own Portal-rendered reset-confirm dialog (via ConfirmDialog's own
|
|
18
|
+
// rotation prop), which (like any Portal content) is unaffected by whichever screen's own
|
|
19
|
+
// FakeLandscapeView wraps the trigger and so has to rotate itself. Defaults to a live ambient
|
|
20
|
+
// read via useRotation() when omitted — note this does NOT make the header/scrollable content
|
|
21
|
+
// below rotate; the caller's own screen still needs its own FakeLandscapeView wrap for that
|
|
22
|
+
// (this component has no transform on its own main body).
|
|
23
|
+
rotation?: ViewRotation
|
|
21
24
|
title?: string
|
|
22
25
|
// 'headlineSmall' (this screen's own default, below) — was originally 'displaySmall' (the full
|
|
23
26
|
// display tier's own smallest step), then 'headlineLarge', both still read as oversized for a
|
|
@@ -36,39 +39,38 @@ interface Props {
|
|
|
36
39
|
resetLabel?: string
|
|
37
40
|
resetConfirmTitle?: string
|
|
38
41
|
resetConfirmBody?: string
|
|
39
|
-
// Independently optional overrides for this screen's own background
|
|
40
|
-
// to the literal-black/white-by-appearance formula described below when omitted, so
|
|
41
|
-
// existing caller (none of which pass these) renders identically to before. A caller with
|
|
42
|
-
// app-wide chrome palette (e.g. a tinted background rather than neutral black/white)
|
|
43
|
-
// whichever of the
|
|
44
|
-
//
|
|
42
|
+
// Independently optional overrides for this screen's own header/background colors — each
|
|
43
|
+
// defaults to the literal-black/white-by-appearance formula described below when omitted, so
|
|
44
|
+
// every existing caller (none of which pass these) renders identically to before. A caller with
|
|
45
|
+
// its own app-wide chrome palette (e.g. a tinted background rather than neutral black/white)
|
|
46
|
+
// passes whichever of the two it needs.
|
|
47
|
+
//
|
|
48
|
+
// There used to be a third override here, cardBg, plus a separate accentColor override for the
|
|
49
|
+
// reset-confirm dialog's own icon/title/card. Both are gone: the reset-confirm step used to be a
|
|
50
|
+
// hand-rolled lookalike of ConfirmDialog with its own copy of the overlay/card/button styling,
|
|
51
|
+
// which is exactly how it drifted from ConfirmDialog's real look in the first place (different
|
|
52
|
+
// button layout, different Cancel styling, and — via cardBg — a different, caller-tintable card
|
|
53
|
+
// background no other ConfirmDialog in the fleet has). It now renders a real ConfirmDialog
|
|
54
|
+
// instance below instead of its own copy, which by construction can never drift from every other
|
|
55
|
+
// confirm prompt in the app again — at the cost of losing that per-caller tinting, which is the
|
|
56
|
+
// right trade: a "you're about to erase everything" prompt should look and read identically
|
|
57
|
+
// everywhere, not take on whichever screen happened to trigger it.
|
|
45
58
|
fg?: string
|
|
46
59
|
bg?: string
|
|
47
|
-
cardBg?: string
|
|
48
|
-
// Overrides the confirm-overlay's alert icon/title, which otherwise default to auto-paper's
|
|
49
|
-
// colors.secondary. That default is tuned to read against auto-paper's OWN background role, not
|
|
50
|
-
// necessarily against a cardBg override above — a caller passing cardBg should generally pass a
|
|
51
|
-
// matching accentColor too, rather than risk colors.secondary landing close in hue/lightness to
|
|
52
|
-
// its own custom card background (this is what actually happened with a tinted felt cardBg in the
|
|
53
|
-
// app this was first overridden for). Every button on this screen (the reset trigger included)
|
|
54
|
-
// fills with colors.secondary/onSecondary as a self-contained, always-legible pair instead of
|
|
55
|
-
// relying on a text-only color read against the surrounding chrome, so none of them need this —
|
|
56
|
-
// only the bare icon/title do.
|
|
57
|
-
accentColor?: string
|
|
58
60
|
children: ReactNode
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
// The stats/achievements screen shell every @tastic game shares — back button + title header,
|
|
62
64
|
// scrollable content area, and an optional reset-everything action with its own confirm-before-
|
|
63
|
-
// destroying
|
|
65
|
+
// destroying dialog — extracted after the same header/scroll/reset-confirm shape was independently
|
|
64
66
|
// built into LightCycles' own achievements.tsx. Colors default to the same literal-black/white-by-
|
|
65
67
|
// appearance formula BaseSettingsDialog hardcodes internally (same as this package's own
|
|
66
|
-
// BaseSettingsDialog) — zero-config for every existing caller — but fg/bg
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
export function BaseStatsScreen({ rotation: rotationOverride, title = 'Achievements', titleVariant = 'headlineSmall', onBack, insets, onReset, resetLabel = 'Reset All Stats', resetConfirmTitle = 'Reset Everything?', resetConfirmBody = 'This permanently erases all stats and achievements. This cannot be undone.', fg: fgOverride, bg: bgOverride,
|
|
68
|
+
// BaseSettingsDialog) — zero-config for every existing caller — but fg/bg are each independently
|
|
69
|
+
// overridable for a caller whose own app-wide chrome palette isn't that literal black/white
|
|
70
|
+
// convention. `children` is the seam for whatever isn't shared: this component has no opinion on
|
|
71
|
+
// what a "stat" is or how achievements are catalogued — see StatRow/StatSection/AchievementRow for
|
|
72
|
+
// the smaller presentational pieces built to go inside it.
|
|
73
|
+
export function BaseStatsScreen({ rotation: rotationOverride, title = 'Achievements', titleVariant = 'headlineSmall', onBack, insets, onReset, resetLabel = 'Reset All Stats', resetConfirmTitle = 'Reset Everything?', resetConfirmBody = 'This permanently erases all stats and achievements. This cannot be undone.', fg: fgOverride, bg: bgOverride, children }: Props) {
|
|
72
74
|
const { dark, colors } = useAutoPaperTheme()
|
|
73
75
|
const ambientRotation = useRotation()
|
|
74
76
|
const rotation = rotationOverride ?? ambientRotation
|
|
@@ -77,15 +79,10 @@ export function BaseStatsScreen({ rotation: rotationOverride, title = 'Achieveme
|
|
|
77
79
|
|
|
78
80
|
// High-contrast retro look by default: literal black/white by appearance, matching every other
|
|
79
81
|
// screen in this ecosystem (title/loadout/settings all use this identical formula) rather than
|
|
80
|
-
// auto-paper's own (slightly tinted) background role
|
|
81
|
-
//
|
|
82
|
-
// whose own chrome isn't that literal black/white convention; cardBorder has no override (see the
|
|
83
|
-
// Props doc above for why).
|
|
82
|
+
// auto-paper's own (slightly tinted) background role. Overridable per-instance via the props
|
|
83
|
+
// above for a caller whose own chrome isn't that literal black/white convention.
|
|
84
84
|
const fg = fgOverride ?? (dark ? '#FFFFFF' : '#000000')
|
|
85
85
|
const bg = bgOverride ?? (dark ? '#000000' : '#FFFFFF')
|
|
86
|
-
const cardBg = cardBgOverride ?? (dark ? '#111111' : '#F2F2F2')
|
|
87
|
-
const cardBorder = dark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.2)'
|
|
88
|
-
const accentColor = accentColorOverride ?? colors.secondary
|
|
89
86
|
|
|
90
87
|
return (
|
|
91
88
|
<View style={[styles.container, { backgroundColor: bg }]}>
|
|
@@ -100,42 +97,27 @@ export function BaseStatsScreen({ rotation: rotationOverride, title = 'Achieveme
|
|
|
100
97
|
{children}
|
|
101
98
|
|
|
102
99
|
{showReset && (
|
|
103
|
-
<Button mode='contained' onPress={() => setConfirmResetVisible(true)} buttonColor={colors.
|
|
100
|
+
<Button mode='contained' onPress={() => setConfirmResetVisible(true)} buttonColor={colors.danger} textColor={colors.onDanger} style={styles.resetButton}>
|
|
104
101
|
{resetLabel}
|
|
105
102
|
</Button>
|
|
106
103
|
)}
|
|
107
104
|
</ScrollView>
|
|
108
105
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
mode='contained'
|
|
125
|
-
onPress={() => {
|
|
126
|
-
onReset?.()
|
|
127
|
-
setConfirmResetVisible(false)
|
|
128
|
-
}}
|
|
129
|
-
style={styles.overlayButton}
|
|
130
|
-
buttonColor={colors.secondary}
|
|
131
|
-
textColor={colors.onSecondary}
|
|
132
|
-
>
|
|
133
|
-
Reset
|
|
134
|
-
</Button>
|
|
135
|
-
</View>
|
|
136
|
-
</View>
|
|
137
|
-
</Portal>
|
|
138
|
-
)}
|
|
106
|
+
<ConfirmDialog
|
|
107
|
+
visible={confirmResetVisible}
|
|
108
|
+
title={resetConfirmTitle}
|
|
109
|
+
message={resetConfirmBody}
|
|
110
|
+
confirmLabel='Reset'
|
|
111
|
+
cancelLabel='Cancel'
|
|
112
|
+
icon='alert-outline'
|
|
113
|
+
destructive
|
|
114
|
+
rotation={rotation}
|
|
115
|
+
onCancel={() => setConfirmResetVisible(false)}
|
|
116
|
+
onConfirm={() => {
|
|
117
|
+
onReset?.()
|
|
118
|
+
setConfirmResetVisible(false)
|
|
119
|
+
}}
|
|
120
|
+
/>
|
|
139
121
|
</View>
|
|
140
122
|
)
|
|
141
123
|
}
|
|
@@ -154,30 +136,6 @@ const styles = StyleSheet.create({
|
|
|
154
136
|
gap: 4,
|
|
155
137
|
paddingBottom: 8
|
|
156
138
|
},
|
|
157
|
-
// Same overlay shape BaseSettingsDialog's own info overlay uses (backdrop + centered card),
|
|
158
|
-
// rotated in place the same way rather than via Portal-root transform for the same reason — see
|
|
159
|
-
// that component's own identical styles for the full rationale.
|
|
160
|
-
overlay: {
|
|
161
|
-
alignItems: 'center',
|
|
162
|
-
backgroundColor: 'rgba(0,0,0,0.72)',
|
|
163
|
-
bottom: 0,
|
|
164
|
-
justifyContent: 'center',
|
|
165
|
-
left: 0,
|
|
166
|
-
position: 'absolute',
|
|
167
|
-
right: 0,
|
|
168
|
-
top: 0
|
|
169
|
-
},
|
|
170
|
-
overlayBody: { textAlign: 'center' },
|
|
171
|
-
overlayButton: { width: 160 },
|
|
172
|
-
overlayCard: {
|
|
173
|
-
alignItems: 'center',
|
|
174
|
-
borderRadius: 20,
|
|
175
|
-
borderWidth: 1,
|
|
176
|
-
gap: 16,
|
|
177
|
-
maxWidth: 360,
|
|
178
|
-
padding: 32
|
|
179
|
-
},
|
|
180
|
-
overlayTitle: { fontWeight: 'bold', marginBottom: -8 },
|
|
181
139
|
resetButton: {
|
|
182
140
|
marginTop: 16
|
|
183
141
|
},
|
package/src/ConfirmDialog.tsx
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { useAutoPaperTheme } from '@rific/auto-paper'
|
|
2
2
|
import { Button } from '@rific/feedback-press'
|
|
3
|
+
import { toRotationStyle, type ViewRotation } from '@tastic/core'
|
|
3
4
|
import { StyleSheet, View } from 'react-native'
|
|
4
5
|
import { Icon, Portal, Text } from 'react-native-paper'
|
|
5
6
|
|
|
7
|
+
import { getOverlayCardColors, overlayStyles } from './overlayCard'
|
|
8
|
+
|
|
6
9
|
export interface ConfirmDialogProps {
|
|
7
10
|
visible: boolean
|
|
8
11
|
title: string
|
|
@@ -11,10 +14,10 @@ export interface ConfirmDialogProps {
|
|
|
11
14
|
cancelLabel: string
|
|
12
15
|
onConfirm: () => void
|
|
13
16
|
onCancel: () => void
|
|
14
|
-
// Live physical-hold rotation (see @tastic/
|
|
17
|
+
// Live physical-hold rotation (see @tastic/core's getViewRotation) - same convention as
|
|
15
18
|
// BaseSettingsDialog's own rotation prop; defaults to 0 for a caller with no per-player zone to
|
|
16
19
|
// match (a portrait-only or single-player game has nothing to stay consistent with).
|
|
17
|
-
rotation?:
|
|
20
|
+
rotation?: ViewRotation
|
|
18
21
|
// Any react-native-paper Icon `source` name, shown above the title - defaults to a plain
|
|
19
22
|
// question mark, since this component has no way to know why a caller is asking.
|
|
20
23
|
icon?: string
|
|
@@ -35,18 +38,14 @@ export function ConfirmDialog({ visible, title, message, confirmLabel, cancelLab
|
|
|
35
38
|
const { dark, colors } = useAutoPaperTheme()
|
|
36
39
|
if (!visible) return null
|
|
37
40
|
|
|
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)'
|
|
41
|
+
const { fg, cardBg, cardBorder } = getOverlayCardColors(dark)
|
|
43
42
|
const accent = destructive ? colors.danger : colors.primary
|
|
44
43
|
const onAccent = destructive ? colors.onDanger : colors.onPrimary
|
|
45
44
|
|
|
46
45
|
return (
|
|
47
46
|
<Portal>
|
|
48
|
-
<View style={styles.overlay}>
|
|
49
|
-
<View style={[styles.card, { backgroundColor: cardBg, borderColor: cardBorder }, rotation
|
|
47
|
+
<View style={[overlayStyles.overlay, styles.overlay]}>
|
|
48
|
+
<View style={[overlayStyles.card, styles.card, { backgroundColor: cardBg, borderColor: cardBorder }, toRotationStyle(rotation)]}>
|
|
50
49
|
<Icon source={icon} size={64} color={accent} />
|
|
51
50
|
<Text variant='headlineLarge' style={[styles.title, { color: accent }]}>
|
|
52
51
|
{title}
|
|
@@ -69,36 +68,50 @@ export function ConfirmDialog({ visible, title, message, confirmLabel, cancelLab
|
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
const styles = StyleSheet.create({
|
|
71
|
+
// flexBasis (not flex: 1's own 0%) is each button's real floor: whenever both fit side by side,
|
|
72
|
+
// flexGrow still splits any leftover width evenly beyond that floor, same visual result as the
|
|
73
|
+
// old flex: 1 on a roomy screen - but a button can never be squeezed narrower than 128, only
|
|
74
|
+
// wrap to its own row instead (see actions' flexWrap below). react-native-paper's Button
|
|
75
|
+
// hardcodes numberOfLines={1} on its label with no override, so a button squeezed under its
|
|
76
|
+
// label's natural width silently ellipsizes ("Cancel" -> "Can...") instead of wrapping - assumed
|
|
77
|
+
// fixable by just giving the row more width, until confirmed live that these two-word labels
|
|
78
|
+
// (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. flexBasis is the real fix: past a certain
|
|
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).
|
|
72
82
|
actionButton: {
|
|
73
|
-
|
|
83
|
+
flexBasis: 128,
|
|
84
|
+
flexGrow: 1
|
|
74
85
|
},
|
|
75
86
|
actions: {
|
|
76
87
|
flexDirection: 'row',
|
|
88
|
+
flexWrap: 'wrap',
|
|
77
89
|
gap: 12,
|
|
78
90
|
width: '100%'
|
|
79
91
|
},
|
|
80
92
|
body: {
|
|
81
93
|
textAlign: 'center'
|
|
82
94
|
},
|
|
95
|
+
// Additive on top of overlayCard's own shared `card` shape (borderRadius/borderWidth/gap/maxWidth/
|
|
96
|
+
// padding) — claims its full allowance instead of just maxWidth alone, which only caps how wide the
|
|
97
|
+
// card is allowed to get - without an explicit width, a flex-centered child sizes to its own content
|
|
98
|
+
// instead of stretching, so the card (and therefore actions below) can end up narrower than the
|
|
99
|
+
// screen actually has room for, needlessly forcing the action row to wrap sooner than it has to.
|
|
83
100
|
card: {
|
|
84
|
-
|
|
85
|
-
borderRadius: 20,
|
|
86
|
-
borderWidth: 1,
|
|
87
|
-
gap: 16,
|
|
88
|
-
maxWidth: 360,
|
|
89
|
-
padding: 32
|
|
101
|
+
width: '100%'
|
|
90
102
|
},
|
|
103
|
+
// Additive on top of overlayCard's own shared `overlay` backdrop — gives the card room to breathe
|
|
104
|
+
// from the screen edges now that it reliably stretches to fill its full width allowance (see
|
|
105
|
+
// card's own width comment) - without this, width: '100%' would run the card edge-to-edge on a
|
|
106
|
+
// screen narrower than maxWidth's own 360.
|
|
91
107
|
overlay: {
|
|
92
|
-
|
|
93
|
-
backgroundColor: 'rgba(0,0,0,0.72)',
|
|
94
|
-
bottom: 0,
|
|
95
|
-
justifyContent: 'center',
|
|
96
|
-
left: 0,
|
|
97
|
-
position: 'absolute',
|
|
98
|
-
right: 0,
|
|
99
|
-
top: 0
|
|
108
|
+
paddingHorizontal: 24
|
|
100
109
|
},
|
|
110
|
+
// textAlign is load-bearing, not decorative, once `title` wraps to more than one line - centered
|
|
111
|
+
// within card's own alignItems: 'center' only centers the Text's block as a whole; each wrapped
|
|
112
|
+
// line inside that block still defaults to left-aligned without this.
|
|
101
113
|
title: {
|
|
102
|
-
fontWeight: 'bold'
|
|
114
|
+
fontWeight: 'bold',
|
|
115
|
+
textAlign: 'center'
|
|
103
116
|
}
|
|
104
117
|
})
|
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'
|
|
@@ -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
|
+
})
|