11// Third party dependencies.
2- import React , { useRef , useCallback } from 'react' ;
3- import { Pressable , View } from 'react-native' ;
2+ import React , { useRef , useEffect } from 'react' ;
3+ import { Pressable , Animated } from 'react-native' ;
44
55// External dependencies.
66import { useTailwind } from '@metamask/design-system-twrnc-preset' ;
99 TextVariant ,
1010 FontWeight ,
1111} from '@metamask/design-system-react-native' ;
12+ import { AnimationDuration } from '../../../constants/animation.constants' ;
1213
1314// Internal dependencies.
1415import { TabProps } from './Tab.types' ;
@@ -19,67 +20,69 @@ const Tab: React.FC<TabProps> = ({
1920 isDisabled = false ,
2021 onPress,
2122 testID,
22- onLayout,
2323 ...pressableProps
2424} ) => {
2525 const tw = useTailwind ( ) ;
26- const viewRef = useRef < View > ( null ) ;
26+ const scaleAnim = useRef ( new Animated . Value ( isActive ? 1 : 0 ) ) . current ;
2727
28- const handleOnLayout = useCallback (
29- ( layoutEvent : Parameters < NonNullable < typeof onLayout > > [ 0 ] ) => {
30- if ( onLayout ) {
31- onLayout ( layoutEvent ) ;
32- }
33- } ,
34- [ onLayout ] ,
35- ) ;
28+ useEffect ( ( ) => {
29+ Animated . timing ( scaleAnim , {
30+ toValue : isActive && ! isDisabled ? 1 : 0 ,
31+ duration : AnimationDuration . Fast ,
32+ useNativeDriver : true ,
33+ } ) . start ( ) ;
34+ } , [ isActive , isDisabled , scaleAnim ] ) ;
3635
3736 return (
38- < View
39- ref = { viewRef }
40- onLayout = { handleOnLayout }
41- style = { tw . style ( 'flex-shrink-0' ) }
37+ < Pressable
38+ style = { tw . style (
39+ 'flex-shrink-0 px-0 py-1 flex-row items-center justify-center relative' ,
40+ isDisabled && 'opacity-50' ,
41+ ) }
42+ onPress = { isDisabled ? undefined : onPress }
43+ disabled = { isDisabled }
44+ testID = { testID }
45+ { ...pressableProps }
4246 >
43- < Pressable
44- style = { tw . style (
45- 'px-0 py-1 flex-row items-center justify-center relative' ,
46- isDisabled && 'opacity-50' ,
47- ) }
48- onPress = { isDisabled ? undefined : onPress }
49- disabled = { isDisabled }
50- testID = { testID }
51- { ...pressableProps }
47+ { /* Hidden bold text that determines layout size */ }
48+ < Text
49+ variant = { TextVariant . BodyMd }
50+ fontWeight = { FontWeight . Bold }
51+ numberOfLines = { 1 }
52+ style = { tw . style ( 'opacity-0' ) }
53+ >
54+ { label }
55+ </ Text >
56+
57+ { /* Visible text positioned absolutely over the hidden text */ }
58+ < Text
59+ variant = { TextVariant . BodyMd }
60+ fontWeight = {
61+ isActive && ! isDisabled ? FontWeight . Bold : FontWeight . Regular
62+ }
63+ twClassName = {
64+ isDisabled
65+ ? 'text-muted'
66+ : isActive
67+ ? 'text-default'
68+ : 'text-alternative'
69+ }
70+ numberOfLines = { 1 }
71+ style = { tw . style ( 'absolute inset-0 flex items-center justify-center' ) }
5272 >
53- { /* Hidden bold text that determines layout size */ }
54- < Text
55- variant = { TextVariant . BodyMd }
56- fontWeight = { FontWeight . Bold }
57- numberOfLines = { 1 }
58- style = { tw . style ( 'opacity-0' ) }
59- >
60- { label }
61- </ Text >
73+ { label }
74+ </ Text >
6275
63- { /* Visible text positioned absolutely over the hidden text */ }
64- < Text
65- variant = { TextVariant . BodyMd }
66- fontWeight = {
67- isActive && ! isDisabled ? FontWeight . Bold : FontWeight . Regular
68- }
69- twClassName = {
70- isDisabled
71- ? 'text-muted'
72- : isActive
73- ? 'text-default'
74- : 'text-alternative'
75- }
76- numberOfLines = { 1 }
77- style = { tw . style ( 'absolute inset-0 flex items-center justify-center' ) }
78- >
79- { label }
80- </ Text >
81- </ Pressable >
82- </ View >
76+ { /* Animated underline */ }
77+ < Animated . View
78+ style = { [
79+ tw . style ( 'absolute bottom-0 left-0 right-0 h-0.5 bg-icon-default' ) ,
80+ {
81+ transform : [ { scaleX : scaleAnim } ] ,
82+ } ,
83+ ] }
84+ />
85+ </ Pressable >
8386 ) ;
8487} ;
8588
0 commit comments