@sigmela/router 0.0.11 → 0.0.12

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.
@@ -1,58 +0,0 @@
1
- import type { StackPresentationTypes } from 'react-native-screens';
2
- import React, { createContext, useContext } from 'react';
3
- import type { Router } from './Router';
4
-
5
- export const RouterContext = createContext<Router | null>(null);
6
-
7
- type RouteLocalContextValue = {
8
- presentation: StackPresentationTypes;
9
- params?: Record<string, unknown>;
10
- query?: Record<string, unknown>;
11
- pattern?: string;
12
- path?: string;
13
- };
14
- export const RouteLocalContext = createContext<RouteLocalContextValue | null>(
15
- null
16
- );
17
-
18
- export const useRouter = (): Router => {
19
- const ctx = useContext(RouterContext);
20
- if (ctx == null) {
21
- throw new Error('useRouter must be used within RouterContext.Provider');
22
- }
23
- return ctx;
24
- };
25
-
26
- export const useCurrentRoute = () => {
27
- const router = useRouter();
28
- const subscribe = React.useCallback(
29
- (cb: () => void) => router.subscribe(cb),
30
- [router]
31
- );
32
- const get = React.useCallback(() => router.getVisibleRoute(), [router]);
33
- return React.useSyncExternalStore(subscribe, get, get);
34
- };
35
-
36
- export function useParams<
37
- TParams extends Record<string, unknown> = Record<string, unknown>,
38
- >(): TParams {
39
- const local = React.useContext(RouteLocalContext);
40
- return (local?.params ?? {}) as TParams;
41
- }
42
-
43
- export function useQueryParams<
44
- TQuery extends Record<string, unknown> = Record<string, unknown>,
45
- >(): TQuery {
46
- const local = React.useContext(RouteLocalContext);
47
- return (local?.query ?? {}) as TQuery;
48
- }
49
-
50
- export function useRoute(): RouteLocalContextValue {
51
- const local = React.useContext(RouteLocalContext);
52
-
53
- if (!local) {
54
- throw new Error('useRoute must be used within RouterLocalContext.Provider');
55
- }
56
-
57
- return local;
58
- }
@@ -1,64 +0,0 @@
1
- import { ScreenStackItem as RNSScreenStackItem } from 'react-native-screens';
2
- import { RouteLocalContext, useRouter } from './RouterContext';
3
- import type { HistoryItem, ScreenOptions, NavigationAppearance } from './types';
4
- import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native';
5
- import { memo } from 'react';
6
-
7
- interface ScreenStackItemProps {
8
- item: HistoryItem;
9
- stackId?: string;
10
- stackAnimation?: ScreenOptions['stackAnimation'];
11
- screenStyle?: StyleProp<ViewStyle>;
12
- headerAppearance?: NavigationAppearance['header'];
13
- }
14
-
15
- export const ScreenStackItem = memo<ScreenStackItemProps>(
16
- ({ item, stackId, stackAnimation, screenStyle }) => {
17
- const router = useRouter();
18
-
19
- const onDismissed = () => {
20
- if (stackId) {
21
- const history = router.getStackHistory(stackId);
22
- const topKey = history.length ? history[history.length - 1]?.key : null;
23
- if (topKey && topKey === item.key) {
24
- router.goBack();
25
- }
26
- }
27
- };
28
-
29
- const value = {
30
- presentation: item.options?.stackPresentation ?? 'push',
31
- params: item.params,
32
- query: item.query,
33
- pattern: item.pattern,
34
- path: item.path,
35
- };
36
-
37
- const { header, ...screenProps } = item.options || {};
38
- // Merge global header appearance with per-screen header options
39
- // const mergedHeader = {
40
- // ...(headerAppearance ?? {}),
41
- // ...(header ?? {}),
42
- // } as any;
43
- // Hide header by default if not specified
44
- const headerConfig = header ?? { hidden: true };
45
-
46
- // console.log('headerConfig', headerAppearance, item.key);
47
- return (
48
- <RNSScreenStackItem
49
- key={item.key}
50
- screenId={item.key}
51
- onDismissed={onDismissed}
52
- style={StyleSheet.absoluteFill}
53
- contentStyle={screenStyle}
54
- headerConfig={headerConfig}
55
- {...screenProps}
56
- stackAnimation={stackAnimation ?? item.options?.stackAnimation}
57
- >
58
- <RouteLocalContext.Provider value={value}>
59
- <item.component {...(item.passProps || {})} />
60
- </RouteLocalContext.Provider>
61
- </RNSScreenStackItem>
62
- );
63
- }
64
- );
@@ -1,41 +0,0 @@
1
- import { memo, useCallback, useSyncExternalStore } from 'react';
2
- import { ScreenStackItem } from './ScreenStackItem';
3
- import { NavigationStack } from './NavigationStack';
4
- import { ScreenStack } from 'react-native-screens';
5
- import { useRouter } from './RouterContext';
6
- import type { HistoryItem } from './types';
7
- import { StyleSheet } from 'react-native';
8
-
9
- export interface StackRendererProps {
10
- stack: NavigationStack;
11
- }
12
-
13
- export const StackRenderer = memo<StackRendererProps>(({ stack }) => {
14
- const router = useRouter();
15
- const stackId = stack.getId();
16
- const subscribe = useCallback(
17
- (cb: () => void) => router.subscribeStack(stackId, cb),
18
- [router, stackId]
19
- );
20
- const get = useCallback(
21
- () => router.getStackHistory(stackId),
22
- [router, stackId]
23
- );
24
- const historyForThisStack: HistoryItem[] = useSyncExternalStore(
25
- subscribe,
26
- get,
27
- get
28
- );
29
-
30
- return (
31
- <ScreenStack style={styles.flex}>
32
- {historyForThisStack.map((item) => (
33
- <ScreenStackItem key={item.key} item={item} stackId={stackId} />
34
- ))}
35
- </ScreenStack>
36
- );
37
- });
38
-
39
- const styles = StyleSheet.create({
40
- flex: { flex: 1 },
41
- });
@@ -1,154 +0,0 @@
1
- import { useCallback, useSyncExternalStore, memo, useEffect } from 'react';
2
- import { type NativeSyntheticEvent, Platform } from 'react-native';
3
- import type { NavigationAppearance } from '../types';
4
- import { StackRenderer } from '../StackRenderer';
5
- import { TabBarContext } from './TabBarContext';
6
- import { useRouter } from '../RouterContext';
7
- import type { TabBar } from './TabBar';
8
- import {
9
- type NativeFocusChangeEvent,
10
- BottomTabsScreen,
11
- BottomTabs,
12
- } from 'react-native-screens';
13
-
14
- export interface RenderTabBarProps {
15
- tabBar: TabBar;
16
- appearance?: NavigationAppearance['tabBar'];
17
- }
18
-
19
- // Helpers outside render to avoid re-creation
20
- const isImageSource = (value: any) => {
21
- if (value == null) return false;
22
- const valueType = typeof value;
23
- if (valueType === 'number') return true;
24
- if (Array.isArray(value)) return value.length > 0;
25
- if (valueType === 'object') {
26
- if ('uri' in value || 'width' in value || 'height' in value) return true;
27
- if (
28
- 'sfSymbolName' in value ||
29
- 'imageSource' in value ||
30
- 'templateSource' in value
31
- )
32
- return false;
33
- }
34
- return false;
35
- };
36
-
37
- const buildIOSIcon = (value: any) => {
38
- if (!value) return undefined;
39
- if (
40
- typeof value === 'object' &&
41
- (('sfSymbolName' in value ||
42
- 'imageSource' in value ||
43
- 'templateSource' in value) as any)
44
- ) {
45
- return value;
46
- }
47
- return { templateSource: value } as any;
48
- };
49
-
50
- export const RenderTabBar = memo<RenderTabBarProps>(
51
- ({ tabBar, appearance }) => {
52
- const router = useRouter();
53
- const subscribe = useCallback(
54
- (cb: () => void) => tabBar.subscribe(cb),
55
- [tabBar]
56
- );
57
- const snapshot = useSyncExternalStore(
58
- subscribe,
59
- tabBar.getState,
60
- tabBar.getState
61
- );
62
- const { tabs, index } = snapshot;
63
-
64
- const {
65
- standardAppearance,
66
- scrollEdgeAppearance,
67
- tabBarItemStyle,
68
- tintColor,
69
- backgroundColor,
70
- } = appearance ?? {};
71
-
72
- useEffect(() => {
73
- router.ensureTabSeed(index);
74
- }, [index, router]);
75
-
76
- const onNativeFocusChange = useCallback(
77
- (event: NativeSyntheticEvent<NativeFocusChangeEvent>) => {
78
- const tabKey = event.nativeEvent.tabKey;
79
- const tabIndex = tabs.findIndex((route) => route.tabKey === tabKey);
80
- router.onTabIndexChange(tabIndex);
81
- },
82
- [tabs, router]
83
- );
84
-
85
- return (
86
- <TabBarContext.Provider value={tabBar}>
87
- <BottomTabs
88
- onNativeFocusChange={onNativeFocusChange}
89
- tabBarBackgroundColor={backgroundColor}
90
- tabBarTintColor={tintColor}
91
- tabBarItemTitleFontFamily={tabBarItemStyle?.titleFontFamily}
92
- tabBarItemTitleFontSize={tabBarItemStyle?.titleFontSize}
93
- tabBarItemTitleFontSizeActive={tabBarItemStyle?.titleFontSizeActive}
94
- tabBarItemTitleFontWeight={tabBarItemStyle?.titleFontWeight}
95
- tabBarItemTitleFontStyle={tabBarItemStyle?.titleFontStyle}
96
- tabBarItemTitleFontColor={tabBarItemStyle?.titleFontColor}
97
- tabBarItemTitleFontColorActive={tabBarItemStyle?.titleFontColorActive}
98
- tabBarItemIconColor={tabBarItemStyle?.iconColor}
99
- tabBarItemIconColorActive={tabBarItemStyle?.iconColorActive}
100
- tabBarItemActiveIndicatorColor={tabBarItemStyle?.activeIndicatorColor}
101
- tabBarItemActiveIndicatorEnabled={
102
- tabBarItemStyle?.activeIndicatorEnabled
103
- }
104
- tabBarItemRippleColor={tabBarItemStyle?.rippleColor}
105
- tabBarItemLabelVisibilityMode={tabBarItemStyle?.labelVisibilityMode}
106
- // tabBarMinimizeBehavior={}
107
- >
108
- {tabs.map((tab) => {
109
- const isFocused = tab.tabKey === tabs[index]?.tabKey;
110
- const stack = tabBar.stacks[tab.tabKey];
111
- const Screen = tabBar.screens[tab.tabKey];
112
-
113
- // Map unified icon to platform-specific props expected by RNS BottomTabsScreen
114
- const { icon, selectedIcon, ...restTab } = tab as any;
115
-
116
- let mappedTabProps: any = restTab;
117
- if (icon || selectedIcon) {
118
- if (Platform.OS === 'android') {
119
- mappedTabProps = {
120
- ...restTab,
121
- ...(isImageSource(icon) ? { iconResource: icon } : null),
122
- };
123
- } else {
124
- mappedTabProps = {
125
- ...restTab,
126
- ...(icon ? { icon: buildIOSIcon(icon) } : null),
127
- ...(selectedIcon
128
- ? { selectedIcon: buildIOSIcon(selectedIcon) }
129
- : null),
130
- };
131
- }
132
- }
133
-
134
- return (
135
- <BottomTabsScreen
136
- scrollEdgeAppearance={scrollEdgeAppearance}
137
- standardAppearance={standardAppearance}
138
- isFocused={isFocused}
139
- key={tab.tabKey}
140
- {...mappedTabProps}
141
- >
142
- {stack ? (
143
- <StackRenderer stack={stack} />
144
- ) : Screen ? (
145
- <Screen />
146
- ) : null}
147
- </BottomTabsScreen>
148
- );
149
- })}
150
- </BottomTabs>
151
- </TabBarContext.Provider>
152
- );
153
- }
154
- );
@@ -1,106 +0,0 @@
1
- import React from 'react';
2
- import { NavigationStack } from '../NavigationStack';
3
- import type { TabItem } from '../types';
4
- import type { ImageSourcePropType } from 'react-native';
5
-
6
- type IOSIconShape =
7
- | { sfSymbolName: string }
8
- | { imageSource: ImageSourcePropType }
9
- | { templateSource: ImageSourcePropType };
10
-
11
- type ExtendedIcon = ImageSourcePropType | IOSIconShape;
12
-
13
- // Internal representation used by TabBar to support unified icon source while keeping original android props
14
- type InternalTabItem = Omit<TabItem, 'icon' | 'selectedIcon'> & {
15
- icon?: ExtendedIcon;
16
- selectedIcon?: ExtendedIcon;
17
- };
18
-
19
- type TabBarConfig = Omit<InternalTabItem, 'tabKey' | 'key'> & {
20
- stack?: NavigationStack;
21
- screen?: React.ComponentType<any>;
22
- };
23
- export class TabBar {
24
- public screens: Record<string, React.ComponentType<any>> = {};
25
- public stacks: Record<string, NavigationStack> = {};
26
- private listeners: Set<() => void> = new Set();
27
-
28
- private state: {
29
- tabs: InternalTabItem[];
30
- config: TabBarConfig;
31
- index: number;
32
- };
33
-
34
- constructor(config: TabBarConfig = {}) {
35
- this.state = {
36
- tabs: [],
37
- index: 0,
38
- config,
39
- };
40
- }
41
-
42
- public addTab(tab: Omit<TabBarConfig, 'tabKey'> & { key: string }): TabBar {
43
- const { key, ...rest } = tab;
44
- const nextIndex = this.state.tabs.length;
45
- const tabKey = key ?? `tab-${nextIndex}`;
46
-
47
- this.state.tabs.push({
48
- tabKey,
49
- ...rest,
50
- });
51
-
52
- if (tab.stack) {
53
- this.stacks[tabKey] = tab.stack;
54
- } else if (tab.screen) {
55
- this.screens[tabKey] = tab.screen;
56
- }
57
-
58
- return this;
59
- }
60
-
61
- public setBadge(tabIndex: number, badge: string | null): void {
62
- this.setState({
63
- tabs: this.state.tabs.map((route, index) =>
64
- index === tabIndex
65
- ? { ...route, badgeValue: badge ?? undefined }
66
- : route
67
- ),
68
- });
69
- }
70
-
71
- public setTabBarConfig(config: Partial<TabBarConfig>): void {
72
- this.setState({
73
- config: {
74
- ...this.state.config,
75
- ...config,
76
- },
77
- });
78
- }
79
-
80
- public onIndexChange(index: number) {
81
- this.setState({ index });
82
- }
83
-
84
- public getState = () => {
85
- return this.state;
86
- };
87
-
88
- private setState(state: Partial<typeof this.state>): void {
89
- this.state = {
90
- ...this.state,
91
- ...state,
92
- };
93
- this.notifyListeners();
94
- }
95
-
96
- private notifyListeners(): void {
97
- this.listeners.forEach((listener) => listener());
98
- }
99
-
100
- public subscribe(listener: () => void): () => void {
101
- this.listeners.add(listener);
102
- return () => {
103
- this.listeners.delete(listener);
104
- };
105
- }
106
- }
@@ -1,4 +0,0 @@
1
- import { createContext } from 'react';
2
- import { TabBar } from './TabBar';
3
-
4
- export const TabBarContext = createContext<TabBar | null>(null);
@@ -1,10 +0,0 @@
1
- import { useContext } from 'react';
2
- import { TabBarContext } from './TabBarContext';
3
-
4
- export const useTabBar = () => {
5
- const tabBar = useContext(TabBarContext);
6
- if (!tabBar) {
7
- throw new Error('useTabBar must be used within a TabBarProvider');
8
- }
9
- return tabBar;
10
- };
@@ -1,27 +0,0 @@
1
- type ControllerPresenter<P = any> = (passProps?: P) => void;
2
-
3
- type ControllerInput<TParams, TQuery> = {
4
- params: TParams;
5
- query: TQuery;
6
- };
7
-
8
- export type Controller<
9
- TParams = Record<string, unknown>,
10
- TQuery = Record<string, unknown>,
11
- > = (
12
- input: ControllerInput<TParams, TQuery>,
13
- present: ControllerPresenter
14
- ) => void;
15
-
16
- export type ComponentWithController = {
17
- controller?: Controller<any, any>;
18
- component: React.ComponentType<any>;
19
- };
20
-
21
- export type MixedComponent = ComponentWithController | React.ComponentType<any>;
22
-
23
- export function createController<TParams, TQuery = Record<string, unknown>>(
24
- controller: Controller<TParams, TQuery>
25
- ) {
26
- return controller;
27
- }
package/src/index.ts DELETED
@@ -1,24 +0,0 @@
1
- export { useTabBar } from './TabBar/useTabBar';
2
- export { TabBar } from './TabBar/TabBar';
3
-
4
- export type { NavigationProps } from './Navigation';
5
- export type { NavigationAppearance } from './types';
6
-
7
- /**
8
- * Navigation System
9
- */
10
-
11
- export { Router } from './Router';
12
- export { Navigation } from './Navigation';
13
- export { createController } from './createController';
14
- export { NavigationStack } from './NavigationStack';
15
- export type { HistoryItem } from './types';
16
- export { StackRenderer } from './StackRenderer';
17
-
18
- export {
19
- useRouter,
20
- useCurrentRoute,
21
- useParams,
22
- useQueryParams,
23
- useRoute,
24
- } from './RouterContext';