react-native-platform-components 0.7.0 → 0.8.1

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 +238 -102
  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
@@ -7,6 +7,8 @@ High-quality **native UI components for React Native**, implemented with platfor
7
7
 
8
8
  This library focuses on **true native behavior**, not JavaScript re-implementations.
9
9
 
10
+ **Have a component request?** If there's a native UI component you'd like to see added, [open an issue](https://github.com/JarX-Concepts/react-native-platform-components/issues/new) describing the component and its native APIs on iOS and Android.
11
+
10
12
  <table>
11
13
  <tr>
12
14
  <td align="center"><strong>iOS DatePicker</strong></td>
@@ -40,6 +42,14 @@ This library focuses on **true native behavior**, not JavaScript re-implementati
40
42
  <td><img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/ios-segmentedcontrol.gif" height="550" /></td>
41
43
  <td><img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/android-segmentedcontrol.gif" height="550" /></td>
42
44
  </tr>
45
+ <tr>
46
+ <td align="center"><strong>iOS LiquidGlass</strong></td>
47
+ <td align="center"><strong>Android LiquidGlass</strong></td>
48
+ </tr>
49
+ <tr>
50
+ <td><img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/ios-liquidglass.gif" height="550" /></td>
51
+ <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>
52
+ </tr>
43
53
  </table>
44
54
 
45
55
  ### Components
@@ -48,6 +58,7 @@ This library focuses on **true native behavior**, not JavaScript re-implementati
48
58
  - **ContextMenu** – native context menus with long-press activation (UIContextMenuInteraction on iOS, PopupMenu on Android)
49
59
  - **SelectionMenu** – native selection menus (Material on Android, system menus on iOS)
50
60
  - **SegmentedControl** – native segmented controls (UISegmentedControl on iOS, MaterialButtonToggleGroup on Android)
61
+ - **LiquidGlass** – iOS 26+ glass morphism effects (UIGlassEffect on iOS, fallback View on Android)
51
62
 
52
63
  ### Goals
53
64
 
@@ -106,12 +117,11 @@ eas build --platform android
106
117
  **Config Plugin:**
107
118
 
108
119
  Add to your `app.json`:
120
+
109
121
  ```json
110
122
  {
111
123
  "expo": {
112
- "plugins": [
113
- ["react-native-platform-components/app.plugin", {}]
114
- ]
124
+ "plugins": [["react-native-platform-components/app.plugin", {}]]
115
125
  }
116
126
  }
117
127
  ```
@@ -124,18 +134,20 @@ For a complete working example, see the [`example-expo/`](./example-expo) direct
124
134
 
125
135
  This library is built for the **React Native New Architecture** (Fabric + TurboModules).
126
136
 
127
- | Feature | Status |
128
- |---------|--------|
129
- | Fabric (New Renderer) | Supported |
130
- | Codegen | Used for type-safe native bindings |
131
- | TurboModules | N/A (view components only) |
132
- | Old Architecture | Not supported |
137
+ | Feature | Status |
138
+ | --------------------- | ---------------------------------- |
139
+ | Fabric (New Renderer) | Supported |
140
+ | Codegen | Used for type-safe native bindings |
141
+ | TurboModules | N/A (view components only) |
142
+ | Old Architecture | Not supported |
133
143
 
134
144
  **Tested with:**
145
+
135
146
  - React Native 0.81+ (bare and Expo)
136
147
  - Expo SDK 54+
137
148
 
138
149
  **Requirements:**
150
+
139
151
  - New Architecture must be enabled in your app
140
152
  - For bare React Native: set `newArchEnabled=true` in `gradle.properties` (Android) and use the `RCT_NEW_ARCH_ENABLED` flag (iOS)
141
153
  - For Expo: set `"newArchEnabled": true` in `app.json`
@@ -167,8 +179,8 @@ export function Example() {
167
179
  setVisible(false);
168
180
  }}
169
181
  onClosed={() => setVisible(false)}
170
- ios={{preferredStyle: 'inline'}}
171
- android={{material: 'system'}}
182
+ ios={{ preferredStyle: 'inline' }}
183
+ android={{ material: 'system' }}
172
184
  />
173
185
  </>
174
186
  );
