@applicaster/zapp-react-native-utils 13.0.0-rc.99 → 14.0.0-rc.2

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.
Files changed (41) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +55 -6
  2. package/actionsExecutor/consts.ts +4 -0
  3. package/appUtils/__tests__/__snapshots__/localizationsHelper.test.ts.snap +151 -0
  4. package/appUtils/__tests__/allZappLocales.ts +79 -0
  5. package/appUtils/__tests__/{localizationsHelper.test.js → localizationsHelper.test.ts} +11 -0
  6. package/appUtils/accessibilityManager/const.ts +18 -0
  7. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +1 -0
  8. package/appUtils/focusManager/index.ios.ts +14 -4
  9. package/appUtils/focusManager/utils/__tests__/findChild.test.ts +35 -0
  10. package/appUtils/focusManager/utils/index.ts +5 -0
  11. package/appUtils/localizationsHelper.ts +10 -2
  12. package/appUtils/playerManager/playerHooks/usePlayerCurrentTime.tsx +11 -7
  13. package/cellUtils/index.ts +9 -5
  14. package/componentsUtils/index.ts +8 -1
  15. package/localizationUtils/index.ts +3 -3
  16. package/manifestUtils/defaultManifestConfigurations/generalContent.js +13 -0
  17. package/manifestUtils/defaultManifestConfigurations/player.js +0 -8
  18. package/manifestUtils/index.js +2 -0
  19. package/manifestUtils/keys.js +27 -2
  20. package/navigationUtils/__tests__/navigationUtils.test.js +0 -65
  21. package/navigationUtils/index.ts +0 -31
  22. package/package.json +2 -2
  23. package/playerUtils/configurationGenerator.ts +0 -16
  24. package/playerUtils/index.ts +17 -0
  25. package/reactHooks/app/useAppState.ts +2 -2
  26. package/reactHooks/feed/useBatchLoading.ts +10 -12
  27. package/reactHooks/navigation/{useGetTabBarHeight.ts → getTabBarHeight.ts} +1 -1
  28. package/reactHooks/navigation/useGetBottomTabBarHeight.ts +10 -3
  29. package/reactHooks/navigation/useNavigationPluginData.ts +8 -4
  30. package/reactHooks/navigation/useNavigationType.ts +4 -2
  31. package/reactHooks/screen/__tests__/useScreenBackgroundColor.test.tsx +69 -0
  32. package/reactHooks/screen/useScreenBackgroundColor.ts +3 -15
  33. package/reactHooks/state/README.md +79 -0
  34. package/reactHooks/state/ZStoreProvider.tsx +71 -0
  35. package/reactHooks/state/__tests__/ZStoreProvider.test.tsx +66 -0
  36. package/reactHooks/state/index.ts +2 -0
  37. package/reactHooks/useListenEventBusEvent.ts +1 -1
  38. package/reactUtils/index.ts +9 -0
  39. package/typeGuards/index.ts +3 -0
  40. package/utils/index.ts +1 -1
  41. package/zappFrameworkUtils/localStorageHelper.ts +32 -10
@@ -42,14 +42,6 @@ function getPlayerConfiguration({ platform }) {
42
42
  label_tooltip:
43
43
  "Enable if you want to have adaptive background gradient based on audio background image.",
44
44
  },
