react-native-platform-components 0.7.0 → 0.8.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 (34) hide show
  1. package/README.md +106 -0
  2. package/android/src/main/java/com/platformcomponents/PCLiquidGlassView.kt +84 -0
  3. package/android/src/main/java/com/platformcomponents/PCLiquidGlassViewManager.kt +52 -0
  4. package/android/src/main/java/com/platformcomponents/PlatformComponentsPackage.kt +1 -0
  5. package/ios/PCLiquidGlass.h +10 -0
  6. package/ios/PCLiquidGlass.mm +140 -0
  7. package/ios/PCLiquidGlass.swift +354 -0
  8. package/ios/PCSelectionMenu.swift +1 -1
  9. package/lib/commonjs/LiquidGlass.js +72 -0
  10. package/lib/commonjs/LiquidGlass.js.map +1 -0
  11. package/lib/commonjs/LiquidGlassNativeComponent.ts +110 -0
  12. package/lib/commonjs/index.js +11 -0
  13. package/lib/commonjs/index.js.map +1 -1
  14. package/lib/module/LiquidGlass.js +64 -0
  15. package/lib/module/LiquidGlass.js.map +1 -0
  16. package/lib/module/LiquidGlassNativeComponent.ts +110 -0
  17. package/lib/module/index.js +1 -0
  18. package/lib/module/index.js.map +1 -1
  19. package/lib/typescript/commonjs/src/LiquidGlass.d.ts +96 -0
  20. package/lib/typescript/commonjs/src/LiquidGlass.d.ts.map +1 -0
  21. package/lib/typescript/commonjs/src/LiquidGlassNativeComponent.d.ts +93 -0
  22. package/lib/typescript/commonjs/src/LiquidGlassNativeComponent.d.ts.map +1 -0
  23. package/lib/typescript/commonjs/src/index.d.ts +1 -0
  24. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  25. package/lib/typescript/module/src/LiquidGlass.d.ts +96 -0
  26. package/lib/typescript/module/src/LiquidGlass.d.ts.map +1 -0
  27. package/lib/typescript/module/src/LiquidGlassNativeComponent.d.ts +93 -0
  28. package/lib/typescript/module/src/LiquidGlassNativeComponent.d.ts.map +1 -0
  29. package/lib/typescript/module/src/index.d.ts +1 -0
  30. package/lib/typescript/module/src/index.d.ts.map +1 -1
  31. package/package.json +12 -4
  32. package/src/LiquidGlass.tsx +169 -0
  33. package/src/LiquidGlassNativeComponent.ts +110 -0
  34. package/src/index.tsx +1 -0
package/README.md CHANGED
@@ -40,6 +40,14 @@ This library focuses on **true native behavior**, not JavaScript re-implementati
40
40
  <td><img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/ios-segmentedcontrol.gif" height="550" /></td>
41
41
  <td><img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/android-segmentedcontrol.gif" height="550" /></td>
42
42
  </tr>
43
+ <tr>
44
+ <td align="center"><strong>iOS LiquidGlass</strong></td>
45
+ <td align="center"><strong>Android LiquidGlass</strong></td>
46
+ </tr>
47
+ <tr>
48
+ <td><img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/ios-liquidglass.gif" height="550" /></td>
49
+ <td align="center"><em>iOS 26+ only</em><br/><br/>On Android, renders as a<br/>regular View with optional<br/>fallback background color.</td>
50
+ </tr>
43
51
  </table>
44
52
 
45
53
  ### Components
@@ -48,6 +56,7 @@ This library focuses on **true native behavior**, not JavaScript re-implementati
48
56
  - **ContextMenu** – native context menus with long-press activation (UIContextMenuInteraction on iOS, PopupMenu on Android)
49
57
  - **SelectionMenu** – native selection menus (Material on Android, system menus on iOS)
50
58
  - **SegmentedControl** – native segmented controls (UISegmentedControl on iOS, MaterialButtonToggleGroup on Android)
