0

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.

halfer
  • 19,824
  • 17
  • 99
  • 186
Joel Hager
  • 2,990
  • 3
  • 15
  • 44

1 Answers1

0

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

Focused icon

unfocused

Unfocused

Ashwith Saldanha
  • 1,700
  • 1
  • 5
  • 15
  • Fonts aren't as performant and I have trouble getting them to look consistent across iOS and Android. Plus - There's *got* to be a way to just dynamically set a gradient fill. I know you can with regular react, and I'm *sure* there's got to be a way with svgs with react native. I just don't even know what specifically to ask to get what I need. :/ – Joel Hager Dec 17 '21 at 07:33