@tastic/hud 0.12.0 → 0.13.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
@@ -409,9 +409,108 @@ return (
409
409
  )
410
410
  ```
411
411
 
412
- `AchievementCatalogSection` is the only place in this package with a `@tastic/achievements`
413
- dependency, and even there it's type-only (`AchievementCatalogRow`) — never a value/runtime
414
- import. See Peer Dependencies below.
412
+ `AchievementCatalogSection` and `AchievementUnlockList` (below) are the only places in this package
413
+ with a `@tastic/achievements` dependency, and both are type-only (`AchievementCatalogRow`,
414
+ `AchievementTier`) — never a value/runtime import. See Peer Dependencies below.
415
+
416
+ ## Achievement unlocks on a result card (`AchievementUnlockList`)
417
+
418
+ The achievements a match just unlocked, for a game-over or round-over card: one row per
419
+ achievement, each with its owner's avatar (the seat's `ProfileChip`, or a plain avatar in the seat's
420
+ color for a guest), a tier-colored icon badge, and the title. Rows are listed seat by seat. Renders
421
+ nothing when no seat unlocked anything, so it can sit in the card unconditionally.
422
+
423
+ ```tsx
424
+ import { ACHIEVEMENT_TIER_COLORS } from '@tastic/achievements'
425
+ import { AchievementUnlockList } from '@tastic/hud'
426
+
427
+ <AchievementUnlockList
428
+ unlocks={seatUnlocks} // Partial<Record<Seat, { id, icon, title, tier }[]>>
429
+ seats={[1, 2]}
430
+ owners={{ 1: { profile: p1Profile, color: p1Color }, 2: { profile: null, color: p2Color } }}
431
+ tierColors={ACHIEVEMENT_TIER_COLORS} // a prop, so @tastic/achievements stays type-only here
432
+ />
433
+ ```
434
+
435
+ Optional `fg` (title color, defaults from the theme) and `style` (the list container).
436
+
437
+ For muted profile colors, mount `@tastic/profile`'s `ProfileColorProvider`: the `ProfileChip`s here
438
+ pick it up. A guest seat's avatar draws the `color` you pass as-is, so pass it already muted.
439
+
440
+ ## How-to-play guide (`@tastic/hud/guide`)
441
+
442
+ A short, skippable, paged "how to play" card that shows once on a fresh install and can be replayed on
443
+ demand from Settings. Import it from the **`@tastic/hud/guide` subpath**, not the main barrel: it's a
444
+ standalone build that pulls in only `react`, `react-native`, `react-native-paper`,
445
+ `react-native-reanimated`, `@rific/auto-paper` and `@rific/feedback-press` — none of Skia,
446
+ `@tastic/core`, `@tastic/profile`, `@tastic/split-screen`, `@tastic/achievements` or `@rific/updater`
447
+ (all marked optional in `peerDependenciesMeta`), so an app that has none of them can use it. A test
448
+ (`src/__tests__/guideImports.test.ts`) fails if any file under `src/guide/` ever imports one.
449
+
450
+ ```tsx
451
+ import { createGuideSlice, GuideProvider, SeatDiagram, SwipeHint, useAutoShowGuide, useGuide } from '@tastic/hud/guide'
452
+
453
+ // 1. A persisted "seen" version — its own slice, mounted under `guide` in your root reducer.
454
+ const guide = createGuideSlice({ currentVersion: 1 }) // { actions.markSeen, reducer, selectVersionSeen }
455
+
456
+ // 2. Once, near the root, wrapping your screens (inside PersistGate + your theme/Paper providers).
457
+ // `rotation` is your game's live fake-landscape rotation (useRotation / getViewRotation).
458
+ <GuideProvider
459
+ steps={[{ title: 'Steer Your Cycle', body: 'Swipe up, down, left or right.', art: <SwipeHint direction='up' /> }]}
460
+ currentVersion={1}
461
+ seenVersion={useSelector(guide.selectVersionSeen)}
462
+ onSeen={(v) => dispatch(guide.actions.markSeen(v))}
463
+ rotation={rotation}
464
+ >
465
+ <Screens />
466
+ </GuideProvider>
467
+
468
+ // 3. On the Home screen, and nowhere else: opens it once per session for a player who hasn't seen it.
469
+ useAutoShowGuide()
470
+
471
+ // 4. Replay from Settings — BaseSettingsDialog renders a "How to Play" row when this is passed.
472
+ const { open } = useGuide()
473
+ <BaseSettingsDialog {...props} onShowHowToPlay={open} />
474
+ ```
475
+
476
+ - **Existing players are grandfathered.** On a fresh install redux-persist's `REHYDRATE` has no payload;
477
+ an existing player has a payload that simply predates the `guide` key. `createGuideSlice` stamps the
478
+ second with `currentVersion` instead of `0`, so an OTA update never greets them with a tutorial (they
479
+ can still replay it). Only finishing or skipping writes; a replay never does. Bump `currentVersion`
480
+ only for a real controls/rules change — everyone sees it again.
481
+ - **If your last shipped build predates your redux-persist store, you also need the migrate.** Those
482
+ players have no root store yet, which looks exactly like a fresh install. Pass the AsyncStorage keys the
483
+ shipped build wrote (read them from the last shipped commit, not today's source):
484
+
485
+ ```ts
486
+ const persistConfig = {
487
+ key: 'root',
488
+ storage: AsyncStorage,
489
+ migrate: createReturningPlayerMigrate(AsyncStorage, ['pong.settings', 'pong.stats', 'pong.achievements'])
490
+ }
491
+ ```
492
+
493
+ No root store but any of those keys present means "played an earlier build": that launch rehydrates
494
+ with an empty payload, which the slice grandfathers. An existing root store passes through untouched.
495
+ - **Home only.** Call `useAutoShowGuide()` from the Home screen — that placement is what keeps it from
496
+ firing over a live match or a two-seat loadout, where a single centered card can't be read right-way-up
497
+ by both players. Pass `false` to hold it back (e.g. while a deep link is being handled).
498
+ - **Leave the row off during a live match.** Omit `onShowHowToPlay` on any screen where Settings pauses or
499
+ overlays a running game.
500
+ - **Content is the app's.** A `GuideStep` is `{ title, body, art? }`; only the app knows its own controls
501
+ and win condition. Shared illustrations for the arcade games: `SwipeHint` (animated finger sweeping
502
+ toward an arrowhead; `pointer='mouse'` for a desktop seat that plays by dragging), `DirectionKeysHint`
503
+ (an inverted-T of key caps, its keyboard counterpart; pass the seat's real keys as `labels`, arrow
504
+ icons when omitted, `axis='vertical'` for up/down-only games), `SeatDiagram` (a phone split into two
505
+ seat-colored halves) and `SeatDevicesHint` (its desktop counterpart: a keyboard and/or mouse, each in
506
+ the color of the seat using it). All take their colors from the theme's primary/secondary by default. Give every
507
+ card an illustration: the pager sizes all pages to the tallest, so a card without one leaves a gap.
508
+ - **Fleet copy conventions.** Title Case titles ("Last One Riding Wins"), one-word buttons. The action
509
+ row is ConfirmDialog's (`overlayActionStyles`): outlined Skip (first card only) or Back on the left,
510
+ contained Next on the right, `Ready` on the last card. None of that is configurable: every game gets the identical
511
+ dialog and supplies only its cards (Jay, 2026-09-24: "only their content should be different"). Short bodies,
512
+ and only say what's true in every mode ("your half" is only true in two-player).
513
+ - **Not `onboarding`.** LightCycles and Snake already use that word for their 3-2-1-GO countdown.
415
514
 
416
515
  ## Install
417
516
 
@@ -423,13 +522,35 @@ self-reading rotation behavior on these four components (defaulting to `@tastic/
423
522
  npm install @tastic/hud
