react-native-platform-components 0.8.3 → 0.8.5

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 +29 -2
  2. package/android/src/main/java/com/platformcomponents/ColorParser.kt +275 -0
  3. package/android/src/main/java/com/platformcomponents/PCContextMenuView.kt +1 -12
  4. package/android/src/main/java/com/platformcomponents/PCLiquidGlassView.kt +1 -22
  5. package/ios/PCLiquidGlass.mm +1 -10
  6. package/ios/PCLiquidGlass.swift +265 -30
  7. package/ios/PCSegmentedControl.swift +259 -3
  8. package/lib/commonjs/LiquidGlass.js +2 -4
  9. package/lib/commonjs/LiquidGlass.js.map +1 -1
  10. package/lib/commonjs/LiquidGlassNativeComponent.ts +0 -11
  11. package/lib/commonjs/SelectionMenu.js.map +1 -1
  12. package/lib/module/LiquidGlass.js +2 -4
  13. package/lib/module/LiquidGlass.js.map +1 -1
  14. package/lib/module/LiquidGlassNativeComponent.ts +0 -11
  15. package/lib/module/SelectionMenu.js.map +1 -1
  16. package/lib/typescript/commonjs/src/LiquidGlass.d.ts +0 -12
  17. package/lib/typescript/commonjs/src/LiquidGlass.d.ts.map +1 -1
  18. package/lib/typescript/commonjs/src/LiquidGlassNativeComponent.d.ts +0 -9
  19. package/lib/typescript/commonjs/src/LiquidGlassNativeComponent.d.ts.map +1 -1
  20. package/lib/typescript/commonjs/src/SelectionMenu.d.ts +1 -0
  21. package/lib/typescript/commonjs/src/SelectionMenu.d.ts.map +1 -1
  22. package/lib/typescript/module/src/LiquidGlass.d.ts +0 -12
  23. package/lib/typescript/module/src/LiquidGlass.d.ts.map +1 -1
  24. package/lib/typescript/module/src/LiquidGlassNativeComponent.d.ts +0 -9
  25. package/lib/typescript/module/src/LiquidGlassNativeComponent.d.ts.map +1 -1
  26. package/lib/typescript/module/src/SelectionMenu.d.ts +1 -0
  27. package/lib/typescript/module/src/SelectionMenu.d.ts.map +1 -1
  28. package/package.json +1 -1
  29. package/src/LiquidGlass.tsx +1 -17
  30. package/src/LiquidGlassNativeComponent.ts +0 -11
  31. package/src/SelectionMenu.tsx +2 -0
package/README.md CHANGED
@@ -641,8 +641,6 @@ Native glass morphism effect using **UIGlassEffect** on iOS 26+. On Android and
641
641
  | `interactive` | `boolean` | Enable touch interaction feedback (default: `false`) |
642
642
  | `tintColor` | `string` | Overlay tint color (hex string) |
643
643
  | `colorScheme` | `'light' \| 'dark' \| 'system'` | Appearance mode (default: `'system'`) |
644
- | `shadowRadius` | `number` | Shadow/glow radius (default: `20`) |
645
- | `isHighlighted` | `boolean` | Manual highlight state control |
646
644
 
647
645
  ### Android Props (`android`)
648
646
 
@@ -702,6 +700,35 @@ This is intentional. The goal is native fidelity, not pixel-level customization.
702
700
 
703
701
  ---
704
702
 
