@sigmela/router 0.0.11
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/LICENSE +20 -0
- package/README.md +346 -0
- package/lib/module/Navigation.js +74 -0
- package/lib/module/NavigationStack.js +72 -0
- package/lib/module/Router.js +571 -0
- package/lib/module/RouterContext.js +33 -0
- package/lib/module/ScreenStackItem.js +61 -0
- package/lib/module/StackRenderer.js +29 -0
- package/lib/module/TabBar/RenderTabBar.js +122 -0
- package/lib/module/TabBar/TabBar.js +74 -0
- package/lib/module/TabBar/TabBarContext.js +4 -0
- package/lib/module/TabBar/useTabBar.js +11 -0
- package/lib/module/createController.js +5 -0
- package/lib/module/index.js +14 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +3 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/Navigation.d.ts +8 -0
- package/lib/typescript/src/NavigationStack.d.ts +30 -0
- package/lib/typescript/src/Router.d.ts +70 -0
- package/lib/typescript/src/RouterContext.d.ts +19 -0
- package/lib/typescript/src/ScreenStackItem.d.ts +12 -0
- package/lib/typescript/src/StackRenderer.d.ts +6 -0
- package/lib/typescript/src/TabBar/RenderTabBar.d.ts +8 -0
- package/lib/typescript/src/TabBar/TabBar.d.ts +43 -0
- package/lib/typescript/src/TabBar/TabBarContext.d.ts +3 -0
- package/lib/typescript/src/TabBar/useTabBar.d.ts +2 -0
- package/lib/typescript/src/createController.d.ts +14 -0
- package/lib/typescript/src/index.d.ts +15 -0
- package/lib/typescript/src/types.d.ts +244 -0
- package/package.json +166 -0
- package/src/Navigation.tsx +102 -0
- package/src/NavigationStack.ts +106 -0
- package/src/Router.ts +684 -0
- package/src/RouterContext.tsx +58 -0
- package/src/ScreenStackItem.tsx +64 -0
- package/src/StackRenderer.tsx +41 -0
- package/src/TabBar/RenderTabBar.tsx +154 -0
- package/src/TabBar/TabBar.ts +106 -0
- package/src/TabBar/TabBarContext.ts +4 -0
- package/src/TabBar/useTabBar.ts +10 -0
- package/src/createController.ts +27 -0
- package/src/index.ts +24 -0
- package/src/types.ts +272 -0
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
);
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
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
|
+
);
|
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
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';
|