@hero-design/rn 8.130.2 → 8.131.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.
@@ -1,4 +1,4 @@
1
- import { Animated, Platform, StyleSheet, View } from 'react-native';
1
+ import { Animated, Platform, View } from 'react-native';
2
2
  import styled from '@emotion/native';
3
3
 
4
4
  // Checks if the platform is Android 7x and 8.x, i.e., API levels 24 to 27 (Android 7.0 to 8.1),
@@ -22,30 +22,75 @@ const HeaderTabWrapper = styled(View)<{
22
22
  backgroundColor: theme.__hd__.tabs.colors.headerBackground,
23
23
  }));
24
24
 
25
- const HeaderTabItem = styled(Animated.View)<{ isFirstItem?: boolean }>(
26
- ({ theme, isFirstItem }) => ({
27
- marginLeft: isFirstItem ? 0 : theme.__hd__.tabs.space.itemMargin,
28
- paddingVertical: theme.__hd__.tabs.space.itemVerticalPadding,
29
- })
30
- );
25
+ const getItemMarginLeft = (
26
+ isFirstItem: boolean | undefined,
27
+ themeVariant: 'underlined' | 'highlighted' | undefined,
28
+ highlightedMargin: number,
29
+ defaultMargin: number
30
+ ) => {
31
+ if (isFirstItem) return 0;
32
+ if (themeVariant === 'highlighted') return highlightedMargin;
33
+ return defaultMargin;
34
+ };
31
35
 
