react-native-vconsole 0.3.1 → 0.4.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/src/VConsole.tsx CHANGED
@@ -4,10 +4,12 @@ import {
4
4
  Clipboard,
5
5
  Dimensions,
6
6
  FlatList,
7
+ Keyboard,
7
8
  NativeModules,
8
9
  PanResponder,
9
10
  Platform,
10
11
  Pressable,
12
+ TextInput,
11
13
  StatusBar,
12
14
  StyleSheet,
13
15
  Text,
@@ -42,7 +44,7 @@ import type {
42
44
  const BUTTON_WIDTH = 88;
43
45
  const BUTTON_HEIGHT = 36;
44
46
  const PANEL_HEIGHT_RATIO = 7 / 9;
45
- const EMPTY_FILTER: string[] = [];
47
+ const EMPTY_EXCLUDE: VConsoleExclude = {};
46
48
  const LOG_SUB_TABS: LogFilterTab[] = ['All', 'log', 'info', 'warn', 'error'];
47
49
  const ROOT_TABS: VConsoleTab[] = ['Log', 'Network', 'System', 'App'];
48
50
 
@@ -62,7 +64,12 @@ type NativeModuleShape = {
62
64
 
63
65
  export type VConsoleProps = {
64
66
  enable?: boolean;
65
- exclude?: string[];
67
+ exclude?: VConsoleExclude;
68
+ };
69
+
70
+ type VConsoleExclude = {
71
+ domains?: string[];
72
+ ip?: boolean;
66
73
  };
67
74
 
68
75
  function clamp(value: number, min: number, max: number): number {
@@ -102,6 +109,18 @@ function formatMemorySize(bytes: unknown): string {
102
109
  return `${mb.toFixed(2)} MB`;
103
110
  }
104
111
 
112
+ function formatLogTime(timestamp: number): string {
113
+ const date = new Date(timestamp);
114
+ if (Number.isNaN(date.getTime())) {
115
+ return '--:--:--.---';
116
+ }
117
+ const hh = String(date.getHours()).padStart(2, '0');
118
+ const mm = String(date.getMinutes()).padStart(2, '0');
119
+ const ss = String(date.getSeconds()).padStart(2, '0');
120
+ const ms = String(date.getMilliseconds()).padStart(3, '0');
121
+ return `${hh}:${mm}:${ss}.${ms}`;
122
+ }
123
+
105
124
  function prettyText(value: unknown): string {
106
125
  if (value === undefined) {
107
126
  return '';
@@ -116,19 +135,103 @@ function prettyText(value: unknown): string {
116
135
  }
117
136
  }
118
137
 
138
+ function isNetworkErrorEntry(item: NetworkEntry): boolean {
139
+ return item.isError === true;
140
+ }
141
+
119
142
  function buildNetworkCopyText(item: NetworkEntry): string {
120
143
  const status = item.status ?? '-';
121
144
  const duration =
122
145
  typeof item.durationMs === 'number' ? `${item.durationMs}ms` : '-';
146
+ const isError = isNetworkErrorEntry(item);
123
147
 
124
- return [
148
+ const segments = [
125
149
  `${item.method} ${item.url}`,
126
150
  `status ${status} duration ${duration}`,
127
151
  `request headers\n${prettyText(item.requestHeaders)}`,
128
152
  `request body\n${prettyText(item.requestBody)}`,
129
- `response headers\n${prettyText(item.responseHeaders)}`,
130
- `response data\n${prettyText(item.responseData)}`,
131
- ].join('\n');
153
+ ];
154
+
155
+ if (isError) {
156
+ segments.push(
157
+ `error reason\n${item.errorReason ?? 'Network request failed'}`
158
+ );
159
+ } else {
160
+ segments.push(`response headers\n${prettyText(item.responseHeaders)}`);
161
+ segments.push(`response data\n${prettyText(item.responseData)}`);
162
+ }
163
+
164
+ return segments.join('\n');
165
+ }
166
+
167
+ const FORBIDDEN_RETRY_HEADERS = new Set([
168
+ 'host',
169
+ 'content-length',
170
+ 'accept-encoding',
171
+ 'connection',
172
+ 'origin',
173
+ 'referer',
174
+ ]);
175
+
176
+ function normalizeRetryUrl(rawUrl: string): string {
177
+ if (!rawUrl) {
178
+ return '';
179
+ }
180
+ if (/^\/\//.test(rawUrl)) {
181
+ return `https:${rawUrl}`;
182
+ }
183
+ return rawUrl;
184
+ }
185
+
186
+ function buildRetryHeaders(
187
+ headers: Record<string, string> | undefined
188
+ ): Record<string, string> {
189
+ const nextHeaders: Record<string, string> = {};
190
+ if (!headers) {
191
+ return nextHeaders;
192
+ }
193
+
194
+ Object.entries(headers).forEach(([key, value]) => {
195
+ if (!FORBIDDEN_RETRY_HEADERS.has(key.toLowerCase())) {
196
+ nextHeaders[key] = value;
197
+ }
198
+ });
199
+ return nextHeaders;
200
+ }
201
+
202
+ function buildRetryBody(payload: unknown, method: string): unknown | undefined {
203
+ if (method === 'GET' || method === 'HEAD' || payload == null) {
204
+ return undefined;
205
+ }
206
+ if (typeof payload === 'string') {
207
+ return payload;
208
+ }
209
+ if (typeof payload === 'number' || typeof payload === 'boolean') {
210
+ return String(payload);
211
+ }
212
+ if (typeof FormData !== 'undefined' && payload instanceof FormData) {
213
+ return payload;
214
+ }
215
+ if (
216
+ typeof URLSearchParams !== 'undefined' &&
217
+ payload instanceof URLSearchParams
218
+ ) {
219
+ return payload;
220
+ }
221
+ if (typeof Blob !== 'undefined' && payload instanceof Blob) {
222
+ return payload;
223
+ }
224
+ if (typeof ArrayBuffer !== 'undefined' && payload instanceof ArrayBuffer) {
225
+ return payload;
226
+ }
227
+ if (ArrayBuffer.isView(payload)) {
228
+ return payload;
229
+ }
230
+ try {
231
+ return JSON.stringify(payload);
232
+ } catch {
233
+ return String(payload);
234
+ }
132
235
  }
133
236
 
134
237
  type ObjectTreeProps = {
@@ -225,7 +328,7 @@ function useFlatListRefs() {
225
328
 
226
329
  export function VConsole({
227
330
  enable = true,
228
- exclude = EMPTY_FILTER,
331
+ exclude = EMPTY_EXCLUDE,
229
332
  }: VConsoleProps) {
230
333
  const nativeModule = NativeModules.Vconsole as NativeModuleShape | undefined;
231
334
  const { width, height } = Dimensions.get('window');
@@ -258,6 +361,11 @@ export function VConsole({
258
361
  const [logSubTab, setLogSubTab] = useState<LogFilterTab>('All');
259
362
  const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
260
363
  const [networkEntries, setNetworkEntries] = useState<NetworkEntry[]>([]);
364
+ const [logFilterInput, setLogFilterInput] = useState('');
365
+ const [networkFilterInput, setNetworkFilterInput] = useState('');
366
+ const [debouncedLogFilter, setDebouncedLogFilter] = useState('');
367
+ const [debouncedNetworkFilter, setDebouncedNetworkFilter] = useState('');
368
+ const [keyboardHeight, setKeyboardHeight] = useState(0);
261
369
  const [expandedMap, setExpandedMap] = useState<ExpandedMap>({});
262
370
  const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(null);
263
371
  const [appInfo, setAppInfo] = useState<AppInfo | null>(null);
@@ -266,10 +374,14 @@ export function VConsole({
266
374
  const panelTranslateY = useRef(new Animated.Value(panelHeight)).current;
267
375
  const logListRefs = useFlatListRefs();
268
376
  const networkListRef = useRef<FlatList<NetworkEntry>>(null);
269
- const normalizedExclude = useMemo(
270
- () => exclude.map((item) => item.trim().toLowerCase()).filter(Boolean),
271
- [exclude]
377
+ const normalizedExcludeDomains = useMemo(
378
+ () =>
379
+ (exclude.domains ?? [])
380
+ .map((item) => item.trim().toLowerCase())
381
+ .filter(Boolean),
382
+ [exclude.domains]
272
383
  );
384
+ const shouldExcludeIp = exclude.ip === true;
273
385
 
274
386
  useEffect(() => {
275
387
  if (!enable) {
@@ -278,7 +390,10 @@ export function VConsole({
278
390
  }
279
391
 
280
392
  installConsoleProxy();
281
- installXhrProxy({ excludeHosts: normalizedExclude });
393
+ installXhrProxy({
394
+ excludeHosts: normalizedExcludeDomains,
395
+ excludeIp: shouldExcludeIp,
396
+ });
282
397
 
283
398
  const unsubscribeLog = subscribeLogEntries(setLogEntries);
284
399
  const unsubscribeNetwork = subscribeNetworkEntries(setNetworkEntries);
@@ -291,7 +406,7 @@ export function VConsole({
291
406
  uninstallConsoleProxy();
292
407
  uninstallXhrProxy();
293
408
  };
294
- }, [enable, normalizedExclude]);
409
+ }, [enable, normalizedExcludeDomains, shouldExcludeIp]);
295
410
 
296
411
  useEffect(() => {
297
412
  dragPosition.stopAnimation((value) => {
@@ -317,6 +432,39 @@ export function VConsole({
317
432
  }
318
433
  }, [activeTab, appInfo, nativeModule, panelVisible, systemInfo]);
319
434
 
435
+ useEffect(() => {
436
+ const showEvent =
437
+ Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
438
+ const hideEvent =
439
+ Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
440
+
441
+ const showSubscription = Keyboard.addListener(showEvent, (event) => {
442
+ setKeyboardHeight(event.endCoordinates?.height ?? 0);
443
+ });
444
+ const hideSubscription = Keyboard.addListener(hideEvent, () => {
445
+ setKeyboardHeight(0);
446
+ });
447
+
448
+ return () => {
449
+ showSubscription.remove();
450
+ hideSubscription.remove();
451
+ };
452
+ }, []);
453
+
454
+ useEffect(() => {
455
+ const timer = setTimeout(() => {
456
+ setDebouncedLogFilter(logFilterInput);
457
+ }, 1000);
458
+ return () => clearTimeout(timer);
459
+ }, [logFilterInput]);
460
+
461
+ useEffect(() => {
462
+ const timer = setTimeout(() => {
463
+ setDebouncedNetworkFilter(networkFilterInput);
464
+ }, 1000);
465
+ return () => clearTimeout(timer);
466
+ }, [networkFilterInput]);
467
+
320
468
  const panResponder = useMemo(
321
469
  () =>
322
470
  PanResponder.create({
@@ -370,15 +518,36 @@ export function VConsole({
370
518
  });
371
519
  };
372
520
 
521
+ const normalizedLogFilter = debouncedLogFilter.trim().toLowerCase();
522
+ const normalizedNetworkFilter = debouncedNetworkFilter.trim().toLowerCase();
523
+
524
+ const filteredLogEntries = useMemo(() => {
525
+ if (!normalizedLogFilter) {
526
+ return logEntries;
527
+ }
528
+ return logEntries.filter((item) =>
529
+ item.text.toLowerCase().includes(normalizedLogFilter)
530
+ );
531
+ }, [logEntries, normalizedLogFilter]);
532
+
533
+ const filteredNetworkEntries = useMemo(() => {
534
+ if (!normalizedNetworkFilter) {
535
+ return networkEntries;
536
+ }
537
+ return networkEntries.filter((item) =>
538
+ item.url.toLowerCase().includes(normalizedNetworkFilter)
539
+ );
540
+ }, [networkEntries, normalizedNetworkFilter]);
541
+
373
542
  const logDataByTab = useMemo(
374
543
  () => ({
375
- All: logEntries,
376
- log: logEntries.filter((item) => item.level === 'log'),
377
- info: logEntries.filter((item) => item.level === 'info'),
378
- warn: logEntries.filter((item) => item.level === 'warn'),
379
- error: logEntries.filter((item) => item.level === 'error'),
544
+ All: filteredLogEntries,
545
+ log: filteredLogEntries.filter((item) => item.level === 'log'),
546
+ info: filteredLogEntries.filter((item) => item.level === 'info'),
547
+ warn: filteredLogEntries.filter((item) => item.level === 'warn'),
548
+ error: filteredLogEntries.filter((item) => item.level === 'error'),
380
549
  }),
381
- [logEntries]
550
+ [filteredLogEntries]
382
551
  );
383
552
 
384
553
  const onToggleNode = (key: string) => {
@@ -407,6 +576,39 @@ export function VConsole({
407
576
  networkListRef.current?.scrollToEnd({ animated: true });
408
577
  };
409
578
 
579
+ const retryNetworkRequest = (item: NetworkEntry) => {
580
+ const method = (item.method || 'GET').toUpperCase();
581
+ const url = normalizeRetryUrl(item.url);
582
+ if (!url) {
583
+ console.error('[vConsole] Retry failed: empty request URL');
584
+ return;
585
+ }
586
+
587
+ const headers = buildRetryHeaders(item.requestHeaders);
588
+ const body = buildRetryBody(item.requestBody, method);
589
+ const hasContentType = Object.keys(headers).some(
590
+ (key) => key.toLowerCase() === 'content-type'
591
+ );
592
+
593
+ if (
594
+ body &&
595
+ typeof body === 'string' &&
596
+ typeof item.requestBody === 'object' &&
597
+ item.requestBody !== null &&
598
+ !hasContentType
599
+ ) {
600
+ headers['Content-Type'] = 'application/json';
601
+ }
602
+
603
+ fetch(url, {
604
+ method,
605
+ headers,
606
+ body: body as never,
607
+ }).catch((error: unknown) => {
608
+ console.error('[vConsole] Retry request failed', error);
609
+ });
610
+ };
611
+
410
612
  const renderRootTab = (tab: VConsoleTab) => (
411
613
  <Pressable
412
614
  key={tab}
@@ -445,6 +647,10 @@ export function VConsole({
445
647
  <View style={styles.listItemMain}>
446
648
  <Text style={[styles.logLevelText, { color: levelTheme.color }]}>
447
649
  [{item.level.toUpperCase()}]
650
+ <Text style={styles.logTimeText}>
651
+ {' '}
652
+ {formatLogTime(item.timestamp)}
653
+ </Text>
448
654
  </Text>
449
655
  <View style={styles.logPayload}>
450
656
  {item.args.map((arg, index) => (
@@ -471,18 +677,31 @@ export function VConsole({
471
677
  const renderNetworkItem: FlatListProps<NetworkEntry>['renderItem'] = ({
472
678
  item,
473
679
  }) => {
680
+ const isError = isNetworkErrorEntry(item);
681
+ const startedTime = formatLogTime(item.startedAt);
682
+ const finishedTime =
683
+ typeof item.finishedAt === 'number'
684
+ ? formatLogTime(item.finishedAt)
685
+ : '-';
474
686
  return (
475
- <View style={styles.listItem}>
687
+ <View
688
+ style={[
689
+ styles.listItem,
690
+ isError ? { backgroundColor: LOG_THEME.error.backgroundColor } : null,
691
+ ]}
692
+ >
476
693
  <View style={styles.listItemMain}>
477
694
  <Text style={styles.networkTitle}>
478
695
  {item.method} {item.url}
479
696
  </Text>
480
697
  <Text style={styles.networkLabel}>
481
- Status: {item.status ?? '-'}
698
+ Time: {startedTime}
699
+ {finishedTime !== '-' ? ` ~ ${finishedTime}` : ''}
482
700
  {' '}
483
701
  Duration:{' '}
484
702
  {typeof item.durationMs === 'number' ? `${item.durationMs}ms` : '-'}
485
703
  </Text>
704
+ <Text style={styles.networkLabel}>Status: {item.status ?? '-'}</Text>
486
705
  <View style={styles.networkBlock}>
487
706
  <Text style={styles.networkLabel}>Request Headers</Text>
488
707
  <ObjectTree
@@ -501,26 +720,39 @@ export function VConsole({
501
720
  onToggle={onToggleNode}
502
721
  />
503
722
  </View>
504
- <View style={styles.networkBlock}>
505
- <Text style={styles.networkLabel}>Response Headers</Text>
506
- <ObjectTree
507
- value={item.responseHeaders}
508
- nodeKey={`${item.id}.responseHeaders`}
509
- expandedMap={expandedMap}
510
- onToggle={onToggleNode}
511
- />
512
- </View>
513
- <View style={styles.networkBlock}>
514
- <Text style={styles.networkLabel}>Response Data</Text>
515
- <ScrollView horizontal={true}>
516
- <ObjectTree
517
- value={item.responseData ?? ''}
518
- nodeKey={`${item.id}.responseData`}
519
- expandedMap={expandedMap}
520
- onToggle={onToggleNode}
521
- />
522
- </ScrollView>
523
- </View>
723
+ {isError ? (
724
+ <View style={styles.networkBlock}>
725
+ <Text style={[styles.networkLabel, styles.networkErrorLabel]}>
726
+ Error Reason
727
+ </Text>
728
+ <Text style={styles.networkErrorText}>
729
+ {item.errorReason ?? 'Network request failed'}
730
+ </Text>
731
+ </View>
732
+ ) : (
733
+ <>
734
+ <View style={styles.networkBlock}>
735
+ <Text style={styles.networkLabel}>Response Headers</Text>
736
+ <ObjectTree
737
+ value={item.responseHeaders}
738
+ nodeKey={`${item.id}.responseHeaders`}
739
+ expandedMap={expandedMap}
740
+ onToggle={onToggleNode}
741
+ />
742
+ </View>
743
+ <View style={styles.networkBlock}>
744
+ <Text style={styles.networkLabel}>Response Data</Text>
745
+ <ScrollView horizontal={true}>
746
+ <ObjectTree
747
+ value={item.responseData ?? ''}
748
+ nodeKey={`${item.id}.responseData`}
749
+ expandedMap={expandedMap}
750
+ onToggle={onToggleNode}
751
+ />
752
+ </ScrollView>
753
+ </View>
754
+ </>
755
+ )}
524
756
  </View>
525
757
  <Pressable
526
758
  style={styles.copyButton}
@@ -530,12 +762,18 @@ export function VConsole({
530
762
  >
531
763
  <Text style={styles.copyButtonText}>Copy</Text>
532
764
  </Pressable>
765
+ <Pressable
766
+ style={styles.retryButton}
767
+ onPress={() => retryNetworkRequest(item)}
768
+ >
769
+ <Text style={styles.retryButtonText}>Retry</Text>
770
+ </Pressable>
533
771
  </View>
534
772
  );
535
773
  };
536
774
 
537
- const renderLogPanel = () => (
538
- <View style={styles.contentArea}>
775
+ const renderLogPanel = (visible: boolean) => (
776
+ <View style={[styles.contentArea, visible ? {} : styles.hidden]}>
539
777
  <View style={styles.subTabRow}>
540
778
  {LOG_SUB_TABS.map((tab) => (
541
779
  <Pressable
@@ -573,6 +811,16 @@ export function VConsole({
573
811
  </View>
574
812
  ))}
575
813
  </View>
814
+ <View style={styles.filterInputWrap}>
815
+ <TextInput
816
+ style={styles.filterInput}
817
+ textAlignVertical="center"
818
+ value={logFilterInput}
819
+ onChangeText={setLogFilterInput}
820
+ placeholder="filter..."
821
+ placeholderTextColor="#999999"
822
+ />
823
+ </View>
576
824
  <View style={styles.actionsRow}>
577
825
  {renderActionButton('Clear', () => {
578
826
  clearLogEntries();
@@ -585,15 +833,24 @@ export function VConsole({
585
833
  </View>
586
834
  );
587
835
 
588
- const renderNetworkPanel = () => (
589
- <View style={styles.contentArea}>
836
+ const renderNetworkPanel = (visible: boolean) => (
837
+ <View style={[styles.contentArea, visible ? {} : styles.hidden]}>
590
838
  <FlatList
591
839
  ref={networkListRef}
592
- data={networkEntries}
840
+ data={filteredNetworkEntries}
593
841
  keyExtractor={(item) => `network-${item.id}`}
594
842
  renderItem={renderNetworkItem}
595
843
  ItemSeparatorComponent={ListSeparator}
596
844
  />
845
+ <View style={styles.filterInputWrap}>
846
+ <TextInput
847
+ style={styles.filterInput}
848
+ value={networkFilterInput}
849
+ onChangeText={setNetworkFilterInput}
850
+ placeholder="filter"
851
+ placeholderTextColor="#999999"
852
+ />
853
+ </View>
597
854
  <View style={styles.actionsRow}>
598
855
  {renderActionButton('Clear', () => {
599
856
  clearNetworkEntries();
@@ -606,8 +863,8 @@ export function VConsole({
606
863
  </View>
607
864
  );
608
865
 
609
- const renderSystemPanel = () => (
610
- <View style={styles.contentArea}>
866
+ const renderSystemPanel = (visible: boolean) => (
867
+ <View style={[styles.contentArea, visible ? {} : styles.hidden]}>
611
868
  <View style={styles.infoCard}>
612
869
  <Text style={styles.infoText}>
613
870
  Brand: {systemInfo?.manufacturer ?? '-'}
@@ -642,8 +899,8 @@ export function VConsole({
642
899
  </View>
643
900
  );
644
901
 
645
- const renderAppPanel = () => (
646
- <View style={styles.contentArea}>
902
+ const renderAppPanel = (visible: boolean) => (
903
+ <View style={[styles.contentArea, visible ? {} : styles.hidden]}>
647
904
  <View style={styles.infoCard}>
648
905
  <Text style={styles.infoText}>
649
906
  App Version: {appInfo?.appVersion ?? '-'}
@@ -686,15 +943,16 @@ export function VConsole({
686
943
  styles.panel,
687
944
  {
688
945
  height: panelHeight,
946
+ marginBottom: keyboardHeight,
689
947
  transform: [{ translateY: panelTranslateY }],
690
948
  },
691
949
  ]}
692
950
  >
693
951
  <View style={styles.topTabRow}>{ROOT_TABS.map(renderRootTab)}</View>
694
- {activeTab === 'Log' ? renderLogPanel() : null}
695
- {activeTab === 'Network' ? renderNetworkPanel() : null}
696
- {activeTab === 'System' ? renderSystemPanel() : null}
697
- {activeTab === 'App' ? renderAppPanel() : null}
952
+ {renderLogPanel(activeTab === 'Log')}
953
+ {renderNetworkPanel(activeTab === 'Network')}
954
+ {renderSystemPanel(activeTab === 'System')}
955
+ {renderAppPanel(activeTab === 'App')}
698
956
  </Animated.View>
699
957
  </View>
700
958
  ) : null}
@@ -813,6 +1071,11 @@ const styles = StyleSheet.create({
813
1071
  fontWeight: '700',
814
1072
  marginBottom: 4,
815
1073
  },
1074
+ logTimeText: {
1075
+ fontSize: 11,
1076
+ fontWeight: '400',
1077
+ color: '#888888',
1078
+ },
816
1079
  logPayload: {
817
1080
  flex: 1,
818
1081
  },
@@ -830,6 +1093,20 @@ const styles = StyleSheet.create({
830
1093
  fontSize: 11,
831
1094
  color: '#333333',
832
1095
  },
1096
+ retryButton: {
1097
+ position: 'absolute',
1098
+ right: 8,
1099
+ top: 40,
1100
+ borderWidth: StyleSheet.hairlineWidth,
1101
+ borderColor: '#D0D0D0',
1102
+ borderRadius: 6,
1103
+ paddingVertical: 4,
1104
+ paddingHorizontal: 8,
1105
+ },
1106
+ retryButtonText: {
1107
+ fontSize: 11,
1108
+ color: '#333333',
1109
+ },
833
1110
  valuePrimitive: {
834
1111
  color: '#222222',
835
1112
  fontSize: 12,
@@ -892,6 +1169,32 @@ const styles = StyleSheet.create({
892
1169
  color: '#444444',
893
1170
  marginBottom: 2,
894
1171
  },
1172
+ networkErrorLabel: {
1173
+ color: LOG_THEME.error.color,
1174
+ fontWeight: '600',
1175
+ },
1176
+ networkErrorText: {
1177
+ color: LOG_THEME.error.color,
1178
+ fontSize: 12,
1179
+ },
1180
+ filterInputWrap: {
1181
+ borderTopWidth: StyleSheet.hairlineWidth,
1182
+ borderTopColor: '#E1E1E1',
1183
+ paddingHorizontal: 8,
1184
+ paddingTop: 8,
1185
+ paddingBottom: 6,
1186
+ },
1187
+ filterInput: {
1188
+ height: 34,
1189
+ borderWidth: StyleSheet.hairlineWidth,
1190
+ borderColor: '#D0D0D0',
1191
+ borderRadius: 8,
1192
+ paddingHorizontal: 10,
1193
+ fontSize: 12,
1194
+ color: '#222222',
1195
+ backgroundColor: '#FFFFFF',
1196
+ paddingVertical: 0,
1197
+ },
895
1198
  actionsRow: {
896
1199
  borderTopWidth: StyleSheet.hairlineWidth,
897
1200
  borderTopColor: '#E1E1E1',