react-native-tab-view icon indicating copy to clipboard operation
react-native-tab-view copied to clipboard

`renderLabel` and `renderIcon` wrap or cut off text/icon if unfocused text/icon is smaller than focused text

Open Ceda-EI opened this issue 4 years ago • 10 comments

Current behaviour

If the Text component returned by renderLabel when focused is false is smaller (in width) than the Text returned when focused is true, then the Text component for focused tab gets wrapped.

Similar issue exists with renderIcon. If the Icon returned by renderIcon when focused is false is smaller (in width and height) than the Icon returned when focused is true, then the Icon for focused tab gets cut off.

Neither of the issues happen when the focused one is smaller than the unfocused one. Infact, that causes another issue where the box designated for focused version is larger than it needs and it gets offcenter.

It seems that in both the cases, the text is drawn within a box of width of the unfocused component.

Expected behaviour

The icon and text should get enough space to be drawn.

Code sample

import * as React from 'react';
import { View, StyleSheet, Dimensions, Text } from 'react-native';
import { TabView, TabBar, SceneMap } from 'react-native-tab-view';

const FirstRoute = () => (
  <View style={[styles.scene, { backgroundColor: '#ff4081' }]} />
);

const SecondRoute = () => (
  <View style={[styles.scene, { backgroundColor: '#673ab7' }]} />
);
function renderLabel({ route, focused, color }) {
  return <Text style={{ color }}>{focused ? route.title : ' '}</Text>;
}
const initialLayout = { width: Dimensions.get('window').width };

export default function App() {
  const [index, setIndex] = React.useState(0);
  const [routes] = React.useState([
    { key: 'first', title: 'First' },
    { key: 'second', title: 'Second' },
  ]);

  const renderScene = SceneMap({
    first: FirstRoute,
    second: SecondRoute,
  });

  return (
    <TabView
      navigationState={{ index, routes }}
      renderScene={renderScene}
      onIndexChange={setIndex}
      initialLayout={initialLayout}
      renderTabBar={props => <TabBar {...props} renderLabel={renderLabel} />}
    />
  );
}

const styles = StyleSheet.create({
  scene: {
    flex: 1,
  },
});

Above code: https://snack.expo.io/HySYUg6UL

Changing renderLabel to the following code will display the offcenter issue

function renderLabel({ route, focused, color }) {
  return <Text style={{ color }}>{focused ? 'x' : 'a bit long text'}</Text>;
}

Code with above modification: https://snack.expo.io/B1qdDg6UU

Screenshots (if applicable)

Wrapping issue

Wrapping Issue

Offcenter issue

Offcenter Issue

What have you tried

My current workaround for hiding labels on inactive tabs (I only want to show icons) is to set text color as color of tab bar. this isn't exactly perfect since the text becomes partially visible during a tap and isn't useful in all cases (e.g. if you want to show an abbrevation in non focused cases).

Your Environment

software version
android 9.0
react-native 3.2.1
react-native-tab-view 2.13.0
react-native-gesture-handler 1.6.0
react-native-reanimated 1.7.0
node 13.8.0
yarn 1.22.0

Ceda-EI avatar Mar 28 '20 16:03 Ceda-EI

me too

whereiscode avatar Apr 21 '20 02:04 whereiscode

I have a very similar issue. My render label function bolds focused tab. This causes an issue on android where the last character gets wrapped to the the next line when the tab is focused.

shockoe-hatch avatar Apr 22 '20 15:04 shockoe-hatch

@shockoe-hatch that is definitely the same issue! Making the text bold will increase the width of the text so since width of focused text will be larger than unfocused text, it will wrap.

Ceda-EI avatar Apr 22 '20 15:04 Ceda-EI

I'm still waiting for this issue to be fixed.

randomdipesh avatar May 05 '20 03:05 randomdipesh

For anyone looking for a quick hack to work around this issue, I have setup my renderLabel to render two Text components, one is the "largest" size and is essentially hidden by being the same color as the background color and the "smallest" is then positioned absolute over it.

            renderLabel={({ route, focused }) => (
              <View>
                <Text style={[...styles.tabBarLabelActive, { color: 'white' }]}>
                  {route.title?.toUpperCase()}
                </Text>
                <Text
                  style={[
                    ...(focused
                      ? styles.tabBarLabelActive
                      : styles.tabBarLabel),
                    { position: 'absolute' },
                  ]}
                >
                  {route.title?.toUpperCase()}
                </Text>
              </View>
            )}

MPiccinato avatar May 11 '20 15:05 MPiccinato

@MPiccinato this worked for me! Thanks!

tcK1 avatar Jul 07 '20 14:07 tcK1

So I found a better solution, leaving the bold text rendered on the screen from the start, along with the main text, however transparent and with a small size. The problem I see is that the lib does not render again when changing the font size, which causes a mismatch with the layout, some memoization or pure components.

My solution:

renderLabel={({route, focused}) => {
  return (
    <View>
      <Text style={{fontWeight: focused ? 'bold' : 'normal'}}>
        {route.title}
      </Text>
      <View style={{height: 1}}>
        <Text
          style={{
            fontWeight: 'bold',
            color: 'transparent',
          }}>
          {route.title}
        </Text>
      </View>
    </View>
  );
}}

dnlsilva avatar Oct 26 '20 17:10 dnlsilva

Hello, is there any update to the issue? it's still present in v3.1.1

anceque avatar Oct 25 '21 14:10 anceque

borderWidth: 1, borderColor: "transparent", Try to add this to text style. It's working for me.

florencelim27 avatar Dec 03 '21 02:12 florencelim27

To fix this issue, custom the width of the possible text :

<TabView
    navigationState={{index, routes}}
    renderTabBar={props => (
      <TabBar
        {...props}
        indicatorStyle={{backgroundColor: colors.primary,height:1,bottom:-1}}
        style={{
          backgroundColor: 'white',
          elevation: 0,
          borderBottomWidth: 1,
          borderColor: colors.gray[200],
          paddingVertical:spacing.tiny,
        }}
        inactiveColor={colors.gray[800]}
        activeColor={colors.primary}
        renderLabel={({route, focused, color}) => (
          <Text
            color={color}
            // make the width for the text fixed
            **style={{width:100,textAlign:"center"}}**
            variant={focused ? 'largeSemiBold' : 'mediumRegular'}
            >
            {route.title}
          </Text>
        )}
      />)}
    renderScene={renderScene}
    onIndexChange={setIndex}
    initialLayout={{width: layout.width
    }}
  />

Mohammad-Erhim avatar Aug 29 '22 07:08 Mohammad-Erhim