59
+ - **LiquidGlass** – iOS 26+ glass morphism effects (UIGlassEffect on iOS, fallback View on Android)
51
60
 
52
61
  ### Goals
53
62
 
@@ -388,6 +397,42 @@ export function Example() {
388
397
 
389
398
  ---
390
399
 
400
+ ### LiquidGlass
401
+
402
+ ```tsx
403
+ import { LiquidGlass, isLiquidGlassSupported } from 'react-native-platform-components';
404
+ import { View, Text, Image } from 'react-native';
405
+
406
+ export function Example() {
407
+ return (
408
+ <View style={{ flex: 1 }}>
409
+ {/* Background content */}
410
+ <Image source={{ uri: 'https://example.com/photo.jpg' }} style={{ flex: 1 }} />
411
+
412
+ {/* Glass effect overlay */}
413
+ <LiquidGlass
414
+ style={{ position: 'absolute', top: 50, left: 20, right: 20, padding: 20 }}
415
+ cornerRadius={20}
416
+ ios={{
417
+ effect: 'regular',
418
+ interactive: true,
419
+ colorScheme: 'system',
420
+ }}
421
+ android={{
422
+ fallbackBackgroundColor: '#FFFFFF80',
423
+ }}
424
+ >
425
+ <Text style={{ fontSize: 18, fontWeight: '600' }}>
426
+ {isLiquidGlassSupported ? 'Glass Effect!' : 'Fallback View'}
427
+ </Text>
428
+ </LiquidGlass>
429
+ </View>
430
+ );
431
+ }
432
+ ```
433
+
434
+ ---
435
+
391
436
  ## Components
392
437
 
393
438
  ## DatePicker
@@ -557,6 +602,67 @@ Icons work the same as ContextMenu:
557
602
 
558
603
  ---
559
604
 
605
+ ## LiquidGlass
606
+
607
+ Native glass morphism effect using **UIGlassEffect** on iOS 26+. On Android and older iOS versions, renders as a regular View with optional fallback styling.
608
+
609
+ > **Note:** LiquidGlass requires **iOS 26+** (Xcode 16+). On older iOS versions and Android, the component renders children without the glass effect. Use `isLiquidGlassSupported` to check availability and provide fallback UI.
610
+
611
+ ### Props
612
+
613
+ | Prop | Type | Description |
614
+ |------|------|-------------|
615
+ | `cornerRadius` | `number` | Corner radius for the glass effect (default: `0`) |
616
+ | `children` | `ReactNode` | Content to render inside the glass container |
617
+
618
+ ### iOS Props (`ios`)
619
+
620
+ | Prop | Type | Description |
621
+ |------|------|-------------|
622
+ | `effect` | `'clear' \| 'regular' \| 'none'` | Glass effect intensity (default: `'regular'`) |
623
+ | `interactive` | `boolean` | Enable touch interaction feedback (default: `false`) |
624
+ | `tintColor` | `string` | Overlay tint color (hex string) |
625
+ | `colorScheme` | `'light' \| 'dark' \| 'system'` | Appearance mode (default: `'system'`) |
626
+ | `shadowRadius` | `number` | Shadow/glow radius (default: `20`) |
627
+ | `isHighlighted` | `boolean` | Manual highlight state control |
628
+
629
+ ### Android Props (`android`)
630
+
631
+ | Prop | Type | Description |
632
+ |------|------|-------------|
633
+ | `fallbackBackgroundColor` | `string` | Background color when glass effect unavailable |
634
+
635
+ ### Constants
636
+
637
+ | Export | Type | Description |
638
+ |--------|------|-------------|
639
+ | `isLiquidGlassSupported` | `boolean` | `true` on iOS 26+, `false` otherwise |
640
+
641
+ ### Effect Modes
642
+
643
+ - **`'regular'`** (default): Standard glass blur intensity with full glass morphism effect
644
+ - **`'clear'`**: More transparent, subtle glass effect
645
+ - **`'none'`**: No glass effect (useful for animating materialization/dematerialization)
646
+
647
+ ### Platform Behavior
648
+
649
+ | Platform | iOS 26+ | iOS < 26 | Android |
650
+ |----------|---------|----------|---------|
651
+ | Glass Effect | Full glass morphism | No effect | No effect |
652
+ | Corner Radius | Applied | Applied | Applied |
653
+ | Tint Color | Supported | Ignored | Ignored |
654
+ | Interactive | Supported | Ignored | Ignored |
655
+ | Fallback BG | N/A | Transparent | Configurable |
656
+
657
+ ### Usage Tips
658
+
659
+ 1. **Check support first**: Use `isLiquidGlassSupported` to conditionally render fallback UI
660
+ 2. **Background content**: Glass effects work best over images or colorful backgrounds
661
+ 3. **Interactive mode**: Only applies on mount; cannot be toggled after initial render
662
+ 4. **Android fallback**: Set `android.fallbackBackgroundColor` for a semi-transparent background
663
+
664
+ ---
665
+
560
666
  ## Design Philosophy
561
667
 
562
668
  - **Native first** — no JS re-implementation of pickers
@@ -0,0 +1,84 @@
1
+ package com.platformcomponents
2
+
3
+ import android.content.Context
4
+ import android.graphics.Color
5
+ import android.graphics.drawable.GradientDrawable
6
+ import android.view.View
7
+ import android.widget.FrameLayout
8
+
9
+ /**
10
+ * Android stub implementation for LiquidGlass.
11
+ *
12
+ * LiquidGlass is an iOS 26+ only feature. On Android, this component renders
13
+ * as a regular FrameLayout with optional fallback styling (background color, corner radius).
14
+ */
15
+ class PCLiquidGlassView(context: Context) : FrameLayout(context) {
16
+
17
+ companion object {
18
+ private const val TAG = "PCLiquidGlass"
19
+ }
20
+
21
+ // --- Props ---
22
+ var cornerRadius: Float = 0f
23
+ set(value) {
24
+ field = value
25
+ updateBackground()
26
+ }
27
+
28
+ var fallbackBackgroundColor: String? = null
29
+ set(value) {
30
+ field = value
31
+ updateBackground()
32
+ }
33
+
34
+ init {
35
+ // Ensure children can be rendered
36
+ clipChildren = false
37
+ clipToPadding = false
38
+ }
39
+
40
+ private fun updateBackground() {
41
+ val bgColor = fallbackBackgroundColor?.let { parseColor(it) }
42
+
43
+ if (cornerRadius > 0 || bgColor != null) {
44
+ val drawable = GradientDrawable().apply {
45
+ shape = GradientDrawable.RECTANGLE
46
+ cornerRadii = FloatArray(8) { cornerRadius * resources.displayMetrics.density }
47
+ setColor(bgColor ?: Color.TRANSPARENT)
48
+ }
49
+ background = drawable
50
+ clipToOutline = cornerRadius > 0
51
+ outlineProvider = android.view.ViewOutlineProvider.BACKGROUND
52
+ } else {
53
+ background = null
54
+ clipToOutline = false
55
+ }
56
+ }
57
+
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
+ // ---- Measurement ----
79
+
80
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
81
+ // Standard FrameLayout measurement
82
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
83
+ }
84
+ }
@@ -0,0 +1,52 @@
1
+ package com.platformcomponents
2
+
3
+ import com.facebook.react.bridge.ReadableMap
4
+ import com.facebook.react.uimanager.ThemedReactContext
5
+ import com.facebook.react.uimanager.ViewGroupManager
6
+ import com.facebook.react.uimanager.ViewManagerDelegate
7
+ import com.facebook.react.viewmanagers.PCLiquidGlassManagerDelegate
8
+ import com.facebook.react.viewmanagers.PCLiquidGlassManagerInterface
9
+
10
+ /**
11
+ * Android ViewManager for LiquidGlass.
12
+ *
13
+ * LiquidGlass is iOS-only, so this manager provides a stub implementation
14
+ * that renders a basic FrameLayout with optional fallback styling.
15
+ * Uses ViewGroupManager since LiquidGlass can contain children.
16
+ */
17
+ class PCLiquidGlassViewManager :
18
+ ViewGroupManager<PCLiquidGlassView>(),
19
+ PCLiquidGlassManagerInterface<PCLiquidGlassView> {
20
+
21
+ companion object {
22
+ private const val TAG = "PCLiquidGlass"
23
+ }
24
+
25
+ private val delegate: ViewManagerDelegate<PCLiquidGlassView> =
26
+ PCLiquidGlassManagerDelegate(this)
27
+
28
+ override fun getName(): String = "PCLiquidGlass"
29
+
30
+ override fun getDelegate(): ViewManagerDelegate<PCLiquidGlassView> = delegate
31
+
32
+ override fun createViewInstance(reactContext: ThemedReactContext): PCLiquidGlassView {
33
+ return PCLiquidGlassView(reactContext)
34
+ }
35
+
36
+ override fun setCornerRadius(view: PCLiquidGlassView, value: Float) {
37
+ view.cornerRadius = value
38
+ }
39
+
40
+ override fun setIos(view: PCLiquidGlassView, value: ReadableMap?) {
41
+ // iOS props are ignored on Android
42
+ }
43
+
44
+ override fun setAndroid(view: PCLiquidGlassView, value: ReadableMap?) {
45
+ val fallbackColor = value?.let {
46
+ if (it.hasKey("fallbackBackgroundColor") && !it.isNull("fallbackBackgroundColor")) {
47
+ it.getString("fallbackBackgroundColor")
48
+ } else null
49
+ }
50
+ view.fallbackBackgroundColor = fallbackColor
51
+ }
52
+ }
@@ -15,6 +15,7 @@ class PlatformComponentsViewPackage : ReactPackage {
15
15
  PCDatePickerViewManager(),
16
16
  PCContextMenuViewManager(),
17
17
  PCSegmentedControlViewManager(),
18
+ PCLiquidGlassViewManager(),
18
19
  )