703
+ ## Color Formats
704
+
705
+ All color props in this library support the same formats as React Native's `backgroundColor`:
706
+
707
+ | Format | Example | Description |
708
+ | --- | --- | --- |
709
+ | Hex | `#RGB`, `#RRGGBB`, `#RRGGBBAA` | Standard hex colors |
710
+ | RGB | `rgb(255, 0, 0)` | RGB values (0-255) |
711
+ | RGBA | `rgba(255, 0, 0, 0.5)` | RGB with alpha (0-1) |
712
+ | HSL | `hsl(0, 100%, 50%)` | Hue (0-360), saturation, lightness |
713
+ | HSLA | `hsla(0, 100%, 50%, 0.5)` | HSL with alpha (0-1) |
714
+ | Named | `red`, `steelblue`, `transparent` | CSS named colors |
715
+
716
+ **Props that accept colors:**
717
+
718
+ - `ContextMenu`: `imageColor` (icon tint)
719
+ - `SegmentedControl`: `ios.selectedSegmentTintColor`
720
+ - `LiquidGlass`: `ios.tintColor`, `android.fallbackBackgroundColor`
721
+
722
+ ```tsx
723
+ // All of these are equivalent
724
+ <ContextMenu actions={[{ id: '1', title: 'Red', imageColor: '#FF0000' }]} />
725
+ <ContextMenu actions={[{ id: '1', title: 'Red', imageColor: 'rgb(255, 0, 0)' }]} />
726
+ <ContextMenu actions={[{ id: '1', title: 'Red', imageColor: 'hsl(0, 100%, 50%)' }]} />
727
+ <ContextMenu actions={[{ id: '1', title: 'Red', imageColor: 'red' }]} />
728
+ ```
729
+
730
+ ---
731
+
705
732
  ## Android Theme Configuration
706
733
 
707
734
  > **⚠️ Your app may hard crash if you skip this section.** Android components require specific theme configuration. Components can crash immediately on mount if the required theme attributes are missing.
