@tastic/shell 0.1.0 → 0.2.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
@@ -4,7 +4,9 @@ The app around a pack of React Native games: Home, each game's loadout, the stat
4
4
  and Settings, the pack's one Redux store, and every provider the games run under. An app hands it one
5
5
  `PackDefinition` (its name, its games, its seat colors, its hero) and keeps only its games, its hero
6
6
  and a handful of thin expo-router routes. Two-seat duels and games for one player alike: a game can
7
- leave out its seat 2 and its loadout, come back on a cold launch, and show a preview on Home.
7
+ leave out its seat 2 and its loadout, come back on a cold launch, and show a preview on Home, and a
8
+ pack for one player at a time (CardTastic, Solitaire renamed) can lay its own table, theme, toaster,
9
+ How to Play and stats tabs into the same screens.
8
10
 
9
11
  Built from the shell PuckTastic and DuelTastic each carried in `src/shell/` (made identical on
10
12
  2026-10-28), so the packs and the next ones (CardTastic) share one copy. Part of the `@tastic`
@@ -20,10 +22,11 @@ package ecosystem, on [`@tastic/hud`](https://github.com/jayrdeaton/react-native
20
22
  - **Screens** for the routes: `HomeScreen` (the pack's hero, seat 1's button, a row per game, or a
21
23
  grid of preview tiles on a wide screen), `LoadoutRoute` and `PlayRoute` (`loadout/[game]`,
22
24
  `play/[game]`), `ProfilesRoute` and `StatsScreen`.
23
- - **The store**: `createPackStore(pack, { keyPrefix, migrate })` builds one persisted store from the
24
- shell's keys (theme, haptics, sound, Keep Awake, profiles, seat selection, seat colors, app settings,
25
- seat 2, the never-saved `liveplay`, and `lastActiveGame` in a pack with a resumable game), the
26
- pack's own `reducers` and every game's own `reducers`.
25
+ - **The store**: `createPackStore(pack, { keyPrefix, migrate, blacklist, serializableIgnoredPaths,
26
+ middleware })` builds one persisted store from the shell's keys (theme, haptics, sound, Keep Awake,
27
+ profiles, seat selection, seat colors, app settings, seat 2, the never-saved `liveplay`, and
28
+ `lastActiveGame` in a pack with a resumable game), the pack's own `reducers` and every game's own
29
+ `reducers`.
27
30
  - **What a game builds on**: `createPlayerSettings` (its settings and controls, kept per player),
28
31
  `createGameStats` (its stats and achievements), `createGameGuideSlice` (which version of its How to
29
32
  Play a player has seen), `onProfileRemoved` (forget a deleted profile), `useActiveRound`,
@@ -101,6 +104,31 @@ the front of an in-app link, so a route starting with a game id equal to the bas
101
104
  | `reducers?` | The pack's own store keys (what `SettingsSections` saves), beside the shell's and the games'. Never a key either already mounts. |
102
105
  | `StatsSections?` | A section across the pack's games (a calendar of play), first on the stats screen's Stats tab, handed its two filters (`PackStatsSectionsProps`: `profileId`, null for Everyone, and `gameId`, null for All Games). |
103
106
 
