@umituz/react-native-design-system 2.3.13 → 2.3.15
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/package.json +32 -13
- package/src/index.ts +116 -0
- package/src/layouts/ScreenLayout/ScreenLayout.example.tsx +2 -2
- package/src/layouts/ScreenLayout/ScreenLayout.tsx +1 -1
- package/src/molecules/animation/core/AnimationCore.ts +29 -0
- package/src/molecules/animation/domain/entities/Animation.ts +81 -0
- package/src/molecules/animation/domain/entities/Fireworks.ts +44 -0
- package/src/molecules/animation/domain/entities/Theme.ts +76 -0
- package/src/molecules/animation/index.ts +146 -0
- package/src/molecules/animation/infrastructure/services/AnimationConfigService.ts +35 -0
- package/src/molecules/animation/infrastructure/services/SpringAnimationConfigService.ts +67 -0
- package/src/molecules/animation/infrastructure/services/TimingAnimationConfigService.ts +57 -0
- package/src/molecules/animation/infrastructure/services/__tests__/SpringAnimationConfigService.test.ts +114 -0
- package/src/molecules/animation/infrastructure/services/__tests__/TimingAnimationConfigService.test.ts +105 -0
- package/src/molecules/animation/presentation/components/Fireworks.tsx +126 -0
- package/src/molecules/animation/presentation/components/__tests__/Fireworks.test.tsx +189 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useAnimation.integration.test.ts +216 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useFireworks.test.ts +242 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useGesture.test.ts +111 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useSpringAnimation.test.ts +131 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useTimingAnimation.test.ts +175 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useTransformAnimation.test.ts +137 -0
- package/src/molecules/animation/presentation/hooks/useAnimation.ts +77 -0
- package/src/molecules/animation/presentation/hooks/useFireworks.ts +141 -0
- package/src/molecules/animation/presentation/hooks/useGesture.ts +61 -0
- package/src/molecules/animation/presentation/hooks/useGestureCreators.ts +163 -0
- package/src/molecules/animation/presentation/hooks/useGestureState.ts +53 -0
- package/src/molecules/animation/presentation/hooks/useIconAnimations.ts +119 -0
- package/src/molecules/animation/presentation/hooks/useModalAnimations.ts +124 -0
- package/src/molecules/animation/presentation/hooks/useReanimatedReady.ts +60 -0
- package/src/molecules/animation/presentation/hooks/useSpringAnimation.ts +69 -0
- package/src/molecules/animation/presentation/hooks/useTimingAnimation.ts +111 -0
- package/src/molecules/animation/presentation/hooks/useTransformAnimation.ts +57 -0
- package/src/molecules/animation/presentation/providers/AnimationThemeProvider.tsx +62 -0
- package/src/molecules/animation/presentation/providers/__tests__/AnimationThemeProvider.test.tsx +165 -0
- package/src/molecules/animation/types/global.d.ts +97 -0
- package/src/molecules/calendar/domain/entities/CalendarDay.entity.ts +115 -0
- package/src/molecules/calendar/domain/entities/CalendarEvent.entity.ts +202 -0
- package/src/molecules/calendar/domain/repositories/ICalendarRepository.ts +120 -0
- package/src/molecules/calendar/index.ts +98 -0
- package/src/molecules/calendar/infrastructure/services/CalendarEvents.ts +196 -0
- package/src/molecules/calendar/infrastructure/services/CalendarGeneration.ts +172 -0
- package/src/molecules/calendar/infrastructure/services/CalendarPermissions.ts +92 -0
- package/src/molecules/calendar/infrastructure/services/CalendarService.ts +161 -0
- package/src/molecules/calendar/infrastructure/services/CalendarSync.ts +205 -0
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.ts +307 -0
- package/src/molecules/calendar/infrastructure/utils/DateUtilities.ts +128 -0
- package/src/molecules/calendar/presentation/components/AtomicCalendar.tsx +279 -0
- package/src/molecules/calendar/presentation/hooks/useCalendar.ts +356 -0
- package/src/molecules/celebration/domain/entities/CelebrationConfig.ts +17 -0
- package/src/molecules/celebration/domain/entities/FireworksConfig.ts +32 -0
- package/src/molecules/celebration/index.ts +93 -0
- package/src/molecules/celebration/infrastructure/services/FireworksConfigService.ts +49 -0
- package/src/molecules/celebration/presentation/components/CelebrationFireworksOverlay.tsx +33 -0
- package/src/molecules/celebration/presentation/components/CelebrationModal.tsx +78 -0
- package/src/molecules/celebration/presentation/components/CelebrationModalContent.tsx +90 -0
- package/src/molecules/celebration/presentation/hooks/useCelebrationModalAnimation.ts +49 -0
- package/src/molecules/celebration/presentation/hooks/useCelebrationState.ts +45 -0
- package/src/molecules/celebration/presentation/styles/CelebrationModalStyles.ts +65 -0
- package/src/molecules/countdown/components/Countdown.tsx +128 -0
- package/src/molecules/countdown/components/CountdownHeader.tsx +84 -0
- package/src/molecules/countdown/components/TimeUnit.tsx +73 -0
- package/src/molecules/countdown/hooks/useCountdown.ts +107 -0
- package/src/molecules/countdown/index.ts +25 -0
- package/src/molecules/countdown/types/CountdownTypes.ts +31 -0
- package/src/molecules/countdown/utils/TimeCalculator.ts +46 -0
- package/src/molecules/emoji/domain/entities/Emoji.ts +129 -0
- package/src/molecules/emoji/index.ts +177 -0
- package/src/molecules/emoji/presentation/components/EmojiPicker.tsx +102 -0
- package/src/molecules/emoji/presentation/hooks/useEmojiPicker.ts +171 -0
- package/src/molecules/index.ts +24 -0
- package/src/molecules/long-press-menu/domain/entities/MenuAction.ts +37 -0
- package/src/molecules/long-press-menu/index.ts +16 -0
- package/src/molecules/navigation/StackNavigator.tsx +75 -0
- package/src/molecules/navigation/TabsNavigator.tsx +94 -0
- package/src/molecules/navigation/components/FabButton.tsx +45 -0
- package/src/molecules/navigation/components/TabLabel.tsx +47 -0
- package/src/molecules/navigation/createStackNavigator.ts +20 -0
- package/src/molecules/navigation/createTabNavigator.ts +20 -0
- package/src/molecules/navigation/hooks/useTabBarStyles.ts +54 -0
- package/src/molecules/navigation/index.ts +37 -0
- package/src/molecules/navigation/types.ts +118 -0
- package/src/molecules/navigation/utils/AppNavigation.ts +101 -0
- package/src/molecules/navigation/utils/IconRenderer.ts +50 -0
- package/src/molecules/navigation/utils/LabelProcessor.ts +70 -0
- package/src/molecules/navigation/utils/NavigationCleanup.ts +62 -0
- package/src/molecules/navigation/utils/NavigationTheme.ts +21 -0
- package/src/molecules/navigation/utils/NavigationValidator.ts +61 -0
- package/src/molecules/navigation/utils/ScreenFactory.ts +115 -0
- package/src/molecules/navigation/utils/__tests__/IconRenderer.getIconName.test.ts +109 -0
- package/src/molecules/navigation/utils/__tests__/IconRenderer.renderIcon.test.ts +116 -0
- package/src/molecules/navigation/utils/__tests__/LabelProcessor.processLabel.test.ts +116 -0
- package/src/molecules/navigation/utils/__tests__/LabelProcessor.processTitle.test.ts +59 -0
- package/src/molecules/navigation/utils/__tests__/NavigationCleanup.test.ts +271 -0
- package/src/molecules/navigation/utils/__tests__/NavigationValidator.test.ts +252 -0
- package/src/molecules/swipe-actions/domain/entities/SwipeAction.ts +194 -0
- package/src/molecules/swipe-actions/index.ts +6 -0
- package/src/molecules/swipe-actions/presentation/components/SwipeActionButton.tsx +131 -0
- package/src/theme/hooks/useResponsiveDesignTokens.ts +1 -1
- package/src/utilities/clipboard/ClipboardUtils.ts +71 -0
- package/src/utilities/clipboard/index.ts +5 -0
- package/src/utilities/index.ts +6 -0
- package/src/utilities/sharing/domain/entities/Share.ts +210 -0
- package/src/utilities/sharing/index.ts +205 -0
- package/src/utilities/sharing/infrastructure/services/SharingService.ts +165 -0
- package/src/utilities/sharing/presentation/hooks/useSharing.ts +154 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { createStackNavigator as createRNStackNavigator } from "@react-navigation/stack";
|
|
3
|
+
import type { ParamListBase } from "@react-navigation/native";
|
|
4
|
+
import type { StackNavigatorConfig, StackScreen } from "./types";
|
|
5
|
+
import { NavigationValidator } from "./utils/NavigationValidator";
|
|
6
|
+
import { createStackScreen } from "./utils/ScreenFactory";
|
|
7
|
+
import { useAppDesignTokens } from "../../theme";
|
|
8
|
+
|
|
9
|
+
// Create the navigator instance ONCE outside the component
|
|
10
|
+
const Stack = createRNStackNavigator();
|
|
11
|
+
|
|
12
|
+
export interface StackNavigatorProps<T extends ParamListBase> {
|
|
13
|
+
config: StackNavigatorConfig<T>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* StackNavigator Component
|
|
18
|
+
*
|
|
19
|
+
* Reusable Stack Navigator component that handles configuration and rendering.
|
|
20
|
+
* Integrates with design tokens for consistent themed styling.
|
|
21
|
+
*/
|
|
22
|
+
export function StackNavigator<T extends ParamListBase>({ config }: StackNavigatorProps<T>) {
|
|
23
|
+
const tokens = useAppDesignTokens();
|
|
24
|
+
|
|
25
|
+
// Validate configuration
|
|
26
|
+
if (__DEV__) {
|
|
27
|
+
try {
|
|
28
|
+
NavigationValidator.validateScreens(config.screens, "stack");
|
|
29
|
+
NavigationValidator.validateInitialRoute(config.initialRouteName, config.screens);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (__DEV__) console.error('[StackNavigator] Configuration validation failed:', error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { screens } = config;
|
|
36
|
+
|
|
37
|
+
// Default screen options using design tokens
|
|
38
|
+
const defaultScreenOptions = useMemo(() => ({
|
|
39
|
+
headerStyle: {
|
|
40
|
+
backgroundColor: tokens.colors.surface,
|
|
41
|
+
elevation: 0,
|
|
42
|
+
shadowOpacity: 0,
|
|
43
|
+
borderBottomWidth: 1,
|
|
44
|
+
borderBottomColor: tokens.colors.surfaceVariant,
|
|
45
|
+
},
|
|
46
|
+
headerTitleStyle: {
|
|
47
|
+
color: tokens.colors.onSurface,
|
|
48
|
+
fontSize: 18,
|
|
49
|
+
fontWeight: "600" as any,
|
|
50
|
+
},
|
|
51
|
+
headerTintColor: tokens.colors.primary,
|
|
52
|
+
cardStyle: {
|
|
53
|
+
backgroundColor: tokens.colors.background,
|
|
54
|
+
},
|
|
55
|
+
}), [tokens]);
|
|
56
|
+
|
|
57
|
+
if (screens.length === 0) {
|
|
58
|
+
if (__DEV__) console.warn('[StackNavigator] No screens found');
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Stack.Navigator
|
|
64
|
+
initialRouteName={config.initialRouteName as string}
|
|
65
|
+
screenOptions={{
|
|
66
|
+
...defaultScreenOptions,
|
|
67
|
+
...(config.screenOptions as any),
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
{screens.map((screen) =>
|
|
71
|
+
createStackScreen(screen as StackScreen<any>, config, Stack)
|
|
72
|
+
)}
|
|
73
|
+
</Stack.Navigator>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
|
3
|
+
import type { ParamListBase } from "@react-navigation/native";
|
|
4
|
+
import type { TabNavigatorConfig, TabScreen } from "./types";
|
|
5
|
+
import { NavigationValidator } from "./utils/NavigationValidator";
|
|
6
|
+
import { createTabScreen } from "./utils/ScreenFactory";
|
|
7
|
+
import { useAppDesignTokens } from "../../theme";
|
|
8
|
+
|
|
9
|
+
// Create the navigator instance ONCE outside the component
|
|
10
|
+
const Tab = createBottomTabNavigator();
|
|
11
|
+
|
|
12
|
+
export interface TabsNavigatorProps<T extends ParamListBase> {
|
|
13
|
+
config: TabNavigatorConfig<T>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* TabsNavigator Component
|
|
18
|
+
*
|
|
19
|
+
* Reusable Bottom Tab Navigator component that handles configuration and rendering.
|
|
20
|
+
* Integrates with design tokens for consistent themed styling.
|
|
21
|
+
*/
|
|
22
|
+
export function TabsNavigator<T extends ParamListBase>({
|
|
23
|
+
config,
|
|
24
|
+
}: TabsNavigatorProps<T>) {
|
|
25
|
+
const tokens = useAppDesignTokens();
|
|
26
|
+
|
|
27
|
+
// Validate configuration
|
|
28
|
+
if (__DEV__) {
|
|
29
|
+
try {
|
|
30
|
+
NavigationValidator.validateScreens(config.screens, "tab");
|
|
31
|
+
NavigationValidator.validateInitialRoute(
|
|
32
|
+
config.initialRouteName,
|
|
33
|
+
config.screens
|
|
34
|
+
);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (__DEV__)
|
|
37
|
+
console.error("[TabsNavigator] Configuration validation failed:", error);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Memoize filtered screens
|
|
42
|
+
const visibleScreens = useMemo(
|
|
43
|
+
() => config.screens.filter((screen) => screen.visible !== false),
|
|
44
|
+
[config.screens]
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Default screen options using design tokens
|
|
48
|
+
const defaultScreenOptions = useMemo(
|
|
49
|
+
() => ({
|
|
50
|
+
tabBarActiveTintColor: tokens.colors.primary,
|
|
51
|
+
tabBarInactiveTintColor: tokens.colors.onSurface,
|
|
52
|
+
tabBarStyle: {
|
|
53
|
+
backgroundColor: tokens.colors.surface,
|
|
54
|
+
borderTopColor: tokens.colors.surfaceVariant,
|
|
55
|
+
height: (tokens.spacing as any).tabBarHeight || 60,
|
|
56
|
+
paddingBottom: 8,
|
|
57
|
+
paddingTop: 8,
|
|
58
|
+
},
|
|
59
|
+
headerStyle: {
|
|
60
|
+
backgroundColor: tokens.colors.surface,
|
|
61
|
+
elevation: 0,
|
|
62
|
+
shadowOpacity: 0,
|
|
63
|
+
borderBottomWidth: 1,
|
|
64
|
+
borderBottomColor: tokens.colors.surfaceVariant,
|
|
65
|
+
},
|
|
66
|
+
headerTitleStyle: {
|
|
67
|
+
color: tokens.colors.onSurface,
|
|
68
|
+
fontSize: 18,
|
|
69
|
+
fontWeight: "600" as any,
|
|
70
|
+
},
|
|
71
|
+
headerTintColor: tokens.colors.primary,
|
|
72
|
+
}),
|
|
73
|
+
[tokens]
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (visibleScreens.length === 0) {
|
|
77
|
+
if (__DEV__) console.warn("[TabsNavigator] No visible screens found");
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Tab.Navigator
|
|
83
|
+
initialRouteName={config.initialRouteName as string}
|
|
84
|
+
screenOptions={{
|
|
85
|
+
...defaultScreenOptions,
|
|
86
|
+
...(config.screenOptions as any),
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
{visibleScreens.map((screen) =>
|
|
90
|
+
createTabScreen(screen as TabScreen<any>, config, Tab)
|
|
91
|
+
)}
|
|
92
|
+
</Tab.Navigator>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleSheet, View, type ViewStyle } from 'react-native';
|
|
3
|
+
import { useAppDesignTokens } from '../../../theme';
|
|
4
|
+
|
|
5
|
+
export interface FabButtonProps {
|
|
6
|
+
/** Content to render inside the FAB */
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
/** Container style overrides */
|
|
9
|
+
style?: ViewStyle;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Standard FAB container component.
|
|
14
|
+
* Provides a centered, elevated circular container using design tokens.
|
|
15
|
+
*/
|
|
16
|
+
export const FabButton: React.FC<FabButtonProps> = ({
|
|
17
|
+
children,
|
|
18
|
+
style,
|
|
19
|
+
}) => {
|
|
20
|
+
const tokens = useAppDesignTokens();
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<View style={[
|
|
24
|
+
styles.container,
|
|
25
|
+
{
|
|
26
|
+
backgroundColor: tokens.colors.primary,
|
|
27
|
+
borderColor: tokens.colors.onPrimary,
|
|
28
|
+
shadowColor: tokens.colors.onSurface,
|
|
29
|
+
},
|
|
30
|
+
style
|
|
31
|
+
]}>
|
|
32
|
+
{children}
|
|
33
|
+
</View>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const styles = StyleSheet.create({
|
|
38
|
+
container: {
|
|
39
|
+
alignItems: 'center',
|
|
40
|
+
justifyContent: 'center',
|
|
41
|
+
borderWidth: 1,
|
|
42
|
+
borderColor: "rgba(255, 255, 255, 0.1)",
|
|
43
|
+
zIndex: 10,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { AtomicText } from '../../../atoms';
|
|
3
|
+
|
|
4
|
+
export interface TabLabelProps {
|
|
5
|
+
label: string;
|
|
6
|
+
focused: boolean;
|
|
7
|
+
color?: string;
|
|
8
|
+
focusedColor?: string;
|
|
9
|
+
unfocusedColor?: string;
|
|
10
|
+
fontSize?: number;
|
|
11
|
+
focusedWeight?: '400' | '500' | '600' | '700' | '800' | '900';
|
|
12
|
+
unfocusedWeight?: '400' | '500' | '600' | '700' | '800' | '900';
|
|
13
|
+
textStyle?: any;
|
|
14
|
+
textType?: 'labelSmall' | 'labelMedium' | 'labelLarge' | 'bodySmall';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const TabLabel: React.FC<TabLabelProps> = React.memo(({
|
|
18
|
+
label,
|
|
19
|
+
focused,
|
|
20
|
+
color,
|
|
21
|
+
focusedColor,
|
|
22
|
+
unfocusedColor,
|
|
23
|
+
fontSize,
|
|
24
|
+
focusedWeight = '600',
|
|
25
|
+
unfocusedWeight = '500',
|
|
26
|
+
textStyle,
|
|
27
|
+
textType = 'labelSmall',
|
|
28
|
+
}) => {
|
|
29
|
+
const textStyleMemo = useMemo(() => [
|
|
30
|
+
{
|
|
31
|
+
color: color || (focused ? focusedColor : unfocusedColor),
|
|
32
|
+
textAlign: 'center' as const,
|
|
33
|
+
fontSize,
|
|
34
|
+
fontWeight: focused ? focusedWeight : unfocusedWeight,
|
|
35
|
+
},
|
|
36
|
+
textStyle,
|
|
37
|
+
], [color, focused, focusedColor, unfocusedColor, fontSize, focusedWeight, unfocusedWeight, textStyle]);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<AtomicText
|
|
41
|
+
type={textType}
|
|
42
|
+
style={textStyleMemo}
|
|
43
|
+
>
|
|
44
|
+
{label}
|
|
45
|
+
</AtomicText>
|
|
46
|
+
);
|
|
47
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ParamListBase } from "@react-navigation/native";
|
|
3
|
+
import type { StackNavigatorConfig } from "./types";
|
|
4
|
+
import { StackNavigator } from "./StackNavigator";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a Stack Navigator component based on configuration.
|
|
8
|
+
*
|
|
9
|
+
* @deprecated Use <StackNavigator config={...} /> instead to prevent re-mounting issues.
|
|
10
|
+
* This wrapper is maintained for backward compatibility but using the component directly is recommended.
|
|
11
|
+
*/
|
|
12
|
+
export function createStackNavigator<T extends ParamListBase>(
|
|
13
|
+
config: StackNavigatorConfig<T>
|
|
14
|
+
): React.ComponentType {
|
|
15
|
+
// Return a component that renders the new StackNavigator
|
|
16
|
+
// Using React.memo to prevent unnecessary re-renders of the wrapper
|
|
17
|
+
return React.memo(function StackNavigatorWrapper() {
|
|
18
|
+
return React.createElement(StackNavigator as any, { config });
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ParamListBase } from "@react-navigation/native";
|
|
3
|
+
import type { TabNavigatorConfig } from "./types";
|
|
4
|
+
import { TabsNavigator } from "./TabsNavigator";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a Tab Navigator component based on configuration.
|
|
8
|
+
*
|
|
9
|
+
* @deprecated Use <TabsNavigator config={...} /> instead to prevent re-mounting issues.
|
|
10
|
+
* This wrapper is maintained for backward compatibility but using the component directly is recommended.
|
|
11
|
+
*/
|
|
12
|
+
export function createTabNavigator<T extends ParamListBase>(
|
|
13
|
+
config: TabNavigatorConfig<T>
|
|
14
|
+
): React.ComponentType {
|
|
15
|
+
// Return a component that renders the new TabsNavigator
|
|
16
|
+
// Using React.memo to prevent unnecessary re-renders of the wrapper
|
|
17
|
+
return React.memo(function TabNavigatorWrapper() {
|
|
18
|
+
return React.createElement(TabsNavigator as any, { config });
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { useAppDesignTokens } from '../../../theme';
|
|
4
|
+
|
|
5
|
+
export interface TabBarConfig {
|
|
6
|
+
backgroundColor?: string;
|
|
7
|
+
borderTopColor?: string;
|
|
8
|
+
borderTopWidth?: number;
|
|
9
|
+
paddingTop?: number;
|
|
10
|
+
paddingBottom?: number;
|
|
11
|
+
minHeight?: number;
|
|
12
|
+
activeTintColor?: string;
|
|
13
|
+
inactiveTintColor?: string;
|
|
14
|
+
labelFontSize?: number;
|
|
15
|
+
labelFontWeight?: string;
|
|
16
|
+
labelMarginTop?: number;
|
|
17
|
+
labelMarginBottom?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function useTabBarStyles(config: TabBarConfig = {}) {
|
|
21
|
+
const tokens = useAppDesignTokens();
|
|
22
|
+
|
|
23
|
+
const tabBarStyle = useMemo(() => ({
|
|
24
|
+
backgroundColor: config.backgroundColor || tokens.colors.surface,
|
|
25
|
+
borderTopColor: config.borderTopColor || tokens.colors.borderLight,
|
|
26
|
+
borderTopWidth: config.borderTopWidth ?? 1,
|
|
27
|
+
paddingTop: config.paddingTop ?? 12,
|
|
28
|
+
paddingBottom: config.paddingBottom ?? (Platform.OS === 'ios' ? 24 : 12),
|
|
29
|
+
minHeight: config.minHeight ?? (Platform.OS === 'ios' ? 80 : 70),
|
|
30
|
+
}), [config.backgroundColor, config.borderTopColor, config.borderTopWidth,
|
|
31
|
+
config.paddingTop, config.paddingBottom, config.minHeight, tokens.colors.surface,
|
|
32
|
+
tokens.colors.borderLight]);
|
|
33
|
+
|
|
34
|
+
const screenOptions = useMemo(() => ({
|
|
35
|
+
headerShown: false,
|
|
36
|
+
tabBarLabelStyle: {
|
|
37
|
+
fontSize: config.labelFontSize ?? 12,
|
|
38
|
+
fontWeight: (config.labelFontWeight ?? '600') as '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | 'normal' | 'bold',
|
|
39
|
+
marginTop: config.labelMarginTop ?? 12,
|
|
40
|
+
marginBottom: config.labelMarginBottom ?? 4,
|
|
41
|
+
},
|
|
42
|
+
tabBarActiveTintColor: config.activeTintColor || tokens.colors.primary,
|
|
43
|
+
tabBarInactiveTintColor: config.inactiveTintColor || tokens.colors.textSecondary,
|
|
44
|
+
tabBarStyle,
|
|
45
|
+
}), [config.labelFontSize, config.labelFontWeight, config.labelMarginTop,
|
|
46
|
+
config.labelMarginBottom, config.activeTintColor, config.inactiveTintColor,
|
|
47
|
+
tabBarStyle, tokens.colors.primary, tokens.colors.textSecondary]);
|
|
48
|
+
|
|
49
|
+
return useMemo(() => ({
|
|
50
|
+
tokens,
|
|
51
|
+
screenOptions,
|
|
52
|
+
tabBarStyle,
|
|
53
|
+
}), [tokens, screenOptions, tabBarStyle]);
|
|
54
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export { createTabNavigator } from "./createTabNavigator";
|
|
2
|
+
export { createStackNavigator } from "./createStackNavigator";
|
|
3
|
+
export { TabsNavigator, type TabsNavigatorProps } from "./TabsNavigator";
|
|
4
|
+
export { StackNavigator, type StackNavigatorProps } from "./StackNavigator";
|
|
5
|
+
export { FabButton, type FabButtonProps } from "./components/FabButton";
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
TabScreen,
|
|
9
|
+
TabNavigatorConfig,
|
|
10
|
+
StackScreen,
|
|
11
|
+
StackNavigatorConfig,
|
|
12
|
+
BaseScreen,
|
|
13
|
+
BaseNavigatorConfig,
|
|
14
|
+
IconRendererProps,
|
|
15
|
+
LabelProcessorProps,
|
|
16
|
+
FabConfig,
|
|
17
|
+
} from "./types";
|
|
18
|
+
|
|
19
|
+
export { DEFAULT_FAB_CONFIG } from "./types";
|
|
20
|
+
|
|
21
|
+
export { NavigationCleanupManager } from "./utils/NavigationCleanup";
|
|
22
|
+
export type { NavigationCleanup } from "./utils/NavigationCleanup";
|
|
23
|
+
|
|
24
|
+
export type {
|
|
25
|
+
BottomTabNavigationOptions,
|
|
26
|
+
BottomTabScreenProps,
|
|
27
|
+
} from "@react-navigation/bottom-tabs";
|
|
28
|
+
|
|
29
|
+
export type { StackNavigationOptions } from "@react-navigation/stack";
|
|
30
|
+
|
|
31
|
+
// Navigation Utilities
|
|
32
|
+
export { AppNavigation } from "./utils/AppNavigation";
|
|
33
|
+
export { TabLabel, type TabLabelProps } from "./components/TabLabel";
|
|
34
|
+
export { useTabBarStyles, type TabBarConfig } from "./hooks/useTabBarStyles";
|
|
35
|
+
|
|
36
|
+
// Navigation Theme
|
|
37
|
+
export { createNavigationTheme } from "./utils/NavigationTheme";
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { ParamListBase } from "@react-navigation/native";
|
|
2
|
+
import type {
|
|
3
|
+
BottomTabNavigationOptions,
|
|
4
|
+
BottomTabScreenProps,
|
|
5
|
+
} from "@react-navigation/bottom-tabs";
|
|
6
|
+
import type { StackNavigationOptions } from "@react-navigation/stack";
|
|
7
|
+
import type { IconName } from "../../atoms/AtomicIcon";
|
|
8
|
+
|
|
9
|
+
export type NavigationParams = Record<string, unknown>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* FAB (Floating Action Button) configuration for center tab
|
|
13
|
+
*/
|
|
14
|
+
export interface FabConfig {
|
|
15
|
+
/** FAB button size (default: 56) */
|
|
16
|
+
size?: number;
|
|
17
|
+
/** Vertical offset from tab bar (default: -20) */
|
|
18
|
+
offsetY?: number;
|
|
19
|
+
/** Border radius (default: size / 2) */
|
|
20
|
+
borderRadius?: number;
|
|
21
|
+
/** Border width when inactive (default: 3) */
|
|
22
|
+
borderWidth?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Base interface for all screen configurations
|
|
27
|
+
* @template T - Type of navigation parameters for the screen
|
|
28
|
+
*/
|
|
29
|
+
export interface BaseScreen<T extends ParamListBase = ParamListBase> {
|
|
30
|
+
/** Unique name identifier for the screen */
|
|
31
|
+
name: string;
|
|
32
|
+
/** React component to render for this screen */
|
|
33
|
+
component: React.ComponentType<T>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Configuration for a tab navigation screen
|
|
38
|
+
* @template T - Type of navigation parameters for the screen
|
|
39
|
+
*/
|
|
40
|
+
export interface TabScreen<T extends ParamListBase = ParamListBase>
|
|
41
|
+
extends BaseScreen<T> {
|
|
42
|
+
/** Display label for the tab */
|
|
43
|
+
label: string;
|
|
44
|
+
/** Icon identifier for the tab */
|
|
45
|
+
icon?: IconName;
|
|
46
|
+
/** Mark this tab as a FAB (center floating button) */
|
|
47
|
+
isFab?: boolean;
|
|
48
|
+
/** Additional navigation options for the tab */
|
|
49
|
+
options?:
|
|
50
|
+
| BottomTabNavigationOptions
|
|
51
|
+
| ((props: BottomTabScreenProps<T>) => BottomTabNavigationOptions);
|
|
52
|
+
/** Whether the tab should be visible */
|
|
53
|
+
visible?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Configuration for a stack navigation screen
|
|
58
|
+
* @template T - Type of navigation parameters for the screen
|
|
59
|
+
*/
|
|
60
|
+
export interface StackScreen<T extends ParamListBase = ParamListBase>
|
|
61
|
+
extends BaseScreen<T> {
|
|
62
|
+
/** Additional navigation options for the stack screen */
|
|
63
|
+
options?:
|
|
64
|
+
| StackNavigationOptions
|
|
65
|
+
| ((props: { navigation: unknown; route: unknown }) => StackNavigationOptions);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface BaseNavigatorConfig<T extends ParamListBase = ParamListBase> {
|
|
69
|
+
screens: TabScreen[] | StackScreen[];
|
|
70
|
+
initialRouteName?: Extract<keyof T, string>;
|
|
71
|
+
getLabel?: (label: string) => string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface TabNavigatorConfig<T extends ParamListBase = ParamListBase>
|
|
75
|
+
extends BaseNavigatorConfig<T> {
|
|
76
|
+
screens: TabScreen[];
|
|
77
|
+
/** Custom icon renderer function */
|
|
78
|
+
renderIcon?: (
|
|
79
|
+
iconName: string,
|
|
80
|
+
focused: boolean,
|
|
81
|
+
routeName: string,
|
|
82
|
+
isFab: boolean
|
|
83
|
+
) => React.ReactElement;
|
|
84
|
+
/** Get icon name for a tab */
|
|
85
|
+
getTabIcon?: (routeName: string, focused: boolean) => IconName;
|
|
86
|
+
/** Screen options for all tabs */
|
|
87
|
+
screenOptions?:
|
|
88
|
+
| BottomTabNavigationOptions
|
|
89
|
+
| ((props: BottomTabScreenProps<T>) => BottomTabNavigationOptions);
|
|
90
|
+
/** FAB configuration for center button styling */
|
|
91
|
+
fabConfig?: FabConfig;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface StackNavigatorConfig<T extends ParamListBase = ParamListBase>
|
|
95
|
+
extends BaseNavigatorConfig<T> {
|
|
96
|
+
screens: StackScreen[];
|
|
97
|
+
screenOptions?: StackNavigationOptions;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface IconRendererProps {
|
|
101
|
+
iconName: IconName;
|
|
102
|
+
focused: boolean;
|
|
103
|
+
routeName: string;
|
|
104
|
+
isFab: boolean;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface LabelProcessorProps {
|
|
108
|
+
label: string;
|
|
109
|
+
getLabel?: (label: string) => string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Default FAB configuration values */
|
|
113
|
+
export const DEFAULT_FAB_CONFIG: Required<FabConfig> = {
|
|
114
|
+
size: 56,
|
|
115
|
+
offsetY: -20,
|
|
116
|
+
borderRadius: 28,
|
|
117
|
+
borderWidth: 3,
|
|
118
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { NavigationContainerRef, CommonActions, StackActions } from '@react-navigation/native';
|
|
2
|
+
|
|
3
|
+
export class AppNavigation {
|
|
4
|
+
private static navigationRef: NavigationContainerRef<any> | null = null;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Set the global navigation reference
|
|
8
|
+
*/
|
|
9
|
+
static setRef(ref: NavigationContainerRef<any> | null): void {
|
|
10
|
+
this.navigationRef = ref;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Set the global navigation reference (alias for setRef)
|
|
15
|
+
* @deprecated Use setRef instead
|
|
16
|
+
*/
|
|
17
|
+
static setNavigationRef(ref: NavigationContainerRef<any> | null): void {
|
|
18
|
+
this.setRef(ref);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the global navigation reference
|
|
23
|
+
*/
|
|
24
|
+
static getRef(): NavigationContainerRef<any> | null {
|
|
25
|
+
return this.navigationRef;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Navigate to a route
|
|
30
|
+
*/
|
|
31
|
+
static navigate(name: string, params?: object): void {
|
|
32
|
+
if (this.navigationRef?.isReady()) {
|
|
33
|
+
this.navigationRef.navigate(name, params);
|
|
34
|
+
} else if (__DEV__) {
|
|
35
|
+
console.warn('[AppNavigation] Navigation ref is not ready. Call setRef() first.');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Push a route onto the stack
|
|
41
|
+
*/
|
|
42
|
+
static push(name: string, params?: object): void {
|
|
43
|
+
if (this.navigationRef?.isReady()) {
|
|
44
|
+
this.navigationRef.dispatch(StackActions.push(name, params));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Go back to the previous screen
|
|
50
|
+
*/
|
|
51
|
+
static goBack(): void {
|
|
52
|
+
if (this.navigationRef?.isReady() && this.navigationRef.canGoBack()) {
|
|
53
|
+
this.navigationRef.goBack();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Reset the navigation state
|
|
59
|
+
*/
|
|
60
|
+
static reset(name: string, params?: object): void {
|
|
61
|
+
if (this.navigationRef?.isReady()) {
|
|
62
|
+
this.navigationRef.dispatch(
|
|
63
|
+
CommonActions.reset({
|
|
64
|
+
index: 0,
|
|
65
|
+
routes: [{ name, params }],
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Replace the current route
|
|
73
|
+
*/
|
|
74
|
+
static replace(name: string, params?: object): void {
|
|
75
|
+
if (this.navigationRef?.isReady()) {
|
|
76
|
+
this.navigationRef.dispatch(StackActions.replace(name, params));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Navigate to a screen in a nested navigator (e.g. Tab > Stack > Screen)
|
|
82
|
+
*/
|
|
83
|
+
static navigateToNested(parentParams: { screen: string; params?: any }): void {
|
|
84
|
+
// This is a simplified wrapper for navigating to nested stacks which usually looks like:
|
|
85
|
+
// navigate('ParentStack', { screen: 'ChildScreen', params: { ... } })
|
|
86
|
+
// But React Navigation handle this quite well with basic .navigate too if hierarchy is clear.
|
|
87
|
+
// Kept simple for now.
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Navigate to a screen in the parent navigator
|
|
92
|
+
*/
|
|
93
|
+
static navigateToParent(name: string, params?: object): void {
|
|
94
|
+
if (this.navigationRef?.isReady()) {
|
|
95
|
+
const parent = this.navigationRef.getParent();
|
|
96
|
+
if (parent) {
|
|
97
|
+
parent.navigate(name, params);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { IconRendererProps } from "../types";
|
|
3
|
+
import { AtomicIcon } from "../../../atoms/AtomicIcon";
|
|
4
|
+
import type { IconName } from "../../../atoms/AtomicIcon";
|
|
5
|
+
|
|
6
|
+
type RenderIconFn = (
|
|
7
|
+
iconName: IconName,
|
|
8
|
+
focused: boolean,
|
|
9
|
+
routeName: string,
|
|
10
|
+
isFab: boolean
|
|
11
|
+
) => React.ReactElement;
|
|
12
|
+
|
|
13
|
+
export class IconRenderer {
|
|
14
|
+
static renderIcon(
|
|
15
|
+
props: IconRendererProps,
|
|
16
|
+
renderIcon?: RenderIconFn
|
|
17
|
+
): React.ReactElement | null {
|
|
18
|
+
const { iconName, focused, routeName, isFab } = props;
|
|
19
|
+
|
|
20
|
+
if (renderIcon) {
|
|
21
|
+
try {
|
|
22
|
+
const result = renderIcon(iconName, focused, routeName, isFab);
|
|
23
|
+
if (React.isValidElement(result)) {
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
// Fallback to default
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!iconName) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return React.createElement(AtomicIcon, {
|
|
36
|
+
name: iconName,
|
|
37
|
+
size: isFab ? "lg" : "md",
|
|
38
|
+
color: focused ? "primary" : "onSurface",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static getIconName(
|
|
43
|
+
routeName: string,
|
|
44
|
+
focused: boolean,
|
|
45
|
+
icon: IconName,
|
|
46
|
+
getTabIcon?: (routeName: string, focused: boolean) => IconName
|
|
47
|
+
): IconName {
|
|
48
|
+
return getTabIcon ? getTabIcon(routeName, focused) : icon;
|
|
49
|
+
}
|
|
50
|
+
}
|