react-native-platform-components 0.9.0 → 1.0.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.
package/README.md CHANGED
@@ -2,12 +2,50 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/react-native-platform-components.svg)](https://www.npmjs.com/package/react-native-platform-components)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/react-native-platform-components.svg)](https://www.npmjs.com/package/react-native-platform-components)
5
+ [![CI](https://github.com/JarX-Concepts/react-native-platform-components/actions/workflows/ci.yml/badge.svg)](https://github.com/JarX-Concepts/react-native-platform-components/actions/workflows/ci.yml)
6
+ [![license](https://img.shields.io/npm/l/react-native-platform-components.svg)](./LICENSE)
5
7
 
6
- High-quality **native UI components for React Native**, implemented with platform-first APIs and exposed through clean, typed JavaScript interfaces.
8
+ Native **DatePicker**, **ContextMenu**, **SelectionMenu**, **SegmentedControl** and **LiquidGlass** for React Native. Every component is the real platform widget on both iOS and Android (UIKit and SwiftUI on iOS, Material 3 on Android), behind one typed, declarative API. No JavaScript re-implementations.
7
9
 
8
- This library focuses on **true native behavior**, not JavaScript re-implementations.
10
+ ```sh
11
+ npm install react-native-platform-components
12
+ ```
9
13
 
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.
14
+ ```tsx
15
+ import { SegmentedControl } from 'react-native-platform-components';
16
+
17
+ <SegmentedControl
18
+ segments={[
19
+ { label: 'Day', value: 'day' },
20
+ { label: 'Week', value: 'week' },
21
+ ]}
22
+ selectedValue={range}
23
+ onSelect={setRange}
24
+ />;
25
+ ```
26
+
27
+ That renders `UISegmentedControl` on iOS and Material 3 segmented buttons (`MaterialButtonToggleGroup`) on Android. The library needs the New Architecture: React Native 0.81+, or Expo SDK 54+ with a dev client (not Expo Go). See [Installation](#installation) and the [documentation site](https://jarx-concepts.github.io/react-native-platform-components).
28
+
29
+ ## Why this library
30
+
31
+ | You need | This library | Common alternative |
32
+ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
33
+ | Date and time pickers | `UIDatePicker` on iOS; `MaterialDatePicker` / `MaterialTimePicker` or the AppCompat dialogs on Android. Declarative `visible` prop, modal or embedded. | `@react-native-community/datetimepicker`: platform dialogs on Android; Material 3 pickers are an [open request](https://github.com/react-native-datetimepicker/datetimepicker/issues/790). |
34
+ | Segmented control | `UISegmentedControl` on iOS; Material 3 segmented buttons on Android. Icons and badges on both. | `@react-native-segmented-control/segmented-control`: native on iOS, drawn in JavaScript on Android. |
35
+ | Context menu | `UIContextMenuInteraction` on iOS; `PopupMenu` on Android. Long-press gesture or modal trigger, icons on both. | `zeego`: native on both platforms, through two additional native dependencies (`react-native-ios-context-menu`, `@react-native-menu/menu`). |
36
+ | Selection menu | System menus on iOS; Material exposed dropdown or `Spinner` on Android. Headless or inline. | `@react-native-picker/picker`: wheel picker on iOS, dialog or dropdown `Spinner` on Android; no menu-style presentation. |
37
+ | Liquid glass | `UIGlassEffect` on iOS 26+, a fallback `View` elsewhere, `isLiquidGlassSupported` flag. | `@callstack/liquid-glass`, `expo-glass-effect`: same idea on iOS; here it ships with the components above. |
38
+ | All of the above | One package, one API shape (platform-only props under `ios={{ }}` and `android={{ }}`), Fabric + Codegen typed bindings, Expo config plugin, TypeScript. | Four or five packages with different conventions, install steps and upgrade cadences. |
39
+
40
+ ## Components
41
+
42
+ | Component | iOS | Android | Docs |
43
+ | -------------------- | -------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
44
+ | **DatePicker** | `UIDatePicker` | `MaterialDatePicker` / `MaterialTimePicker` or AppCompat dialogs | [DatePicker](https://jarx-concepts.github.io/react-native-platform-components/components/datepicker) |
45
+ | **ContextMenu** | `UIContextMenuInteraction` | `PopupMenu` | [ContextMenu](https://jarx-concepts.github.io/react-native-platform-components/components/contextmenu) |
46
+ | **SelectionMenu** | System menus | Material exposed dropdown or `Spinner` | [SelectionMenu](https://jarx-concepts.github.io/react-native-platform-components/components/selectionmenu) |
47
+ | **SegmentedControl** | `UISegmentedControl` | `MaterialButtonToggleGroup` (Material 3 segmented buttons) | [SegmentedControl](https://jarx-concepts.github.io/react-native-platform-components/components/segmentedcontrol) |
48
+ | **LiquidGlass** | `UIGlassEffect` (iOS 26+) | Fallback `View` | [LiquidGlass](https://jarx-concepts.github.io/react-native-platform-components/components/liquidglass) |
11
49
 
12
50
  <table>
13
51
  <tr>
@@ -52,20 +90,9 @@ This library focuses on **true native behavior**, not JavaScript re-implementati
52
90
  </tr>
53
91
  </table>
54
92
 
55
- ### Components
56
-
57
- - **DatePicker** – native date & time pickers with modal and embedded presentations
58
- - **ContextMenu** – native context menus with long-press activation (UIContextMenuInteraction on iOS, PopupMenu on Android)
59
- - **SelectionMenu** – native selection menus (Material on Android, system menus on iOS)
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)
62
-
63
- ### Goals
93
+ **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.
64
94
 
65
- - Feel **100% native** on each platform
66
- - Support modern platform design systems (Material 3 on Android, system pickers on iOS)
67
- - Offer **headless** and **inline** modes for maximum layout control
68
- - Integrate cleanly with **React Native Codegen / Fabric**
95
+ **Using it in a shipped app?** Open a PR to list it here.
69
96
 
70
97
  ---
71
98
 
@@ -77,898 +104,11 @@ npm install react-native-platform-components
77
104
  yarn add react-native-platform-components
78
105
  ```
79
106
 
80
- ### iOS
107
+ **iOS:** `cd ios && pod install`. Nothing else to configure.
81
108
 
82
- ```sh
83
- cd ios
84
- pod install
85
- ```
86
-
87
- - Minimum iOS version: **iOS 13+**
88
- - Uses `UIDatePicker`, SwiftUI Menu, and `UIContextMenuInteraction`
89
-
90
- ### Android
91
-
92
- - Uses native Android Views with Material Design (including `PopupMenu` for context menus)
93
- - Supports **Material 3** styling
94
- - **⚠️ Your app may crash if theme is not configured** — See [Android Theme Configuration](#android-theme-configuration) below
95
-
96
- ### Expo (Managed Workflow)
97
-
98
- > **Note:** This library is **not supported in Expo Go**. It requires native code and must be used with [Expo Dev Client](https://docs.expo.dev/develop/development-builds/introduction/) or EAS Build.
99
-
100
- ```sh
101
- npx expo install react-native-platform-components
102
- npx expo prebuild
103
- npx expo run:ios
104
- # or
105
- npx expo run:android
106
- ```
107
-
108
- The library includes an Expo config plugin that handles all native configuration automatically. No manual native setup is required.
109
-
110
- **EAS Build:**
111
-
112
- ```sh
113
- eas build --platform ios
114
- eas build --platform android
115
- ```
116
-
117
- **Config Plugin:**
109
+ **Android:** nothing to configure. The components work with the default AppCompat app theme; give the app a Material 3 theme to have `SegmentedControl` and the Material pickers use your colors. See [Android Theme Configuration](https://jarx-concepts.github.io/react-native-platform-components/guides/android-theme).
118
110
 
119
- Add to your `app.json`:
120
-
121
- ```json
122
- {
123
- "expo": {
124
- "plugins": [["react-native-platform-components/app.plugin", {}]]
125
- }
126
- }
127
- ```
128
-
129
- For a complete working example, see the [`example-expo/`](./example-expo) directory.
130
-
131
- ---
132
-
133
- ## React Native New Architecture
134
-
135
- This library is built for the **React Native New Architecture** (Fabric + TurboModules).
136
-
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 |
143
-
144
- **Tested with:**
145
-
146
- - React Native 0.81+ (bare and Expo)
147
- - Expo SDK 54+
148
-
149
- **Requirements:**
150
-
151
- - New Architecture must be enabled in your app
152
- - For bare React Native: set `newArchEnabled=true` in `gradle.properties` (Android) and use the `RCT_NEW_ARCH_ENABLED` flag (iOS)
153
- - For Expo: set `"newArchEnabled": true` in `app.json`
154
-
155
- ---
156
-
157
- ## Quick Start
158
-
159
- ### DatePicker (Modal)
160
-
161
- ```tsx
162
- import { DatePicker } from 'react-native-platform-components';
163
-
164
- export function Example() {
165
- const [date, setDate] = React.useState<Date | null>(null);
166
- const [visible, setVisible] = React.useState(false);
167
-
168
- return (
169
- <>
170
- <Button title="Pick date" onPress={() => setVisible(true)} />
171
-
172
- <DatePicker
173
- date={date}
174
- visible={visible}
175
- presentation="modal"
176
- mode="date"
177
- onConfirm={(d, confirmed) => {
178
- setDate(d);
179
- if (confirmed) setVisible(false);
180
- }}
181
- onClosed={() => setVisible(false)}
182
- ios={{ preferredStyle: 'inline' }}
183
- android={{ material: 'system' }}
184
- />
185
- </>
186
- );
187
- }
188
- ```
189
-
190
- ### DatePicker (Embedded)
191
-
192
- ```tsx
193
- import { DatePicker } from 'react-native-platform-components';
194
-
195
- export function Example() {
196
- const [date, setDate] = React.useState<Date | null>(new Date());
197
-
198
- return (
199
- <DatePicker
200
- date={date}
201
- presentation="embedded"
202
- mode="date"
203
- onConfirm={(d, confirmed) => setDate(d)}
204
- ios={{ preferredStyle: 'inline' }}
205
- android={{ material: 'system' }}
206
- />
207
- );
208
- }
209
- ```
210
-
211
- ---
212
-
213
- ### ContextMenu (Gesture Mode)
214
-
215
- ```tsx
216
- import { ContextMenu } from 'react-native-platform-components';
217
- import { Platform, View, Text } from 'react-native';
218
-
219
- export function Example() {
220
- const [lastAction, setLastAction] = React.useState<string | null>(null);
221
-
222
- return (
223
- <ContextMenu
224
- title="Options"
225
- actions={[
226
- {
227
- id: 'copy',
228
- title: 'Copy',
229
- image: Platform.OS === 'ios' ? 'doc.on.doc' : 'content_copy',
230
- },
231
- {
232
- id: 'share',
233
- title: 'Share',
234
- image: Platform.OS === 'ios' ? 'square.and.arrow.up' : 'share',
235
- },
236
- {
237
- id: 'delete',
238
- title: 'Delete',
239
- image: Platform.OS === 'ios' ? 'trash' : 'delete',
240
- attributes: { destructive: true },
241
- },
242
- ]}
243
- onPressAction={(id, title) => setLastAction(title)}
244
- >
245
- <View
246
- style={{ padding: 20, backgroundColor: '#E8F4FD', borderRadius: 8 }}
247
- >
248
- <Text>Long-press me</Text>
249
- </View>
250
- </ContextMenu>
251
- );
252
- }
253
- ```
254
-
255
- ### ContextMenu (Modal Mode)
256
-
257
- ```tsx
258
- import { ContextMenu } from 'react-native-platform-components';
259
- import { View, Text } from 'react-native';
260
-
261
- export function Example() {
262
- return (
263
- <ContextMenu
264
- title="Actions"
265
- actions={[
266
- { id: 'edit', title: 'Edit' },
267
- { id: 'duplicate', title: 'Duplicate' },
268
- { id: 'delete', title: 'Delete', attributes: { destructive: true } },
269
- ]}
270
- trigger="tap" // or "longPress" (default)
271
- onPressAction={(id) => console.log('Selected:', id)}
272
- >
273
- <View style={{ padding: 16, backgroundColor: '#eee' }}>
274
- <Text>Tap or long-press me</Text>
275
- </View>
276
- </ContextMenu>
277
- );
278
- }
279
- ```
280
-
281
- ---
282
-
283
- ### SelectionMenu (Headless)
284
-
285
- ```tsx
286
- import { SelectionMenu } from 'react-native-platform-components';
287
-
288
- const options = [
289
- { label: 'Apple', data: 'apple' },
290
- { label: 'Banana', data: 'banana' },
291
- { label: 'Orange', data: 'orange' },
292
- ];
293
-
294
- export function Example() {
295
- const [visible, setVisible] = React.useState(false);
296
- const [value, setValue] = React.useState<string | null>(null);
297
-
298
- return (
299
- <>
300
- <Button title="Open menu" onPress={() => setVisible(true)} />
301
-
302
- <SelectionMenu
303
- options={options}
304
- selected={value}
305
- visible={visible}
306
- onSelect={(data) => {
307
- setValue(data);
308
- setVisible(false);
309
- }}
310
- onRequestClose={() => setVisible(false)}
311
- />
312
- </>
313
- );
314
- }
315
- ```
316
-
317
- ### SelectionMenu (Inline)
318
-
319
- ```tsx
320
- import { SelectionMenu } from 'react-native-platform-components';
321
-
322
- const options = [
323
- { label: 'Apple', data: 'apple' },
324
- { label: 'Banana', data: 'banana' },
325
- { label: 'Orange', data: 'orange' },
326
- ];
327
-
328
- export function Example() {
329
- const [value, setValue] = React.useState<string | null>(null);
330
-
331
- return (
332
- <SelectionMenu
333
- options={options}
334
- selected={value}
335
- presentation="embedded"
336
- placeholder="Select fruit"
337
- onSelect={(data) => setValue(data)}
338
- android={{ material: 'm3' }}
339
- />
340
- );
341
- }
342
- ```
343
-
344
- ---
345
-
346
- ### SegmentedControl
347
-
348
- ```tsx
349
- import { SegmentedControl } from 'react-native-platform-components';
350
-
351
- const segments = [
352
- { label: 'Day', value: 'day' },
353
- { label: 'Week', value: 'week' },
354
- { label: 'Month', value: 'month' },
355
- ];
356
-
357
- export function Example() {
358
- const [selected, setSelected] = React.useState('day');
359
-
360
- return (
361
- <SegmentedControl
362
- segments={segments}
363
- selectedValue={selected}
364
- onSelect={(value) => setSelected(value)}
365
- />
366
- );
367
- }
368
- ```
369
-
370
- ### SegmentedControl (With Icons)
371
-
372
- ```tsx
373
- import { SegmentedControl } from 'react-native-platform-components';
374
-
375
- const segments = [
376
- {
377
- label: 'List',
378
- value: 'list',
379
- // Native symbol on each platform, no Platform.OS branching
380
- icon: {
381
- ios: { type: 'sfSymbol', name: 'list.bullet' },
382
- android: { type: 'drawable', name: 'list_bullet' },
383
- },
384
- },
385
- {
386
- label: 'Alerts',
387
- value: 'alerts',
388
- // One image asset shared by both platforms, tinted like a template
389
- icon: { type: 'image', source: require('./bell.png') },
390
- },
391
- ];
392
-
393
- export function Example() {
394
- const [selected, setSelected] = React.useState('list');
395
-
396
- return (
397
- <SegmentedControl
398
- segments={segments}
399
- selectedValue={selected}
400
- onSelect={(value) => setSelected(value)}
401
- // 'auto' | 'labeled' | 'unlabeled'
402
- labelVisibility="auto"
403
- ios={{ apportionsSegmentWidthsByContent: true }}
404
- />
405
- );
406
- }
407
- ```
408
-
409
- ---
410
-
411
- ### LiquidGlass
412
-
413
- ```tsx
414
- import {
415
- LiquidGlass,
416
- isLiquidGlassSupported,
417
- } from 'react-native-platform-components';
418
- import { View, Text, Image } from 'react-native';
419
-
420
- export function Example() {
421
- return (
422
- <View style={{ flex: 1 }}>
423
- {/* Background content */}
424
- <Image
425
- source={{ uri: 'https://example.com/photo.jpg' }}
426
- style={{ flex: 1 }}
427
- />
428
-
429
- {/* Glass effect overlay */}
430
- <LiquidGlass
431
- style={{
432
- position: 'absolute',
433
- top: 50,
434
- left: 20,
435
- right: 20,
436
- padding: 20,
437
- }}
438
- cornerRadius={20}
439
- ios={{
440
- effect: 'regular',
441
- interactive: true,
442
- colorScheme: 'system',
443
- }}
444
- android={{
445
- fallbackBackgroundColor: '#FFFFFF80',
446
- }}
447
- >
448
- <Text style={{ fontSize: 18, fontWeight: '600' }}>
449
- {isLiquidGlassSupported ? 'Glass Effect!' : 'Fallback View'}
450
- </Text>
451
- </LiquidGlass>
452
- </View>
453
- );
454
- }
455
- ```
456
-
457
- ---
458
-
459
- ## Components
460
-
461
- ## DatePicker
462
-
463
- Native date & time picker using **platform system pickers**.
464
-
465
- ### Props
466
-
467
- | Prop | Type | Description |
468
- | -------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------- |
469
- | `date` | `Date \| null` | Controlled date value |
470
- | `minDate` | `Date \| null` | Minimum selectable date |
471
- | `maxDate` | `Date \| null` | Maximum selectable date |
472
- | `locale` | `string` | Locale identifier (e.g., `'en-US'`) |
473
- | `timeZoneName` | `string` | Time zone identifier |
474
- | `mode` | `'date' \| 'time' \| 'dateAndTime' \| 'countDownTimer'` | Picker mode |
475
- | `presentation` | `'modal' \| 'embedded'` | Presentation style |
476
- | `visible` | `boolean` | Controls modal visibility (modal mode only) |
477
- | `onConfirm` | `(date: Date, confirmed: boolean) => void` | Called on date change; `confirmed` is `true` for deliberate selections (see below) |
478
- | `onClosed` | `() => void` | Called when modal is dismissed |
479
-
480
- ### iOS Props (`ios`)
481
-
482
- | Prop | Type | Description |
483
- | -------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------ |
484
- | `preferredStyle` | `'automatic' \| 'compact' \| 'inline' \| 'wheels'` | iOS date picker style |
485
- | `countDownDurationSeconds` | `number` | Duration for countdown timer mode |
486
- | `minuteInterval` | `number` | Minute interval (1-30) |
487
- | `roundsToMinuteInterval` | `'inherit' \| 'round' \| 'noRound'` | Rounding behavior |
488
- | `showConfirmToolbar` | `boolean` | Modal only. Show Cancel/Done toolbar below the picker. Defaults to `true`. See below. |
489
-
490
- ### Android Props (`android`)
491
-
492
- | Prop | Type | Description |
493
- | --------------------- | ------------------ | ---------------------------------------------------------------------- |
494
- | `firstDayOfWeek` | `number` | First day of week (1-7, Sunday=1) |
495
- | `material` | `'system' \| 'm3'` | Material Design style (modal only; embedded always uses system picker) |
496
- | `dialogTitle` | `string` | Custom dialog title |
497
- | `positiveButtonTitle` | `string` | Custom confirm button text |
498
- | `negativeButtonTitle` | `string` | Custom cancel button text |
499
-
500
- ### The `confirmed` Flag
501
-
502
- `onConfirm` fires on every date/time change, but the second argument (`confirmed`) lets you distinguish between browsing and deliberate selections:
503
-
504
- | Platform / Mode | Every change | Deliberate selection |
505
- | -------------------- | ----------------------------------------- | ------------------------------------- |
506
- | **iOS modal** | `confirmed: false` (user still adjusting) | `confirmed: true` (tapping **Done**) |
507
- | **iOS embedded** | `confirmed: true` | — |
508
- | **Android modal** | — | `confirmed: true` (pressing OK) |
509
- | **Android embedded** | `confirmed: true` | — |
510
-
511
- On iOS in modal presentation, the picker is shown in a popover with a Cancel/Done toolbar below it. Tapping **Done** emits `confirmed: true`; tapping **Cancel** or outside the popover calls `onClosed`. Set `ios.showConfirmToolbar: false` to hide the toolbar — in that mode `confirmed: true` never fires, and your app is expected to drive dismissal by flipping `visible` off (reading the current date from the stream of `confirmed: false` events). This only makes UX sense paired with `ios.preferredStyle: 'inline'`.
512
-
513
- A common pattern is to close the modal only on a confirmed selection:
514
-
515
- ```tsx
516
- <DatePicker
517
- date={date}
518
- visible={visible}
519
- presentation="modal"
520
- mode="time"
521
- onConfirm={(d, confirmed) => {
522
- setDate(d);
523
- if (confirmed) setVisible(false);
524
- }}
525
- onClosed={() => setVisible(false)}
526
- />
527
- ```
528
-
529
- ---
530
-
531
- ## ContextMenu
532
-
533
- Native context menu that wraps content and responds to **long-press** or **tap** gestures.
534
-
535
- ### Props
536
-
537
- | Prop | Type | Description |
538
- | --------------- | --------------------------------- | ------------------------------------------- |
539
- | `title` | `string` | Menu title (shown as header on iOS) |
540
- | `actions` | `ContextMenuAction[]` | Array of menu actions |
541
- | `disabled` | `boolean` | Disables the menu |
542
- | `trigger` | `'longPress' \| 'tap'` | How the menu opens (default: `'longPress'`) |
543
- | `onPressAction` | `(actionId, actionTitle) => void` | Called when user selects an action |
544
- | `onMenuOpen` | `() => void` | Called when menu opens |
545
- | `onMenuClose` | `() => void` | Called when menu closes |
546
- | `children` | `ReactNode` | Content to wrap (required) |
547
-
548
- ### ContextMenuAction
549
-
550
- | Property | Type | Description |
551
- | ------------ | -------------------------------------- | ------------------------------------------------- |
552
- | `id` | `string` | Unique identifier returned in callbacks |
553
- | `title` | `string` | Display text |
554
- | `subtitle` | `string` | Secondary text (iOS only) |
555
- | `image` | `string` | Icon name (SF Symbol on iOS, drawable on Android) |
556
- | `imageColor` | `string` | Tint color for the icon (hex string) |
557
- | `attributes` | `{ destructive?, disabled?, hidden? }` | Action attributes |
558
- | `state` | `'off' \| 'on' \| 'mixed'` | Checkmark state |
559
- | `subactions` | `ContextMenuAction[]` | Nested actions for submenu |
560
-
561
- ### iOS Props (`ios`)
562
-
563
- | Prop | Type | Description |
564
- | --------------- | --------- | --------------------------------- |
565
- | `enablePreview` | `boolean` | Enable preview when long-pressing |
566
-
567
- ### Android Props (`android`)
568
-
569
- | Prop | Type | Description |
570
- | ---------------- | ------------------- | ---------------------------------------------- |
571
- | `anchorPosition` | `'left' \| 'right'` | Anchor position for the popup menu |
572
- | `visible` | `boolean` | Programmatic visibility control (Android only) |
573
-
574
- ### Trigger Modes
575
-
576
- - **Long-Press** (default): Long-press on wrapped content triggers the menu.
577
- - **Tap** (`trigger="tap"`): Single tap on wrapped content triggers the menu.
578
- - **Programmatic** (Android only): Use `android.visible` to control menu visibility programmatically. iOS does not support programmatic menu opening due to platform limitations.
579
-
580
- ### Icon Support
581
-
582
- - **iOS**: Use SF Symbol names (e.g., `'trash'`, `'square.and.arrow.up'`, `'doc.on.doc'`)
583
- - **Android**: Use drawable resource names or Material icon names
584
-
585
- ---
586
-
587
- ## SelectionMenu
588
-
589
- Native selection menu with **modal** and **embedded** modes.
590
-
591
- ### Props
592
-
593
- | Prop | Type | Description |
594
- | ------------------ | ----------------------------------- | ----------------------------------------------- |
595
- | `options` | `{ label: string; data: string }[]` | Array of options to display |
596
- | `selected` | `string \| null` | Currently selected option's `data` value |
597
- | `disabled` | `boolean` | Disables the menu |
598
- | `placeholder` | `string` | Placeholder text when no selection |
599
- | `presentation` | `'modal' \| 'embedded'` | Presentation mode (default: `'modal'`) |
600
- | `visible` | `boolean` | Controls modal mode menu visibility |
601
- | `onSelect` | `(data, label, index) => void` | Called when user selects an option |
602
- | `onRequestClose` | `() => void` | Called when menu is dismissed without selection |
603
- | `android.material` | `'system' \| 'm3'` | Material Design style preference |
604
-
605
- ### Modes
606
-
607
- - **Modal mode** (default): Menu visibility controlled by `visible` prop. Use for custom trigger UI.
608
- - **Embedded mode** (`presentation="embedded"`): Native picker UI rendered inline. Menu managed internally.
609
-
610
- > **Note:** On iOS, modal mode uses a custom popover to enable programmatic presentation. For the full native menu experience (system animations, scroll physics), use embedded mode. This is an intentional trade-off: modal gives you control over the trigger UI, embedded gives you the complete system menu behavior.
611
-
612
- ---
613
-
614
- ## SegmentedControl
615
-
616
- Native segmented control using **UISegmentedControl** on iOS and **MaterialButtonToggleGroup** on Android.
617
-
618
- ### Props
619
-
620
- | Prop | Type | Description |
621
- | ----------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
622
- | `segments` | `SegmentedControlSegment[]` | Array of segments to display |
623
- | `selectedValue` | `string \| null` | Currently selected segment's `value` |
624
- | `disabled` | `boolean` | Disables the entire control |
625
- | `labelVisibility` | `'auto' \| 'labeled' \| 'unlabeled'` | How labels and icons combine. See [Label visibility](#label-visibility). Default: `'auto'` |
626
- | `selectedSegmentColor` | `ColorValue` | Background of the selected segment |
627
- | `activeTintColor` | `ColorValue` | Text and icon color of the selected segment |
628
- | `inactiveTintColor` | `ColorValue` | Text and icon color of unselected segments |
629
- | `labelStyle` | `{ fontFamily?, fontSize?, fontWeight?, fontStyle? }` | Font for segment labels. See [Styling](#styling) |
630
- | `badgeStyle` | `{ backgroundColor?, color? }` | Colors for segment badges. See [Badges](#badges) |
631
- | `onSelect` | `(value: string, index: number) => void` | Called when user selects a segment |
632
- | `onDeselect` | `() => void` | Called when the user clears the selection by tapping the selected segment. Android only; requires `android.selectionRequired: false` |
633
-
634
- ### SegmentedControlSegment
635
-
636
- | Property | Type | Description |
637
- | -------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------- |
638
- | `label` | `string` | Display text for the segment |
639
- | `value` | `string` | Unique value returned in callbacks |
640
- | `disabled` | `boolean` | Disables this specific segment |
641
- | `icon` | `SegmentedControlIcon` | Optional icon. See [Icon Support](#icon-support-1) |
642
- | `badge` | `string \| number` | Badge at the segment's top-right corner, e.g. an unread count. See [Badges](#badges) |
643
- | `accessibilityLabel` | `string` | Screen-reader label. Defaults to `label`. On iOS it applies to icon segments; text segments announce their title |
644
-
645
- ### iOS Props (`ios`)
646
-
647
- | Prop | Type | Description |
648
- | ---------------------------------- | --------- | --------------------------------------------------------------------------- |
649
- | `momentary` | `boolean` | If true, segments don't show selected state |
650
- | `apportionsSegmentWidthsByContent` | `boolean` | If true, segment widths are proportional to content |
651
- | `selectedSegmentTintColor` | `string` | **Deprecated.** Use `selectedSegmentColor`, which works on both platforms. |
652
-
653
- ### Android Props (`android`)
654
-
655
- | Prop | Type | Description |
656
- | ------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
657
- | `selectionRequired` | `boolean` | If true (default), one segment must always be selected. Set to `false` to let a tap on the selected segment clear it and fire `onDeselect`. |
658
- | `rippleColor` | `ColorValue` | Ripple shown while pressing a segment |
659
- | `strokeColor` | `ColorValue` | Outline color of the segments |
660
-
661
- ### Icon Support
662
-
663
- `icon` accepts a single source, or an `{ ios, android }` pair so you never branch on `Platform.OS`:
664
-
665
- | Shape | iOS | Android |
666
- | ------------------------------------------- | ----------------------------------- | --------------------------------------------- |
667
- | `'name'` (string) | SF Symbol name | Drawable resource name |
668
- | `{ type: 'sfSymbol', name }` | SF Symbol | Ignored (segment shows its label) |
669
- | `{ type: 'drawable', name }` | Ignored (segment shows its label) | Drawable from `res/drawable` |
670
- | `{ type: 'image', source, tinted? }` | Image asset or `{ uri }` | Image asset or `{ uri }` |
671
- | `{ ios: <source>, android: <source> }` | Uses `ios` | Uses `android` |
672
-
673
- ```tsx
674
- // A bundled asset works everywhere and is tinted like a template.
675
- icon: { type: 'image', source: require('./icons/bell.png') }
676
-
677
- // Keep the original colors of a full-color image.
678
- icon: { type: 'image', source: require('./icons/logo.png'), tinted: false }
679
-
680
- // Remote images load asynchronously; pass `scale` for @2x/@3x artwork.
681
- icon: { type: 'image', source: { uri: 'https://example.com/icon@2x.png', scale: 2 } }
682
- ```
683
-
684
- Image icons render at their point size, so ship `@2x` / `@3x` variants sized around 18–22 points. Local assets load synchronously in release builds; in development they stream from Metro and the segment shows its label until the image arrives.
685
-
686
- ### Label visibility
687
-
688
- `UISegmentedControl` shows either a title or an image per segment, while Material buttons can show both. `labelVisibility` makes the outcome predictable:
689
-
690
- | Value | iOS | Android |
691
- | --------------------- | ------------------------------------------ | -------------------------- |
692
- | `'auto'` (default) | Icon when the segment has one, else label | Icon and label |
693
- | `'labeled'` | Label only (icon is not shown) | Icon and label |
694
- | `'unlabeled'` | Icon when the segment has one, else label | Icon only, else label |
695
-
696
- Screen readers announce the label (or `accessibilityLabel`) in every mode on both platforms.
697
-
698
- ### Styling
699
-
700
- Colors take any React Native `ColorValue` (hex, `rgba()`, named colors, `PlatformColor`, `DynamicColorIOS`). Fonts follow the `Text` style conventions, and each field falls back to the platform default.
701
-
702
- ```tsx
703
- <SegmentedControl
704
- segments={segments}
705
- selectedValue={selected}
706
- onSelect={setSelected}
707
- selectedSegmentColor="#FF6B35"
708
- activeTintColor="white"
709
- inactiveTintColor="#8E8E93"
710
- labelStyle={{ fontWeight: '700', fontSize: 14 }}
711
- android={{ rippleColor: 'rgba(255, 107, 53, 0.25)', strokeColor: '#FF6B35' }}
712
- />
713
- ```
714
-
715
- | Prop | iOS | Android |
716
- | ---------------------- | -------------------------------------------- | -------------------------------------------- |
717
- | `selectedSegmentColor` | `selectedSegmentTintColor` (the pill) | Checked button background |
718
- | `activeTintColor` | Selected title / template image color | Checked button text and icon tint |
719
- | `inactiveTintColor` | Normal title / template image color | Unchecked button text and icon tint |
720
- | `labelStyle` | Title font (default: 13pt system) | Button typeface and size (default: theme) |
721
- | `android.rippleColor` | — | Press ripple |
722
- | `android.strokeColor` | — | Button outline |
723
-
724
- Images with `tinted: false` keep their own colors and ignore the tint props.
725
-
726
- ### Badges
727
-
728
- Give a segment a `badge` to show a count or short status at its top-right corner. Numbers render as-is, so format them yourself (`'99+'`); `undefined` hides the badge.
729
-
730
- ```tsx
731
- <SegmentedControl
732
- segments={[
733
- { label: 'Inbox', value: 'inbox', badge: unreadCount || undefined },
734
- { label: 'Sent', value: 'sent' },
735
- { label: 'Drafts', value: 'drafts', badge: 'new' },
736
- ]}
737
- selectedValue={mailbox}
738
- onSelect={setMailbox}
739
- badgeStyle={{ backgroundColor: '#5856D6', color: 'white' }}
740
- />
741
- ```
742
-
743
- - **iOS**: a capsule label drawn over the segment (UISegmentedControl has no badge API). Defaults to system red with white text.
744
- - **Android**: a Material `BadgeDrawable` attached to the button. Defaults to the theme's error color.
745
- - Screen readers announce the badge with the segment label ("Inbox, 3").
746
-
747
- ---
748
-
749
- ## LiquidGlass
750
-
751
- Native glass morphism effect using **UIGlassEffect** on iOS 26+. On Android and older iOS versions, renders as a regular View with optional fallback styling.
752
-
753
- > **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.
754
-
755
- ### Props
756
-
757
- | Prop | Type | Description |
758
- | -------------- | ----------- | ------------------------------------------------- |
759
- | `cornerRadius` | `number` | Corner radius for the glass effect (default: `0`) |
760
- | `children` | `ReactNode` | Content to render inside the glass container |
761
-
762
- ### iOS Props (`ios`)
763
-
764
- | Prop | Type | Description |
765
- | ------------- | -------------------------------- | ---------------------------------------------------- |
766
- | `effect` | `'clear' \| 'regular' \| 'none'` | Glass effect intensity (default: `'regular'`) |
767
- | `interactive` | `boolean` | Enable touch interaction feedback (default: `false`) |
768
- | `tintColor` | `string` | Overlay tint color (hex string) |
769
- | `colorScheme` | `'light' \| 'dark' \| 'system'` | Appearance mode (default: `'system'`) |
770
-
771
- ### Android Props (`android`)
772
-
773
- | Prop | Type | Description |
774
- | ------------------------- | -------- | ---------------------------------------------- |
775
- | `fallbackBackgroundColor` | `string` | Background color when glass effect unavailable |
776
-
777
- ### Constants
778
-
779
- | Export | Type | Description |
780
- | ------------------------ | --------- | ------------------------------------ |
781
- | `isLiquidGlassSupported` | `boolean` | `true` on iOS 26+, `false` otherwise |
782
-
783
- ### Effect Modes
784
-
785
- - **`'regular'`** (default): Standard glass blur intensity with full glass morphism effect
786
- - **`'clear'`**: More transparent, subtle glass effect
787
- - **`'none'`**: No glass effect (useful for animating materialization/dematerialization)
788
-
789
- ### Platform Behavior
790
-
791
- | Platform | iOS 26+ | iOS < 26 | Android |
792
- | ------------- | ------------------- | ----------- | ------------ |
793
- | Glass Effect | Full glass morphism | No effect | No effect |
794
- | Corner Radius | Applied | Applied | Applied |
795
- | Tint Color | Supported | Ignored | Ignored |
796
- | Interactive | Supported | Ignored | Ignored |
797
- | Fallback BG | N/A | Transparent | Configurable |
798
-
799
- ### Usage Tips
800
-
801
- 1. **Check support first**: Use `isLiquidGlassSupported` to conditionally render fallback UI
802
- 2. **Background content**: Glass effects work best over images or colorful backgrounds
803
- 3. **Interactive mode**: Only applies on mount; cannot be toggled after initial render
804
- 4. **Android fallback**: Set `android.fallbackBackgroundColor` for a semi-transparent background
805
-
806
- ---
807
-
808
- ## Design Philosophy
809
-
810
- - **Native first** — no JS re-implementation of pickers
811
- - **Headless-friendly** — works with any custom UI
812
- - **Codegen-safe** — string unions & sentinel values for type safety
813
- - **Predictable behavior** — no surprise re-renders or layout hacks
814
- - **Platform conventions** — respects native UX patterns
815
-
816
- ---
817
-
818
- ## Theming
819
-
820
- This library does not expose theming props. Components inherit their appearance from your app's native platform theme.
821
-
822
- - **iOS**: Components follow system appearance (light/dark mode) and use system-defined styles (e.g., `UIBlurEffect` for menu backgrounds). These are not customizable per-component.
823
- - **Android**: Components respect your app's Material Theme. Customize via your `styles.xml` or Material 3 theme configuration.
824
-
825
- This is intentional. The goal is native fidelity, not pixel-level customization. If you need custom styling beyond what the platform theme provides, this library may not be the right fit.
826
-
827
- ---
828
-
829
- ## Color Formats
830
-
831
- All color props in this library support the same formats as React Native's `backgroundColor`:
832
-
833
- | Format | Example | Description |
834
- | ------ | --------------------------------- | ---------------------------------- |
835
- | Hex | `#RGB`, `#RRGGBB`, `#RRGGBBAA` | Standard hex colors |
836
- | RGB | `rgb(255, 0, 0)` | RGB values (0-255) |
837
- | RGBA | `rgba(255, 0, 0, 0.5)` | RGB with alpha (0-1) |
838
- | HSL | `hsl(0, 100%, 50%)` | Hue (0-360), saturation, lightness |
839
- | HSLA | `hsla(0, 100%, 50%, 0.5)` | HSL with alpha (0-1) |
840
- | Named | `red`, `steelblue`, `transparent` | CSS named colors |
841
-
842
- **Props that accept colors:**
843
-
844
- - `ContextMenu`: `imageColor` (icon tint)
845
- - `SegmentedControl`: `selectedSegmentColor`, `activeTintColor`, `inactiveTintColor`, `badgeStyle.backgroundColor`, `badgeStyle.color`, `android.rippleColor`, `android.strokeColor` (these also accept `PlatformColor` / `DynamicColorIOS`)
846
- - `LiquidGlass`: `ios.tintColor`, `android.fallbackBackgroundColor`
847
-
848
- ```tsx
849
- // All of these are equivalent
850
- <ContextMenu actions={[{ id: '1', title: 'Red', imageColor: '#FF0000' }]} />
851
- <ContextMenu actions={[{ id: '1', title: 'Red', imageColor: 'rgb(255, 0, 0)' }]} />
852
- <ContextMenu actions={[{ id: '1', title: 'Red', imageColor: 'hsl(0, 100%, 50%)' }]} />
853
- <ContextMenu actions={[{ id: '1', title: 'Red', imageColor: 'red' }]} />
854
- ```
855
-
856
- ---
857
-
858
- ## Android Theme Configuration
859
-
860
- > **⚠️ 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.
861
-
862
- ### Theme Requirements by Component
863
-
864
- | Component | Mode | Required Theme | Crash if Missing |
865
- | -------------------- | ---------------------------- | ------------------- | ---------------- |
866
- | **SegmentedControl** | (always M3) | `Theme.Material3.*` | ✅ Yes |
867
- | **DatePicker** | `android.material: 'm3'` | `Theme.Material3.*` | ✅ Yes |
868
- | **DatePicker** | `android.material: 'system'` | `Theme.AppCompat.*` | ✅ Yes |
869
- | **SelectionMenu** | `android.material: 'm3'` | `Theme.Material3.*` | ✅ Yes |
870
- | **SelectionMenu** | `android.material: 'system'` | `Theme.AppCompat.*` | ✅ Yes |
871
- | **ContextMenu** | — | Any | ❌ No |
872
- | **LiquidGlass** | — | Any | ❌ No |
873
-
874
- ### Material 3 Theme Setup (Required for SegmentedControl)
875
-
876
- `SegmentedControl` always uses Material 3 widgets (`MaterialButtonToggleGroup`). Your app **must** use a Material 3 theme or the app will crash on component mount.
877
-
878
- **1. Update your app theme in `android/app/src/main/res/values/styles.xml`:**
879
-
880
- ```xml
881
- <resources>
882
- <!-- Base application theme - MUST inherit from Material3 -->
883
- <style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
884
- <!-- Material 3 requires these color attributes -->
885
- <item name="colorPrimary">@color/md_theme_primary</item>
886
- <item name="colorOnPrimary">@color/md_theme_onPrimary</item>
887
- <item name="colorPrimaryContainer">@color/md_theme_primaryContainer</item>
888
- <item name="colorOnPrimaryContainer">@color/md_theme_onPrimaryContainer</item>
889
- <item name="colorSecondary">@color/md_theme_secondary</item>
890
- <item name="colorOnSecondary">@color/md_theme_onSecondary</item>
891
- <item name="colorSecondaryContainer">@color/md_theme_secondaryContainer</item>
892
- <item name="colorOnSecondaryContainer">@color/md_theme_onSecondaryContainer</item>
893
- <item name="colorTertiary">@color/md_theme_tertiary</item>
894
- <item name="colorOnTertiary">@color/md_theme_onTertiary</item>
895
- <item name="colorBackground">@color/md_theme_background</item>
896
- <item name="colorOnBackground">@color/md_theme_onBackground</item>
897
- <item name="colorSurface">@color/md_theme_surface</item>
898
- <item name="colorOnSurface">@color/md_theme_onSurface</item>
899
- <item name="colorError">@color/md_theme_error</item>
900
- <item name="colorOnError">@color/md_theme_onError</item>
901
- </style>
902
- </resources>
903
- ```
904
-
905
- **2. Define your Material 3 colors in `android/app/src/main/res/values/colors.xml`:**
906
-
907
- ```xml
908
- <resources>
909
- <!-- Generate these using Material Theme Builder: https://m3.material.io/theme-builder -->
910
- <color name="md_theme_primary">#6750A4</color>
911
- <color name="md_theme_onPrimary">#FFFFFF</color>
912
- <color name="md_theme_primaryContainer">#EADDFF</color>
913
- <color name="md_theme_onPrimaryContainer">#21005D</color>
914
- <color name="md_theme_secondary">#625B71</color>
915
- <color name="md_theme_onSecondary">#FFFFFF</color>
916
- <color name="md_theme_secondaryContainer">#E8DEF8</color>
917
- <color name="md_theme_onSecondaryContainer">#1D192B</color>
918
- <color name="md_theme_tertiary">#7D5260</color>
919
- <color name="md_theme_onTertiary">#FFFFFF</color>
920
- <color name="md_theme_background">#FFFBFE</color>
921
- <color name="md_theme_onBackground">#1C1B1F</color>
922
- <color name="md_theme_surface">#FFFBFE</color>
923
- <color name="md_theme_onSurface">#1C1B1F</color>
924
- <color name="md_theme_error">#B3261E</color>
925
- <color name="md_theme_onError">#FFFFFF</color>
926
- </resources>
927
- ```
928
-
929
- > **Tip:** Use Google's [Material Theme Builder](https://m3.material.io/theme-builder) to generate a complete color scheme.
930
-
931
- ### Common Crash Scenarios
932
-
933
- #### Crash: `Cannot find theme attribute materialButtonOutlinedStyle`
934
-
935
- **Cause:** Using `SegmentedControl` without a Material 3 theme.
936
-
937
- **Fix:** Update your theme to inherit from `Theme.Material3.*`:
938
-
939
- ```xml
940
- <!-- Change this -->
941
- <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
942
-
943
- <!-- To this -->
944
- <style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
945
- ```
946
-
947
- #### Crash: `You need to use a Theme.AppCompat theme`
948
-
949
- **Cause:** Using `DatePicker` or `SelectionMenu` with `android.material: 'system'` while not extending AppCompat.
950
-
951
- **Fix:** Ensure your theme inherits from `Theme.AppCompat.*` or `Theme.Material3.*` (which extends AppCompat), and your `MainActivity` extends `AppCompatActivity`.
952
-
953
- ### Mode Selection Guide
954
-
955
- Choose the appropriate mode based on your app's theme:
956
-
957
- ```tsx
958
- // If your app uses Theme.Material3.* (recommended)
959
- <DatePicker android={{ material: 'm3' }} />
960
- <SelectionMenu android={{ material: 'm3' }} />
961
- <SegmentedControl /> // Always M3
962
-
963
- // If your app uses Theme.AppCompat.*
964
- <DatePicker android={{ material: 'system' }} />
965
- <SelectionMenu android={{ material: 'system' }} />
966
- // ⚠️ SegmentedControl will crash - upgrade to Material 3
967
- ```
968
-
969
- ### Expo Configuration
970
-
971
- For Expo projects, the config plugin can configure your theme automatically. Add to `app.json`:
111
+ **Expo:** needs a dev client or EAS Build (native code, so not Expo Go). The config plugin sets up the Material 3 theme for you:
972
112
 
973
113
  ```json
974
114
  {
@@ -976,106 +116,47 @@ For Expo projects, the config plugin can configure your theme automatically. Add
976
116
  "plugins": [
977
117
  [
978
118
  "react-native-platform-components/app.plugin",
979
- {
980
- "android": {
981
- "theme": "material3"
982
- }
983
- }
119
+ { "android": { "theme": "material3" } }
984
120
  ]
985
121
  ]
986
122
  }
987
123
  }
988
124
  ```
989
125
 
990
- Then run `npx expo prebuild` to apply the configuration.
991
-
992
- ---
993
-
994
- ## Icons
995
-
996
- ContextMenu supports icons on menu items. Icons are specified by name and resolved differently on each platform. SegmentedControl accepts the same names, plus image assets and per-platform pairs; see [SegmentedControl Icon Support](#icon-support-1).
997
-
998
- ### iOS
999
-
1000
- Use [SF Symbols](https://developer.apple.com/sf-symbols/) names. These are built into iOS and require no additional setup.
1001
-
1002
- ```tsx
1003
- // Common SF Symbols
1004
- image: 'doc.on.doc'; // Copy
1005
- image: 'square.and.arrow.up'; // Share
1006
- image: 'trash'; // Delete
1007
- image: 'pencil'; // Edit
1008
- image: 'checkmark.circle'; // Checkmark
1009
- ```
1010
-
1011
- Browse available symbols using Apple's SF Symbols app or [sfsymbols.com](https://sfsymbols.com).
1012
-
1013
- ### Android
1014
-
1015
- Use drawable resource names from your app's `res/drawable` directory. You must add these resources yourself.
1016
-
1017
- ```tsx
1018
- // Reference drawable by name (without extension)
1019
- image: 'content_copy'; // res/drawable/content_copy.xml
1020
- image: 'share'; // res/drawable/share.xml
1021
- image: 'delete'; // res/drawable/delete.xml
126
+ ```sh
127
+ npx expo install react-native-platform-components
128
+ npx expo prebuild
129
+ npx expo run:ios # or run:android
1022
130
  ```
1023
131
 
1024
- **Adding drawable resources:**
1025
-
1026
- 1. Create vector drawable XML files in `android/app/src/main/res/drawable/`
1027
- 2. Use [Material Icons](https://fonts.google.com/icons) as a source — download SVG and convert to Android Vector Drawable
1028
- 3. Name the file to match the `image` prop value (e.g., `content_copy.xml` for `image: 'content_copy'`)
132
+ The library is built for the New Architecture (Fabric + Codegen) and does not support the old architecture.
1029
133
 
1030
- Example vector drawable (`res/drawable/content_copy.xml`):
1031
-
1032
- ```xml
1033
- <vector xmlns:android="http://schemas.android.com/apk/res/android"
1034
- android:width="24dp"
1035
- android:height="24dp"
1036
- android:viewportWidth="24"
1037
- android:viewportHeight="24">
1038
- <path
1039
- android:fillColor="@android:color/white"
1040
- android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
1041
- </vector>
1042
- ```
134
+ **Compatibility:**
1043
135
 
1044
- ### Cross-platform pattern
136
+ | Platform | Minimum |
137
+ | ------------ | --------------------------------------------------------- |
138
+ | React Native | 0.81 with the New Architecture enabled |
139
+ | Expo SDK | 54, with a dev client or EAS Build (not Expo Go) |
140
+ | React | 19 |
141
+ | iOS | 13 (LiquidGlass needs iOS 26) |
142
+ | Android | API 24 (Android 7.0); Material Components 1.12 is bundled |
1045
143
 
1046
- Use `Platform.OS` to provide the correct icon name for each platform:
144
+ Full details: [Installation](https://jarx-concepts.github.io/react-native-platform-components/installation).
1047
145
 
1048
- ```tsx
1049
- import { Platform } from 'react-native';
146
+ ## Documentation
1050
147
 
1051
- const actions = [
1052
- {
1053
- id: 'copy',
1054
- title: 'Copy',
1055
- image: Platform.OS === 'ios' ? 'doc.on.doc' : 'content_copy',
1056
- },
1057
- {
1058
- id: 'share',
1059
- title: 'Share',
1060
- image: Platform.OS === 'ios' ? 'square.and.arrow.up' : 'share',
1061
- },
1062
- {
1063
- id: 'delete',
1064
- title: 'Delete',
1065
- image: Platform.OS === 'ios' ? 'trash' : 'delete',
1066
- attributes: { destructive: true },
1067
- },
1068
- ];
1069
- ```
148
+ Everything else lives on the documentation site:
1070
149
 
1071
- ---
150
+ - [Quick Start](https://jarx-concepts.github.io/react-native-platform-components/quick-start): copy-paste examples for every component
151
+ - [DatePicker](https://jarx-concepts.github.io/react-native-platform-components/components/datepicker), [ContextMenu](https://jarx-concepts.github.io/react-native-platform-components/components/contextmenu), [SelectionMenu](https://jarx-concepts.github.io/react-native-platform-components/components/selectionmenu), [SegmentedControl](https://jarx-concepts.github.io/react-native-platform-components/components/segmentedcontrol), [LiquidGlass](https://jarx-concepts.github.io/react-native-platform-components/components/liquidglass): props, platform options and behavior
152
+ - [Android Theme Configuration](https://jarx-concepts.github.io/react-native-platform-components/guides/android-theme): Material 3 setup and what happens without it
153
+ - [Icons](https://jarx-concepts.github.io/react-native-platform-components/guides/icons): SF Symbols, drawables and images
154
+ - [Theming and Colors](https://jarx-concepts.github.io/react-native-platform-components/guides/theming): design philosophy, theming, accepted color formats
1072
155
 
1073
156
  ## Contributing
1074
157
 
1075
158
  See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
1076
159
 
1077
- **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.
1078
-
1079
160
  ## License
1080
161
 
1081
162
  MIT