-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
refactor: simplify Tabs component #22284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| // Third party dependencies. | ||
| import React, { useRef, useCallback } from 'react'; | ||
| import { Pressable, View } from 'react-native'; | ||
| import React, { useRef, useEffect } from 'react'; | ||
| import { Pressable, Animated } from 'react-native'; | ||
|
|
||
| // External dependencies. | ||
| import { useTailwind } from '@metamask/design-system-twrnc-preset'; | ||
|
|
@@ -9,6 +9,7 @@ import { | |
| TextVariant, | ||
| FontWeight, | ||
| } from '@metamask/design-system-react-native'; | ||
| import { AnimationDuration } from '../../../constants/animation.constants'; | ||
|
|
||
| // Internal dependencies. | ||
| import { TabProps } from './Tab.types'; | ||
|
|
@@ -19,67 +20,78 @@ const Tab: React.FC<TabProps> = ({ | |
| isDisabled = false, | ||
| onPress, | ||
| testID, | ||
| onLayout, | ||
| ...pressableProps | ||
| }) => { | ||
| const tw = useTailwind(); | ||
| const viewRef = useRef<View>(null); | ||
| const scaleAnim = useRef(new Animated.Value(isActive ? 1 : 0)).current; | ||
| const isInitialMount = useRef(true); | ||
|
|
||
| const handleOnLayout = useCallback( | ||
| (layoutEvent: Parameters<NonNullable<typeof onLayout>>[0]) => { | ||
| if (onLayout) { | ||
| onLayout(layoutEvent); | ||
| } | ||
| }, | ||
| [onLayout], | ||
| ); | ||
| useEffect(() => { | ||
| // Skip animation on initial mount - just set the value | ||
| if (isInitialMount.current) { | ||
| isInitialMount.current = false; | ||
| scaleAnim.setValue(isActive && !isDisabled ? 1 : 0); | ||
| return; | ||
| } | ||
|
|
||
| // Animate on subsequent changes | ||
| Animated.timing(scaleAnim, { | ||
| toValue: isActive && !isDisabled ? 1 : 0, | ||
| duration: AnimationDuration.Fast, | ||
| useNativeDriver: true, | ||
| }).start(); | ||
| }, [isActive, isDisabled, scaleAnim]); | ||
georgewrmarshall marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+29
to
+43
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handles animation of underline |
||
|
|
||
| return ( | ||
| <View | ||
| ref={viewRef} | ||
| onLayout={handleOnLayout} | ||
| style={tw.style('flex-shrink-0')} | ||
|
Comment on lines
-38
to
-41
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reducing element bloat by removing wrapping View as it's no longer needed |
||
| <Pressable | ||
| style={tw.style( | ||
| 'flex-shrink-0 px-0 py-1 flex-row items-center justify-center relative', | ||
| isDisabled && 'opacity-50', | ||
| )} | ||
| onPress={isDisabled ? undefined : onPress} | ||
| disabled={isDisabled} | ||
| testID={testID} | ||
| {...pressableProps} | ||
| > | ||
| <Pressable | ||
| style={tw.style( | ||
| 'px-0 py-1 flex-row items-center justify-center relative', | ||
| isDisabled && 'opacity-50', | ||
| )} | ||
| onPress={isDisabled ? undefined : onPress} | ||
| disabled={isDisabled} | ||
| testID={testID} | ||
| {...pressableProps} | ||
| {/* Hidden bold text that determines layout size */} | ||
| <Text | ||
| variant={TextVariant.BodyMd} | ||
| fontWeight={FontWeight.Bold} | ||
| numberOfLines={1} | ||
| style={tw.style('opacity-0')} | ||
| > | ||
| {label} | ||
| </Text> | ||
|
|
||
| {/* Visible text positioned absolutely over the hidden text */} | ||
| <Text | ||
| variant={TextVariant.BodyMd} | ||
| fontWeight={ | ||
| isActive && !isDisabled ? FontWeight.Bold : FontWeight.Regular | ||
| } | ||
| twClassName={ | ||
| isDisabled | ||
| ? 'text-muted' | ||
| : isActive | ||
| ? 'text-default' | ||
| : 'text-alternative' | ||
| } | ||
| numberOfLines={1} | ||
| style={tw.style('absolute inset-0 flex items-center justify-center')} | ||
| > | ||
| {/* Hidden bold text that determines layout size */} | ||
| <Text | ||
| variant={TextVariant.BodyMd} | ||
| fontWeight={FontWeight.Bold} | ||
| numberOfLines={1} | ||
| style={tw.style('opacity-0')} | ||
| > | ||
| {label} | ||
| </Text> | ||
| {label} | ||
| </Text> | ||
|
|
||
| {/* Visible text positioned absolutely over the hidden text */} | ||
| <Text | ||
| variant={TextVariant.BodyMd} | ||
| fontWeight={ | ||
| isActive && !isDisabled ? FontWeight.Bold : FontWeight.Regular | ||
| } | ||
| twClassName={ | ||
| isDisabled | ||
| ? 'text-muted' | ||
| : isActive | ||
| ? 'text-default' | ||
| : 'text-alternative' | ||
| } | ||
| numberOfLines={1} | ||
| style={tw.style('absolute inset-0 flex items-center justify-center')} | ||
| > | ||
| {label} | ||
| </Text> | ||
| </Pressable> | ||
| </View> | ||
| {/* Animated underline */} | ||
| <Animated.View | ||
| style={[ | ||
| tw.style('absolute bottom-0 left-0 right-0 h-0.5 bg-icon-default'), | ||
| { | ||
| transform: [{ scaleX: scaleAnim }], | ||
| }, | ||
| ]} | ||
| /> | ||
|
Comment on lines
+85
to
+93
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved underline into the Tab component. A bit of flexibility on the animation style here |
||
| </Pressable> | ||
| ); | ||
| }; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| // Third party dependencies. | ||
| import { PressableProps, LayoutChangeEvent } from 'react-native'; | ||
| import { PressableProps } from 'react-native'; | ||
|
|
||
| /** | ||
| * Tab component props | ||
|
|
@@ -21,8 +21,4 @@ export interface TabProps extends PressableProps { | |
| * Callback when tab is pressed | ||
| */ | ||
| onPress: () => void; | ||
| /** | ||
| * Callback when tab layout changes | ||
| */ | ||
| onLayout?: (event: LayoutChangeEvent) => void; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not used |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer needed