react-native-web-serial-api 0.0.2 → 0.1.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.
Files changed (120) hide show
  1. package/README.md +23 -0
  2. package/TESTING.md +301 -0
  3. package/android/build.gradle +2 -2
  4. package/android/src/main/java/dev/webserialapi/NativeUsbSerialModule.java +7 -1
  5. package/lib/commonjs/UsbSerial.js +58 -26
  6. package/lib/commonjs/UsbSerial.js.map +1 -1
  7. package/lib/commonjs/WebSerial.js +169 -57
  8. package/lib/commonjs/WebSerial.js.map +1 -1
  9. package/lib/commonjs/index.js +13 -1
  10. package/lib/commonjs/index.js.map +1 -1
  11. package/lib/commonjs/lib/dom-exception.js +176 -0
  12. package/lib/commonjs/lib/dom-exception.js.map +1 -0
  13. package/lib/commonjs/lib/event-target.js +138 -0
  14. package/lib/commonjs/lib/event-target.js.map +1 -0
  15. package/lib/commonjs/lib/promise.js +23 -0
  16. package/lib/commonjs/lib/promise.js.map +1 -0
  17. package/lib/commonjs/testing/index.js +70 -0
  18. package/lib/commonjs/testing/index.js.map +1 -0
  19. package/lib/commonjs/testing/install.js +54 -0
  20. package/lib/commonjs/testing/install.js.map +1 -0
  21. package/lib/commonjs/testing/serial-device.js +164 -0
  22. package/lib/commonjs/testing/serial-device.js.map +1 -0
  23. package/lib/commonjs/testing/virtual-serial.js +615 -0
  24. package/lib/commonjs/testing/virtual-serial.js.map +1 -0
  25. package/lib/commonjs/transport.js +61 -0
  26. package/lib/commonjs/transport.js.map +1 -0
  27. package/lib/typescript/src/UsbSerial.d.ts +24 -67
  28. package/lib/typescript/src/UsbSerial.d.ts.map +1 -1
  29. package/lib/typescript/src/WebSerial.d.ts +11 -2
  30. package/lib/typescript/src/WebSerial.d.ts.map +1 -1
  31. package/lib/typescript/src/index.d.ts +2 -0
  32. package/lib/typescript/src/index.d.ts.map +1 -1
  33. package/lib/typescript/src/lib/dom-exception.d.ts +100 -0
  34. package/lib/typescript/src/lib/dom-exception.d.ts.map +1 -0
  35. package/lib/typescript/src/lib/event-target.d.ts +53 -0
  36. package/lib/typescript/src/lib/event-target.d.ts.map +1 -0
  37. package/lib/typescript/src/lib/promise.d.ts +11 -0
  38. package/lib/typescript/src/lib/promise.d.ts.map +1 -0
  39. package/lib/typescript/src/testing/index.d.ts +23 -0
  40. package/lib/typescript/src/testing/index.d.ts.map +1 -0
  41. package/lib/typescript/src/testing/install.d.ts +25 -0
  42. package/lib/typescript/src/testing/install.d.ts.map +1 -0
  43. package/lib/typescript/src/testing/serial-device.d.ts +127 -0
  44. package/lib/typescript/src/testing/serial-device.d.ts.map +1 -0
  45. package/lib/typescript/src/testing/virtual-serial.d.ts +205 -0
  46. package/lib/typescript/src/testing/virtual-serial.d.ts.map +1 -0
  47. package/lib/typescript/src/transport.d.ts +131 -0
  48. package/lib/typescript/src/transport.d.ts.map +1 -0
  49. package/package.json +38 -2
  50. package/src/UsbSerial.ts +65 -90
  51. package/src/WebSerial.ts +227 -88
  52. package/src/index.ts +2 -7
  53. package/src/lib/dom-exception.ts +129 -60
  54. package/src/lib/event-target.ts +46 -21
  55. package/src/lib/promise.ts +7 -7
  56. package/src/testing/index.ts +42 -0
  57. package/src/testing/install.ts +65 -0
  58. package/src/testing/serial-device.ts +193 -0
  59. package/src/testing/virtual-serial.ts +801 -0
  60. package/src/transport.ts +200 -0
  61. package/babel.config.js +0 -3
  62. package/biome.json +0 -35
  63. package/example/.watchmanconfig +0 -1
  64. package/example/App.tsx +0 -71
  65. package/example/__tests__/App.test.tsx +0 -16
  66. package/example/__tests__/connectEvents.test.tsx +0 -81
  67. package/example/__tests__/getPorts.test.tsx +0 -140
  68. package/example/android/app/build.gradle +0 -120
  69. package/example/android/app/debug.keystore +0 -0
  70. package/example/android/app/proguard-rules.pro +0 -10
  71. package/example/android/app/src/debug/AndroidManifest.xml +0 -9
  72. package/example/android/app/src/main/AndroidManifest.xml +0 -38
  73. package/example/android/app/src/main/java/dev/uzlopak/MainActivity.kt +0 -22
  74. package/example/android/app/src/main/java/dev/uzlopak/MainApplication.kt +0 -41
  75. package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +0 -37
  76. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  77. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  78. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  79. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  80. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  81. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  82. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  83. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  84. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  85. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  86. package/example/android/app/src/main/res/values/strings.xml +0 -3
  87. package/example/android/app/src/main/res/values/styles.xml +0 -9
  88. package/example/android/build.gradle +0 -22
  89. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  90. package/example/android/gradle/wrapper/gradle-wrapper.properties +0 -7
  91. package/example/android/gradle.properties +0 -47
  92. package/example/android/gradlew +0 -252
  93. package/example/android/gradlew.bat +0 -94
  94. package/example/android/settings.gradle +0 -6
  95. package/example/app.json +0 -4
  96. package/example/babel.config.js +0 -21
  97. package/example/biome.json +0 -47
  98. package/example/deploy.sh +0 -11
  99. package/example/index.html +0 -26
  100. package/example/index.js +0 -9
  101. package/example/index.web.js +0 -8
  102. package/example/jest.config.js +0 -12
  103. package/example/metro.config.js +0 -58
  104. package/example/package-lock.json +0 -14510
  105. package/example/package.json +0 -48
  106. package/example/react-native.config.js +0 -17
  107. package/example/src/components/AppBar.tsx +0 -73
  108. package/example/src/components/Menu.tsx +0 -90
  109. package/example/src/components/SingleChoiceDialog.tsx +0 -120
  110. package/example/src/screens/ConnectScreen.tsx +0 -195
  111. package/example/src/screens/DevicesScreen.tsx +0 -248
  112. package/example/src/screens/TerminalScreen.tsx +0 -564
  113. package/example/src/settings.ts +0 -43
  114. package/example/src/theme.ts +0 -19
  115. package/example/src/util/TextUtil.ts +0 -129
  116. package/example/tsconfig.json +0 -10
  117. package/example/vite.config.mjs +0 -55
  118. package/scripts/deploy-release.sh +0 -127
  119. package/tsconfig.build.json +0 -7
  120. package/tsconfig.json +0 -20
