How to manage dynamic fill with React Native SVG - react-native

I have some SVGs in my React native .66.1 app, and I've set up react-native-svg and react-native-svg-transformer and have set it up correctly. I can import my SVGs as components, and it all works as expected.
However, I don't know how to dynamically set the fill based on an expression in my app. For instance, I can set the gradient in my SVG, but I can't override the color on the state change.
How do you set it up to track a value, or how to override it?
My SVG:
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient gradientUnits="userSpaceOnUse" x1="12.99" y1="0" x2="12.99" y2="25.92" id="gradient" gradientTransform="matrix(0.707053, -0.707161, 1.001161, 1.001008, -9.106794, 9.237734)">
<stop offset="0" style="stop-color: rgb(161, 70, 183);"/>
<stop offset="1" style="stop-color: rgb(0, 38, 94);"/>
</linearGradient>
</defs>
<path d="M13.91.38a1.3 1.3 0 0 0-1.84 0L.33 12.14c-.4.4-.35.73-.29.89.06.16.25.44.8.44h1.32v10.45a2 2 0 0 0 1.92 2h5.95v-7.08h5.76v7.08h6.1c1.03 0 1.9-.93 1.9-2V13.47h1.36c.54 0 .73-.28.79-.44.06-.16.1-.5-.28-.9L13.9.39Z" fill-rule="evenodd" />
</svg>
My Drawer Screen:
<Drawer.Screen name="Home" options={{ drawerIcon: ({color, size, focused}) => <HomeIcon style={{maxWidth: 28}} fill={focused ? colors.primary.purpleMain : darkMode ? 'white' : 'black'} />}} component={Home} />
What I want:
I want the icon to be the gradient on focused, and either black or white depending on darkMode. Any ideas? I've tried setting the fill in App.tsx, but it doesn't track. This is what I tried: 'url(#gradient)' (gradient is the id of the linearGradient in my svg).
My perfect situation would be not to have to embed the linearGradient in every SVG, but to 'set' it to each solid SVG (just a 135deg purple gradient).
It's worth noting my dynamic color works if I just use regular colors and I omit the fill property inside my SVG. Also - if I use 'currentColor' inside my SVG, the color just remains blue, but I don't even see anywhere that the color blue is called.

i would suggest you to create font (ie: .ttf) format for your icons using https://icomoon.io/, after that replace Icon from GradientIcon adng home page respectively,
Here i am using react-native-vector-icons for icons
working example: Snack Expo
GradientIcon.tsx
import React, { FC } from 'react';
import {
View,
StyleSheet,
StyleProp,
ViewStyle,
ViewProps,
} from 'react-native';
import IonIcon, { IconProps } from 'react-native-vector-icons/Ionicons';
// in bare react native use. react-native-linear-gradient
import { LinearGradient } from 'expo-linear-gradient';
import MaskedView, {
MaskedViewProps,
} from '#react-native-community/masked-view';
/**
* GradientIonIconProps
*/
type GradientIonIconProps = IconProps & {
colors?: string[];
containerStyle?: StyleProp<ViewStyle>;
start?: {
x: number;
y: number;
};
end?: {
x: number;
y: number;
};
};
/**
* GradientIonIcon
*/
const GradientIonIcon: FC<GradientIonIconProps> = (props) => {
const {
children,
containerStyle,
start = { x: 0, y: 0 },
end = { x: 1, y: 0 },
colors = ['#3D7BF7', '#26CCFF'],
...rest
} = props;
return (
<MaskedView
style={containerStyle}
maskElement={
<IonIcon {...rest} style={{ color: '#fff' }} color="#fff" />
}>
<LinearGradient {...{ colors, start, end }}>
<IonIcon {...rest} color="transparent" />
</LinearGradient>
</MaskedView>
);
};
export default GradientIonIcon;
create home icon container which updates only when size ans focused props changes
IconContainer
const HomeIconContainer: React.FC<{ size: number; focused: boolean }> =
React.memo(
({ size, focused }) => {
console.log(focused);
const Icon = React.useMemo(
() => (focused ? GradientIonIcon : IonIcon),
[focused]
);
return (
<Icon
containerStyle={{ width: size, height: size }}
size={size}
name="ios-home"
color="purple"
/>
);
},
(prev, next) => prev.size === next.size && prev.focused === next.focused
);
then use this icon in your navigation options
DraerNAvigator
<Drawer.Screen
name="Home"
options={{
drawerIcon: ({ size, focused }) => (
<HomeIconContainer {...{ size, focused }} />
),
}}
component={Home}
/>
#Result
focused
unfocused

