How can I re-style the search text color? - react-select

I am using the react-select v2 component and am having difficulty restyling the input text for the search portion of the component.
Here is the component itself:
<AsyncSelect
value={this.props.value}
onChange={this.props.onChange}
isSearchable
options={
this.state.options
}
onInputChange={this.onInputChange}
styles={styleSheet}
/>
Here is the stylesheet:
const styleSheet = {
input: (base, state) => ({
...base,
color: "red"
}),
};
This seems to have no effect on the text color, though I'm unsure as to why. Any insight would be greatly appreciated. Thanks.
Edit:
I can see in components/Input.js that there is no support for "color":
const inputStyle = isHidden => ({
background: 0,
border: 0,
fontSize: 'inherit',
opacity: isHidden ? 0 : 1,
outline: 0,
padding: 0,
});
In which case would this need to be a feature request, or is there some alternate way of applying this?

const styleSheet = {
input: (base, state) => ({
...base,
'[type="text"]': {
fontFamily: 'Helvetica, sans-serif !important',
fontSize: 13,
fontWeight: 900,
color: 'green'
}
})
};
And then later in your render method:
<Select styles={styleSheet} />

I had to add the CSS rule !important because without that the code doesn't work for me.
const customStyles = {
input: (styles) => ({
...styles,
'[type="text"]': {
color: 'white !important'
}
})
};

Related

Error in Reanimated "tried to synchronously call function res from a different thread "

Have some gesture handlers that work fine in the browser but I am getting this error on iOS in my onEnd callback in the useAnimatedGestureHandler hook.
Here is all the code related to the gesture I am trying to add
const headerHeight = useSharedValue(176)
const outerStyle = useAnimatedStyle(() => ({
minHeight: 176,
maxHeight: 416,
height: headerHeight.value,
borderBottomLeftRadius: 20,
borderBottomRightRadius: 20,
position: 'relative',
overflow: 'visible',
zIndex: 502,
}))
const innerStyle = useAnimatedStyle(() => ({
overflow: 'hidden',
height: headerHeight.value,
minHeight: 176,
maxHeight: 416,
borderBottomLeftRadius: 20,
borderBottomRightRadius: 20,
}))
const resizeHeaderHeight = useAnimatedGestureHandler({
onStart: () => {},
onActive: (event) => {
headerHeight.value = event.absoluteY
},
onEnd: () => {
if(headerHeight.value < 305) {
headerHeight.value = withTiming(176, {
duration: 500,
})
setHeaderExpanded(false)
} else {
headerHeight.value = withTiming(416, {
duration: 500,
})
setHeaderExpanded(true)
}
},
})
return <>
<PanGestureHandler onGestureEvent={resizeHeaderHeight}>
<Animated.View style={outerStyle}>
<Animated.View style={innerStyle}>
<HeaderComponent
expandable={true}
hideContentCollapsed={false}
onClickExpand={() => {
// setHeaderExpanded(!headerExpanded)
}}
onClickTitle={openMonthPicker}
>{{
title: <Title />,
content: <HeaderCalendar />,
buttons: [
<RefreshButton key='refresh' />,
<AssignmentOffersButton key='assignment-offers' navigation={navigation} />,
<FiltersButton key='filters' navigation={navigation} />,
],
}}</HeaderComponent>
</Animated.View>
<ExpandButton isExpanded={headerExpanded} onClick={()=> {}} />
</Animated.View>
</PanGestureHandler>
{headerExpanded && <Overlay onClick={() => {
setHeaderExpanded(!headerExpanded)
}} />}
</>
}
export default observer(Header)
Have tried defining the onEnd as a 'worklet' and using the runOnJs function suggested to solve this but I am not sure I am doing it correctly since I still have the error every time the onEnd runs.
I'm sorry for the late response.
I think that, as you have already mentioned, the problem lies in not using runOnJS.
Basically onStart, onActive and onEnd are full-fledged worklets, i.e. javascript functions that will be executed on the UI Thread.
Consequently if you have functions that can only be executed on the javascript thread and need to be launched from a worklet it must always be specified with runOnJS.
To be more concrete, in the onEnd function you should wrap the setHeaderExpanded function like this:
runOnJS(setHeaderExpanded)(true) // the boolean value you want to use.