@@ -1,48 +0,0 @@
1
- {
2
- "name": "react-native-web-serial-api-example",
3
- "version": "0.0.1",
4
- "private": true,
5
- "scripts": {
6
- "android": "react-native run-android",
7
- "start": "react-native start",
8
- "build:android": "cd android && ./gradlew assembleDebug",
9
- "web": "vite",
10
- "web:build": "vite build",
11
- "web:preview": "vite preview",
12
- "lint": "biome check",
13
- "format": "biome check --write",
14
- "test": "jest"
15
- },
16
- "dependencies": {
17
- "react": "19.2.3",
18
- "react-dom": "19.2.3",
19
- "react-native": "0.85.3",
20
- "react-native-web": "^0.21.2",
21
- "react-native-web-serial-api": "file:.."
22
- },
23
- "devDependencies": {
24
- "@babel/core": "^7.25.2",
25
- "@babel/preset-env": "^7.25.3",
26
- "@babel/runtime": "^7.25.0",
27
- "@biomejs/biome": "2.4.16",
28
- "@react-native-community/cli": "20.1.3",
29
- "@react-native-community/cli-platform-android": "20.1.3",
30
- "@react-native/babel-preset": "0.85.3",
31
- "@react-native/jest-preset": "0.85.3",
32
- "@react-native/metro-config": "0.85.3",
33
- "@react-native/typescript-config": "0.85.3",
34
- "@testing-library/react-native": "^13.3.3",
35
- "@types/react": "^19.2.6",
36
- "@types/react-dom": "^19.2.0",
37
- "@vitejs/plugin-react": "^6.0.2",
38
- "babel-jest": "^29.6.3",
39
- "babel-plugin-module-resolver": "^5.0.3",
40
- "jest": "^29.6.3",
41
- "react-test-renderer": "19.2.3",
42
- "typescript": "6.0.3",
43
- "vite": "^8.0.0"
44
- },
45
- "engines": {
46
- "node": ">=22"
47
- }
48
- }
@@ -1,17 +0,0 @@
1
- const path = require('node:path');
2
- const pkg = require('../package.json');
3
-
4
- /**
5
- * The library lives one level up (repo root) and is installed here as a symlink
6
- * pointing to an ancestor of this example dir. React Native autolinking skips
7
- * dependencies whose resolved path is an ancestor of the project, so we map the
8
- * dependency explicitly to the library's android module. This mirrors what
9
- * create-react-native-library scaffolds for its example app.
10
- */
11
- module.exports = {
12
- dependencies: {
13
- [pkg.name]: {
14
- root: path.join(__dirname, '..'),
15
- },
16
- },
17
- };
@@ -1,73 +0,0 @@
1
- import React from 'react';
2
- import {
3
- Platform,
4
- StatusBar,
5
- StyleSheet,
6
- Text,
7
- TouchableOpacity,
8
- View,
9
- } from 'react-native';
10
- import {colors} from '../theme';
11
- import {Menu, type MenuItem} from './Menu';
12
-
13
- type Props = {
14
- title: string;
15
- onBack?: () => void;
16
- menu?: MenuItem[];
17
- };
18
-
19
- const statusBarHeight =
20
- Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) : 0;
21
-
22
- export function AppBar({title, onBack, menu}: Props) {
23
- const [open, setOpen] = React.useState(false);
24
- return (
25
- <View style={styles.wrap}>
26
- <View style={styles.bar}>
27
- {onBack ? (
28
- <TouchableOpacity onPress={onBack} style={styles.iconBtn}>
29
- <Text style={styles.icon}>←</Text>
30
- </TouchableOpacity>
31
- ) : null}
32
- <Text style={styles.title} numberOfLines={1}>
33
- {title}
34
- </Text>
35
- <View style={styles.spacer} />
36
- {menu?.length ? (
37
- <TouchableOpacity
38
- onPress={() => setOpen(true)}
39
- style={styles.iconBtn}>
40
- <Text style={styles.icon}>⋮</Text>
41
- </TouchableOpacity>
42
- ) : null}
43
- </View>
44
- {menu ? (
45
- <Menu visible={open} items={menu} onClose={() => setOpen(false)} />
46
- ) : null}
47
- </View>
48
- );
49
- }
50
-
51
- const styles = StyleSheet.create({
52
- wrap: {backgroundColor: colors.primary, paddingTop: statusBarHeight},
53
- bar: {
54
- height: 56,
55
- flexDirection: 'row',
56
- alignItems: 'center',
57
- paddingHorizontal: 4,
58
- },
59
- iconBtn: {
60
- width: 44,
61
- height: 44,
62
- alignItems: 'center',
63
- justifyContent: 'center',
64
- },
65
- icon: {color: colors.onPrimary, fontSize: 22},
66
- title: {
67
- color: colors.onPrimary,
68
- fontSize: 20,
69
- fontWeight: '600',
70
- marginLeft: 8,
71
- },
72
- spacer: {flex: 1},
73
- });
@@ -1,90 +0,0 @@
1
- import {
2
- Modal,
3
- Pressable,
4
- ScrollView,
5
- StyleSheet,
6
- Text,
7
- View,
8
- } from 'react-native';
9
- import {colors} from '../theme';
10
-
11
- export type MenuItem = {
12
- key: string;
13
- title: string;
14
- checkable?: boolean;
15
- checked?: boolean;
16
- disabled?: boolean;
17
- onPress: () => void;
18
- };
19
-
20
- type Props = {
21
- visible: boolean;
22
- items: MenuItem[];
23
- onClose: () => void;
24
- };
25
-
26
- /** Android-style overflow (⋮) dropdown anchored to the top-right. */
27
- export function Menu({visible, items, onClose}: Props) {
28
- return (
29
- <Modal
30
- visible={visible}
31
- transparent
32
- animationType="fade"
33
- onRequestClose={onClose}>
34
- <Pressable style={styles.backdrop} onPress={onClose}>
35
- <View style={styles.menu}>
36
- <ScrollView>
37
- {items.map(item => (
38
- <Pressable
39
- key={item.key}
40
- style={styles.item}
41
- disabled={item.disabled}
42
- onPress={() => {
43
- onClose();
44
- item.onPress();
45
- }}>
46
- <Text
47
- style={[styles.itemText, item.disabled && styles.disabled]}>
48
- {item.title}
49
- </Text>
50
- {item.checkable ? (
51
- <Text style={styles.check}>{item.checked ? '☑' : '☐'}</Text>
52
- ) : null}
53
- </Pressable>
54
- ))}
55
- </ScrollView>
56
- </View>
57
- </Pressable>
58
- </Modal>
59
- );
60
- }
61
-
62
- const styles = StyleSheet.create({
63
- backdrop: {
64
- flex: 1,
65
- alignItems: 'flex-end',
66
- paddingTop: 4,
67
- paddingRight: 4,
68
- },
69
- menu: {
70
- minWidth: 220,
71
- maxHeight: '80%',
72
- backgroundColor: colors.background,
73
- borderRadius: 4,
74
- paddingVertical: 4,
75
- elevation: 8,
76
- shadowColor: '#000',
77
- shadowOpacity: 0.3,
78
- shadowRadius: 6,
79
- shadowOffset: {width: 0, height: 2},
80
- },
81
- item: {
82
- flexDirection: 'row',
83
- alignItems: 'center',
84
- paddingVertical: 12,
85
- paddingHorizontal: 16,
86
- },
87
- itemText: {flex: 1, fontSize: 16, color: colors.text},
88
- disabled: {color: colors.textSecondary, opacity: 0.5},
89
- check: {fontSize: 16, marginLeft: 16, color: colors.text},
90
- });
@@ -1,120 +0,0 @@
1
- import {
2
- Alert,
3
- Modal,
4
- Pressable,
5
- ScrollView,
6
- StyleSheet,
7
- Text,
8
- View,
9
- } from 'react-native';
10
- import {colors} from '../theme';
11
-
12
- export type Choice<T> = {label: string; value: T};
13
-
14
- type Props<T> = {
15
- visible: boolean;
16
- title: string;
17
- options: Choice<T>[];
18
- selected: T;
19
- onSelect: (value: T) => void;
20
- onClose: () => void;
21
- /** When set, shows an "Info" button that pops this message. */
22
- infoMessage?: string;
23
- };
24
-
25
- /** Single-choice (radio) dialog, mirroring AlertDialog.setSingleChoiceItems. */
26
- export function SingleChoiceDialog<T extends string | number>({
27
- visible,
28
- title,
29
- options,
30
- selected,
31
- onSelect,
32
- onClose,
33
- infoMessage,
34
- }: Props<T>) {
35
- return (
36
- <Modal
37
- visible={visible}
38
- transparent
39
- animationType="fade"
40
- onRequestClose={onClose}>
41
- <Pressable style={styles.backdrop} onPress={onClose}>
42
- <Pressable style={styles.card} onPress={() => {}}>
43
- <Text style={styles.title}>{title}</Text>
44
- <ScrollView style={styles.list}>
45
- {options.map(opt => {
46
- const isSelected = opt.value === selected;
47
- return (
48
- <Pressable
49
- key={String(opt.value)}
50
- style={styles.row}
51
- onPress={() => {
52
- onSelect(opt.value);
53
- onClose();
54
- }}>
55
- <Text style={styles.radio}>{isSelected ? '◉' : '◯'}</Text>
56
- <Text style={styles.label}>{opt.label}</Text>
57
- </Pressable>
58
- );
59
- })}
60
- </ScrollView>
61
- <View style={styles.footer}>
62
- {infoMessage ? (
63
- <Pressable
64
- style={styles.footerBtn}
65
- onPress={() => Alert.alert(title, infoMessage)}>
66
- <Text style={styles.footerText}>INFO</Text>
67
- </Pressable>
68
- ) : null}
69
- <Pressable style={styles.footerBtn} onPress={onClose}>
70
- <Text style={styles.footerText}>CANCEL</Text>
71
- </Pressable>
72
- </View>
73
- </Pressable>
74
- </Pressable>
75
- </Modal>
76
- );
77
- }
78
-
79
- const styles = StyleSheet.create({
80
- backdrop: {
81
- flex: 1,
82
- backgroundColor: 'rgba(0,0,0,0.4)',
83
- justifyContent: 'center',
84
- alignItems: 'center',
85
- padding: 24,
86
- },
87
- card: {
88
- width: '100%',
89
- maxWidth: 360,
90
- maxHeight: '80%',
91
- backgroundColor: colors.background,
92
- borderRadius: 6,
93
- paddingVertical: 16,
94
- elevation: 12,
95
- },
96
- title: {
97
- fontSize: 18,
98
- fontWeight: '600',
99
- color: colors.text,
100
- paddingHorizontal: 20,
101
- paddingBottom: 12,
102
- },
103
- list: {flexGrow: 0},
104
- row: {
105
- flexDirection: 'row',
106
- alignItems: 'center',
107
- paddingVertical: 12,
108
- paddingHorizontal: 20,
109
- },
110
- radio: {fontSize: 18, color: colors.primary, marginRight: 16},
111
- label: {fontSize: 16, color: colors.text},
112
- footer: {
113
- flexDirection: 'row',
114
- justifyContent: 'flex-end',
115
- paddingHorizontal: 12,
116
- paddingTop: 8,
117
- },
118
- footerBtn: {paddingVertical: 8, paddingHorizontal: 12},
119
- footerText: {color: colors.primary, fontWeight: '600', fontSize: 14},
120
- });
@@ -1,195 +0,0 @@
1
- import React from 'react';
2
- import {
3
- ScrollView,
4
- StyleSheet,
5
- Text,
6
- TouchableOpacity,
7
- View,
8
- } from 'react-native';
9
- import type {SerialPort} from 'react-native-web-serial-api';
10
- import {AppBar} from '../components/AppBar';
11
- import {
12
- type Choice,
13
- SingleChoiceDialog,
14
- } from '../components/SingleChoiceDialog';
15
- import {
16
- BAUD_RATES,
17
- type ConnectionSettings,
18
- DATA_BITS,
19
- DEFAULT_SETTINGS,
20
- FLOW_CONTROL_LABELS,
21
- FLOW_CONTROLS,
22
- PARITIES,
23
- STOP_BITS,
24
- } from '../settings';
25
- import {colors} from '../theme';
26
-
27
- type Props = {
28
- port: SerialPort;
29
- initial?: ConnectionSettings;
30
- onBack: () => void;
31
- onConnect: (settings: ConnectionSettings) => void;
32
- };
33
-
34
- function hex4(n: number | undefined): string {
35
- return (n ?? 0).toString(16).toUpperCase().padStart(4, '0');
36
- }
37
-
38
- export function ConnectScreen({port, initial, onBack, onConnect}: Props) {
39
- const [settings, setSettings] = React.useState<ConnectionSettings>(
40
- initial ?? DEFAULT_SETTINGS,
41
- );
42
- // Which dropdown's dialog is open (null = none).
43
- const [open, setOpen] = React.useState<keyof ConnectionSettings | null>(null);
44
-
45
- const info = port.getInfo();
46
-
47
- const set = <K extends keyof ConnectionSettings>(
48
- key: K,
49
- value: ConnectionSettings[K],
50
- ) => setSettings(s => ({...s, [key]: value}));
51
-
52
- return (
53
- <View style={styles.container}>
54
- <AppBar title="Connection settings" onBack={onBack} />
55
-
56
- <ScrollView contentContainerStyle={styles.form}>
57
- <Text style={styles.device}>
58
- {`Vendor ${hex4(info.usbVendorId)} · Product ${hex4(
59
- info.usbProductId,
60
- )}`}
61
- </Text>
62
-
63
- <Dropdown
64
- label="Baud rate"
65
- value={String(settings.baudRate)}
66
- onPress={() => setOpen('baudRate')}
67
- />
68
- <Dropdown
69
- label="Data bits"
70
- value={String(settings.dataBits)}
71
- onPress={() => setOpen('dataBits')}
72
- />
73
- <Dropdown
74
- label="Stop bits"
75
- value={String(settings.stopBits)}
76
- onPress={() => setOpen('stopBits')}
77
- />
78
- <Dropdown
79
- label="Parity"
80
- value={settings.parity}
81
- onPress={() => setOpen('parity')}
82
- />
83
- <Dropdown
84
- label="Flow control"
85
- value={FLOW_CONTROL_LABELS[settings.flowControl]}
86
- onPress={() => setOpen('flowControl')}
87
- />
88
-
89
- <TouchableOpacity
90
- style={styles.connectBtn}
91
- onPress={() => onConnect(settings)}>
92
- <Text style={styles.connectText}>Connect</Text>
93
- </TouchableOpacity>
94
- </ScrollView>
95
-
96
- <SingleChoiceDialog
97
- visible={open === 'baudRate'}
98
- title="Baud rate"
99
- options={BAUD_RATES.map(b => ({label: String(b), value: b}))}
100
- selected={settings.baudRate}
101
- onSelect={v => set('baudRate', v)}
102
- onClose={() => setOpen(null)}
103
- />
104
- <SingleChoiceDialog
105
- visible={open === 'dataBits'}
106
- title="Data bits"
107
- options={DATA_BITS.map(b => ({label: String(b), value: b}))}
108
- selected={settings.dataBits}
109
- onSelect={v => set('dataBits', v as ConnectionSettings['dataBits'])}
110
- onClose={() => setOpen(null)}
111
- />
112
- <SingleChoiceDialog
113
- visible={open === 'stopBits'}
114
- title="Stop bits"
115
- options={STOP_BITS.map(b => ({label: String(b), value: b}))}
116
- selected={settings.stopBits}
117
- onSelect={v => set('stopBits', v as ConnectionSettings['stopBits'])}
118
- onClose={() => setOpen(null)}
119
- />
120
- <SingleChoiceDialog
121
- visible={open === 'parity'}
122
- title="Parity"
123
- options={PARITIES.map(p => ({label: p, value: p})) as Choice<string>[]}
124
- selected={settings.parity}
125
- onSelect={v => set('parity', v as ConnectionSettings['parity'])}
126
- onClose={() => setOpen(null)}
127
- />
128
- <SingleChoiceDialog
129
- visible={open === 'flowControl'}
130
- title="Flow control"
131
- options={
132
- FLOW_CONTROLS.map(f => ({
133
- label: FLOW_CONTROL_LABELS[f],
134
- value: f,
135
- })) as Choice<string>[]
136
- }
137
- selected={settings.flowControl}
138
- onSelect={v =>
139
- set('flowControl', v as ConnectionSettings['flowControl'])
140
- }
141
- onClose={() => setOpen(null)}
142
- />
143
- </View>
144
- );
145
- }
146
-
147
- function Dropdown({
148
- label,
149
- value,
150
- onPress,
151
- }: {
152
- label: string;
153
- value: string;
154
- onPress: () => void;
155
- }) {
156
- return (
157
- <TouchableOpacity style={styles.row} onPress={onPress}>
158
- <Text style={styles.rowLabel}>{label}</Text>
159
- <View style={styles.rowValueWrap}>
160
- <Text style={styles.rowValue}>{value}</Text>
161
- <Text style={styles.caret}>▾</Text>
162
- </View>
163
- </TouchableOpacity>
164
- );
165
- }
166
-
167
- const styles = StyleSheet.create({
168
- container: {flex: 1, backgroundColor: colors.background},
169
- form: {padding: 16},
170
- device: {
171
- fontSize: 13,
172
- color: colors.textSecondary,
173
- marginBottom: 16,
174
- },
175
- row: {
176
- flexDirection: 'row',
177
- alignItems: 'center',
178
- justifyContent: 'space-between',
179
- paddingVertical: 14,
180
- borderBottomWidth: 1,
181
- borderBottomColor: colors.divider,
182
- },
183
- rowLabel: {fontSize: 16, color: colors.text},
184
- rowValueWrap: {flexDirection: 'row', alignItems: 'center'},
185
- rowValue: {fontSize: 16, color: colors.primary, fontWeight: '600'},
186
- caret: {fontSize: 14, color: colors.primary, marginLeft: 8},
187
- connectBtn: {
188
- marginTop: 28,
189
- backgroundColor: colors.primary,
190
- borderRadius: 6,
191
- paddingVertical: 14,
192
- alignItems: 'center',
193
- },
194
- connectText: {color: colors.onPrimary, fontSize: 16, fontWeight: '600'},
195
- });