424
523
  ```
425
524
 
525
+ ## ContentGutter
526
+
527
+ Caps a play area at `maxContentWidth` and splits the leftover width into two equal gutters
528
+ (`leftGutter`/`rightGutter` decorations). The width it clamps against is `@tastic/core`'s
529
+ `useRotatedWindowDimensions()`, so under a fake-landscape ancestor (`FakeLandscapeView`: OS window
530
+ stays portrait, the screen is only visually rotated) it caps against the swapped, post-rotation width
531
+ instead of the never-changing raw window width. It is an exact identity with `useWindowDimensions()`
532
+ when no rotation applies.
533
+
426
534
  ## Peer dependencies
427
535
 
428
- `react`, `react-native`, `react-native-paper` (`Icon`, `IconButton`, `Text`), `@rific/auto-paper`
429
- (`defaultColors`, `getContrastColor`, `getBlendedColor`, `SeedColor`), `@rific/feedback-press`
430
- (`IconButton`, `TouchableRipple`), [`@tastic/core`](https://github.com/jayrdeaton/react-native-game-core)
431
- (>=0.1.0 — `useRotation()`, see Rotation above),
432
- [`@tastic/achievements`](https://github.com/jayrdeaton/react-native-game-achievements) (>=0.1.2 —
433
- `AchievementCatalogRow`, in `AchievementCatalogSection.tsx` only, and type-only: no value/runtime
434
- import, see Achievements catalog above) — none of these are bundled, so use whatever versions your
435
- app already has.
536
+ None of these are bundled, so use whatever versions your app already has. Six are marked `optional` in
537
+ `peerDependenciesMeta` only so that an app using just the `@tastic/hud/guide` subpath (Hangman) can
538
+ install hud without them. **npm does not install optional peers for you**, so an app that imports the
539
+ main `@tastic/hud` entry must list every peer that entry loads in its own `dependencies`, including
540
+ the four optional ones marked "main entry" below. Without them, Metro fails to bundle with "Unable to
541
+ resolve module".
542
+
543
+ | Peer | Floor | Needed by |
544
+ | --- | --- | --- |
545
+ | `react`, `react-native` | >=19.0.0, >=0.76.0 | every entry |
546
+ | `react-native-paper` | >=5.0.0 | main entry, `./guide` |
547
+ | `react-native-reanimated` | >=3.0.0 | main entry (`StaggeredWord`), `./guide` |
548
+ | `@rific/auto-paper` | >=0.9.4 | main entry (`AutoAppearancePicker` first shipped in 0.9.4), `./guide` |
549
+ | `@rific/feedback-press` | >=0.10.2 | main entry (`useSoundSettings` first shipped in 0.10.2), `./guide` |
550
+ | `react-native-safe-area-context` | >=5.0.0 | main entry (`useZoneClampedAlign`) |
551
+ | `@tastic/core` | >=0.5.0, optional | main entry: `useRotation()` and most dialogs; `ContentGutter` needs `useRotatedWindowDimensions()`, first shipped in 0.5.0 |
552
+ | `@rific/updater` | >=0.4.0, optional | main entry: `BaseSettingsDialog`, `UpdateDialog` |
553
+ | `@tastic/profile` | >=0.4.0, optional | main entry: `PlayerSetupPanel`, `AchievementUnlockList` (`ProfileChip`) |
554
+ | `@tastic/split-screen` | >=0.5.0, optional | main entry: `useZoneClampedAlign` |
555
+ | `@shopify/react-native-skia` | >=1.5.0, optional | `TriggerGaugeHost`'s gauge (lazy-loaded behind `SkiaGate`) and `./skia-gate` |
556
+ | `@tastic/achievements` | >=0.2.0, optional | `AchievementCatalogSection`, `AchievementUnlockList`, type-only (`AchievementCatalogRow`, first exported in 0.2.0; `AchievementTier`) |
@@ -0,0 +1,77 @@
1
+ // src/overlayCard.ts
2
+ import { StyleSheet } from "react-native";
3
+ function getOverlayCardColors(dark) {
4
+ return {
5
+ fg: dark ? "#FFFFFF" : "#000000",
6
+ fgMuted: dark ? "rgba(255,255,255,0.4)" : "rgba(0,0,0,0.4)",
7
+ cardBg: dark ? "#111111" : "#F2F2F2",
8
+ cardBorder: dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)"
9
+ };
10
+ }
11
+ var overlayStyles = StyleSheet.create({
12
+ card: {
13
+ alignItems: "center",
14
+ borderRadius: 20,
15
+ borderWidth: 1,
16
+ gap: 16,
17
+ maxWidth: 360,
18
+ padding: 32
19
+ },
20
+ overlay: {
21
+ alignItems: "center",
22
+ backgroundColor: "rgba(0,0,0,0.72)",
23
+ bottom: 0,
24
+ justifyContent: "center",
25
+ left: 0,
26
+ position: "absolute",
27
+ right: 0,
28
+ top: 0
29
+ }
30
+ });
31
+ var overlayActionStyles = StyleSheet.create({
32
+ // minWidth (not flexBasis) is each button's real floor - whenever both fit side by side, flexGrow
33
+ // still splits any leftover width evenly beyond that floor, same visual result as a plain flex: 1
34
+ // on a roomy screen, but a button can never be squeezed narrower than 128, only wrap to its own
35
+ // row instead (see actions' flexWrap below). This used to be flexBasis: 128, which measured
36
+ // correctly on web but rendered every button roughly SQUARE on iOS - confirmed live (see the
37
+ // "Quit Match?" Cancel/Quit buttons and the single-button "OK" dialogs both coming out ~128pt
38
+ // tall instead of wide). Root cause: react-native-paper's Button renders its Surface as two
39
+ // nested views on iOS only (Surface.tsx's SurfaceIOS, for shadow rendering) - only a curated
40
+ // allowlist of style keys (flex/flexGrow/flexShrink/width/height/position/... - notably NOT
41
+ // flexBasis) reaches the outer view, which is the real flex item inside this row; every other
42
+ // key, flexBasis included, falls through to an inner view whose own unspecified flexDirection
43
+ // defaults to column. flexGrow: 1 landed on the (row-context) outer view and grew width
44
+ // correctly; flexBasis: 128 landed on the (column-context) inner view instead, where a flexBasis
45
+ // sets HEIGHT, not width. minWidth isn't axis-relative like flexBasis, so it constrains width
46
+ // correctly regardless of which of the two views it ends up on. react-native-paper's Button also
47
+ // hardcodes numberOfLines={1} on its label with no override, so a button squeezed under its
48
+ // label's natural width silently ellipsizes ("Cancel" -> "Can...") instead of wrapping - assumed
49
+ // fixable by just giving the row more width, until confirmed live that these two-word labels
50
+ // (Cancel/Reset/New Game) can still lose the truncation fight even with the extra room ConfirmDialog's own
51
+ // card width fix provides, on a narrow enough screen.
52
+ actionButton: {
53
+ flexGrow: 1,
54
+ minWidth: 128
55
+ },
56
+ // A lone confirm button (no cancel - BaseSettingsDialog's info-only "OK" dialogs, e.g. the
57
+ // update-check "no update found" message) has nothing left to share actions' width with, so
58
+ // actionButton's own flexGrow: 1 would stretch it across the entire row instead of reading as a
59
+ // normal button. Same 128 floor, no flexGrow, so it stays at that natural size instead of filling
60
+ // the card - centered by actions' own justifyContent below.
61
+ actionButtonSingle: {
62
+ minWidth: 128
63
+ },
64
+ actions: {
65
+ flexDirection: "row",
66
+ flexWrap: "wrap",
67
+ gap: 12,
68
+ justifyContent: "center",
69
+ width: "100%"
70
+ }
71
+ });
72
+
73
+ export {
74
+ getOverlayCardColors,
75
+ overlayStyles,
76
+ overlayActionStyles
77
+ };
@@ -0,0 +1,118 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface DirectionKeyLabels {
5
+ up: string;
6
+ left: string;
7
+ down: string;
8
+ right: string;
9
+ }
10
+ type DirectionKeysAxis = 'both' | 'vertical';
11
+ interface DirectionKeysHintProps {
12
+ labels?: Partial<DirectionKeyLabels>;
13
+ axis?: DirectionKeysAxis;
14
+ color?: string;
15
+ }
16
+ declare function DirectionKeysHint({ labels, axis, color }: DirectionKeysHintProps): react.JSX.Element;
17
+
18
+ interface GuideStep {
19
+ title: string;
20
+ body: string;
21
+ art?: ReactNode;
22
+ }
23
+ type GuideRotation = 0 | 90 | 180 | -90;
24
+
25
+ interface GuideContextValue {
26
+ isOpen: boolean;
27
+ open: () => void;
28
+ close: () => void;
29
+ requestAutoShow: () => void;
30
+ }
31
+ interface GuideProviderProps {
32
+ steps: GuideStep[];
33
+ currentVersion: number;
34
+ seenVersion: number;
35
+ onSeen: (version: number) => void;
36
+ rotation?: GuideRotation;
37
+ children?: ReactNode;
38
+ }
39
+ declare function GuideProvider({ steps, currentVersion, seenVersion, onSeen, rotation, children }: GuideProviderProps): react.JSX.Element;
40
+ declare function useGuide(): GuideContextValue;
41
+ declare function useAutoShowGuide(enabled?: boolean): void;
42
+
43
+ interface GuideState {
44
+ versionSeen: number;
45
+ }
46
+ type GuideAction = {
47
+ type: string;
48
+ payload?: unknown;
49
+ key?: string;
50
+ };
51
+ interface GuideSliceOptions {
52
+ currentVersion: number;
53
+ mountKey?: string;
54
+ persistKey?: string;
55
+ }
56
+ interface GuideSlice {
57
+ actions: {
58
+ markSeen: (version: number) => {
59
+ type: string;
60
+ payload: number;
61
+ };
62
+ };
63
+ reducer: (state: GuideState | undefined, action: GuideAction) => GuideState;
64
+ selectVersionSeen: (rootState: object) => number;
65
+ }
66
+ declare function createGuideSlice({ currentVersion, mountKey, persistKey }: GuideSliceOptions): GuideSlice;
67
+ interface GuideStorageReader {
68
+ getItem: (key: string) => Promise<string | null>;
69
+ }
70
+ type GuidePersistedState = {
71
+ _persist: {
72
+ version: number;
73
+ rehydrated: boolean;
74
+ };
75
+ } | undefined;
76
+ declare function createReturningPlayerMigrate(storage: GuideStorageReader, legacyKeys: readonly string[]): (state: GuidePersistedState, version: number) => Promise<GuidePersistedState>;
77
+
78
+ declare function getGuideCardSize(windowWidth: number, windowHeight: number, rotation: GuideRotation): {
79
+ width: number;
80
+ pageWidth: number;
81
+ maxHeight: number;
82
+ artHeight: number;
83
+ };
84
+ interface HowToPlayDialogProps {
85
+ visible: boolean;
86
+ steps: GuideStep[];
87
+ onFinish: () => void;
88
+ onSkip: () => void;
89
+ rotation?: GuideRotation;
90
+ }
91
+ declare function HowToPlayDialog({ visible, steps, ...rest }: HowToPlayDialogProps): react.JSX.Element | null;
92
+
93
+ type SeatDevice = 'keyboard' | 'mouse';
94
+ interface SeatDevicesHintProps {
95
+ devices: readonly SeatDevice[];
96
+ seatColors?: readonly string[];
97
+ }
98
+ declare function SeatDevicesHint({ devices, seatColors }: SeatDevicesHintProps): react.JSX.Element;
99
+
100
+ interface SeatDiagramProps {
101
+ topColor?: string;
102
+ bottomColor?: string;
103
+ topLabel?: string;
104
+ bottomLabel?: string;
105
+ }
106
+ declare function SeatDiagram({ topColor, bottomColor, topLabel, bottomLabel }: SeatDiagramProps): react.JSX.Element;
107
+
108
+ type SwipeDirection = 'up' | 'down' | 'left' | 'right';
109
+ type SwipePointer = 'finger' | 'mouse';
110
+ interface SwipeHintProps {
111
+ direction?: SwipeDirection;
112
+ pointer?: SwipePointer;
113
+ color?: string;
114
+ length?: number;
115
+ }
116
+ declare function SwipeHint({ direction, pointer, color, length }: SwipeHintProps): react.JSX.Element;
117
+
118
+ export { type DirectionKeyLabels, type DirectionKeysAxis, DirectionKeysHint, type DirectionKeysHintProps, type GuideAction, type GuidePersistedState, GuideProvider, type GuideProviderProps, type GuideRotation, type GuideSlice, type GuideSliceOptions, type GuideState, type GuideStep, type GuideStorageReader, HowToPlayDialog, type HowToPlayDialogProps, type SeatDevice, SeatDevicesHint, type SeatDevicesHintProps, SeatDiagram, type SeatDiagramProps, type SwipeDirection, SwipeHint, type SwipeHintProps, type SwipePointer, createGuideSlice, createReturningPlayerMigrate, getGuideCardSize, useAutoShowGuide, useGuide };
@@ -0,0 +1,118 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface DirectionKeyLabels {
5
+ up: string;
6
+ left: string;
7
+ down: string;
8
+ right: string;
9
+ }
10
+ type DirectionKeysAxis = 'both' | 'vertical';
11
+ interface DirectionKeysHintProps {
12
+ labels?: Partial<DirectionKeyLabels>;
13
+ axis?: DirectionKeysAxis;
14
+ color?: string;
15
+ }
16
+ declare function DirectionKeysHint({ labels, axis, color }: DirectionKeysHintProps): react.JSX.Element;
17
+
18
+ interface GuideStep {
19
+ title: string;
20
+ body: string;
21
+ art?: ReactNode;
22
+ }
23
+ type GuideRotation = 0 | 90 | 180 | -90;
24
+
25
+ interface GuideContextValue {
26
+ isOpen: boolean;
27
+ open: () => void;
28
+ close: () => void;
29
+ requestAutoShow: () => void;
30
+ }
31
+ interface GuideProviderProps {
32
+ steps: GuideStep[];
33
+ currentVersion: number;
34
+ seenVersion: number;
35
+ onSeen: (version: number) => void;
36
+ rotation?: GuideRotation;
37
+ children?: ReactNode;
38
+ }
39
+ declare function GuideProvider({ steps, currentVersion, seenVersion, onSeen, rotation, children }: GuideProviderProps): react.JSX.Element;
40
+ declare function useGuide(): GuideContextValue;
41
+ declare function useAutoShowGuide(enabled?: boolean): void;
42
+
43
+ interface GuideState {
44
+ versionSeen: number;
45
+ }
46
+ type GuideAction = {
47
+ type: string;
48
+ payload?: unknown;
49
+ key?: string;
50
+ };
51
+ interface GuideSliceOptions {
52
+ currentVersion: number;
53
+ mountKey?: string;
54
+ persistKey?: string;
55
+ }
56
+ interface GuideSlice {
57
+ actions: {
58
+ markSeen: (version: number) => {
59
+ type: string;
60
+ payload: number;
61
+ };
62
+ };
63
+ reducer: (state: GuideState | undefined, action: GuideAction) => GuideState;
64
+ selectVersionSeen: (rootState: object) => number;
65
+ }
66
+ declare function createGuideSlice({ currentVersion, mountKey, persistKey }: GuideSliceOptions): GuideSlice;
67
+ interface GuideStorageReader {
68
+ getItem: (key: string) => Promise<string | null>;
69
+ }
70
+ type GuidePersistedState = {
71
+ _persist: {
72
+ version: number;
73
+ rehydrated: boolean;
74
+ };
75
+ } | undefined;
76
+ declare function createReturningPlayerMigrate(storage: GuideStorageReader, legacyKeys: readonly string[]): (state: GuidePersistedState, version: number) => Promise<GuidePersistedState>;
77
+
78
+ declare function getGuideCardSize(windowWidth: number, windowHeight: number, rotation: GuideRotation): {
79
+ width: number;
80
+ pageWidth: number;
81
+ maxHeight: number;
82
+ artHeight: number;
83
+ };
84
+ interface HowToPlayDialogProps {
85
+ visible: boolean;
86
+ steps: GuideStep[];
87
+ onFinish: () => void;
88
+ onSkip: () => void;
89
+ rotation?: GuideRotation;
90
+ }
91
+ declare function HowToPlayDialog({ visible, steps, ...rest }: HowToPlayDialogProps): react.JSX.Element | null;
92
+
93
+ type SeatDevice = 'keyboard' | 'mouse';
94
+ interface SeatDevicesHintProps {
95
+ devices: readonly SeatDevice[];
96
+ seatColors?: readonly string[];
97
+ }
98
+ declare function SeatDevicesHint({ devices, seatColors }: SeatDevicesHintProps): react.JSX.Element;
99
+
100
+ interface SeatDiagramProps {
101
+ topColor?: string;
102
+ bottomColor?: string;
103
+ topLabel?: string;
104
+ bottomLabel?: string;
105
+ }
106
+ declare function SeatDiagram({ topColor, bottomColor, topLabel, bottomLabel }: SeatDiagramProps): react.JSX.Element;
107
+
108
+ type SwipeDirection = 'up' | 'down' | 'left' | 'right';
109
+ type SwipePointer = 'finger' | 'mouse';
110
+ interface SwipeHintProps {
111
+ direction?: SwipeDirection;
112
+ pointer?: SwipePointer;
113
+ color?: string;
114
+ length?: number;
115
+ }
116
+ declare function SwipeHint({ direction, pointer, color, length }: SwipeHintProps): react.JSX.Element;
117
+
118
+ export { type DirectionKeyLabels, type DirectionKeysAxis, DirectionKeysHint, type DirectionKeysHintProps, type GuideAction, type GuidePersistedState, GuideProvider, type GuideProviderProps, type GuideRotation, type GuideSlice, type GuideSliceOptions, type GuideState, type GuideStep, type GuideStorageReader, HowToPlayDialog, type HowToPlayDialogProps, type SeatDevice, SeatDevicesHint, type SeatDevicesHintProps, SeatDiagram, type SeatDiagramProps, type SwipeDirection, SwipeHint, type SwipeHintProps, type SwipePointer, createGuideSlice, createReturningPlayerMigrate, getGuideCardSize, useAutoShowGuide, useGuide };