react-native-platform-components 0.5.5 → 0.6.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 +295 -83
- 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/PlatformComponentsPackage.kt +1 -0
- package/ios/PCContextMenu.h +12 -0
- package/ios/PCContextMenu.mm +247 -0
- package/ios/PCContextMenu.swift +346 -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/src/ContextMenu.d.ts +79 -0
- package/lib/typescript/src/ContextMenu.d.ts.map +1 -0
- package/lib/typescript/src/ContextMenuNativeComponent.d.ts +122 -0
- package/lib/typescript/src/ContextMenuNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/ContextMenu.tsx +209 -0
- package/src/ContextMenuNativeComponent.ts +141 -0
- package/src/index.tsx +1 -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,11 +70,11 @@ 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
|
|
|
@@ -73,62 +82,53 @@ pod install
|
|
|
73
82
|
|
|
74
83
|
## Quick Start
|
|
75
84
|
|
|
76
|
-
###
|
|
85
|
+
### DatePicker (Modal)
|
|
77
86
|
|
|
78
87
|
```tsx
|
|
79
|
-
import {
|
|
80
|
-
|
|
81
|
-
const options = [
|
|
82
|
-
{ label: 'Apple', data: 'apple' },
|
|
83
|
-
{ label: 'Banana', data: 'banana' },
|
|
84
|
-
{ label: 'Orange', data: 'orange' },
|
|
85
|
-
];
|
|
88
|
+
import { DatePicker } from 'react-native-platform-components';
|
|
86
89
|
|
|
87
90
|
export function Example() {
|
|
91
|
+
const [date, setDate] = React.useState<Date | null>(null);
|
|
88
92
|
const [visible, setVisible] = React.useState(false);
|
|
89
|
-
const [value, setValue] = React.useState<string | null>(null);
|
|
90
93
|
|
|
91
94
|
return (
|
|
92
95
|
<>
|
|
93
|
-
<Button title="
|
|
96
|
+
<Button title="Pick date" onPress={() => setVisible(true)} />
|
|
94
97
|
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
selected={value}
|
|
98
|
+
<DatePicker
|
|
99
|
+
date={date}
|
|
98
100
|
visible={visible}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
presentation="modal"
|
|
102
|
+
mode="date"
|
|
103
|
+
onConfirm={(d) => {
|
|
104
|
+
setDate(d);
|
|
101
105
|
setVisible(false);
|
|
102
106
|
}}
|
|
103
|
-
|
|
107
|
+
onClosed={() => setVisible(false)}
|
|
108
|
+
ios={{preferredStyle: 'inline'}}
|
|
109
|
+
android={{material: 'system'}}
|
|
104
110
|
/>
|
|
105
111
|
</>
|
|
106
112
|
);
|
|
107
113
|
}
|
|
108
114
|
```
|
|
109
115
|
|
|
110
|
-
###
|
|
116
|
+
### DatePicker (Embedded)
|
|
111
117
|
|
|
112
118
|
```tsx
|
|
113
|
-
import {
|
|
114
|
-
|
|
115
|
-
const options = [
|
|
116
|
-
{ label: 'Apple', data: 'apple' },
|
|
117
|
-
{ label: 'Banana', data: 'banana' },
|
|
118
|
-
{ label: 'Orange', data: 'orange' },
|
|
119
|
-
];
|
|
119
|
+
import { DatePicker } from 'react-native-platform-components';
|
|
120
120
|
|
|
121
121
|
export function Example() {
|
|
122
|
-
const [
|
|
122
|
+
const [date, setDate] = React.useState<Date | null>(new Date());
|
|
123
123
|
|
|
124
124
|
return (
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
selected={value}
|
|
125
|
+
<DatePicker
|
|
126
|
+
date={date}
|
|
128
127
|
presentation="embedded"
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
mode="date"
|
|
129
|
+
onConfirm={(d) => setDate(d)}
|
|
130
|
+
ios={{preferredStyle: 'inline'}}
|
|
131
|
+
android={{material: 'system'}}
|
|
132
132
|
/>
|
|
133
133
|
);
|
|
134
134
|
}
|
|
@@ -136,53 +136,130 @@ export function Example() {
|
|
|
136
136
|
|
|
137
137
|
---
|
|
138
138
|
|
|
139
|
-
###
|
|
139
|
+
### ContextMenu (Gesture Mode)
|
|
140
140
|
|
|
141
141
|
```tsx
|
|
142
|
-
import {
|
|
142
|
+
import { ContextMenu } from 'react-native-platform-components';
|
|
143
|
+
import { Platform, View, Text } from 'react-native';
|
|
144
|
+
|
|
145
|
+
export function Example() {
|
|
146
|
+
const [lastAction, setLastAction] = React.useState<string | null>(null);
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<ContextMenu
|
|
150
|
+
title="Options"
|
|
151
|
+
actions={[
|
|
152
|
+
{
|
|
153
|
+
id: 'copy',
|
|
154
|
+
title: 'Copy',
|
|
155
|
+
image: Platform.OS === 'ios' ? 'doc.on.doc' : 'content_copy',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: 'share',
|
|
159
|
+
title: 'Share',
|
|
160
|
+
image: Platform.OS === 'ios' ? 'square.and.arrow.up' : 'share',
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'delete',
|
|
164
|
+
title: 'Delete',
|
|
165
|
+
image: Platform.OS === 'ios' ? 'trash' : 'delete',
|
|
166
|
+
attributes: { destructive: true },
|
|
167
|
+
},
|
|
168
|
+
]}
|
|
169
|
+
onPressAction={(id, title) => setLastAction(title)}
|
|
170
|
+
>
|
|
171
|
+
<View style={{ padding: 20, backgroundColor: '#E8F4FD', borderRadius: 8 }}>
|
|
172
|
+
<Text>Long-press me</Text>
|
|
173
|
+
</View>
|
|
174
|
+
</ContextMenu>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### ContextMenu (Modal Mode)
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
import { ContextMenu } from 'react-native-platform-components';
|
|
183
|
+
import { View, Text } from 'react-native';
|
|
184
|
+
|
|
185
|
+
export function Example() {
|
|
186
|
+
return (
|
|
187
|
+
<ContextMenu
|
|
188
|
+
title="Actions"
|
|
189
|
+
actions={[
|
|
190
|
+
{ id: 'edit', title: 'Edit' },
|
|
191
|
+
{ id: 'duplicate', title: 'Duplicate' },
|
|
192
|
+
{ id: 'delete', title: 'Delete', attributes: { destructive: true } },
|
|
193
|
+
]}
|
|
194
|
+
trigger="tap" // or "longPress" (default)
|
|
195
|
+
onPressAction={(id) => console.log('Selected:', id)}
|
|
196
|
+
>
|
|
197
|
+
<View style={{ padding: 16, backgroundColor: '#eee' }}>
|
|
198
|
+
<Text>Tap or long-press me</Text>
|
|
199
|
+
</View>
|
|
200
|
+
</ContextMenu>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### SelectionMenu (Headless)
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
import { SelectionMenu } from 'react-native-platform-components';
|
|
211
|
+
|
|
212
|
+
const options = [
|
|
213
|
+
{ label: 'Apple', data: 'apple' },
|
|
214
|
+
{ label: 'Banana', data: 'banana' },
|
|
215
|
+
{ label: 'Orange', data: 'orange' },
|
|
216
|
+
];
|
|
143
217
|
|
|
144
218
|
export function Example() {
|
|
145
|
-
const [date, setDate] = React.useState<Date | null>(null);
|
|
146
219
|
const [visible, setVisible] = React.useState(false);
|
|
220
|
+
const [value, setValue] = React.useState<string | null>(null);
|
|
147
221
|
|
|
148
222
|
return (
|
|
149
223
|
<>
|
|
150
|
-
<Button title="
|
|
224
|
+
<Button title="Open menu" onPress={() => setVisible(true)} />
|
|
151
225
|
|
|
152
|
-
<
|
|
153
|
-
|
|
226
|
+
<SelectionMenu
|
|
227
|
+
options={options}
|
|
228
|
+
selected={value}
|
|
154
229
|
visible={visible}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
onConfirm={(d) => {
|
|
158
|
-
setDate(d);
|
|
230
|
+
onSelect={(data) => {
|
|
231
|
+
setValue(data);
|
|
159
232
|
setVisible(false);
|
|
160
233
|
}}
|
|
161
|
-
|
|
162
|
-
ios={{preferredStyle: 'inline'}}
|
|
163
|
-
android={{material: 'system'}}
|
|
234
|
+
onRequestClose={() => setVisible(false)}
|
|
164
235
|
/>
|
|
165
236
|
</>
|
|
166
237
|
);
|
|
167
238
|
}
|
|
168
239
|
```
|
|
169
240
|
|
|
170
|
-
###
|
|
241
|
+
### SelectionMenu (Inline)
|
|
171
242
|
|
|
172
243
|
```tsx
|
|
173
|
-
import {
|
|
244
|
+
import { SelectionMenu } from 'react-native-platform-components';
|
|
245
|
+
|
|
246
|
+
const options = [
|
|
247
|
+
{ label: 'Apple', data: 'apple' },
|
|
248
|
+
{ label: 'Banana', data: 'banana' },
|
|
249
|
+
{ label: 'Orange', data: 'orange' },
|
|
250
|
+
];
|
|
174
251
|
|
|
175
252
|
export function Example() {
|
|
176
|
-
const [
|
|
253
|
+
const [value, setValue] = React.useState<string | null>(null);
|
|
177
254
|
|
|
178
255
|
return (
|
|
179
|
-
<
|
|
180
|
-
|
|
256
|
+
<SelectionMenu
|
|
257
|
+
options={options}
|
|
258
|
+
selected={value}
|
|
181
259
|
presentation="embedded"
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
android={{material: 'system'}}
|
|
260
|
+
placeholder="Select fruit"
|
|
261
|
+
onSelect={(data) => setValue(data)}
|
|
262
|
+
android={{ material: 'm3' }}
|
|
186
263
|
/>
|
|
187
264
|
);
|
|
188
265
|
}
|
|
@@ -192,33 +269,6 @@ export function Example() {
|
|
|
192
269
|
|
|
193
270
|
## Components
|
|
194
271
|
|
|
195
|
-
## SelectionMenu
|
|
196
|
-
|
|
197
|
-
Native selection menu with **modal** and **embedded** modes.
|
|
198
|
-
|
|
199
|
-
### Props
|
|
200
|
-
|
|
201
|
-
| Prop | Type | Description |
|
|
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 |
|
|
212
|
-
|
|
213
|
-
### Modes
|
|
214
|
-
|
|
215
|
-
- **Modal mode** (default): Menu visibility controlled by `visible` prop. Use for custom trigger UI.
|
|
216
|
-
- **Embedded mode** (`presentation="embedded"`): Native picker UI rendered inline. Menu managed internally.
|
|
217
|
-
|
|
218
|
-
> **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.
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
272
|
## DatePicker
|
|
223
273
|
|
|
224
274
|
Native date & time picker using **platform system pickers**.
|
|
@@ -259,6 +309,89 @@ Native date & time picker using **platform system pickers**.
|
|
|
259
309
|
|
|
260
310
|
---
|
|
261
311
|
|
|
312
|
+
## ContextMenu
|
|
313
|
+
|
|
314
|
+
Native context menu that wraps content and responds to **long-press** or **tap** gestures.
|
|
315
|
+
|
|
316
|
+
### Props
|
|
317
|
+
|
|
318
|
+
| Prop | Type | Description |
|
|
319
|
+
|------|------|-------------|
|
|
320
|
+
| `title` | `string` | Menu title (shown as header on iOS) |
|
|
321
|
+
| `actions` | `ContextMenuAction[]` | Array of menu actions |
|
|
322
|
+
| `disabled` | `boolean` | Disables the menu |
|
|
323
|
+
| `trigger` | `'longPress' \| 'tap'` | How the menu opens (default: `'longPress'`) |
|
|
324
|
+
| `onPressAction` | `(actionId, actionTitle) => void` | Called when user selects an action |
|
|
325
|
+
| `onMenuOpen` | `() => void` | Called when menu opens |
|
|
326
|
+
| `onMenuClose` | `() => void` | Called when menu closes |
|
|
327
|
+
| `children` | `ReactNode` | Content to wrap (required) |
|
|
328
|
+
|
|
329
|
+
### ContextMenuAction
|
|
330
|
+
|
|
331
|
+
| Property | Type | Description |
|
|
332
|
+
|----------|------|-------------|
|
|
333
|
+
| `id` | `string` | Unique identifier returned in callbacks |
|
|
334
|
+
| `title` | `string` | Display text |
|
|
335
|
+
| `subtitle` | `string` | Secondary text (iOS only) |
|
|
336
|
+
| `image` | `string` | Icon name (SF Symbol on iOS, drawable on Android) |
|
|
337
|
+
| `imageColor` | `string` | Tint color for the icon (hex string) |
|
|
338
|
+
| `attributes` | `{ destructive?, disabled?, hidden? }` | Action attributes |
|
|
339
|
+
| `state` | `'off' \| 'on' \| 'mixed'` | Checkmark state |
|
|
340
|
+
| `subactions` | `ContextMenuAction[]` | Nested actions for submenu |
|
|
341
|
+
|
|
342
|
+
### iOS Props (`ios`)
|
|
343
|
+
|
|
344
|
+
| Prop | Type | Description |
|
|
345
|
+
|------|------|-------------|
|
|
346
|
+
| `enablePreview` | `boolean` | Enable preview when long-pressing |
|
|
347
|
+
|
|
348
|
+
### Android Props (`android`)
|
|
349
|
+
|
|
350
|
+
| Prop | Type | Description |
|
|
351
|
+
|------|------|-------------|
|
|
352
|
+
| `anchorPosition` | `'left' \| 'right'` | Anchor position for the popup menu |
|
|
353
|
+
| `visible` | `boolean` | Programmatic visibility control (Android only) |
|
|
354
|
+
|
|
355
|
+
### Trigger Modes
|
|
356
|
+
|
|
357
|
+
- **Long-Press** (default): Long-press on wrapped content triggers the menu.
|
|
358
|
+
- **Tap** (`trigger="tap"`): Single tap on wrapped content triggers the menu.
|
|
359
|
+
- **Programmatic** (Android only): Use `android.visible` to control menu visibility programmatically. iOS does not support programmatic menu opening due to platform limitations.
|
|
360
|
+
|
|
361
|
+
### Icon Support
|
|
362
|
+
|
|
363
|
+
- **iOS**: Use SF Symbol names (e.g., `'trash'`, `'square.and.arrow.up'`, `'doc.on.doc'`)
|
|
364
|
+
- **Android**: Use drawable resource names or Material icon names
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## SelectionMenu
|
|
369
|
+
|
|
370
|
+
Native selection menu with **modal** and **embedded** modes.
|
|
371
|
+
|
|
372
|
+
### Props
|
|
373
|
+
|
|
374
|
+
| Prop | Type | Description |
|
|
375
|
+
|------|------|-------------|
|
|
376
|
+
| `options` | `{ label: string; data: string }[]` | Array of options to display |
|
|
377
|
+
| `selected` | `string \| null` | Currently selected option's `data` value |
|
|
378
|
+
| `disabled` | `boolean` | Disables the menu |
|
|
379
|
+
| `placeholder` | `string` | Placeholder text when no selection |
|
|
380
|
+
| `presentation` | `'modal' \| 'embedded'` | Presentation mode (default: `'modal'`) |
|
|
381
|
+
| `visible` | `boolean` | Controls modal mode menu visibility |
|
|
382
|
+
| `onSelect` | `(data, label, index) => void` | Called when user selects an option |
|
|
383
|
+
| `onRequestClose` | `() => void` | Called when menu is dismissed without selection |
|
|
384
|
+
| `android.material` | `'system' \| 'm3'` | Material Design style preference |
|
|
385
|
+
|
|
386
|
+
### Modes
|
|
387
|
+
|
|
388
|
+
- **Modal mode** (default): Menu visibility controlled by `visible` prop. Use for custom trigger UI.
|
|
389
|
+
- **Embedded mode** (`presentation="embedded"`): Native picker UI rendered inline. Menu managed internally.
|
|
390
|
+
|
|
391
|
+
> **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.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
262
395
|
## Design Philosophy
|
|
263
396
|
|
|
264
397
|
- **Native first** — no JS re-implementation of pickers
|
|
@@ -280,6 +413,85 @@ This is intentional. The goal is native fidelity, not pixel-level customization.
|
|
|
280
413
|
|
|
281
414
|
---
|
|
282
415
|
|
|
416
|
+
## Icons
|
|
417
|
+
|
|
418
|
+
ContextMenu supports icons on menu items. Icons are specified by name and resolved differently on each platform.
|
|
419
|
+
|
|
420
|
+
### iOS
|
|
421
|
+
|
|
422
|
+
Use [SF Symbols](https://developer.apple.com/sf-symbols/) names. These are built into iOS and require no additional setup.
|
|
423
|
+
|
|
424
|
+
```tsx
|
|
425
|
+
// Common SF Symbols
|
|
426
|
+
image: 'doc.on.doc' // Copy
|
|
427
|
+
image: 'square.and.arrow.up' // Share
|
|
428
|
+
image: 'trash' // Delete
|
|
429
|
+
image: 'pencil' // Edit
|
|
430
|
+
image: 'checkmark.circle' // Checkmark
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Browse available symbols using Apple's SF Symbols app or [sfsymbols.com](https://sfsymbols.com).
|
|
434
|
+
|
|
435
|
+
### Android
|
|
436
|
+
|
|
437
|
+
Use drawable resource names from your app's `res/drawable` directory. You must add these resources yourself.
|
|
438
|
+
|
|
439
|
+
```tsx
|
|
440
|
+
// Reference drawable by name (without extension)
|
|
441
|
+
image: 'content_copy' // res/drawable/content_copy.xml
|
|
442
|
+
image: 'share' // res/drawable/share.xml
|
|
443
|
+
image: 'delete' // res/drawable/delete.xml
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**Adding drawable resources:**
|
|
447
|
+
|
|
448
|
+
1. Create vector drawable XML files in `android/app/src/main/res/drawable/`
|
|
449
|
+
2. Use [Material Icons](https://fonts.google.com/icons) as a source — download SVG and convert to Android Vector Drawable
|
|
450
|
+
3. Name the file to match the `image` prop value (e.g., `content_copy.xml` for `image: 'content_copy'`)
|
|
451
|
+
|
|
452
|
+
Example vector drawable (`res/drawable/content_copy.xml`):
|
|
453
|
+
|
|
454
|
+
```xml
|
|
455
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
456
|
+
android:width="24dp"
|
|
457
|
+
android:height="24dp"
|
|
458
|
+
android:viewportWidth="24"
|
|
459
|
+
android:viewportHeight="24">
|
|
460
|
+
<path
|
|
461
|
+
android:fillColor="@android:color/white"
|
|
462
|
+
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"/>
|
|
463
|
+
</vector>
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Cross-platform pattern
|
|
467
|
+
|
|
468
|
+
Use `Platform.OS` to provide the correct icon name for each platform:
|
|
469
|
+
|
|
470
|
+
```tsx
|
|
471
|
+
import { Platform } from 'react-native';
|
|
472
|
+
|
|
473
|
+
const actions = [
|
|
474
|
+
{
|
|
475
|
+
id: 'copy',
|
|
476
|
+
title: 'Copy',
|
|
477
|
+
image: Platform.OS === 'ios' ? 'doc.on.doc' : 'content_copy',
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
id: 'share',
|
|
481
|
+
title: 'Share',
|
|
482
|
+
image: Platform.OS === 'ios' ? 'square.and.arrow.up' : 'share',
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
id: 'delete',
|
|
486
|
+
title: 'Delete',
|
|
487
|
+
image: Platform.OS === 'ios' ? 'trash' : 'delete',
|
|
488
|
+
attributes: { destructive: true },
|
|
489
|
+
},
|
|
490
|
+
];
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
283
495
|
## Contributing
|
|
284
496
|
|
|
285
497
|
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
|