react-native-web-serial-api 0.0.3 → 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 (119) hide show
  1. package/README.md +23 -0
  2. package/TESTING.md +301 -0
  3. package/android/build.gradle +2 -2
  4. package/lib/commonjs/UsbSerial.js +58 -26
  5. package/lib/commonjs/UsbSerial.js.map +1 -1
  6. package/lib/commonjs/WebSerial.js +169 -57
  7. package/lib/commonjs/WebSerial.js.map +1 -1
  8. package/lib/commonjs/index.js +13 -1
  9. package/lib/commonjs/index.js.map +1 -1
  10. package/lib/commonjs/lib/dom-exception.js +176 -0
  11. package/lib/commonjs/lib/dom-exception.js.map +1 -0
  12. package/lib/commonjs/lib/event-target.js +138 -0
  13. package/lib/commonjs/lib/event-target.js.map +1 -0
  14. package/lib/commonjs/lib/promise.js +23 -0
  15. package/lib/commonjs/lib/promise.js.map +1 -0
  16. package/lib/commonjs/testing/index.js +70 -0
  17. package/lib/commonjs/testing/index.js.map +1 -0
  18. package/lib/commonjs/testing/install.js +54 -0
  19. package/lib/commonjs/testing/install.js.map +1 -0
  20. package/lib/commonjs/testing/serial-device.js +164 -0
  21. package/lib/commonjs/testing/serial-device.js.map +1 -0
  22. package/lib/commonjs/testing/virtual-serial.js +615 -0
  23. package/lib/commonjs/testing/virtual-serial.js.map +1 -0
  24. package/lib/commonjs/transport.js +61 -0
  25. package/lib/commonjs/transport.js.map +1 -0
  26. package/lib/typescript/src/UsbSerial.d.ts +24 -67
  27. package/lib/typescript/src/UsbSerial.d.ts.map +1 -1
  28. package/lib/typescript/src/WebSerial.d.ts +11 -2
  29. package/lib/typescript/src/WebSerial.d.ts.map +1 -1
  30. package/lib/typescript/src/index.d.ts +2 -0
  31. package/lib/typescript/src/index.d.ts.map +1 -1
  32. package/lib/typescript/src/lib/dom-exception.d.ts +100 -0
  33. package/lib/typescript/src/lib/dom-exception.d.ts.map +1 -0
  34. package/lib/typescript/src/lib/event-target.d.ts +53 -0
  35. package/lib/typescript/src/lib/event-target.d.ts.map +1 -0
  36. package/lib/typescript/src/lib/promise.d.ts +11 -0
  37. package/lib/typescript/src/lib/promise.d.ts.map +1 -0
  38. package/lib/typescript/src/testing/index.d.ts +23 -0
  39. package/lib/typescript/src/testing/index.d.ts.map +1 -0
  40. package/lib/typescript/src/testing/install.d.ts +25 -0
  41. package/lib/typescript/src/testing/install.d.ts.map +1 -0
  42. package/lib/typescript/src/testing/serial-device.d.ts +127 -0
  43. package/lib/typescript/src/testing/serial-device.d.ts.map +1 -0
  44. package/lib/typescript/src/testing/virtual-serial.d.ts +205 -0
  45. package/lib/typescript/src/testing/virtual-serial.d.ts.map +1 -0
  46. package/lib/typescript/src/transport.d.ts +131 -0
  47. package/lib/typescript/src/transport.d.ts.map +1 -0
  48. package/package.json +38 -2
  49. package/src/UsbSerial.ts +65 -90
  50. package/src/WebSerial.ts +227 -88
  51. package/src/index.ts +2 -7
  52. package/src/lib/dom-exception.ts +129 -60
  53. package/src/lib/event-target.ts +46 -21
  54. package/src/lib/promise.ts +7 -7
  55. package/src/testing/index.ts +42 -0
  56. package/src/testing/install.ts +65 -0
  57. package/src/testing/serial-device.ts +193 -0
  58. package/src/testing/virtual-serial.ts +801 -0
  59. package/src/transport.ts +200 -0
  60. package/babel.config.js +0 -3
  61. package/biome.json +0 -35
  62. package/example/.watchmanconfig +0 -1
  63. package/example/App.tsx +0 -71
  64. package/example/__tests__/App.test.tsx +0 -16
  65. package/example/__tests__/connectEvents.test.tsx +0 -81
  66. package/example/__tests__/getPorts.test.tsx +0 -140
  67. package/example/android/app/build.gradle +0 -120
  68. package/example/android/app/debug.keystore +0 -0
  69. package/example/android/app/proguard-rules.pro +0 -10
  70. package/example/android/app/src/debug/AndroidManifest.xml +0 -9
  71. package/example/android/app/src/main/AndroidManifest.xml +0 -38
  72. package/example/android/app/src/main/java/dev/uzlopak/MainActivity.kt +0 -22
  73. package/example/android/app/src/main/java/dev/uzlopak/MainApplication.kt +0 -41
  74. package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +0 -37
  75. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  76. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  77. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  78. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  79. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  80. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  81. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  82. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  83. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  84. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  85. package/example/android/app/src/main/res/values/strings.xml +0 -3
  86. package/example/android/app/src/main/res/values/styles.xml +0 -9
  87. package/example/android/build.gradle +0 -22
  88. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  89. package/example/android/gradle/wrapper/gradle-wrapper.properties +0 -7
  90. package/example/android/gradle.properties +0 -47
  91. package/example/android/gradlew +0 -252
  92. package/example/android/gradlew.bat +0 -94
  93. package/example/android/settings.gradle +0 -6
  94. package/example/app.json +0 -4
  95. package/example/babel.config.js +0 -21
  96. package/example/biome.json +0 -47
  97. package/example/deploy.sh +0 -11
  98. package/example/index.html +0 -26
  99. package/example/index.js +0 -9
  100. package/example/index.web.js +0 -8
  101. package/example/jest.config.js +0 -12
  102. package/example/metro.config.js +0 -58
  103. package/example/package-lock.json +0 -14510
  104. package/example/package.json +0 -48
  105. package/example/react-native.config.js +0 -17
  106. package/example/src/components/AppBar.tsx +0 -73
  107. package/example/src/components/Menu.tsx +0 -90
  108. package/example/src/components/SingleChoiceDialog.tsx +0 -120
  109. package/example/src/screens/ConnectScreen.tsx +0 -195
  110. package/example/src/screens/DevicesScreen.tsx +0 -252
  111. package/example/src/screens/TerminalScreen.tsx +0 -572
  112. package/example/src/settings.ts +0 -43
  113. package/example/src/theme.ts +0 -19
  114. package/example/src/util/TextUtil.ts +0 -129
  115. package/example/tsconfig.json +0 -10
  116. package/example/vite.config.mjs +0 -55
  117. package/scripts/deploy-release.sh +0 -127
  118. package/tsconfig.build.json +0 -7
  119. 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
- });