@hero-design/rn 8.63.2 → 8.63.4-alpha.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @hero-design/rn
2
2
 
3
+ ## 8.63.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#3129](https://github.com/Thinkei/hero-design/pull/3129) [`116f04b1a`](https://github.com/Thinkei/hero-design/commit/116f04b1ae7a63a4f3563a935989a318200ce044) Thanks [@vinhphan-eh](https://github.com/vinhphan-eh)! - Fix mobile visual tests workflow
8
+
9
+ - Updated dependencies [[`de331a19e`](https://github.com/Thinkei/hero-design/commit/de331a19ea56e55ff18b060a21b31831df60c794), [`264d28d3c`](https://github.com/Thinkei/hero-design/commit/264d28d3cb2f68b96bc94f4b72a9681cd4c4abb0)]:
10
+ - @hero-design/react-native-month-year-picker@8.42.10
11
+
3
12
  ## 8.63.2
4
13
 
5
14
  ### Patch Changes
package/es/index.js CHANGED
@@ -15040,6 +15040,7 @@ var Toast$1 = function Toast(_ref2) {
15040
15040
  outputRange: toastConfig.position === 'top' ? [-20, distance] : [20, -distance]
15041
15041
  });
15042
15042
  return /*#__PURE__*/React__default.createElement(Container, {
15043
+ testID: "toast-container",
15043
15044
  themeVariant: variant,
15044
15045
  themeIntent: intent,
15045
15046
  style: [style, {
@@ -17826,6 +17827,20 @@ var ScrollableTabHeader = function ScrollableTabHeader(_ref2) {
17826
17827
  }));
17827
17828
  };
17828
17829
 
17830
+ var useHandlePageScroll = function useHandlePageScroll() {
17831
+ // Used as a flag to prevent calling onTabPress on initial render
17832
+ var hasScrolled = useRef(false);
17833
+ var onPageScrollStateChanged = useCallback(function (e) {
17834
+ if (!hasScrolled.current && e.nativeEvent.pageScrollState === 'dragging') {
17835
+ hasScrolled.current = true;
17836
+ }
17837
+ }, []);
17838
+ return {
17839
+ onPageScrollStateChanged: onPageScrollStateChanged,
17840
+ hasScrolled: hasScrolled
17841
+ };
17842
+ };
17843
+
17829
17844
  var TabContext = /*#__PURE__*/React__default.createContext(null);
17830
17845
  var ScreenContext = /*#__PURE__*/React__default.createContext(null);
17831
17846
  var useIsFocused = function useIsFocused() {
@@ -17853,14 +17868,15 @@ var ScrollableTab = function ScrollableTab(_ref) {
17853
17868
  componentTestID = _ref.testID,
17854
17869
  _ref$variant = _ref.variant,
17855
17870
  variant = _ref$variant === void 0 ? 'highlighted' : _ref$variant;
17856
- var pagerViewRef = React__default.useRef(null);
17871
+ var pagerViewRef = useRef(null);
17857
17872
  var insets = useSafeAreaInsets();
17858
17873
  var selectedTabIndex = tabs.findIndex(function (item) {
17859
17874
  return item.key === selectedTabKey;
17860
17875
  });
17861
- // Used as a flag to prevent calling onTabPress on initial render
17862
- var hasScrolled = useRef(false);
17863
- React__default.useEffect(function () {
17876
+ var _useHandlePageScroll = useHandlePageScroll(),
17877
+ hasScrolled = _useHandlePageScroll.hasScrolled,
17878
+ onPageScrollStateChanged = _useHandlePageScroll.onPageScrollStateChanged;
17879
+ useEffect(function () {
17864
17880
  var timeoutHandle;
17865
17881
  if (selectedTabIndex !== -1) {
17866
17882
  // If the selected tab is changed too quickly, the setPage is crashed and not work anymore
@@ -17877,7 +17893,7 @@ var ScrollableTab = function ScrollableTab(_ref) {
17877
17893
  }
17878
17894
  };
17879
17895
  }, [selectedTabIndex, pagerViewRef]);
17880
- var tabContextProviderValue = React__default.useMemo(function () {
17896
+ var tabContextProviderValue = useMemo(function () {
17881
17897
  return {
17882
17898
  selectedTabKey: selectedTabKey
17883
17899
  };
@@ -17899,11 +17915,7 @@ var ScrollableTab = function ScrollableTab(_ref) {
17899
17915
  useNext: true,
17900
17916
  initialPage: selectedTabIndex,
17901
17917
  ref: pagerViewRef,
17902
- onPageScrollStateChanged: function onPageScrollStateChanged(e) {
17903
- if (!hasScrolled.current && e.nativeEvent.pageScrollState === 'dragging') {
17904
- hasScrolled.current = true;
17905
- }
17906
- },
17918
+ onPageScrollStateChanged: onPageScrollStateChanged,
17907
17919
  onPageSelected: function onPageSelected(e) {
17908
17920
  var index = e.nativeEvent.position;
17909
17921
  var selectedItem = tabs[index];
@@ -17967,23 +17979,26 @@ var Tabs = function Tabs(_ref2) {
17967
17979
  componentTestID = _ref2.testID;
17968
17980
  var theme = useTheme$1();
17969
17981
  var insets = useSafeAreaInsets();
17970
- var pagerViewRef = React__default.useRef(null);
17982
+ var pagerViewRef = useRef(null);
17971
17983
  var selectedTabIndex = tabs.findIndex(function (item) {
17972
17984
  return item.key === selectedTabKey;
17973
17985
  });
17974
- var scrollOffsetAnimatedValue = React__default.useRef(new Animated.Value(0)).current;
17975
- var positionAnimatedValue = React__default.useRef(new Animated.Value(0)).current;
17976
- var _React$useState = React__default.useState(0),
17977
- _React$useState2 = _slicedToArray(_React$useState, 2),
17978
- tabsWidth = _React$useState2[0],
17979
- setTabsWidth = _React$useState2[1];
17986
+ var scrollOffsetAnimatedValue = useRef(new Animated.Value(0)).current;
17987
+ var positionAnimatedValue = useRef(new Animated.Value(0)).current;
17988
+ var _useState = useState(0),
17989
+ _useState2 = _slicedToArray(_useState, 2),
17990
+ tabsWidth = _useState2[0],
17991
+ setTabsWidth = _useState2[1];
17992
+ var _useHandlePageScroll = useHandlePageScroll(),
17993
+ onPageScrollStateChanged = _useHandlePageScroll.onPageScrollStateChanged,
17994
+ hasScrolled = _useHandlePageScroll.hasScrolled;
17980
17995
  useEffect(function () {
17981
17996
  if (selectedTabIndex !== -1) {
17982
17997
  var _pagerViewRef$current;
17983
17998
  (_pagerViewRef$current = pagerViewRef.current) === null || _pagerViewRef$current === void 0 || _pagerViewRef$current.setPage(selectedTabIndex);
17984
17999
  }
17985
18000
  }, [selectedTabIndex]);
17986
- var tabContextProviderValue = React__default.useMemo(function () {
18001
+ var tabContextProviderValue = useMemo(function () {
17987
18002
  return {
17988
18003
  selectedTabKey: selectedTabKey
17989
18004
  };
@@ -18039,10 +18054,11 @@ var Tabs = function Tabs(_ref2) {
18039
18054
  onPageSelected: function onPageSelected(e) {
18040
18055
  var index = e.nativeEvent.position;
18041
18056
  var selectedItem = tabs[index];
18042
- if (selectedItem) {
18057
+ if (hasScrolled.current && selectedItem) {
18043
18058
  onTabPress(selectedItem.key);
18044
18059
  }
18045
18060
  },
18061
+ onPageScrollStateChanged: onPageScrollStateChanged,
18046
18062
  onPageScroll: Animated.event([{
18047
18063
  nativeEvent: {
18048
18064
  offset: scrollOffsetAnimatedValue,
@@ -0,0 +1,42 @@
1
+ const heroDesign = require('@hero-design/eslint-plugin');
2
+ const _import = require('eslint-plugin-import');
3
+ const { FlatCompat } = require('@eslint/eslintrc');
4
+ const { includeIgnoreFile } = require('@eslint/compat');
5
+ const path = require('path');
6
+
7
+ const compat = new FlatCompat({
8
+ baseDirectory: __dirname,
9
+ });
10
+
11
+ const gitignorePath = path.resolve(__dirname, '../../.gitignore');
12
+
13
+ module.exports = [
14
+ ...compat.extends('hd', 'plugin:@hero-design/recommendedRn'),
15
+ includeIgnoreFile(gitignorePath),
16
+ {
17
+ files: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'],
18
+
19
+ plugins: {
20
+ '@hero-design': heroDesign,
21
+ import: _import,
22
+ },
23
+
24
+ languageOptions: {
25
+ parserOptions: {
26
+ tsconfigRootDir: __dirname,
27
+ project: ['./tsconfig.json'],
28
+ },
29
+ },
30
+
31
+ rules: {
32
+ 'no-underscore-dangle': [
33
+ 'error',
34
+ {
35
+ allow: ['__hd__'],
36
+ },
37
+ ],
38
+
39
+ 'import/no-cycle': 'error',
40
+ },
41
+ },
42
+ ];
package/lib/index.js CHANGED
@@ -15070,6 +15070,7 @@ var Toast$1 = function Toast(_ref2) {
15070
15070
  outputRange: toastConfig.position === 'top' ? [-20, distance] : [20, -distance]
15071
15071
  });
15072
15072
  return /*#__PURE__*/React__default["default"].createElement(Container, {
15073
+ testID: "toast-container",
15073
15074
  themeVariant: variant,
15074
15075
  themeIntent: intent,
15075
15076
  style: [style, {
@@ -17856,6 +17857,20 @@ var ScrollableTabHeader = function ScrollableTabHeader(_ref2) {
17856
17857
  }));
17857
17858
  };
17858
17859
 
17860
+ var useHandlePageScroll = function useHandlePageScroll() {
17861
+ // Used as a flag to prevent calling onTabPress on initial render
17862
+ var hasScrolled = React.useRef(false);
17863
+ var onPageScrollStateChanged = React.useCallback(function (e) {
17864
+ if (!hasScrolled.current && e.nativeEvent.pageScrollState === 'dragging') {
17865
+ hasScrolled.current = true;
17866
+ }
17867
+ }, []);
17868
+ return {
17869
+ onPageScrollStateChanged: onPageScrollStateChanged,
17870
+ hasScrolled: hasScrolled
17871
+ };
17872
+ };
17873
+
17859
17874
  var TabContext = /*#__PURE__*/React__default["default"].createContext(null);
17860
17875
  var ScreenContext = /*#__PURE__*/React__default["default"].createContext(null);
17861
17876
  var useIsFocused = function useIsFocused() {
@@ -17883,14 +17898,15 @@ var ScrollableTab = function ScrollableTab(_ref) {
17883
17898
  componentTestID = _ref.testID,
17884
17899
  _ref$variant = _ref.variant,
17885
17900
  variant = _ref$variant === void 0 ? 'highlighted' : _ref$variant;
17886
- var pagerViewRef = React__default["default"].useRef(null);
17901
+ var pagerViewRef = React.useRef(null);
17887
17902
  var insets = reactNativeSafeAreaContext.useSafeAreaInsets();
17888
17903
  var selectedTabIndex = tabs.findIndex(function (item) {
17889
17904
  return item.key === selectedTabKey;
17890
17905
  });
17891
- // Used as a flag to prevent calling onTabPress on initial render
17892
- var hasScrolled = React.useRef(false);
17893
- React__default["default"].useEffect(function () {
17906
+ var _useHandlePageScroll = useHandlePageScroll(),
17907
+ hasScrolled = _useHandlePageScroll.hasScrolled,
17908
+ onPageScrollStateChanged = _useHandlePageScroll.onPageScrollStateChanged;
17909
+ React.useEffect(function () {
17894
17910
  var timeoutHandle;
17895
17911
  if (selectedTabIndex !== -1) {
17896
17912
  // If the selected tab is changed too quickly, the setPage is crashed and not work anymore
@@ -17907,7 +17923,7 @@ var ScrollableTab = function ScrollableTab(_ref) {
17907
17923
  }
17908
17924
  };
17909
17925
  }, [selectedTabIndex, pagerViewRef]);
17910
- var tabContextProviderValue = React__default["default"].useMemo(function () {
17926
+ var tabContextProviderValue = React.useMemo(function () {
17911
17927
  return {
17912
17928
  selectedTabKey: selectedTabKey
17913
17929
  };
@@ -17929,11 +17945,7 @@ var ScrollableTab = function ScrollableTab(_ref) {
17929
17945
  useNext: true,
17930
17946
  initialPage: selectedTabIndex,
17931
17947
  ref: pagerViewRef,
17932
- onPageScrollStateChanged: function onPageScrollStateChanged(e) {
17933
- if (!hasScrolled.current && e.nativeEvent.pageScrollState === 'dragging') {
17934
- hasScrolled.current = true;
17935
- }
17936
- },
17948
+ onPageScrollStateChanged: onPageScrollStateChanged,
17937
17949
  onPageSelected: function onPageSelected(e) {
17938
17950
  var index = e.nativeEvent.position;
17939
17951
  var selectedItem = tabs[index];
@@ -17997,23 +18009,26 @@ var Tabs = function Tabs(_ref2) {
17997
18009
  componentTestID = _ref2.testID;
17998
18010
  var theme = useTheme$1();
17999
18011
  var insets = reactNativeSafeAreaContext.useSafeAreaInsets();
18000
- var pagerViewRef = React__default["default"].useRef(null);
18012
+ var pagerViewRef = React.useRef(null);
18001
18013
  var selectedTabIndex = tabs.findIndex(function (item) {
18002
18014
  return item.key === selectedTabKey;
18003
18015
  });
18004
- var scrollOffsetAnimatedValue = React__default["default"].useRef(new reactNative.Animated.Value(0)).current;
18005
- var positionAnimatedValue = React__default["default"].useRef(new reactNative.Animated.Value(0)).current;
18006
- var _React$useState = React__default["default"].useState(0),
18007
- _React$useState2 = _slicedToArray(_React$useState, 2),
18008
- tabsWidth = _React$useState2[0],
18009
- setTabsWidth = _React$useState2[1];
18016
+ var scrollOffsetAnimatedValue = React.useRef(new reactNative.Animated.Value(0)).current;
18017
+ var positionAnimatedValue = React.useRef(new reactNative.Animated.Value(0)).current;
18018
+ var _useState = React.useState(0),
18019
+ _useState2 = _slicedToArray(_useState, 2),
18020
+ tabsWidth = _useState2[0],
18021
+ setTabsWidth = _useState2[1];
18022
+ var _useHandlePageScroll = useHandlePageScroll(),
18023
+ onPageScrollStateChanged = _useHandlePageScroll.onPageScrollStateChanged,
18024
+ hasScrolled = _useHandlePageScroll.hasScrolled;
18010
18025
  React.useEffect(function () {
18011
18026
  if (selectedTabIndex !== -1) {
18012
18027
  var _pagerViewRef$current;
18013
18028
  (_pagerViewRef$current = pagerViewRef.current) === null || _pagerViewRef$current === void 0 || _pagerViewRef$current.setPage(selectedTabIndex);
18014
18029
  }
18015
18030
  }, [selectedTabIndex]);
18016
- var tabContextProviderValue = React__default["default"].useMemo(function () {
18031
+ var tabContextProviderValue = React.useMemo(function () {
18017
18032
  return {
18018
18033
  selectedTabKey: selectedTabKey
18019
18034
  };
@@ -18069,10 +18084,11 @@ var Tabs = function Tabs(_ref2) {
18069
18084
  onPageSelected: function onPageSelected(e) {
18070
18085
  var index = e.nativeEvent.position;
18071
18086
  var selectedItem = tabs[index];
18072
- if (selectedItem) {
18087
+ if (hasScrolled.current && selectedItem) {
18073
18088
  onTabPress(selectedItem.key);
18074
18089
  }
18075
18090
  },
18091
+ onPageScrollStateChanged: onPageScrollStateChanged,
18076
18092
  onPageScroll: reactNative.Animated.event([{
18077
18093
  nativeEvent: {
18078
18094
  offset: scrollOffsetAnimatedValue,
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@hero-design/rn",
3
- "version": "8.63.2",
3
+ "version": "8.63.4-alpha.0",
4
4
  "license": "MIT",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.js",
7
7
  "types": "types/index.d.ts",
8
8
  "scripts": {
9
- "lint": "eslint src --ext .js,.jsx,.ts,.tsx --ignore-path ../../.gitignore",
9
+ "lint": "eslint src",
10
10
  "type-check": "tsc --noEmit",
11
11
  "test": "jest --runInBand",
12
12
  "test:watch": "jest --runInBand --watch",
@@ -28,7 +28,7 @@
28
28
  "nanoid": "^4.0.2"
29
29
  },
30
30
  "peerDependencies": {
31
- "@hero-design/react-native-month-year-picker": "^8.42.9",
31
+ "@hero-design/react-native-month-year-picker": "^8.42.10",
32
32
  "@react-native-community/datetimepicker": "^3.5.2 || ^7.6.1",
33
33
  "@react-native-community/slider": "^4.5.1",
34
34
  "react": "18.2.0",
@@ -47,8 +47,11 @@
47
47
  "@babel/preset-typescript": "^7.20.0",
48
48
  "@babel/runtime": "^7.20.0",
49
49
  "@emotion/jest": "^11.11.0",
50
- "@hero-design/eslint-plugin": "9.0.0",
51
- "@hero-design/react-native-month-year-picker": "^8.42.9",
50
+ "@eslint/compat": "^1.1.1",
51
+ "@eslint/eslintrc": "^3.1.0",
52
+ "@eslint/js": "^9.8.0",
53
+ "@hero-design/eslint-plugin": "9.0.1",
54
+ "@hero-design/react-native-month-year-picker": "^8.42.10",
52
55
  "@react-native-community/datetimepicker": "7.6.1",
53
56
  "@react-native-community/slider": "^4.5.1",
54
57
  "@rollup/plugin-babel": "^5.3.1",
@@ -86,6 +89,7 @@
86
89
  "rollup": "^2.68.0",
87
90
  "rollup-plugin-copy": "^3.4.0",
88
91
  "rollup-plugin-flow": "^1.1.1",
92
+ "rollup-plugin-visualizer": "^5.12.0",
89
93
  "ts-jest": "^29.1.1",
90
94
  "typescript": "4.8.4"
91
95
  },
package/rollup.config.js CHANGED
@@ -6,11 +6,16 @@ import json from '@rollup/plugin-json';
6
6
  import replace from '@rollup/plugin-replace';
7
7
  import copy from 'rollup-plugin-copy';
8
8
  import flow from 'rollup-plugin-flow';
9
+ import { visualizer } from 'rollup-plugin-visualizer';
9
10
 
10
11
  import pkg from './package.json';
11
12
 
12
13
  const extensions = ['.js', '.jsx', '.ts', '.tsx'];
13
14
 
15
+ const generateBuildStats = process.env.GENERATE_BUILD_STATS === 'true';
16
+ const bundleTemplate = process.env.BUNDLE_TEMPLATE || 'treemap';
17
+ const fileName = process.env.FILE_NAME || `stats/${pkg.version}/rn-stats.html`;
18
+
14
19
  export default {
15
20
  input: 'src/index.ts',
16
21
  output: [
@@ -54,5 +59,13 @@ export default {
54
59
  },
55
60
  ],
56
61
  }),
62
+ ...(generateBuildStats
63
+ ? [
64
+ visualizer({
65
+ filename: fileName,
66
+ template: bundleTemplate,
67
+ }),
68
+ ]
69
+ : []),
57
70
  ],
58
71
  };
@@ -6,6 +6,6 @@ sonar.organization=thinkei
6
6
 
7
7
  sonar.sources=.
8
8
  sonar.inclusions=**/*
9
- sonar.exclusions=**/__tests__/**,**/public/**
9
+ sonar.exclusions=**/__tests__/**,**/public/**,**/stats/**,**.config.js
10
10
  sonar.java.binaries=**/src/main/java
11
11
  sonar.javascript.lcov.reportPaths=./coverage/lcov.info
@@ -1,10 +1,11 @@
1
- import React, { useRef } from 'react';
2
- import { useSafeAreaInsets } from 'react-native-safe-area-context';
1
+ import React, { useEffect, useMemo, useRef } from 'react';
3
2
  import PagerView from 'react-native-pager-view';
4
- import { TabContainer } from './StyledScrollableTabs';
3
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
5
4
  import type { TabsProps } from '.';
6
5
  import SceneView from './SceneView';
7
6
  import ScrollableTabHeader from './ScrollableTabsHeader/ScrollableTabsHeader';
7
+ import { TabContainer } from './StyledScrollableTabs';
8
+ import useHandlePageScroll from './useHandlePageScroll';
8
9
  import { ScreenContext, TabContext } from './useIsFocused';
9
10
 
10
11
  export interface ScrollableTabProps extends TabsProps {
@@ -23,15 +24,15 @@ const ScrollableTab = ({
23
24
  testID: componentTestID,
24
25
  variant = 'highlighted',
25
26
  }: ScrollableTabProps) => {
26
- const pagerViewRef = React.useRef<PagerView>(null);
27
+ const pagerViewRef = useRef<PagerView>(null);
27
28
  const insets = useSafeAreaInsets();
28
29
  const selectedTabIndex = tabs.findIndex(
29
30
  (item) => item.key === selectedTabKey
30
31
  );
31
- // Used as a flag to prevent calling onTabPress on initial render
32
- const hasScrolled = useRef(false);
33
32
 
34
- React.useEffect(() => {
33
+ const { hasScrolled, onPageScrollStateChanged } = useHandlePageScroll();
34
+
35
+ useEffect(() => {
35
36
  let timeoutHandle: ReturnType<typeof setTimeout>;
36
37
  if (selectedTabIndex !== -1) {
37
38
  // If the selected tab is changed too quickly, the setPage is crashed and not work anymore
@@ -49,7 +50,7 @@ const ScrollableTab = ({
49
50
  };
50
51
  }, [selectedTabIndex, pagerViewRef]);
51
52
 
52
- const tabContextProviderValue = React.useMemo(
53
+ const tabContextProviderValue = useMemo(
53
54
  () => ({
54
55
  selectedTabKey,
55
56
  }),
@@ -72,14 +73,7 @@ const ScrollableTab = ({
72
73
  useNext
73
74
  initialPage={selectedTabIndex}
74
75
  ref={pagerViewRef}
75
- onPageScrollStateChanged={(e) => {
76
- if (
77
- !hasScrolled.current &&
78
- e.nativeEvent.pageScrollState === 'dragging'
79
- ) {
80
- hasScrolled.current = true;
81
- }
82
- }}
76
+ onPageScrollStateChanged={onPageScrollStateChanged}
83
77
  onPageSelected={(e) => {
84
78
  const index = e.nativeEvent.position;
85
79
  const selectedItem = tabs[index];
@@ -99,6 +99,38 @@ describe('Tabs.Scroll', () => {
99
99
  expect(getByText('Calendar Screen unfocused')).toBeDefined();
100
100
  });
101
101
 
102
+ it('calls onTabPress correctly', () => {
103
+ const onTabPress = jest.fn();
104
+ const { getByText } = renderWithTheme(
105
+ <SafeAreaProvider
106
+ initialMetrics={{
107
+ frame: { x: 0, y: 0, width: 0, height: 0 },
108
+ insets: { top: 0, left: 0, right: 0, bottom: 0 },
109
+ }}
110
+ >
111
+ <ScrollableTabs
112
+ tabs={[
113
+ {
114
+ key: 'work',
115
+ activeItem: 'Work',
116
+ component: <CustomScreen title="Work Screen" />,
117
+ },
118
+ ]}
119
+ onTabPress={onTabPress}
120
+ selectedTabKey="work"
121
+ />
122
+ </SafeAreaProvider>
123
+ );
124
+
125
+ // Not calling the function on first render
126
+ expect(onTabPress).not.toHaveBeenCalled();
127
+
128
+ // Calling the function on tab press
129
+ fireEvent.press(getByText('Work'));
130
+
131
+ expect(onTabPress).toHaveBeenCalledTimes(1);
132
+ });
133
+
102
134
  describe('lazy', () => {
103
135
  it('render all screens when lazy = false', async () => {
104
136
  const { queryByText } = renderWithTheme(
@@ -110,6 +110,38 @@ describe('Tabs', () => {
110
110
  fireEvent.press(getByText('Home'));
111
111
  expect(getByText('Home Screen focused')).toBeDefined();
112
112
  });
113
+
114
+ it('calls onTabPress only when pressed', () => {
115
+ const onTabPress = jest.fn();
116
+ const { getByText } = renderWithTheme(
117
+ <SafeAreaProvider
118
+ initialMetrics={{
119
+ frame: { x: 0, y: 0, width: 0, height: 0 },
120
+ insets: { top: 0, left: 0, right: 0, bottom: 0 },
121
+ }}
122
+ >
123
+ <Tabs
124
+ tabs={[
125
+ {
126
+ key: 'work',
127
+ activeItem: 'Work',
128
+ component: <CustomScreen title="Work Screen" />,
129
+ },
130
+ ]}
131
+ onTabPress={onTabPress}
132
+ selectedTabKey="work"
133
+ />
134
+ </SafeAreaProvider>
135
+ );
136
+
137
+ // Not calling the function on first render
138
+ expect(onTabPress).not.toHaveBeenCalled();
139
+
140
+ // Calling the function on tab press
141
+ fireEvent.press(getByText('Work'));
142
+
143
+ expect(onTabPress).toHaveBeenCalledTimes(1);
144
+ });
113
145
  });
114
146
 
115
147
  describe('useIsFocused', () => {
@@ -1,6 +1,6 @@
1
1
  import { useTheme } from '@emotion/react';
2
2
  import type { ReactNode } from 'react';
3
- import React, { useEffect } from 'react';
3
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
4
4
  import type { StyleProp, ViewProps, ViewStyle } from 'react-native';
5
5
  import { Animated, TouchableWithoutFeedback, View } from 'react-native';
6
6
  import type { PagerViewOnPageScrollEventData } from 'react-native-pager-view';
@@ -19,6 +19,7 @@ import {
19
19
  } from './StyledTabs';
20
20
  import type { BadgeConfigType } from './TabWithBadge';
21
21
  import TabWithBadge from './TabWithBadge';
22
+ import useHandlePageScroll from './useHandlePageScroll';
22
23
  import { ScreenContext, TabContext, useIsFocused } from './useIsFocused';
23
24
 
24
25
  export type ItemType =
@@ -57,7 +58,7 @@ export interface TabsProps extends ViewProps {
57
58
  */
58
59
  barStyle?: StyleProp<ViewStyle>;
59
60
  /**
60
- * Whether inactive screen should be removed and unmounted in React.
61
+ * Whether inactive screen should be removed and unmounted in
61
62
  * Defaults value is `false`.
62
63
  */
63
64
  lazy?: boolean;
@@ -116,13 +117,15 @@ const Tabs = ({
116
117
  }: TabsProps): JSX.Element => {
117
118
  const theme = useTheme();
118
119
  const insets = useSafeAreaInsets();
119
- const pagerViewRef = React.useRef<PagerView>(null);
120
+ const pagerViewRef = useRef<PagerView>(null);
120
121
  const selectedTabIndex = tabs.findIndex(
121
122
  (item) => item.key === selectedTabKey
122
123
  );
123
- const scrollOffsetAnimatedValue = React.useRef(new Animated.Value(0)).current;
124
- const positionAnimatedValue = React.useRef(new Animated.Value(0)).current;
125
- const [tabsWidth, setTabsWidth] = React.useState<number>(0);
124
+ const scrollOffsetAnimatedValue = useRef(new Animated.Value(0)).current;
125
+ const positionAnimatedValue = useRef(new Animated.Value(0)).current;
126
+ const [tabsWidth, setTabsWidth] = useState<number>(0);
127
+
128
+ const { onPageScrollStateChanged, hasScrolled } = useHandlePageScroll();
126
129
 
127
130
  useEffect(() => {
128
131
  if (selectedTabIndex !== -1) {
@@ -130,7 +133,7 @@ const Tabs = ({
130
133
  }
131
134
  }, [selectedTabIndex]);
132
135
 
133
- const tabContextProviderValue = React.useMemo(
136
+ const tabContextProviderValue = useMemo(
134
137
  () => ({
135
138
  selectedTabKey,
136
139
  }),
@@ -201,10 +204,11 @@ const Tabs = ({
201
204
  onPageSelected={(e) => {
202
205
  const index = e.nativeEvent.position;
203
206
  const selectedItem = tabs[index];
204
- if (selectedItem) {
207
+ if (hasScrolled.current && selectedItem) {
205
208
  onTabPress(selectedItem.key);
206
209
  }
207
210
  }}
211
+ onPageScrollStateChanged={onPageScrollStateChanged}
208
212
  onPageScroll={Animated.event<PagerViewOnPageScrollEventData>(
209
213
  [
210
214
  {
@@ -0,0 +1,32 @@
1
+ import { useCallback, useRef } from 'react';
2
+ import { NativeSyntheticEvent } from 'react-native';
3
+
4
+ const useHandlePageScroll = () => {
5
+ // Used as a flag to prevent calling onTabPress on initial render
6
+ const hasScrolled = useRef(false);
7
+
8
+ const onPageScrollStateChanged = useCallback(
9
+ (
10
+ e: NativeSyntheticEvent<
11
+ Readonly<{
12
+ pageScrollState: 'idle' | 'dragging' | 'settling';
13
+ }>
14
+ >
15
+ ) => {
16
+ if (
17
+ !hasScrolled.current &&
18
+ e.nativeEvent.pageScrollState === 'dragging'
19
+ ) {
20
+ hasScrolled.current = true;
21
+ }
22
+ },
23
+ []
24
+ );
25
+
26
+ return {
27
+ onPageScrollStateChanged,
28
+ hasScrolled,
29
+ };
30
+ };
31
+
32
+ export default useHandlePageScroll;
@@ -92,6 +92,7 @@ const Toast = ({
92
92
 
93
93
  return (
94
94
  <Container
95
+ testID="toast-container"
95
96
  themeVariant={variant}
96
97
  themeIntent={intent}
97
98
  style={[