32
- const HeaderTabItemOutlineWrapper = styled(View)(({ theme }) => ({
36
+ const HeaderTabItem = styled(Animated.View)<{
37
+ isFirstItem?: boolean;
38
+ themeVariant?: 'underlined' | 'highlighted';
39
+ }>(({ theme, isFirstItem, themeVariant }) => ({
40
+ marginLeft: getItemMarginLeft(
41
+ isFirstItem,
42
+ themeVariant,
43
+ theme.__hd__.tabs.space.highlightedItemMargin,
44
+ theme.__hd__.tabs.space.itemMargin
45
+ ),
33
46
  paddingVertical: theme.__hd__.tabs.space.itemVerticalPadding,
34
- ...StyleSheet.absoluteFillObject,
47
+ alignItems: 'center',
48
+ justifyContent: 'center',
35
49
  }));
36
50
 
37
- const HeaderTabItemOutline = styled(Animated.View)<{ themeActive: boolean }>(
38
- ({ theme, themeActive }) => ({
39
- borderRadius: theme.__hd__.tabs.radii.outline,
40
- backgroundColor: themeActive
41
- ? theme.__hd__.tabs.colors.activeBackground
42
- : undefined,
43
- })
44
- );
51
+ // Three-piece pill: left cap + body + right cap, all native-driver animated.
52
+ // Splitting into pieces keeps border-radius on fixed-width views so scaleX
53
+ // never distorts the rounded corners. Cap width equals the border-radius so
54
+ // the rounded edge is fully contained within the cap piece.
55
+ const HeaderTabPillLeft = styled(Animated.View)(({ theme }) => ({
56
+ position: 'absolute',
57
+ top: 0,
58
+ bottom: 0,
59
+ left: 0,
60
+ width: theme.__hd__.tabs.radii.highlightedOutline,
61
+ borderTopLeftRadius: theme.__hd__.tabs.radii.highlightedOutline,
62
+ backgroundColor: theme.__hd__.tabs.colors.highlightedActiveBackground,
63
+ }));
64
+
65
+ const HeaderTabPillBody = styled(Animated.View)(({ theme }) => ({
66
+ position: 'absolute',
67
+ top: 0,
68
+ bottom: 0,
69
+ left: 0,
70
+ width: 1,
71
+ backgroundColor: theme.__hd__.tabs.colors.highlightedActiveBackground,
72
+ }));
73
+
74
+ const HeaderTabPillRight = styled(Animated.View)(({ theme }) => ({
75
+ position: 'absolute',
76
+ top: 0,
77
+ bottom: 0,
78
+ left: 0,
79
+ width: theme.__hd__.tabs.radii.highlightedOutline,
80
+ borderTopRightRadius: theme.__hd__.tabs.radii.highlightedOutline,
81
+ backgroundColor: theme.__hd__.tabs.colors.highlightedActiveBackground,
82
+ }));
83
+
84
+ const HeaderTabItemActiveBorder = styled(Animated.View)(({ theme }) => ({
85
+ position: 'absolute',
86
+ bottom: 0,
87
+ width: 1,
88
+ height: theme.__hd__.tabs.borderWidths.highlightedActiveBorder,
89
+ backgroundColor: theme.__hd__.tabs.colors.highlightedActiveBorder,
90
+ }));
45
91
 
46
92
  const HeaderTabItemWrapper = styled(View)(({ theme }) => ({
47
93
  paddingHorizontal: theme.__hd__.tabs.space.outlineHorizontalPadding,
48
- paddingVertical: theme.__hd__.tabs.space.outlineVerticalPadding,
49
94
  position: 'relative',
50
95
  justifyContent: 'center',
51
96
  ...(!isAndroid8 && {
@@ -54,7 +99,7 @@ const HeaderTabItemWrapper = styled(View)(({ theme }) => ({
54
99
  }));
55
100
 
56
101
  const HeaderTabItemIndicator = styled(Animated.View)(({ theme }) => ({
57
- width: '100%',
102
+ width: 1,
58
103
  height: theme.__hd__.tabs.sizes.indicator,
59
104
  position: 'absolute',
60
105
  bottom: theme.__hd__.tabs.space.tabIndicatorBottom,
@@ -67,8 +112,10 @@ export {
67
112
  TabScreen,
68
113
  TabContainer,
69
114
  HeaderTabWrapper,
70
- HeaderTabItemOutlineWrapper,
71
- HeaderTabItemOutline,
115
+ HeaderTabPillLeft,
116
+ HeaderTabPillBody,
117
+ HeaderTabPillRight,
118
+ HeaderTabItemActiveBorder,
72
119
  HeaderTabItemWrapper,
73
120
  HeaderTabItemIndicator,
74
121
  };
@@ -35,6 +35,8 @@ export type TabType = {
35
35
  component: ReactNode;
36
36
  testID?: string;
37
37
  badge?: BadgeConfigType;
38
+ /** Whether the tab is disabled. Disabled tabs cannot be pressed. */
39
+ disabled?: boolean;
38
40
  };
39
41
 
40
42
  export interface TabsHeaderProps {
@@ -9,6 +9,10 @@ const getTabsTheme = (theme: GlobalTheme) => {
9
9
  indicator: theme.colors.primary,
10
10
  text: theme.colors.onDefaultGlobalSurface,
11
11
  headerBackground: theme.colors.defaultGlobalSurface,
12
+ highlightedActiveText: theme.colors.primary,
13
+ highlightedActiveBorder: theme.colors.primary,
14
+ highlightedActiveBackground: theme.colors.neutralGlobalSurface,
15
+ highlightedDisabledText: theme.colors.disabledOnDefaultGlobalSurface,
12
16
  };
13
17
 
14
18
  const space = {
@@ -17,16 +21,19 @@ const getTabsTheme = (theme: GlobalTheme) => {
17
21
  itemVerticalPadding: theme.space.small,
18
22
  itemMargin: theme.space.smallMedium,
19
23
  outlineHorizontalPadding: theme.space.small,
20
- outlineVerticalPadding: theme.space.xsmall,
21
24
  tabIndicatorBottom: -theme.space.xxsmall,
25
+ highlightedItemMargin: theme.space.xsmall,
26
+ highlightedBarTopPadding: theme.space.xxsmall,
22
27
  };
23
28
 
24
29
  const radii = {
25
- outline: theme.radii.xlarge,
30
+ highlightedOutline: theme.radii.medium,
26
31
  };
27
32
 
28
33
  const borderWidths = {
29
34
  headerBottom: theme.borderWidths.medium,
35
+ highlightedHeaderBottom: theme.borderWidths.base,
36
+ highlightedActiveBorder: theme.borderWidths.base,
30
37
  };
31
38
 
32
39
  const sizes = {
@@ -4,6 +4,7 @@ import type { SystemPalette } from './types';
4
4
  const ehJobsSystemPalette: SystemPalette = {
5
5
  ...swagLightSystemPalette,
6
6
  name: 'ehJobs',
7
+ primary: '#7622d7',
7
8
  secondary: '#40d1ff',
8
9
  onSecondary: '#460078',
9
10
  secondaryHighlightedSurface: '#ecfaff',
@@ -3,7 +3,7 @@ import type { SystemPalette, BrandSystemPalette } from './types';
3
3
  import swagLightGlobalPalette from './swagLightGlobal';
4
4
 
5
5
  const ehWorkBrandSystemPalette: BrandSystemPalette = {
6
- primary: '#460078',
6
+ primary: '#7622d7',
7
7
  onPrimary: '#fdfbff',
8
8
  secondary: '#b382fd',
9
9
  onSecondary: palette.white,
@@ -32,7 +32,7 @@ export declare const StyledCheckMark: import("@emotion/native").StyledComponent<
32
32
  as?: React.ElementType;
33
33
  } & {
34
34
  themeSize: "small" | "xsmall" | "medium" | "large" | "xlarge" | "xxxsmall";
35
- themeIntent: "secondary" | "primary" | "text" | "success" | "info" | "warning" | "danger" | "muted" | "inactive" | "disabled-text" | "text-inverted";
35
+ themeIntent: "primary" | "secondary" | "text" | "success" | "info" | "warning" | "danger" | "muted" | "inactive" | "disabled-text" | "text-inverted";
36
36
  } & {
37
37
  ref?: import("react").Ref<import("react-native-vector-icons/Icon").Icon> | undefined;
38
38
  } & {
@@ -36,5 +36,5 @@ export interface ScrollableTabHeaderProps extends ViewProps {
36
36
  */
37
37
  variant?: 'underlined' | 'highlighted';
38
38
  }
39
- declare const ScrollableTabHeader: ({ onTabPress, selectedIndex, tabs, barStyle, testID, insets, variant, }: ScrollableTabHeaderProps) => React.JSX.Element;
39
+ declare const ScrollableTabHeader: ({ onTabPress, selectedIndex: rawSelectedIndex, tabs, barStyle, testID, insets, variant, }: ScrollableTabHeaderProps) => React.JSX.Element;
40
40
  export default ScrollableTabHeader;
@@ -0,0 +1,75 @@
1
+ import { Animated } from 'react-native';
2
+ import type { LayoutChangeEvent } from 'react-native';
3
+ /**
4
+ * Drives two visual layers that slide to the selected tab on every press:
5
+ *
6
+ * Layer 1 — bottom border / underline (indicatorStyle)
7
+ * ─────────────────────────────────────────────────────
8
+ * Uses the "width:1 + scaleX" trick: the element has a fixed stylesheet
9
+ * width of 1px and scaleX is set to the target pixel width, giving a visual
10
+ * width of 1 × scaleX pixels without touching any layout property.
11
+ * Both translateX and scaleX are transform properties → native driver.
12
+ * Caveat: scaleX also scales border-radius, so this layer has no border-radius.
13
+ *
14
+ * Layer 2 — pill background (pillLeftStyle / pillBodyStyle / pillRightStyle)
15
+ * ─────────────────────────────────────────────────────────────────────────────
16
+ * The pill is split into three absolutely-positioned children so that
17
+ * border-radius is never distorted by scale:
18
+ *
19
+ * ┌──────────────────────────────────────────────────────┐
20
+ * │ [cap-left 8px] [body width-1 + scaleX] [cap-right 8px] │
21
+ * └──────────────────────────────────────────────────────┘
22
+ *
23
+ * cap-left — fixed 8px wide, borderTopLeftRadius:8, translateX = pillX
24
+ * body — width:1 + scaleX trick (scaleX = tabWidth - 16),
25
+ * transformOrigin 'left center',
26
+ * translateX = pillX + 8 (via Animated.add)
27
+ * cap-right — fixed 8px wide, borderTopRightRadius:8,
28
+ * translateX = pillX + tabWidth - 8 (via Animated.add)
29
+ *
30
+ * All four animated values use the native driver (translateX and scaleX are
31
+ * transform properties). `width` is never animated, so no JS driver needed.
32
+ *
33
+ * Driver summary:
34
+ * indicatorX native translateX — slides the bottom border
35
+ * indicatorScaleX native scaleX — stretches the bottom border
36
+ * pillX native translateX — slides all three pill pieces
37
+ * pillBodyScaleX native scaleX — stretches the pill body
38
+ * pillRightOffsetX native translateX — positions the right cap
39
+ * (Animated.add: pillX + tabWidth - 8)
40
+ */
41
+ declare const useIndicatorAnimation: ({ selectedIndex, tabsLength, pillCapWidth, }: {
42
+ selectedIndex: number | undefined;
43
+ tabsLength: number;
44
+ /** Width of each rounded cap, should equal theme radii.highlightedOutline. */
45
+ pillCapWidth: number;
46
+ }) => {
47
+ indicatorStyle: {
48
+ readonly transformOrigin: "left center";
49
+ readonly transform: readonly [{
50
+ readonly translateX: Animated.Value;
51
+ }, {
52
+ readonly scaleX: Animated.Value;
53
+ }];
54
+ };
55
+ pillLeftStyle: {
56
+ readonly transform: readonly [{
57
+ readonly translateX: Animated.Value;
58
+ }];
59
+ };
60
+ pillBodyStyle: {
61
+ readonly transformOrigin: "left center";
62
+ readonly transform: readonly [{
63
+ readonly translateX: Animated.AnimatedAddition<string | number>;
64
+ }, {
65
+ readonly scaleX: Animated.Value;
66
+ }];
67
+ };
68
+ pillRightStyle: {
69
+ readonly transform: readonly [{
70
+ readonly translateX: Animated.AnimatedAddition<string | number>;
71
+ }];
72
+ };
73
+ onTabLayout: (index: number, event: LayoutChangeEvent) => void;
74
+ };
75
+ export default useIndicatorAnimation;
@@ -29,18 +29,23 @@ declare const HeaderTabItem: import("@emotion/native").StyledComponent<Animated.
29
29
  as?: React.ElementType;
30
30
  } & {
31
31
  isFirstItem?: boolean;
32
+ themeVariant?: "underlined" | "highlighted";
32
33
  }, {}, {}>;
33
- declare const HeaderTabItemOutlineWrapper: import("@emotion/native").StyledComponent<import("react-native").ViewProps & {
34
+ declare const HeaderTabPillLeft: import("@emotion/native").StyledComponent<Animated.AnimatedProps<import("react-native").ViewProps & import("react").RefAttributes<View>> & {
34
35
  theme?: import("@emotion/react").Theme;
35
36
  as?: React.ElementType;
36
- }, {}, {
37
- ref?: import("react").Ref<View> | undefined;
38
- }>;
39
- declare const HeaderTabItemOutline: import("@emotion/native").StyledComponent<Animated.AnimatedProps<import("react-native").ViewProps & import("react").RefAttributes<View>> & {
37
+ }, {}, {}>;
38
+ declare const HeaderTabPillBody: import("@emotion/native").StyledComponent<Animated.AnimatedProps<import("react-native").ViewProps & import("react").RefAttributes<View>> & {
39
+ theme?: import("@emotion/react").Theme;
40
+ as?: React.ElementType;
41
+ }, {}, {}>;
42
+ declare const HeaderTabPillRight: import("@emotion/native").StyledComponent<Animated.AnimatedProps<import("react-native").ViewProps & import("react").RefAttributes<View>> & {
43
+ theme?: import("@emotion/react").Theme;
44
+ as?: React.ElementType;
45
+ }, {}, {}>;
46
+ declare const HeaderTabItemActiveBorder: import("@emotion/native").StyledComponent<Animated.AnimatedProps<import("react-native").ViewProps & import("react").RefAttributes<View>> & {
40
47
  theme?: import("@emotion/react").Theme;
41
48
  as?: React.ElementType;
42
- } & {
43
- themeActive: boolean;
44
49
  }, {}, {}>;
45
50
  declare const HeaderTabItemWrapper: import("@emotion/native").StyledComponent<import("react-native").ViewProps & {
46
51
  theme?: import("@emotion/react").Theme;
@@ -52,4 +57,4 @@ declare const HeaderTabItemIndicator: import("@emotion/native").StyledComponent<
52
57
  theme?: import("@emotion/react").Theme;
53
58
  as?: React.ElementType;
54
59
  }, {}, {}>;
55
- export { HeaderTabItem, TabScreen, TabContainer, HeaderTabWrapper, HeaderTabItemOutlineWrapper, HeaderTabItemOutline, HeaderTabItemWrapper, HeaderTabItemIndicator, };
60
+ export { HeaderTabItem, TabScreen, TabContainer, HeaderTabWrapper, HeaderTabPillLeft, HeaderTabPillBody, HeaderTabPillRight, HeaderTabItemActiveBorder, HeaderTabItemWrapper, HeaderTabItemIndicator, };
@@ -14,6 +14,8 @@ export type TabType = {
14
14
  component: ReactNode;
15
15
  testID?: string;
16
16
  badge?: BadgeConfigType;
17
+ /** Whether the tab is disabled. Disabled tabs cannot be pressed. */
18
+ disabled?: boolean;
17
19
  };
18
20
  export interface TabsHeaderProps {
19
21
  tabs: TabType[];
@@ -80,7 +82,7 @@ export interface TabsProps extends ViewProps {
80
82
  declare const _default: (({ onTabPress, selectedTabKey, tabs, containerStyle, barStyle, lazy, lazyPreloadDistance, swipeEnabled, testID: componentTestID, header, }: TabsProps) => ReactElement) & {
81
83
  Header: ({ tabs, selectedTabKey, onTabPress, barStyle, insets, componentTestID, tabsWidth, setTabsWidth, positionAnimatedValue, scrollOffsetAnimatedValue, }: TabsHeaderProps) => React.JSX.Element;
82
84
  Scroll: ({ onTabPress, selectedTabKey, tabs, containerStyle, barStyle, lazy, lazyPreloadDistance, swipeEnabled, testID: componentTestID, variant, header, }: import("./ScrollableTabs").ScrollableTabProps) => React.JSX.Element;
83
- ScrollHeader: ({ onTabPress, selectedIndex, tabs, barStyle, testID, insets, variant, }: import("./ScrollableTabsHeader/ScrollableTabsHeader").ScrollableTabHeaderProps) => React.JSX.Element;
85
+ ScrollHeader: ({ onTabPress, selectedIndex: rawSelectedIndex, tabs, barStyle, testID, insets, variant, }: import("./ScrollableTabsHeader/ScrollableTabsHeader").ScrollableTabHeaderProps) => React.JSX.Element;
84
86
  useIsFocused: () => boolean | undefined;
85
87
  };
86
88
  export default _default;
@@ -2,6 +2,8 @@ import type { GlobalTheme } from '../global';
2
2
  declare const getTabsTheme: (theme: GlobalTheme) => {
3
3
  borderWidths: {
4
4
  headerBottom: number;
5
+ highlightedHeaderBottom: number;
6
+ highlightedActiveBorder: number;
5
7
  };
6
8
  colors: {
7
9
  active: string;
@@ -11,6 +13,10 @@ declare const getTabsTheme: (theme: GlobalTheme) => {
11
13
  indicator: string;
12
14
  text: string;
13
15
  headerBackground: string;
16
+ highlightedActiveText: string;
17
+ highlightedActiveBorder: string;
18
+ highlightedActiveBackground: string;
19
+ highlightedDisabledText: string;
14
20
  };
15
21
  space: {
16
22
  flatListHorizontalPadding: number;
@@ -18,11 +24,12 @@ declare const getTabsTheme: (theme: GlobalTheme) => {
18
24
  itemVerticalPadding: number;
19
25
  itemMargin: number;
20
26
  outlineHorizontalPadding: number;
21
- outlineVerticalPadding: number;
22
27
  tabIndicatorBottom: number;
28
+ highlightedItemMargin: number;
29
+ highlightedBarTopPadding: number;
23
30
  };
24
31
  radii: {
25
- outline: number;
32
+ highlightedOutline: number;
26
33
  };
27
34
  sizes: {
28
35
  indicator: number;
@@ -1,45 +0,0 @@
1
- import React from 'react';
2
- import { Animated, Platform } from 'react-native';
3
- import { useAnimatedValueArray } from '../../utils';
4
-
5
- const useInitHighlightedAnimation = ({
6
- selectedIndex = 0,
7
- tabsLength,
8
- variant,
9
- }: {
10
- selectedIndex?: number;
11
- tabsLength: number;
12
- variant: 'underlined' | 'highlighted';
13
- }) => {
14
- const tabsAnims = useAnimatedValueArray(
15
- Array.from({ length: tabsLength }).map((_, i) =>
16
- i === selectedIndex ? 1 : 0
17
- )
18
- );
19
-
20
- React.useEffect(() => {
21
- if (variant !== 'highlighted') {
22
- return;
23
- }
24
-
25
- const animation = Animated.parallel([
26
- ...Array.from({ length: tabsLength }).map((_, i) =>
27
- Animated.timing(tabsAnims[i], {
28
- toValue: i === selectedIndex ? 1 : 0,
29
- duration: 150,
30
- useNativeDriver: Platform.OS !== 'web',
31
- })
32
- ),
33
- ]);
34
-
35
- animation.start();
36
-
37
- return () => {
38
- animation.stop();
39
- };
40
- }, [selectedIndex]);
41
-
42
- return { tabsAnims };
43
- };
44
-
45
- export default useInitHighlightedAnimation;
@@ -1,9 +0,0 @@
1
- import { Animated } from 'react-native';
2
- declare const useInitHighlightedAnimation: ({ selectedIndex, tabsLength, variant, }: {
3
- selectedIndex?: number;
4
- tabsLength: number;
5
- variant: "underlined" | "highlighted";
6
- }) => {
7
- tabsAnims: Animated.Value[];
8
- };
9
- export default useInitHighlightedAnimation;