@tastic/shell 0.3.0 → 0.3.1

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
@@ -18,7 +18,9 @@ package ecosystem, on [`@tastic/hud`](https://github.com/jayrdeaton/react-native
18
18
 
19
19
  - **`RootLayout`**: the root of the app (gesture root, safe area, fonts, Redux and its persist gate,
20
20
  the theme, profiles, every game's own provider, the splash gates, the shared orientation reading,
21
- toasts, How to Play, the OTA update dialog) around expo-router's stack.
21
+ toasts, How to Play, the OTA update dialog) around expo-router's stack. The stack follows the app's
22
+ own theme, not the phone's light or dark appearance: the status bar's text, the root window and the
23
+ navigation theme's background (what shows in a sliding screen's corners during a push or pop).
22
24
  - **Screens** for the routes: `HomeScreen` (the pack's hero, seat 1's button, a row per game, or a
23
25
  grid of preview tiles on a wide screen), `LoadoutRoute` and `PlayRoute` (`loadout/[game]`,
24
26
  `play/[game]`), `ProfilesRoute` and `StatsScreen`.
@@ -112,7 +114,7 @@ PuckTastic and DuelTastic draw what they always did:
112
114
  | `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, each loadout, the stats screen and Profiles draw transparent, with their buttons and tabs on the theme's surface. |
113
115
  | `foreground?` | The color of what those screens draw straight on the background (the hero, the corner buttons, headers), and at half strength a loadout's muted options. Unset is black or white with the theme. |
114
116
  | `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). |
117
+ | `useScreenOptions?` | A hook returning stack screen options (`StackScreenOptions`), merged after the shell's own (a status bar style of the pack's own, Reduce Motion's crossfade). The shell's already follow the app's theme, and stay dark over a `Backdrop`. |
116
118
  | `useFeedbackSounds?` | A hook returning the two sounds every button plays (`FeedbackSounds`: `selection`, `notification`), in place of the shell's two wavs. |