react native top tab bar navigator: indicator width to match text

I have three tabs in a top tab bar navigation with different width text. Is it possible to make the indicator width match the text? On a similar note, how can I make the tabs match the width of the text too without making it display weird. I've tried width auto but it doesn't stay center.
This is how it looks with auto width:
<Tab.Navigator
initialRouteName="Open"
tabBarOptions={{
style: {
backgroundColor: "white",
paddingTop: 20,
paddingHorizontal: 25
},
indicatorStyle: {
borderBottomColor: colorScheme.teal,
borderBottomWidth: 2,
width: '30%',
left:"9%"
},
tabStyle : {
justifyContent: "center",
width: tabBarWidth/3,
}
}}
>
<Tab.Screen
name="Open"
component={WriterRequestScreen}
initialParams={{ screen: 'Open' }}
options={{
tabBarLabel: ({focused}) => <Text style = {{fontSize: 18, fontWeight: 'bold', color: focused? colorScheme.teal : colorScheme.grey}}> Open </Text>,
}}
/>
<Tab.Screen
name="In Progress"
component={WriterRequestScreen}
initialParams={{ screen: 'InProgress' }}
options={{
tabBarLabel: ({focused}) => <Text style = {{fontSize: 18, fontWeight: 'bold', color: focused? colorScheme.teal : colorScheme.grey}}> In Progress </Text>}}
/>
<Tab.Screen
name="Completed"
component={WriterRequestScreen}
initialParams={{ screen: 'Completed' }}
options={{ tabBarLabel: ({focused}) => <Text style = {{fontSize: 18, fontWeight: 'bold', color: focused? colorScheme.teal : colorScheme.grey}}> Completed </Text>}}
/>
</Tab.Navigator>
I also needed to make the indicator fit the text size, a dynamic width for the labels, and a scrollable top bar because of long labels. The result looks like this:
tab bar with dynamic indicator width
If you don't care about the indicator width fitting the labels, you can simply use screenOptions.tabBarScrollEnabled: true in combination with width: "auto" in screenOptions.tabBarIndicatorStyle.
Otherwise, you'll need to make your own tab bar component and pass it to the tabBar property of your <Tab.Navigator>. I used a ScrollView but if you have only a few tabs with short labels, a View would be more simple. Here is the Typescript code for this custom TabBar component:
import { MaterialTopTabBarProps } from "#react-navigation/material-top-tabs";
import { useEffect, useRef, useState } from "react";
import {
Animated,
Dimensions,
View,
TouchableOpacity,
StyleSheet,
ScrollView,
I18nManager,
LayoutChangeEvent,
} from "react-native";
const screenWidth = Dimensions.get("window").width;
const DISTANCE_BETWEEN_TABS = 20;
const TabBar = ({
state,
descriptors,
navigation,
position,
}: MaterialTopTabBarProps): JSX.Element => {
const [widths, setWidths] = useState<(number | undefined)[]>([]);
const scrollViewRef = useRef<ScrollView>(null);
const transform = [];
const inputRange = state.routes.map((_, i) => i);
// keep a ref to easily scroll the tab bar to the focused label
const outputRangeRef = useRef<number[]>([]);
const getTranslateX = (
position: Animated.AnimatedInterpolation,
routes: never[],
widths: number[]
) => {
const outputRange = routes.reduce((acc, _, i: number) => {
if (i === 0) return [DISTANCE_BETWEEN_TABS / 2 + widths[0] / 2];
return [
...acc,
acc[i - 1] + widths[i - 1] / 2 + widths[i] / 2 + DISTANCE_BETWEEN_TABS,
];
}, [] as number[]);
outputRangeRef.current = outputRange;
const translateX = position.interpolate({
inputRange,
outputRange,
extrapolate: "clamp",
});
return Animated.multiply(translateX, I18nManager.isRTL ? -1 : 1);
};
// compute translateX and scaleX because we cannot animate width directly
if (
state.routes.length > 1 &&
widths.length === state.routes.length &&
!widths.includes(undefined)
) {
const translateX = getTranslateX(
position,
state.routes as never[],
widths as number[]
);
transform.push({
translateX,
});
const outputRange = inputRange.map((_, i) => widths[i]) as number[];
transform.push({
scaleX:
state.routes.length > 1
? position.interpolate({
inputRange,
outputRange,
extrapolate: "clamp",
})
: outputRange[0],
});
}
// scrolls to the active tab label when a new tab is focused
useEffect(() => {
if (
state.routes.length > 1 &&
widths.length === state.routes.length &&
!widths.includes(undefined)
) {
if (state.index === 0) {
scrollViewRef.current?.scrollTo({
x: 0,
});
} else {
// keep the focused label at the center of the screen
scrollViewRef.current?.scrollTo({
x: (outputRangeRef.current[state.index] as number) - screenWidth / 2,
});
}
}
}, [state.index, state.routes.length, widths]);
// get the label widths on mount
const onLayout = (event: LayoutChangeEvent, index: number) => {
const { width } = event.nativeEvent.layout;
const newWidths = [...widths];
newWidths[index] = width - DISTANCE_BETWEEN_TABS;
setWidths(newWidths);
};
// basic labels as suggested by react navigation
const labels = state.routes.map((route, index) => {
const { options } = descriptors[route.key];
const label = route.name;
const isFocused = state.index === index;
const onPress = () => {
const event = navigation.emit({
type: "tabPress",
target: route.key,
canPreventDefault: true,
});
if (!isFocused && !event.defaultPrevented) {
// The `merge: true` option makes sure that the params inside the tab screen are preserved
// eslint-disable-next-line
// #ts-ignore
navigation.navigate({ name: route.name, merge: true });
}
};
const inputRange = state.routes.map((_, i) => i);
const opacity = position.interpolate({
inputRange,
outputRange: inputRange.map((i) => (i === index ? 1 : 0.5)),
});
return (
<TouchableOpacity
key={route.key}
accessibilityRole="button"
accessibilityState={isFocused ? { selected: true } : {}}
accessibilityLabel={options.tabBarAccessibilityLabel}
onPress={onPress}
style={styles.button}
>
<View
onLayout={(event) => onLayout(event, index)}
style={styles.buttonContainer}
>
<Animated.Text style={[styles.text, { opacity }]}>
{label}
</Animated.Text>
</View>
</TouchableOpacity>
);
});
return (
<View style={styles.contentContainer}>
<Animated.ScrollView
horizontal
ref={scrollViewRef}
showsHorizontalScrollIndicator={false}
style={styles.container}
>
{labels}
<Animated.View style={[styles.indicator, { transform }]} />
</Animated.ScrollView>
</View>
);
};
const styles = StyleSheet.create({
button: {
alignItems: "center",
justifyContent: "center",
},
buttonContainer: {
paddingHorizontal: DISTANCE_BETWEEN_TABS / 2,
},
container: {
backgroundColor: "black",
flexDirection: "row",
height: 34,
},
contentContainer: {
height: 34,
marginTop: 30,
},
indicator: {
backgroundColor: "white",
bottom: 0,
height: 3,
left: 0,
position: "absolute",
right: 0,
// this must be 1 for the scaleX animation to work properly
width: 1,
},
text: {
color: "white",
fontSize: 14,
textAlign: "center",
},
});
export default TabBar;
I managed to make it work with a mix of:
react navigation example
react-native-tab-view original indicator
jsindos answer
Please let me know if you find a more convenient solution.
You have to add width:auto to tabStyle to make tab width flexible.
Then inside each tabBarLabel <Text> component add style textAlign: "center" and width: YOUR_WIDTH .
YOUR_WIDTH can be different for each tab and can be your text.length * 10 (if you want to make it depended on your text length) or get screen width from Dimensions and divide it by any other number to make it equal widths in screen. Example:
const win = Dimensions.get('window');
...
bigTab: {
fontFamily: "Mulish-Bold",
fontSize: 11,
color: "#fff",
textAlign: "center",
width: win.width/2 - 40
},
smallTab: {
fontFamily: "Mulish-Bold",
fontSize: 11,
color: "#fff",
textAlign: "center",
width: win.width / 5 + 10
}
Remove width from indicatorStyle and use flex:1
indicatorStyle: { borderBottomColor: colorScheme.teal,
borderBottomWidth: 2,
flex:1,
left:"9%"
},
I've achieved this using some hacks around onLayout, please note I've made this with the assumptions of two tabs, and that the second tabs width is greater than the first. It probably will need tweaking for other use cases.
import React, { useEffect, useState } from 'react'
import { createMaterialTopTabNavigator } from '#react-navigation/material-top-tabs'
import { Animated, Text, TouchableOpacity, View } from 'react-native'
const Stack = createMaterialTopTabNavigator()
const DISTANCE_BETWEEN_TABS = 25
function MyTabBar ({ state, descriptors, navigation, position }) {
const [widths, setWidths] = useState([])
const [transform, setTransform] = useState([])
const inputRange = state.routes.map((_, i) => i)
useEffect(() => {
if (widths.length === 2) {
const [startingWidth, transitionWidth] = widths
const translateX = position.interpolate({
inputRange,
outputRange: [0, startingWidth + DISTANCE_BETWEEN_TABS + (transitionWidth - startingWidth) / 2]
})
const scaleX = position.interpolate({
inputRange,
outputRange: [1, transitionWidth / startingWidth]
})
setTransform([{ translateX }, { scaleX }])
}
}, [widths])
return (
<View style={{ flexDirection: 'row' }}>
{state.routes.map((route, index) => {
const { options } = descriptors[route.key]
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name
const isFocused = state.index === index
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true
})
if (!isFocused && !event.defaultPrevented) {
// The `merge: true` option makes sure that the params inside the tab screen are preserved
navigation.navigate({ name: route.name, merge: true })
}
}
const onLayout = event => {
const { width } = event.nativeEvent.layout
setWidths([...widths, width])
}
const opacity = position.interpolate({
inputRange,
outputRange: inputRange.map(i => (i === index ? 0.87 : 0.53))
})
return (
<TouchableOpacity
key={index}
accessibilityRole='button'
accessibilityState={isFocused ? { selected: true } : {}}
accessibilityLabel={options.tabBarAccessibilityLabel}
testID={options.tabBarTestID}
onPress={onPress}
style={{ marginRight: DISTANCE_BETWEEN_TABS }}
>
<Animated.Text
onLayout={onLayout}
style={{
opacity,
color: '#000',
fontSize: 18,
fontFamily: 'OpenSans-Bold',
marginBottom: 15
}}
>
{label}
</Animated.Text>
</TouchableOpacity>
)
})}
<View style={{ backgroundColor: '#DDD', height: 2, position: 'absolute', bottom: 0, left: 0, right: 0 }} />
<Animated.View style={{ position: 'absolute', bottom: 0, left: 0, width: widths.length ? widths[0] : 0, backgroundColor: '#222', height: 2, transform }} />
</View>
)
}
export default () => {
return (
<>
<Stack.Navigator tabBar={props => <MyTabBar {...props} />} style={{ paddingHorizontal: 25 }}>
<Stack.Screen name='Orders' component={() => <Text>A</Text>} />
<Stack.Screen name='Reviews' component={() => <Text>B</Text>} />
</Stack.Navigator>
</>
)
}
Update:
If the menu names are static, it is probably a more robust solution to hard code the widths inside of widths, although this is a little more costly to maintain.
Resources:
https://reactnavigation.org/docs/material-top-tab-navigator/#tabbar
https://github.com/facebook/react-native/issues/13107
In the screenOptions, add following props for
tabBarScrollEnabled: true
tabBarItemStyle: {{width: "auto", minWidht: "100"}}
minWidth is just to keep the design consistent.
Please note, I am using react-navigation 6.x and Camille Hg answer was really helpful.
I had the same issue and I was finally able to make the indicator takes exactly the text size.
I don't know in which version this was possible .. but apparently you can add a custom indicator component (beside the ability to add a custom tabBar component)
when creating the TopTabNavigator it is important to add the properties as described in the code under
// assuming that you want to add paddingHorizontal: 10 for each item!
const TAB_BAR_ITEM_PADDING = 10;
const Tab = createMaterialTopTabNavigator();
function TopTabNavigator() {
return (
<Tab.Navigator
.....
... .
screenOptions={{
....
...
tabBarItemStyle: {
// these properties are important for this method to work !!
width: "auto",
marginHorizontal: 0, // this is to make sure that the spacing of the item comes only from the paddingHorizontal!.
paddingHorizontal: TAB_BAR_ITEM_PADDING, // the desired padding for the item .. stored in a constant to be passed in the custom Indicator
},
tabBarIndicator: props => {
return (
<CustomTabBarIndicator
// the default props
getTabWidth={props.getTabWidth}
jumpTo={props.jumpTo}
layout={props.layout}
navigationState={props.state}
position={props.position}
width={props.width}
style={{
left: TAB_BAR_ITEM_PADDING,
backgroundColor: Colors.primary,
}}
// this is an additional property we will need to make the indicator exactly
tabBarItemPadding={TAB_BAR_ITEM_PADDING}
/>
);
},
}}
>
<Tab.Screen .... />
<Tab.Screen ..... />
<Tab.Screen .... />
</Tab.Navigator>
);
}
now for the CustomTabBarIndIndicator component we simply go to the official github repository for react-native-tab-view and then go to TabBarIndicator.tsx and copy the component over in a file called CustomTabBarIndicator "just to be consistence with the example, but you can call it what ever you want", and don't forget to add the additional property to the Props type for tabBarItemPadding "if you are using typescript"
and now make this small change to the line that is highlighted in the image
change:
const outputRange = inputRange.map(getTabWidth);
to be:
const outputRange = inputRange.map(x => {
// this part is customized to get the indicator to be the same width like the label
// subtract the tabBarItemPadding from the tabWidth
// so that you indicator will be exactly the same size like the label text
return getTabWidth(x) - this.props.tabBarItemPadding * 2;
});
and that was it :)
P.S. I added the image because I didn't know how to exactly describe where to make the change
and if you don't want the typescript .. jsut remove all the types from the code and you are good to go :)