107
+ For a pack for one player at a time (CardTastic, from Solitaire), each optional and off when unset, so
108
+ PuckTastic and DuelTastic draw what they always did:
109
+
110
+ | Field | |
111
+ | --- | --- |
112
+ | `Backdrop?` | A table drawn once behind the whole stack (Solitaire's felt). The stack's two layers go transparent (the screens' `contentStyle` and the navigation theme's `colors.background`, which React Navigation paints around every screen), the root window stays dark so light mode doesn't flash white on a push, Android's push and pop crossfade (its default holds the outgoing screen opaque), and Home, the stats screen and Profiles draw transparent, with their buttons and tabs on the theme's surface. |
113
+ | `foreground?` | The color of what those three screens draw straight on the background (the hero, the corner buttons, headers). Unset is black or white with the theme. |
114
+ | `Provider?` | Mounted once around How to Play and the stack, inside the store, the theme, profiles and every game's `Provider` (one stats context across the pack's games). |
115
+ | `useScreenOptions?` | A hook returning stack screen options (`StackScreenOptions`), merged after the shell's own (a light status bar, Reduce Motion's crossfade). |
116
+ | `useFeedbackSounds?` | A hook returning the two sounds every button plays (`FeedbackSounds`: `selection`, `notification`), in place of the shell's two wavs. |
117
+ | `Toaster?` | The pack's own toaster, drawn in a frame turned with the phone (with the safe-area insets turned onto its edges) in place of the shell's. The toast provider is given no Paper, so nothing portals it out of the frame. |
118
+ | `portraitWhileKeyboard?` | Every screen reads upright while the software keyboard is up (`@tastic/core`'s `OrientationProvider`), for naming a profile sideways. |
119
+ | `edgeGuard?` | `false` leaves Edge Guard out of Settings (no edge controls, no Edge Guard plugin). |
120
+ | `seatTheme?` | `(colors, dark) => ({ color, harmony })`: the theme the seats' colors make (a muted seed from seat 1's color), applied whenever they or dark mode change and as a fresh install's theme, in place of a triad of the raw colors. With it, `useSeatColors` reads each seat's color from where it's remembered (`seatColors`) rather than from the theme. |
121
+ | `previewColor?` | `(hex, dark) => string`: how every profile color is drawn (seat 1's button and swatches, each `ProfileChip`), mounted as `@tastic/profile`'s `ProfileColorProvider`. Saved colors stay as picked. |
122
+ | `guide?` | The pack's own How to Play (`PackGuide`: `version`, `selectVersionSeen`, `markSeen`, `getSteps({ touch })`), for cards that teach every game at once: shown on Home's first launch (never while a cold launch resumes a game), replayed from Home's Settings, never in a play screen. Its seen version is a key of the pack's own. |
123
+ | `guestLabel?`, `nullLabel?` | Seat 1's button: the chip's label for a guest (default `'P1'`) and the guest row's in its menu. |
124
+ | `guestStats?` | The stats screen keeps a guest's stats under `GUEST_PLAYER_ID` (`'guest'`): a Guest entry between Everyone and the profiles once a profile exists, and the screen opens on seat 1's player. Without it the screen opens on Everyone, where guests count. |
125
+ | `AchievementList?` | One achievement list across the pack's games, on the Achievements tab, with the game picker hidden there. |
126
+ | `statsTabs?` | Tabs of the pack's own between Stats and Achievements (`PackStatsTab`: `value`, `label`, `Content`, handed both filters). With them every tab is as wide as its label, with no icons, so three fit a phone. |
127
+ | `resetStats?` | The stats screen's Reset where the pack resets stats itself (`PackStatsReset`: a `useReset` hook returning `(profileId) => void`, and the `labels` for a player's name, Guest, or `null` for Everyone). It follows the player picker, not the game picker. |
128
+ | `statsGameTitles?` | `false` leaves each game's name off its sections on All Games, where the sections name it. |
129
+ | `newProfileColor?` | A new profile's starting color on Profiles. Unset is auto-paper's first swatch. |
130
+ | `usePickerAlign?` | `(open, contentWidth, contentHeight, options?) => AlignResult` (`PickerAlign`), a hook: where seat 1's menu on Home, the stats screen's two pickers and a profile's color swatch open. hud's own placement measures a turned button against the upright window, so with the phone held sideways it can run a menu off the screen (seen on the iOS simulator, 2026-10-29); a pack whose screens turn hands in a frame-aware one (Solitaire's `useFrameAutoAlign`). Profiles passes `{ maxContentWidth: 640 }`, its roster's column. Unset is hud's placement. |
131
+
104
132
  ### `GameDefinition`
105
133
 
106
134
  Each game is one object: its `id`, `title`, `blurb` and `icon` (a component drawn in the seats'
@@ -119,10 +147,41 @@ What a game can leave out or add, each optional, so a two-seat game sets none of
119
147
  | no `cpuDifficulty` (in `GameLoadout`) | A CPU with no levels: seat 2's menu offers one plain CPU. |
120
148
  | `resumable` | A cold launch drops the player back into its play screen if they left the app there rather than backing out of it. The shell remembers only which game (`lastActiveGame`); the game saves and restores its own board. |
121
149
  | `Preview` | Drawn on Home in place of the icon (`GamePreviewProps`: `width`, `height`, the seats' colors, `dark`), in a box of `PREVIEW_ASPECT` (360:200), a frame after Home first paints. While any game has one, Home shows tiles: rows upright, and on a wide screen a grid beside the hero and seat 1's button. A game without one keeps its icon. |
150
+ | no `icon` | Only with a `Preview`, which Home draws in its place. |
151
+ | no `Provider` | A game whose stats live in the pack's own `Provider`. |
152
+ | no `AchievementList`, no `resetStats` | A game whose achievements are in the pack's one list, and whose stats the pack resets (`PackDefinition.AchievementList`, `resetStats`). |
153
+ | no `guide` (`GuidelessGameDefinition`) | A game the pack's own `guide` teaches: its tile opens without cards the first time. A `GameDefinition` keeps `guide` required, so a pack's code that reads its games' cards keeps compiling; `PackDefinition.games` takes either (`PackGameDefinition`). |
154
+ | `Rules` | Its tile's rules button opens this sheet (`GameRulesProps`: `visible`, `onDismiss`) in place of its How to Play. It portals into a host inside Home's turned frame, so it's laid out against the frame and turned with it. |
155
+ | `keepStatusBar` | Its play screen keeps the status bar while the phone is held upright. Unset, every play screen hides it for the whole round. |
122
156
 
123
157
  A game never imports another game, and the shell never imports a game: games reach the shell only
124
158
  through the pack (`usePack()`).
125
159
 
160
+ ### `createPackStore`
161
+
162
+ `createPackStore(pack, config)`, where `config` is `PackStoreConfig`:
163
+
164
+ | Field | |
165
+ | --- | --- |
166
+ | `keyPrefix` | Where the root entry is saved (`<keyPrefix>root`). Never change it once the pack ships. |
167
+ | `migrate?` | redux-persist's migrate, run on the saved store before it rehydrates. |
168
+ | `blacklist?` | Keys left out of the root entry beside `liveplay`: keys that persist themselves (Solitaire's saved boards, one entry per game, since Android can't read an entry back past ~2MB). |
169
+ | `serializableIgnoredPaths?` | State paths the dev serializability check skips (keys too big to walk on every action). |
170
+ | `middleware?` | The pack's own middleware, after the shell's (Solitaire's settings that follow the player). |
171
+
172
+ ### On every pack (0.2.0)
173
+
174
+ Six fixes CardTastic's move brought to every pack's screens: Home's press-away overlay sits in the
175
+ scroller beside seat 1's button in the upright layout (as the frame's first child it was under the whole
176
+ scroller, so a press on a row played the game), seat 1's menu closes on every turn of the phone, both of
177
+ Home's scrollers fade out at an edge with more past it (auto-paper's `FadingScrollView`), a press
178
+ anywhere on the stats screen (its header and margins too) closes a picker's menu, the stats screen's
179
+ column is capped at 640pt in the wide layout, and Profiles turns with the phone (hud's `RotatedFrame`,
180
+ its roster capped at 640pt in the wide layout). Every hud import goes through `@tastic/hud/chrome`,
181
+ `/guide` or `/lobby`, so an app with no Skia can install the shell; a pack's own imports don't change.
182
+ The shell's routes are handed to expo-router as plain `Href`s, so an app with typed routes type-checks
183
+ the shell before its own routes match.
184
+
126
185
  ## Peer dependencies
127
186
 
128
187
  None are bundled. The floors are the versions PuckTastic and DuelTastic ran the shell on when it
@@ -131,11 +190,11 @@ became a package (the lower of the two where they differ).
131
190
  | Peer | Floor | Used for |
132
191
  | --- | --- | --- |
133
192
  | `react`, `react-native` | >=19.3.0, >=0.88.0 | everything |
134
- | `expo-router` | >=58.0.26 | the stack, the routes, `safeBack`, focus effects, the web tab title |
135
- | `expo-font`, `@expo/vector-icons` | >=58.0.10, >=15.1.1 | the icon font and the pack's `fonts`, behind the splash screen |
136
- | `expo-splash-screen`, `@rific/splash-gate` | >=58.0.11, >=0.2.3 | holding the splash screen until the theme, fonts and profiles are ready |
137
- | `expo-constants` | >=58.0.13 | the web tab title |
138
- | `expo-blur`, `@react-native-masked-view/masked-view` | >=58.0.5, >=0.3.2 | auto-paper's blur and its scroll fade |
193
+ | `expo-router` | >=58.0.23 | the stack, the routes, `safeBack`, focus effects, the web tab title |
194
+ | `expo-font`, `@expo/vector-icons` | >=58.0.8, >=15.1.1 | the icon font and the pack's `fonts`, behind the splash screen |
195
+ | `expo-splash-screen`, `@rific/splash-gate` | >=58.0.10, >=0.2.3 | holding the splash screen until the theme, fonts and profiles are ready |
196
+ | `expo-constants` | >=58.0.12 | the web tab title |
197
+ | `expo-blur`, `@react-native-masked-view/masked-view` | >=58.0.4, >=0.3.2 | auto-paper's blur and its scroll fade, Home's fading scrollers |
139
198
  | `expo-haptics`, `expo-keep-awake`, `expo-sensors` | >=58.0.7, >=58.0.3, >=58.0.4 | the toaster's haptics, Keep Awake, the orientation reading |
140
199
  | `react-native-gesture-handler`, `react-native-reanimated`, `react-native-safe-area-context` | >=3.3.0, >=4.7.1, >=5.10.1 | the gesture root, auto-paper's dialogs and the loadout's fades, insets |
141
200
  | `react-native-paper` | >=5.15.3 | text, icons, the toaster and feedback-press |
@@ -145,8 +204,8 @@ became a package (the lower of the two where they differ).
145
204
  | `@rific/feedback-press` | >=0.13.1 | haptic and sound settings, the UI sounds |
146
205
  | `@rific/toaster` | >=0.10.1 | toasts (DuelTastic runs 0.12, PuckTastic 0.10) |
147
206
  | `@tastic/core` | >=0.14.0 | orientation, rotation, the status bar and root background |
148
- | `@tastic/hud` | >=0.25.1 | Settings, the stats screen, the loadout's panels, How to Play |
149
- | `@tastic/profile` | >=0.17.0 | profiles, the seat buttons, seat colors |
207
+ | `@tastic/hud` | >=0.26.0 | Settings, the stats screen, the loadout's panels (its Skia-free `/lobby` entry, new in 0.26.0), How to Play |
208
+ | `@tastic/profile` | >=0.16.2 | profiles, the seat buttons, seat colors |
150
209
  | `@tastic/achievements` | >=0.6.0 | `createGameStats`, `forgetProfileAchievements` |
151
210
  | `@tastic/split-screen` | >=0.5.5 | the 2 Player loadout's two halves |
152
211
  | `@tastic/edge-guard` | >=0.1.5 | Edge Guard during play |
package/dist/index.d.mts CHANGED
@@ -1,11 +1,13 @@
1
1
  import * as react from 'react';
2
- import { ComponentType, ReactNode } from 'react';
2
+ import { ComponentType, ReactNode, ComponentProps } from 'react';
3
3
  import * as _rific_auto_paper from '@rific/auto-paper';
4
- import { ThemeAppearance } from '@rific/auto-paper';
4
+ import { ThemeAppearance, ThemeSettings } from '@rific/auto-paper';
5
5
  import { ViewRotation } from '@tastic/core';
6
- import { PopoverHost, MenuOption } from '@tastic/hud';
6
+ import { PopoverHost, AlignResult } from '@tastic/hud/chrome';
7
7
  import { GuideStep } from '@tastic/hud/guide';
8
+ import { MenuOption } from '@tastic/hud/lobby';
8
9
  import { FontSource } from 'expo-font';
10
+ import { Stack } from 'expo-router';
9
11
  import * as redux from 'redux';
10
12
  import { Reducer, Action } from 'redux';
11
13
  import * as _rific_core from '@rific/core';
@@ -17,6 +19,7 @@ import { SoundSettings } from '@rific/feedback-press';
17
19
  import * as redux_persist from 'redux-persist';
18
20
  import { PersistedState, Persistor } from 'redux-persist';
19
21
  import * as _reduxjs_toolkit from '@reduxjs/toolkit';
22
+ import { Middleware } from '@reduxjs/toolkit';
20
23
  import * as redux_thunk from 'redux-thunk';
21
24
  import * as redux_persist_es_persistReducer from 'redux-persist/es/persistReducer';
22
25
  import { ImageSourcePropType } from 'react-native';
@@ -102,7 +105,7 @@ type LastActiveGameState = {
102
105
  gameId: string | null;
103
106
  };
104
107
 
105
- declare function createShellReducers({ seatColors, appearance }: Pick<PackDefinition, 'seatColors' | 'appearance'>): {
108
+ declare function createShellReducers({ seatColors, appearance, seatTheme }: Pick<PackDefinition, 'seatColors' | 'appearance' | 'seatTheme'>): {
106
109
  theme: _rific_core.SettingsReducer<_rific_auto_paper.ThemeState>;
107
110
  haptic: _rific_core.SettingsReducer<_rific_feedback_press.HapticSettings>;
108
111
  sound: (state: SoundSettings | undefined, action: {
@@ -165,6 +168,7 @@ interface GameLoadout {
165
168
  onChange: (value: string) => void;
166
169
  };
167
170
  }
171
+ declare const GUEST_PLAYER_ID = "guest";
168
172
  interface GameGuide {
169
173
  version: number;
170
174
  selectVersionSeen: (state: ShellState) => number;
@@ -188,11 +192,23 @@ interface GamePreviewProps {
188
192
  p2Color: string;
189
193
  dark: boolean;
190
194
  }
195
+ interface PackGuide {
196
+ version: number;
197
+ selectVersionSeen: (state: ShellState) => number;
198
+ markSeen: (version: number) => Action;
199
+ getSteps: (context: {
200
+ touch: boolean;
201
+ }) => GuideStep[];
202
+ }
203
+ interface GameRulesProps {
204
+ visible: boolean;
205
+ onDismiss: () => void;
206
+ }
191
207
  interface GameDefinition {
192
208
  id: string;
193
209
  title: string;
194
210
  blurb: string;
195
- icon: ComponentType<GameIconProps>;
211
+ icon?: ComponentType<GameIconProps>;
196
212
  Preview?: ComponentType<GamePreviewProps>;
197
213
  seatTwo?: readonly SeatTwo[];
198
214
  twoPlayerSeats?: {
@@ -200,21 +216,27 @@ interface GameDefinition {
200
216
  followUpsideDown: boolean;
201
217
  };
202
218
  reducers: Record<string, Reducer>;
203
- Provider: ComponentType<{
219
+ Provider?: ComponentType<{
204
220
  children: ReactNode;
205
221
  }>;
206
222
  useLoadout?: (mode: RoundMode) => GameLoadout;
207
223
  PlayScreen: ComponentType;
208
224
  resumable?: boolean;
225
+ keepStatusBar?: boolean;
209
226
  StatsSections: ComponentType<{
210
227
  profileId: string | null;
211
228
  }>;
212
- AchievementList: ComponentType<{
229
+ AchievementList?: ComponentType<{
213
230
  profileId: string | null;
214
231
  }>;
215
- resetStats: () => Action;
232
+ resetStats?: () => Action;
216
233
  guide: GameGuide;
234
+ Rules?: ComponentType<GameRulesProps>;
217
235
  }
236
+ type GuidelessGameDefinition = Omit<GameDefinition, 'guide'> & {
237
+ guide?: undefined;
238
+ };
239
+ type PackGameDefinition = GameDefinition | GuidelessGameDefinition;
218
240
  interface HeroTitleProps {
219
241
  words: readonly [string, string];
220
242
  color: string;
@@ -230,11 +252,29 @@ interface PackStatsSectionsProps {
230
252
  profileId: string | null;
231
253
  gameId: string | null;
232
254
  }
255
+ type StackScreenOptions = Exclude<NonNullable<ComponentProps<typeof Stack>['screenOptions']>, (...args: never[]) => unknown>;
256
+ interface FeedbackSounds {
257
+ selection: () => void;
258
+ notification: () => void;
259
+ }
260
+ interface PackStatsTab {
261
+ value: string;
262
+ label: string;
263
+ Content: ComponentType<PackStatsSectionsProps>;
264
+ }
265
+ interface PackStatsReset {
266
+ useReset: () => (profileId: string | null) => void;
267
+ labels: (name: string | null) => {
268
+ label: string;
269
+ title: string;
270
+ body: string;
271
+ };
272
+ }
233
273
  interface PackDefinition {
234
274
  name: string;
235
275
  wordmark: readonly [string, string];
236
276
  colorCaption: string;
237
- games: readonly GameDefinition[];
277
+ games: readonly PackGameDefinition[];
238
278
  seatColors: Record<Seat, string>;
239
279
  appearance?: ThemeAppearance;
240
280
  HeroTitle: ComponentType<HeroTitleProps>;
@@ -244,23 +284,53 @@ interface PackDefinition {
244
284
  SettingsSections?: ComponentType;
245
285
  reducers?: Record<string, Reducer>;
246
286
  StatsSections?: ComponentType<PackStatsSectionsProps>;
287
+ Backdrop?: ComponentType;
288
+ foreground?: string;
289
+ Provider?: ComponentType<{
290
+ children: ReactNode;
291
+ }>;
292
+ useScreenOptions?: () => StackScreenOptions;
293
+ useFeedbackSounds?: () => FeedbackSounds;
294
+ Toaster?: ComponentType;
295
+ portraitWhileKeyboard?: boolean;
296
+ edgeGuard?: boolean;
297
+ seatTheme?: (colors: Record<Seat, string>, dark: boolean) => Pick<ThemeSettings, 'color' | 'harmony'>;
298
+ previewColor?: (hex: string, dark: boolean) => string;
299
+ guide?: PackGuide;
300
+ guestLabel?: string;
301
+ nullLabel?: string;
302
+ guestStats?: boolean;
303
+ AchievementList?: ComponentType<{
304
+ profileId: string | null;
305
+ }>;
306
+ statsTabs?: readonly PackStatsTab[];
307
+ resetStats?: PackStatsReset;
308
+ statsGameTitles?: boolean;
309
+ newProfileColor?: string;
310
+ usePickerAlign?: PickerAlign;
247
311
  }
312
+ type PickerAlign = (open: boolean, contentWidth: number, contentHeight: number, options?: {
313
+ maxContentWidth?: number;
314
+ }) => AlignResult;
248
315
 
249
316
  declare function PackProvider({ pack, children }: {
250
317
  pack: PackDefinition;
251
318
  children: ReactNode;
252
319
  }): react.JSX.Element;
253
320
  declare function usePack(): PackDefinition;
254
- declare function usePackGame(id: string | undefined): GameDefinition | undefined;
321
+ declare function usePackGame(id: string | undefined): PackGameDefinition | undefined;
255
322
 
256
323
  interface PackStoreConfig {
257
324
  keyPrefix: string;
258
325
  migrate?: (state: PersistedState, version: number) => Promise<PersistedState>;
326
+ blacklist?: readonly string[];
327
+ serializableIgnoredPaths?: readonly string[];
328
+ middleware?: readonly Middleware[];
259
329
  }
260
- type PackStoreSource = Pick<PackDefinition, 'seatColors' | 'appearance' | 'reducers'> & {
261
- games: readonly Pick<GameDefinition, 'reducers' | 'resumable'>[];
330
+ type PackStoreSource = Pick<PackDefinition, 'seatColors' | 'appearance' | 'seatTheme' | 'reducers'> & {
331
+ games: readonly Pick<PackGameDefinition, 'reducers' | 'resumable'>[];
262
332
  };
263
- declare function createPackStore(pack: PackStoreSource, { keyPrefix, migrate }: PackStoreConfig): {
333
+ declare function createPackStore(pack: PackStoreSource, { keyPrefix, migrate, blacklist, serializableIgnoredPaths, middleware }: PackStoreConfig): {
264
334
  store: _reduxjs_toolkit.EnhancedStore<{
265
335
  theme: _rific_auto_paper.ThemeState;
266
336
  haptic: _rific_feedback_press.HapticSettings;
@@ -323,6 +393,8 @@ declare function StatsScreen(): react.JSX.Element;
323
393
  interface GameGuideContextValue {
324
394
  openFirstTime: (gameId: string, next: () => void) => void;
325
395
  openFor: (gameId: string) => void;
396
+ openPack: () => void;
397
+ autoShowPack: () => void;
326
398
  }
327
399
  declare function useGameGuide(): GameGuideContextValue;
328
400
 
@@ -484,4 +556,4 @@ declare function createPlayerSettings<S extends WithControls<C>, C>({ id, defaul
484
556
  };
485
557
  };
486
558
 
487
- export { type ActiveRound, type AppSettings, DEFAULT_APP_SETTINGS, DEFAULT_OPPONENT, type GameDefinition, type GameGuide, type GameGuideState, type GameIconProps, type GameLoadout, type GamePreviewProps, type HeroTitleProps, HomeScreen, type LiveplaySliceState, type LoadoutOptionsProps, LoadoutRoute, type OpponentState, PREVIEW_ASPECT, type PackDefinition, PackProvider, type PackStatsSectionsProps, type PackStore, type PackStoreConfig, type PackStoreSource, PlayRoute, type PlayerControlsState, type PlayerSettingsState, type ProfileSelectionState, ProfilesProvider, ProfilesRoute, type RecordResult, RootLayout, type RoundMode, type Seat, type SeatColorsState, type SeatControlLook, type SeatTwo, SettingsDialog, type SettingsDialogProps, type ShellState, StatsScreen, appSettingsActions, reducer$1 as appSettingsReducer, createGameGuideSlice, createGameStats, createPackStore, createPlayerSettings, createSeatColorsReducer, createShellReducers, defaultProfileSelectionState, defaultSeatColorsState, forgetProfileAchievements, humanSeatsFor, imageGameIcon, liveplayActions, reducer$3 as liveplayReducer, onProfileRemoved, opponentActions, reducer$2 as opponentReducer, profileSelectionActions, reducer as profileSelectionReducer, roundModeFor, seatColorsActions, seatPlayerKey, seatTwoFor, useActiveRound, useAppSettings, useGameGuide, useOpponent, usePack, usePackGame, useProfiles, useSeatColors };
559
+ export { type ActiveRound, type AppSettings, DEFAULT_APP_SETTINGS, DEFAULT_OPPONENT, type FeedbackSounds, GUEST_PLAYER_ID, type GameDefinition, type GameGuide, type GameGuideState, type GameIconProps, type GameLoadout, type GamePreviewProps, type GameRulesProps, type GuidelessGameDefinition, type HeroTitleProps, HomeScreen, type LiveplaySliceState, type LoadoutOptionsProps, LoadoutRoute, type OpponentState, PREVIEW_ASPECT, type PackDefinition, type PackGameDefinition, type PackGuide, PackProvider, type PackStatsReset, type PackStatsSectionsProps, type PackStatsTab, type PackStore, type PackStoreConfig, type PackStoreSource, type PickerAlign, PlayRoute, type PlayerControlsState, type PlayerSettingsState, type ProfileSelectionState, ProfilesProvider, ProfilesRoute, type RecordResult, RootLayout, type RoundMode, type Seat, type SeatColorsState, type SeatControlLook, type SeatTwo, SettingsDialog, type SettingsDialogProps, type ShellState, type StackScreenOptions, StatsScreen, appSettingsActions, reducer$1 as appSettingsReducer, createGameGuideSlice, createGameStats, createPackStore, createPlayerSettings, createSeatColorsReducer, createShellReducers, defaultProfileSelectionState, defaultSeatColorsState, forgetProfileAchievements, humanSeatsFor, imageGameIcon, liveplayActions, reducer$3 as liveplayReducer, onProfileRemoved, opponentActions, reducer$2 as opponentReducer, profileSelectionActions, reducer as profileSelectionReducer, roundModeFor, seatColorsActions, seatPlayerKey, seatTwoFor, useActiveRound, useAppSettings, useGameGuide, useOpponent, usePack, usePackGame, useProfiles, useSeatColors };
package/dist/index.d.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  import * as react from 'react';
2
- import { ComponentType, ReactNode } from 'react';
2
+ import { ComponentType, ReactNode, ComponentProps } from 'react';
3
3
  import * as _rific_auto_paper from '@rific/auto-paper';
4
- import { ThemeAppearance } from '@rific/auto-paper';
4
+ import { ThemeAppearance, ThemeSettings } from '@rific/auto-paper';
5
5
  import { ViewRotation } from '@tastic/core';
6
- import { PopoverHost, MenuOption } from '@tastic/hud';
6
+ import { PopoverHost, AlignResult } from '@tastic/hud/chrome';
7
7
  import { GuideStep } from '@tastic/hud/guide';
8
+ import { MenuOption } from '@tastic/hud/lobby';
8
9
  import { FontSource } from 'expo-font';
10
+ import { Stack } from 'expo-router';
9
11
  import * as redux from 'redux';
10
12
  import { Reducer, Action } from 'redux';
11
13
  import * as _rific_core from '@rific/core';
@@ -17,6 +19,7 @@ import { SoundSettings } from '@rific/feedback-press';
17
19
  import * as redux_persist from 'redux-persist';
18
20
  import { PersistedState, Persistor } from 'redux-persist';
19
21
  import * as _reduxjs_toolkit from '@reduxjs/toolkit';
22
+ import { Middleware } from '@reduxjs/toolkit';
20
23
  import * as redux_thunk from 'redux-thunk';
21
24
  import * as redux_persist_es_persistReducer from 'redux-persist/es/persistReducer';
22
25
  import { ImageSourcePropType } from 'react-native';
@@ -102,7 +105,7 @@ type LastActiveGameState = {
102
105
  gameId: string | null;
103
106
  };
104
107
 
105
- declare function createShellReducers({ seatColors, appearance }: Pick<PackDefinition, 'seatColors' | 'appearance'>): {
108
+ declare function createShellReducers({ seatColors, appearance, seatTheme }: Pick<PackDefinition, 'seatColors' | 'appearance' | 'seatTheme'>): {
106
109
  theme: _rific_core.SettingsReducer<_rific_auto_paper.ThemeState>;
107
110
  haptic: _rific_core.SettingsReducer<_rific_feedback_press.HapticSettings>;
108
111
  sound: (state: SoundSettings | undefined, action: {
@@ -165,6 +168,7 @@ interface GameLoadout {
165
168
  onChange: (value: string) => void;
166
169
  };
167
170
  }
171
+ declare const GUEST_PLAYER_ID = "guest";
168
172
  interface GameGuide {
169
173
  version: number;
170
174
  selectVersionSeen: (state: ShellState) => number;
@@ -188,11 +192,23 @@ interface GamePreviewProps {
188
192
  p2Color: string;
189
193
  dark: boolean;
190
194
  }
195
+ interface PackGuide {
196
+ version: number;
197
+ selectVersionSeen: (state: ShellState) => number;
198
+ markSeen: (version: number) => Action;
199
+ getSteps: (context: {
200
+ touch: boolean;
201
+ }) => GuideStep[];
202
+ }
203
+ interface GameRulesProps {
204
+ visible: boolean;
205
+ onDismiss: () => void;
206
+ }
191
207
  interface GameDefinition {
192
208
  id: string;
193
209
  title: string;
194
210
  blurb: string;
195
- icon: ComponentType<GameIconProps>;
211
+ icon?: ComponentType<GameIconProps>;
196
212
  Preview?: ComponentType<GamePreviewProps>;
197
213
  seatTwo?: readonly SeatTwo[];
198
214
  twoPlayerSeats?: {
@@ -200,21 +216,27 @@ interface GameDefinition {
200
216
  followUpsideDown: boolean;
201
217
  };
202
218
  reducers: Record<string, Reducer>;
203
- Provider: ComponentType<{
219
+ Provider?: ComponentType<{
204
220
  children: ReactNode;
205
221
  }>;
206
222
  useLoadout?: (mode: RoundMode) => GameLoadout;
207
223
  PlayScreen: ComponentType;
208
224
  resumable?: boolean;
225
+ keepStatusBar?: boolean;
209
226
  StatsSections: ComponentType<{
210
227
  profileId: string | null;
211
228
  }>;
212
- AchievementList: ComponentType<{
229
+ AchievementList?: ComponentType<{
213
230
  profileId: string | null;
214
231
  }>;
215
- resetStats: () => Action;
232
+ resetStats?: () => Action;
216
233
  guide: GameGuide;
234
+ Rules?: ComponentType<GameRulesProps>;
217
235
  }
236
+ type GuidelessGameDefinition = Omit<GameDefinition, 'guide'> & {
237
+ guide?: undefined;
238
+ };
239
+ type PackGameDefinition = GameDefinition | GuidelessGameDefinition;
218
240
  interface HeroTitleProps {
219
241
  words: readonly [string, string];
220
242
  color: string;
@@ -230,11 +252,29 @@ interface PackStatsSectionsProps {
230
252
  profileId: string | null;
231
253
  gameId: string | null;
232
254
  }
255
+ type StackScreenOptions = Exclude<NonNullable<ComponentProps<typeof Stack>['screenOptions']>, (...args: never[]) => unknown>;
256
+ interface FeedbackSounds {
257
+ selection: () => void;
258
+ notification: () => void;
259
+ }
260
+ interface PackStatsTab {
261
+ value: string;
262
+ label: string;
263
+ Content: ComponentType<PackStatsSectionsProps>;
264
+ }
265
+ interface PackStatsReset {
266
+ useReset: () => (profileId: string | null) => void;
267
+ labels: (name: string | null) => {
268
+ label: string;
269
+ title: string;
270
+ body: string;
271
+ };
272
+ }
233
273
  interface PackDefinition {
234
274
  name: string;
235
275
  wordmark: readonly [string, string];
236
276
  colorCaption: string;
237
- games: readonly GameDefinition[];
277
+ games: readonly PackGameDefinition[];
238
278
  seatColors: Record<Seat, string>;
239
279
  appearance?: ThemeAppearance;
240
280
  HeroTitle: ComponentType<HeroTitleProps>;
@@ -244,23 +284,53 @@ interface PackDefinition {
244
284
  SettingsSections?: ComponentType;
245
285
  reducers?: Record<string, Reducer>;
246
286
  StatsSections?: ComponentType<PackStatsSectionsProps>;
287
+ Backdrop?: ComponentType;
288
+ foreground?: string;
289
+ Provider?: ComponentType<{
290
+ children: ReactNode;
291
+ }>;
292
+ useScreenOptions?: () => StackScreenOptions;
293
+ useFeedbackSounds?: () => FeedbackSounds;
294
+ Toaster?: ComponentType;
295
+ portraitWhileKeyboard?: boolean;
296
+ edgeGuard?: boolean;
297
+ seatTheme?: (colors: Record<Seat, string>, dark: boolean) => Pick<ThemeSettings, 'color' | 'harmony'>;
298
+ previewColor?: (hex: string, dark: boolean) => string;
299
+ guide?: PackGuide;
300
+ guestLabel?: string;
301
+ nullLabel?: string;
302
+ guestStats?: boolean;
303
+ AchievementList?: ComponentType<{
304
+ profileId: string | null;
305
+ }>;
306
+ statsTabs?: readonly PackStatsTab[];
307
+ resetStats?: PackStatsReset;
308
+ statsGameTitles?: boolean;
309
+ newProfileColor?: string;
310
+ usePickerAlign?: PickerAlign;
247
311
  }
312
+ type PickerAlign = (open: boolean, contentWidth: number, contentHeight: number, options?: {
313
+ maxContentWidth?: number;
314
+ }) => AlignResult;
248
315
 
249
316
  declare function PackProvider({ pack, children }: {
250
317
  pack: PackDefinition;
251
318
  children: ReactNode;
252
319
  }): react.JSX.Element;
253
320
  declare function usePack(): PackDefinition;
254
- declare function usePackGame(id: string | undefined): GameDefinition | undefined;
321
+ declare function usePackGame(id: string | undefined): PackGameDefinition | undefined;
255
322
 
256
323
  interface PackStoreConfig {
257
324
  keyPrefix: string;
258
325
  migrate?: (state: PersistedState, version: number) => Promise<PersistedState>;
326
+ blacklist?: readonly string[];
327
+ serializableIgnoredPaths?: readonly string[];
328
+ middleware?: readonly Middleware[];
259
329
  }
260
- type PackStoreSource = Pick<PackDefinition, 'seatColors' | 'appearance' | 'reducers'> & {
261
- games: readonly Pick<GameDefinition, 'reducers' | 'resumable'>[];
330
+ type PackStoreSource = Pick<PackDefinition, 'seatColors' | 'appearance' | 'seatTheme' | 'reducers'> & {
331
+ games: readonly Pick<PackGameDefinition, 'reducers' | 'resumable'>[];
262
332
  };
263
- declare function createPackStore(pack: PackStoreSource, { keyPrefix, migrate }: PackStoreConfig): {
333
+ declare function createPackStore(pack: PackStoreSource, { keyPrefix, migrate, blacklist, serializableIgnoredPaths, middleware }: PackStoreConfig): {
264
334
  store: _reduxjs_toolkit.EnhancedStore<{
265
335
  theme: _rific_auto_paper.ThemeState;
266
336
  haptic: _rific_feedback_press.HapticSettings;
@@ -323,6 +393,8 @@ declare function StatsScreen(): react.JSX.Element;
323
393
  interface GameGuideContextValue {
324
394
  openFirstTime: (gameId: string, next: () => void) => void;
325
395
  openFor: (gameId: string) => void;
396
+ openPack: () => void;
397
+ autoShowPack: () => void;
326
398
  }
327
399
  declare function useGameGuide(): GameGuideContextValue;
328
400
 
@@ -484,4 +556,4 @@ declare function createPlayerSettings<S extends WithControls<C>, C>({ id, defaul
484
556
  };
485
557
  };
486
558
 
487
- export { type ActiveRound, type AppSettings, DEFAULT_APP_SETTINGS, DEFAULT_OPPONENT, type GameDefinition, type GameGuide, type GameGuideState, type GameIconProps, type GameLoadout, type GamePreviewProps, type HeroTitleProps, HomeScreen, type LiveplaySliceState, type LoadoutOptionsProps, LoadoutRoute, type OpponentState, PREVIEW_ASPECT, type PackDefinition, PackProvider, type PackStatsSectionsProps, type PackStore, type PackStoreConfig, type PackStoreSource, PlayRoute, type PlayerControlsState, type PlayerSettingsState, type ProfileSelectionState, ProfilesProvider, ProfilesRoute, type RecordResult, RootLayout, type RoundMode, type Seat, type SeatColorsState, type SeatControlLook, type SeatTwo, SettingsDialog, type SettingsDialogProps, type ShellState, StatsScreen, appSettingsActions, reducer$1 as appSettingsReducer, createGameGuideSlice, createGameStats, createPackStore, createPlayerSettings, createSeatColorsReducer, createShellReducers, defaultProfileSelectionState, defaultSeatColorsState, forgetProfileAchievements, humanSeatsFor, imageGameIcon, liveplayActions, reducer$3 as liveplayReducer, onProfileRemoved, opponentActions, reducer$2 as opponentReducer, profileSelectionActions, reducer as profileSelectionReducer, roundModeFor, seatColorsActions, seatPlayerKey, seatTwoFor, useActiveRound, useAppSettings, useGameGuide, useOpponent, usePack, usePackGame, useProfiles, useSeatColors };
559
+ export { type ActiveRound, type AppSettings, DEFAULT_APP_SETTINGS, DEFAULT_OPPONENT, type FeedbackSounds, GUEST_PLAYER_ID, type GameDefinition, type GameGuide, type GameGuideState, type GameIconProps, type GameLoadout, type GamePreviewProps, type GameRulesProps, type GuidelessGameDefinition, type HeroTitleProps, HomeScreen, type LiveplaySliceState, type LoadoutOptionsProps, LoadoutRoute, type OpponentState, PREVIEW_ASPECT, type PackDefinition, type PackGameDefinition, type PackGuide, PackProvider, type PackStatsReset, type PackStatsSectionsProps, type PackStatsTab, type PackStore, type PackStoreConfig, type PackStoreSource, type PickerAlign, PlayRoute, type PlayerControlsState, type PlayerSettingsState, type ProfileSelectionState, ProfilesProvider, ProfilesRoute, type RecordResult, RootLayout, type RoundMode, type Seat, type SeatColorsState, type SeatControlLook, type SeatTwo, SettingsDialog, type SettingsDialogProps, type ShellState, type StackScreenOptions, StatsScreen, appSettingsActions, reducer$1 as appSettingsReducer, createGameGuideSlice, createGameStats, createPackStore, createPlayerSettings, createSeatColorsReducer, createShellReducers, defaultProfileSelectionState, defaultSeatColorsState, forgetProfileAchievements, humanSeatsFor, imageGameIcon, liveplayActions, reducer$3 as liveplayReducer, onProfileRemoved, opponentActions, reducer$2 as opponentReducer, profileSelectionActions, reducer as profileSelectionReducer, roundModeFor, seatColorsActions, seatPlayerKey, seatTwoFor, useActiveRound, useAppSettings, useGameGuide, useOpponent, usePack, usePackGame, useProfiles, useSeatColors };