@@ -189,8 +201,8 @@ export function Example() {
189
201
  presentation="embedded"
190
202
  mode="date"
191
203
  onConfirm={(d) => setDate(d)}
192
- ios={{preferredStyle: 'inline'}}
193
- android={{material: 'system'}}
204
+ ios={{ preferredStyle: 'inline' }}
205
+ android={{ material: 'system' }}
194
206
  />
195
207
  );
196
208
  }
@@ -230,7 +242,9 @@ export function Example() {
230
242
  ]}
231
243
  onPressAction={(id, title) => setLastAction(title)}
232
244
  >
233
- <View style={{ padding: 20, backgroundColor: '#E8F4FD', borderRadius: 8 }}>
245
+ <View
246
+ style={{ padding: 20, backgroundColor: '#E8F4FD', borderRadius: 8 }}
247
+ >
234
248
  <Text>Long-press me</Text>
235
249
  </View>
236
250
  </ContextMenu>
@@ -388,6 +402,54 @@ export function Example() {
388
402
 
389
403
  ---
390
404
 
405
+ ### LiquidGlass
406
+
407
+ ```tsx
408
+ import {
409
+ LiquidGlass,
410
+ isLiquidGlassSupported,
411
+ } from 'react-native-platform-components';
412
+ import { View, Text, Image } from 'react-native';
413
+
414
+ export function Example() {
415
+ return (
416
+ <View style={{ flex: 1 }}>
417
+ {/* Background content */}
418
+ <Image
419
+ source={{ uri: 'https://example.com/photo.jpg' }}
420
+ style={{ flex: 1 }}
421
+ />
422
+
423
+ {/* Glass effect overlay */}
424
+ <LiquidGlass
425
+ style={{
426
+ position: 'absolute',
427
+ top: 50,
428
+ left: 20,
429
+ right: 20,
430
+ padding: 20,
431
+ }}
432
+ cornerRadius={20}
433
+ ios={{
434
+ effect: 'regular',
435
+ interactive: true,
436
+ colorScheme: 'system',
437
+ }}
438
+ android={{
439
+ fallbackBackgroundColor: '#FFFFFF80',
440
+ }}
441
+ >
442
+ <Text style={{ fontSize: 18, fontWeight: '600' }}>
443
+ {isLiquidGlassSupported ? 'Glass Effect!' : 'Fallback View'}
444
+ </Text>
445
+ </LiquidGlass>
446
+ </View>
447
+ );
448
+ }
449
+ ```
450
+
451
+ ---
452
+
391
453
  ## Components
392
454
 
393
455
  ## DatePicker
@@ -396,37 +458,37 @@ Native date & time picker using **platform system pickers**.
396
458
 
397
459
  ### Props
398
460
 
399
- | Prop | Type | Description |
400
- |------|------|-------------|
401
- | `date` | `Date \| null` | Controlled date value |
402
- | `minDate` | `Date \| null` | Minimum selectable date |
403
- | `maxDate` | `Date \| null` | Maximum selectable date |
404
- | `locale` | `string` | Locale identifier (e.g., `'en-US'`) |
405
- | `timeZoneName` | `string` | Time zone identifier |
406
- | `mode` | `'date' \| 'time' \| 'dateAndTime' \| 'countDownTimer'` | Picker mode |
407
- | `presentation` | `'modal' \| 'embedded'` | Presentation style |
408
- | `visible` | `boolean` | Controls modal visibility (modal mode only) |
409
- | `onConfirm` | `(date: Date) => void` | Called when user confirms selection |
410
- | `onClosed` | `() => void` | Called when modal is dismissed |
461
+ | Prop | Type | Description |
462
+ | -------------- | ------------------------------------------------------- | ------------------------------------------- |
463
+ | `date` | `Date \| null` | Controlled date value |
464
+ | `minDate` | `Date \| null` | Minimum selectable date |
465
+ | `maxDate` | `Date \| null` | Maximum selectable date |
466
+ | `locale` | `string` | Locale identifier (e.g., `'en-US'`) |
467
+ | `timeZoneName` | `string` | Time zone identifier |
468
+ | `mode` | `'date' \| 'time' \| 'dateAndTime' \| 'countDownTimer'` | Picker mode |
469
+ | `presentation` | `'modal' \| 'embedded'` | Presentation style |
470
+ | `visible` | `boolean` | Controls modal visibility (modal mode only) |
471
+ | `onConfirm` | `(date: Date) => void` | Called when user confirms selection |
472
+ | `onClosed` | `() => void` | Called when modal is dismissed |
411
473
 
412
474
  ### iOS Props (`ios`)
413
475
 
414
- | Prop | Type | Description |
415
- |------|------|-------------|
416
- | `preferredStyle` | `'automatic' \| 'compact' \| 'inline' \| 'wheels'` | iOS date picker style |
417
- | `countDownDurationSeconds` | `number` | Duration for countdown timer mode |
418
- | `minuteInterval` | `number` | Minute interval (1-30) |
419
- | `roundsToMinuteInterval` | `'inherit' \| 'round' \| 'noRound'` | Rounding behavior |
476
+ | Prop | Type | Description |
477
+ | -------------------------- | -------------------------------------------------- | --------------------------------- |
478
+ | `preferredStyle` | `'automatic' \| 'compact' \| 'inline' \| 'wheels'` | iOS date picker style |
479
+ | `countDownDurationSeconds` | `number` | Duration for countdown timer mode |
480
+ | `minuteInterval` | `number` | Minute interval (1-30) |
481
+ | `roundsToMinuteInterval` | `'inherit' \| 'round' \| 'noRound'` | Rounding behavior |
420
482
 
421
483
  ### Android Props (`android`)
422
484
 
423
- | Prop | Type | Description |
424
- |------|------|-------------|
425
- | `firstDayOfWeek` | `number` | First day of week (1-7, Sunday=1) |
426
- | `material` | `'system' \| 'm3'` | Material Design style (modal only; embedded always uses system picker) |
427
- | `dialogTitle` | `string` | Custom dialog title |
428
- | `positiveButtonTitle` | `string` | Custom confirm button text |
429
- | `negativeButtonTitle` | `string` | Custom cancel button text |
485
+ | Prop | Type | Description |
486
+ | --------------------- | ------------------ | ---------------------------------------------------------------------- |
487
+ | `firstDayOfWeek` | `number` | First day of week (1-7, Sunday=1) |
488
+ | `material` | `'system' \| 'm3'` | Material Design style (modal only; embedded always uses system picker) |
489
+ | `dialogTitle` | `string` | Custom dialog title |
490
+ | `positiveButtonTitle` | `string` | Custom confirm button text |
491
+ | `negativeButtonTitle` | `string` | Custom cancel button text |
430
492
 
431
493
  ---
432
494
 
@@ -436,42 +498,42 @@ Native context menu that wraps content and responds to **long-press** or **tap**
436
498
 
437
499
  ### Props
438
500
 
439
- | Prop | Type | Description |
440
- |------|------|-------------|
441
- | `title` | `string` | Menu title (shown as header on iOS) |
442
- | `actions` | `ContextMenuAction[]` | Array of menu actions |
443
- | `disabled` | `boolean` | Disables the menu |
444
- | `trigger` | `'longPress' \| 'tap'` | How the menu opens (default: `'longPress'`) |
445
- | `onPressAction` | `(actionId, actionTitle) => void` | Called when user selects an action |
446
- | `onMenuOpen` | `() => void` | Called when menu opens |
447
- | `onMenuClose` | `() => void` | Called when menu closes |
448
- | `children` | `ReactNode` | Content to wrap (required) |
501
+ | Prop | Type | Description |
502
+ | --------------- | --------------------------------- | ------------------------------------------- |
503
+ | `title` | `string` | Menu title (shown as header on iOS) |
504
+ | `actions` | `ContextMenuAction[]` | Array of menu actions |
505
+ | `disabled` | `boolean` | Disables the menu |
506
+ | `trigger` | `'longPress' \| 'tap'` | How the menu opens (default: `'longPress'`) |
507
+ | `onPressAction` | `(actionId, actionTitle) => void` | Called when user selects an action |
508
+ | `onMenuOpen` | `() => void` | Called when menu opens |
509
+ | `onMenuClose` | `() => void` | Called when menu closes |
510
+ | `children` | `ReactNode` | Content to wrap (required) |
449
511
 
450
512
  ### ContextMenuAction
451
513
 
452
- | Property | Type | Description |
453
- |----------|------|-------------|
454
- | `id` | `string` | Unique identifier returned in callbacks |
455
- | `title` | `string` | Display text |
456
- | `subtitle` | `string` | Secondary text (iOS only) |
457
- | `image` | `string` | Icon name (SF Symbol on iOS, drawable on Android) |
458
- | `imageColor` | `string` | Tint color for the icon (hex string) |
459
- | `attributes` | `{ destructive?, disabled?, hidden? }` | Action attributes |
460
- | `state` | `'off' \| 'on' \| 'mixed'` | Checkmark state |
461
- | `subactions` | `ContextMenuAction[]` | Nested actions for submenu |
514
+ | Property | Type | Description |
515
+ | ------------ | -------------------------------------- | ------------------------------------------------- |
516
+ | `id` | `string` | Unique identifier returned in callbacks |
517
+ | `title` | `string` | Display text |
518
+ | `subtitle` | `string` | Secondary text (iOS only) |
519
+ | `image` | `string` | Icon name (SF Symbol on iOS, drawable on Android) |
520
+ | `imageColor` | `string` | Tint color for the icon (hex string) |
521
+ | `attributes` | `{ destructive?, disabled?, hidden? }` | Action attributes |
522
+ | `state` | `'off' \| 'on' \| 'mixed'` | Checkmark state |
523
+ | `subactions` | `ContextMenuAction[]` | Nested actions for submenu |
462
524
 
463
525
  ### iOS Props (`ios`)
464
526
 
465
- | Prop | Type | Description |
466
- |------|------|-------------|
527
+ | Prop | Type | Description |
528
+ | --------------- | --------- | --------------------------------- |
467
529
  | `enablePreview` | `boolean` | Enable preview when long-pressing |
468
530
 
469
531
  ### Android Props (`android`)
470
532
 
471
- | Prop | Type | Description |
472
- |------|------|-------------|
473
- | `anchorPosition` | `'left' \| 'right'` | Anchor position for the popup menu |
474
- | `visible` | `boolean` | Programmatic visibility control (Android only) |
533
+ | Prop | Type | Description |
534
+ | ---------------- | ------------------- | ---------------------------------------------- |
535
+ | `anchorPosition` | `'left' \| 'right'` | Anchor position for the popup menu |
536
+ | `visible` | `boolean` | Programmatic visibility control (Android only) |
475
537
 
476
538
  ### Trigger Modes
477
539
 
@@ -492,17 +554,17 @@ Native selection menu with **modal** and **embedded** modes.
492
554
 
493
555
  ### Props
494
556
 
495
- | Prop | Type | Description |
496
- |------|------|-------------|
497
- | `options` | `{ label: string; data: string }[]` | Array of options to display |
498
- | `selected` | `string \| null` | Currently selected option's `data` value |
499
- | `disabled` | `boolean` | Disables the menu |
500
- | `placeholder` | `string` | Placeholder text when no selection |
501
- | `presentation` | `'modal' \| 'embedded'` | Presentation mode (default: `'modal'`) |
502
- | `visible` | `boolean` | Controls modal mode menu visibility |
503
- | `onSelect` | `(data, label, index) => void` | Called when user selects an option |
504
- | `onRequestClose` | `() => void` | Called when menu is dismissed without selection |
505
- | `android.material` | `'system' \| 'm3'` | Material Design style preference |
557
+ | Prop | Type | Description |
558
+ | ------------------ | ----------------------------------- | ----------------------------------------------- |
559
+ | `options` | `{ label: string; data: string }[]` | Array of options to display |
560
+ | `selected` | `string \| null` | Currently selected option's `data` value |
561
+ | `disabled` | `boolean` | Disables the menu |
562
+ | `placeholder` | `string` | Placeholder text when no selection |
563
+ | `presentation` | `'modal' \| 'embedded'` | Presentation mode (default: `'modal'`) |
564
+ | `visible` | `boolean` | Controls modal mode menu visibility |
565
+ | `onSelect` | `(data, label, index) => void` | Called when user selects an option |
566
+ | `onRequestClose` | `() => void` | Called when menu is dismissed without selection |
567
+ | `android.material` | `'system' \| 'm3'` | Material Design style preference |
506
568
 
507
569
  ### Modes
508
570
 
@@ -519,44 +581,106 @@ Native segmented control using **UISegmentedControl** on iOS and **MaterialButto
519
581
 
520
582
  ### Props
521
583
 
522
- | Prop | Type | Description |
523
- |------|------|-------------|
524
- | `segments` | `SegmentedControlSegment[]` | Array of segments to display |
525
- | `selectedValue` | `string \| null` | Currently selected segment's `value` |
526
- | `disabled` | `boolean` | Disables the entire control |
527
- | `onSelect` | `(value: string, index: number) => void` | Called when user selects a segment |
584
+ | Prop | Type | Description |
585
+ | --------------- | ---------------------------------------- | ------------------------------------ |
586
+ | `segments` | `SegmentedControlSegment[]` | Array of segments to display |
587
+ | `selectedValue` | `string \| null` | Currently selected segment's `value` |
588
+ | `disabled` | `boolean` | Disables the entire control |
589
+ | `onSelect` | `(value: string, index: number) => void` | Called when user selects a segment |
528
590
 
529
591
  ### SegmentedControlSegment
530
592
 
531
- | Property | Type | Description |
532
- |----------|------|-------------|
533
- | `label` | `string` | Display text for the segment |
534
- | `value` | `string` | Unique value returned in callbacks |
535
- | `disabled` | `boolean` | Disables this specific segment |
536
- | `icon` | `string` | Icon name (SF Symbol on iOS, drawable on Android) |
593
+ | Property | Type | Description |
594
+ | ---------- | --------- | ------------------------------------------------- |
595
+ | `label` | `string` | Display text for the segment |
596
+ | `value` | `string` | Unique value returned in callbacks |
597
+ | `disabled` | `boolean` | Disables this specific segment |
598
+ | `icon` | `string` | Icon name (SF Symbol on iOS, drawable on Android) |
537
599
 
538
600
  ### iOS Props (`ios`)
539
601
 
540
- | Prop | Type | Description |
541
- |------|------|-------------|
542
- | `momentary` | `boolean` | If true, segments don't show selected state |
602
+ | Prop | Type | Description |
603
+ | ---------------------------------- | --------- | --------------------------------------------------- |
604
+ | `momentary` | `boolean` | If true, segments don't show selected state |
543
605
  | `apportionsSegmentWidthsByContent` | `boolean` | If true, segment widths are proportional to content |
544
- | `selectedSegmentTintColor` | `string` | Tint color for selected segment (hex string) |
606
+ | `selectedSegmentTintColor` | `string` | Tint color for selected segment (hex string) |
545
607
 
546
608
  ### Android Props (`android`)
547
609
 
548
- | Prop | Type | Description |
549
- |------|------|-------------|
610
+ | Prop | Type | Description |
611
+ | ------------------- | --------- | -------------------------------------------- |
550
612
  | `selectionRequired` | `boolean` | If true, one segment must always be selected |
551
613
 
552
614
  ### Icon Support
553
615
 
554
616
  Icons work the same as ContextMenu:
617
+
555
618
  - **iOS**: Use SF Symbol names (e.g., `'list.bullet'`, `'square.grid.2x2'`)
556
619
  - **Android**: Use drawable resource names (e.g., `'list_bullet'`, `'grid_view'`)
557
620
 
558
621
  ---
559
622
 
623
+ ## LiquidGlass
624
+
625
+ Native glass morphism effect using **UIGlassEffect** on iOS 26+. On Android and older iOS versions, renders as a regular View with optional fallback styling.
626
+
627
+ > **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.
628
+
629
+ ### Props
630
+
631
+ | Prop | Type | Description |
632
+ | -------------- | ----------- | ------------------------------------------------- |
633
+ | `cornerRadius` | `number` | Corner radius for the glass effect (default: `0`) |
634
+ | `children` | `ReactNode` | Content to render inside the glass container |
635
+
636
+ ### iOS Props (`ios`)
637
+
638
+ | Prop | Type | Description |
639
+ | --------------- | -------------------------------- | ---------------------------------------------------- |
640
+ | `effect` | `'clear' \| 'regular' \| 'none'` | Glass effect intensity (default: `'regular'`) |
641
+ | `interactive` | `boolean` | Enable touch interaction feedback (default: `false`) |
642
+ | `tintColor` | `string` | Overlay tint color (hex string) |
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
+
647
+ ### Android Props (`android`)
648
+
649
+ | Prop | Type | Description |
650
+ | ------------------------- | -------- | ---------------------------------------------- |
651
+ | `fallbackBackgroundColor` | `string` | Background color when glass effect unavailable |
652
+
653
+ ### Constants
654
+
655
+ | Export | Type | Description |
656
+ | ------------------------ | --------- | ------------------------------------ |
657
+ | `isLiquidGlassSupported` | `boolean` | `true` on iOS 26+, `false` otherwise |
658
+
659
+ ### Effect Modes
660
+
661
+ - **`'regular'`** (default): Standard glass blur intensity with full glass morphism effect
662
+ - **`'clear'`**: More transparent, subtle glass effect
663
+ - **`'none'`**: No glass effect (useful for animating materialization/dematerialization)
664
+
665
+ ### Platform Behavior
666
+
667
+ | Platform | iOS 26+ | iOS < 26 | Android |
668
+ | ------------- | ------------------- | ----------- | ------------ |
669
+ | Glass Effect | Full glass morphism | No effect | No effect |
670
+ | Corner Radius | Applied | Applied | Applied |
671
+ | Tint Color | Supported | Ignored | Ignored |
672
+ | Interactive | Supported | Ignored | Ignored |
673
+ | Fallback BG | N/A | Transparent | Configurable |
674
+
675
+ ### Usage Tips
676
+
677
+ 1. **Check support first**: Use `isLiquidGlassSupported` to conditionally render fallback UI
678
+ 2. **Background content**: Glass effects work best over images or colorful backgrounds
679
+ 3. **Interactive mode**: Only applies on mount; cannot be toggled after initial render
680
+ 4. **Android fallback**: Set `android.fallbackBackgroundColor` for a semi-transparent background
681
+
682
+ ---
683
+
560
684
  ## Design Philosophy
561
685
 
562
686
  - **Native first** — no JS re-implementation of pickers
@@ -588,11 +712,11 @@ Use [SF Symbols](https://developer.apple.com/sf-symbols/) names. These are built
588
712
 
589
713
  ```tsx
590
714
  // Common SF Symbols
591
- image: 'doc.on.doc' // Copy
592
- image: 'square.and.arrow.up' // Share
593
- image: 'trash' // Delete
594
- image: 'pencil' // Edit
595
- image: 'checkmark.circle' // Checkmark
715
+ image: 'doc.on.doc'; // Copy
716
+ image: 'square.and.arrow.up'; // Share
717
+ image: 'trash'; // Delete
718
+ image: 'pencil'; // Edit
719
+ image: 'checkmark.circle'; // Checkmark
596
720
  ```
597
721
 
598
722
  Browse available symbols using Apple's SF Symbols app or [sfsymbols.com](https://sfsymbols.com).
@@ -603,9 +727,9 @@ Use drawable resource names from your app's `res/drawable` directory. You must a
603
727
 
604
728
  ```tsx
605
729
  // Reference drawable by name (without extension)
606
- image: 'content_copy' // res/drawable/content_copy.xml
607
- image: 'share' // res/drawable/share.xml
608
- image: 'delete' // res/drawable/delete.xml
730
+ image: 'content_copy'; // res/drawable/content_copy.xml
731
+ image: 'share'; // res/drawable/share.xml
732
+ image: 'delete'; // res/drawable/delete.xml
609
733
  ```
610
734
 
611
735
  **Adding drawable resources:**
@@ -666,3 +790,15 @@ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the
666
790
  ## License
667
791
 
668
792
  MIT
793
+
794
+ ---
795
+
796
+ ## Author
797
+
798
+ **Andrew Tosh** — Santa Barbara, California
799
+
800
+ Full-stack developer with deep experience across entertainment and defense industries, specializing in simulation, visualization, and cross-platform mobile development. Technical focus areas include React Native, Rust, C++, real-time 3D visualization, and game engine technologies.
801
+
802
+ **Available for contract work** — Always interested in connecting with new clients for mobile development, visualization systems, and related projects. Hit me up at [andrew.tosh@jarxconcepts.com](mailto:andrew.tosh@jarxconcepts.com).
803
+
804
+ [LinkedIn](https://www.linkedin.com/in/atosh/) · [JarX Concepts](https://github.com/JarX-Concepts)
@@ -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