import React from 'react'; import { StyleSheet, View } from 'react-native'; type Props = { colors: number[]; locations?: number[] | null; startPoint?: Point | null; endPoint?: Point | null; onLayout?: Function; } & React.ComponentProps; type State = { width?: number; height?: number; }; type Point = [number, number]; const PI_2 = Math.PI / 2; function radToDeg(radians: number): number { return (radians * 180.0) / Math.PI; } export default class NativeLinearGradient extends React.PureComponent { state = { width: undefined, height: undefined, }; onLayout = event => { this.setState({ width: event.nativeEvent.layout.width, height: event.nativeEvent.layout.height, }); if (this.props.onLayout) { this.props.onLayout(event); } }; getControlPoints = (): Point[] => { const { startPoint, endPoint } = this.props; let correctedStartPoint: Point = [0.5, 0.0]; if (Array.isArray(startPoint)) { correctedStartPoint = [ startPoint[0] != null ? startPoint[0] : 0.5, startPoint[1] != null ? startPoint[1] : 0.0, ]; } let correctedEndPoint: Point = [0.5, 1.0]; if (Array.isArray(endPoint)) { correctedEndPoint = [ endPoint[0] != null ? endPoint[0] : 0.5, endPoint[1] != null ? endPoint[1] : 1.0, ]; } return [correctedStartPoint, correctedEndPoint]; }; calculateGradientAngleFromControlPoints = (): number => { const [start, end] = this.getControlPoints(); const { width = 0, height = 0 } = this.state; const radians = Math.atan2(height * (end[0] - start[0]), width * (end[1] - start[1])) + PI_2; const degrees = radToDeg(radians); return degrees; }; getWebGradientColorStyle = (): string => { return this.getGradientValues().join(','); }; convertJSColorToGradientSafeColor = (color: number, index: number): string => { const { locations } = this.props; const hexColor = hexStringFromProcessedColor(color); let output = hexColor; if (locations && locations[index]) { const location = Math.max(0, Math.min(1, locations[index])); // Convert 0...1 to 0...100 const percentage = location * 100; output += ` ${percentage}%`; } return output; }; getGradientValues = (): string[] => { return this.props.colors.map(this.convertJSColorToGradientSafeColor); }; getBackgroundImage = (): string => { if (this.state.width && this.state.height) { return `linear-gradient(${this.calculateGradientAngleFromControlPoints()}deg, ${this.getWebGradientColorStyle()})`; } return `transparent`; }; render() { const { colors, locations, startPoint, endPoint, onLayout, style, ...props } = this.props; let flatStyle = style; const backgroundImage = this.getBackgroundImage(); if (backgroundImage) { let compiledStyle = StyleSheet.flatten(style) || {}; flatStyle = { ...compiledStyle, // @ts-ignore: [ts] Property 'backgroundImage' does not exist on type 'ViewStyle'. backgroundImage: this.getBackgroundImage(), }; } // TODO: Bacon: In the future we could consider adding `backgroundRepeat: "no-repeat"`. For more browser support. return ; } } function hexStringFromProcessedColor(argbColor: number): string { const hexColorString = argbColor.toString(16); const withoutAlpha = hexColorString.substring(2); const alpha = hexColorString.substring(0, 2); return `#${withoutAlpha}${alpha}`; }