1import React, { FunctionComponent, useEffect, useState } from 'react'; 2import { LayoutRectangle, View } from 'react-native'; 3import normalizeColor from 'react-native-web/src/modules/normalizeColor'; 4 5type Props = { 6 colors: number[]; 7 locations?: number[] | null; 8 startPoint?: Point | null; 9 endPoint?: Point | null; 10 onLayout?: Function; 11} & React.ComponentProps<typeof View>; 12 13type Point = [number, number]; 14 15const NativeLinearGradient: FunctionComponent<Props> = ({ 16 colors, 17 locations, 18 startPoint, 19 endPoint, 20 ...props 21}: Props) => { 22 const [layout, setLayout] = useState<LayoutRectangle | null>(null); 23 const [gradientColors, setGradientColors] = useState<string[]>([]); 24 const [pseudoAngle, setPseudoAngle] = useState<number>(0); 25 26 const { width = 1, height = 1 } = layout ?? {}; 27 useEffect(() => { 28 const getControlPoints = (): Point[] => { 29 let correctedStartPoint: Point = [0, 0]; 30 if (Array.isArray(startPoint)) { 31 correctedStartPoint = [ 32 startPoint[0] != null ? startPoint[0] : 0.0, 33 startPoint[1] != null ? startPoint[1] : 0.0, 34 ]; 35 } 36 let correctedEndPoint: Point = [0.0, 1.0]; 37 if (Array.isArray(endPoint)) { 38 correctedEndPoint = [ 39 endPoint[0] != null ? endPoint[0] : 0.0, 40 endPoint[1] != null ? endPoint[1] : 1.0, 41 ]; 42 } 43 return [correctedStartPoint, correctedEndPoint]; 44 }; 45 46 const [start, end] = getControlPoints(); 47 start[0] *= width; 48 end[0] *= width; 49 start[1] *= height; 50 end[1] *= height; 51 const py = end[1] - start[1]; 52 const px = end[0] - start[0]; 53 54 setPseudoAngle(90 + (Math.atan2(py, px) * 180) / Math.PI); 55 }, [width, height, startPoint, endPoint]); 56 57 useEffect(() => { 58 const nextGradientColors = colors.map((color: number, index: number): string => { 59 const hexColor = normalizeColor(color); 60 let output = hexColor; 61 if (locations && locations[index]) { 62 const location = Math.max(0, Math.min(1, locations[index])); 63 // Convert 0...1 to 0...100 64 const percentage = location * 100; 65 output += ` ${percentage}%`; 66 } 67 return output; 68 }); 69 70 setGradientColors(nextGradientColors); 71 }, [colors, locations]); 72 73 const colorStyle = gradientColors.join(','); 74 const backgroundImage = `linear-gradient(${pseudoAngle}deg, ${colorStyle})`; 75 // TODO: Bacon: In the future we could consider adding `backgroundRepeat: "no-repeat"`. For more 76 // browser support. 77 return ( 78 <View 79 {...props} 80 style={[ 81 props.style, 82 // @ts-ignore: [ts] Property 'backgroundImage' does not exist on type 'ViewStyle'. 83 { backgroundImage }, 84 ]} 85 onLayout={event => { 86 setLayout(event.nativeEvent.layout); 87 if (props.onLayout) { 88 props.onLayout(event); 89 } 90 }} 91 /> 92 ); 93}; 94 95export default NativeLinearGradient; 96