@hero-design/rn 8.115.1 → 8.116.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,11 @@
1
1
  # @hero-design/rn
2
2
 
3
+ ## 8.116.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#4647](https://github.com/Thinkei/hero-design/pull/4647) [`a5254d3e57528ec951ec233be9a1c6f6d9234a7b`](https://github.com/Thinkei/hero-design/commit/a5254d3e57528ec951ec233be9a1c6f6d9234a7b) Thanks [@cuongnguyeneh](https://github.com/cuongnguyeneh)! - feat: Add support for custom tab header
8
+
3
9
  ## 8.115.1
4
10
 
5
11
  ### Patch Changes
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # @hero-design/rn
2
+
3
+ ## Overview
4
+
5
+ `@hero-design/rn` is a React Native component library built with TypeScript and Emotion. It provides UI components, theming system, chart components (D3.js), form components, mobile-specific components (FAB, BottomSheet, Swipeable), and platform-specific implementations (iOS/Android) following Hero Design's design system principles.
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ yarn add @hero-design/rn
11
+ ```
12
+
13
+ **Peer Dependencies:**
14
+
15
+ ```sh
16
+ yarn add react@18.3.1 react-native@0.77.3
17
+ yarn add @hero-design/react-native-month-year-picker@^8.43.2
18
+ yarn add @ptomasroos/react-native-multi-slider@^2.2.2
19
+ yarn add @react-native-community/datetimepicker@^3.5.2
20
+ yarn add @react-native-community/slider@^4.5.1
21
+ yarn add react-native-gesture-handler@~2.20.2
22
+ yarn add react-native-linear-gradient@^2.8.3
23
+ yarn add react-native-pager-view@^6.7.0
24
+ yarn add react-native-safe-area-context@^4.7.0
25
+ yarn add react-native-svg@^15.11.2
26
+ yarn add react-native-vector-icons@^9.1.0
27
+ yarn add react-native-webview@^13.10.2
28
+ ```
29
+
30
+ **Requirements:**
31
+ - React 18.3.1
32
+ - React Native 0.77.3
33
+ - Node.js >= 20.0.0 (20.19.5 recommended)
34
+ - Yarn >= 4.0.2 (enabled via Corepack: `corepack enable`)
35
+ - iOS Simulator or Android Emulator:
36
+ - **For visual tests compatibility**: iPhone 14 (iOS 18+) or Android Pixel 6 (API 30) are recommended
37
+ - **For non-visual fixes or demo purposes**: Any device frame with compatible OS version is sufficient
38
+
39
+ ## Usage
40
+
41
+ ### Basic Setup
42
+
43
+ Wrap your application with `HeroDesignProvider` to enable theming, localization, and component features (Toast, Portal):
44
+
45
+ ```tsx
46
+ import React from 'react';
47
+ import { HeroDesignProvider, getTheme, Button, Card, Typography } from '@hero-design/rn';
48
+
49
+ function App() {
50
+ const theme = getTheme();
51
+
52
+ return (
53
+ <HeroDesignProvider theme={theme}>
54
+ <Card>
55
+ <Typography.Title>Welcome to Hero Design</Typography.Title>
56
+ <Button text="Get Started" intent="primary" onPress={() => {}} />
57
+ </Card>
58
+ </HeroDesignProvider>
59
+ );
60
+ }
61
+ ```
62
+
63
+ ## Theming
64
+
65
+ Hero Design React Native uses a powerful theming system built on Emotion.
66
+
67
+ ### Using ThemeSwitcher
68
+
69
+ For product-specific themes, use `ThemeSwitcher` which provides a predefined theme. Available theme names: `ehWork`, `ehJobs`, `ehWorkDark`.
70
+
71
+ ```tsx
72
+ import { ThemeSwitcher } from '@hero-design/rn';
73
+
74
+ function App() {
75
+ return (
76
+ <ThemeSwitcher name="ehWork">
77
+ {/* Your app */}
78
+ </ThemeSwitcher>
79
+ );
80
+ }
81
+ ```
82
+
83
+ **Note:** `ThemeSwitcher` only provides theme. For Toast, Portal, and Locale features, use `HeroDesignProvider` with a theme from `getTheme()`.
84
+
85
+ ### Design Tokens
86
+
87
+ The theme includes semantic design tokens organized by category:
88
+
89
+ - **Colors**: Global colors (`defaultGlobalSurface`, `onDefaultGlobalSurface`, etc.) and brand colors (`primary`, `secondary`, etc.)
90
+ - **Typography**: `theme.fontSizes`, `theme.fonts`, `theme.lineHeights`
91
+ - **Spacing**: `theme.space` - Consistent spacing scale (`small`, `medium`, `large`, etc.)
92
+ - **Shadows**: `theme.shadows` - Elevation and depth
93
+ - **Components**: Component-specific theme configurations via `theme.__hd__`
94
+
95
+ For comprehensive design token documentation, visit the [Mobile Design Tokens](https://design.employmenthero.com/mobile/mobile-design-tokens).
96
+
97
+ ## Examples
98
+
99
+ For comprehensive examples and component documentation:
100
+
101
+ - **Documentation Site**: Visit the [Hero Design documentation site](https://design.employmenthero.com/mobile/intro)
102
+ - **Mobile Playground**: Explore the [rn-playground](https://github.com/Thinkei/hero-design/tree/master/apps/rn-playground)
103
+
104
+ ## Contributing
105
+
106
+ Contributions to `@hero-design/rn` are welcome!
107
+
108
+ To get started:
109
+
110
+ 1. Clone the repository: `git clone git@github.com:Thinkei/hero-design.git`
111
+ 2. Enable Corepack: `corepack enable`
112
+ 3. Install dependencies: `yarn install`
113
+ 4. Build the package: `yarn turbo run build --filter=@hero-design/rn`
114
+ 5. Run the playground: `yarn dev:rn`
115
+
116
+ **Note:** An Expo account is required to start the playground. Contact the Hero Design team for access.
117
+
118
+ For detailed contributing guidelines, see the main repository [Contributing documentation](https://design.employmenthero.com/mobile/Contributing/coContribution).
package/es/index.js CHANGED
@@ -28030,51 +28030,19 @@ var getTabItem = function getTabItem(_ref) {
28030
28030
  color: color
28031
28031
  });
28032
28032
  };
28033
- var Tabs = function Tabs(_ref2) {
28034
- var onTabPress = _ref2.onTabPress,
28033
+ var TabsHeader = function TabsHeader(_ref2) {
28034
+ var tabs = _ref2.tabs,
28035
28035
  selectedTabKey = _ref2.selectedTabKey,
28036
- tabs = _ref2.tabs,
28037
- containerStyle = _ref2.containerStyle,
28036
+ onTabPress = _ref2.onTabPress,
28038
28037
  barStyle = _ref2.barStyle,
28039
- _ref2$lazy = _ref2.lazy,
28040
- lazy = _ref2$lazy === void 0 ? false : _ref2$lazy,
28041
- _ref2$lazyPreloadDist = _ref2.lazyPreloadDistance,
28042
- lazyPreloadDistance = _ref2$lazyPreloadDist === void 0 ? 1 : _ref2$lazyPreloadDist,
28043
- _ref2$swipeEnabled = _ref2.swipeEnabled,
28044
- swipeEnabled = _ref2$swipeEnabled === void 0 ? true : _ref2$swipeEnabled,
28045
- componentTestID = _ref2.testID;
28038
+ insets = _ref2.insets,
28039
+ componentTestID = _ref2.componentTestID,
28040
+ tabsWidth = _ref2.tabsWidth,
28041
+ setTabsWidth = _ref2.setTabsWidth,
28042
+ positionAnimatedValue = _ref2.positionAnimatedValue,
28043
+ scrollOffsetAnimatedValue = _ref2.scrollOffsetAnimatedValue;
28046
28044
  var theme = useTheme$1();
28047
- var insets = useSafeAreaInsets();
28048
- var pagerViewRef = useRef(null);
28049
- var selectedTabIndex = tabs.findIndex(function (item) {
28050
- return item.key === selectedTabKey;
28051
- });
28052
- var scrollOffsetAnimatedValue = useRef(new Animated.Value(0)).current;
28053
- var positionAnimatedValue = useRef(new Animated.Value(0)).current;
28054
- var _useState = useState(0),
28055
- _useState2 = _slicedToArray(_useState, 2),
28056
- tabsWidth = _useState2[0],
28057
- setTabsWidth = _useState2[1];
28058
- var _useHandlePageScroll = useHandlePageScroll(),
28059
- onPageScrollStateChanged = _useHandlePageScroll.onPageScrollStateChanged,
28060
- hasScrolled = _useHandlePageScroll.hasScrolled;
28061
- useEffect(function () {
28062
- if (selectedTabIndex !== -1) {
28063
- var _pagerViewRef$current;
28064
- (_pagerViewRef$current = pagerViewRef.current) === null || _pagerViewRef$current === void 0 || _pagerViewRef$current.setPage(selectedTabIndex);
28065
- }
28066
- }, [selectedTabIndex]);
28067
- var tabContextProviderValue = useMemo(function () {
28068
- return {
28069
- selectedTabKey: selectedTabKey
28070
- };
28071
- }, [selectedTabKey]);
28072
- return /*#__PURE__*/React__default.createElement(TabContext.Provider, {
28073
- value: tabContextProviderValue
28074
- }, /*#__PURE__*/React__default.createElement(TabContainer$1, {
28075
- style: containerStyle,
28076
- testID: componentTestID
28077
- }, /*#__PURE__*/React__default.createElement(HeaderTabWrapper$1, {
28045
+ return /*#__PURE__*/React__default.createElement(HeaderTabWrapper$1, {
28078
28046
  themeInsets: insets,
28079
28047
  style: barStyle,
28080
28048
  testID: componentTestID ? "".concat(componentTestID, "-tab-bar") : undefined
@@ -28113,7 +28081,67 @@ var Tabs = function Tabs(_ref2) {
28113
28081
  scrollOffsetAnimatedValue: scrollOffsetAnimatedValue,
28114
28082
  tabsLength: tabs.length,
28115
28083
  tabsWidth: tabsWidth
28116
- }))), /*#__PURE__*/React__default.createElement(AnimatedPagerView, {
28084
+ })));
28085
+ };
28086
+ var Tabs = function Tabs(_ref3) {
28087
+ var onTabPress = _ref3.onTabPress,
28088
+ selectedTabKey = _ref3.selectedTabKey,
28089
+ tabs = _ref3.tabs,
28090
+ containerStyle = _ref3.containerStyle,
28091
+ barStyle = _ref3.barStyle,
28092
+ _ref3$lazy = _ref3.lazy,
28093
+ lazy = _ref3$lazy === void 0 ? false : _ref3$lazy,
28094
+ _ref3$lazyPreloadDist = _ref3.lazyPreloadDistance,
28095
+ lazyPreloadDistance = _ref3$lazyPreloadDist === void 0 ? 1 : _ref3$lazyPreloadDist,
28096
+ _ref3$swipeEnabled = _ref3.swipeEnabled,
28097
+ swipeEnabled = _ref3$swipeEnabled === void 0 ? true : _ref3$swipeEnabled,
28098
+ componentTestID = _ref3.testID,
28099
+ header = _ref3.header;
28100
+ var insets = useSafeAreaInsets();
28101
+ var pagerViewRef = useRef(null);
28102
+ var selectedTabIndex = tabs.findIndex(function (item) {
28103
+ return item.key === selectedTabKey;
28104
+ });
28105
+ var scrollOffsetAnimatedValue = useRef(new Animated.Value(0)).current;
28106
+ var positionAnimatedValue = useRef(new Animated.Value(0)).current;
28107
+ var _useState = useState(0),
28108
+ _useState2 = _slicedToArray(_useState, 2),
28109
+ tabsWidth = _useState2[0],
28110
+ setTabsWidth = _useState2[1];
28111
+ var _useHandlePageScroll = useHandlePageScroll(),
28112
+ onPageScrollStateChanged = _useHandlePageScroll.onPageScrollStateChanged,
28113
+ hasScrolled = _useHandlePageScroll.hasScrolled;
28114
+ useEffect(function () {
28115
+ if (selectedTabIndex !== -1) {
28116
+ var _pagerViewRef$current;
28117
+ (_pagerViewRef$current = pagerViewRef.current) === null || _pagerViewRef$current === void 0 || _pagerViewRef$current.setPage(selectedTabIndex);
28118
+ }
28119
+ }, [selectedTabIndex]);
28120
+ var tabContextProviderValue = useMemo(function () {
28121
+ return {
28122
+ selectedTabKey: selectedTabKey
28123
+ };
28124
+ }, [selectedTabKey]);
28125
+ var headerProps = useMemo(function () {
28126
+ return {
28127
+ tabs: tabs,
28128
+ selectedTabKey: selectedTabKey,
28129
+ onTabPress: onTabPress,
28130
+ barStyle: barStyle,
28131
+ insets: insets,
28132
+ componentTestID: componentTestID,
28133
+ tabsWidth: tabsWidth,
28134
+ setTabsWidth: setTabsWidth,
28135
+ positionAnimatedValue: positionAnimatedValue,
28136
+ scrollOffsetAnimatedValue: scrollOffsetAnimatedValue
28137
+ };
28138
+ }, [tabs, selectedTabKey, onTabPress, barStyle, insets, componentTestID, tabsWidth, setTabsWidth, positionAnimatedValue, scrollOffsetAnimatedValue]);
28139
+ return /*#__PURE__*/React__default.createElement(TabContext.Provider, {
28140
+ value: tabContextProviderValue
28141
+ }, /*#__PURE__*/React__default.createElement(TabContainer$1, {
28142
+ style: containerStyle,
28143
+ testID: componentTestID
28144
+ }, header ? header(headerProps) : /*#__PURE__*/React__default.createElement(TabsHeader, headerProps), /*#__PURE__*/React__default.createElement(AnimatedPagerView, {
28117
28145
  initialPage: selectedTabIndex,
28118
28146
  ref: pagerViewRef,
28119
28147
  onPageSelected: function onPageSelected(e) {
@@ -28154,6 +28182,7 @@ var Tabs = function Tabs(_ref2) {
28154
28182
  }))));
28155
28183
  };
28156
28184
  var index$2 = Object.assign(Tabs, {
28185
+ Header: TabsHeader,
28157
28186
  Scroll: ScrollableTab,
28158
28187
  ScrollHeader: ScrollableTabHeader,
28159
28188
  useIsFocused: useIsFocused
package/lib/index.js CHANGED
@@ -28059,51 +28059,19 @@ var getTabItem = function getTabItem(_ref) {
28059
28059
  color: color
28060
28060
  });
28061
28061
  };
28062
- var Tabs = function Tabs(_ref2) {
28063
- var onTabPress = _ref2.onTabPress,
28062
+ var TabsHeader = function TabsHeader(_ref2) {
28063
+ var tabs = _ref2.tabs,
28064
28064
  selectedTabKey = _ref2.selectedTabKey,
28065
- tabs = _ref2.tabs,
28066
- containerStyle = _ref2.containerStyle,
28065
+ onTabPress = _ref2.onTabPress,
28067
28066
  barStyle = _ref2.barStyle,
28068
- _ref2$lazy = _ref2.lazy,
28069
- lazy = _ref2$lazy === void 0 ? false : _ref2$lazy,
28070
- _ref2$lazyPreloadDist = _ref2.lazyPreloadDistance,
28071
- lazyPreloadDistance = _ref2$lazyPreloadDist === void 0 ? 1 : _ref2$lazyPreloadDist,
28072
- _ref2$swipeEnabled = _ref2.swipeEnabled,
28073
- swipeEnabled = _ref2$swipeEnabled === void 0 ? true : _ref2$swipeEnabled,
28074
- componentTestID = _ref2.testID;
28067
+ insets = _ref2.insets,
28068
+ componentTestID = _ref2.componentTestID,
28069
+ tabsWidth = _ref2.tabsWidth,
28070
+ setTabsWidth = _ref2.setTabsWidth,
28071
+ positionAnimatedValue = _ref2.positionAnimatedValue,
28072
+ scrollOffsetAnimatedValue = _ref2.scrollOffsetAnimatedValue;
28075
28073
  var theme = useTheme$1();
28076
- var insets = reactNativeSafeAreaContext.useSafeAreaInsets();
28077
- var pagerViewRef = React.useRef(null);
28078
- var selectedTabIndex = tabs.findIndex(function (item) {
28079
- return item.key === selectedTabKey;
28080
- });
28081
- var scrollOffsetAnimatedValue = React.useRef(new reactNative.Animated.Value(0)).current;
28082
- var positionAnimatedValue = React.useRef(new reactNative.Animated.Value(0)).current;
28083
- var _useState = React.useState(0),
28084
- _useState2 = _slicedToArray(_useState, 2),
28085
- tabsWidth = _useState2[0],
28086
- setTabsWidth = _useState2[1];
28087
- var _useHandlePageScroll = useHandlePageScroll(),
28088
- onPageScrollStateChanged = _useHandlePageScroll.onPageScrollStateChanged,
28089
- hasScrolled = _useHandlePageScroll.hasScrolled;
28090
- React.useEffect(function () {
28091
- if (selectedTabIndex !== -1) {
28092
- var _pagerViewRef$current;
28093
- (_pagerViewRef$current = pagerViewRef.current) === null || _pagerViewRef$current === void 0 || _pagerViewRef$current.setPage(selectedTabIndex);
28094
- }
28095
- }, [selectedTabIndex]);
28096
- var tabContextProviderValue = React.useMemo(function () {
28097
- return {
28098
- selectedTabKey: selectedTabKey
28099
- };
28100
- }, [selectedTabKey]);
28101
- return /*#__PURE__*/React__namespace.default.createElement(TabContext.Provider, {
28102
- value: tabContextProviderValue
28103
- }, /*#__PURE__*/React__namespace.default.createElement(TabContainer$1, {
28104
- style: containerStyle,
28105
- testID: componentTestID
28106
- }, /*#__PURE__*/React__namespace.default.createElement(HeaderTabWrapper$1, {
28074
+ return /*#__PURE__*/React__namespace.default.createElement(HeaderTabWrapper$1, {
28107
28075
  themeInsets: insets,
28108
28076
  style: barStyle,
28109
28077
  testID: componentTestID ? "".concat(componentTestID, "-tab-bar") : undefined
@@ -28142,7 +28110,67 @@ var Tabs = function Tabs(_ref2) {
28142
28110
  scrollOffsetAnimatedValue: scrollOffsetAnimatedValue,
28143
28111
  tabsLength: tabs.length,
28144
28112
  tabsWidth: tabsWidth
28145
- }))), /*#__PURE__*/React__namespace.default.createElement(AnimatedPagerView, {
28113
+ })));
28114
+ };
28115
+ var Tabs = function Tabs(_ref3) {
28116
+ var onTabPress = _ref3.onTabPress,
28117
+ selectedTabKey = _ref3.selectedTabKey,
28118
+ tabs = _ref3.tabs,
28119
+ containerStyle = _ref3.containerStyle,
28120
+ barStyle = _ref3.barStyle,
28121
+ _ref3$lazy = _ref3.lazy,
28122
+ lazy = _ref3$lazy === void 0 ? false : _ref3$lazy,
28123
+ _ref3$lazyPreloadDist = _ref3.lazyPreloadDistance,
28124
+ lazyPreloadDistance = _ref3$lazyPreloadDist === void 0 ? 1 : _ref3$lazyPreloadDist,
28125
+ _ref3$swipeEnabled = _ref3.swipeEnabled,
28126
+ swipeEnabled = _ref3$swipeEnabled === void 0 ? true : _ref3$swipeEnabled,
28127
+ componentTestID = _ref3.testID,
28128
+ header = _ref3.header;
28129
+ var insets = reactNativeSafeAreaContext.useSafeAreaInsets();
28130
+ var pagerViewRef = React.useRef(null);
28131
+ var selectedTabIndex = tabs.findIndex(function (item) {
28132
+ return item.key === selectedTabKey;
28133
+ });
28134
+ var scrollOffsetAnimatedValue = React.useRef(new reactNative.Animated.Value(0)).current;
28135
+ var positionAnimatedValue = React.useRef(new reactNative.Animated.Value(0)).current;
28136
+ var _useState = React.useState(0),
28137
+ _useState2 = _slicedToArray(_useState, 2),
28138
+ tabsWidth = _useState2[0],
28139
+ setTabsWidth = _useState2[1];
28140
+ var _useHandlePageScroll = useHandlePageScroll(),
28141
+ onPageScrollStateChanged = _useHandlePageScroll.onPageScrollStateChanged,
28142
+ hasScrolled = _useHandlePageScroll.hasScrolled;
28143
+ React.useEffect(function () {
28144
+ if (selectedTabIndex !== -1) {
28145
+ var _pagerViewRef$current;
28146
+ (_pagerViewRef$current = pagerViewRef.current) === null || _pagerViewRef$current === void 0 || _pagerViewRef$current.setPage(selectedTabIndex);
28147
+ }
28148
+ }, [selectedTabIndex]);
28149
+ var tabContextProviderValue = React.useMemo(function () {
28150
+ return {
28151
+ selectedTabKey: selectedTabKey
28152
+ };
28153
+ }, [selectedTabKey]);
28154
+ var headerProps = React.useMemo(function () {
28155
+ return {
28156
+ tabs: tabs,
28157
+ selectedTabKey: selectedTabKey,
28158
+ onTabPress: onTabPress,
28159
+ barStyle: barStyle,
28160
+ insets: insets,
28161
+ componentTestID: componentTestID,
28162
+ tabsWidth: tabsWidth,
28163
+ setTabsWidth: setTabsWidth,
28164
+ positionAnimatedValue: positionAnimatedValue,
28165
+ scrollOffsetAnimatedValue: scrollOffsetAnimatedValue
28166
+ };
28167
+ }, [tabs, selectedTabKey, onTabPress, barStyle, insets, componentTestID, tabsWidth, setTabsWidth, positionAnimatedValue, scrollOffsetAnimatedValue]);
28168
+ return /*#__PURE__*/React__namespace.default.createElement(TabContext.Provider, {
28169
+ value: tabContextProviderValue
28170
+ }, /*#__PURE__*/React__namespace.default.createElement(TabContainer$1, {
28171
+ style: containerStyle,
28172
+ testID: componentTestID
28173
+ }, header ? header(headerProps) : /*#__PURE__*/React__namespace.default.createElement(TabsHeader, headerProps), /*#__PURE__*/React__namespace.default.createElement(AnimatedPagerView, {
28146
28174
  initialPage: selectedTabIndex,
28147
28175
  ref: pagerViewRef,
28148
28176
  onPageSelected: function onPageSelected(e) {
@@ -28183,6 +28211,7 @@ var Tabs = function Tabs(_ref2) {
28183
28211
  }))));
28184
28212
  };
28185
28213
  var index$2 = Object.assign(Tabs, {
28214
+ Header: TabsHeader,
28186
28215
  Scroll: ScrollableTab,
28187
28216
  ScrollHeader: ScrollableTabHeader,
28188
28217
  useIsFocused: useIsFocused
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hero-design/rn",
3
- "version": "8.115.1",
3
+ "version": "8.116.0",
4
4
  "license": "MIT",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.js",
@@ -4,6 +4,6 @@ sonar.organization=thinkei
4
4
 
5
5
  sonar.sources=.
6
6
  sonar.inclusions=**/*
7
- sonar.exclusions=**/__tests__/**,**/public/**,**/stats/**,**.config.js,**/testUtils/**
7
+ sonar.exclusions=**/__tests__/**,**/public/**,**/stats/**,**.config.js,**/testUtils/**,**/lcov-report/**
8
8
  sonar.java.binaries=**/src/main/java
9
- sonar.javascript.lcov.reportPaths=./coverage/lcov.info
9
+ sonar.javascript.lcov.reportPaths=rn-test-results*/lcov.info
@@ -10,7 +10,7 @@ import { TabContainer } from './StyledScrollableTabs';
10
10
  import useHandlePageScroll from './useHandlePageScroll';
11
11
  import { ScreenContext, TabContext } from './useIsFocused';
12
12
 
13
- export interface ScrollableTabProps extends TabsProps {
13
+ export interface ScrollableTabProps extends Omit<TabsProps, 'header'> {
14
14
  variant?: 'underlined' | 'highlighted';
15
15
  header?: FunctionComponent<ScrollableTabHeaderProps>;
16
16
  }
@@ -1,5 +1,5 @@
1
1
  import { useTheme } from '@emotion/react';
2
- import type { ReactElement, ReactNode } from 'react';
2
+ import type { FunctionComponent, ReactElement, ReactNode } from 'react';
3
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';
@@ -37,6 +37,18 @@ export type TabType = {
37
37
  badge?: BadgeConfigType;
38
38
  };
39
39
 
40
+ export interface TabsHeaderProps {
41
+ tabs: TabType[];
42
+ selectedTabKey: string;
43
+ onTabPress: (key: string) => void;
44
+ barStyle?: StyleProp<ViewStyle>;
45
+ insets: { top: number; right: number; bottom: number; left: number };
46
+ componentTestID?: string;
47
+ tabsWidth: number;
48
+ setTabsWidth: (width: number) => void;
49
+ positionAnimatedValue: Animated.Value;
50
+ scrollOffsetAnimatedValue: Animated.Value;
51
+ }
40
52
  export interface TabsProps extends ViewProps {
41
53
  /**
42
54
  * Callback which is called on tab press, receiving key of upcoming active Tab.
@@ -77,6 +89,10 @@ export interface TabsProps extends ViewProps {
77
89
  * Testing id of the component.
78
90
  */
79
91
  testID?: string;
92
+ /**
93
+ * Custom header component.
94
+ */
95
+ header?: FunctionComponent<TabsHeaderProps>;
80
96
  }
81
97
 
82
98
  const AnimatedPagerView = Animated.createAnimatedComponent(PagerView);
@@ -105,6 +121,77 @@ const getTabItem = ({
105
121
  return item({ color });
106
122
  };
107
123
 
124
+ const TabsHeader = ({
125
+ tabs,
126
+ selectedTabKey,
127
+ onTabPress,
128
+ barStyle,
129
+ insets,
130
+ componentTestID,
131
+ tabsWidth,
132
+ setTabsWidth,
133
+ positionAnimatedValue,
134
+ scrollOffsetAnimatedValue,
135
+ }: TabsHeaderProps) => {
136
+ const theme = useTheme();
137
+ return (
138
+ <HeaderTabWrapper
139
+ themeInsets={insets}
140
+ style={barStyle}
141
+ testID={componentTestID ? `${componentTestID}-tab-bar` : undefined}
142
+ >
143
+ <View>
144
+ <HeaderTab
145
+ onLayout={(e) => {
146
+ const { width } = e.nativeEvent.layout;
147
+ if (tabsWidth !== width) {
148
+ setTabsWidth(width);
149
+ }
150
+ }}
151
+ >
152
+ {tabs.map((tab) => {
153
+ const {
154
+ key,
155
+ testID,
156
+ activeItem,
157
+ inactiveItem: originalInactiveItem,
158
+ badge,
159
+ } = tab;
160
+
161
+ const active = selectedTabKey === key;
162
+ const inactiveItem = originalInactiveItem ?? activeItem;
163
+ const tabItem = getTabItem({
164
+ item: active ? activeItem : inactiveItem,
165
+ color: theme.__hd__.tabs.colors.text,
166
+ active,
167
+ });
168
+
169
+ return (
170
+ <TouchableWithoutFeedback
171
+ key={key}
172
+ onPress={() => {
173
+ onTabPress(key);
174
+ }}
175
+ testID={testID}
176
+ >
177
+ <HeaderTabItem>
178
+ <TabWithBadge config={badge} tabItem={tabItem} />
179
+ </HeaderTabItem>
180
+ </TouchableWithoutFeedback>
181
+ );
182
+ })}
183
+ </HeaderTab>
184
+ <ActiveTabIndicator
185
+ positionAnimatedValue={positionAnimatedValue}
186
+ scrollOffsetAnimatedValue={scrollOffsetAnimatedValue}
187
+ tabsLength={tabs.length}
188
+ tabsWidth={tabsWidth}
189
+ />
190
+ </View>
191
+ </HeaderTabWrapper>
192
+ );
193
+ };
194
+
108
195
  const Tabs = ({
109
196
  onTabPress,
110
197
  selectedTabKey,
@@ -115,8 +202,8 @@ const Tabs = ({
115
202
  lazyPreloadDistance = 1,
116
203
  swipeEnabled = true,
117
204
  testID: componentTestID,
205
+ header,
118
206
  }: TabsProps): ReactElement => {
119
- const theme = useTheme();
120
207
  const insets = useSafeAreaInsets();
121
208
  const pagerViewRef = useRef<PagerView>(null);
122
209
  const selectedTabIndex = tabs.findIndex(
@@ -141,63 +228,37 @@ const Tabs = ({
141
228
  [selectedTabKey]
142
229
  );
143
230
 
231
+ const headerProps = useMemo(
232
+ () => ({
233
+ tabs,
234
+ selectedTabKey,
235
+ onTabPress,
236
+ barStyle,
237
+ insets,
238
+ componentTestID,
239
+ tabsWidth,
240
+ setTabsWidth,
241
+ positionAnimatedValue,
242
+ scrollOffsetAnimatedValue,
243
+ }),
244
+ [
245
+ tabs,
246
+ selectedTabKey,
247
+ onTabPress,
248
+ barStyle,
249
+ insets,
250
+ componentTestID,
251
+ tabsWidth,
252
+ setTabsWidth,
253
+ positionAnimatedValue,
254
+ scrollOffsetAnimatedValue,
255
+ ]
256
+ );
257
+
144
258
  return (
145
259
  <TabContext.Provider value={tabContextProviderValue}>
146
260
  <TabContainer style={containerStyle} testID={componentTestID}>
147
- <HeaderTabWrapper
148
- themeInsets={insets}
149
- style={barStyle}
150
- testID={componentTestID ? `${componentTestID}-tab-bar` : undefined}
151
- >
152
- <View>
153
- <HeaderTab
154
- onLayout={(e) => {
155
- const { width } = e.nativeEvent.layout;
156
- if (tabsWidth !== width) {
157
- setTabsWidth(width);
158
- }
159
- }}
160
- >
161
- {tabs.map((tab) => {
162
- const {
163
- key,
164
- testID,
165
- activeItem,
166
- inactiveItem: originalInactiveItem,
167
- badge,
168
- } = tab;
169
-
170
- const active = selectedTabKey === key;
171
- const inactiveItem = originalInactiveItem ?? activeItem;
172
- const tabItem = getTabItem({
173
- item: active ? activeItem : inactiveItem,
174
- color: theme.__hd__.tabs.colors.text,
175
- active,
176
- });
177
-
178
- return (
179
- <TouchableWithoutFeedback
180
- key={key}
181
- onPress={() => {
182
- onTabPress(key);
183
- }}
184
- testID={testID}
185
- >
186
- <HeaderTabItem>
187
- <TabWithBadge config={badge} tabItem={tabItem} />
188
- </HeaderTabItem>
189
- </TouchableWithoutFeedback>
190
- );
191
- })}
192
- </HeaderTab>
193
- <ActiveTabIndicator
194
- positionAnimatedValue={positionAnimatedValue}
195
- scrollOffsetAnimatedValue={scrollOffsetAnimatedValue}
196
- tabsLength={tabs.length}
197
- tabsWidth={tabsWidth}
198
- />
199
- </View>
200
- </HeaderTabWrapper>
261
+ {header ? header(headerProps) : <TabsHeader {...headerProps} />}
201
262
  <AnimatedPagerView
202
263
  initialPage={selectedTabIndex}
203
264
  ref={pagerViewRef}
@@ -250,6 +311,7 @@ const Tabs = ({
250
311
  };
251
312
 
252
313
  export default Object.assign(Tabs, {
314
+ Header: TabsHeader,
253
315
  Scroll: ScrollableTabs,
254
316
  ScrollHeader: ScrollableTabHeader,
255
317
  useIsFocused,
@@ -2,7 +2,7 @@ import type { FunctionComponent } from 'react';
2
2
  import React from 'react';
3
3
  import type { TabsProps } from '.';
4
4
  import type { ScrollableTabHeaderProps } from './ScrollableTabsHeader/ScrollableTabsHeader';
5
- export interface ScrollableTabProps extends TabsProps {
5
+ export interface ScrollableTabProps extends Omit<TabsProps, 'header'> {
6
6
  variant?: 'underlined' | 'highlighted';
7
7
  header?: FunctionComponent<ScrollableTabHeaderProps>;
8
8
  }
@@ -1,6 +1,7 @@
1
- import type { ReactElement, ReactNode } from 'react';
1
+ import type { FunctionComponent, ReactElement, ReactNode } from 'react';
2
2
  import React from 'react';
3
3
  import type { StyleProp, ViewProps, ViewStyle } from 'react-native';
4
+ import { Animated } from 'react-native';
4
5
  import type { IconName } from '../Icon';
5
6
  import type { BadgeConfigType } from './TabWithBadge';
6
7
  export type ItemType = string | IconName | ((props: {
@@ -14,6 +15,23 @@ export type TabType = {
14
15
  testID?: string;
15
16
  badge?: BadgeConfigType;
16
17
  };
18
+ export interface TabsHeaderProps {
19
+ tabs: TabType[];
20
+ selectedTabKey: string;
21
+ onTabPress: (key: string) => void;
22
+ barStyle?: StyleProp<ViewStyle>;
23
+ insets: {
24
+ top: number;
25
+ right: number;
26
+ bottom: number;
27
+ left: number;
28
+ };
29
+ componentTestID?: string;
30
+ tabsWidth: number;
31
+ setTabsWidth: (width: number) => void;
32
+ positionAnimatedValue: Animated.Value;
33
+ scrollOffsetAnimatedValue: Animated.Value;
34
+ }
17
35
  export interface TabsProps extends ViewProps {
18
36
  /**
19
37
  * Callback which is called on tab press, receiving key of upcoming active Tab.
@@ -54,8 +72,13 @@ export interface TabsProps extends ViewProps {
54
72
  * Testing id of the component.
55
73
  */
56
74
  testID?: string;
75
+ /**
76
+ * Custom header component.
77
+ */
78
+ header?: FunctionComponent<TabsHeaderProps>;
57
79
  }
58
- declare const _default: (({ onTabPress, selectedTabKey, tabs, containerStyle, barStyle, lazy, lazyPreloadDistance, swipeEnabled, testID: componentTestID, }: TabsProps) => ReactElement) & {
80
+ declare const _default: (({ onTabPress, selectedTabKey, tabs, containerStyle, barStyle, lazy, lazyPreloadDistance, swipeEnabled, testID: componentTestID, header, }: TabsProps) => ReactElement) & {
81
+ Header: ({ tabs, selectedTabKey, onTabPress, barStyle, insets, componentTestID, tabsWidth, setTabsWidth, positionAnimatedValue, scrollOffsetAnimatedValue, }: TabsHeaderProps) => React.JSX.Element;
59
82
  Scroll: ({ onTabPress, selectedTabKey, tabs, containerStyle, barStyle, lazy, lazyPreloadDistance, swipeEnabled, testID: componentTestID, variant, header, }: import("./ScrollableTabs").ScrollableTabProps) => React.JSX.Element;
60
83
  ScrollHeader: ({ onTabPress, selectedIndex, tabs, barStyle, testID, insets, variant, }: import("./ScrollableTabsHeader/ScrollableTabsHeader").ScrollableTabHeaderProps) => React.JSX.Element;
61
84
  useIsFocused: () => boolean | undefined;