@momo-kits/tab-view 0.92.3-tracking.1 → 0.92.6-rc.3

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/index.tsx CHANGED
@@ -1,11 +1,12 @@
1
- import React, {FC, useCallback, useRef, useState} from 'react';
2
- import {LayoutChangeEvent, View} from 'react-native';
1
+ import React, {FC, useCallback, useContext, useRef, useState} from 'react';
2
+ import {Animated, LayoutChangeEvent, View} from 'react-native';
3
3
  import {Tab, TabViewProps} from './types';
4
4
  import TabBar from './tabBar/TabBar';
5
5
  import styles from './styles';
6
6
  import CardTabBar from './tabBar/CardTabBar';
7
7
  import ScrollableTabBar from './tabBar/SrollableTabBar';
8
8
  import PagerView from 'react-native-pager-view';
9
+ import {ApplicationContext} from '@momo-kits/foundation';
9
10
 
10
11
  const TabView: FC<TabViewProps> = ({
11
12
  scrollable = false,
@@ -14,6 +15,9 @@ const TabView: FC<TabViewProps> = ({
14
15
  type = 'default',
15
16
  initialPage = 0,
16
17
  pagerProps,
18
+ direction,
19
+ selectedColor,
20
+ unselectedColor,
17
21
  }) => {
18
22
  const startPage =
19
23
  initialPage > tabs.length - 1 ? tabs.length - 1 : initialPage;
@@ -21,7 +25,9 @@ const TabView: FC<TabViewProps> = ({
21
25
  const [tabBarWidth, setTabBarWidth] = useState(0);
22
26
  const isCardTab = type === 'card';
23
27
  const pagerRef = useRef<PagerView>(null);
24
- const [scrollX, setScrollX] = useState(0);
28
+ const scrollX = useRef(new Animated.Value(0));
29
+ const lazy = useRef([selectedIndex]).current;
30
+ const {theme} = useContext(ApplicationContext);
25
31
  const _onPressTabItem = (index: number) => {
26
32
  pagerRef.current?.setPage(index);
27
33
  onPressTabItem?.(index);
@@ -32,10 +38,12 @@ const TabView: FC<TabViewProps> = ({
32
38
  setTabBarWidth(e.nativeEvent.layout.width);
33
39
  };
34
40
 
35
- const onPageSelected = (e: {
36
- nativeEvent: {position: React.SetStateAction<number>};
37
- }) => {
38
- setSelectedIndex(e.nativeEvent.position);
41
+ const onPageSelected = (e: {nativeEvent: {position: number}}) => {
42
+ const {position} = e.nativeEvent;
43
+ if (!lazy.includes(position)) {
44
+ lazy.push(position);
45
+ }
46
+ setSelectedIndex(position);
39
47
  };
40
48
 
41
49
  let TabBarComponent = scrollable ? ScrollableTabBar : TabBar;
@@ -46,11 +54,12 @@ const TabView: FC<TabViewProps> = ({
46
54
 
47
55
  const onPageScroll = (e: {nativeEvent: {position: any; offset: any}}) => {
48
56
  const {position, offset} = e.nativeEvent;
49
- setScrollX(position + offset);
57
+ scrollX.current.setValue(position + offset);
50
58
  };
51
59
 
52
60
  const renderScreen = useCallback(
53
- (tab: Tab) => {
61
+ (tab: Tab, index: number) => {
62
+ if (!lazy.includes(index)) return <View />;
54
63
  return <View>{tab.component}</View>;
55
64
  },
56
65
  [tabs],
@@ -64,7 +73,10 @@ const TabView: FC<TabViewProps> = ({
64
73
  selectedIndex={selectedIndex}
65
74
  tabs={tabs}
66
75
  containerWidth={tabBarWidth}
67
- scrollX={scrollX}
76
+ scrollX={scrollX.current}
77
+ direction={direction}
78
+ selectedColor={selectedColor ?? theme.colors.primary}
79
+ unselectedColor={unselectedColor ?? theme.colors.text.default}
68
80
  />
69
81
  </View>
70
82
 
@@ -75,7 +87,7 @@ const TabView: FC<TabViewProps> = ({
75
87
  onPageSelected={onPageSelected}
76
88
  style={styles.pagerView}
77
89
  initialPage={startPage}>
78
- {tabs.map(tab => renderScreen(tab))}
90
+ {tabs.map((tab, index) => renderScreen(tab, index))}
79
91
  </PagerView>
80
92
  </View>
81
93
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/tab-view",
3
- "version": "0.92.3-tracking.1",
3
+ "version": "0.92.6-rc.3",
4
4
  "private": false,
5
5
  "main": "index.tsx",
6
6
  "dependencies": {},
package/styles.ts CHANGED
@@ -14,7 +14,6 @@ export default StyleSheet.create({
14
14
  borderBottomWidth: 1,
15
15
  },
16
16
  tabItem: {
17
- padding: Spacing.M,
18
17
  alignItems: 'center',
19
18
  justifyContent: 'center',
20
19
  },
@@ -26,9 +25,15 @@ export default StyleSheet.create({
26
25
  bottom: 0,
27
26
  },
28
27
  icon: {
28
+ borderRadius: Radius.XS,
29
+ },
30
+ iconHolder: {
31
+ marginRight: Spacing.XS,
29
32
  width: 24,
30
33
  height: 24,
31
- borderRadius: Radius.XS,
34
+ justifyContent: 'center',
35
+ alignItems: 'center',
36
+ overflow: 'hidden',
32
37
  },
33
38
  tabItemWrapper: {
34
39
  paddingHorizontal: Spacing.XS,
@@ -65,14 +70,20 @@ export default StyleSheet.create({
65
70
  },
66
71
  dotSmall: {
67
72
  position: 'absolute',
68
- top: -Spacing.XXS,
69
- right: -Spacing.XXS,
73
+ top: -Spacing.XS,
74
+ right: -Spacing.XS,
70
75
  zIndex: 2,
71
76
  },
72
77
  dot: {
73
78
  position: 'absolute',
74
79
  top: -Spacing.S,
75
- right: -Spacing.XS,
80
+ right: -Spacing.S,
81
+ zIndex: 2,
82
+ },
83
+ badge: {
84
+ position: 'absolute',
85
+ top: -Spacing.S,
86
+ left: Spacing.S,
76
87
  zIndex: 2,
77
88
  },
78
89
  });
@@ -4,6 +4,8 @@ import {TabBarProps} from '../types';
4
4
  import styles from '../styles';
5
5
  import {
6
6
  ApplicationContext,
7
+ Badge,
8
+ BadgeDot,
7
9
  Icon,
8
10
  Radius,
9
11
  Shadow,
@@ -18,6 +20,8 @@ const CardTabBar: FC<TabBarProps> = ({
18
20
  onPressTabItem,
19
21
  containerWidth,
20
22
  selectedIndex,
23
+ selectedColor,
24
+ unselectedColor,
21
25
  }) => {
22
26
  const {theme} = useContext(ApplicationContext);
23
27
  const itemWidth = containerWidth / tabs.length;
@@ -36,6 +40,8 @@ const CardTabBar: FC<TabBarProps> = ({
36
40
  onPressTabItem?.(index);
37
41
  }}
38
42
  width={itemWidth}
43
+ selectedColor={selectedColor}
44
+ unselectedColor={unselectedColor}
39
45
  />
40
46
  );
41
47
  })}
@@ -61,19 +67,46 @@ const CardTabBar: FC<TabBarProps> = ({
61
67
  borderTopLeftRadius: Radius.M,
62
68
  },
63
69
  ]}>
64
- {!!tabs[selectedIndex]?.icon && (
70
+ {!!tabs[selectedIndex]?.renderIcon &&
71
+ !['string', 'boolean', 'number'].includes(
72
+ typeof tabs[selectedIndex]?.renderIcon,
73
+ ) && (
74
+ <View
75
+ style={[
76
+ styles.icon,
77
+ styles.iconHolder,
78
+ {marginRight: Spacing.XS},
79
+ ]}>
80
+ {tabs[selectedIndex]?.renderIcon}
81
+ </View>
82
+ )}
83
+ {!tabs[selectedIndex]?.renderIcon && !!tabs[selectedIndex]?.icon && (
65
84
  <Icon
66
- source={tabs[selectedIndex]?.icon}
85
+ color={selectedColor}
86
+ source={tabs[selectedIndex]?.icon as string}
67
87
  style={[styles.icon, {marginRight: Spacing.XS}]}
68
88
  />
69
89
  )}
70
90
  <Text
71
- color={theme.colors.primary}
91
+ color={selectedColor}
72
92
  numberOfLines={1}
73
93
  style={styles.textCenter}
74
94
  typography={'header_s_semibold'}>
75
95
  {tabs[selectedIndex]?.title || ''}
76
96
  </Text>
97
+ {tabs[selectedIndex]?.showDot && (
98
+ <BadgeDot
99
+ size={tabs[selectedIndex]?.dotSize ?? 'small'}
100
+ style={{
101
+ marginLeft: Spacing.XS,
102
+ }}
103
+ />
104
+ )}
105
+ {!tabs[selectedIndex]?.showDot && tabs[selectedIndex]?.badgeValue && (
106
+ <View>
107
+ <Badge label={tabs[selectedIndex]?.badgeValue} />
108
+ </View>
109
+ )}
77
110
  </View>
78
111
  {!isLast && <PathSvg style={{right: 8}} />}
79
112
  </View>
@@ -9,6 +9,8 @@ const ScrollableTabBar: FC<TabBarProps> = ({
9
9
  tabs = [],
10
10
  onPressTabItem,
11
11
  selectedIndex,
12
+ selectedColor,
13
+ unselectedColor,
12
14
  }) => {
13
15
  const {theme} = useContext(ApplicationContext);
14
16
  const [itemWidthMap, setItemWidthMap] = useState<
@@ -62,6 +64,8 @@ const ScrollableTabBar: FC<TabBarProps> = ({
62
64
  }}
63
65
  active={isActive}
64
66
  tab={item}
67
+ selectedColor={selectedColor}
68
+ unselectedColor={unselectedColor}
65
69
  />
66
70
  </View>
67
71
  );
@@ -86,7 +90,7 @@ const ScrollableTabBar: FC<TabBarProps> = ({
86
90
  {
87
91
  left: itemWidthMap[selectedIndex]?.x || 0,
88
92
  width: animatedWidth,
89
- backgroundColor: theme.colors.primary,
93
+ backgroundColor: selectedColor,
90
94
  },
91
95
  ]}
92
96
  />
package/tabBar/TabBar.tsx CHANGED
@@ -11,9 +11,11 @@ const TabBar: FC<TabBarProps> = ({
11
11
  onPressTabItem,
12
12
  containerWidth = 0,
13
13
  scrollX,
14
+ direction,
15
+ selectedColor,
16
+ unselectedColor,
14
17
  }) => {
15
18
  const {theme} = useContext(ApplicationContext);
16
-
17
19
  const itemWidth = containerWidth / tabs.length;
18
20
 
19
21
  return (
@@ -35,6 +37,9 @@ const TabBar: FC<TabBarProps> = ({
35
37
  }}
36
38
  active={selectedIndex === index}
37
39
  tab={tab}
40
+ direction={direction}
41
+ selectedColor={selectedColor}
42
+ unselectedColor={unselectedColor}
38
43
  />
39
44
  );
40
45
  })}
@@ -42,9 +47,16 @@ const TabBar: FC<TabBarProps> = ({
42
47
  style={[
43
48
  styles.indicator,
44
49
  {
45
- left: scrollX * itemWidth + Spacing.XS,
50
+ left: scrollX.interpolate({
51
+ inputRange: [0, tabs.length - 1],
52
+ outputRange: [
53
+ Spacing.XS,
54
+ (tabs.length - 1) * itemWidth + Spacing.XS,
55
+ ],
56
+ extrapolate: 'clamp',
57
+ }),
46
58
  width: itemWidth - Spacing.XS * 2,
47
- backgroundColor: theme.colors.primary,
59
+ backgroundColor: selectedColor,
48
60
  },
49
61
  ]}
50
62
  />
@@ -1,27 +1,41 @@
1
- import React, {FC, useContext} from 'react';
2
- import {TouchableOpacity} from 'react-native';
1
+ import React, {FC} from 'react';
2
+ import {TouchableOpacity, View} from 'react-native';
3
3
  import {TabItemProps} from '../types';
4
4
  import styles from '../styles';
5
- import {ApplicationContext, Icon, Spacing, Text} from '@momo-kits/foundation';
5
+ import {Badge, BadgeDot, Icon, Spacing, Text,} from '@momo-kits/foundation';
6
6
 
7
7
  const CardTabItem: FC<TabItemProps> = ({
8
8
  tab,
9
9
  active,
10
10
  onPressTabItem,
11
11
  width,
12
+ selectedColor,
13
+ unselectedColor,
12
14
  }) => {
13
- const {title, icon} = tab;
14
- const {theme} = useContext(ApplicationContext);
15
- const color = active ? theme.colors.primary : theme.colors.text.default;
16
- const tintColor = icon?.includes('http') ? null : color;
15
+ const {
16
+ title,
17
+ icon,
18
+ renderIcon,
19
+ showDot = false,
20
+ dotSize = 'small',
21
+ badgeValue,
22
+ } = tab;
23
+ const color = active ? selectedColor : unselectedColor;
17
24
 
18
25
  return (
19
26
  <TouchableOpacity
20
27
  onPress={onPressTabItem}
21
28
  style={[styles.cardTabItem, {width}]}>
22
- {!!icon && (
29
+ {renderIcon &&
30
+ !['string', 'boolean', 'number'].includes(typeof renderIcon) && (
31
+ <View
32
+ style={[styles.icon, styles.iconHolder, {marginRight: Spacing.XS}]}>
33
+ {renderIcon}
34
+ </View>
35
+ )}
36
+ {!renderIcon && !!icon && (
23
37
  <Icon
24
- color={tintColor}
38
+ color={color}
25
39
  style={[styles.icon, {marginRight: Spacing.XS}]}
26
40
  source={icon}
27
41
  />
@@ -32,6 +46,19 @@ const CardTabItem: FC<TabItemProps> = ({
32
46
  typography={'body_default_regular'}>
33
47
  {title}
34
48
  </Text>
49
+ {showDot && (
50
+ <BadgeDot
51
+ size={dotSize}
52
+ style={{
53
+ marginLeft: Spacing.XS,
54
+ }}
55
+ />
56
+ )}
57
+ {!showDot && badgeValue && (
58
+ <View>
59
+ <Badge label={badgeValue} />
60
+ </View>
61
+ )}
35
62
  </TouchableOpacity>
36
63
  );
37
64
  };
@@ -1,60 +1,140 @@
1
1
  import React, {FC, useContext} from 'react';
2
- import {ImageStyle} from 'react-native-fast-image';
3
- import {TouchableOpacity, View, ViewStyle} from 'react-native';
2
+ import {TouchableOpacity, View} from 'react-native';
4
3
  import {TabItemProps} from '../types';
5
4
  import {
6
5
  ApplicationContext,
6
+ Badge,
7
7
  BadgeDot,
8
8
  Icon,
9
+ scaleSize,
9
10
  Spacing,
10
11
  Text,
11
12
  Typography,
12
13
  } from '@momo-kits/foundation';
13
14
  import styles from '../styles';
14
15
 
15
- const TabItem: FC<TabItemProps> = ({tab, active, onPressTabItem, width}) => {
16
+ const TabItem: FC<TabItemProps> = ({
17
+ tab,
18
+ active,
19
+ onPressTabItem,
20
+ width,
21
+ direction,
22
+ selectedColor,
23
+ unselectedColor,
24
+ }) => {
16
25
  const {theme} = useContext(ApplicationContext);
17
26
  const {
18
27
  title,
19
28
  icon,
20
- direction = 'row',
21
29
  showDot = false,
22
30
  dotSize = 'small',
31
+ badgeValue,
32
+ renderIcon,
23
33
  } = tab;
24
34
  const typography: Typography = active
25
35
  ? 'header_s_semibold'
26
36
  : 'body_default_regular';
27
- const color = active ? theme.colors.primary : theme.colors.text.default;
28
- const tintColor = icon?.includes('http') ? null : color;
37
+ const color = active ? selectedColor : unselectedColor;
29
38
  const dotStyle = dotSize === 'large' ? styles.dot : styles.dotSmall;
30
- const iconStyle: ViewStyle =
31
- direction === 'row'
32
- ? {marginRight: Spacing.XS}
33
- : {marginBottom: Spacing.XS};
34
39
 
35
- return (
36
- <TouchableOpacity
37
- onPress={onPressTabItem}
38
- style={[styles.tabItem, {width, flexDirection: direction}]}>
39
- {!!icon && (
40
- <View>
41
- {showDot && <BadgeDot style={dotStyle} size={dotSize} />}
40
+ const renderRowItem = () => {
41
+ return (
42
+ <TouchableOpacity
43
+ style={[
44
+ styles.tabItem,
45
+ {
46
+ width,
47
+ height: scaleSize(48),
48
+ flexDirection: 'row',
49
+ overflow: 'hidden',
50
+ },
51
+ ]}
52
+ onPress={onPressTabItem}>
53
+ {renderIcon &&
54
+ !['string', 'boolean', 'number'].includes(typeof renderIcon) && (
55
+ <View
56
+ style={[
57
+ styles.icon,
58
+ styles.iconHolder,
59
+ {marginRight: Spacing.XS},
60
+ ]}>
61
+ {renderIcon}
62
+ </View>
63
+ )}
64
+ {!renderIcon && !!icon && (
42
65
  <Icon
43
- color={tintColor}
44
- style={[styles.icon, iconStyle as ImageStyle]}
66
+ style={[styles.icon, {marginRight: Spacing.XS}]}
45
67
  source={icon}
68
+ color={color}
46
69
  />
70
+ )}
71
+ <Text numberOfLines={1} typography={typography} color={color}>
72
+ {title}
73
+ </Text>
74
+ {showDot && (
75
+ <BadgeDot size={dotSize} style={{marginLeft: Spacing.XS}} />
76
+ )}
77
+ {!showDot && badgeValue && (
78
+ <View>
79
+ <Badge label={badgeValue} />
80
+ </View>
81
+ )}
82
+ </TouchableOpacity>
83
+ );
84
+ };
85
+
86
+ const renderColumnItem = () => {
87
+ return (
88
+ <TouchableOpacity
89
+ style={[
90
+ styles.tabItem,
91
+ {
92
+ width,
93
+ height: scaleSize(68),
94
+ flexDirection: 'column',
95
+ overflow: 'hidden',
96
+ },
97
+ ]}
98
+ onPress={onPressTabItem}>
99
+ <View>
100
+ {renderIcon &&
101
+ !['string', 'boolean', 'number'].includes(typeof renderIcon) && (
102
+ <View
103
+ style={[
104
+ styles.icon,
105
+ styles.iconHolder,
106
+ {marginBottom: Spacing.XS},
107
+ ]}>
108
+ {renderIcon}
109
+ </View>
110
+ )}
111
+ {!renderIcon && !!icon && (
112
+ <Icon
113
+ style={[
114
+ styles.icon,
115
+ {
116
+ marginBottom: Spacing.XS,
117
+ },
118
+ ]}
119
+ source={icon}
120
+ color={color}
121
+ />
122
+ )}
123
+ {(renderIcon || !!icon) && showDot && (
124
+ <BadgeDot style={dotStyle} size={dotSize} />
125
+ )}
126
+ {(renderIcon || !!icon) && !showDot && badgeValue && (
127
+ <Badge label={badgeValue} style={styles.badge} />
128
+ )}
47
129
  </View>
48
- )}
49
- <Text
50
- style={styles.textCenter}
51
- numberOfLines={1}
52
- color={color}
53
- typography={typography}>
54
- {title}
55
- </Text>
56
- </TouchableOpacity>
57
- );
130
+ <Text numberOfLines={1} typography={typography} color={color}>
131
+ {title}
132
+ </Text>
133
+ </TouchableOpacity>
134
+ );
135
+ };
136
+
137
+ return direction === 'row' ? renderRowItem() : renderColumnItem();
58
138
  };
59
139
 
60
140
  export default TabItem;
package/types.ts CHANGED
@@ -1,5 +1,6 @@
1
- import {ReactElement} from 'react';
1
+ import {ReactElement, ReactNode} from 'react';
2
2
  import {PagerViewProps} from 'react-native-pager-view';
3
+ import {Animated} from 'react-native';
3
4
 
4
5
  export type Tab = {
5
6
  /**
@@ -12,10 +13,7 @@ export type Tab = {
12
13
  */
13
14
  icon?: string;
14
15
 
15
- /**
16
- * Optional. Specifies the layout direction for the tab's content, either in a row or column format.
17
- */
18
- direction?: 'row' | 'column';
16
+ renderIcon?: ReactNode;
19
17
 
20
18
  /**
21
19
  * The main component or content to be displayed when this tab is active.
@@ -31,6 +29,8 @@ export type Tab = {
31
29
  * Optional. Specifies the size of the dot indicator, either 'small' or 'large'.
32
30
  */
33
31
  dotSize?: 'small' | 'large';
32
+
33
+ badgeValue?: string | number;
34
34
  };
35
35
 
36
36
  export type TabItemProps = {
@@ -53,6 +53,15 @@ export type TabItemProps = {
53
53
  * Optional. Specifies the width of the tab item. Useful for customizing layout and appearance.
54
54
  */
55
55
  width?: number;
56
+
57
+ /**
58
+ * Optional. Specifies the layout direction for the tab's content, either in a row or column format.
59
+ */
60
+ direction?: 'row' | 'column';
61
+
62
+ selectedColor?: string;
63
+
64
+ unselectedColor?: string;
56
65
  };
57
66
 
58
67
  export type TabViewProps = {
@@ -85,6 +94,15 @@ export type TabViewProps = {
85
94
  * Optional. Additional properties to pass to the underlying pager view component.
86
95
  */
87
96
  pagerProps?: PagerViewProps;
97
+
98
+ /**
99
+ * Optional. Specifies the layout direction for the tab's content, either in a row or column format.
100
+ */
101
+ direction?: 'row' | 'column';
102
+
103
+ selectedColor?: string;
104
+
105
+ unselectedColor?: string;
88
106
  };
89
107
 
90
108
  /**
@@ -114,5 +132,14 @@ export type TabBarProps = {
114
132
  /**
115
133
  * Represents the horizontal scroll position in a scrollable tab bar.
116
134
  */
117
- scrollX: number;
135
+ scrollX: Animated.Value;
136
+
137
+ /**
138
+ * Optional. Specifies the layout direction for the tab's content, either in a row or column format.
139
+ */
140
+ direction?: 'row' | 'column';
141
+
142
+ selectedColor?: string;
143
+
144
+ unselectedColor?: string;
118
145
  };