@idealyst/navigation 1.0.49 → 1.0.51

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": "@idealyst/navigation",
3
- "version": "1.0.49",
3
+ "version": "1.0.51",
4
4
  "description": "Cross-platform navigation library for React and React Native",
5
5
  "main": "src/index.ts",
6
6
  "module": "src/index.ts",
@@ -37,8 +37,8 @@
37
37
  "publish:npm": "npm publish"
38
38
  },
39
39
  "peerDependencies": {
40
- "@idealyst/components": "^1.0.49",
41
- "@idealyst/theme": "^1.0.49",
40
+ "@idealyst/components": "^1.0.51",
41
+ "@idealyst/theme": "^1.0.51",
42
42
  "@react-navigation/bottom-tabs": "^7.0.0",
43
43
  "@react-navigation/drawer": "^7.0.0",
44
44
  "@react-navigation/native": "^7.0.0",
@@ -1,13 +1,13 @@
1
1
  import React from 'react';
2
- import { AvatarExamples, BadgeExamples, ButtonExamples, CardExamples, CheckboxExamples, DialogExamples, DividerExamples, IconExamples, InputExamples, PopoverExamples, TextExamples, ViewExamples, ThemeExtensionExamples } from "../../../components/src/examples";
3
- import { DataGridShowcase } from "../../../datagrid/src/examples";
4
- import { DatePickerExamples } from "../../../datepicker/src/examples";
5
- import { Screen, Text, View, Button } from "../../../components/src";
2
+ import { ButtonExamples, CardExamples, IconExamples, ThemeExtensionExamples } from "../../../components/src/examples";
3
+ import { Screen, Text, View, Button, Icon } from "../../../components/src";
6
4
  import { UnistylesRuntime } from 'react-native-unistyles';
7
5
  import { RouteParam } from '../routing';
6
+ import { useNavigator } from '../context';
8
7
  import { getNextTheme, getThemeDisplayName, isHighContrastTheme } from './unistyles';
9
8
 
10
9
  const HomeTabScreen = () => {
10
+ const navigator = useNavigator();
11
11
  const currentTheme = UnistylesRuntime.themeName || 'light';
12
12
 
13
13
  const cycleTheme = () => {
@@ -21,10 +21,8 @@ const HomeTabScreen = () => {
21
21
  let newTheme: string;
22
22
 
23
23
  if (isHighContrastTheme(currentTheme)) {
24
- // Switch to standard variant
25
24
  newTheme = currentTheme.includes('dark') ? 'dark' : 'light';
26
25
  } else {
27
- // Switch to high contrast variant
28
26
  newTheme = currentTheme.includes('dark') ? 'darkHighContrast' : 'lightHighContrast';
29
27
  }
30
28
 
@@ -34,32 +32,58 @@ const HomeTabScreen = () => {
34
32
 
35
33
  return (
36
34
  <Screen>
37
- <View>
35
+ <View spacing="lg">
38
36
  <Text size="large" weight="bold">
39
- Welcome to the Component Library
37
+ TabBar Navigation Demo
40
38
  </Text>
41
39
  <Text size="medium">
42
- Use the tabs above to explore different components
40
+ This demonstrates native tab navigation with screen options
43
41
  </Text>
44
42
 
45
- <View style={{ marginTop: 24, gap: 12 }}>
46
- <Text size="medium" weight="semibold">
47
- Theme Controls
48
- </Text>
43
+ <View spacing="md" style={{ marginTop: 24 }}>
44
+ <Text size="medium" weight="semibold">Navigation Tabs</Text>
49
45
  <Text size="small">
50
- Current Theme: {getThemeDisplayName(currentTheme)}
46
+ The tabs below use native React Navigation with screen options for icons and labels.
47
+ On mobile, tabs appear at the bottom. On web, they may adapt based on the platform.
51
48
  </Text>
52
49
 
53
- <Button variant="outlined" onPress={cycleTheme}>
54
- Cycle Theme (Light Dark Light HC Dark HC)
55
- </Button>
50
+ <View spacing="sm">
51
+ <Button size="small" variant="outlined" onPress={() => navigator.navigate({ path: '/components', vars: {} })}>
52
+ <View style={{ flexDirection: 'row', alignItems: 'center' }}>
53
+ <Icon name="view-dashboard" size="sm" style={{ marginRight: 8 }} />
54
+ <Text>Components Tab</Text>
55
+ </View>
56
+ </Button>
57
+ <Button size="small" variant="outlined" onPress={() => navigator.navigate({ path: '/settings', vars: {} })}>
58
+ <View style={{ flexDirection: 'row', alignItems: 'center' }}>
59
+ <Icon name="cog" size="sm" style={{ marginRight: 8 }} />
60
+ <Text>Settings Tab</Text>
61
+ </View>
62
+ </Button>
63
+ <Button size="small" variant="outlined" onPress={() => navigator.navigate({ path: '/theme', vars: {} })}>
64
+ <View style={{ flexDirection: 'row', alignItems: 'center' }}>
65
+ <Icon name="palette" size="sm" style={{ marginRight: 8 }} />
66
+ <Text>Theme Tab</Text>
67
+ </View>
68
+ </Button>
69
+ </View>
70
+ </View>
71
+
72
+ <View spacing="md" style={{ marginTop: 24 }}>
73
+ <Text size="medium" weight="semibold">Theme Controls</Text>
74
+ <Text size="small">Current Theme: {getThemeDisplayName(currentTheme)}</Text>
56
75
 
57
- <Button variant="outlined" onPress={toggleHighContrast}>
58
- Toggle High Contrast
59
- </Button>
76
+ <View style={{ flexDirection: 'row', marginTop: 12 }}>
77
+ <Button variant="outlined" onPress={cycleTheme} style={{ marginRight: 12 }}>
78
+ Cycle Theme
79
+ </Button>
80
+ <Button variant="outlined" onPress={toggleHighContrast}>
81
+ Toggle High Contrast
82
+ </Button>
83
+ </View>
60
84
 
61
85
  {isHighContrastTheme(currentTheme) && (
62
- <Text size="small" style={{ fontStyle: 'italic' }}>
86
+ <Text size="small" style={{ fontStyle: 'italic', marginTop: 8 }}>
63
87
  ♿ High contrast mode is active for better accessibility
64
88
  </Text>
65
89
  )}
@@ -69,93 +93,67 @@ const HomeTabScreen = () => {
69
93
  );
70
94
  };
71
95
 
72
- const AvatarTabScreen = () => (
73
- <Screen>
74
- <AvatarExamples />
75
- </Screen>
76
- );
77
-
78
- const BadgeTabScreen = () => (
79
- <Screen>
80
- <BadgeExamples />
81
- </Screen>
82
- );
83
-
84
- const ButtonTabScreen = () => (
85
- <Screen>
86
- <ButtonExamples />
87
- </Screen>
88
- );
89
-
90
- const CardTabScreen = () => (
91
- <Screen>
92
- <CardExamples />
93
- </Screen>
94
- );
95
-
96
- const CheckboxTabScreen = () => (
96
+ const ComponentsTabScreen = () => (
97
97
  <Screen>
98
- <CheckboxExamples />
99
- </Screen>
100
- );
101
-
102
- const DividerTabScreen = () => (
103
- <Screen>
104
- <DividerExamples />
105
- </Screen>
106
- );
107
-
108
- const InputTabScreen = () => (
109
- <Screen>
110
- <InputExamples />
111
- </Screen>
112
- );
113
-
114
- const TextTabScreen = () => (
115
- <Screen>
116
- <TextExamples />
117
- </Screen>
118
- );
119
-
120
- const ViewTabScreen = () => (
121
- <Screen>
122
- <ViewExamples />
123
- </Screen>
124
- );
125
-
126
- const IconTabScreen = () => (
127
- <Screen>
128
- <IconExamples />
129
- </Screen>
130
- );
131
-
132
- const DialogTabScreen = () => (
133
- <Screen>
134
- <DialogExamples />
135
- </Screen>
136
- );
137
-
138
- const PopoverTabScreen = () => (
139
- <Screen>
140
- <PopoverExamples />
141
- </Screen>
142
- );
143
-
144
- const DataGridTabScreen = () => (
145
- <Screen>
146
- <DataGridShowcase />
98
+ <View spacing="lg">
99
+ <Text size="large" weight="bold">Components</Text>
100
+ <Text>Explore UI components with native tab navigation</Text>
101
+
102
+ <View spacing="md">
103
+ <Text size="medium" weight="semibold">Button Examples</Text>
104
+ <ButtonExamples />
105
+ </View>
106
+
107
+ <View spacing="md">
108
+ <Text size="medium" weight="semibold">Card Examples</Text>
109
+ <CardExamples />
110
+ </View>
111
+
112
+ <View spacing="md">
113
+ <Text size="medium" weight="semibold">Icon Examples</Text>
114
+ <IconExamples />
115
+ </View>
116
+ </View>
147
117
  </Screen>
148
118
  );
149
119
 
150
- const DatePickerTabScreen = () => (
151
- <Screen>
152
- <DatePickerExamples />
153
- </Screen>
154
- );
120
+ const SettingsTabScreen = () => {
121
+ const currentTheme = UnistylesRuntime.themeName || 'light';
122
+
123
+ return (
124
+ <Screen>
125
+ <View spacing="lg">
126
+ <Text size="large" weight="bold">Settings</Text>
127
+ <Text>Configure the TabBar demo settings</Text>
128
+
129
+ <View spacing="md">
130
+ <Text size="medium" weight="semibold">Navigation Info</Text>
131
+ <Text size="small">
132
+ This tab uses native navigation with screen options:
133
+ </Text>
134
+ <Text size="small">• tabBarIcon: "cog" (Material Design Icons)</Text>
135
+ <Text size="small">• tabBarLabel: "Settings"</Text>
136
+ <Text size="small">• title: "Settings"</Text>
137
+ </View>
138
+
139
+ <View spacing="md">
140
+ <Text size="medium" weight="semibold">Current State</Text>
141
+ <Text size="small">Theme: {getThemeDisplayName(currentTheme)}</Text>
142
+ <Text size="small">Platform: React Navigation (Native) / React Router (Web)</Text>
143
+ </View>
144
+ </View>
145
+ </Screen>
146
+ );
147
+ };
155
148
 
156
- const ThemeExtensionTabScreen = () => (
149
+ const ThemeTabScreen = () => (
157
150
  <Screen>
158
- <ThemeExtensionExamples />
151
+ <View spacing="lg">
152
+ <Text size="large" weight="bold">Theme System</Text>
153
+ <Text>Explore the theme extension system</Text>
154
+
155
+ <ThemeExtensionExamples />
156
+ </View>
159
157
  </Screen>
160
158
  );
161
159
 
@@ -165,23 +163,56 @@ const TabRouter: RouteParam = {
165
163
  layout: {
166
164
  type: "tab",
167
165
  },
166
+ screenOptions: {
167
+ title: 'Home',
168
+ tabBarLabel: 'Home',
169
+ tabBarIcon: ({ focused, size }) => {
170
+ return <Icon name="home" color={focused ? 'blue' : 'black'} size={size} />
171
+ },
172
+ },
168
173
  routes: [
169
- { path: "avatar", component: AvatarTabScreen },
170
- { path: "badge", component: BadgeTabScreen },
171
- { path: "button", component: ButtonTabScreen },
172
- { path: "card", component: CardTabScreen },
173
- { path: "checkbox", component: CheckboxTabScreen },
174
- { path: "divider", component: DividerTabScreen },
175
- { path: "input", component: InputTabScreen },
176
- { path: "text", component: TextTabScreen },
177
- { path: "view", component: ViewTabScreen },
178
- { path: "icon", component: IconTabScreen },
179
- { path: "dialog", component: DialogTabScreen },
180
- { path: "popover", component: PopoverTabScreen },
181
- { path: "datagrid", component: DataGridTabScreen },
182
- { path: "datepicker", component: DatePickerTabScreen },
183
- { path: "theme-extension", component: ThemeExtensionTabScreen },
174
+ {
175
+ path: "components",
176
+ component: ComponentsTabScreen,
177
+ screenOptions: {
178
+ title: 'Components',
179
+ tabBarLabel: 'Components',
180
+ tabBarIcon: (props) => {
181
+ if (props.focused) {
182
+ return <Icon name="view-dashboard" color={props.focused ? 'blue' : 'black'} size={props.size} />;
183
+ }
184
+ return <Icon name="view-dashboard-outline" color={props.color} size={props.size} />;
185
+ },
186
+ },
187
+ },
188
+ {
189
+ path: "settings",
190
+ component: SettingsTabScreen,
191
+ screenOptions: {
192
+ title: 'Settings',
193
+ tabBarLabel: 'Settings',
194
+ tabBarIcon: ({ focused, size }) => (
195
+ <Icon
196
+ name="cog"
197
+ color={focused ? 'blue' : 'black'}
198
+ size={size}
199
+ style={{ opacity: focused ? 1 : 0.8 }}
200
+ />
201
+ ),
202
+ },
203
+ },
204
+ {
205
+ path: "theme",
206
+ component: ThemeTabScreen,
207
+ screenOptions: {
208
+ title: 'Theme',
209
+ tabBarLabel: 'Theme',
210
+ tabBarIcon: ({ focused, size }) => (
211
+ <Icon name="palette" color={focused ? 'blue' : 'black'} size={size} />
212
+ ),
213
+ },
214
+ },
184
215
  ],
185
- }
216
+ };
186
217
 
187
218
  export default TabRouter;
@@ -75,7 +75,6 @@ const GeneralLayout: React.FC<GeneralLayoutProps> = ({
75
75
  {/* Header */}
76
76
  {headerConfig.enabled && (
77
77
  <View
78
- background="surface"
79
78
  border="thin"
80
79
  spacing="md"
81
80
  style={[
@@ -0,0 +1,283 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { View, Text, Button, Icon, Pressable } from '@idealyst/components';
3
+ import { TabBarLayoutProps, TabBarConfig, TabButtonProps } from './types';
4
+ import { tabBarLayoutStyles } from './TabBarLayout.styles';
5
+ import { Dimensions } from 'react-native';
6
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
7
+
8
+ const DEFAULT_AUTO_BREAKPOINT = 768;
9
+
10
+ const NativeHeader: React.FC<{
11
+ config: any;
12
+ styles: any;
13
+ showTabsInHeader: boolean;
14
+ tabBarConfig: TabBarConfig;
15
+ insets: any;
16
+ }> = ({ config, styles, showTabsInHeader, tabBarConfig, insets }) => {
17
+ const handleBackPress = () => {
18
+ if (config.onBackPress) {
19
+ config.onBackPress();
20
+ }
21
+ // Could integrate with React Navigation here
22
+ // navigation.goBack();
23
+ };
24
+
25
+ const headerDynamicStyles = {
26
+ height: config.height,
27
+ minHeight: config.height,
28
+ paddingTop: config.enabled ? insets.top : 0,
29
+ };
30
+
31
+ return (
32
+ <View
33
+ style={[
34
+ styles.headerContainer,
35
+ headerDynamicStyles,
36
+ config.style,
37
+ ]}
38
+ >
39
+ <View style={styles.headerContent}>
40
+ {/* Custom content overrides native elements */}
41
+ {config.content ? (
42
+ config.content
43
+ ) : (
44
+ <View style={{ flexDirection: 'row', alignItems: 'center', flex: 1 }}>
45
+ {/* Back Button */}
46
+ {config.showBackButton && (
47
+ <Button
48
+ variant="text"
49
+ onPress={handleBackPress}
50
+ style={{ marginRight: 8, minWidth: 'auto' }}
51
+ >
52
+ <Icon name="arrow-left" size="lg" color="primary" />
53
+ </Button>
54
+ )}
55
+
56
+ {/* Title */}
57
+ {config.title && (
58
+ <Text
59
+ size="large"
60
+ weight="semibold"
61
+ style={{ flex: 1 }}
62
+ >
63
+ {config.title}
64
+ </Text>
65
+ )}
66
+ </View>
67
+ )}
68
+ </View>
69
+
70
+ <View style={styles.headerRightContent}>
71
+ {/* Tabs in header for wide screens */}
72
+ {showTabsInHeader && tabBarConfig.items.length > 0 && (
73
+ <TabBar config={tabBarConfig} position="header" />
74
+ )}
75
+
76
+ {/* Additional right content */}
77
+ {config.rightContent}
78
+ </View>
79
+ </View>
80
+ );
81
+ };
82
+
83
+ const TabButton: React.FC<TabButtonProps> = ({ item, isActive, onPress, position }) => {
84
+ const styles = tabBarLayoutStyles;
85
+ const isHeader = position === 'header';
86
+
87
+ return (
88
+ <Pressable
89
+ onPress={onPress}
90
+ disabled={item.disabled}
91
+ style={[
92
+ isHeader ? styles.tabButtonHeader : styles.tabButton,
93
+ isActive && styles.tabButtonActive,
94
+ item.disabled && styles.tabButtonDisabled,
95
+ ]}
96
+ >
97
+ <View style={styles.tabIconContainer}>
98
+ {item.icon && (
99
+ typeof item.icon === 'string' ? (
100
+ <Icon name={item.icon as any} size={isHeader ? "lg" : "md"} color={isActive ? 'primary' : 'secondary'} />
101
+ ) : (
102
+ item.icon
103
+ )
104
+ )}
105
+ {item.badge !== undefined && (
106
+ <View style={styles.tabBadge}>
107
+ <Text style={styles.tabBadgeText}>
108
+ {typeof item.badge === 'number' && item.badge > 99 ? '99+' : item.badge}
109
+ </Text>
110
+ </View>
111
+ )}
112
+ </View>
113
+ {(!isHeader || (isHeader && item.label)) && (
114
+ <Text
115
+ size={isHeader ? "medium" : "small"}
116
+ color={isActive ? 'primary' : 'secondary'}
117
+ style={isHeader ? { marginLeft: 8 } : { marginTop: 2, textAlign: 'center', fontSize: 10 }}
118
+ >
119
+ {item.label}
120
+ </Text>
121
+ )}
122
+ </Pressable>
123
+ );
124
+ };
125
+
126
+ const TabBar: React.FC<{
127
+ config: TabBarConfig;
128
+ position: 'bottom' | 'header';
129
+ }> = ({ config, position }) => {
130
+ const styles = tabBarLayoutStyles;
131
+
132
+ if (config.renderTabBar) {
133
+ return (
134
+ <>
135
+ {config.renderTabBar({
136
+ items: config.items,
137
+ activeTab: config.activeTab,
138
+ onTabSelect: config.onTabSelect,
139
+ position,
140
+ })}
141
+ </>
142
+ );
143
+ }
144
+
145
+ const containerStyle = position === 'header'
146
+ ? styles.headerTabs
147
+ : [styles.tabBarBottom, config.style];
148
+
149
+ return (
150
+ <View style={containerStyle}>
151
+ {config.items.map((item) => {
152
+ if (item.renderTab) {
153
+ return (
154
+ <React.Fragment key={item.id}>
155
+ {item.renderTab({
156
+ item,
157
+ isActive: item.id === config.activeTab,
158
+ onPress: () => config.onTabSelect?.(item.id),
159
+ position,
160
+ })}
161
+ </React.Fragment>
162
+ );
163
+ }
164
+
165
+ return (
166
+ <TabButton
167
+ key={item.id}
168
+ item={item}
169
+ isActive={item.id === config.activeTab}
170
+ onPress={() => config.onTabSelect?.(item.id)}
171
+ position={position}
172
+ />
173
+ );
174
+ })}
175
+ </View>
176
+ );
177
+ };
178
+
179
+ const TabBarLayout: React.FC<TabBarLayoutProps> = ({
180
+ children,
181
+ tabBar = {},
182
+ header = {},
183
+ style,
184
+ testID,
185
+ }) => {
186
+ const styles = tabBarLayoutStyles;
187
+ const insets = useSafeAreaInsets();
188
+ const [screenWidth, setScreenWidth] = useState(() => Dimensions.get('window').width);
189
+
190
+ // Default tab bar configuration
191
+ const tabBarConfig = {
192
+ items: [],
193
+ activeTab: undefined,
194
+ onTabSelect: undefined,
195
+ position: 'auto' as const,
196
+ autoBreakpoint: DEFAULT_AUTO_BREAKPOINT,
197
+ style: undefined,
198
+ tabStyle: undefined,
199
+ showLabels: true,
200
+ renderTabBar: undefined,
201
+ ...tabBar,
202
+ };
203
+
204
+ // Default header configuration
205
+ const headerConfig = {
206
+ enabled: true,
207
+ height: 64,
208
+ title: undefined,
209
+ showBackButton: false,
210
+ onBackPress: undefined,
211
+ content: null,
212
+ rightContent: null,
213
+ style: undefined,
214
+ showTabs: true,
215
+ native: true,
216
+ ...header,
217
+ };
218
+
219
+ // Update screen width on orientation change
220
+ useEffect(() => {
221
+ const updateDimensions = () => {
222
+ setScreenWidth(Dimensions.get('window').width);
223
+ };
224
+
225
+ const subscription = Dimensions.addEventListener('change', updateDimensions);
226
+ return () => subscription?.remove();
227
+ }, []);
228
+
229
+ // Determine actual tab position - native always uses bottom for mobile
230
+ const actualTabPosition = (() => {
231
+ if (tabBarConfig.position === 'auto') {
232
+ return 'bottom'; // Native mobile apps typically use bottom tabs
233
+ }
234
+ return tabBarConfig.position;
235
+ })();
236
+
237
+ const showTabsInHeader = actualTabPosition === 'header' && headerConfig.showTabs;
238
+ const showTabsAtBottom = actualTabPosition === 'bottom';
239
+
240
+ // Create dynamic styles for bottom tab bar
241
+ const tabBarBottomStyles = {
242
+ paddingBottom: showTabsAtBottom ? insets.bottom : 0,
243
+ };
244
+
245
+ return (
246
+ <View
247
+ style={[
248
+ styles.container,
249
+ style,
250
+ ]}
251
+ testID={testID}
252
+ >
253
+ {/* Header */}
254
+ {headerConfig.enabled && (
255
+ <NativeHeader
256
+ config={headerConfig}
257
+ styles={styles}
258
+ showTabsInHeader={showTabsInHeader}
259
+ tabBarConfig={tabBarConfig}
260
+ insets={insets}
261
+ />
262
+ )}
263
+
264
+ {/* Main Content Area */}
265
+ <View style={styles.bodyContainer}>
266
+ <View style={styles.mainContent}>
267
+ <View style={styles.contentArea}>
268
+ {children}
269
+ </View>
270
+ </View>
271
+ </View>
272
+
273
+ {/* Bottom Tab Bar */}
274
+ {showTabsAtBottom && tabBarConfig.items.length > 0 && (
275
+ <View style={tabBarBottomStyles}>
276
+ <TabBar config={tabBarConfig} position="bottom" />
277
+ </View>
278
+ )}
279
+ </View>
280
+ );
281
+ };
282
+
283
+ export default TabBarLayout;