117
119
  | `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
120
  | `portraitWhileKeyboard?` | Every screen reads upright while the software keyboard is up (`@tastic/core`'s `OrientationProvider`), for naming a profile sideways. |
package/dist/index.js CHANGED
@@ -484,8 +484,9 @@ function AppStack() {
484
484
  const pack = usePack();
485
485
  const { Backdrop } = pack;
486
486
  const { dark } = (0, import_auto_paper2.useAutoPaperTheme)();
487
- const rootBackground = (0, import_core6.useThemedRootBackground)(Backdrop ? true : dark, false);
488
- const statusBar = (0, import_core6.useRotationAwareStatusBar)();
487
+ const darkRoot = Backdrop ? true : dark;
488
+ const rootBackground = (0, import_core6.useThemedRootBackground)(darkRoot, false);
489
+ const statusBar = (0, import_core6.useRotationAwareStatusBar)({ style: darkRoot ? "light" : "dark" });
489
490
  const packOptions = (pack.useScreenOptions ?? noScreenOptions)();
490
491
  const backdropOptions = Backdrop ? { contentStyle: TRANSPARENT_CONTENT, animation: import_react_native2.Platform.select({ android: "fade" }) } : void 0;
491
492
  const playOptions = (0, import_react9.useCallback)(
@@ -495,15 +496,19 @@ function AppStack() {
495
496
  },
496
497
  [pack.games]
497
498
  );
498
- const navigationTheme = (0, import_react9.useMemo)(() => Backdrop ? { ...import_expo_router.DarkTheme, colors: { ...import_expo_router.DarkTheme.colors, background: "transparent" } } : null, [Backdrop]);
499
- const stack = /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_expo_router.Stack, { screenOptions: { ...rootBackground, ...statusBar, ...backdropOptions, ...packOptions }, children: [
499
+ const navigationBackground = Backdrop ? TRANSPARENT_CONTENT.backgroundColor : rootBackground.contentStyle.backgroundColor;
500
+ const navigationTheme = (0, import_react9.useMemo)(() => {
501
+ const base = darkRoot ? import_expo_router.DarkTheme : import_expo_router.DefaultTheme;
502
+ return { ...base, colors: { ...base.colors, background: navigationBackground } };
503
+ }, [darkRoot, navigationBackground]);
504
+ const stack = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_expo_router.ThemeProvider, { value: navigationTheme, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_expo_router.Stack, { screenOptions: { ...rootBackground, ...statusBar, ...backdropOptions, ...packOptions }, children: [
500
505
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_expo_router.Stack.Screen, { name: "index" }),
501
506
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_expo_router.Stack.Screen, { name: "play/[game]", options: playOptions })
502
- ] });
503
- if (!Backdrop || !navigationTheme) return stack;
507
+ ] }) });
508
+ if (!Backdrop) return stack;
504
509
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native2.View, { style: styles.root, children: [
505
510
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Backdrop, {}),
506
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_expo_router.ThemeProvider, { value: navigationTheme, children: stack })
511
+ stack
507
512
  ] });
508
513
  }
509
514
  function OrientationBridge({ children }) {
package/dist/index.mjs CHANGED
@@ -29,7 +29,7 @@ import { OrientationProvider, useRotationAwareStatusBar, useThemedRootBackground
29
29
  import { RotatedFrame } from "@tastic/hud/chrome";
30
30
  import { ProfileColorProvider } from "@tastic/profile";
31
31
  import * as Haptics from "expo-haptics";
32
- import { DarkTheme, Stack, ThemeProvider as NavigationThemeProvider } from "expo-router";
32
+ import { DarkTheme, DefaultTheme, Stack, ThemeProvider as NavigationThemeProvider } from "expo-router";
33
33
  import { DeviceMotion } from "expo-sensors";
34
34
  import { useCallback as useCallback6, useMemo as useMemo3 } from "react";
35
35
  import { Platform as Platform2, StyleSheet, View } from "react-native";
@@ -410,8 +410,9 @@ function AppStack() {
410
410
  const pack = usePack();
411
411
  const { Backdrop } = pack;
412
412
  const { dark } = useAutoPaperTheme();
413
- const rootBackground = useThemedRootBackground(Backdrop ? true : dark, false);
414
- const statusBar = useRotationAwareStatusBar();
413
+ const darkRoot = Backdrop ? true : dark;
414
+ const rootBackground = useThemedRootBackground(darkRoot, false);
415
+ const statusBar = useRotationAwareStatusBar({ style: darkRoot ? "light" : "dark" });
415
416
  const packOptions = (pack.useScreenOptions ?? noScreenOptions)();
416
417
  const backdropOptions = Backdrop ? { contentStyle: TRANSPARENT_CONTENT, animation: Platform2.select({ android: "fade" }) } : void 0;
417
418
  const playOptions = useCallback6(
@@ -421,15 +422,19 @@ function AppStack() {
421
422
  },
422
423
  [pack.games]
423
424
  );
424
- const navigationTheme = useMemo3(() => Backdrop ? { ...DarkTheme, colors: { ...DarkTheme.colors, background: "transparent" } } : null, [Backdrop]);
425
- const stack = /* @__PURE__ */ jsxs2(Stack, { screenOptions: { ...rootBackground, ...statusBar, ...backdropOptions, ...packOptions }, children: [
425
+ const navigationBackground = Backdrop ? TRANSPARENT_CONTENT.backgroundColor : rootBackground.contentStyle.backgroundColor;
426
+ const navigationTheme = useMemo3(() => {
427
+ const base = darkRoot ? DarkTheme : DefaultTheme;
428
+ return { ...base, colors: { ...base.colors, background: navigationBackground } };
429
+ }, [darkRoot, navigationBackground]);
430
+ const stack = /* @__PURE__ */ jsx9(NavigationThemeProvider, { value: navigationTheme, children: /* @__PURE__ */ jsxs2(Stack, { screenOptions: { ...rootBackground, ...statusBar, ...backdropOptions, ...packOptions }, children: [
426
431
  /* @__PURE__ */ jsx9(Stack.Screen, { name: "index" }),
427
432
  /* @__PURE__ */ jsx9(Stack.Screen, { name: "play/[game]", options: playOptions })
428
- ] });
429
- if (!Backdrop || !navigationTheme) return stack;
433
+ ] }) });
434
+ if (!Backdrop) return stack;
430
435
  return /* @__PURE__ */ jsxs2(View, { style: styles.root, children: [
431
436
  /* @__PURE__ */ jsx9(Backdrop, {}),
432
- /* @__PURE__ */ jsx9(NavigationThemeProvider, { value: navigationTheme, children: stack })
437
+ stack
433
438
  ] });
434
439
  }
435
440
  function OrientationBridge({ children }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tastic/shell",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "The app around a pack of React Native games, two-seat or for one player: Home, the loadout, the stats screen, Profiles and Settings, the pack's Redux store and its root providers, driven by one PackDefinition",
5
5
  "keywords": [
6
6
  "react-native",
@@ -5,7 +5,7 @@ import { OrientationProvider, useRotationAwareStatusBar, useThemedRootBackground
5
5
  import { RotatedFrame } from '@tastic/hud/chrome'
6
6
  import { ProfileColorProvider } from '@tastic/profile'
7
7
  import * as Haptics from 'expo-haptics'
8
- import { DarkTheme, Stack, ThemeProvider as NavigationThemeProvider } from 'expo-router'
8
+ import { DarkTheme, DefaultTheme, Stack, ThemeProvider as NavigationThemeProvider } from 'expo-router'
9
9
  import { DeviceMotion } from 'expo-sensors'
10
10
  import { ReactNode, useCallback, useMemo } from 'react'
11
11
  import { Platform, StyleSheet, View } from 'react-native'
@@ -50,13 +50,16 @@ import type { PackDefinition, PackGameDefinition } from './types'
50
50
  const PLAY_OPTIONS = { statusBarHidden: true }
51
51
  const KEEP_STATUS_BAR_OPTIONS = {}
52
52
 
53
- // A pack's Backdrop (PackDefinition.Backdrop) is drawn once behind the whole stack and has to show
54
- // through two layers: each screen's contentStyle, and the layers React Navigation paints its theme's
55
- // `colors.background` on around every screen (the ScreenStack's container on native, each screen's
56
- // wrapper on the web). contentStyle alone left an opaque light grey slab over Solitaire's felt on the
57
- // web. Nothing else reads the navigation theme (headers are hidden; Paper has its own). Built only for
58
- // a pack with a backdrop, as it renders: never at import, where a pack's tests may stand in for
59
- // expo-router without a DarkTheme.
53
+ // The navigation theme is the root background too. React Navigation paints its theme's
54
+ // `colors.background` on the layers around every screen (the ScreenStack's container on native, each
55
+ // screen's wrapper on the web), and expo-router hands its container no theme, so that was always
56
+ // DefaultTheme's light grey: on iOS it showed in a sliding screen's rounded corners during every push and
57
+ // pop, whatever the app's theme. So the stack always gets a theme whose background is the root
58
+ // background's own color, and a pack's Backdrop (PackDefinition.Backdrop), drawn once behind the whole
59
+ // stack, gets a transparent one so it shows through both layers (contentStyle alone left an opaque light
60
+ // grey slab over Solitaire's felt on the web). Nothing else reads the navigation theme (headers are
61
+ // hidden; Paper has its own). Built as the stack renders, never at import, where a pack's tests may stand
62
+ // in for expo-router without its themes.
60
63
  const TRANSPARENT_CONTENT = { backgroundColor: 'transparent' }
61
64
 
62
65
  // The pack's own screen options (PackDefinition.useScreenOptions), as a hook every pack calls: an
@@ -67,10 +70,13 @@ function AppStack() {
67
70
  const pack = usePack()
68
71
  const { Backdrop } = pack
69
72
  const { dark } = useAutoPaperTheme()
70
- // Over a backdrop the root window stays dark whatever the mode: a backdrop is a dark table (the
71
- // felt), and a light root window would flash white around it during a push or pop.
72
- const rootBackground = useThemedRootBackground(Backdrop ? true : dark, false)
73
- const statusBar = useRotationAwareStatusBar()
73
+ // Over a backdrop the root stays dark whatever the mode: a backdrop is a dark table (the felt), and a
74
+ // light root window would flash white around it during a push or pop.
75
+ const darkRoot = Backdrop ? true : dark
76
+ const rootBackground = useThemedRootBackground(darkRoot, false)
77
+ // The status bar's text follows the app's theme, not the phone's appearance (react-native-screens'
78
+ // default `auto`), which drew dark text on a dark theme's black Home with the phone set to light.
79
+ const statusBar = useRotationAwareStatusBar({ style: darkRoot ? 'light' : 'dark' })
74
80
  const packOptions = (pack.useScreenOptions ?? noScreenOptions)()
75
81
  // Over a backdrop every screen is transparent, and Android crossfades: its default push and pop hold
76
82
  // the outgoing screen opaque for ~450ms while the new one slides over it, which would show Home
@@ -83,18 +89,24 @@ function AppStack() {
83
89
  },
84
90
  [pack.games]
85
91
  )
86
- const navigationTheme = useMemo(() => (Backdrop ? { ...DarkTheme, colors: { ...DarkTheme.colors, background: 'transparent' } } : null), [Backdrop])
92
+ const navigationBackground = Backdrop ? TRANSPARENT_CONTENT.backgroundColor : rootBackground.contentStyle.backgroundColor
93
+ const navigationTheme = useMemo(() => {
94
+ const base = darkRoot ? DarkTheme : DefaultTheme
95
+ return { ...base, colors: { ...base.colors, background: navigationBackground } }
96
+ }, [darkRoot, navigationBackground])
87
97
  const stack = (
88
- <Stack screenOptions={{ ...rootBackground, ...statusBar, ...backdropOptions, ...packOptions }}>
89
- <Stack.Screen name='index' />
90
- <Stack.Screen name='play/[game]' options={playOptions} />
91
- </Stack>
98
+ <NavigationThemeProvider value={navigationTheme}>
99
+ <Stack screenOptions={{ ...rootBackground, ...statusBar, ...backdropOptions, ...packOptions }}>
100
+ <Stack.Screen name='index' />
101
+ <Stack.Screen name='play/[game]' options={playOptions} />
102
+ </Stack>
103
+ </NavigationThemeProvider>
92
104
  )
93
- if (!Backdrop || !navigationTheme) return stack
105
+ if (!Backdrop) return stack
94
106
  return (
95
107
  <View style={styles.root}>
96
108
  <Backdrop />
97
- <NavigationThemeProvider value={navigationTheme}>{stack}</NavigationThemeProvider>
109
+ {stack}
98
110
  </View>
99
111
  )
100
112
  }