@umituz/react-native-design-system 2.5.32 → 2.5.34

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "2.5.32",
3
+ "version": "2.5.34",
4
4
  "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive and safe area utilities",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -3,21 +3,38 @@ import { View } from "react-native";
3
3
  import type { ParamListBase } from "@react-navigation/native";
4
4
  import { AtomicIcon } from "../../../atoms/AtomicIcon";
5
5
  import { useAppDesignTokens } from "../../../theme";
6
- import { useSafeAreaInsets } from "../../../safe-area";
6
+ import { useResponsive } from "../../../responsive";
7
7
  import type { TabNavigatorConfig, TabScreen } from "../types";
8
8
 
9
9
  export interface UseTabConfigProps<T extends ParamListBase> {
10
10
  config: TabNavigatorConfig<T>;
11
11
  }
12
12
 
13
+ /**
14
+ * Get the appropriate icon name based on focus state
15
+ * Ionicons convention: filled for active, outline for inactive
16
+ */
17
+ const getIconNameForState = (baseName: string, focused: boolean): string => {
18
+ if (!baseName) return "help-circle-outline";
19
+
20
+ const hasOutlineSuffix = baseName.endsWith("-outline");
21
+ const hasSharpSuffix = baseName.endsWith("-sharp");
22
+
23
+ if (hasOutlineSuffix || hasSharpSuffix) {
24
+ return baseName;
25
+ }
26
+
27
+ return focused ? baseName : `${baseName}-outline`;
28
+ };
29
+
13
30
  export function useTabConfig<T extends ParamListBase>(props: UseTabConfigProps<T>): TabNavigatorConfig<T> {
14
31
  const { config } = props;
15
32
  const tokens = useAppDesignTokens();
16
- const insets = useSafeAreaInsets();
33
+ const { tabBarConfig, insets } = useResponsive();
17
34
 
18
35
  const finalConfig: TabNavigatorConfig<T> = useMemo(() => {
19
36
  const screens = config.screens as TabScreen<T>[];
20
-
37
+
21
38
  return {
22
39
  ...config,
23
40
  renderIcon: (
@@ -34,7 +51,7 @@ export function useTabConfig<T extends ParamListBase>(props: UseTabConfigProps<T
34
51
  const fab = config.fabConfig;
35
52
 
36
53
  if (isFab) {
37
- const fabSize = fab?.size ?? 84;
54
+ const fabSize = fab?.size ?? tabBarConfig.fabSize;
38
55
  return React.createElement(View, {
39
56
  style: {
40
57
  width: fabSize,
@@ -43,7 +60,7 @@ export function useTabConfig<T extends ParamListBase>(props: UseTabConfigProps<T
43
60
  backgroundColor: tokens.colors.primary,
44
61
  justifyContent: "center",
45
62
  alignItems: "center",
46
- marginTop: fab?.offsetY ?? -32,
63
+ marginTop: fab?.offsetY ?? tabBarConfig.fabOffsetY,
47
64
  }
48
65
  }, React.createElement(AtomicIcon, {
49
66
  name: iconName,
@@ -53,25 +70,28 @@ export function useTabConfig<T extends ParamListBase>(props: UseTabConfigProps<T
53
70
  }));
54
71
  }
55
72
 
73
+ const resolvedIconName = getIconNameForState(iconName, focused);
74
+
56
75
  return React.createElement(AtomicIcon, {
57
- name: iconName,
76
+ name: resolvedIconName,
58
77
  customColor: focused ? tokens.colors.primary : tokens.colors.textSecondary,
59
- size: "lg"
78
+ customSize: tabBarConfig.iconSize,
60
79
  });
61
80
  },
62
81
  screenOptions: {
63
82
  tabBarActiveTintColor: tokens.colors.primary,
64
83
  tabBarInactiveTintColor: tokens.colors.textSecondary,
65
84
  tabBarShowLabel: false,
66
- tabBarIconStyle: {
67
- marginBottom: 0,
85
+ tabBarItemStyle: {
86
+ paddingVertical: tabBarConfig.paddingTop,
68
87
  },
69
88
  tabBarStyle: {
70
89
  backgroundColor: tokens.colors.surface,
71
- borderTopColor: tokens.colors.borderLight,
72
90
  borderTopWidth: 0,
73
- paddingBottom: insets.bottom || 0,
74
- height: tokens.spacing.tabBarHeight + (insets.bottom || 16),
91
+ paddingBottom: tabBarConfig.paddingBottom,
92
+ paddingTop: tabBarConfig.paddingTop,
93
+ height: tabBarConfig.height,
94
+ elevation: 0,
75
95
  },
76
96
  headerStyle: {
77
97
  backgroundColor: tokens.colors.surface,
@@ -89,7 +109,7 @@ export function useTabConfig<T extends ParamListBase>(props: UseTabConfigProps<T
89
109
  : {}),
90
110
  },
91
111
  };
92
- }, [tokens, config, insets]);
112
+ }, [tokens, config, tabBarConfig, insets]);
93
113
 
94
114
  return finalConfig;
95
115
  }
@@ -16,6 +16,9 @@ export {
16
16
  getResponsiveHorizontalPadding,
17
17
  getResponsiveBottomPosition,
18
18
  getResponsiveFABPosition,
19
+ getResponsiveTabBarHeight,
20
+ getResponsiveTabBarConfig,
21
+ type ResponsiveTabBarConfig,
19
22
  getResponsiveModalMaxHeight,
20
23
  getResponsiveMinModalHeight,
21
24
  getResponsiveModalWidth,
@@ -21,6 +21,9 @@ export {
21
21
  getResponsiveHorizontalPadding,
22
22
  getResponsiveBottomPosition,
23
23
  getResponsiveFABPosition,
24
+ getResponsiveTabBarHeight,
25
+ getResponsiveTabBarConfig,
26
+ type ResponsiveTabBarConfig,
24
27
  } from './responsiveLayout';
25
28
 
26
29
  // Responsive modal utilities
@@ -96,3 +96,98 @@ export const getResponsiveFABPosition = (
96
96
  return { bottom: 90, right: 20 };
97
97
  }
98
98
  };
99
+
100
+ /**
101
+ * Tab bar height constants
102
+ */
103
+ const TAB_BAR_CONSTANTS = {
104
+ BASE_HEIGHT_PHONE: 60,
105
+ BASE_HEIGHT_TABLET: 70,
106
+ MIN_PADDING_BOTTOM: 8,
107
+ MIN_PADDING_TOP: 8,
108
+ ICON_SIZE_PHONE: 24,
109
+ ICON_SIZE_TABLET: 28,
110
+ FAB_SIZE_PHONE: 64,
111
+ FAB_SIZE_TABLET: 72,
112
+ FAB_OFFSET_Y_PHONE: -24,
113
+ FAB_OFFSET_Y_TABLET: -28,
114
+ } as const;
115
+
116
+ /**
117
+ * Responsive tab bar configuration
118
+ */
119
+ export interface ResponsiveTabBarConfig {
120
+ height: number;
121
+ paddingBottom: number;
122
+ paddingTop: number;
123
+ iconSize: number;
124
+ fabSize: number;
125
+ fabOffsetY: number;
126
+ }
127
+
128
+ /**
129
+ * Get responsive tab bar height based on device and safe area
130
+ */
131
+ export const getResponsiveTabBarHeight = (
132
+ insets: { bottom?: number } = { bottom: 0 }
133
+ ): number => {
134
+ try {
135
+ validateSafeAreaInsets(insets);
136
+ const { width } = getScreenDimensions();
137
+ const { bottom = 0 } = insets;
138
+
139
+ const baseHeight = width >= DEVICE_BREAKPOINTS.SMALL_TABLET
140
+ ? TAB_BAR_CONSTANTS.BASE_HEIGHT_TABLET
141
+ : TAB_BAR_CONSTANTS.BASE_HEIGHT_PHONE;
142
+
143
+ const bottomPadding = Math.max(bottom, TAB_BAR_CONSTANTS.MIN_PADDING_BOTTOM);
144
+
145
+ return baseHeight + bottomPadding;
146
+ } catch {
147
+ return TAB_BAR_CONSTANTS.BASE_HEIGHT_PHONE + TAB_BAR_CONSTANTS.MIN_PADDING_BOTTOM;
148
+ }
149
+ };
150
+
151
+ /**
152
+ * Get complete responsive tab bar configuration
153
+ */
154
+ export const getResponsiveTabBarConfig = (
155
+ insets: { bottom?: number } = { bottom: 0 }
156
+ ): ResponsiveTabBarConfig => {
157
+ try {
158
+ validateSafeAreaInsets(insets);
159
+ const { width } = getScreenDimensions();
160
+ const { bottom = 0 } = insets;
161
+ const isTabletSize = width >= DEVICE_BREAKPOINTS.SMALL_TABLET;
162
+
163
+ const baseHeight = isTabletSize
164
+ ? TAB_BAR_CONSTANTS.BASE_HEIGHT_TABLET
165
+ : TAB_BAR_CONSTANTS.BASE_HEIGHT_PHONE;
166
+
167
+ const paddingBottom = Math.max(bottom, TAB_BAR_CONSTANTS.MIN_PADDING_BOTTOM);
168
+
169
+ return {
170
+ height: baseHeight + paddingBottom,
171
+ paddingBottom,
172
+ paddingTop: TAB_BAR_CONSTANTS.MIN_PADDING_TOP,
173
+ iconSize: isTabletSize
174
+ ? TAB_BAR_CONSTANTS.ICON_SIZE_TABLET
175
+ : TAB_BAR_CONSTANTS.ICON_SIZE_PHONE,
176
+ fabSize: isTabletSize
177
+ ? TAB_BAR_CONSTANTS.FAB_SIZE_TABLET
178
+ : TAB_BAR_CONSTANTS.FAB_SIZE_PHONE,
179
+ fabOffsetY: isTabletSize
180
+ ? TAB_BAR_CONSTANTS.FAB_OFFSET_Y_TABLET
181
+ : TAB_BAR_CONSTANTS.FAB_OFFSET_Y_PHONE,
182
+ };
183
+ } catch {
184
+ return {
185
+ height: TAB_BAR_CONSTANTS.BASE_HEIGHT_PHONE + TAB_BAR_CONSTANTS.MIN_PADDING_BOTTOM,
186
+ paddingBottom: TAB_BAR_CONSTANTS.MIN_PADDING_BOTTOM,
187
+ paddingTop: TAB_BAR_CONSTANTS.MIN_PADDING_TOP,
188
+ iconSize: TAB_BAR_CONSTANTS.ICON_SIZE_PHONE,
189
+ fabSize: TAB_BAR_CONSTANTS.FAB_SIZE_PHONE,
190
+ fabOffsetY: TAB_BAR_CONSTANTS.FAB_OFFSET_Y_PHONE,
191
+ };
192
+ }
193
+ };
@@ -19,6 +19,7 @@ import {
19
19
  getResponsiveHorizontalPadding,
20
20
  getResponsiveBottomPosition,
21
21
  getResponsiveFABPosition,
22
+ getResponsiveTabBarConfig,
22
23
  getResponsiveModalMaxHeight,
23
24
  getResponsiveMinModalHeight,
24
25
  getResponsiveModalLayout,
@@ -31,6 +32,7 @@ import {
31
32
  type ResponsiveModalLayout,
32
33
  type ResponsiveBottomSheetLayout,
33
34
  type ResponsiveDialogLayout,
35
+ type ResponsiveTabBarConfig,
34
36
  } from './responsive';
35
37
  import {
36
38
  isSmallPhone,
@@ -82,6 +84,9 @@ export interface UseResponsiveReturn {
82
84
  bottomSheetLayout: ResponsiveBottomSheetLayout;
83
85
  dialogLayout: ResponsiveDialogLayout;
84
86
 
87
+ // Tab bar configuration
88
+ tabBarConfig: ResponsiveTabBarConfig;
89
+
85
90
  // Utility functions
86
91
  getLogoSize: (baseSize?: number) => number;
87
92
  getInputHeight: (baseHeight?: number) => number;
@@ -143,6 +148,9 @@ export const useResponsive = (): UseResponsiveReturn => {
143
148
  bottomSheetLayout: getResponsiveBottomSheetLayout(),
144
149
  dialogLayout: getResponsiveDialogLayout(),
145
150
 
151
+ // Tab bar configuration
152
+ tabBarConfig: getResponsiveTabBarConfig(insets),
153
+
146
154
  // Utility functions (memoized)
147
155
  getLogoSize,
148
156
  getInputHeight,