How can I change time text color?

I use react-native-gifted-chat for my chat. I want to change time font color. I changed it as the doc said, but its not changing. I want both time colors are black.
using "react-native-gifted-chat": "^0.16.1"
const renderTime = (props) => {
return (
<Time
{...props}
textStyle={{
left: {
color: 'black',
},
right: {
color: 'black',
},
}}
/>
);
};
Looks like you need to pass timeTextStyle instead of textStyle.
Try:
const renderTime = (props) => {
return (
<Time
{...props}
timeTextStyle={{
left: {
color: 'black',
},
right: {
color: 'black',
},
}}
/>
);
};

Changing toolbar color when in searchable mode (react-native-material-ui)

would like to know is there a way to change the toolbar color when it is in searchable ? I have set the toolbar color by "style={{ container: {backgroundColor: theme.colors.primary}}}", but when click on the search icon, the search bar remains as white...
<Toolbar
style={{ container: {backgroundColor: theme.colors.primary}}}
centerElement={<Text style={customStyles.title}>Select id</Text>}
searchable={{
autoFocus: true,
placeholder: "Keyword",
onChangeText: text => searchFilterFunction(text),
onSearchCloseRequested: () => setName(nameList),
}}
/>
Noticed that the background color of the search bar (when active) is taken from theme as the source state below:
renderAnimatedBackgrounds = styles => {
const { theme } = this.props;
const {
diameter,
bgPosition,
radius,
defaultScaleValue,
searchScaleValue,
order,
} = this.state;
const bgStyle = {
position: 'absolute',
top: -radius,
width: diameter,
height: diameter,
borderRadius: radius,
};
const { toolbarSearchActive } = theme;
const container = StyleSheet.flatten(styles.container);
const searchActive = StyleSheet.flatten(toolbarSearchActive.container);
const bgSearch = (
<Animated.View
key="searchBackground"
style={[
bgStyle,
{
left: bgPosition,
backgroundColor: searchActive.backgroundColor,
transform: [{ scale: searchScaleValue }],
},
]}
/>
);
However, I have tried to add props "theme= {{ toolbarSearchActive: {container: {backgroundColor: theme.colors.primary }}}}" to toolbar but not taking effect.

Handling focus in react native with multiple input

I am looking to only toggle the focus state on one of the inputs instead of both when onFocus is fired.
With both inputs having the 'onFocus' and 'onBlur' events I was expecting the state to update on only the current element that is focussed.
Should I be using refs instead of state for this sort of event handling?
class SignInScreen extends Component {
state = {
isFocused: false,
style: {
inputContainer: styles.input
}
}
onFocus = () => {
if(!this.state.isFocused) {
this.setState({
isFocused: true,
style: {
inputContainer: styles.inputDifferent
}
})
}
}
onBlur = () => {
if(this.state.isFocused) {
this.setState({
isFocused: false,
style: {
inputContainer: styles.input
}
})
}
}
render() {
return (
<Input
containerStyle={[styles.input, this.state.style.inputContainer]}
onFocus={this.onFocus}
onBlur={this.onBlur}
/>
<Input
containerStyle={[styles.input, this.state.style.inputContainer]}
onFocus={this.onFocus}
onBlur={this.onBlur}
/>
)
}
}
const styles = StyleSheet.create({
input: {
borderWidth: 1,
borderColor: 'white,
marginBottom: 16,
maxWidth: '90%',
marginLeft: 'auto',
marginRight: 'auto'
},
inputDifferent: {
backgroundColor: 'gray',
}
});
Modify your code Like This:
Edited:
class SignInScreen extends Component {
state = {
isFocused: false,
firstLoad: true
}
onFocus = () => {
this.setState({
isFocused: !this.state.isFocused,
firstLoad: false
})
}
render() {
const {isFocused} = this.state
return (
<View>
<Input
containerStyle={isFocused || firstLoad ? styles.input : styles.inputDifferent}
onFocus={this.onFocus}
/>
<Input
containerStyle={!isFocused || firstLoad ? styles.input : styles.inputDifferent}
onFocus={this.onFocus}
/>
</View>
)
}
}
const styles = StyleSheet.create({
input: {
borderWidth: 1,
borderColor: 'white',
marginBottom: 16,
maxWidth: '90%',
marginLeft: 'auto',
marginRight: 'auto'
},
inputDifferent: {
backgroundColor: 'gray',
}
});
its unnecessary to include styles on state. it is better to only use this.state.isFocus to make code simplier. just used this to make a condition on what style the inputs will be using.
you can also disregard onBlur. because all we need on the conditions is isFocus.
the key factor here is the exclamation symbol that will make the state toggle.
Edited: firstLoad state added to make the style of both inputs the same on load. that will be used on the condition inside input styles.
note: it can only work with two inputs