react-native-platform-components 0.5.5 → 0.6.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.
- package/README.md +342 -72
- package/android/src/main/java/com/platformcomponents/PCContextMenuView.kt +419 -0
- package/android/src/main/java/com/platformcomponents/PCContextMenuViewManager.kt +200 -0
- package/android/src/main/java/com/platformcomponents/PCSelectionMenuView.kt +4 -0
- package/android/src/main/java/com/platformcomponents/PlatformComponentsPackage.kt +1 -0
- package/app.plugin.cjs +4 -0
- package/expo-module.config.json +4 -0
- package/ios/PCContextMenu.h +12 -0
- package/ios/PCContextMenu.mm +247 -0
- package/ios/PCContextMenu.swift +389 -0
- package/ios/PCDatePickerView.swift +25 -11
- package/lib/commonjs/ContextMenu.js +118 -0
- package/lib/commonjs/ContextMenu.js.map +1 -0
- package/lib/commonjs/ContextMenuNativeComponent.ts +141 -0
- package/lib/commonjs/DatePicker.js +86 -0
- package/lib/commonjs/DatePicker.js.map +1 -0
- package/lib/commonjs/DatePickerNativeComponent.ts +69 -0
- package/lib/commonjs/SelectionMenu.js +73 -0
- package/lib/commonjs/SelectionMenu.js.map +1 -0
- package/lib/commonjs/SelectionMenuNativeComponent.ts +97 -0
- package/lib/commonjs/index.js +50 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/sharedTypes.js +6 -0
- package/lib/commonjs/sharedTypes.js.map +1 -0
- package/lib/module/ContextMenu.js +111 -0
- package/lib/module/ContextMenu.js.map +1 -0
- package/lib/module/ContextMenuNativeComponent.ts +141 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/src/ContextMenu.d.ts +79 -0
- package/lib/typescript/commonjs/src/ContextMenu.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/ContextMenuNativeComponent.d.ts +122 -0
- package/lib/typescript/commonjs/src/ContextMenuNativeComponent.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/DatePicker.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/DatePickerNativeComponent.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/SelectionMenu.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/SelectionMenuNativeComponent.d.ts.map +1 -0
- package/lib/typescript/{src → commonjs/src}/index.d.ts +1 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/sharedTypes.d.ts.map +1 -0
- package/lib/typescript/module/src/ContextMenu.d.ts +79 -0
- package/lib/typescript/module/src/ContextMenu.d.ts.map +1 -0
- package/lib/typescript/module/src/ContextMenuNativeComponent.d.ts +122 -0
- package/lib/typescript/module/src/ContextMenuNativeComponent.d.ts.map +1 -0
- package/lib/typescript/module/src/DatePicker.d.ts +40 -0
- package/lib/typescript/module/src/DatePicker.d.ts.map +1 -0
- package/lib/typescript/module/src/DatePickerNativeComponent.d.ts +54 -0
- package/lib/typescript/module/src/DatePickerNativeComponent.d.ts.map +1 -0
- package/lib/typescript/module/src/SelectionMenu.d.ts +47 -0
- package/lib/typescript/module/src/SelectionMenu.d.ts.map +1 -0
- package/lib/typescript/module/src/SelectionMenuNativeComponent.d.ts +78 -0
- package/lib/typescript/module/src/SelectionMenuNativeComponent.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +5 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -0
- package/lib/typescript/module/src/sharedTypes.d.ts +12 -0
- package/lib/typescript/module/src/sharedTypes.d.ts.map +1 -0
- package/package.json +32 -11
- package/plugin/build/index.cjs +26 -0
- package/plugin/build/index.d.ts +22 -0
- package/plugin/build/index.d.ts.map +1 -0
- package/plugin/tsconfig.json +16 -0
- package/src/ContextMenu.tsx +209 -0
- package/src/ContextMenuNativeComponent.ts +141 -0
- package/src/index.tsx +1 -0
- package/lib/typescript/src/DatePicker.d.ts.map +0 -1
- package/lib/typescript/src/DatePickerNativeComponent.d.ts.map +0 -1
- package/lib/typescript/src/SelectionMenu.d.ts.map +0 -1
- package/lib/typescript/src/SelectionMenuNativeComponent.d.ts.map +0 -1
- package/lib/typescript/src/index.d.ts.map +0 -1
- package/lib/typescript/src/sharedTypes.d.ts.map +0 -1
- /package/lib/typescript/{src → commonjs/src}/DatePicker.d.ts +0 -0
- /package/lib/typescript/{src → commonjs/src}/DatePickerNativeComponent.d.ts +0 -0
- /package/lib/typescript/{src → commonjs/src}/SelectionMenu.d.ts +0 -0
- /package/lib/typescript/{src → commonjs/src}/SelectionMenuNativeComponent.d.ts +0 -0
- /package/lib/typescript/{src → commonjs/src}/sharedTypes.d.ts +0 -0
- /package/lib/typescript/{package.json → module/package.json} +0 -0
package/README.md
CHANGED
|
@@ -15,6 +15,14 @@
|
|
|
15
15
|
<td><img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/ios-datepicker.gif" height="350" /></td>
|
|
16
16
|
<td><img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/android-datepicker.gif" height="350" /></td>
|
|
17
17
|
</tr>
|
|
18
|
+
<tr>
|
|
19
|
+
<td align="center"><strong>iOS ContextMenu</strong></td>
|
|
20
|
+
<td align="center"><strong>Android ContextMenu</strong></td>
|
|
21
|
+
</tr>
|
|
22
|
+
<tr>
|
|
23
|
+
<td><img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/ios-contextmenu.gif" height="350" /></td>
|
|
24
|
+
<td><img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/android-contextmenu.gif" height="350" /></td>
|
|
25
|
+
</tr>
|
|
18
26
|
<tr>
|
|
19
27
|
<td align="center"><strong>iOS SelectionMenu</strong></td>
|
|
20
28
|
<td align="center"><strong>Android SelectionMenu</strong></td>
|
|
@@ -29,8 +37,9 @@
|
|
|
29
37
|
<p>High-quality <strong>native UI components for React Native</strong>, implemented with platform-first APIs and exposed through clean, typed JavaScript interfaces.</p>
|
|
30
38
|
<p>This library focuses on <strong>true native behavior</strong>, not JavaScript re-implementations — providing:</p>
|
|
31
39
|
<ul>
|
|
32
|
-
<li><strong>SelectionMenu</strong> – native selection menus (Material on Android, system menus on iOS)</li>
|
|
33
40
|
<li><strong>DatePicker</strong> – native date & time pickers with modal and embedded presentations</li>
|
|
41
|
+
<li><strong>ContextMenu</strong> – native context menus with long-press activation (UIContextMenuInteraction on iOS, PopupMenu on Android)</li>
|
|
42
|
+
<li><strong>SelectionMenu</strong> – native selection menus (Material on Android, system menus on iOS)</li>
|
|
34
43
|
</ul>
|
|
35
44
|
<p>The goal is to provide components that:</p>
|
|
36
45
|
<ul>
|
|
@@ -61,81 +70,76 @@ pod install
|
|
|
61
70
|
```
|
|
62
71
|
|
|
63
72
|
- Minimum iOS version: **iOS 13+**
|
|
64
|
-
- Uses `UIDatePicker
|
|
73
|
+
- Uses `UIDatePicker`, SwiftUI Menu, and `UIContextMenuInteraction`
|
|
65
74
|
|
|
66
75
|
### Android
|
|
67
76
|
|
|
68
|
-
- Uses native Android Views with Material Design
|
|
77
|
+
- Uses native Android Views with Material Design (including `PopupMenu` for context menus)
|
|
69
78
|
- Supports **Material 3** styling
|
|
70
79
|
- No additional setup required beyond autolinking
|
|
71
80
|
|
|
72
|
-
|
|
81
|
+
### Expo (Managed Workflow)
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
> **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.
|
|
75
84
|
|
|
76
|
-
|
|
85
|
+
```sh
|
|
86
|
+
npx expo install react-native-platform-components
|
|
87
|
+
npx expo prebuild
|
|
88
|
+
npx expo run:ios
|
|
89
|
+
# or
|
|
90
|
+
npx expo run:android
|
|
91
|
+
```
|
|
77
92
|
|
|
78
|
-
|
|
79
|
-
import { SelectionMenu } from 'react-native-platform-components';
|
|
93
|
+
The library includes an Expo config plugin that handles all native configuration automatically. No manual native setup is required.
|
|
80
94
|
|
|
81
|
-
|
|
82
|
-
{ label: 'Apple', data: 'apple' },
|
|
83
|
-
{ label: 'Banana', data: 'banana' },
|
|
84
|
-
{ label: 'Orange', data: 'orange' },
|
|
85
|
-
];
|
|
95
|
+
**EAS Build:**
|
|
86
96
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
97
|
+
```sh
|
|
98
|
+
eas build --platform ios
|
|
99
|
+
eas build --platform android
|
|
100
|
+
```
|
|
90
101
|
|
|
91
|
-
|
|
92
|
-
<>
|
|
93
|
-
<Button title="Open menu" onPress={() => setVisible(true)} />
|
|
102
|
+
**Config Plugin:**
|
|
94
103
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
onRequestClose={() => setVisible(false)}
|
|
104
|
-
/>
|
|
105
|
-
</>
|
|
106
|
-
);
|
|
104
|
+
Add to your `app.json`:
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"expo": {
|
|
108
|
+
"plugins": [
|
|
109
|
+
["react-native-platform-components/app.plugin", {}]
|
|
110
|
+
]
|
|
111
|
+
}
|
|
107
112
|
}
|
|
108
113
|
```
|
|
109
114
|
|
|
110
|
-
|
|
115
|
+
For a complete working example, see the [`example-expo/`](./example-expo) directory.
|
|
111
116
|
|
|
112
|
-
|
|
113
|
-
import { SelectionMenu } from 'react-native-platform-components';
|
|
117
|
+
---
|
|
114
118
|
|
|
115
|
-
|
|
116
|
-
{ label: 'Apple', data: 'apple' },
|
|
117
|
-
{ label: 'Banana', data: 'banana' },
|
|
118
|
-
{ label: 'Orange', data: 'orange' },
|
|
119
|
-
];
|
|
119
|
+
## React Native New Architecture
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
const [value, setValue] = React.useState<string | null>(null);
|
|
121
|
+
This library is built for the **React Native New Architecture** (Fabric + TurboModules).
|
|
123
122
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
123
|
+
| Feature | Status |
|
|
124
|
+
|---------|--------|
|
|
125
|
+
| Fabric (New Renderer) | Supported |
|
|
126
|
+
| Codegen | Used for type-safe native bindings |
|
|
127
|
+
| TurboModules | N/A (view components only) |
|
|
128
|
+
| Old Architecture | Not supported |
|
|
129
|
+
|
|
130
|
+
**Tested with:**
|
|
131
|
+
- React Native 0.81+ (bare and Expo)
|
|
132
|
+
- Expo SDK 54+
|
|
133
|
+
|
|
134
|
+
**Requirements:**
|
|
135
|
+
- New Architecture must be enabled in your app
|
|
136
|
+
- For bare React Native: set `newArchEnabled=true` in `gradle.properties` (Android) and use the `RCT_NEW_ARCH_ENABLED` flag (iOS)
|
|
137
|
+
- For Expo: set `"newArchEnabled": true` in `app.json`
|
|
136
138
|
|
|
137
139
|
---
|
|
138
140
|
|
|
141
|
+
## Quick Start
|
|
142
|
+
|
|
139
143
|
### DatePicker (Modal)
|
|
140
144
|
|
|
141
145
|
```tsx
|
|
@@ -190,35 +194,139 @@ export function Example() {
|
|
|
190
194
|
|
|
191
195
|
---
|
|
192
196
|
|
|
193
|
-
|
|
197
|
+
### ContextMenu (Gesture Mode)
|
|
194
198
|
|
|
195
|
-
|
|
199
|
+
```tsx
|
|
200
|
+
import { ContextMenu } from 'react-native-platform-components';
|
|
201
|
+
import { Platform, View, Text } from 'react-native';
|
|
196
202
|
|
|
197
|
-
|
|
203
|
+
export function Example() {
|
|
204
|
+
const [lastAction, setLastAction] = React.useState<string | null>(null);
|
|
198
205
|
|
|
199
|
-
|
|
206
|
+
return (
|
|
207
|
+
<ContextMenu
|
|
208
|
+
title="Options"
|
|
209
|
+
actions={[
|
|
210
|
+
{
|
|
211
|
+
id: 'copy',
|
|
212
|
+
title: 'Copy',
|
|
213
|
+
image: Platform.OS === 'ios' ? 'doc.on.doc' : 'content_copy',
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: 'share',
|
|
217
|
+
title: 'Share',
|
|
218
|
+
image: Platform.OS === 'ios' ? 'square.and.arrow.up' : 'share',
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: 'delete',
|
|
222
|
+
title: 'Delete',
|
|
223
|
+
image: Platform.OS === 'ios' ? 'trash' : 'delete',
|
|
224
|
+
attributes: { destructive: true },
|
|
225
|
+
},
|
|
226
|
+
]}
|
|
227
|
+
onPressAction={(id, title) => setLastAction(title)}
|
|
228
|
+
>
|
|
229
|
+
<View style={{ padding: 20, backgroundColor: '#E8F4FD', borderRadius: 8 }}>
|
|
230
|
+
<Text>Long-press me</Text>
|
|
231
|
+
</View>
|
|
232
|
+
</ContextMenu>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
```
|
|
200
236
|
|
|
201
|
-
|
|
202
|
-
|------|------|-------------|
|
|
203
|
-
| `options` | `{ label: string; data: string }[]` | Array of options to display |
|
|
204
|
-
| `selected` | `string \| null` | Currently selected option's `data` value |
|
|
205
|
-
| `disabled` | `boolean` | Disables the menu |
|
|
206
|
-
| `placeholder` | `string` | Placeholder text when no selection |
|
|
207
|
-
| `presentation` | `'modal' \| 'embedded'` | Presentation mode (default: `'modal'`) |
|
|
208
|
-
| `visible` | `boolean` | Controls modal mode menu visibility |
|
|
209
|
-
| `onSelect` | `(data, label, index) => void` | Called when user selects an option |
|
|
210
|
-
| `onRequestClose` | `() => void` | Called when menu is dismissed without selection |
|
|
211
|
-
| `android.material` | `'system' \| 'm3'` | Material Design style preference |
|
|
237
|
+
### ContextMenu (Modal Mode)
|
|
212
238
|
|
|
213
|
-
|
|
239
|
+
```tsx
|
|
240
|
+
import { ContextMenu } from 'react-native-platform-components';
|
|
241
|
+
import { View, Text } from 'react-native';
|
|
214
242
|
|
|
215
|
-
|
|
216
|
-
|
|
243
|
+
export function Example() {
|
|
244
|
+
return (
|
|
245
|
+
<ContextMenu
|
|
246
|
+
title="Actions"
|
|
247
|
+
actions={[
|
|
248
|
+
{ id: 'edit', title: 'Edit' },
|
|
249
|
+
{ id: 'duplicate', title: 'Duplicate' },
|
|
250
|
+
{ id: 'delete', title: 'Delete', attributes: { destructive: true } },
|
|
251
|
+
]}
|
|
252
|
+
trigger="tap" // or "longPress" (default)
|
|
253
|
+
onPressAction={(id) => console.log('Selected:', id)}
|
|
254
|
+
>
|
|
255
|
+
<View style={{ padding: 16, backgroundColor: '#eee' }}>
|
|
256
|
+
<Text>Tap or long-press me</Text>
|
|
257
|
+
</View>
|
|
258
|
+
</ContextMenu>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
```
|
|
217
262
|
|
|
218
|
-
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
### SelectionMenu (Headless)
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
import { SelectionMenu } from 'react-native-platform-components';
|
|
269
|
+
|
|
270
|
+
const options = [
|
|
271
|
+
{ label: 'Apple', data: 'apple' },
|
|
272
|
+
{ label: 'Banana', data: 'banana' },
|
|
273
|
+
{ label: 'Orange', data: 'orange' },
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
export function Example() {
|
|
277
|
+
const [visible, setVisible] = React.useState(false);
|
|
278
|
+
const [value, setValue] = React.useState<string | null>(null);
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<>
|
|
282
|
+
<Button title="Open menu" onPress={() => setVisible(true)} />
|
|
283
|
+
|
|
284
|
+
<SelectionMenu
|
|
285
|
+
options={options}
|
|
286
|
+
selected={value}
|
|
287
|
+
visible={visible}
|
|
288
|
+
onSelect={(data) => {
|
|
289
|
+
setValue(data);
|
|
290
|
+
setVisible(false);
|
|
291
|
+
}}
|
|
292
|
+
onRequestClose={() => setVisible(false)}
|
|
293
|
+
/>
|
|
294
|
+
</>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### SelectionMenu (Inline)
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
import { SelectionMenu } from 'react-native-platform-components';
|
|
303
|
+
|
|
304
|
+
const options = [
|
|
305
|
+
{ label: 'Apple', data: 'apple' },
|
|
306
|
+
{ label: 'Banana', data: 'banana' },
|
|
307
|
+
{ label: 'Orange', data: 'orange' },
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
export function Example() {
|
|
311
|
+
const [value, setValue] = React.useState<string | null>(null);
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<SelectionMenu
|
|
315
|
+
options={options}
|
|
316
|
+
selected={value}
|
|
317
|
+
presentation="embedded"
|
|
318
|
+
placeholder="Select fruit"
|
|
319
|
+
onSelect={(data) => setValue(data)}
|
|
320
|
+
android={{ material: 'm3' }}
|
|
321
|
+
/>
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
```
|
|
219
325
|
|
|
220
326
|
---
|
|
221
327
|
|
|
328
|
+
## Components
|
|
329
|
+
|
|
222
330
|
## DatePicker
|
|
223
331
|
|
|
224
332
|
Native date & time picker using **platform system pickers**.
|
|
@@ -259,6 +367,89 @@ Native date & time picker using **platform system pickers**.
|
|
|
259
367
|
|
|
260
368
|
---
|
|
261
369
|
|
|
370
|
+
## ContextMenu
|
|
371
|
+
|
|
372
|
+
Native context menu that wraps content and responds to **long-press** or **tap** gestures.
|
|
373
|
+
|
|
374
|
+
### Props
|
|
375
|
+
|
|
376
|
+
| Prop | Type | Description |
|
|
377
|
+
|------|------|-------------|
|
|
378
|
+
| `title` | `string` | Menu title (shown as header on iOS) |
|
|
379
|
+
| `actions` | `ContextMenuAction[]` | Array of menu actions |
|
|
380
|
+
| `disabled` | `boolean` | Disables the menu |
|
|
381
|
+
| `trigger` | `'longPress' \| 'tap'` | How the menu opens (default: `'longPress'`) |
|
|
382
|
+
| `onPressAction` | `(actionId, actionTitle) => void` | Called when user selects an action |
|
|
383
|
+
| `onMenuOpen` | `() => void` | Called when menu opens |
|
|
384
|
+
| `onMenuClose` | `() => void` | Called when menu closes |
|
|
385
|
+
| `children` | `ReactNode` | Content to wrap (required) |
|
|
386
|
+
|
|
387
|
+
### ContextMenuAction
|
|
388
|
+
|
|
389
|
+
| Property | Type | Description |
|
|
390
|
+
|----------|------|-------------|
|
|
391
|
+
| `id` | `string` | Unique identifier returned in callbacks |
|
|
392
|
+
| `title` | `string` | Display text |
|
|
393
|
+
| `subtitle` | `string` | Secondary text (iOS only) |
|
|
394
|
+
| `image` | `string` | Icon name (SF Symbol on iOS, drawable on Android) |
|
|
395
|
+
| `imageColor` | `string` | Tint color for the icon (hex string) |
|
|
396
|
+
| `attributes` | `{ destructive?, disabled?, hidden? }` | Action attributes |
|
|
397
|
+
| `state` | `'off' \| 'on' \| 'mixed'` | Checkmark state |
|
|
398
|
+
| `subactions` | `ContextMenuAction[]` | Nested actions for submenu |
|
|
399
|
+
|
|
400
|
+
### iOS Props (`ios`)
|
|
401
|
+
|
|
402
|
+
| Prop | Type | Description |
|
|
403
|
+
|------|------|-------------|
|
|
404
|
+
| `enablePreview` | `boolean` | Enable preview when long-pressing |
|
|
405
|
+
|
|
406
|
+
### Android Props (`android`)
|
|
407
|
+
|
|
408
|
+
| Prop | Type | Description |
|
|
409
|
+
|------|------|-------------|
|
|
410
|
+
| `anchorPosition` | `'left' \| 'right'` | Anchor position for the popup menu |
|
|
411
|
+
| `visible` | `boolean` | Programmatic visibility control (Android only) |
|
|
412
|
+
|
|
413
|
+
### Trigger Modes
|
|
414
|
+
|
|
415
|
+
- **Long-Press** (default): Long-press on wrapped content triggers the menu.
|
|
416
|
+
- **Tap** (`trigger="tap"`): Single tap on wrapped content triggers the menu.
|
|
417
|
+
- **Programmatic** (Android only): Use `android.visible` to control menu visibility programmatically. iOS does not support programmatic menu opening due to platform limitations.
|
|
418
|
+
|
|
419
|
+
### Icon Support
|
|
420
|
+
|
|
421
|
+
- **iOS**: Use SF Symbol names (e.g., `'trash'`, `'square.and.arrow.up'`, `'doc.on.doc'`)
|
|
422
|
+
- **Android**: Use drawable resource names or Material icon names
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## SelectionMenu
|
|
427
|
+
|
|
428
|
+
Native selection menu with **modal** and **embedded** modes.
|
|
429
|
+
|
|
430
|
+
### Props
|
|
431
|
+
|
|
432
|
+
| Prop | Type | Description |
|
|
433
|
+
|------|------|-------------|
|
|
434
|
+
| `options` | `{ label: string; data: string }[]` | Array of options to display |
|
|
435
|
+
| `selected` | `string \| null` | Currently selected option's `data` value |
|
|
436
|
+
| `disabled` | `boolean` | Disables the menu |
|
|
437
|
+
| `placeholder` | `string` | Placeholder text when no selection |
|
|
438
|
+
| `presentation` | `'modal' \| 'embedded'` | Presentation mode (default: `'modal'`) |
|
|
439
|
+
| `visible` | `boolean` | Controls modal mode menu visibility |
|
|
440
|
+
| `onSelect` | `(data, label, index) => void` | Called when user selects an option |
|
|
441
|
+
| `onRequestClose` | `() => void` | Called when menu is dismissed without selection |
|
|
442
|
+
| `android.material` | `'system' \| 'm3'` | Material Design style preference |
|
|
443
|
+
|
|
444
|
+
### Modes
|
|
445
|
+
|
|
446
|
+
- **Modal mode** (default): Menu visibility controlled by `visible` prop. Use for custom trigger UI.
|
|
447
|
+
- **Embedded mode** (`presentation="embedded"`): Native picker UI rendered inline. Menu managed internally.
|
|
448
|
+
|
|
449
|
+
> **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.
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
262
453
|
## Design Philosophy
|
|
263
454
|
|
|
264
455
|
- **Native first** — no JS re-implementation of pickers
|
|
@@ -280,6 +471,85 @@ This is intentional. The goal is native fidelity, not pixel-level customization.
|
|
|
280
471
|
|
|
281
472
|
---
|
|
282
473
|
|
|
474
|
+
## Icons
|
|
475
|
+
|
|
476
|
+
ContextMenu supports icons on menu items. Icons are specified by name and resolved differently on each platform.
|
|
477
|
+
|
|
478
|
+
### iOS
|
|
479
|
+
|
|
480
|
+
Use [SF Symbols](https://developer.apple.com/sf-symbols/) names. These are built into iOS and require no additional setup.
|
|
481
|
+
|
|
482
|
+
```tsx
|
|
483
|
+
// Common SF Symbols
|
|
484
|
+
image: 'doc.on.doc' // Copy
|
|
485
|
+
image: 'square.and.arrow.up' // Share
|
|
486
|
+
image: 'trash' // Delete
|
|
487
|
+
image: 'pencil' // Edit
|
|
488
|
+
image: 'checkmark.circle' // Checkmark
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Browse available symbols using Apple's SF Symbols app or [sfsymbols.com](https://sfsymbols.com).
|
|
492
|
+
|
|
493
|
+
### Android
|
|
494
|
+
|
|
495
|
+
Use drawable resource names from your app's `res/drawable` directory. You must add these resources yourself.
|
|
496
|
+
|
|
497
|
+
```tsx
|
|
498
|
+
// Reference drawable by name (without extension)
|
|
499
|
+
image: 'content_copy' // res/drawable/content_copy.xml
|
|
500
|
+
image: 'share' // res/drawable/share.xml
|
|
501
|
+
image: 'delete' // res/drawable/delete.xml
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
**Adding drawable resources:**
|
|
505
|
+
|
|
506
|
+
1. Create vector drawable XML files in `android/app/src/main/res/drawable/`
|
|
507
|
+
2. Use [Material Icons](https://fonts.google.com/icons) as a source — download SVG and convert to Android Vector Drawable
|
|
508
|
+
3. Name the file to match the `image` prop value (e.g., `content_copy.xml` for `image: 'content_copy'`)
|
|
509
|
+
|
|
510
|
+
Example vector drawable (`res/drawable/content_copy.xml`):
|
|
511
|
+
|
|
512
|
+
```xml
|
|
513
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
514
|
+
android:width="24dp"
|
|
515
|
+
android:height="24dp"
|
|
516
|
+
android:viewportWidth="24"
|
|
517
|
+
android:viewportHeight="24">
|
|
518
|
+
<path
|
|
519
|
+
android:fillColor="@android:color/white"
|
|
520
|
+
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"/>
|
|
521
|
+
</vector>
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### Cross-platform pattern
|
|
525
|
+
|
|
526
|
+
Use `Platform.OS` to provide the correct icon name for each platform:
|
|
527
|
+
|
|
528
|
+
```tsx
|
|
529
|
+
import { Platform } from 'react-native';
|
|
530
|
+
|
|
531
|
+
const actions = [
|
|
532
|
+
{
|
|
533
|
+
id: 'copy',
|
|
534
|
+
title: 'Copy',
|
|
535
|
+
image: Platform.OS === 'ios' ? 'doc.on.doc' : 'content_copy',
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
id: 'share',
|
|
539
|
+
title: 'Share',
|
|
540
|
+
image: Platform.OS === 'ios' ? 'square.and.arrow.up' : 'share',
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
id: 'delete',
|
|
544
|
+
title: 'Delete',
|
|
545
|
+
image: Platform.OS === 'ios' ? 'trash' : 'delete',
|
|
546
|
+
attributes: { destructive: true },
|
|
547
|
+
},
|
|
548
|
+
];
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
283
553
|
## Contributing
|
|
284
554
|
|
|
285
555
|
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
|