@tastic/hud 0.21.0 → 0.23.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 +36 -15
- package/dist/chrome/index.d.mts +99 -18
- package/dist/chrome/index.d.ts +99 -18
- package/dist/chrome/index.js +363 -63
- package/dist/chrome/index.mjs +19 -3
- package/dist/{chunk-UOA35OWJ.mjs → chunk-BXG44LIA.mjs} +333 -45
- package/dist/index.d.mts +12 -81
- package/dist/index.d.ts +12 -81
- package/dist/index.js +88 -72
- package/dist/index.mjs +105 -365
- package/package.json +3 -2
- package/src/AchievementCatalogSection.tsx +7 -6
- package/src/AchievementRow.tsx +13 -13
- package/src/ActivityStatSection.tsx +10 -5
- package/src/BaseSettingsDialog.tsx +13 -13
- package/src/StatRow.tsx +10 -11
- package/src/StatSection.tsx +14 -14
- package/src/chrome/index.ts +13 -4
- package/src/index.ts +1 -0
- package/src/wellSurface.ts +30 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tastic/hud",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.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",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"@infinitetoken/jest-config": "^0.5.2",
|
|
89
89
|
"@infinitetoken/tsconfig": "^0.6.0",
|
|
90
90
|
"@rific/auto-paper": "^0.13.3",
|
|
91
|
-
"@rific/core": "^0.
|
|
91
|
+
"@rific/core": "^0.4.0",
|
|
92
92
|
"@rific/feedback-press": "^0.13.0",
|
|
93
93
|
"@rific/updater": "^0.7.1",
|
|
94
94
|
"@shopify/react-native-skia": "^2.14.0",
|
|
@@ -117,6 +117,7 @@
|
|
|
117
117
|
},
|
|
118
118
|
"peerDependencies": {
|
|
119
119
|
"@rific/auto-paper": "^0.13.0",
|
|
120
|
+
"@rific/core": ">=0.4.0",
|
|
120
121
|
"@rific/feedback-press": ">=0.10.2",
|
|
121
122
|
"@rific/updater": ">=0.4.0",
|
|
122
123
|
"@shopify/react-native-skia": ">=2.6.0",
|
|
@@ -15,12 +15,13 @@ import { MONO_FONT } from './fonts'
|
|
|
15
15
|
interface Props {
|
|
16
16
|
rows: AchievementCatalogRow[]
|
|
17
17
|
label?: string
|
|
18
|
-
// Independently optional overrides for
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
18
|
+
// Independently optional overrides for the heading's and rows' colors. fg and sectionBg go to each
|
|
19
|
+
// <AchievementRow> (whose own defaults are the well's, see wellSurface.ts). fgMuted goes to the heading and
|
|
20
|
+
// the rows; omitted, the heading (drawn on the screen, not on a well) keeps its own screen-derived default
|
|
21
|
+
// and the rows theirs.
|
|
22
22
|
fg?: string
|
|
23
23
|
fgMuted?: string
|
|
24
|
+
sectionBg?: string
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
// The "ALL ACHIEVEMENTS" heading + one <AchievementRow> per row - extracted verbatim from every
|
|
@@ -42,7 +43,7 @@ interface Props {
|
|
|
42
43
|
// about 1.4:1 and 1.8:1 against the light screen's white, so a bar or check in either barely shows;
|
|
43
44
|
// mixed, they're about 5:1 and 6:1 (bronze goes from about 3:1 to 9:1). The badge keeps the true tier
|
|
44
45
|
// color: its glyph is black, which reads on all three.
|
|
45
|
-
export function AchievementCatalogSection({ rows, label = 'ALL ACHIEVEMENTS', fg, fgMuted: fgMutedOverride }: Props) {
|
|
46
|
+
export function AchievementCatalogSection({ rows, label = 'ALL ACHIEVEMENTS', fg, fgMuted: fgMutedOverride, sectionBg }: Props) {
|
|
46
47
|
const { dark } = useAutoPaperTheme()
|
|
47
48
|
const fgMuted = fgMutedOverride ?? (dark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.5)')
|
|
48
49
|
const tierAccent = (tierColor: string) => (dark ? tierColor : getBlendedColor('#000000', tierColor, 0.5))
|
|
@@ -53,7 +54,7 @@ export function AchievementCatalogSection({ rows, label = 'ALL ACHIEVEMENTS', fg
|
|
|
53
54
|
{label}
|
|
54
55
|
</Text>
|
|
55
56
|
{rows.map((row) => (
|
|
56
|
-
<AchievementRow key={row.id} icon={row.icon} title={row.title} description={row.description} badgeColor={row.unlockedAt !== undefined ? row.tierColor : LOCKED_BADGE_COLOR} checkColor={tierAccent(row.tierColor)} unlockedLabel={row.unlockedLabel} progress={row.progress} deviceMarker={row.deviceMarker} fg={fg} fgMuted={fgMutedOverride} />
|
|
57
|
+
<AchievementRow key={row.id} icon={row.icon} title={row.title} description={row.description} badgeColor={row.unlockedAt !== undefined ? row.tierColor : LOCKED_BADGE_COLOR} checkColor={tierAccent(row.tierColor)} unlockedLabel={row.unlockedLabel} progress={row.progress} deviceMarker={row.deviceMarker} fg={fg} fgMuted={fgMutedOverride} sectionBg={sectionBg} />
|
|
57
58
|
))}
|
|
58
59
|
</Fragment>
|
|
59
60
|
)
|
package/src/AchievementRow.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { useAutoPaperTheme } from '@rific/auto-paper'
|
|
2
1
|
import { StyleSheet, View } from 'react-native'
|
|
3
2
|
import { Icon, ProgressBar, Text } from 'react-native-paper'
|
|
4
3
|
|
|
4
|
+
import { useWellColors, WELL_SHADOW } from './wellSurface'
|
|
5
|
+
|
|
5
6
|
// A neutral gray for a locked badge's own background - exported so a caller computing its own
|
|
6
7
|
// badge color (unlocked ? tierColor : LOCKED_BADGE_COLOR, matching LightCycles' own achievements.tsx)
|
|
7
8
|
// doesn't need to invent or duplicate this exact tone.
|
|
@@ -32,10 +33,9 @@ interface Props {
|
|
|
32
33
|
// from a specific profile's own tab - optional since only an app with per-profile stat views has
|
|
33
34
|
// this concept at all.
|
|
34
35
|
deviceMarker?: boolean
|
|
35
|
-
// Independently optional overrides for this row's own text/background colors - each defaults to
|
|
36
|
-
// the
|
|
37
|
-
//
|
|
38
|
-
// with its own app-wide chrome palette passes whichever of the three it needs.
|
|
36
|
+
// Independently optional overrides for this row's own text/background colors - each defaults to the well's
|
|
37
|
+
// (wellSurface.ts: the theme's surface and its onSurface/onSurfaceVariant text), like StatSection/StatRow. A
|
|
38
|
+
// caller with its own app-wide chrome palette passes whichever of the three it needs.
|
|
39
39
|
fg?: string
|
|
40
40
|
fgMuted?: string
|
|
41
41
|
sectionBg?: string
|
|
@@ -44,16 +44,15 @@ interface Props {
|
|
|
44
44
|
// One row per achievement - a tier-colored (or locked-gray) badge, title/description, an optional
|
|
45
45
|
// progress bar while locked, and either an unlocked check-mark+label or a lock icon. Extracted
|
|
46
46
|
// verbatim from LightCycles' own achievements.tsx, where this exact row shape backed its entire
|
|
47
|
-
// "ALL ACHIEVEMENTS" list.
|
|
48
|
-
//
|
|
49
|
-
// own
|
|
50
|
-
// chrome palette. Has no opinion on what an achievement actually IS (id, unlock predicate, catalog)
|
|
47
|
+
// "ALL ACHIEVEMENTS" list. Each row is its own well (wellSurface.ts: the theme's surface on MD3's level-1 shadow);
|
|
48
|
+
// fg/fgMuted/its own background default to the well's colors, each independently overridable via the matching
|
|
49
|
+
// prop for a caller with its own chrome palette. Has no opinion on what an achievement actually IS (id, unlock predicate, catalog)
|
|
51
50
|
// - that stays entirely app-local; this is purely the presentational row.
|
|
52
51
|
export function AchievementRow({ icon, title, description, badgeColor, unlockedLabel, checkColor, progress, deviceMarker, fg: fgOverride, fgMuted: fgMutedOverride, sectionBg: sectionBgOverride }: Props) {
|
|
53
|
-
const
|
|
54
|
-
const fg = fgOverride ??
|
|
55
|
-
const fgMuted = fgMutedOverride ??
|
|
56
|
-
const sectionBg = sectionBgOverride ??
|
|
52
|
+
const well = useWellColors()
|
|
53
|
+
const fg = fgOverride ?? well.fg
|
|
54
|
+
const fgMuted = fgMutedOverride ?? well.fgMuted
|
|
55
|
+
const sectionBg = sectionBgOverride ?? well.bg
|
|
57
56
|
const unlocked = unlockedLabel !== undefined
|
|
58
57
|
|
|
59
58
|
return (
|
|
@@ -116,6 +115,7 @@ const styles = StyleSheet.create({
|
|
|
116
115
|
row: {
|
|
117
116
|
alignItems: 'center',
|
|
118
117
|
borderRadius: 12,
|
|
118
|
+
boxShadow: WELL_SHADOW,
|
|
119
119
|
flexDirection: 'row',
|
|
120
120
|
gap: 12,
|
|
121
121
|
padding: 12
|
|
@@ -9,6 +9,11 @@ interface ActivityStats {
|
|
|
9
9
|
|
|
10
10
|
interface Props {
|
|
11
11
|
stats: ActivityStats
|
|
12
|
+
// Optional overrides for the section's and its rows' colors, passed straight through to StatSection and each
|
|
13
|
+
// StatRow (whose own defaults are the well's, see wellSurface.ts) - for a caller with its own chrome palette.
|
|
14
|
+
fg?: string
|
|
15
|
+
fgMuted?: string
|
|
16
|
+
sectionBg?: string
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
// The "ACTIVITY" StatSection (Days Played / Day Streak / Best Day Streak) - extracted verbatim
|
|
@@ -18,12 +23,12 @@ interface Props {
|
|
|
18
23
|
// currentDayStreak/bestDayStreak) rather than importing DayStreakState itself, so this component
|
|
19
24
|
// has no dependency on @tastic/achievements at all: any caller's own stats/statsView object that
|
|
20
25
|
// happens to carry these three fields already satisfies it structurally.
|
|
21
|
-
export function ActivityStatSection({ stats }: Props) {
|
|
26
|
+
export function ActivityStatSection({ stats, fg, fgMuted, sectionBg }: Props) {
|
|
22
27
|
return (
|
|
23
|
-
<StatSection label='ACTIVITY'>
|
|
24
|
-
<StatRow label='Days Played' value={String(stats.distinctDaysPlayed)} />
|
|
25
|
-
<StatRow label='Day Streak' value={String(stats.currentDayStreak)} />
|
|
26
|
-
<StatRow label='Best Day Streak' value={String(stats.bestDayStreak)} />
|
|
28
|
+
<StatSection label='ACTIVITY' fg={fg} sectionBg={sectionBg}>
|
|
29
|
+
<StatRow label='Days Played' value={String(stats.distinctDaysPlayed)} fg={fg} fgMuted={fgMuted} />
|
|
30
|
+
<StatRow label='Day Streak' value={String(stats.currentDayStreak)} fg={fg} fgMuted={fgMuted} />
|
|
31
|
+
<StatRow label='Best Day Streak' value={String(stats.bestDayStreak)} fg={fg} fgMuted={fgMuted} />
|
|
27
32
|
</StatSection>
|
|
28
33
|
)
|
|
29
34
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AutoAppearancePicker, Dialog, FadingScrollView, useAutoPaperTheme, useBlurModule, useThemeSettings } from '@rific/auto-paper'
|
|
2
|
+
import { useExpoKeepAwake, useKeepAwakeSettings } from '@rific/core'
|
|
2
3
|
import { Button, SoundContext, TouchableRipple, useHapticSettings, useSoundSettings, useVibration } from '@rific/feedback-press'
|
|
3
4
|
import { useUpdater } from '@rific/updater'
|
|
4
5
|
import { rotateInsets, useIsTouchPrimaryDevice, useRotation, type ViewRotation } from '@tastic/core'
|
|
@@ -56,13 +57,7 @@ export interface BaseSettingsDialogProps {
|
|
|
56
57
|
// @tastic/edge-guard's own swizzle is a no-op on every other platform.
|
|
57
58
|
deferBottomEdgeGestures?: boolean
|
|
58
59
|
onDeferBottomEdgeGestures?: (value: boolean) => void
|
|
59
|
-
//
|
|
60
|
-
// This dialog only shows and reports the toggle; the app itself keeps the screen on while it's set
|
|
61
|
-
// (expo-keep-awake on its match screen), so this package needs no native dependency for it. Shown
|
|
62
|
-
// on the same devices as Lock Orientation (see isTouchPrimary below): a desktop/laptop browser
|
|
63
|
-
// stays awake on its own while someone is playing with its keyboard or mouse.
|
|
64
|
-
keepAwake?: boolean
|
|
65
|
-
onKeepAwakeChange?: (value: boolean) => void
|
|
60
|
+
// No Keep Awake props: that row reads @rific/core's Keep Awake setting itself (see showKeepAwake).
|
|
66
61
|
// Explicit opt-outs for the rows that pull their own state straight from @rific/feedback-press,
|
|
67
62
|
// @rific/auto-paper, and @rific/updater's own shared contexts/hooks rather than from anything the
|
|
68
63
|
// caller passes in - unlike Lock Orientation/Edge Guard above, there's no "value" to omit for
|
|
@@ -97,7 +92,7 @@ export interface BaseSettingsDialogProps {
|
|
|
97
92
|
// is the seam for whatever isn't shared: a game with its own board/difficulty settings (or a stats
|
|
98
93
|
// backup flow, or anything else genuinely specific to it) renders those below Appearance, not by
|
|
99
94
|
// forking this component.
|
|
100
|
-
export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures,
|
|
95
|
+
export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, onShowHowToPlay, children }: BaseSettingsDialogProps) {
|
|
101
96
|
const { colors } = useAutoPaperTheme()
|
|
102
97
|
const ambientRotation = useRotation()
|
|
103
98
|
const rotation = rotationOverride ?? ambientRotation
|
|
@@ -113,7 +108,7 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
|
|
|
113
108
|
// Dialog's own 32pt padding is simply overhung.
|
|
114
109
|
const insets = rotateInsets(useSafeAreaInsets(), rotation)
|
|
115
110
|
const sheetWidth = Math.min(overlayStyles.card.maxWidth, (turned ? window.height : window.width) - 2 * Math.max(insets.left, insets.right, SHEET_GUTTER))
|
|
116
|
-
// Also gates the Lock Orientation and Keep Awake rows below (alongside their own
|
|
111
|
+
// Also gates the Lock Orientation and Keep Awake rows below (alongside their own conditions) - true
|
|
117
112
|
// unconditionally on native (see the hook's own doc), so this only actually excludes a
|
|
118
113
|
// desktop/laptop browser, where a mouse-driven window has no physical orientation to lock
|
|
119
114
|
// against; a resize there would just make the setting silently stop reflowing the layout instead
|
|
@@ -121,6 +116,8 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
|
|
|
121
116
|
const isTouchPrimary = useIsTouchPrimaryDevice()
|
|
122
117
|
const { settings: hapticSettings, set: setHapticSettings } = useHapticSettings()
|
|
123
118
|
const { settings: soundSettings, set: setSoundSettings } = useSoundSettings()
|
|
119
|
+
const { settings: keepAwakeSettings, set: setKeepAwakeSettings } = useKeepAwakeSettings()
|
|
120
|
+
const expoKeepAwake = useExpoKeepAwake()
|
|
124
121
|
// Sound/Haptics toggle themselves: the ripple's automatic press feedback fires on onPressIn,
|
|
125
122
|
// before onPress applies the toggle, so it reflects the OLD enabled value - backwards from what
|
|
126
123
|
// a settings toggle should confirm (turning off would click/buzz, turning on would go silent).
|
|
@@ -160,7 +157,10 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
|
|
|
160
157
|
|
|
161
158
|
const showLockOrientation = isTouchPrimary && lockOrientation !== undefined && onLockOrientationChange !== undefined
|
|
162
159
|
const showEdgeGuard = Platform.OS === 'ios' && deferBottomEdgeGestures !== undefined && onDeferBottomEdgeGestures !== undefined
|
|
163
|
-
|
|
160
|
+
// Only in an app that opted in by handing expo-keep-awake to @rific/core's KeepAwakeProvider:
|
|
161
|
+
// without the module the setting would do nothing. On the same devices as Lock Orientation, since a
|
|
162
|
+
// desktop/laptop browser stays awake on its own while someone plays with its keyboard or mouse.
|
|
163
|
+
const showKeepAwake = isTouchPrimary && expoKeepAwake !== undefined
|
|
164
164
|
const showHaptics = Platform.OS !== 'web' && !hideHaptics
|
|
165
165
|
const showSound = !hideSound
|
|
166
166
|
const showAppearance = !hideAppearance
|
|
@@ -243,11 +243,11 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
|
|
|
243
243
|
</TouchableRipple>
|
|
244
244
|
)}
|
|
245
245
|
|
|
246
|
-
{/*
|
|
246
|
+
{/* The app's own useKeepAwake('game') does the keeping awake; this only flips the shared setting. */}
|
|
247
247
|
{showKeepAwake && (
|
|
248
|
-
<TouchableRipple onPress={() =>
|
|
248
|
+
<TouchableRipple onPress={() => setKeepAwakeSettings({ enabled: !keepAwakeSettings.enabled })} style={styles.toggleButton} accessibilityLabel={`Keep awake ${keepAwakeSettings.enabled ? 'on' : 'off'}`}>
|
|
249
249
|
<View style={styles.toggleContent}>
|
|
250
|
-
<SettingIcon source={
|
|
250
|
+
<SettingIcon source={keepAwakeSettings.enabled ? 'eye' : 'eye-closed'} color={keepAwakeSettings.enabled ? colors.tertiary : colors.onSurfaceVariant} containerColor={keepAwakeSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant} />
|
|
251
251
|
<View style={styles.flexShrink}>
|
|
252
252
|
<Text variant='bodyLarge' style={{ color: colors.onSurface }}>
|
|
253
253
|
Keep Awake
|
package/src/StatRow.tsx
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
1
|
-
import { useAutoPaperTheme } from '@rific/auto-paper'
|
|
2
1
|
import { StyleSheet, View } from 'react-native'
|
|
3
2
|
import { Text } from 'react-native-paper'
|
|
4
3
|
|
|
4
|
+
import { useWellColors } from './wellSurface'
|
|
5
|
+
|
|
5
6
|
interface Props {
|
|
6
7
|
label: string
|
|
7
8
|
value: string
|
|
8
|
-
// Independently optional overrides for this row's own text colors - default to the
|
|
9
|
-
//
|
|
10
|
-
// when omitted, so every existing caller renders identically to before.
|
|
9
|
+
// Independently optional overrides for this row's own text colors - default to the text colors of the well it
|
|
10
|
+
// sits in (wellSurface.ts: onSurface, onSurfaceVariant).
|
|
11
11
|
fg?: string
|
|
12
12
|
fgMuted?: string
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
// A single labeled stat - label on the left (muted), value on the right (bold, full-contrast).
|
|
16
16
|
// Extracted from LightCycles' own achievements.tsx, where this exact row shape backed every
|
|
17
|
-
// section (Overall/Vs CPU/Two Player/Activity). fg/fgMuted default to the
|
|
18
|
-
//
|
|
19
|
-
// each independently overridable via the matching prop for a caller with its own chrome palette.
|
|
17
|
+
// section (Overall/Vs CPU/Two Player/Activity). fg/fgMuted default to the text colors of the StatSection well it
|
|
18
|
+
// sits in, each independently overridable via the matching prop for a caller with its own chrome palette.
|
|
20
19
|
// Deliberately just label+value text, not a color swatch or profile chip - an app needing a row
|
|
21
|
-
// with a leading visual builds its own on top of
|
|
20
|
+
// with a leading visual builds its own on top of the same useWellColors() (see LightCycles' own
|
|
22
21
|
// ColorStatRow/ProfileRankingRow for an example), since what that visual even is varies per game.
|
|
23
22
|
export function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }: Props) {
|
|
24
|
-
const
|
|
25
|
-
const fg = fgOverride ??
|
|
26
|
-
const fgMuted = fgMutedOverride ??
|
|
23
|
+
const well = useWellColors()
|
|
24
|
+
const fg = fgOverride ?? well.fg
|
|
25
|
+
const fgMuted = fgMutedOverride ?? well.fgMuted
|
|
27
26
|
|
|
28
27
|
return (
|
|
29
28
|
<View style={styles.row}>
|
package/src/StatSection.tsx
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
1
|
-
import { useAutoPaperTheme } from '@rific/auto-paper'
|
|
2
1
|
import { ReactNode } from 'react'
|
|
3
2
|
import { StyleSheet, View } from 'react-native'
|
|
4
3
|
import { Text } from 'react-native-paper'
|
|
5
4
|
|
|
5
|
+
import { useWellColors, WELL_SHADOW } from './wellSurface'
|
|
6
|
+
|
|
6
7
|
interface Props {
|
|
7
8
|
label: string
|
|
8
|
-
// Independently optional overrides for this section's own label color/background - default to
|
|
9
|
-
// the
|
|
10
|
-
// own doc for why) when omitted, so every existing caller renders identically to before.
|
|
9
|
+
// Independently optional overrides for this section's own label color/background - default to the well's
|
|
10
|
+
// (wellSurface.ts: the theme's surface and its onSurface text).
|
|
11
11
|
fg?: string
|
|
12
12
|
sectionBg?: string
|
|
13
13
|
children: ReactNode
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
// A labeled
|
|
17
|
-
// shape from LightCycles' own achievements.tsx, generalized to any
|
|
18
|
-
// StatRow). fg/sectionBg default to the
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// rendering the string it's given.
|
|
16
|
+
// A labeled grouping box, a well (wellSurface.ts: the theme's surface on MD3's level-1 shadow) - the
|
|
17
|
+
// "OVERALL"/"VS CPU"/"ACTIVITY"-style section shape from LightCycles' own achievements.tsx, generalized to any
|
|
18
|
+
// content (typically a stack of StatRow). fg/sectionBg default to the well's colors, each independently
|
|
19
|
+
// overridable via the matching prop for a caller with its own chrome palette. It was a faint tint until
|
|
20
|
+
// 2026-10-17 (6% white in dark mode, 4% black in light). The label itself carries the section's own meaning
|
|
21
|
+
// (what it's a section OF), so this component has no opinion on that beyond rendering the string it's given.
|
|
23
22
|
export function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, children }: Props) {
|
|
24
|
-
const
|
|
25
|
-
const fg = fgOverride ??
|
|
26
|
-
const sectionBg = sectionBgOverride ??
|
|
23
|
+
const well = useWellColors()
|
|
24
|
+
const fg = fgOverride ?? well.fg
|
|
25
|
+
const sectionBg = sectionBgOverride ?? well.bg
|
|
27
26
|
|
|
28
27
|
return (
|
|
29
28
|
<View style={[styles.section, { backgroundColor: sectionBg }]}>
|
|
@@ -46,6 +45,7 @@ const styles = StyleSheet.create({
|
|
|
46
45
|
},
|
|
47
46
|
section: {
|
|
48
47
|
borderRadius: 12,
|
|
48
|
+
boxShadow: WELL_SHADOW,
|
|
49
49
|
gap: 8,
|
|
50
50
|
padding: 16
|
|
51
51
|
}
|
package/src/chrome/index.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// The fleet-chrome entry point (`@tastic/hud/chrome`): the shared dialogs, stats screen pieces, corner
|
|
2
|
-
// buttons and style tokens, reachable without the main barrel, for an
|
|
3
|
-
// peers. Hangman is the reason it exists: it has @tastic/core,
|
|
4
|
-
// no Skia, no @tastic/profile and no @tastic/split-screen.
|
|
5
|
-
//
|
|
2
|
+
// buttons, popover primitives, color picker and style tokens, reachable without the main barrel, for an
|
|
3
|
+
// app that has none of its heavy peers. Hangman is the reason it exists: it has @tastic/core,
|
|
4
|
+
// @rific/updater and the Paper stack, but no Skia, no @tastic/profile and no @tastic/split-screen. The
|
|
5
|
+
// popover pieces (PopoverBody, PressAwayOverlay, usePopoverHost, useAutoAlign) and InlineColorPicker are
|
|
6
|
+
// here for @tastic/profile 0.16+, which imports them from this entry instead of the main barrel so its
|
|
7
|
+
// ProfilePicker and ProfilesScreen work in Hangman. Everything reachable from this file must stay free of
|
|
8
|
+
// those three and of the main barrel; @tastic/achievements may appear only as `import type`.
|
|
6
9
|
// AchievementUnlockList is left out on purpose because it value-imports @tastic/profile's ProfileChip.
|
|
7
10
|
// src/__tests__/chromeImports.test.ts walks the real import graph from here and enforces all of it.
|
|
8
11
|
export { AchievementCatalogSection } from '../AchievementCatalogSection'
|
|
@@ -15,11 +18,17 @@ export { ContentGutter } from '../ContentGutter'
|
|
|
15
18
|
export { CornerActionButtons } from '../CornerActionButtons'
|
|
16
19
|
export { COMPACT_DIALOG_HEIGHT, DialogHeader, type DialogHeaderProps, isCompactDialogHeight } from '../DialogHeader'
|
|
17
20
|
export { MONO_FONT } from '../fonts'
|
|
21
|
+
export { getInlineColorPickerContentSize, InlineColorPicker } from '../InlineColorPicker'
|
|
18
22
|
export { overlayActionStyles, overlayStyles } from '../overlayCard'
|
|
19
23
|
export { OverlayBackdrop, type OverlayBackdropProps, OverlayCard, type OverlayCardProps, type OverlayCardThemeColors, useOverlayCardColors } from '../OverlaySurface'
|
|
24
|
+
export { PopoverBody } from '../PopoverBody'
|
|
25
|
+
export { PressAwayOverlay } from '../PressAwayOverlay'
|
|
20
26
|
export { RotatedFrame, type RotatedFrameProps } from '../RotatedFrame'
|
|
21
27
|
export { getStaggeredWordDuration, StaggeredWord, type StaggeredWordProps } from '../StaggeredWord'
|
|
22
28
|
export { StatRow } from '../StatRow'
|
|
23
29
|
export { StatSection } from '../StatSection'
|
|
24
30
|
export { UpdateDialog, type UpdateDialogProps } from '../UpdateDialog'
|
|
31
|
+
export { type AlignResult, type PopoverAlign, type PopoverRotation, type PopoverVerticalAlign, useAutoAlign } from '../useAutoAlign'
|
|
25
32
|
export { useCompactDialog } from '../useCompactDialog'
|
|
33
|
+
export { type PopoverHost, usePopoverHost } from '../usePopoverHost'
|
|
34
|
+
export { useWellColors, WELL_SHADOW, type WellColors } from '../wellSurface'
|
package/src/index.ts
CHANGED
|
@@ -42,3 +42,4 @@ export { useCompactDialog } from './useCompactDialog'
|
|
|
42
42
|
export { type PopoverHost, usePopoverHost } from './usePopoverHost'
|
|
43
43
|
export { type QuitConfirmation, useQuitConfirmation } from './useQuitConfirmation'
|
|
44
44
|
export { useZoneClampedAlign } from './useZoneClampedAlign'
|
|
45
|
+
export { useWellColors, WELL_SHADOW, type WellColors } from './wellSurface'
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useAutoPaperTheme } from '@rific/auto-paper'
|
|
2
|
+
|
|
3
|
+
// A well is a resting card on a screen: a stats section, an achievement row, a game's menu tile. Every well in the
|
|
4
|
+
// fleet is the theme's plain surface (the same surface as the dialogs, OverlayCard and auto-paper's Dialog) lifted off
|
|
5
|
+
// the screen by MD3's level-1 shadow, with no elevation tint (Jay, 2026-10-17: Solitaire's Achievements wells read as
|
|
6
|
+
// a different shade from its Home tiles, and he picked "shadow, no tint" over MD3's tonal elevation, which washes the
|
|
7
|
+
// surface with the primary like the dialogs no longer are).
|
|
8
|
+
//
|
|
9
|
+
// The shadow is what makes a well show on a screen the color of the surface: MD3 (as react-native-paper implements it)
|
|
10
|
+
// gives `background` and `surface` the same tone, and BaseStatsScreen's light-mode #FFFFFF is the same as the light
|
|
11
|
+
// surface to the eye. In dark mode the surface is already lighter than a black screen, and the shadow barely shows.
|
|
12
|
+
|
|
13
|
+
// MD3's level-1 elevation shadow (its web spec's two layers: a tight key shadow and a soft ambient one). boxShadow is
|
|
14
|
+
// drawn outside the view on iOS, Android and web alike, so a well's own background is never darkened by it.
|
|
15
|
+
export const WELL_SHADOW = '0px 1px 2px rgba(0, 0, 0, 0.3), 0px 1px 3px 1px rgba(0, 0, 0, 0.15)'
|
|
16
|
+
|
|
17
|
+
export interface WellColors {
|
|
18
|
+
// The well's own background.
|
|
19
|
+
bg: string
|
|
20
|
+
// Text on the well: titles and values, then labels and descriptions.
|
|
21
|
+
fg: string
|
|
22
|
+
fgMuted: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// The colors of a well and the text on it, from the app's own theme: the same roles the dialogs' text uses
|
|
26
|
+
// (useOverlayCardColors).
|
|
27
|
+
export function useWellColors(): WellColors {
|
|
28
|
+
const { colors } = useAutoPaperTheme()
|
|
29
|
+
return { bg: colors.surface, fg: colors.onSurface, fgMuted: colors.onSurfaceVariant }
|
|
30
|
+
}
|