react-native-linear-gradient-fabric 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +132 -0
  2. package/android/build.gradle +107 -0
  3. package/android/gradle.properties +4 -0
  4. package/android/src/main/AndroidManifest.xml +3 -0
  5. package/android/src/main/AndroidManifestNew.xml +2 -0
  6. package/android/src/main/java/com/lineargradientfabric/LinearGradientFabricPackage.kt +17 -0
  7. package/android/src/main/java/com/lineargradientfabric/LinearGradientView.kt +128 -0
  8. package/android/src/newarch/LinearGradientViewManager.kt +92 -0
  9. package/android/src/oldarch/LinearGradientViewManager.kt +82 -0
  10. package/ios/LinearGradientView.h +13 -0
  11. package/ios/LinearGradientView.m +145 -0
  12. package/ios/LinearGradientViewComponentView.h +13 -0
  13. package/ios/LinearGradientViewComponentView.mm +181 -0
  14. package/ios/LinearGradientViewManager.h +5 -0
  15. package/ios/LinearGradientViewManager.m +56 -0
  16. package/lib/commonjs/LinearGradientNativeComponent.js +10 -0
  17. package/lib/commonjs/LinearGradientNativeComponent.js.map +1 -0
  18. package/lib/commonjs/index.js +73 -0
  19. package/lib/commonjs/index.js.map +1 -0
  20. package/lib/module/LinearGradientNativeComponent.js +3 -0
  21. package/lib/module/LinearGradientNativeComponent.js.map +1 -0
  22. package/lib/module/index.js +64 -0
  23. package/lib/module/index.js.map +1 -0
  24. package/lib/typescript/src/LinearGradientNativeComponent.d.ts +39 -0
  25. package/lib/typescript/src/LinearGradientNativeComponent.d.ts.map +1 -0
  26. package/lib/typescript/src/index.d.ts +54 -0
  27. package/lib/typescript/src/index.d.ts.map +1 -0
  28. package/package.json +145 -0
  29. package/react-native-linear-gradient-fabric.podspec +20 -0
  30. package/src/LinearGradientNativeComponent.ts +43 -0
  31. package/src/index.tsx +128 -0
