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