@tastic/hud 0.16.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 CHANGED
@@ -76,6 +76,11 @@ corner of the screen.
76
76
  out after being independently copied into several games. See Rotation below for how each of these
77
77
  (plus `SectionedDropdown`/`InlineColorPicker`) handles rotating inside a `@tastic/split-screen`
78
78
  zone.
79
+ - **`OverlayCard`** / **`OverlayBackdrop`** / **`useOverlayCardColors`** - the card and dim behind
80
+ `ConfirmDialog` and the how-to-play guide, in the app's own theme colors and blurred when the
81
+ player's Blur setting allows it. See Overlay cards below.
82
+ - **`DialogHeader`** / **`useCompactDialog`** - the one header every dialog opens with (an icon over
83
+ a bold title in the theme's primary), and the rule for its compact form. See Overlay cards below.
79
84
 
80
85
  ## Rotation
81
86
 
@@ -235,6 +240,54 @@ export function SettingsDialog({ visible, onDismiss, rotation = 0, settings, set
235
240
  }
236
241
  ```
237
242
 
243
+ ## Overlay cards (`OverlayCard`, `OverlayBackdrop`, `useOverlayCardColors`)
244
+
245
+ `ConfirmDialog` and `HowToPlayDialog` draw their card and the dim behind it with these, and a game's
246
+ own overlay (a result banner, a game-over card) should too, so every card in the fleet looks the same:
247
+
248
+ ```tsx
249
+ import { DialogHeader, OverlayBackdrop, OverlayCard, overlayActionStyles, useCompactDialog, useOverlayCardColors } from '@tastic/hud'
250
+
251
+ function GameOverCard({ onRestart }: { onRestart: () => void }) {
252
+ const { fgMuted } = useOverlayCardColors()
253
+ const { compact, onLayout } = useCompactDialog()
254
+ return (
255
+ <OverlayBackdrop onLayout={onLayout}>
256
+ <OverlayCard accessibilityViewIsModal>
257
+ <DialogHeader icon='flag-checkered' title='Game Over' compact={compact} />
258
+ <Text style={{ color: fgMuted }}>Score 42</Text>
259
+ <View style={overlayActionStyles.actions}>...</View>
260
+ </OverlayCard>
261
+ </OverlayBackdrop>
262
+ )
263
+ }
264
+ ```
265
+
266
+ - **Colors come from the app's theme.** Text is `onSurface` (`fg`) and `onSurfaceVariant` (`fgMuted`),
267
+ and the dim is the theme's `backdrop`, the same scrim auto-paper's own `Dialog` uses. The card is the
268
+ Settings sheet's surface: auto-paper's `BlurView` with no elevation (the theme's plain surface, no
269
+ wash of the primary), no border, and the `Dialog`'s corner radius. So every card and the Settings
270
+ sheet look like one family, in each game's palette and light/dark mode.
271
+ - **Every dialog opens with `DialogHeader`.** A 64pt icon over a large bold title, both in the theme's
272
+ primary (pass `color` for another role, as `ConfirmDialog` does with danger when `destructive`).
273
+ `ConfirmDialog`, the how-to-play guide (titled "How to Play") and `BaseSettingsDialog` all use it;
274
+ an app's own cards should too. With `compact` the icon shrinks to 32pt and sits beside a smaller
275
+ title. `useCompactDialog(rotation?)` decides it: put its `onLayout` on the `OverlayBackdrop`, and it
276
+ is compact when the space the dialog lays out in is under `COMPACT_DIALOG_HEIGHT` (528pt) along the
277
+ card's own vertical axis, so a phone held sideways. One-button dialogs end in a single contained
278
+ primary button (`overlayActionStyles.actionButtonSingle`), the Settings sheet's Done included.
279
+ - **Blur is automatic.** The card blurs what's behind it when the player's Blur setting is on and the
280
+ app hands `expo-blur` to auto-paper's `Provider` (`expoBlur={ExpoBlur}`). Otherwise it paints that
281
+ raised surface color instead: on Android (`BlurView` needs a blur target there, which a card has
282
+ none of), in an app without `expo-blur`, or with Blur off. Beyond passing `expoBlur`, a game wires
283
+ nothing for this.
284
+ - **The Blur setting lives in `BaseSettingsDialog`.** Its APPEARANCE section shows a Blur row under the
285
+ appearance picker that flips auto-paper's own `blur` theme setting, which the settings sheet, Paper
286
+ menus and these cards all follow. The row only shows where blur can actually happen: not on
287
+ Android, not in an app that never passed `expoBlur`, and not with `hideAppearance`.
288
+ - **`getOverlayCardColors` is gone.** It returned the old fixed black/white card (`#F2F2F2`/`#111111`),
289
+ which ignored the theme and never blurred. Use `useOverlayCardColors` for a card's text and edges.
290
+
238
291
  ## Zone-aware popovers (`useZoneClampedAlign`)
239
292
 
240
293
  `useAutoAlign` - and everything built on it, including `SectionedDropdown`, `InlineColorPicker`, and
@@ -565,7 +618,8 @@ touches the main barrel. It's for an app that has `@tastic/core` and `@rific/upd
565
618
  - Stats: `BaseStatsScreen`, `StatSection`, `StatRow`, `AchievementRow`, `LOCKED_BADGE_COLOR`,
566
619
  `AchievementCatalogSection`, `ActivityStatSection`
567
620
  - Chrome: `CornerActionButtons`, `ContentGutter`, `StaggeredWord`, `getStaggeredWordDuration`
568
- - Style tokens: `getOverlayCardColors`, `overlayStyles`, `overlayActionStyles`, `MONO_FONT`
621
+ - Overlay cards: `OverlayCard`, `OverlayBackdrop`, `useOverlayCardColors`, `DialogHeader`, `useCompactDialog`, `isCompactDialogHeight`, `COMPACT_DIALOG_HEIGHT`
622
+ - Style tokens: `overlayStyles`, `overlayActionStyles`, `MONO_FONT`
569
623
 
570
624
  Each is the same component the main entry exports and is documented above. Only the import path changes:
571
625
 
@@ -619,12 +673,12 @@ resolve module".
619
673
  | `react`, `react-native` | >=19.0.0, >=0.76.0 | every entry |
620
674
  | `react-native-paper` | >=5.0.0 | main entry, `./guide`, `./chrome` |
621
675
  | `react-native-reanimated` | >=3.0.0 | main entry (`StaggeredWord`), `./guide`, `./chrome` (`StaggeredWord`) |
622
- | `@rific/auto-paper` | >=0.12.2 | main entry (`BaseSettingsDialog` needs the nested `Portal.Host` first shipped in 0.12.2), `./guide`, `./chrome` |
676
+ | `@rific/auto-paper` | >=0.12.2 | main entry (`BaseSettingsDialog` needs the nested `Portal.Host` first shipped in 0.12.2; `OverlayCard` uses `BlurView` and `useBlur`, and `BaseSettingsDialog`'s Blur row uses `useBlurModule` and `useThemeSettings`, all in 0.12.2), `./guide`, `./chrome` |
623
677
  | `@rific/feedback-press` | >=0.10.2 | main entry (`useSoundSettings` first shipped in 0.10.2), `./guide`, `./chrome` |
624
678
  | `react-native-safe-area-context` | >=5.0.0 | main entry (`useZoneClampedAlign`), `./chrome` (`RotatedFrame`, `BaseSettingsDialog`) |
625
679
  | `@tastic/core` | >=0.12.0, optional | main entry: `useRotation()` and most dialogs; `RotatedFrame` and `BaseSettingsDialog` need `FakeLandscapeView`'s `rotation`/`passThrough`, first shipped in 0.12.0; `./chrome` (the same dialogs) |
626
680
  | `@rific/updater` | >=0.4.0, optional | main entry and `./chrome`: `BaseSettingsDialog`, `UpdateDialog` |
627
681
  | `@tastic/profile` | >=0.4.0, optional | main entry: `PlayerSetupPanel`, `AchievementUnlockList` (`ProfileChip`) |
628
682
  | `@tastic/split-screen` | >=0.5.0, optional | main entry: `useZoneClampedAlign` |
629
- | `@shopify/react-native-skia` | >=1.5.0, optional | `TriggerGaugeHost`'s gauge (lazy-loaded behind `SkiaGate`) and `./skia-gate` |
683
+ | `@shopify/react-native-skia` | >=2.6.0, optional | `TriggerGaugeHost`'s gauge (lazy-loaded behind `SkiaGate`) and `./skia-gate` |
630
684
  | `@tastic/achievements` | >=0.2.0, optional | `AchievementCatalogSection`, `AchievementUnlockList`, type-only (`AchievementCatalogRow`, first exported in 0.2.0; `AchievementTier`); `./chrome` needs it installed only to type-check |
@@ -54,7 +54,7 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
54
54
  const canvasSize = diameter + LIT_THICKNESS;
55
55
  const oval = Skia.XYWHRect(LIT_THICKNESS / 2, LIT_THICKNESS / 2, diameter, diameter);
56
56
  const unlitPath = useDerivedValue(() => {
57
- const path = Skia.Path.Make();
57
+ const path = Skia.PathBuilder.Make();
58
58
  for (let i = 0; i < segments; i++) {
59
59
  if (litIndices.includes(i)) continue;
60
60
  let local = 1;
@@ -68,13 +68,13 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
68
68
  const dashCenterDeg = startDeg + (i + 0.5) * step;
69
69
  path.addArc(oval, dashCenterDeg - 90 - sweep / 2, sweep);
70
70
  }
71
- return path;
71
+ return path.detach();
72
72
  }, [segments, litIndicesKey, step, startDeg, dashAngleDeg, totalDurationMs, oval.x, oval.y, oval.width, oval.height]);
73
73
  const litPath = useDerivedValue(() => {
74
- const path = Skia.Path.Make();
74
+ const path = Skia.PathBuilder.Make();
75
75
  if (mountProgress.value >= 1 && singleSelect) {
76
76
  path.addArc(oval, angleDeg.value - 90 - dashAngleDeg / 2, dashAngleDeg);
77
- return path;
77
+ return path.detach();
78
78
  }
79
79
  for (let i = 0; i < segments; i++) {
80
80
  const isLitNow = litIndices.includes(i);
@@ -98,7 +98,7 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
98
98
  const dashCenterDeg = startDeg + (i + 0.5) * step;
99
99
  path.addArc(oval, dashCenterDeg - 90 - sweep / 2, sweep);
100
100
  }
101
- return path;
101
+ return path.detach();
102
102
  }, [segments, litIndicesKey, transitionFromKey, singleSelect, step, startDeg, dashAngleDeg, totalDurationMs, oval.x, oval.y, oval.width, oval.height]);
103
103
  if (segments < 2) return null;
104
104
  const gaugeSizeStyle = {
@@ -2,7 +2,7 @@ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import { AchievementCatalogRow } from '@tastic/achievements';
4
4
  import { ViewRotation } from '@tastic/core';
5
- import { StyleProp, ViewStyle, Insets, TextStyle, LayoutChangeEvent } from 'react-native';
5
+ import { StyleProp, ViewStyle, Insets, LayoutChangeEvent, TextStyle } from 'react-native';
6
6
 
7
7
  interface Props$7 {
8
8
  rows: AchievementCatalogRow[];
@@ -117,20 +117,23 @@ interface Props$2 {
117
117
  }
118
118
  declare function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon, backLabel, hitSlop }: Props$2): react.JSX.Element;
119
119
 
120
+ declare const COMPACT_DIALOG_HEIGHT = 528;
121
+ declare function isCompactDialogHeight(height: number): boolean;
122
+ interface DialogHeaderProps {
123
+ icon: string;
124
+ title: string;
125
+ color?: string;
126
+ compact?: boolean;
127
+ style?: StyleProp<ViewStyle>;
128
+ }
129
+ declare function DialogHeader({ icon, title, color, compact, style }: DialogHeaderProps): react.JSX.Element;
130
+
120
131
  declare const MONO_FONT: string;
121
132
 
122
- interface OverlayCardColors {
123
- fg: string;
124
- fgMuted: string;
125
- cardBg: string;
126
- cardBorder: string;
127
- }
128
- declare function getOverlayCardColors(dark: boolean): OverlayCardColors;
129
133
  declare const overlayStyles: {
130
134
  card: {
131
135
  alignItems: "center";
132
136
  borderRadius: number;
133
- borderWidth: number;
134
137
  gap: number;
135
138
  maxWidth: number;
136
139
  padding: number;
@@ -163,6 +166,27 @@ declare const overlayActionStyles: {
163
166
  };
164
167
  };
165
168
 
169
+ interface OverlayCardThemeColors {
170
+ fg: string;
171
+ fgMuted: string;
172
+ cardBorder: string;
173
+ backdrop: string;
174
+ }
175
+ declare function useOverlayCardColors(): OverlayCardThemeColors;
176
+ interface OverlayBackdropProps {
177
+ children?: ReactNode;
178
+ style?: StyleProp<ViewStyle>;
179
+ onLayout?: (e: LayoutChangeEvent) => void;
180
+ }
181
+ declare function OverlayBackdrop({ children, style, onLayout }: OverlayBackdropProps): react.JSX.Element;
182
+ interface OverlayCardProps {
183
+ children?: ReactNode;
184
+ style?: StyleProp<ViewStyle>;
185
+ accessibilityViewIsModal?: boolean;
186
+ testID?: string;
187
+ }
188
+ declare function OverlayCard({ children, style, ...rest }: OverlayCardProps): react.JSX.Element;
189
+
166
190
  interface RotatedFrameProps {
167
191
  children: ReactNode;
168
192
  /** The angle to turn the frame by. Defaults to the live physical hold (@tastic/core's useRotation); pass it
@@ -237,4 +261,9 @@ interface UpdateDialogProps {
237
261
  }
238
262
  declare function UpdateDialog({ rotation, autoCheck, autoPrompt, onError }: UpdateDialogProps): react.JSX.Element;
239
263
 
240
- export { AchievementCatalogSection, AchievementRow, ActivityStatSection, BaseSettingsDialog, type BaseSettingsDialogProps, BaseStatsScreen, ConfirmDialog, type ConfirmDialogProps, ContentGutter, CornerActionButtons, LOCKED_BADGE_COLOR, MONO_FONT, type OverlayCardColors, RotatedFrame, type RotatedFrameProps, StaggeredWord, type StaggeredWordProps, StatRow, StatSection, UpdateDialog, type UpdateDialogProps, getOverlayCardColors, getStaggeredWordDuration, overlayActionStyles, overlayStyles };
264
+ declare function useCompactDialog(rotation?: ViewRotation): {
265
+ compact: boolean;
266
+ onLayout: (e: LayoutChangeEvent) => void;
267
+ };
268
+
269
+ export { AchievementCatalogSection, AchievementRow, ActivityStatSection, BaseSettingsDialog, type BaseSettingsDialogProps, BaseStatsScreen, COMPACT_DIALOG_HEIGHT, ConfirmDialog, type ConfirmDialogProps, ContentGutter, CornerActionButtons, DialogHeader, type DialogHeaderProps, LOCKED_BADGE_COLOR, MONO_FONT, OverlayBackdrop, type OverlayBackdropProps, OverlayCard, type OverlayCardProps, type OverlayCardThemeColors, RotatedFrame, type RotatedFrameProps, StaggeredWord, type StaggeredWordProps, StatRow, StatSection, UpdateDialog, type UpdateDialogProps, getStaggeredWordDuration, isCompactDialogHeight, overlayActionStyles, overlayStyles, useCompactDialog, useOverlayCardColors };
@@ -2,7 +2,7 @@ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import { AchievementCatalogRow } from '@tastic/achievements';
4
4
  import { ViewRotation } from '@tastic/core';
5
- import { StyleProp, ViewStyle, Insets, TextStyle, LayoutChangeEvent } from 'react-native';
5
+ import { StyleProp, ViewStyle, Insets, LayoutChangeEvent, TextStyle } from 'react-native';
6
6
 
7
7
  interface Props$7 {
8
8
  rows: AchievementCatalogRow[];
@@ -117,20 +117,23 @@ interface Props$2 {
117
117
  }
118
118
  declare function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon, backLabel, hitSlop }: Props$2): react.JSX.Element;
119
119
 
120
+ declare const COMPACT_DIALOG_HEIGHT = 528;
121
+ declare function isCompactDialogHeight(height: number): boolean;
122
+ interface DialogHeaderProps {
123
+ icon: string;
124
+ title: string;
125
+ color?: string;
126
+ compact?: boolean;
127
+ style?: StyleProp<ViewStyle>;
128
+ }
129
+ declare function DialogHeader({ icon, title, color, compact, style }: DialogHeaderProps): react.JSX.Element;
130
+
120
131
  declare const MONO_FONT: string;
121
132
 
122
- interface OverlayCardColors {
123
- fg: string;
124
- fgMuted: string;
125
- cardBg: string;
126
- cardBorder: string;
127
- }
128
- declare function getOverlayCardColors(dark: boolean): OverlayCardColors;
129
133
  declare const overlayStyles: {
130
134
  card: {
131
135
  alignItems: "center";
132
136
  borderRadius: number;
133
- borderWidth: number;
134
137
  gap: number;
135
138
  maxWidth: number;
136
139
  padding: number;
@@ -163,6 +166,27 @@ declare const overlayActionStyles: {
163
166
  };
164
167
  };
165
168
 
169
+ interface OverlayCardThemeColors {
170
+ fg: string;
171
+ fgMuted: string;
172
+ cardBorder: string;
173
+ backdrop: string;
174
+ }
175
+ declare function useOverlayCardColors(): OverlayCardThemeColors;
176
+ interface OverlayBackdropProps {
177
+ children?: ReactNode;
178
+ style?: StyleProp<ViewStyle>;
179
+ onLayout?: (e: LayoutChangeEvent) => void;
180
+ }
181
+ declare function OverlayBackdrop({ children, style, onLayout }: OverlayBackdropProps): react.JSX.Element;
182
+ interface OverlayCardProps {
183
+ children?: ReactNode;
184
+ style?: StyleProp<ViewStyle>;
185
+ accessibilityViewIsModal?: boolean;
186
+ testID?: string;
187
+ }
188
+ declare function OverlayCard({ children, style, ...rest }: OverlayCardProps): react.JSX.Element;
189
+
166
190
  interface RotatedFrameProps {
167
191
  children: ReactNode;
168
192
  /** The angle to turn the frame by. Defaults to the live physical hold (@tastic/core's useRotation); pass it
@@ -237,4 +261,9 @@ interface UpdateDialogProps {
237
261
  }
238
262
  declare function UpdateDialog({ rotation, autoCheck, autoPrompt, onError }: UpdateDialogProps): react.JSX.Element;
239
263
 
240
- export { AchievementCatalogSection, AchievementRow, ActivityStatSection, BaseSettingsDialog, type BaseSettingsDialogProps, BaseStatsScreen, ConfirmDialog, type ConfirmDialogProps, ContentGutter, CornerActionButtons, LOCKED_BADGE_COLOR, MONO_FONT, type OverlayCardColors, RotatedFrame, type RotatedFrameProps, StaggeredWord, type StaggeredWordProps, StatRow, StatSection, UpdateDialog, type UpdateDialogProps, getOverlayCardColors, getStaggeredWordDuration, overlayActionStyles, overlayStyles };
264
+ declare function useCompactDialog(rotation?: ViewRotation): {
265
+ compact: boolean;
266
+ onLayout: (e: LayoutChangeEvent) => void;
267
+ };
268
+
269
+ export { AchievementCatalogSection, AchievementRow, ActivityStatSection, BaseSettingsDialog, type BaseSettingsDialogProps, BaseStatsScreen, COMPACT_DIALOG_HEIGHT, ConfirmDialog, type ConfirmDialogProps, ContentGutter, CornerActionButtons, DialogHeader, type DialogHeaderProps, LOCKED_BADGE_COLOR, MONO_FONT, OverlayBackdrop, type OverlayBackdropProps, OverlayCard, type OverlayCardProps, type OverlayCardThemeColors, RotatedFrame, type RotatedFrameProps, StaggeredWord, type StaggeredWordProps, StatRow, StatSection, UpdateDialog, type UpdateDialogProps, getStaggeredWordDuration, isCompactDialogHeight, overlayActionStyles, overlayStyles, useCompactDialog, useOverlayCardColors };