package/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # react-native-linear-gradient-fabric
2
+
3
+ A modern replacement for `react-native-linear-gradient` with full New Architecture (Fabric) support.
4
+
5
+ ## Features
6
+
7
+ - Full support for React Native New Architecture (Fabric)
8
+ - Backward compatible with Old Architecture via Interop Layer
9
+ - GPU-accelerated gradient rendering
10
+ - All features from the original `react-native-linear-gradient`
11
+
12
+ ## Installation
13
+
14
+ ```sh
15
+ bun add react-native-linear-gradient-fabric
16
+ # or
17
+ npm install react-native-linear-gradient-fabric
18
+ ```
19
+
20
+ ### iOS
21
+
22
+ ```sh
23
+ cd ios && pod install
24
+ ```
25
+
26
+ ### Android
27
+
28
+ No additional steps required. The library will be automatically linked.
29
+
30
+ ## Usage
31
+
32
+ ```tsx
33
+ import { LinearGradient } from 'react-native-linear-gradient-fabric';
34
+
35
+ // Basic vertical gradient
36
+ <LinearGradient
37
+ colors={['#4c669f', '#3b5998', '#192f6a']}
38
+ style={{ flex: 1 }}
39
+ />
40
+
41
+ // Horizontal gradient
42
+ <LinearGradient
43
+ colors={['#ff6b6b', '#feca57', '#48dbfb']}
44
+ start={{ x: 0, y: 0.5 }}
45
+ end={{ x: 1, y: 0.5 }}
46
+ style={{ height: 100 }}
47
+ />
48
+
49
+ // Diagonal gradient
50
+ <LinearGradient
51
+ colors={['#a55eea', '#45aaf2']}
52
+ start={{ x: 0, y: 0 }}
53
+ end={{ x: 1, y: 1 }}
54
+ style={{ height: 100 }}
55
+ />
56
+
57
+ // Gradient with custom color stops
58
+ <LinearGradient
59
+ colors={['#ff0000', '#ffff00', '#00ff00']}
60
+ locations={[0, 0.3, 1]}
61
+ style={{ height: 100 }}
62
+ />
63
+
64
+ // Angle-based gradient
65
+ <LinearGradient
66
+ colors={['#667eea', '#764ba2']}
67
+ useAngle
68
+ angle={45}
69
+ style={{ height: 100 }}
70
+ />
71
+
72
+ // Gradient as background with children
73
+ <LinearGradient
74
+ colors={['#00c6fb', '#005bea']}
75
+ style={{ padding: 20, borderRadius: 10 }}
76
+ >
77
+ <Text style={{ color: 'white' }}>Hello, Gradient!</Text>
78
+ </LinearGradient>
79
+ ```
80
+
81
+ ## Props
82
+
83
+ | Prop | Type | Required | Default | Description |
84
+ |------|------|----------|---------|-------------|
85
+ | `colors` | `ColorValue[]` | Yes | - | An array of at least 2 colors |
86
+ | `start` | `{ x: number, y: number }` | No | `{ x: 0.5, y: 0 }` | Start point of the gradient |
87
+ | `end` | `{ x: number, y: number }` | No | `{ x: 0.5, y: 1 }` | End point of the gradient |
88
+ | `locations` | `number[]` | No | evenly distributed | Color stop positions (0-1) |
89
+ | `useAngle` | `boolean` | No | `false` | Use angle instead of start/end |
90
+ | `angle` | `number` | No | `0` | Gradient angle in degrees |
91
+ | `angleCenter` | `{ x: number, y: number }` | No | `{ x: 0.5, y: 0.5 }` | Center point for angle rotation |
92
+ | `style` | `ViewStyle` | No | - | Style for the gradient view |
93
+ | `children` | `ReactNode` | No | - | Content to render on top |
94
+
95
+ ### Coordinate System
96
+
97
+ The `start` and `end` props use a coordinate system where:
98
+ - `x: 0` = left edge, `x: 1` = right edge
99
+ - `y: 0` = top edge, `y: 1` = bottom edge
100
+
101
+ ### Angle System
102
+
103
+ When using `useAngle` and `angle`:
104
+ - `0°` = upward (top)
105
+ - `90°` = rightward
106
+ - `180°` = downward (bottom)
107
+ - `270°` = leftward
108
+
109
+ ## Testing
110
+
111
+ For testing, you can use the provided mock:
112
+
113
+ ```js
114
+ // jest.setup.js
115
+ jest.mock('react-native-linear-gradient-fabric', () =>
116
+ require('react-native-linear-gradient-fabric/jest/linear-gradient-mock')
117
+ );
118
+ ```
119
+
120
+ ## Requirements
121
+
122
+ - React Native 0.72+
123
+ - iOS 13.4+
124
+ - Android SDK 21+
125
+
126
+ ## License
127
+
128
+ MIT
129
+
130
+ ## Contributing
131
+
132
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
@@ -0,0 +1,107 @@
1
+ buildscript {
2
+ ext.safeExtGet = {prop, fallback ->
3
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
4
+ }
5
+ repositories {
6
+ google()
7
+ mavenCentral()
8
+ }
9
+ dependencies {
10
+ classpath("com.android.tools.build:gradle:7.4.2")
11
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${safeExtGet('kotlinVersion', '1.8.22')}")
12
+ }
13
+ }
14
+
15
+ def isNewArchitectureEnabled() {
16
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
17
+ }
18
+
19
+ apply plugin: "com.android.library"
20
+ apply plugin: "kotlin-android"
21
+
22
+ if (isNewArchitectureEnabled()) {
23
+ apply plugin: "com.facebook.react"
24
+ }
25
+
26
+ def getExtOrDefault(name) {
27
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["LinearGradientFabric_" + name]
28
+ }
29
+
30
+ def getExtOrIntegerDefault(name) {
31
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["LinearGradientFabric_" + name]).toInteger()
32
+ }
33
+
34
+ def supportsNamespace() {
35
+ def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
36
+ def major = parsed[0].toInteger()
37
+ def minor = parsed[1].toInteger()
38
+ return (major == 7 && minor >= 3) || major >= 8
39
+ }
40
+
41
+ android {
42
+ if (supportsNamespace()) {
43
+ namespace "com.lineargradientfabric"
44
+ }
45
+
46
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
47
+
48
+ defaultConfig {
49
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
50
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
51
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
52
+ }
53
+
54
+ buildFeatures {
55
+ buildConfig true
56
+ }
57
+
58
+ buildTypes {
59
+ release {
60
+ minifyEnabled false
61
+ }
62
+ }
63
+
64
+ lintOptions {
65
+ disable "GradleCompatible"
66
+ }
67
+
68
+ compileOptions {
69
+ sourceCompatibility JavaVersion.VERSION_17
70
+ targetCompatibility JavaVersion.VERSION_17
71
+ }
72
+
73
+ kotlinOptions {
74
+ jvmTarget = "17"
75
+ }
76
+
77
+ sourceSets {
78
+ main {
79
+ if (supportsNamespace()) {
80
+ manifest.srcFile "src/main/AndroidManifestNew.xml"
81
+ }
82
+ if (isNewArchitectureEnabled()) {
83
+ java.srcDirs += ["src/newarch"]
84
+ } else {
85
+ java.srcDirs += ["src/oldarch"]
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ repositories {
92
+ mavenCentral()
93
+ google()
94
+ }
95
+
96
+ dependencies {
97
+ implementation "com.facebook.react:react-android"
98
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:${safeExtGet('kotlinVersion', '1.8.22')}"
99
+ }
100
+
101
+ if (isNewArchitectureEnabled()) {
102
+ react {
103
+ jsRootDir = file("../src/")
104
+ libraryName = "LinearGradientFabric"
105
+ codegenJavaPackageName = "com.lineargradientfabric"
106
+ }
107
+ }
@@ -0,0 +1,4 @@
1
+ LinearGradientFabric_kotlinVersion=1.8.22
2
+ LinearGradientFabric_minSdkVersion=21
3
+ LinearGradientFabric_targetSdkVersion=34
4
+ LinearGradientFabric_compileSdkVersion=34
@@ -0,0 +1,3 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ package="com.lineargradientfabric">
3
+ </manifest>
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,17 @@
1
+ package com.lineargradientfabric
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ class LinearGradientFabricPackage : ReactPackage {
9
+
10
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
11
+ return emptyList()
12
+ }
13
+
14
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
15
+ return listOf(LinearGradientViewManager())
16
+ }
17
+ }
@@ -0,0 +1,128 @@
1
+ package com.lineargradientfabric
2
+
3
+ import android.content.Context
4
+ import android.graphics.Canvas
5
+ import android.graphics.LinearGradient
6
+ import android.graphics.Paint
7
+ import android.graphics.Shader
8
+ import android.widget.FrameLayout
9
+ import kotlin.math.cos
10
+ import kotlin.math.sin
11
+
12
+ class LinearGradientView(context: Context) : FrameLayout(context) {
13
+
14
+ private val paint = Paint()
15
+ private var colors: IntArray = intArrayOf()
16
+ private var locations: FloatArray? = null
17
+ private var startX: Float = 0.5f
18
+ private var startY: Float = 0f
19
+ private var endX: Float = 0.5f
20
+ private var endY: Float = 1f
21
+ private var useAngle: Boolean = false
22
+ private var angle: Float = 0f
23
+ private var angleCenterX: Float = 0.5f
24
+ private var angleCenterY: Float = 0.5f
25
+
26
+ init {
27
+ // Enable drawing for ViewGroup
28
+ setWillNotDraw(false)
29
+ }
30
+
31
+ fun setColors(colors: IntArray) {
32
+ this.colors = colors
33
+ invalidate()
34
+ }
35
+
36
+ fun setLocations(locations: FloatArray?) {
37
+ this.locations = locations
38
+ invalidate()
39
+ }
40
+
41
+ fun setStartPoint(x: Float, y: Float) {
42
+ this.startX = x
43
+ this.startY = y
44
+ invalidate()
45
+ }
46
+
47
+ fun setEndPoint(x: Float, y: Float) {
48
+ this.endX = x
49
+ this.endY = y
50
+ invalidate()
51
+ }
52
+
53
+ fun setUseAngle(useAngle: Boolean) {
54
+ this.useAngle = useAngle
55
+ invalidate()
56
+ }
57
+
58
+ fun setAngle(angle: Float) {
59
+ this.angle = angle
60
+ invalidate()
61
+ }
62
+
63
+ fun setAngleCenter(x: Float, y: Float) {
64
+ this.angleCenterX = x
65
+ this.angleCenterY = y
66
+ invalidate()
67
+ }
68
+
69
+ override fun onDraw(canvas: Canvas) {
70
+ if (colors.size < 2 || width == 0 || height == 0) {
71
+ super.onDraw(canvas)
72
+ return
73
+ }
74
+
75
+ val (x0, y0, x1, y1) = calculateGradientPoints()
76
+
77
+ val gradient = LinearGradient(
78
+ x0, y0, x1, y1,
79
+ colors,
80
+ locations,
81
+ Shader.TileMode.CLAMP
82
+ )
83
+
84
+ paint.shader = gradient
85
+ canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
86
+
87
+ super.onDraw(canvas)
88
+ }
89
+
90
+ private fun calculateGradientPoints(): FloatArray {
91
+ return if (useAngle) {
92
+ calculatePointsFromAngle()
93
+ } else {
94
+ floatArrayOf(
95
+ startX * width,
96
+ startY * height,
97
+ endX * width,
98
+ endY * height
99
+ )
100
+ }
101
+ }
102
+
103
+ private fun calculatePointsFromAngle(): FloatArray {
104
+ // Convert angle from degrees to radians
105
+ // Angle 0 = up, 90 = right, 180 = down, 270 = left
106
+ val angleRad = Math.toRadians((angle - 90).toDouble())
107
+
108
+ // Calculate the gradient direction vector
109
+ val dx = cos(angleRad).toFloat()
110
+ val dy = sin(angleRad).toFloat()
111
+
112
+ // Calculate the center point in pixels
113
+ val centerPx = angleCenterX * width
114
+ val centerPy = angleCenterY * height
115
+
116
+ // Calculate the length to cover the view
117
+ // Use half the diagonal for proper coverage
118
+ val halfWidth = width / 2f
119
+ val halfHeight = height / 2f
120
+
121
+ val x0 = centerPx - dx * halfWidth
122
+ val y0 = centerPy - dy * halfHeight
123
+ val x1 = centerPx + dx * halfWidth
124
+ val y1 = centerPy + dy * halfHeight
125
+
126
+ return floatArrayOf(x0, y0, x1, y1)
127
+ }
128
+ }
@@ -0,0 +1,92 @@
1
+ package com.lineargradientfabric
2
+
3
+ import com.facebook.react.bridge.ReadableArray
4
+ import com.facebook.react.bridge.ReadableMap
5
+ import com.facebook.react.module.annotations.ReactModule
6
+ import com.facebook.react.uimanager.ThemedReactContext
7
+ import com.facebook.react.uimanager.ViewGroupManager
8
+ import com.facebook.react.uimanager.ViewManagerDelegate
9
+ import com.facebook.react.uimanager.annotations.ReactProp
10
+ import com.facebook.react.viewmanagers.LinearGradientViewManagerDelegate
11
+ import com.facebook.react.viewmanagers.LinearGradientViewManagerInterface
12
+
13
+ @ReactModule(name = LinearGradientViewManager.REACT_CLASS)
14
+ class LinearGradientViewManager : ViewGroupManager<LinearGradientView>(),
15
+ LinearGradientViewManagerInterface<LinearGradientView> {
16
+
17
+ private val delegate: ViewManagerDelegate<LinearGradientView> = LinearGradientViewManagerDelegate(this)
18
+
19
+ override fun getDelegate(): ViewManagerDelegate<LinearGradientView> = delegate
20
+
21
+ override fun getName(): String = REACT_CLASS
22
+
23
+ override fun createViewInstance(reactContext: ThemedReactContext): LinearGradientView {
24
+ return LinearGradientView(reactContext)
25
+ }
26
+
27
+ @ReactProp(name = "colors")
28
+ override fun setColors(view: LinearGradientView, colors: ReadableArray?) {
29
+ if (colors == null) return
30
+
31
+ val colorArray = IntArray(colors.size())
32
+ for (i in 0 until colors.size()) {
33
+ colorArray[i] = colors.getInt(i)
34
+ }
35
+ view.setColors(colorArray)
36
+ }
37
+
38
+ @ReactProp(name = "locations")
39
+ override fun setLocations(view: LinearGradientView, locations: ReadableArray?) {
40
+ if (locations == null) {
41
+ view.setLocations(null)
42
+ return
43
+ }
44
+
45
+ val locationArray = FloatArray(locations.size())
46
+ for (i in 0 until locations.size()) {
47
+ locationArray[i] = locations.getDouble(i).toFloat()
48
+ }
49
+ view.setLocations(locationArray)
50
+ }
51
+
52
+ @ReactProp(name = "startPoint")
53
+ override fun setStartPoint(view: LinearGradientView, startPoint: ReadableMap?) {
54
+ if (startPoint == null) return
55
+
56
+ val x = startPoint.getDouble("x").toFloat()
57
+ val y = startPoint.getDouble("y").toFloat()
58
+ view.setStartPoint(x, y)
59
+ }
60
+
61
+ @ReactProp(name = "endPoint")
62
+ override fun setEndPoint(view: LinearGradientView, endPoint: ReadableMap?) {
63
+ if (endPoint == null) return
64
+
65
+ val x = endPoint.getDouble("x").toFloat()
66
+ val y = endPoint.getDouble("y").toFloat()
67
+ view.setEndPoint(x, y)
68
+ }
69
+
70
+ @ReactProp(name = "useAngle", defaultBoolean = false)
71
+ override fun setUseAngle(view: LinearGradientView, useAngle: Boolean) {
72
+ view.setUseAngle(useAngle)
73
+ }
74
+
75
+ @ReactProp(name = "angle", defaultFloat = 0f)
76
+ override fun setAngle(view: LinearGradientView, angle: Float) {
77
+ view.setAngle(angle)
78
+ }
79
+
80
+ @ReactProp(name = "angleCenter")
81
+ override fun setAngleCenter(view: LinearGradientView, angleCenter: ReadableMap?) {
82
+ if (angleCenter == null) return
83
+
84
+ val x = angleCenter.getDouble("x").toFloat()
85
+ val y = angleCenter.getDouble("y").toFloat()
86
+ view.setAngleCenter(x, y)
87
+ }
88
+
89
+ companion object {
90
+ const val REACT_CLASS = "LinearGradientView"
91
+ }
92
+ }
@@ -0,0 +1,82 @@
1
+ package com.lineargradientfabric
2
+
3
+ import com.facebook.react.bridge.ReadableArray
4
+ import com.facebook.react.bridge.ReadableMap
5
+ import com.facebook.react.uimanager.ThemedReactContext
6
+ import com.facebook.react.uimanager.ViewGroupManager
7
+ import com.facebook.react.uimanager.annotations.ReactProp
8
+
9
+ class LinearGradientViewManager : ViewGroupManager<LinearGradientView>() {
10
+
11
+ override fun getName(): String = REACT_CLASS
12
+
13
+ override fun createViewInstance(reactContext: ThemedReactContext): LinearGradientView {
14
+ return LinearGradientView(reactContext)
15
+ }
16
+
17
+ @ReactProp(name = "colors")
18
+ fun setColors(view: LinearGradientView, colors: ReadableArray?) {
19
+ if (colors == null) return
20
+
21
+ val colorArray = IntArray(colors.size())
22
+ for (i in 0 until colors.size()) {
23
+ colorArray[i] = colors.getInt(i)
24
+ }
25
+ view.setColors(colorArray)
26
+ }
27
+
28
+ @ReactProp(name = "locations")
29
+ fun setLocations(view: LinearGradientView, locations: ReadableArray?) {
30
+ if (locations == null) {
31
+ view.setLocations(null)
32
+ return
33
+ }
34
+
35
+ val locationArray = FloatArray(locations.size())
36
+ for (i in 0 until locations.size()) {
37
+ locationArray[i] = locations.getDouble(i).toFloat()
38
+ }
39
+ view.setLocations(locationArray)
40
+ }
41
+
42
+ @ReactProp(name = "startPoint")
43
+ fun setStartPoint(view: LinearGradientView, startPoint: ReadableMap?) {
44
+ if (startPoint == null) return
45
+
46
+ val x = startPoint.getDouble("x").toFloat()
47
+ val y = startPoint.getDouble("y").toFloat()
48
+ view.setStartPoint(x, y)
49
+ }
50
+
51
+ @ReactProp(name = "endPoint")
52
+ fun setEndPoint(view: LinearGradientView, endPoint: ReadableMap?) {
53
+ if (endPoint == null) return
54
+
55
+ val x = endPoint.getDouble("x").toFloat()
56
+ val y = endPoint.getDouble("y").toFloat()
57
+ view.setEndPoint(x, y)
58
+ }
59
+
60
+ @ReactProp(name = "useAngle")
61
+ fun setUseAngle(view: LinearGradientView, useAngle: Boolean) {
62
+ view.setUseAngle(useAngle)
63
+ }
64
+
65
+ @ReactProp(name = "angle")
66
+ fun setAngle(view: LinearGradientView, angle: Float) {
67
+ view.setAngle(angle)
68
+ }
69
+
70
+ @ReactProp(name = "angleCenter")
71
+ fun setAngleCenter(view: LinearGradientView, angleCenter: ReadableMap?) {
72
+ if (angleCenter == null) return
73
+
74
+ val x = angleCenter.getDouble("x").toFloat()
75
+ val y = angleCenter.getDouble("y").toFloat()
76
+ view.setAngleCenter(x, y)
77
+ }
78
+
79
+ companion object {
80
+ const val REACT_CLASS = "LinearGradientView"
81
+ }
82
+ }
@@ -0,0 +1,13 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ @interface LinearGradientView : UIView
4
+
5
+ @property (nonatomic, copy) NSArray<NSNumber *> *colors;
6
+ @property (nonatomic, copy) NSArray<NSNumber *> *locations;
7
+ @property (nonatomic, assign) CGPoint startPoint;
8
+ @property (nonatomic, assign) CGPoint endPoint;
9
+ @property (nonatomic, assign) BOOL useAngle;
10
+ @property (nonatomic, assign) CGFloat angle;
11
+ @property (nonatomic, assign) CGPoint angleCenter;
12
+
13
+ @end