Related

How can I change icon width-height on react native?

I've created SVG component for all svg's. I just want to change width height with props but I couldn't figure out. I'm using icons like this now <SVGCustomIcon name="InboxMenu" />. how can I also add width height props?
custom SVG component
const icons: SVGCustomIconMap = {
InboxMenu: (
<Svg width={18} height={20} viewBox="0 0 18 20" fill="none">
<Path
d="M11.25 17.5c0 .629-.262 1.3-.73 1.77-.47.468-1.141.73-1.77.73a2.5 2.5 0 01-1.77-.73 2.563 2.563 0 01-.73-1.77h5z"
fill="#949494"
/>
.....
),
ProfileMenu: (
<Svg width={20} height={22} viewBox="0 0 20 22" fill="none">
......
),
};
const SVGCustomIcon = ({ name }: SVGCustomIconProps) => {
return <>{icons[name] ? icons[name] : null}</>;
};
export default SVGCustomIcon;
type.ts
export type SVGCustomIcon = React.SVGProps<SVGSVGElement>;
export interface SVGCustomIconMap {
[key: string]: SVGCustomIcon;
}
export interface SVGCustomIconProps {
name: string;
}
you can try this,
export interface SVGCustomIconMap {
[key: string]: any;
}
export interface SVGCustomIconProps {
name: string;
width?: number;
height?: number;
}
export type TSize = {
width?: number;
height?: number;
};
const icons: SVGCustomIconMap = {
InboxMenu: ({ width, height }: TSize) => {
return (
<>
<Svg
width={width || 18}
height={height || 20}
viewBox="0 0 18 20"
fill="none">
{/* .... your rest code .... */}
</>
);
},
ProfileMenu: ({ width, height }: TSize) => {
return (
<>
<Svg
width={width || 20}
height={height || 22}
viewBox="0 0 20 22"
fill="none">
{/* ...... your rest code ....... */}
</>
);
},
};
const SVGCustomIcon = ({ name, width, height }: SVGCustomIconProps) => {
const SvgCustom = icons?.[name] ? icons[name] : null;
if (!SvgCustom) {
return null;
}
return <SvgCustom width={width} height={height} />;
};
export default SVGCustomIcon;
//how to call
<SVGCustomIcon name="InboxMenu" width={20} height={22} />;
I would try adding a `preserveAspectRatio="none" to your svg component. I don't work in React Native a lot but I vaguely remember this issue.
ProfileMenu: (
<Svg width={20} height={22} viewBox="0 0 20 22" fill="none" preserveAspectRatio="none">
)
To change the width and height of an SVG icon in a React Native application, you can use the style prop of the Svg component.
Here is an example of how you can set the width and height of an SVG icon to 50 pixels:
Copy code
import { Svg } from 'react-native-svg';
function MyIcon() {
return (
<Svg width={50} height={50}>
{/* Icon content goes here */}
</Svg>
);
}
You can also use the style prop to set the width and height using a style object:
Copy code
import { Svg } from 'react-native-svg';
function MyIcon() {
return (
<Svg style={{ width: 50, height: 50 }}>
{/* Icon content goes here */}
</Svg>
);
}
Keep in mind that the width and height of the Svg component will determine the size of the entire icon, including any elements inside it. You may need to adjust the size of the individual elements within the icon as well to achieve the desired appearance.

Is it possible to make Native Base Factory ignore specific props?

I am trying to style some components and run them through Factory. The problem is that when I do that to Icons from 'react-native-vector-icons' it takes the scale, etc.. and puts them into style{{}}, which makes the icon small. Feather component is the 'r-n-vector-icons/feather' icon set
Icon default
<Feather
testID={attachmentsListItemIcon}
name="download"
size={20}
color="black"
style={{
marginVertical: 10
}}
/>
The same component ran though Native Base Factory
<StyledFeather
testID={attachmentsListItemIcon}
name="download"
size={20}
color="black"
my={10}
/>
Native Factory for the StyledFeather
const StyledFeather = ({
name,
testID,
size,
...props
}: {
name: string
testID: string
size: number
}) => {
const FactoryFeather = Factory(Feather)
return <FactoryFeather size={size} testID={testID} name={name} {...props} />
}
export { StyledFeather }

react native flat list how to force list items to be the same height?

I have a React-Native application where I am using FlatList to display a list of items obtained from the server. The list has 2 columns and I need my list items to be the same height. I put a border around the code rendering my list items but the list items are not the same height. I have tried using flexbox settings to make the view fill the container, but everything I try makes no difference.
I have created a simplified version of my app to illustrate the issue:
See that the red bordered areas are NOT the same height. I need to get these to be the same height.
The grey border is added in the view wrapping the component responsible for a list item and the red border is the root view of the component responsible for a list item. See the code below for clarity.
I can not use the grey border in my application because my application shows empty boxes whilst the component responsible for a list item is getting additional information from the server before it renders itself
Furthermore I can not used fixed sizes for heights.
Application Project structure and code
My code is split up in a manner where the files ending in "container.js" get the data from the server and pass it to its matching rendering component. For example, "MainListContainer" would be getting the list from the server and then pass the list data to "MainList", and "ListItemContainer" would get additional information about the single list item from the server and pass it to "ListItem" to render the actual item. I have kept this model in my simplified application so its as close to my real application as possible.
index.js
import {AppRegistry} from 'react-native';
import MainListContainer from './app/components/MainListContainer';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => MainListContainer);
MainListContainer.js
import React from 'react';
import MainList from './MainList';
const data = [
{id: '1', title: 'Item 1', subtitle: 'A', description: 'This is the first item.'},
{id: '2', title: 'Item 2', subtitle: 'B', description: 'The Big Brown Fox Jumped over the lazy dogs. The Big Brown Fox Jumped over the lazy dogs.',},
];
const MainListContainer = () => {
return ( <MainList items={data} /> );
};
export default MainListContainer;
MainList.js
import React from 'react';
import {StyleSheet, FlatList, View} from 'react-native';
import ListItemContainer from './ListItemContainer';
export default class MainList extends React.Component {
constructor(props) {
super(props);
this.state = { numColumns: 2};
this.renderItem = this.renderItem.bind(this);
}
renderItem({item, index}) {
return (
<View style={styles.flatListItemContainer}> <!-- THIS IS WHERE THE GREY BORDER IS ADDED -->
<ListItemContainer key={index} item={item} />
</View>
);
}
render() {
const {items} = this.props;
const {numColumns} = this.state;
return (
<View>
<FlatList
data={items}
renderItem={this.renderItem}
numColumns={numColumns}
key={numColumns}
keyExtractor={(item) => item.id}
/>
</View>
);
}
};
const styles = StyleSheet.create({
flatListItemContainer: {
flex: 1,
margin: 10,
borderColor: '#ccc',
borderWidth: 1,
},
});
ListItemContainer.js
import React from 'react';
import ListItem from './ListItem';
const ListItemContainer = (props) => {
const { item } = props;
return (
<ListItem item={item} />
);
};
export default ListItemContainer;
ListItem.js
import React from 'react';
import {TouchableHighlight, View, StyleSheet, Image, Text} from 'react-native';
const ListItem = (props) => {
const { item } = props;
return (
<TouchableHighlight
underlayColor="white"
>
<View style={styles.containerView}> <!-- THIS IS WHERE THE RED BORDER IS ADDED -->
<View style={styles.top_row}>
<Image style={styles.image} source={require('../images/placeholder.png')} />
<View style={styles.title_texts}>
<Text style={{fontWeight:'bold'}}>{item.title}</Text>
<Text style={{color: 'rgb(115, 115, 115)'}}>{item.subtitle}</Text>
</View>
</View>
<Text>{item.description}</Text>
</View>
</TouchableHighlight>
);
};
export default ListItem;
const styles = StyleSheet.create({
containerView: {
padding: 14,
borderColor: 'red',
borderWidth: 1,
},
top_row: {
flex: 1,
flexDirection: 'row',
marginBottom: 10,
},
title_texts: {
flex: 1,
flexDirection: 'column',
},
image: {
alignSelf: 'flex-end',
resizeMode: 'cover',
height: 40,
width: 40,
marginRight: 20
},
});
What I have tried
ListItem.js : move the style onto the "TouchableHighlight" view
ListItem.js : add a view wrapping "TouchableHighlight" view and adding style there
ListItem.js : added "alignItems:'stretch' on the "TouchableHighlight, added it to the "containerView" style, tried it on the description field too
same as "alignItems" but used "alignedSelf" instead
same as "alignItems" but used "alignedContent" instead
tried using "flexGrow" on different views (container, description)
You can measure the height of every element in the list and when you determine the maximum height, you can use that height for every element in the list.
const Parent = ({ ...props }) => {
const [maxHeight, setMaxHeight] = useState<number>(0);
const computeMaxHeight = (h: number) => {
if (h > maxHeight) setMaxHeight(h);
}
return (
<FlatList
data={props.data}
renderItem={({ item }) => (
<RenderItem
item={item}
computeHeight={(h) => computeMaxHeight(h)}
height={maxHeight}
/>
)}
....
/>
)
}
The Items:
const RenderItem = ({...props }) => {
return (
<View
style={{ height: props.height }}
onLayout={(event) => props.computeHeight(event.nativeEvent.layout.height)}
>
<Stuffs />
</View>
)
}
This is a very non-performant way of achieving this. I would avoid this if I have a long list or any list of more than a few items. You however can put certain checks in place to limit rerendering etc. Or alternatively if it is only text that will affect the height, then you can only measure the height of the element with the most text and use that element's height for the rest.
Instead of set fixed width height, you can use flex box to achieve it. I just solved the issue by removing alignSelf at the FlatList and add alignItems center on it.
Wrap the flatList in flex box with align item center, you can add the code in your MainList.js file, the first <View>, i.e:
render() {
const {items} = this.props;
const {numColumns} = this.state;
return (
<View style={{flex: 1, alignItems: 'center'>
<FlatList
data={items}
renderItem={this.renderItem}
numColumns={numColumns}
key={numColumns}
keyExtractor={(item) => item.id}
/>
</View>
);
If still not reflected, you may try to add flex:1, alignItems center in FlatList style props.
You are missing a very basic concept of giving fixed height to the flatlist items, in your ListItem.js, try to set height:200 in containerView. Let me know if that works for you

React Native: Sidebar is shown but unable to interact with it

I am using React Native Sidebar
This is the sidebar:
<Sidebar
ref={(ref) => this._drawer = ref}
leftSidebar={ this.renderLeftSidebar() }
leftSidebarWidth = {200}
>
</Sidebar>
This is how I'm rendering the left sidebar:
renderLeftSidebar = () =>{
return (
<View style = {{ position:'absolute', backgroundColor:'#24292e4f', height:Dimensions.get('window').height}}>
<DrawerContent/>
</View>
)
}
And this is the content for that left sidebar:
export default DrawerContent = () => {
return (
<View style={styles.animatedBox}>
<Image
source={require('../assets/images/header.png')}
style={{height:200, alignSelf:'center'}}
/>
<View style = {styles.drawerContentView}>
<Icon
name='setting'
type='antdesign'
color='#4abce3'
size ={22}
iconStyle = {styles.drawerItemIconStyle}
onPress={() => console.log('hello settings')}
/>
</View>
The sidebar opens just fine and renders correctly, but when I try and press anything on the sidebar the press goes through the sidebar and interacts with elements rendered beneath it. Should I be using zIndex in my styles or is my approach completely wrong?
If you are using React-native-vector-icons, the ICON itself has no button properties.
You can use Icon.Button
import Icon from 'react-native-vector-icons/AntDesign';
...
<Icon.Button
name='setting'
color='#4abce3'
size ={22}
iconStyle = {styles.drawerItemIconStyle}
onPress={() => console.log('hello settings')}
>
This is Setting
</Icon.Button>
OR You can use TouchableOpacity
import {
TouchableOpacity
} from 'react-native'
import Icon from 'react-native-vector-icons/AntDesign';
...
<TouchableOpacity onPress={() => console.log('hello settings')}>
<Icon
name='setting'
color='#4abce3'
size ={22}
iconStyle = {styles.drawerItemIconStyle}
/>
</TouchableOpacity>

where is defined the: `this.props` referenced on the first line inside the `render()` function?

On the following code:
where is defined the: this.props referenced on the first line inside the render() function? Is there any relationship between that lowercase: props variable with the capitalized Props type? any convention here?
what implication has: ...extends React.Component<Props>?
what implication has the /* #flow */ line on the very top of the file?
https://github.com/callstack/react-native-paper/blob/e4ca933f386d7b485f6580c332f0638a55dfe2db/example/src/CardExample.js#L27
/* #flow */
import * as React from 'react';
import { Alert, ScrollView, StyleSheet } from 'react-native';
import {
Title,
Caption,
Paragraph,
Card,
Button,
withTheme,
type Theme,
} from 'react-native-paper';
type Props = {
theme: Theme,
};
class CardExample extends React.Component<Props> {
static title = 'Card';
render() {
const {
theme: {
colors: { background },
},
} = this.props;
return (
<ScrollView
style={[styles.container, { backgroundColor: background }]}
contentContainerStyle={styles.content}
>
<Card style={styles.card}>
<Card.Cover source={require('../assets/wrecked-ship.jpg')} />
<Card.Content>
<Title>Abandoned Ship</Title>
<Paragraph>
The Abandoned Ship is a wrecked ship located on Route 108 in
Hoenn, originally being a ship named the S.S. Cactus. The second
part of the ship can only be accessed by using Dive and contains
the Scanner.
</Paragraph>
</Card.Content>
</Card>
<Card style={styles.card}>
<Card.Cover source={require('../assets/forest.jpg')} />
<Card.Actions>
<Button onPress={() => {}}>Share</Button>
<Button onPress={() => {}}>Explore</Button>
</Card.Actions>
</Card>
<Card style={styles.card}>
<Card.Content>
<Title>Berries</Title>
<Caption>Omega Ruby</Caption>
<Paragraph>
Dotted around the Hoenn region, you will find loamy soil, many of
which are housing berries. Once you have picked the berries, then
you have the ability to use that loamy soil to grow your own
berries. These can be any berry and will require attention to get
the best crop.
</Paragraph>
</Card.Content>
</Card>
<Card style={styles.card}>
<Title>Just Strawberries</Title>
<Card.Cover source={require('../assets/strawberries.jpg')} />
</Card>
<Card
style={styles.card}
onPress={() => {
Alert.alert('The Chameleon is Pressed');
}}
>
<Card.Cover source={require('../assets/chameleon.jpg')} />
<Card.Content>
<Title>Pressable Chameleon</Title>
<Paragraph>
This is a pressable chameleon. If you press me, I will alert.
</Paragraph>
</Card.Content>
</Card>
<Card
style={styles.card}
onLongPress={() => {
Alert.alert('The City is Long Pressed');
}}
>
<Card.Cover source={require('../assets/city.jpg')} />
<Card.Content>
<Title>Long Pressable City</Title>
<Paragraph>
This is a long press only city. If you long press me, I will
alert.
</Paragraph>
</Card.Content>
</Card>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
padding: 4,
},
card: {
margin: 4,
},
});
export default withTheme(CardExample);
Could you give me some light on this?
References:
https://flow.org/en/docs/react/components/
Thanks!
props is defined in Component (is in base class) because you have FLOW this.props should have shape of Props )
You are extending base Component and you component can accept "Props" shape data in your example you component will accept a property theme of shape "Props".
Flow is static typing to JavaScript (https://github.com/facebook/flow || https://flow.org/)