19
20
  }
20
21
 
@@ -0,0 +1,10 @@
1
+ // PCLiquidGlass.h
2
+
3
+ #import <React/RCTViewComponentView.h>
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ @interface PCLiquidGlass : RCTViewComponentView
8
+ @end
9
+
10
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,140 @@
1
+ // PCLiquidGlass.mm
2
+
3
+ #import "PCLiquidGlass.h"
4
+
5
+ #import <React/RCTComponentViewFactory.h>
6
+ #import <React/RCTConversions.h>
7
+ #import <React/RCTFabricComponentsPlugins.h>
8
+
9
+ #import <react/renderer/components/PlatformComponentsViewSpec/ComponentDescriptors.h>
10
+ #import <react/renderer/components/PlatformComponentsViewSpec/EventEmitters.h>
11
+ #import <react/renderer/components/PlatformComponentsViewSpec/Props.h>
12
+
13
+ #if __has_include(<PlatformComponents/PlatformComponents-Swift.h>)
14
+ #import <PlatformComponents/PlatformComponents-Swift.h>
15
+ #else
16
+ #import "PlatformComponents-Swift.h"
17
+ #endif
18
+
19
+ using namespace facebook::react;
20
+
21
+ @implementation PCLiquidGlass {
22
+ PCLiquidGlassView *_view;
23
+ }
24
+
25
+ + (ComponentDescriptorProvider)componentDescriptorProvider {
26
+ return concreteComponentDescriptorProvider<PCLiquidGlassComponentDescriptor>();
27
+ }
28
+
29
+ - (instancetype)initWithFrame:(CGRect)frame {
30
+ if (self = [super initWithFrame:frame]) {
31
+ _view = [[PCLiquidGlassView alloc] initWithEffect:nil];
32
+ self.contentView = _view;
33
+
34
+ // Set up press callback
35
+ __weak __typeof(self) weakSelf = self;
36
+ _view.onPressCallback = ^(CGFloat x, CGFloat y) {
37
+ __typeof(self) strongSelf = weakSelf;
38
+ if (!strongSelf) return;
39
+
40
+ auto eventEmitter =
41
+ std::static_pointer_cast<const PCLiquidGlassEventEmitter>(
42
+ strongSelf->_eventEmitter);
43
+ if (!eventEmitter) return;
44
+
45
+ PCLiquidGlassEventEmitter::OnGlassPress payload = {
46
+ .x = (float)x,
47
+ .y = (float)y,
48
+ };
49
+ eventEmitter->onGlassPress(payload);
50
+ };
51
+ }
52
+ return self;
53
+ }
54
+
55
+ // Mount children into the UIVisualEffectView's contentView
56
+ - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
57
+ [_view.contentView insertSubview:childComponentView atIndex:index];
58
+ }
59
+
60
+ - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
61
+ [childComponentView removeFromSuperview];
62
+ }
63
+
64
+ - (void)updateProps:(Props::Shared const &)props
65
+ oldProps:(Props::Shared const &)oldProps {
66
+ const auto &newProps =
67
+ *std::static_pointer_cast<const PCLiquidGlassProps>(props);
68
+ const auto prevProps =
69
+ std::static_pointer_cast<const PCLiquidGlassProps>(oldProps);
70
+
71
+ BOOL needsSetup = NO;
72
+
73
+ // cornerRadius -> glassCornerRadius
74
+ if (!prevProps || newProps.cornerRadius != prevProps->cornerRadius) {
75
+ _view.glassCornerRadius = newProps.cornerRadius;
76
+ }
77
+
78
+ // iOS-specific props
79
+ const auto &newIos = newProps.ios;
80
+ const auto &oldIos = prevProps ? prevProps->ios : PCLiquidGlassIosStruct{};
81
+
82
+ // interactive - needs setup to re-create effect with isInteractive
83
+ if (!prevProps || newIos.interactive != oldIos.interactive) {
84
+ _view.interactive = (newIos.interactive == "true");
85
+ needsSetup = YES;
86
+ }
87
+
88
+ // effect -> effectStyle
89
+ if (!prevProps || newIos.effect != oldIos.effect) {
90
+ if (!newIos.effect.empty()) {
91
+ _view.effectStyle = [NSString stringWithUTF8String:newIos.effect.c_str()];
92
+ } else {
93
+ _view.effectStyle = @"regular";
94
+ }
95
+ needsSetup = YES;
96
+ }
97
+
98
+ // tintColor -> glassTintColor
99
+ if (!prevProps || newIos.tintColor != oldIos.tintColor) {
100
+ if (!newIos.tintColor.empty()) {
101
+ _view.glassTintColor = [NSString stringWithUTF8String:newIos.tintColor.c_str()];
102
+ } else {
103
+ _view.glassTintColor = nil;
104
+ }
105
+ needsSetup = YES;
106
+ }
107
+
108
+ // colorScheme
109
+ if (!prevProps || newIos.colorScheme != oldIos.colorScheme) {
110
+ if (!newIos.colorScheme.empty()) {
111
+ _view.colorScheme = [NSString stringWithUTF8String:newIos.colorScheme.c_str()];
112
+ } else {
113
+ _view.colorScheme = @"system";
114
+ }
115
+ needsSetup = YES;
116
+ }
117
+
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
+ // Apply glass effect if any glass-related props changed
129
+ if (needsSetup) {
130
+ [_view setupView];
131
+ }
132
+
133
+ [super updateProps:props oldProps:oldProps];
134
+ }
135
+
136
+ @end
137
+
138
+ Class<RCTComponentViewProtocol> PCLiquidGlassCls(void) {
139
+ return PCLiquidGlass.class;
140
+ }