45
- {
46
- key: "audio_player_background_image_query",
47
- label: "Audio Player Image Query",
48
- initial_value: "",
49
- type: "text_input",
50
- label_tooltip:
51
- "Set the query params which control the size of the image e.g width, height. This will help to optimize the loading time, when magic background is enabled.",
52
- },
53
45
  {
54
46
  key: "audio_player_background_image_default_color",
55
47
  label: "Audio Player Background Image Default Color",
@@ -8,6 +8,7 @@ const {
8
8
 
9
9
  const { tvActionButtonsContainer } = require("./tvAction/container");
10
10
  const { tvActionButton } = require("./tvAction/button");
11
+ const { compact } = require("./_internals");
11
12
 
12
13
  const { spacingKey, absolutePositionElement } = require("./containers");
13
14
 
@@ -46,4 +47,5 @@ module.exports = {
46
47
  tvProgressBar,
47
48
  secondaryImage,
48
49
  getUpdatedSecondaryImageKeys,
50
+ compact,
49
51
  };
@@ -21,7 +21,30 @@ const ZAPPIFEST_FIELDS = {
21
21
  },
22
22
  };
23
23
 
24
- const TV_PLATFORMS = ["tvOS", "android", "LG", "samsung", "roku", "vizio"];
24
+ const TV_PLATFORMS_FOR_FONTS = [
25
+ "tvOS",
26
+ "android",
27
+ "LG",
28
+ "samsung",
29
+ "roku",
30
+ "vizio",
31
+ ];
32
+
33
+ const MOBILE_PLATFORMS = [
34
+ "ios",
35
+ "ios_for_quickbrick",
36
+ "android",
37
+ "android_for_quickbrick",
38
+ ];
39
+
40
+ const TV_PLATFORMS = [
41
+ "android_tv_for_quickbrick",
42
+ "amazon_fire_tv_for_quickbrick",
43
+ "tvos_for_quickbrick",
44
+ "samsung_tv",
45
+ "lg_tv",
46
+ "vizio",
47
+ ];
25
48
 
26
49
  const FontPropsForTVPlatform = (platform) => ({
27
50
  [`${R.toLower(platform)}FontFamily`]: {
@@ -92,7 +115,7 @@ const TV_FONT_KEY_DATA = {
92
115
  name: "focused font color",
93
116
  type: ZAPPIFEST_FIELDS.color_picker,
94
117
  },
95
- ...R.reduce(addTVFontsForPlatform, {}, TV_PLATFORMS),
118
+ ...R.reduce(addTVFontsForPlatform, {}, TV_PLATFORMS_FOR_FONTS),
96
119
  textAlignment: {
97
120
  name: "text alignment",
98
121
  type: ZAPPIFEST_FIELDS.select,
@@ -2083,4 +2106,6 @@ module.exports = {
2083
2106
  TV_CELL_BADGE_FIELDS,
2084
2107
  progressBarFields,
2085
2108
  secondaryImageFields,
2109
+ TV_PLATFORMS,
2110
+ MOBILE_PLATFORMS,
2086
2111
  };
@@ -319,68 +319,3 @@ describe("getRiverFromRoute", () => {
319
319
  expect(getRiverFromRoute({ route, rivers })).toEqual(river);
320
320
  });
321
321
  });
322
-
323
- describe("isPreviousRouteHook", () => {
324
- const { isPreviousRouteHook } = navigationUtils;
325
-
326
- const history = {
327
- entries: [],
328
- };
329
-
330
- it("returns false if it's a root route", () => {
331
- history.entries.push({ pathname: "/river/root" });
332
- const currentResult = isPreviousRouteHook(history.entries);
333
- expect(currentResult).toBe(false);
334
- });
335
-
336
- it("returns false if previous root is not a hook", () => {
337
- history.entries.push({ pathname: "/almostHooks/secondLevel" });
338
- const currentResult = isPreviousRouteHook(history.entries);
339
- expect(currentResult).toBe(false);
340
- });
341
-
342
- it("returns true if previous root is a hook", () => {
343
- history.entries.push({ pathname: "/hooks/ThirdLevel" });
344
- history.entries.push({ pathname: "/almostHooks/forthLevel" });
345
- const currentResult = isPreviousRouteHook(history.entries);
346
- expect(currentResult).toBe(true);
347
- });
348
- });
349
-
350
- describe("getPreviousHooksCount", () => {
351
- const { getPreviousHooksCount } = navigationUtils;
352
-
353
- const history = {
354
- entries: [],
355
- };
356
-
357
- it("returns 0 if it's a root route", () => {
358
- history.entries.push({ pathname: "/river/root" });
359
- const currentResult = getPreviousHooksCount(history);
360
- expect(currentResult).toBe(0);
361
- });
362
-
363
- it("returns 0 if previous root is not a hook", () => {
364
- history.entries.push({ pathname: "/almostHooks/secondLevel" });
365
- const currentResult = getPreviousHooksCount(history);
366
- expect(currentResult).toBe(0);
367
- });
368
-
369
- it("returns 1 if previous root is a hook", () => {
370
- history.entries = [];
371
- history.entries.push({ pathname: "/hooks/ThirdLevel" });
372
- history.entries.push({ pathname: "/almostHooks/forthLevel" });
373
- const currentResult = getPreviousHooksCount(history);
374
- expect(currentResult).toBe(1);
375
- });
376
-
377
- it("returns 2 if 2 previous routes are a hooks", () => {
378
- history.entries = [];
379
- history.entries.push({ pathname: "/almostHooks/forthLevel" });
380
- history.entries.push({ pathname: "/hooks/myHook" });
381
- history.entries.push({ pathname: "/hooks/myHook" });
382
- history.entries.push({ pathname: "/almostHooks/forthLevel" });
383
- const currentResult = getPreviousHooksCount(history);
384
- expect(currentResult).toBe(2);
385
- });
386
- });
@@ -363,37 +363,6 @@ export function getRiverFromRoute({
363
363
  return screenType && screenId ? { screenType, screenId } : null;
364
364
  }
365
365
 
366
- /**
367
- * Function returns true if the previous route (in the react-router history) is a hook plugin
368
- * @param {*} entries - React-Router History Object
369
- * @return boolean
370
- */
371
- export function isPreviousRouteHook(entries: [{ pathname: string }]): boolean {
372
- const previousRoute = entries[entries.length - 2];
373
- if (!previousRoute) return false;
374
-
375
- return R.test(/\/hooks\/.*/g, previousRoute.pathname);
376
- }
377
-
378
- /**
379
- * Function returns number of consecutive hooks that are behind the current route
380
- * @param {*} history - React-Router History Object
381
- * @return true
382
- */
383
- export function getPreviousHooksCount(history: {
384
- entries: [{ pathname: string }];
385
- }): number {
386
- let hooksCount = 0;
387
- let entries = R.clone(history.entries);
388
-
389
- while (isPreviousRouteHook(entries)) {
390
- hooksCount++;
391
- entries = R.init(entries);
392
- }
393
-
394
- return hooksCount;
395
- }
396
-
397
366
  export const usesVideoModal = (
398
367
  item: ZappEntry,
399
368
  rivers: Record<string, ZappRiver>,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "13.0.0-rc.99",
3
+ "version": "14.0.0-rc.2",
4
4
  "description": "Applicaster Zapp React Native utilities package",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "homepage": "https://github.com/applicaster/quickbrick#readme",
29
29
  "dependencies": {
30
- "@applicaster/applicaster-types": "13.0.0-rc.99",
30
+ "@applicaster/applicaster-types": "14.0.0-rc.2",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",
@@ -39,22 +39,6 @@ function getPlayerConfiguration({ platform }) {
39
39
  label_tooltip:
40
40
  "Disable if you don't want the content to have a right orientation",
41
41
  },
42
- {
43
- key: "magic_background",
44
- label: "Enable Magic Background",
45
- initial_value: false,
46
- type: "switch",
47
- label_tooltip:
48
- "Enable if you want to have adaptive background gradient based on audio background image.",
49
- },
50
- {
51
- key: "audio_player_background_image_query",
52
- label: "Audio Player Image Query",
53
- initial_value: "",
54
- type: "text_input",
55
- label_tooltip:
56
- "Set the query params which control the size of the image e.g width, height. This will help to optimize the loading time, when magic background is enabled.",
57
- },
58
42
  {
59
43
  key: "audio_player_background_image_default_color",
60
44
  label: "Audio Player Background Image Default Color",
@@ -1,5 +1,7 @@
1
1
  import * as R from "ramda";
2
2
  import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
3
+ import { isString } from "@applicaster/zapp-react-native-utils/typeGuards";
4
+
3
5
  import { getBoolFromConfigValue } from "../configurationUtils";
4
6
 
5
7
  /**
@@ -72,3 +74,18 @@ function isLiveByManager(): boolean {
72
74
  export function isLive(entry: ZappEntry): boolean {
73
75
  return isEntryLive(entry) || isLiveByManager();
74
76
  }
77
+
78
+ export const isAudioItem = (item: Option<ZappEntry>) => {
79
+ if (
80
+ isString(item?.content?.type) &&
81
+ item?.content?.type?.includes?.("audio")
82
+ ) {
83
+ return true;
84
+ }
85
+
86
+ if (isString(item?.type?.value) && item?.type?.value?.includes?.("audio")) {
87
+ return true;
88
+ }
89
+
90
+ return false;
91
+ };
@@ -14,10 +14,10 @@ export function useAppState(returnIsActive) {
14
14
  };
15
15
 
16
16
  React.useEffect(() => {
17
- AppState.addEventListener("change", setAppState);
17
+ const subscription = AppState.addEventListener("change", setAppState);
18
18
 
19
19
  return () => {
20
- AppState.removeEventListener("change", setAppState);
20
+ subscription.remove();
21
21
  };
22
22
  }, []);
23
23
 
@@ -1,7 +1,7 @@
1
- import { isNil, complement, compose, map, min, prop, take, uniq } from "ramda";
1
+ import { complement, compose, isNil, map, min, prop, take, uniq } from "ramda";
2
2
  import { useDispatch } from "react-redux";
3
3
  import * as React from "react";
4
- import { useZappPipesFeeds } from "@applicaster/zapp-react-native-redux/hooks/useZappPipesFeeds";
4
+ import { useZappPipesFeeds } from "@applicaster/zapp-react-native-redux/hooks";
5
5
  import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
6
6
  import { isNilOrEmpty } from "../../reactUtils/helpers";
7
7
  import { ZappPipesSearchContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
@@ -9,6 +9,7 @@ import {
9
9
  getInflatedDataSourceUrl,
10
10
  getSearchContext,
11
11
  } from "@applicaster/zapp-react-native-utils/reactHooks";
12
+ import { isGallery } from "@applicaster/zapp-react-native-utils/componentsUtils";
12
13
  import { useScreenContext } from "../screen/useScreenContext";
13
14
 
14
15
  type Options = {
@@ -46,9 +47,7 @@ const filterEmptyData = (data) => {
46
47
  };
47
48
 
48
49
  const getData = (rawData) =>
49
- rawData.component_type === "gallery-qb"
50
- ? rawData.ui_components[0].data
51
- : rawData.data;
50
+ isGallery(rawData) ? rawData.ui_components[0].data : rawData.data;
52
51
 
53
52
  const extractData = compose(uniq, map(getData));
54
53
 
@@ -70,13 +69,12 @@ export const useBatchLoading = (
70
69
  const [hasEverBeenReady, setHasEverBeenReady] = React.useState(false);
71
70
 
72
71
  // if first component is gallery-qb, take only one component for initial load
73
- const takeSize =
74
- componentsToRender?.[0]?.component_type === "gallery-qb"
75
- ? 1
76
- : min(
77
- options.initialBatchSize ?? DEFAULT_BATCH_SIZE,
78
- componentsToRender.length
79
- );
72
+ const takeSize = isGallery(componentsToRender?.[0])
73
+ ? 1
74
+ : min(
75
+ options.initialBatchSize ?? DEFAULT_BATCH_SIZE,
76
+ componentsToRender.length
77
+ );
80
78
 
81
79
  const takeBatchSize = React.useCallback(take(takeSize), [takeSize]);
82
80
 
@@ -3,7 +3,7 @@ import { platformSelect } from "../../reactUtils";
3
3
  const TAB_BAR_HEIGHT_IOS = 49;
4
4
  const TAB_BAR_HEIGHT_ANDROID = 56;
5
5
 
6
- export const useGetTabBarHeight = () =>
6
+ export const getTabBarHeight = () =>
7
7
  platformSelect({
8
8
  ios: TAB_BAR_HEIGHT_IOS,
9
9
  android: TAB_BAR_HEIGHT_ANDROID,
@@ -1,12 +1,19 @@
1
1
  import { useGetNavBarTopBorderWidth } from "./useGetNavBarTopBorderWidth";
2
- import { useGetTabBarHeight } from "./useGetTabBarHeight";
2
+ import { getTabBarHeight } from "./getTabBarHeight";
3
+ import { useNavigation } from "./useNavigation";
3
4
  import { MenuTypes, useNavigationType } from "./useNavigationType";
5
+ import { useNavigationPluginData } from "./useNavigationPluginData";
4
6
 
5
7
  export const useGetBottomTabBarHeight = (): number => {
8
+ const { activeRiver } = useNavigation();
9
+
10
+ const navigationPluginData = useNavigationPluginData(activeRiver);
11
+ const navigationType = useNavigationType(navigationPluginData);
12
+
6
13
  const topBorderWidth = useGetNavBarTopBorderWidth();
7
- const tabBarHeight = useGetTabBarHeight();
14
+ const tabBarHeight = getTabBarHeight();
8
15
 
9
- const isBottomBarNavigation = useNavigationType() === MenuTypes.bottomTabBar;
16
+ const isBottomBarNavigation = navigationType === MenuTypes.bottomTabBar;
10
17
 
11
18
  return !isBottomBarNavigation ? 0 : tabBarHeight + topBorderWidth;
12
19
  };
@@ -1,13 +1,17 @@
1
1
  import { useRoute } from "./useRoute";
2
2
 
3
- export const useNavigationPluginData = (): ZappNavigation | undefined => {
3
+ export const useNavigationPluginData = (
4
+ screenData?: LegacyNavigationScreenData | null
5
+ ): ZappNavigation | undefined => {
4
6
  const {
5
7
  screenData: useRouteScreenData,
6
8
  }: { screenData: QuickBrickNavigationData | null } = useRoute();
7
9
 
8
- const navigations = useRouteScreenData?.targetScreen
9
- ? (useRouteScreenData.targetScreen as ZappRiver).navigations
10
- : (useRouteScreenData as ZappRiver).navigations;
10
+ const activeScreenData = screenData ?? useRouteScreenData;
11
+
12
+ const navigations = activeScreenData?.targetScreen
13
+ ? (activeScreenData.targetScreen as ZappRiver).navigations
14
+ : (activeScreenData as ZappRiver).navigations;
11
15
 
12
16
  const navigationMenu = navigations?.find((nav) => nav.category === "menu");
13
17
 
@@ -5,8 +5,10 @@ export enum MenuTypes {
5
5
  bottomTabBar = "BOTTOM_TAB_BAR",
6
6
  }
7
7
 
8
- export const useNavigationType = (): MenuTypes => {
9
- const navigationMenu = useNavigationPluginData();
8
+ export const useNavigationType = (navigation?: ZappNavigation): MenuTypes => {
9
+ const navigationPluginData = useNavigationPluginData();
10
+
11
+ const navigationMenu = navigation ?? navigationPluginData;
10
12
 
11
13
  return !navigationMenu ||
12
14
  navigationMenu.navigation_type === "quick_brick_side_menu"
@@ -0,0 +1,69 @@
1
+ import { renderHook } from "@testing-library/react-hooks";
2
+
3
+ jest.mock(
4
+ "@applicaster/zapp-react-native-ui-components/Components/River/useScreenConfiguration",
5
+ () => ({
6
+ useScreenConfiguration: jest.fn(),
7
+ })
8
+ );
9
+
10
+ const {
11
+ useScreenConfiguration,
12
+ } = require("@applicaster/zapp-react-native-ui-components/Components/River/useScreenConfiguration");
13
+
14
+ const { useScreenBackgroundColor } = require("../useScreenBackgroundColor");
15
+
16
+ describe("useScreenBackgroundColor", () => {
17
+ afterEach(() => {
18
+ jest.clearAllMocks();
19
+ });
20
+
21
+ it("should return the background color from screen configuration", () => {
22
+ useScreenConfiguration.mockReturnValue({
23
+ backgroundColor: "#FF0000",
24
+ });
25
+
26
+ // Render the hook with a screen ID
27
+ const { result } = renderHook(() =>
28
+ useScreenBackgroundColor("test-screen-id")
29
+ );
30
+
31
+ expect(result.current).toBe("#FF0000");
32
+
33
+ expect(useScreenConfiguration).toHaveBeenCalledWith("test-screen-id");
34
+ });
35
+
36
+ it("should return 'transparent' when background color is empty", () => {
37
+ useScreenConfiguration.mockReturnValue({
38
+ backgroundColor: "",
39
+ });
40
+
41
+ const { result } = renderHook(() =>
42
+ useScreenBackgroundColor("test-screen-id")
43
+ );
44
+
45
+ expect(result.current).toBe("transparent");
46
+ });
47
+
48
+ it("should return 'transparent' when background color is null", () => {
49
+ useScreenConfiguration.mockReturnValue({
50
+ backgroundColor: null,
51
+ });
52
+
53
+ const { result } = renderHook(() =>
54
+ useScreenBackgroundColor("test-screen-id")
55
+ );
56
+
57
+ expect(result.current).toBe("transparent");
58
+ });
59
+
60
+ it("should return 'transparent' when background color is undefined", () => {
61
+ useScreenConfiguration.mockReturnValue({});
62
+
63
+ const { result } = renderHook(() =>
64
+ useScreenBackgroundColor("test-screen-id")
65
+ );
66
+
67
+ expect(result.current).toBe("transparent");
68
+ });
69
+ });
@@ -1,23 +1,11 @@
1
- import * as React from "react";
2
- import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
3
1
  import { useScreenConfiguration } from "@applicaster/zapp-react-native-ui-components/Components/River/useScreenConfiguration";
4
2
  import { ifEmptyUseFallback } from "@applicaster/zapp-react-native-utils/cellUtils";
5
3
 
4
+ const DEFAULT_BACKGROUND_FALLBACK = "transparent";
5
+
6
6
  export const useScreenBackgroundColor = (screenId: string): string => {
7
7
  const { backgroundColor: screenBackgroundColor } =
8
8
  useScreenConfiguration(screenId);
9
9
 
10
- const theme = useTheme();
11
-
12
- const themeBackgroundColor = React.useMemo(
13
- () => theme.app_background_color,
14
- [theme.app_background_color]
15
- );
16
-
17
- const backgroundColor = ifEmptyUseFallback(
18
- screenBackgroundColor,
19
- themeBackgroundColor
20
- );
21
-
22
- return backgroundColor;
10
+ return ifEmptyUseFallback(screenBackgroundColor, DEFAULT_BACKGROUND_FALLBACK);
23
11
  };
@@ -0,0 +1,79 @@
1
+ # ZStoreProvider and useZStore
2
+
3
+ This module provides a React context-based solution for managing Zustand stores with named access.
4
+
5
+ ## Usage
6
+
7
+ ### ZStoreProvider
8
+
9
+ The `ZStoreProvider` component creates a Zustand store from the provided value and makes it available to child components.
10
+
11
+ ```tsx
12
+ import { ZStoreProvider } from "@applicaster/zapp-react-native-utils/reactHooks/state";
13
+
14
+ // In your component
15
+ <ZStoreProvider name="playerConfiguration" value={controller?.config}>
16
+ <YourComponent />
17
+ </ZStoreProvider>
18
+ ```
19
+
20
+ ### useZStore
21
+
22
+ The `useZStore` hook allows you to access a Zustand store by name from within a `ZStoreProvider`.
23
+
24
+ ```tsx
25
+ import { useZStore } from "@applicaster/zapp-react-native-utils/reactHooks/state";
26
+ import { useStore } from "zustand";
27
+
28
+ // In your component
29
+ const MyComponent = () => {
30
+ const store = useZStore("playerConfiguration");
31
+ const config = useStore(store, (state) => state.someProperty);
32
+
33
+ return <Text>{config}</Text>;
34
+ };
35
+ ```
36
+
37
+ ## Example
38
+
39
+ ```tsx
40
+ import React from "react";
41
+ import { ZStoreProvider, useZStore } from "@applicaster/zapp-react-native-utils/reactHooks/state";
42
+ import { useStore } from "zustand";
43
+
44
+ // Component that uses the store
45
+ const PlayerConfigDisplay = () => {
46
+ const store = useZStore("playerConfiguration");
47
+ const config = useStore(store, (state) => state);
48
+
49
+ return (
50
+ <View>
51
+ <Text>Player Config: {JSON.stringify(config)}</Text>
52
+ </View>
53
+ );
54
+ };
55
+
56
+ // Main component that provides the store
57
+ const PlayerComponent = ({ controller }) => {
58
+ return (
59
+ <ZStoreProvider name="playerConfiguration" value={controller?.config}>
60
+ <PlayerConfigDisplay />
61
+ </ZStoreProvider>
62
+ );
63
+ };
64
+ ```
65
+
66
+ ## Features
67
+
68
+ - **Named Stores**: Access stores by name instead of importing them directly
69
+ - **Context-based**: Uses React Context for store management
70
+ - **Zustand Integration**: Seamlessly works with existing Zustand stores
71
+ - **Type Safety**: Full TypeScript support
72
+ - **Error Handling**: Clear error messages when stores are not found or used outside providers
73
+
74
+ ## Error Handling
75
+
76
+ The `useZStore` hook will throw errors in the following cases:
77
+
78
+ 1. **Used outside provider**: "useZStore must be used within a ZStoreProvider"
79
+ 2. **Store not found**: "Store with name 'storeName' not found. Make sure it's provided by a ZStoreProvider"
@@ -0,0 +1,71 @@
1
+ import React, { createContext, useContext, ReactNode, useMemo } from "react";
2
+ import { create, UseBoundStore } from "zustand";
3
+
4
+ interface ZStoreContextType {
5
+ stores: Map<string, UseBoundStore<any>>;
6
+ registerStore: (name: string, store: UseBoundStore<any>) => void;
7
+ getStore: (name: string) => UseBoundStore<any> | undefined;
8
+ }
9
+
10
+ const ZStoreContext = createContext<ZStoreContextType | null>(null);
11
+
12
+ interface ZStoreProviderProps {
13
+ children: ReactNode;
14
+ name: string;
15
+ value: any;
16
+ }
17
+
18
+ export const ZStoreProvider: React.FC<ZStoreProviderProps> = ({
19
+ children,
20
+ name,
21
+ value,
22
+ }) => {
23
+ const parentContext = useContext(ZStoreContext);
24
+
25
+ const context = useMemo(() => {
26
+ if (parentContext) {
27
+ // If parent context exists, create a new store and register it
28
+ const store = create(() => value);
29
+ parentContext.registerStore(name, store);
30
+
31
+ return parentContext;
32
+ }
33
+
34
+ // Create a new context if none exists
35
+ const stores = new Map<string, UseBoundStore<any>>();
36
+
37
+ // Create a store from the provided value
38
+ const store = create(() => value);
39
+ stores.set(name, store);
40
+
41
+ return {
42
+ stores,
43
+ registerStore: (storeName: string, storeInstance: UseBoundStore<any>) => {
44
+ stores.set(storeName, storeInstance);
45
+ },
46
+ getStore: (storeName: string) => stores.get(storeName),
47
+ };
48
+ }, [parentContext, name, value]);
49
+
50
+ return (
51
+ <ZStoreContext.Provider value={context}>{children}</ZStoreContext.Provider>
52
+ );
53
+ };
54
+
55
+ export const useZStore = (name: string): UseBoundStore<any> => {
56
+ const context = useContext(ZStoreContext);
57
+
58
+ if (!context) {
59
+ throw new Error("useZStore must be used within a ZStoreProvider");
60
+ }
61
+
62
+ const store = context.getStore(name);
63
+
64
+ if (!store) {
65
+ throw new Error(
66
+ `Store with name "${name}" not found. Make sure it's provided by a ZStoreProvider.`
67
+ );
68
+ }
69
+
70
+ return store;
71
+ };