0
# System Integration
1
2
React Native's system integration APIs adapted for web, providing access to device capabilities, clipboard operations, URL handling, sharing functionality, and internationalization support.
3
4
## AppRegistry
5
6
The JavaScript entry point for running React Native applications on the web. AppRegistry provides the core application lifecycle management for registering and running React Native Web apps.
7
8
```javascript { .api }
9
const AppRegistry: {
10
registerComponent: (appKey: string, componentProvider: () => React.ComponentType) => string;
11
runApplication: (appKey: string, appParameters: AppParameters) => void;
12
getAppKeys: () => string[];
13
getApplication: (appKey: string, appParameters?: AppParameters) => {element: React.ReactNode, getStyleElement: (any) => React.ReactNode};
14
registerConfig: (config: AppConfig[]) => void;
15
registerRunnable: (appKey: string, run: Function) => string;
16
unmountApplicationComponentAtRootTag: (rootTag: Element) => void;
17
setComponentProviderInstrumentationHook: (hook: ComponentProviderInstrumentationHook) => void;
18
setWrapperComponentProvider: (provider: WrapperComponentProvider) => void;
19
};
20
```
21
22
**Web Implementation:**
23
AppRegistry on web manages the mounting and lifecycle of React applications, providing compatibility with React Native's application registration pattern while integrating with web DOM elements.
24
25
### registerComponent()
26
27
Registers a React component as a runnable application that can be started with `runApplication`.
28
29
```javascript { .api }
30
AppRegistry.registerComponent(appKey: string, componentProvider: () => React.ComponentType): string
31
```
32
33
### runApplication()
34
35
Runs a registered application by mounting it to a DOM element. This is the primary method for starting React Native Web applications.
36
37
```javascript { .api }
38
AppRegistry.runApplication(appKey: string, appParameters: AppParameters): void
39
```
40
41
**Usage:**
42
```javascript
43
import React from "react";
44
import { AppRegistry, View, Text, StyleSheet } from "react-native-web";
45
46
// Define your app component
47
const App = () => (
48
<View style={styles.container}>
49
<Text style={styles.title}>Hello React Native Web!</Text>
50
</View>
51
);
52
53
const styles = StyleSheet.create({
54
container: {
55
flex: 1,
56
justifyContent: "center",
57
alignItems: "center",
58
backgroundColor: "#f5f5f5",
59
},
60
title: {
61
fontSize: 24,
62
fontWeight: "bold",
63
color: "#333",
64
},
65
});
66
67
// Register the component
68
AppRegistry.registerComponent("MyApp", () => App);
69
70
// Run the application (typically done once at app startup)
71
AppRegistry.runApplication("MyApp", {
72
rootTag: document.getElementById("root"),
73
});
74
```
75
76
### getAppKeys()
77
78
Returns an array of all registered application keys.
79
80
```javascript { .api }
81
AppRegistry.getAppKeys(): string[]
82
```
83
84
### getApplication()
85
86
Gets the application configuration for server-side rendering or advanced use cases.
87
88
```javascript { .api }
89
AppRegistry.getApplication(appKey: string, appParameters?: AppParameters): {element: React.ReactNode, getStyleElement: (any) => React.ReactNode}
90
```
91
92
## Appearance
93
94
System appearance and color scheme detection API that responds to user theme preferences and provides real-time updates when the color scheme changes.
95
96
```javascript { .api }
97
const Appearance: {
98
getColorScheme: () => 'light' | 'dark';
99
addChangeListener: (listener: (preferences: AppearancePreferences) => void) => {remove: () => void};
100
};
101
```
102
103
**Web Implementation:**
104
Uses the CSS media query `(prefers-color-scheme: dark)` to detect system theme preferences and provides listeners for theme changes.
105
106
### getColorScheme()
107
108
Returns the current color scheme preference based on system settings.
109
110
```javascript { .api }
111
Appearance.getColorScheme(): 'light' | 'dark'
112
```
113
114
### addChangeListener()
115
116
Adds a listener for color scheme changes. Returns an object with a `remove` method to unsubscribe.
117
118
```javascript { .api }
119
Appearance.addChangeListener(listener: (preferences: AppearancePreferences) => void): {remove: () => void}
120
```
121
122
**Usage:**
123
```javascript
124
import React, { useState, useEffect } from "react";
125
import { Appearance, View, Text, StyleSheet } from "react-native-web";
126
127
function ThemedApp() {
128
const [colorScheme, setColorScheme] = useState(Appearance.getColorScheme());
129
130
useEffect(() => {
131
const subscription = Appearance.addChangeListener(({ colorScheme }) => {
132
setColorScheme(colorScheme);
133
});
134
135
return () => subscription.remove();
136
}, []);
137
138
const styles = colorScheme === 'dark' ? darkStyles : lightStyles;
139
140
return (
141
<View style={styles.container}>
142
<Text style={styles.title}>
143
Current theme: {colorScheme}
144
</Text>
145
</View>
146
);
147
}
148
149
const lightStyles = StyleSheet.create({
150
container: {
151
flex: 1,
152
backgroundColor: '#ffffff',
153
justifyContent: 'center',
154
alignItems: 'center',
155
},
156
title: {
157
color: '#000000',
158
fontSize: 18,
159
},
160
});
161
162
const darkStyles = StyleSheet.create({
163
container: {
164
flex: 1,
165
backgroundColor: '#000000',
166
justifyContent: 'center',
167
alignItems: 'center',
168
},
169
title: {
170
color: '#ffffff',
171
fontSize: 18,
172
},
173
});
174
```
175
176
## Alert
177
178
Browser-compatible alert system for displaying modal dialogs and user notifications with customizable actions and styling.
179
180
```javascript { .api }
181
const Alert: {
182
alert: (title?: string, message?: string, buttons?: AlertButton[], options?: AlertOptions) => void;
183
};
184
```
185
186
**Web Implementation:**
187
The web implementation provides a minimal interface that can be extended with custom modal implementations. The default implementation uses browser APIs where available.
188
189
```javascript { .api }
190
Alert.alert(): void
191
```
192
193
**Usage:**
194
```javascript
195
import { Alert } from "react-native-web";
196
197
// Basic usage (web implementation is minimal)
198
function ShowAlert() {
199
const handlePress = () => {
200
Alert.alert();
201
};
202
203
return (
204
<TouchableOpacity onPress={handlePress}>
205
<Text>Show Alert</Text>
206
</TouchableOpacity>
207
);
208
}
209
210
// Custom alert implementation for web
211
function CustomAlert({ visible, title, message, buttons, onDismiss }) {
212
if (!visible) return null;
213
214
return (
215
<View style={styles.overlay}>
216
<View style={styles.alertContainer}>
217
{title && <Text style={styles.title}>{title}</Text>}
218
{message && <Text style={styles.message}>{message}</Text>}
219
220
<View style={styles.buttonContainer}>
221
{buttons?.map((button, index) => (
222
<TouchableOpacity
223
key={index}
224
style={[styles.button, button.style === 'destructive' && styles.destructiveButton]}
225
onPress={() => {
226
button.onPress?.();
227
onDismiss();
228
}}
229
>
230
<Text style={[styles.buttonText, button.style === 'destructive' && styles.destructiveText]}>
231
{button.text}
232
</Text>
233
</TouchableOpacity>
234
))}
235
</View>
236
</View>
237
</View>
238
);
239
}
240
241
const styles = StyleSheet.create({
242
overlay: {
243
position: 'absolute',
244
top: 0,
245
left: 0,
246
right: 0,
247
bottom: 0,
248
backgroundColor: 'rgba(0,0,0,0.5)',
249
justifyContent: 'center',
250
alignItems: 'center',
251
zIndex: 1000
252
},
253
alertContainer: {
254
backgroundColor: 'white',
255
borderRadius: 12,
256
padding: 20,
257
minWidth: 300,
258
maxWidth: 400,
259
margin: 20
260
},
261
title: {
262
fontSize: 18,
263
fontWeight: 'bold',
264
marginBottom: 8,
265
textAlign: 'center'
266
},
267
message: {
268
fontSize: 14,
269
marginBottom: 20,
270
textAlign: 'center',
271
color: '#666'
272
},
273
buttonContainer: {
274
flexDirection: 'row',
275
justifyContent: 'space-between'
276
},
277
button: {
278
flex: 1,
279
padding: 12,
280
marginHorizontal: 4,
281
backgroundColor: '#007AFF',
282
borderRadius: 8,
283
alignItems: 'center'
284
},
285
destructiveButton: {
286
backgroundColor: '#FF3B30'
287
},
288
buttonText: {
289
color: 'white',
290
fontWeight: '600'
291
},
292
destructiveText: {
293
color: 'white'
294
}
295
});
296
```
297
298
## Clipboard
299
300
Web-compatible clipboard operations for reading and writing text content with fallback implementations for older browsers.
301
302
```javascript { .api }
303
const Clipboard: {
304
isAvailable: () => boolean;
305
getString: () => Promise<string>;
306
setString: (text: string) => boolean;
307
};
308
```
309
310
### isAvailable()
311
Check if clipboard operations are supported in the current browser.
312
313
```javascript { .api }
314
Clipboard.isAvailable(): boolean
315
```
316
317
### getString()
318
Get text content from the clipboard (web implementation returns empty string).
319
320
```javascript { .api }
321
Clipboard.getString(): Promise<string>
322
```
323
324
### setString()
325
Set text content to the clipboard using browser APIs.
326
327
```javascript { .api }
328
Clipboard.setString(text: string): boolean
329
```
330
331
**Usage:**
332
```javascript
333
import { Clipboard } from "react-native-web";
334
335
function ClipboardDemo() {
336
const [clipboardText, setClipboardText] = React.useState('');
337
const [status, setStatus] = React.useState('');
338
339
const copyToClipboard = (text) => {
340
if (Clipboard.isAvailable()) {
341
const success = Clipboard.setString(text);
342
setStatus(success ? 'Copied to clipboard!' : 'Failed to copy');
343
344
// Clear status after 2 seconds
345
setTimeout(() => setStatus(''), 2000);
346
} else {
347
setStatus('Clipboard not available');
348
}
349
};
350
351
const pasteFromClipboard = async () => {
352
try {
353
// Modern browser API
354
if (navigator.clipboard && navigator.clipboard.readText) {
355
const text = await navigator.clipboard.readText();
356
setClipboardText(text);
357
setStatus('Pasted from clipboard!');
358
} else {
359
// Fallback for react-native-web implementation
360
const text = await Clipboard.getString();
361
setClipboardText(text);
362
setStatus('Retrieved from clipboard');
363
}
364
} catch (error) {
365
setStatus('Failed to read clipboard');
366
console.error('Clipboard error:', error);
367
}
368
};
369
370
return (
371
<View style={styles.container}>
372
<Text>Clipboard Operations</Text>
373
374
<TextInput
375
style={styles.input}
376
placeholder="Enter text to copy"
377
value={clipboardText}
378
onChangeText={setClipboardText}
379
/>
380
381
<View style={styles.buttonRow}>
382
<TouchableOpacity
383
style={styles.button}
384
onPress={() => copyToClipboard(clipboardText)}
385
>
386
<Text style={styles.buttonText}>Copy Text</Text>
387
</TouchableOpacity>
388
389
<TouchableOpacity
390
style={styles.button}
391
onPress={pasteFromClipboard}
392
>
393
<Text style={styles.buttonText}>Paste Text</Text>
394
</TouchableOpacity>
395
</View>
396
397
{status ? <Text style={styles.status}>{status}</Text> : null}
398
399
<View style={styles.presetButtons}>
400
<TouchableOpacity
401
style={styles.presetButton}
402
onPress={() => copyToClipboard('https://react-native-web.js.org')}
403
>
404
<Text style={styles.buttonText}>Copy URL</Text>
405
</TouchableOpacity>
406
407
<TouchableOpacity
408
style={styles.presetButton}
409
onPress={() => copyToClipboard('contact@example.com')}
410
>
411
<Text style={styles.buttonText}>Copy Email</Text>
412
</TouchableOpacity>
413
</View>
414
</View>
415
);
416
}
417
418
// Enhanced clipboard with modern API
419
class EnhancedClipboard {
420
static async write(text) {
421
try {
422
if (navigator.clipboard && navigator.clipboard.writeText) {
423
await navigator.clipboard.writeText(text);
424
return true;
425
} else {
426
return Clipboard.setString(text);
427
}
428
} catch (error) {
429
console.error('Clipboard write failed:', error);
430
return false;
431
}
432
}
433
434
static async read() {
435
try {
436
if (navigator.clipboard && navigator.clipboard.readText) {
437
return await navigator.clipboard.readText();
438
} else {
439
return await Clipboard.getString();
440
}
441
} catch (error) {
442
console.error('Clipboard read failed:', error);
443
return '';
444
}
445
}
446
447
static async writeRichText(html, text) {
448
try {
449
if (navigator.clipboard && navigator.clipboard.write) {
450
const clipboardItems = [
451
new ClipboardItem({
452
'text/html': new Blob([html], { type: 'text/html' }),
453
'text/plain': new Blob([text], { type: 'text/plain' })
454
})
455
];
456
await navigator.clipboard.write(clipboardItems);
457
return true;
458
} else {
459
// Fallback to plain text
460
return await this.write(text);
461
}
462
} catch (error) {
463
console.error('Rich text clipboard failed:', error);
464
return false;
465
}
466
}
467
}
468
```
469
470
## Linking
471
472
URL handling and deep linking capabilities adapted for web environments with support for navigation, external links, and URL event handling.
473
474
```javascript { .api }
475
const Linking: {
476
addEventListener: (eventType: string, callback: Function) => { remove: () => void };
477
removeEventListener: (eventType: string, callback: Function) => void;
478
canOpenURL: (url: string) => Promise<boolean>;
479
getInitialURL: () => Promise<string>;
480
openURL: (url: string, target?: string) => Promise<void>;
481
};
482
```
483
484
### addEventListener()
485
Listen for URL change events and other linking-related events.
486
487
```javascript { .api }
488
Linking.addEventListener(
489
eventType: string,
490
callback: (...args: any[]) => void
491
): { remove: () => void }
492
```
493
494
### canOpenURL()
495
Check if a URL can be opened (always returns true on web).
496
497
```javascript { .api }
498
Linking.canOpenURL(url: string): Promise<boolean>
499
```
500
501
### getInitialURL()
502
Get the initial URL that opened the app (current page URL on web).
503
504
```javascript { .api }
505
Linking.getInitialURL(): Promise<string>
506
```
507
508
### openURL()
509
Open a URL in the browser with configurable target window.
510
511
```javascript { .api }
512
Linking.openURL(url: string, target?: string): Promise<void>
513
```
514
515
**Usage:**
516
```javascript
517
import { Linking } from "react-native-web";
518
519
function LinkingDemo() {
520
const [currentUrl, setCurrentUrl] = React.useState('');
521
522
React.useEffect(() => {
523
// Get initial URL
524
Linking.getInitialURL().then(setCurrentUrl);
525
526
// Listen for URL changes (web-specific implementation)
527
const subscription = Linking.addEventListener('onOpen', (event) => {
528
console.log('URL opened:', event);
529
setCurrentUrl(event.url || window.location.href);
530
});
531
532
// Cleanup
533
return () => subscription.remove();
534
}, []);
535
536
const openExternalLink = (url) => {
537
Linking.canOpenURL(url).then(supported => {
538
if (supported) {
539
Linking.openURL(url, '_blank');
540
} else {
541
console.log("Don't know how to open URI: " + url);
542
}
543
});
544
};
545
546
const openInSameTab = (url) => {
547
Linking.openURL(url, '_self');
548
};
549
550
const openEmail = (email, subject = '', body = '') => {
551
const emailUrl = `mailto:${email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
552
Linking.openURL(emailUrl);
553
};
554
555
const openPhone = (phoneNumber) => {
556
const phoneUrl = `tel:${phoneNumber}`;
557
Linking.openURL(phoneUrl);
558
};
559
560
return (
561
<View style={styles.container}>
562
<Text style={styles.title}>Linking Demo</Text>
563
<Text style={styles.currentUrl}>Current URL: {currentUrl}</Text>
564
565
<View style={styles.buttonContainer}>
566
<TouchableOpacity
567
style={styles.linkButton}
568
onPress={() => openExternalLink('https://github.com')}
569
>
570
<Text style={styles.buttonText}>Open GitHub (New Tab)</Text>
571
</TouchableOpacity>
572
573
<TouchableOpacity
574
style={styles.linkButton}
575
onPress={() => openInSameTab('https://react-native-web.js.org')}
576
>
577
<Text style={styles.buttonText}>Open RNW Docs (Same Tab)</Text>
578
</TouchableOpacity>
579
580
<TouchableOpacity
581
style={styles.linkButton}
582
onPress={() => openEmail('hello@example.com', 'Hello!', 'This is a test email.')}
583
>
584
<Text style={styles.buttonText}>Send Email</Text>
585
</TouchableOpacity>
586
587
<TouchableOpacity
588
style={styles.linkButton}
589
onPress={() => openPhone('+1234567890')}
590
>
591
<Text style={styles.buttonText}>Call Phone</Text>
592
</TouchableOpacity>
593
</View>
594
</View>
595
);
596
}
597
598
// Advanced URL handling
599
class URLHandler {
600
static parseURL(url) {
601
try {
602
const urlObj = new URL(url);
603
return {
604
protocol: urlObj.protocol,
605
host: urlObj.host,
606
pathname: urlObj.pathname,
607
search: urlObj.search,
608
hash: urlObj.hash,
609
searchParams: Object.fromEntries(urlObj.searchParams)
610
};
611
} catch (error) {
612
console.error('Invalid URL:', error);
613
return null;
614
}
615
}
616
617
static buildURL(base, params = {}) {
618
const url = new URL(base);
619
Object.entries(params).forEach(([key, value]) => {
620
url.searchParams.set(key, value);
621
});
622
return url.toString();
623
}
624
625
static async openWithConfirmation(url, message = 'Open this link?') {
626
if (window.confirm(message)) {
627
return Linking.openURL(url, '_blank');
628
}
629
}
630
631
static trackLinkClick(url, analytics = {}) {
632
// Track the link click
633
if (analytics.track) {
634
analytics.track('External Link Clicked', { url });
635
}
636
return Linking.openURL(url, '_blank');
637
}
638
}
639
```
640
641
## Share
642
643
Web Share API integration for sharing content with native browser sharing capabilities and fallback implementations.
644
645
```javascript { .api }
646
const Share: {
647
share: (content: ShareContent, options?: ShareOptions) => Promise<ShareResult>;
648
sharedAction: string;
649
dismissedAction: string;
650
};
651
```
652
653
### share()
654
Share content using the Web Share API or fallback methods.
655
656
```javascript { .api }
657
Share.share(
658
content: {
659
title?: string;
660
message?: string;
661
url?: string;
662
},
663
options?: {}
664
): Promise<{ action: string }>
665
```
666
667
**Usage:**
668
```javascript
669
import { Share } from "react-native-web";
670
671
function ShareDemo() {
672
const [shareResult, setShareResult] = React.useState('');
673
674
const shareContent = async (content) => {
675
try {
676
const result = await Share.share(content);
677
678
if (result.action === Share.sharedAction) {
679
setShareResult('Content shared successfully!');
680
} else if (result.action === Share.dismissedAction) {
681
setShareResult('Share dialog dismissed');
682
}
683
} catch (error) {
684
console.error('Share failed:', error);
685
setShareResult('Share not supported or failed');
686
687
// Fallback: copy to clipboard
688
if (content.url) {
689
navigator.clipboard?.writeText(content.url);
690
setShareResult('URL copied to clipboard as fallback');
691
}
692
}
693
};
694
695
const shareCurrentPage = () => {
696
shareContent({
697
title: document.title,
698
message: 'Check out this page!',
699
url: window.location.href
700
});
701
};
702
703
const shareCustomContent = () => {
704
shareContent({
705
title: 'React Native Web',
706
message: 'Build native web apps with React Native!',
707
url: 'https://necolas.github.io/react-native-web/'
708
});
709
};
710
711
const shareTextOnly = () => {
712
shareContent({
713
message: 'This is a text-only share with no URL'
714
});
715
};
716
717
return (
718
<View style={styles.container}>
719
<Text style={styles.title}>Share Demo</Text>
720
721
<View style={styles.buttonContainer}>
722
<TouchableOpacity style={styles.shareButton} onPress={shareCurrentPage}>
723
<Text style={styles.buttonText}>Share Current Page</Text>
724
</TouchableOpacity>
725
726
<TouchableOpacity style={styles.shareButton} onPress={shareCustomContent}>
727
<Text style={styles.buttonText}>Share Custom Content</Text>
728
</TouchableOpacity>
729
730
<TouchableOpacity style={styles.shareButton} onPress={shareTextOnly}>
731
<Text style={styles.buttonText}>Share Text Only</Text>
732
</TouchableOpacity>
733
</View>
734
735
{shareResult ? (
736
<Text style={styles.result}>{shareResult}</Text>
737
) : null}
738
</View>
739
);
740
}
741
742
// Enhanced sharing with multiple fallbacks
743
class EnhancedShare {
744
static async share(content, options = {}) {
745
// Try Web Share API first
746
if (navigator.share) {
747
try {
748
await navigator.share(content);
749
return { action: 'shared' };
750
} catch (error) {
751
if (error.name === 'AbortError') {
752
return { action: 'dismissed' };
753
}
754
// Fall through to other methods
755
}
756
}
757
758
// Fallback: Social media sharing
759
return this.fallbackShare(content, options);
760
}
761
762
static async fallbackShare(content, options) {
763
const { title, message, url } = content;
764
const shareText = `${title ? title + ': ' : ''}${message || ''}${url ? ' ' + url : ''}`;
765
766
if (options.preferredMethod === 'clipboard') {
767
return this.shareViaClipboard(shareText);
768
}
769
770
// Show custom share modal
771
return this.showShareModal(content);
772
}
773
774
static async shareViaClipboard(text) {
775
try {
776
await navigator.clipboard.writeText(text);
777
alert('Content copied to clipboard!');
778
return { action: 'shared' };
779
} catch (error) {
780
console.error('Clipboard share failed:', error);
781
return { action: 'failed' };
782
}
783
}
784
785
static showShareModal(content) {
786
return new Promise((resolve) => {
787
// Create custom share modal
788
const modal = document.createElement('div');
789
modal.innerHTML = `
790
<div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 1000; display: flex; align-items: center; justify-content: center;">
791
<div style="background: white; border-radius: 12px; padding: 20px; max-width: 400px; margin: 20px;">
792
<h3>Share Content</h3>
793
<p>${content.title || ''}</p>
794
<p>${content.message || ''}</p>
795
<div style="display: flex; gap: 10px; margin-top: 20px;">
796
<button onclick="window.shareViaTwitter('${content.url}', '${content.message}')">Twitter</button>
797
<button onclick="window.shareViaFacebook('${content.url}')">Facebook</button>
798
<button onclick="window.copyToClipboard('${content.url || content.message}')">Copy</button>
799
<button onclick="window.closeShareModal()">Cancel</button>
800
</div>
801
</div>
802
</div>
803
`;
804
805
document.body.appendChild(modal);
806
807
// Add global functions
808
window.closeShareModal = () => {
809
document.body.removeChild(modal);
810
resolve({ action: 'dismissed' });
811
};
812
813
window.shareViaTwitter = (url, text) => {
814
window.open(`https://twitter.com/intent/tweet?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`);
815
window.closeShareModal();
816
resolve({ action: 'shared' });
817
};
818
819
window.shareViaFacebook = (url) => {
820
window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}`);
821
window.closeShareModal();
822
resolve({ action: 'shared' });
823
};
824
825
window.copyToClipboard = async (text) => {
826
await navigator.clipboard.writeText(text);
827
window.closeShareModal();
828
resolve({ action: 'shared' });
829
};
830
});
831
}
832
}
833
```
834
835
## Vibration
836
837
Vibration API wrapper for web with support for patterns and fallback behavior on non-supporting devices.
838
839
```javascript { .api }
840
const Vibration: {
841
cancel: () => void;
842
vibrate: (pattern?: number | number[]) => void;
843
};
844
```
845
846
### vibrate()
847
Trigger device vibration with optional pattern support.
848
849
```javascript { .api }
850
Vibration.vibrate(pattern?: number | number[] = 400): void
851
```
852
853
### cancel()
854
Cancel any ongoing vibration.
855
856
```javascript { .api }
857
Vibration.cancel(): void
858
```
859
860
**Usage:**
861
```javascript
862
import { Vibration } from "react-native-web";
863
864
function VibrationDemo() {
865
const [isVibrationSupported, setIsVibrationSupported] = React.useState(false);
866
867
React.useEffect(() => {
868
// Check if vibration is supported
869
setIsVibrationSupported('vibrate' in navigator);
870
}, []);
871
872
const singleVibration = () => {
873
Vibration.vibrate(200);
874
};
875
876
const patternVibration = () => {
877
// Pattern: [vibrate, pause, vibrate, pause, ...]
878
Vibration.vibrate([100, 200, 100, 200, 300]);
879
};
880
881
const longVibration = () => {
882
Vibration.vibrate(1000);
883
};
884
885
const cancelVibration = () => {
886
Vibration.cancel();
887
};
888
889
// Haptic feedback patterns
890
const hapticPatterns = {
891
notification: [50, 50, 50],
892
warning: [100, 100, 100, 100, 200],
893
error: [200, 100, 200, 100, 200],
894
success: [50, 50, 100],
895
click: [10],
896
doubleClick: [10, 50, 10]
897
};
898
899
const playHaptic = (pattern) => {
900
if (isVibrationSupported) {
901
Vibration.vibrate(hapticPatterns[pattern]);
902
} else {
903
console.log(`Haptic feedback: ${pattern}`);
904
}
905
};
906
907
return (
908
<View style={styles.container}>
909
<Text style={styles.title}>Vibration Demo</Text>
910
911
<Text style={styles.status}>
912
Vibration Support: {isVibrationSupported ? 'Supported' : 'Not Supported'}
913
</Text>
914
915
<View style={styles.buttonContainer}>
916
<TouchableOpacity
917
style={styles.vibrationButton}
918
onPress={singleVibration}
919
>
920
<Text style={styles.buttonText}>Single Vibration</Text>
921
</TouchableOpacity>
922
923
<TouchableOpacity
924
style={styles.vibrationButton}
925
onPress={patternVibration}
926
>
927
<Text style={styles.buttonText}>Pattern Vibration</Text>
928
</TouchableOpacity>
929
930
<TouchableOpacity
931
style={styles.vibrationButton}
932
onPress={longVibration}
933
>
934
<Text style={styles.buttonText}>Long Vibration</Text>
935
</TouchableOpacity>
936
937
<TouchableOpacity
938
style={[styles.vibrationButton, styles.cancelButton]}
939
onPress={cancelVibration}
940
>
941
<Text style={styles.buttonText}>Cancel Vibration</Text>
942
</TouchableOpacity>
943
</View>
944
945
<Text style={styles.sectionTitle}>Haptic Feedback Patterns</Text>
946
<View style={styles.hapticContainer}>
947
{Object.keys(hapticPatterns).map((pattern) => (
948
<TouchableOpacity
949
key={pattern}
950
style={styles.hapticButton}
951
onPress={() => playHaptic(pattern)}
952
>
953
<Text style={styles.hapticText}>{pattern}</Text>
954
</TouchableOpacity>
955
))}
956
</View>
957
</View>
958
);
959
}
960
961
// Enhanced vibration with game-like haptics
962
class GameHaptics {
963
static isSupported() {
964
return 'vibrate' in navigator;
965
}
966
967
static feedback = {
968
// UI Interactions
969
buttonPress: () => Vibration.vibrate(10),
970
buttonRelease: () => Vibration.vibrate(5),
971
972
// Game Events
973
powerUp: () => Vibration.vibrate([50, 50, 100, 50, 150]),
974
levelComplete: () => Vibration.vibrate([100, 100, 100, 100, 200]),
975
gameOver: () => Vibration.vibrate([200, 100, 200, 100, 400]),
976
977
// Notifications
978
message: () => Vibration.vibrate([80, 80, 80]),
979
alert: () => Vibration.vibrate([100, 200, 100, 200, 300]),
980
981
// Continuous effects
982
startEngine: () => {
983
const pattern = Array(10).fill([20, 30]).flat();
984
Vibration.vibrate(pattern);
985
},
986
987
stopEngine: () => Vibration.cancel()
988
};
989
990
static playSequence(sequence, callback) {
991
let index = 0;
992
993
const playNext = () => {
994
if (index < sequence.length) {
995
this.feedback[sequence[index]]();
996
index++;
997
setTimeout(playNext, 500); // Delay between haptics
998
} else if (callback) {
999
callback();
1000
}
1001
};
1002
1003
playNext();
1004
}
1005
}
1006
```
1007
1008
## I18nManager
1009
1010
Internationalization manager for handling right-to-left (RTL) layouts and locale-specific formatting with web-compatible implementation.
1011
1012
```javascript { .api }
1013
const I18nManager: {
1014
allowRTL: (allowRTL: boolean) => void;
1015
forceRTL: (forceRTL: boolean) => void;
1016
getConstants: () => { isRTL: boolean };
1017
isRTL?: boolean;
1018
};
1019
```
1020
1021
### allowRTL()
1022
Allow RTL layout (web implementation is no-op).
1023
1024
```javascript { .api }
1025
I18nManager.allowRTL(allowRTL: boolean): void
1026
```
1027
1028
### forceRTL()
1029
Force RTL layout (web implementation is no-op).
1030
1031
```javascript { .api }
1032
I18nManager.forceRTL(forceRTL: boolean): void
1033
```
1034
1035
### getConstants()
1036
Get internationalization constants including RTL status.
1037
1038
```javascript { .api }
1039
I18nManager.getConstants(): { isRTL: boolean }
1040
```
1041
1042
**Usage:**
1043
```javascript
1044
import { I18nManager } from "react-native-web";
1045
1046
function I18nDemo() {
1047
const [isRTL, setIsRTL] = React.useState(false);
1048
1049
React.useEffect(() => {
1050
const constants = I18nManager.getConstants();
1051
setIsRTL(constants.isRTL);
1052
}, []);
1053
1054
// Web-specific RTL detection
1055
const detectRTLFromDOM = () => {
1056
const direction = document.dir || document.documentElement.dir || 'ltr';
1057
return direction === 'rtl';
1058
};
1059
1060
const detectRTLFromLanguage = (language) => {
1061
const rtlLanguages = ['ar', 'he', 'fa', 'ur', 'yi'];
1062
return rtlLanguages.includes(language.split('-')[0]);
1063
};
1064
1065
return (
1066
<View style={[styles.container, isRTL && styles.rtlContainer]}>
1067
<Text style={styles.title}>
1068
Internationalization Demo
1069
</Text>
1070
1071
<Text>RTL Status: {isRTL ? 'Right-to-Left' : 'Left-to-Right'}</Text>
1072
<Text>DOM Direction: {detectRTLFromDOM() ? 'RTL' : 'LTR'}</Text>
1073
1074
<View style={[styles.textContainer, isRTL && styles.rtlText]}>
1075
<Text style={styles.label}>Name:</Text>
1076
<Text style={styles.value}>John Doe</Text>
1077
</View>
1078
1079
<View style={[styles.textContainer, isRTL && styles.rtlText]}>
1080
<Text style={styles.label}>العربية:</Text>
1081
<Text style={styles.value}>مرحبا بالعالم</Text>
1082
</View>
1083
</View>
1084
);
1085
}
1086
1087
// Enhanced I18n utilities for web
1088
class WebI18nManager {
1089
static detectDirection(locale) {
1090
const rtlLocales = [
1091
'ar', 'arc', 'dv', 'fa', 'ha', 'he', 'khw', 'ks',
1092
'ku', 'ps', 'ur', 'yi'
1093
];
1094
1095
const language = locale.split('-')[0];
1096
return rtlLocales.includes(language) ? 'rtl' : 'ltr';
1097
}
1098
1099
static setDocumentDirection(direction) {
1100
document.documentElement.dir = direction;
1101
document.documentElement.setAttribute('data-direction', direction);
1102
}
1103
1104
static createRTLStyles(styles, isRTL) {
1105
if (!isRTL) return styles;
1106
1107
const rtlStyles = { ...styles };
1108
1109
// Flip horizontal margins and padding
1110
if (styles.marginLeft !== undefined) {
1111
rtlStyles.marginRight = styles.marginLeft;
1112
rtlStyles.marginLeft = styles.marginRight || 0;
1113
}
1114
1115
if (styles.paddingLeft !== undefined) {
1116
rtlStyles.paddingRight = styles.paddingLeft;
1117
rtlStyles.paddingLeft = styles.paddingRight || 0;
1118
}
1119
1120
// Flip text alignment
1121
if (styles.textAlign === 'left') {
1122
rtlStyles.textAlign = 'right';
1123
} else if (styles.textAlign === 'right') {
1124
rtlStyles.textAlign = 'left';
1125
}
1126
1127
// Flip flex direction
1128
if (styles.flexDirection === 'row') {
1129
rtlStyles.flexDirection = 'row-reverse';
1130
} else if (styles.flexDirection === 'row-reverse') {
1131
rtlStyles.flexDirection = 'row';
1132
}
1133
1134
return rtlStyles;
1135
}
1136
1137
static useRTLLayout(locale) {
1138
const [isRTL, setIsRTL] = React.useState(false);
1139
1140
React.useEffect(() => {
1141
const direction = this.detectDirection(locale);
1142
const rtl = direction === 'rtl';
1143
1144
setIsRTL(rtl);
1145
this.setDocumentDirection(direction);
1146
}, [locale]);
1147
1148
return isRTL;
1149
}
1150
}
1151
1152
const styles = StyleSheet.create({
1153
container: {
1154
flex: 1,
1155
padding: 16
1156
},
1157
rtlContainer: {
1158
flexDirection: 'row-reverse'
1159
},
1160
title: {
1161
fontSize: 18,
1162
fontWeight: 'bold',
1163
marginBottom: 16
1164
},
1165
textContainer: {
1166
flexDirection: 'row',
1167
marginBottom: 8,
1168
alignItems: 'center'
1169
},
1170
rtlText: {
1171
flexDirection: 'row-reverse'
1172
},
1173
label: {
1174
fontWeight: 'bold',
1175
marginRight: 8
1176
},
1177
value: {
1178
flex: 1
1179
}
1180
});
1181
```
1182
1183
## Types
1184
1185
```javascript { .api }
1186
interface AppParameters {
1187
rootTag: Element;
1188
initialProps?: Object;
1189
hydrate?: boolean;
1190
mode?: 'legacy' | 'concurrent';
1191
callback?: () => void;
1192
}
1193
1194
interface AppConfig {
1195
appKey: string;
1196
component?: () => React.ComponentType;
1197
run?: Function;
1198
section?: boolean;
1199
}
1200
1201
type ComponentProviderInstrumentationHook = (component: () => React.ComponentType) => React.ComponentType;
1202
type WrapperComponentProvider = (any) => React.ComponentType;
1203
1204
type ColorSchemeName = 'light' | 'dark';
1205
1206
interface AppearancePreferences {
1207
colorScheme: ColorSchemeName;
1208
}
1209
1210
interface ShareContent {
1211
title?: string;
1212
message?: string;
1213
url?: string;
1214
}
1215
1216
interface ShareOptions {}
1217
1218
interface ShareResult {
1219
action: string;
1220
}
1221
1222
type VibratePattern = number | number[];
1223
1224
interface I18nConstants {
1225
isRTL: boolean;
1226
}
1227
1228
type AlertButton = {
1229
text: string;
1230
onPress?: () => void;
1231
style?: 'default' | 'cancel' | 'destructive';
1232
};
1233
1234
type AlertOptions = {
1235
cancelable?: boolean;
1236
onDismiss?: () => void;
1237
};
1238
1239
interface LinkingEventCallback {
1240
(event: { url: string }): void;
1241
}
1242
```