1import React from 'react'; 2import { View } from 'react-native'; 3 4type Props = { 5 colors: number[]; 6 locations?: number[] | null; 7 startPoint?: Point | null; 8 endPoint?: Point | null; 9 onLayout?: Function; 10} & React.ComponentProps<typeof View>; 11 12type State = { 13 width?: number; 14 height?: number; 15}; 16 17type Point = [number, number]; 18 19const PI_2 = Math.PI / 2; 20 21export default class NativeLinearGradient extends React.PureComponent<Props, State> { 22 state = { 23 width: undefined, 24 height: undefined, 25 }; 26 27 onLayout = event => { 28 this.setState({ 29 width: event.nativeEvent.layout.width, 30 height: event.nativeEvent.layout.height, 31 }); 32 if (this.props.onLayout) { 33 this.props.onLayout(event); 34 } 35 }; 36 37 getControlPoints = (): Point[] => { 38 const { startPoint, endPoint } = this.props; 39 40 let correctedStartPoint: Point = [0, 0]; 41 if (Array.isArray(startPoint)) { 42 correctedStartPoint = [ 43 startPoint[0] != null ? startPoint[0] : 0.0, 44 startPoint[1] != null ? startPoint[1] : 0.0, 45 ]; 46 } 47 let correctedEndPoint: Point = [0.0, 1.0]; 48 if (Array.isArray(endPoint)) { 49 correctedEndPoint = [ 50 endPoint[0] != null ? endPoint[0] : 0.0, 51 endPoint[1] != null ? endPoint[1] : 1.0, 52 ]; 53 } 54 return [correctedStartPoint, correctedEndPoint]; 55 }; 56 57 calculateGradientAngleFromControlPoints = (): number => { 58 const [start, end] = this.getControlPoints(); 59 const { width = 1, height = 1 } = this.state; 60 start[0] *= width; 61 end[0] *= width; 62 start[1] *= height; 63 end[1] *= height; 64 const py = end[1] - start[1]; 65 const px = end[0] - start[0]; 66 return 90 + (Math.atan2(py, px) * 180) / Math.PI; 67 }; 68 69 getWebGradientColorStyle = (): string => { 70 return this.getGradientValues().join(','); 71 }; 72 73 convertJSColorToGradientSafeColor = (color: number, index: number): string => { 74 const { locations } = this.props; 75 const hexColor = hexStringFromProcessedColor(color); 76 let output = hexColor; 77 if (locations && locations[index]) { 78 const location = Math.max(0, Math.min(1, locations[index])); 79 // Convert 0...1 to 0...100 80 const percentage = location * 100; 81 output += ` ${percentage}%`; 82 } 83 return output; 84 }; 85 86 getGradientValues = (): string[] => { 87 return this.props.colors.map(this.convertJSColorToGradientSafeColor); 88 }; 89 90 getBackgroundImage = (): string => { 91 return `linear-gradient(${this.calculateGradientAngleFromControlPoints()}deg, ${this.getWebGradientColorStyle()})`; 92 }; 93 94 render() { 95 const { colors, locations, startPoint, endPoint, onLayout, style, ...props } = this.props; 96 const backgroundImage = this.getBackgroundImage(); 97 // TODO: Bacon: In the future we could consider adding `backgroundRepeat: "no-repeat"`. For more browser support. 98 return ( 99 <View 100 style={[ 101 style, 102 // @ts-ignore: [ts] Property 'backgroundImage' does not exist on type 'ViewStyle'. 103 { backgroundImage }, 104 ]} 105 onLayout={this.onLayout} 106 {...props} 107 /> 108 ); 109 } 110} 111 112function hexStringFromProcessedColor(argbColor: number): string { 113 if (argbColor === 0) { 114 return `rgba(0,0,0,0)`; 115 } 116 const hexColorString = argbColor.toString(16); 117 const withoutAlpha = hexColorString.substring(2); 118 const alpha = hexColorString.substring(0, 2); 119 return `#${withoutAlpha}${alpha}`; 120} 121