@@ -0,0 +1,275 @@
1
+ package com.platformcomponents
2
+
3
+ import android.graphics.Color
4
+ import kotlin.math.roundToInt
5
+
6
+ /**
7
+ * Parses color strings in React Native compatible formats:
8
+ * - Hex: #RGB, #RRGGBB, #RRGGBBAA
9
+ * - RGB: rgb(r, g, b) where r, g, b are 0-255
10
+ * - RGBA: rgba(r, g, b, a) where r, g, b are 0-255 and a is 0-1
11
+ * - HSL: hsl(h, s%, l%) where h is 0-360, s and l are 0-100
12
+ * - HSLA: hsla(h, s%, l%, a) where h is 0-360, s and l are 0-100, a is 0-1
13
+ * - Named colors: red, blue, transparent, etc. (CSS named colors)
14
+ */
15
+ object ColorParser {
16
+ fun parse(colorString: String): Int? {
17
+ return try {
18
+ val trimmed = colorString.trim().lowercase()
19
+
20
+ // Try named colors first
21
+ namedColors[trimmed]?.let { return it }
22
+
23
+ // Try rgba format: rgba(r, g, b, a)
24
+ if (trimmed.startsWith("rgba(") && trimmed.endsWith(")")) {
25
+ val inner = trimmed.removePrefix("rgba(").removeSuffix(")")
26
+ val parts = inner.split(",").map { it.trim() }
27
+ if (parts.size == 4) {
28
+ val r = parts[0].toInt().coerceIn(0, 255)
29
+ val g = parts[1].toInt().coerceIn(0, 255)
30
+ val b = parts[2].toInt().coerceIn(0, 255)
31
+ val a = (parts[3].toFloat() * 255).roundToInt().coerceIn(0, 255)
32
+ return Color.argb(a, r, g, b)
33
+ }
34
+ }
35
+
36
+ // Try rgb format: rgb(r, g, b)
37
+ if (trimmed.startsWith("rgb(") && trimmed.endsWith(")")) {
38
+ val inner = trimmed.removePrefix("rgb(").removeSuffix(")")
39
+ val parts = inner.split(",").map { it.trim() }
40
+ if (parts.size == 3) {
41
+ val r = parts[0].toInt().coerceIn(0, 255)
42
+ val g = parts[1].toInt().coerceIn(0, 255)
43
+ val b = parts[2].toInt().coerceIn(0, 255)
44
+ return Color.rgb(r, g, b)
45
+ }
46
+ }
47
+
48
+ // Try hsla format: hsla(h, s%, l%, a)
49
+ if (trimmed.startsWith("hsla(") && trimmed.endsWith(")")) {
50
+ val inner = trimmed.removePrefix("hsla(").removeSuffix(")")
51
+ val parts = inner.split(",").map { it.trim().removeSuffix("%") }
52
+ if (parts.size == 4) {
53
+ val h = parts[0].toFloat() / 360f
54
+ val s = parts[1].toFloat() / 100f
55
+ val l = parts[2].toFloat() / 100f
56
+ val a = parts[3].toFloat()
57
+ val rgb = hslToRgb(h, s, l)
58
+ return Color.argb((a * 255).roundToInt(), rgb.first, rgb.second, rgb.third)
59
+ }
60
+ }
61
+
62
+ // Try hsl format: hsl(h, s%, l%)
63
+ if (trimmed.startsWith("hsl(") && trimmed.endsWith(")")) {
64
+ val inner = trimmed.removePrefix("hsl(").removeSuffix(")")
65
+ val parts = inner.split(",").map { it.trim().removeSuffix("%") }
66
+ if (parts.size == 3) {
67
+ val h = parts[0].toFloat() / 360f
68
+ val s = parts[1].toFloat() / 100f
69
+ val l = parts[2].toFloat() / 100f
70
+ val rgb = hslToRgb(h, s, l)
71
+ return Color.rgb(rgb.first, rgb.second, rgb.third)
72
+ }
73
+ }
74
+
75
+ // Fall back to hex parsing
76
+ var sanitized = trimmed
77
+ if (!sanitized.startsWith("#")) {
78
+ sanitized = "#$sanitized"
79
+ }
80
+
81
+ // Handle #RRGGBBAA format (web/CSS style) by converting to #AARRGGBB (Android style)
82
+ if (sanitized.length == 9) {
83
+ val rrggbb = sanitized.substring(1, 7)
84
+ val aa = sanitized.substring(7, 9)
85
+ sanitized = "#$aa$rrggbb"
86
+ }
87
+
88
+ Color.parseColor(sanitized)
89
+ } catch (e: Exception) {
90
+ null
91
+ }
92
+ }
93
+
94
+ private fun hslToRgb(h: Float, s: Float, l: Float): Triple<Int, Int, Int> {
95
+ if (s == 0f) {
96
+ val v = (l * 255).roundToInt()
97
+ return Triple(v, v, v)
98
+ }
99
+
100
+ val q = if (l < 0.5f) l * (1 + s) else l + s - l * s
101
+ val p = 2 * l - q
102
+
103
+ fun hueToRgb(p: Float, q: Float, t: Float): Int {
104
+ var tt = t
105
+ if (tt < 0) tt += 1
106
+ if (tt > 1) tt -= 1
107
+ val result = when {
108
+ tt < 1f/6f -> p + (q - p) * 6 * tt
109
+ tt < 1f/2f -> q
110
+ tt < 2f/3f -> p + (q - p) * (2f/3f - tt) * 6
111
+ else -> p
112
+ }
113
+ return (result * 255).roundToInt().coerceIn(0, 255)
114
+ }
115
+
116
+ return Triple(
117
+ hueToRgb(p, q, h + 1f/3f),
118
+ hueToRgb(p, q, h),
119
+ hueToRgb(p, q, h - 1f/3f)
120
+ )
121
+ }
122
+
123
+ // CSS named colors (React Native compatible)
124
+ private val namedColors = mapOf(
125
+ "transparent" to Color.TRANSPARENT,
126
+ "aliceblue" to Color.rgb(240, 248, 255),
127
+ "antiquewhite" to Color.rgb(250, 235, 215),
128
+ "aqua" to Color.rgb(0, 255, 255),
129
+ "aquamarine" to Color.rgb(127, 255, 212),
130
+ "azure" to Color.rgb(240, 255, 255),
131
+ "beige" to Color.rgb(245, 245, 220),
132
+ "bisque" to Color.rgb(255, 228, 196),
133
+ "black" to Color.BLACK,
134
+ "blanchedalmond" to Color.rgb(255, 235, 205),
135
+ "blue" to Color.BLUE,
136
+ "blueviolet" to Color.rgb(138, 43, 226),
137
+ "brown" to Color.rgb(165, 42, 42),
138
+ "burlywood" to Color.rgb(222, 184, 135),
139
+ "cadetblue" to Color.rgb(95, 158, 160),
140
+ "chartreuse" to Color.rgb(127, 255, 0),
141
+ "chocolate" to Color.rgb(210, 105, 30),
142
+ "coral" to Color.rgb(255, 127, 80),
143
+ "cornflowerblue" to Color.rgb(100, 149, 237),
144
+ "cornsilk" to Color.rgb(255, 248, 220),
145
+ "crimson" to Color.rgb(220, 20, 60),
146
+ "cyan" to Color.CYAN,
147
+ "darkblue" to Color.rgb(0, 0, 139),
148
+ "darkcyan" to Color.rgb(0, 139, 139),
149
+ "darkgoldenrod" to Color.rgb(184, 134, 11),
150
+ "darkgray" to Color.DKGRAY,
151
+ "darkgreen" to Color.rgb(0, 100, 0),
152
+ "darkgrey" to Color.DKGRAY,
153
+ "darkkhaki" to Color.rgb(189, 183, 107),
154
+ "darkmagenta" to Color.rgb(139, 0, 139),
155
+ "darkolivegreen" to Color.rgb(85, 107, 47),
156
+ "darkorange" to Color.rgb(255, 140, 0),
157
+ "darkorchid" to Color.rgb(153, 50, 204),
158
+ "darkred" to Color.rgb(139, 0, 0),
159
+ "darksalmon" to Color.rgb(233, 150, 122),
160
+ "darkseagreen" to Color.rgb(143, 188, 143),
161
+ "darkslateblue" to Color.rgb(72, 61, 139),
162
+ "darkslategray" to Color.rgb(47, 79, 79),
163
+ "darkslategrey" to Color.rgb(47, 79, 79),
164
+ "darkturquoise" to Color.rgb(0, 206, 209),
165
+ "darkviolet" to Color.rgb(148, 0, 211),
166
+ "deeppink" to Color.rgb(255, 20, 147),
167
+ "deepskyblue" to Color.rgb(0, 191, 255),
168
+ "dimgray" to Color.rgb(105, 105, 105),
169
+ "dimgrey" to Color.rgb(105, 105, 105),
170
+ "dodgerblue" to Color.rgb(30, 144, 255),
171
+ "firebrick" to Color.rgb(178, 34, 34),
172
+ "floralwhite" to Color.rgb(255, 250, 240),
173
+ "forestgreen" to Color.rgb(34, 139, 34),
174
+ "fuchsia" to Color.rgb(255, 0, 255),
175
+ "gainsboro" to Color.rgb(220, 220, 220),
176
+ "ghostwhite" to Color.rgb(248, 248, 255),
177
+ "gold" to Color.rgb(255, 215, 0),
178
+ "goldenrod" to Color.rgb(218, 165, 32),
179
+ "gray" to Color.GRAY,
180
+ "green" to Color.GREEN,
181
+ "greenyellow" to Color.rgb(173, 255, 47),
182
+ "grey" to Color.GRAY,
183
+ "honeydew" to Color.rgb(240, 255, 240),
184
+ "hotpink" to Color.rgb(255, 105, 180),
185
+ "indianred" to Color.rgb(205, 92, 92),
186
+ "indigo" to Color.rgb(75, 0, 130),
187
+ "ivory" to Color.rgb(255, 255, 240),
188
+ "khaki" to Color.rgb(240, 230, 140),
189
+ "lavender" to Color.rgb(230, 230, 250),
190
+ "lavenderblush" to Color.rgb(255, 240, 245),
191
+ "lawngreen" to Color.rgb(124, 252, 0),
192
+ "lemonchiffon" to Color.rgb(255, 250, 205),
193
+ "lightblue" to Color.rgb(173, 216, 230),
194
+ "lightcoral" to Color.rgb(240, 128, 128),
195
+ "lightcyan" to Color.rgb(224, 255, 255),
196
+ "lightgoldenrodyellow" to Color.rgb(250, 250, 210),
197
+ "lightgray" to Color.LTGRAY,
198
+ "lightgreen" to Color.rgb(144, 238, 144),
199
+ "lightgrey" to Color.LTGRAY,
200
+ "lightpink" to Color.rgb(255, 182, 193),
201
+ "lightsalmon" to Color.rgb(255, 160, 122),
202
+ "lightseagreen" to Color.rgb(32, 178, 170),
203
+ "lightskyblue" to Color.rgb(135, 206, 250),
204
+ "lightslategray" to Color.rgb(119, 136, 153),
205
+ "lightslategrey" to Color.rgb(119, 136, 153),
206
+ "lightsteelblue" to Color.rgb(176, 196, 222),
207
+ "lightyellow" to Color.rgb(255, 255, 224),
208
+ "lime" to Color.rgb(0, 255, 0),
209
+ "limegreen" to Color.rgb(50, 205, 50),
210
+ "linen" to Color.rgb(250, 240, 230),
211
+ "magenta" to Color.MAGENTA,
212
+ "maroon" to Color.rgb(128, 0, 0),
213
+ "mediumaquamarine" to Color.rgb(102, 205, 170),
214
+ "mediumblue" to Color.rgb(0, 0, 205),
215
+ "mediumorchid" to Color.rgb(186, 85, 211),
216
+ "mediumpurple" to Color.rgb(147, 112, 219),
217
+ "mediumseagreen" to Color.rgb(60, 179, 113),
218
+ "mediumslateblue" to Color.rgb(123, 104, 238),
219
+ "mediumspringgreen" to Color.rgb(0, 250, 154),
220
+ "mediumturquoise" to Color.rgb(72, 209, 204),
221
+ "mediumvioletred" to Color.rgb(199, 21, 133),
222
+ "midnightblue" to Color.rgb(25, 25, 112),
223
+ "mintcream" to Color.rgb(245, 255, 250),
224
+ "mistyrose" to Color.rgb(255, 228, 225),
225
+ "moccasin" to Color.rgb(255, 228, 181),
226
+ "navajowhite" to Color.rgb(255, 222, 173),
227
+ "navy" to Color.rgb(0, 0, 128),
228
+ "oldlace" to Color.rgb(253, 245, 230),
229
+ "olive" to Color.rgb(128, 128, 0),
230
+ "olivedrab" to Color.rgb(107, 142, 35),
231
+ "orange" to Color.rgb(255, 165, 0),
232
+ "orangered" to Color.rgb(255, 69, 0),
233
+ "orchid" to Color.rgb(218, 112, 214),
234
+ "palegoldenrod" to Color.rgb(238, 232, 170),
235
+ "palegreen" to Color.rgb(152, 251, 152),
236
+ "paleturquoise" to Color.rgb(175, 238, 238),
237
+ "palevioletred" to Color.rgb(219, 112, 147),
238
+ "papayawhip" to Color.rgb(255, 239, 213),
239
+ "peachpuff" to Color.rgb(255, 218, 185),
240
+ "peru" to Color.rgb(205, 133, 63),
241
+ "pink" to Color.rgb(255, 192, 203),
242
+ "plum" to Color.rgb(221, 160, 221),
243
+ "powderblue" to Color.rgb(176, 224, 230),
244
+ "purple" to Color.rgb(128, 0, 128),
245
+ "rebeccapurple" to Color.rgb(102, 51, 153),
246
+ "red" to Color.RED,
247
+ "rosybrown" to Color.rgb(188, 143, 143),
248
+ "royalblue" to Color.rgb(65, 105, 225),
249
+ "saddlebrown" to Color.rgb(139, 69, 19),
250
+ "salmon" to Color.rgb(250, 128, 114),
251
+ "sandybrown" to Color.rgb(244, 164, 96),
252
+ "seagreen" to Color.rgb(46, 139, 87),
253
+ "seashell" to Color.rgb(255, 245, 238),
254
+ "sienna" to Color.rgb(160, 82, 45),
255
+ "silver" to Color.rgb(192, 192, 192),
256
+ "skyblue" to Color.rgb(135, 206, 235),
257
+ "slateblue" to Color.rgb(106, 90, 205),
258
+ "slategray" to Color.rgb(112, 128, 144),
259
+ "slategrey" to Color.rgb(112, 128, 144),
260
+ "snow" to Color.rgb(255, 250, 250),
261
+ "springgreen" to Color.rgb(0, 255, 127),
262
+ "steelblue" to Color.rgb(70, 130, 180),
263
+ "tan" to Color.rgb(210, 180, 140),
264
+ "teal" to Color.rgb(0, 128, 128),
265
+ "thistle" to Color.rgb(216, 191, 216),
266
+ "tomato" to Color.rgb(255, 99, 71),
267
+ "turquoise" to Color.rgb(64, 224, 208),
268
+ "violet" to Color.rgb(238, 130, 238),
269
+ "wheat" to Color.rgb(245, 222, 179),
270
+ "white" to Color.WHITE,
271
+ "whitesmoke" to Color.rgb(245, 245, 245),
272
+ "yellow" to Color.YELLOW,
273
+ "yellowgreen" to Color.rgb(154, 205, 50)
274
+ )
275
+ }
@@ -390,24 +390,13 @@ class PCContextMenuView(context: Context) : ReactViewGroup(context) {
390
390
  private fun tintDrawable(drawable: Drawable, colorString: String?): Drawable {
391
391
  if (colorString.isNullOrEmpty()) return drawable
392
392
 
393
- val color = parseColor(colorString) ?: return drawable
393
+ val color = ColorParser.parse(colorString) ?: return drawable
394
394
 
395
395
  val wrappedDrawable = DrawableCompat.wrap(drawable.mutate())
396
396
  DrawableCompat.setTint(wrappedDrawable, color)
397
397
  return wrappedDrawable
398
398
  }
399
399
 
400
- private fun parseColor(colorString: String): Int? {
401
- return try {
402
- // Support hex colors like "#FF0000" or "FF0000"
403
- val hex = if (colorString.startsWith("#")) colorString else "#$colorString"
404
- Color.parseColor(hex)
405
- } catch (e: Exception) {
406
- Log.d(TAG, "Failed to parse color: $colorString")
407
- null
408
- }
409
- }
410
-
411
400
  override fun onDetachedFromWindow() {
412
401
  cancelPendingLongPress()
413
402
  if (popupShowing) {
@@ -3,7 +3,6 @@ package com.platformcomponents
3
3
  import android.content.Context
4
4
  import android.graphics.Color
5
5
  import android.graphics.drawable.GradientDrawable
6
- import android.view.View
7
6
  import android.widget.FrameLayout
8
7
 
9
8
  /**
@@ -38,7 +37,7 @@ class PCLiquidGlassView(context: Context) : FrameLayout(context) {
38
37
  }
39
38
 
40
39
  private fun updateBackground() {
41
- val bgColor = fallbackBackgroundColor?.let { parseColor(it) }
40
+ val bgColor = fallbackBackgroundColor?.let { ColorParser.parse(it) }
42
41
 
43
42
  if (cornerRadius > 0 || bgColor != null) {
44
43
  val drawable = GradientDrawable().apply {
@@ -55,26 +54,6 @@ class PCLiquidGlassView(context: Context) : FrameLayout(context) {
55
54
  }
56
55
  }
57
56
 
58
- private fun parseColor(colorString: String): Int? {
59
- return try {
60
- var sanitized = colorString.trim()
61
- if (!sanitized.startsWith("#")) {
62
- sanitized = "#$sanitized"
63
- }
64
-
65
- // Handle #RRGGBBAA format (web/CSS style) by converting to #AARRGGBB (Android style)
66
- if (sanitized.length == 9) {
67
- val rrggbb = sanitized.substring(1, 7)
68
- val aa = sanitized.substring(7, 9)
69
- sanitized = "#$aa$rrggbb"
70
- }
71
-
72
- Color.parseColor(sanitized)
73
- } catch (e: Exception) {
74
- null
75
- }
76
- }
77
-
78
57
  // ---- Measurement ----
79
58
 
80
59
  override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
@@ -61,6 +61,7 @@ using namespace facebook::react;
61
61
  [childComponentView removeFromSuperview];
62
62
  }
63
63
 
64
+
64
65
  - (void)updateProps:(Props::Shared const &)props
65
66
  oldProps:(Props::Shared const &)oldProps {
66
67
  const auto &newProps =
@@ -115,16 +116,6 @@ using namespace facebook::react;
115
116
  needsSetup = YES;
116
117
  }
117
118
 
118
- // shadowRadius -> glassShadowRadius
119
- if (!prevProps || newIos.shadowRadius != oldIos.shadowRadius) {
120
- _view.glassShadowRadius = newIos.shadowRadius;
121
- }
122
-
123
- // isHighlighted
124
- if (!prevProps || newIos.isHighlighted != oldIos.isHighlighted) {
125
- _view.isHighlighted = (newIos.isHighlighted == "true");
126
- }
127
-
128
119
  // Apply glass effect if any glass-related props changed
129
120
  if (needsSetup) {
130
121
  [_view setupView];