rn-backstage 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -2
- package/lib/commonjs/Backstage.js +6 -0
- package/lib/commonjs/Backstage.js.map +1 -1
- package/lib/commonjs/components/BackstagePanel.js +18 -2
- package/lib/commonjs/components/BackstagePanel.js.map +1 -1
- package/lib/commonjs/components/FlagsTab.js +11 -9
- package/lib/commonjs/components/FlagsTab.js.map +1 -1
- package/lib/commonjs/components/FloatingPill.js +82 -22
- package/lib/commonjs/components/FloatingPill.js.map +1 -1
- package/lib/commonjs/components/InfoTab.js +5 -1
- package/lib/commonjs/components/InfoTab.js.map +1 -1
- package/lib/commonjs/components/LogItem.js +6 -0
- package/lib/commonjs/components/LogItem.js.map +1 -1
- package/lib/commonjs/components/LogsTab.js +2 -0
- package/lib/commonjs/components/LogsTab.js.map +1 -1
- package/lib/commonjs/components/NetworkTab.js +2 -0
- package/lib/commonjs/components/NetworkTab.js.map +1 -1
- package/lib/commonjs/components/StorageTab.js +655 -0
- package/lib/commonjs/components/StorageTab.js.map +1 -0
- package/lib/commonjs/constants.js +72 -6
- package/lib/commonjs/constants.js.map +1 -1
- package/lib/commonjs/index.js +77 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/types.js +1 -0
- package/lib/commonjs/types.js.map +1 -1
- package/lib/module/Backstage.js +6 -0
- package/lib/module/Backstage.js.map +1 -1
- package/lib/module/components/BackstagePanel.js +18 -2
- package/lib/module/components/BackstagePanel.js.map +1 -1
- package/lib/module/components/FlagsTab.js +11 -9
- package/lib/module/components/FlagsTab.js.map +1 -1
- package/lib/module/components/FloatingPill.js +86 -26
- package/lib/module/components/FloatingPill.js.map +1 -1
- package/lib/module/components/InfoTab.js +6 -2
- package/lib/module/components/InfoTab.js.map +1 -1
- package/lib/module/components/LogItem.js +7 -1
- package/lib/module/components/LogItem.js.map +1 -1
- package/lib/module/components/LogsTab.js +2 -0
- package/lib/module/components/LogsTab.js.map +1 -1
- package/lib/module/components/NetworkTab.js +2 -0
- package/lib/module/components/NetworkTab.js.map +1 -1
- package/lib/module/components/StorageTab.js +650 -0
- package/lib/module/components/StorageTab.js.map +1 -0
- package/lib/module/constants.js +72 -6
- package/lib/module/constants.js.map +1 -1
- package/lib/module/index.js +13 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/Backstage.d.ts.map +1 -1
- package/lib/typescript/components/BackstagePanel.d.ts +2 -1
- package/lib/typescript/components/BackstagePanel.d.ts.map +1 -1
- package/lib/typescript/components/FloatingPill.d.ts +2 -0
- package/lib/typescript/components/FloatingPill.d.ts.map +1 -1
- package/lib/typescript/components/InfoTab.d.ts.map +1 -1
- package/lib/typescript/components/LogItem.d.ts.map +1 -1
- package/lib/typescript/components/StorageTab.d.ts +9 -0
- package/lib/typescript/components/StorageTab.d.ts.map +1 -0
- package/lib/typescript/constants.d.ts +59 -4
- package/lib/typescript/constants.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +12 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +16 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +6 -4
- package/src/Backstage.tsx +6 -0
- package/src/components/BackstagePanel.tsx +23 -4
- package/src/components/FlagsTab.tsx +6 -4
- package/src/components/FloatingPill.tsx +122 -28
- package/src/components/InfoTab.tsx +6 -5
- package/src/components/LogItem.tsx +13 -4
- package/src/components/LogsTab.tsx +2 -2
- package/src/components/NetworkTab.tsx +2 -2
- package/src/components/StorageTab.tsx +607 -0
- package/src/constants.ts +80 -4
- package/src/index.ts +15 -0
- package/src/types.ts +22 -0
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import React, { useRef } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import React, { useCallback, useRef, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Animated,
|
|
4
|
+
Dimensions,
|
|
5
|
+
LayoutChangeEvent,
|
|
6
|
+
PanResponder,
|
|
7
|
+
SafeAreaView,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
Text,
|
|
10
|
+
View,
|
|
11
|
+
} from 'react-native'
|
|
12
|
+
import { Metrics, MonospaceFont, TestIDs } from '../constants'
|
|
4
13
|
import { useBackstageTheme } from '../ThemeContext'
|
|
5
14
|
import type { BackstageStyleOverrides } from '../types'
|
|
6
15
|
|
|
@@ -12,6 +21,8 @@ interface FloatingPillProps {
|
|
|
12
21
|
onPress: () => void
|
|
13
22
|
initialX?: number
|
|
14
23
|
initialY?: number
|
|
24
|
+
pillWidth?: number
|
|
25
|
+
pillHeight?: number
|
|
15
26
|
styles?: BackstageStyleOverrides
|
|
16
27
|
}
|
|
17
28
|
|
|
@@ -20,12 +31,32 @@ interface FloatingPillProps {
|
|
|
20
31
|
const PILL_WIDTH = Metrics.pillWidth
|
|
21
32
|
const PILL_HEIGHT = Metrics.pillHeight
|
|
22
33
|
const DRAG_THRESHOLD = 5
|
|
34
|
+
const INSET_PADDING = 8 // extra padding from safe area edges
|
|
23
35
|
|
|
24
|
-
|
|
36
|
+
interface SafeAreaInsets {
|
|
37
|
+
top: number
|
|
38
|
+
bottom: number
|
|
39
|
+
left: number
|
|
40
|
+
right: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function clampPosition(
|
|
44
|
+
x: number,
|
|
45
|
+
y: number,
|
|
46
|
+
insets: SafeAreaInsets,
|
|
47
|
+
pillW: number,
|
|
48
|
+
pillH: number,
|
|
49
|
+
): { x: number; y: number } {
|
|
25
50
|
const { width, height } = Dimensions.get('window')
|
|
26
51
|
return {
|
|
27
|
-
x: Math.max(
|
|
28
|
-
|
|
52
|
+
x: Math.max(
|
|
53
|
+
insets.left + INSET_PADDING,
|
|
54
|
+
Math.min(x, width - pillW - insets.right - INSET_PADDING),
|
|
55
|
+
),
|
|
56
|
+
y: Math.max(
|
|
57
|
+
insets.top + INSET_PADDING,
|
|
58
|
+
Math.min(y, height - pillH - insets.bottom - INSET_PADDING),
|
|
59
|
+
),
|
|
29
60
|
}
|
|
30
61
|
}
|
|
31
62
|
|
|
@@ -37,17 +68,55 @@ export const FloatingPill: React.FC<FloatingPillProps> = ({
|
|
|
37
68
|
onPress,
|
|
38
69
|
initialX,
|
|
39
70
|
initialY,
|
|
71
|
+
pillWidth: propWidth,
|
|
72
|
+
pillHeight: propHeight,
|
|
40
73
|
styles: propStyles,
|
|
41
74
|
}) => {
|
|
75
|
+
const pillW = propWidth ?? PILL_WIDTH
|
|
76
|
+
const pillH = propHeight ?? PILL_HEIGHT
|
|
77
|
+
|
|
42
78
|
const { width: screenW, height: screenH } = Dimensions.get('window')
|
|
43
|
-
const defaultX = initialX ?? screenW -
|
|
44
|
-
const defaultY = initialY ?? screenH -
|
|
79
|
+
const defaultX = initialX ?? screenW - pillW - 16
|
|
80
|
+
const defaultY = initialY ?? screenH - pillH - 120
|
|
45
81
|
|
|
46
82
|
const pan = useRef(new Animated.ValueXY({ x: defaultX, y: defaultY })).current
|
|
47
83
|
const lastPosition = useRef({ x: defaultX, y: defaultY })
|
|
48
84
|
const isDragging = useRef(false)
|
|
49
85
|
const dragDistance = useRef(0)
|
|
50
86
|
|
|
87
|
+
// Safe area insets measured from invisible SafeAreaView
|
|
88
|
+
const [insets, setInsets] = useState<SafeAreaInsets>({
|
|
89
|
+
top: 0,
|
|
90
|
+
bottom: 0,
|
|
91
|
+
left: 0,
|
|
92
|
+
right: 0,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const handleSafeAreaLayout = useCallback(
|
|
96
|
+
(event: LayoutChangeEvent) => {
|
|
97
|
+
const { x, y, width, height } = event.nativeEvent.layout
|
|
98
|
+
const screen = Dimensions.get('window')
|
|
99
|
+
const newInsets: SafeAreaInsets = {
|
|
100
|
+
top: y,
|
|
101
|
+
left: x,
|
|
102
|
+
bottom: screen.height - y - height,
|
|
103
|
+
right: screen.width - x - width,
|
|
104
|
+
}
|
|
105
|
+
setInsets(newInsets)
|
|
106
|
+
|
|
107
|
+
// Re-clamp current position to new safe area
|
|
108
|
+
const clamped = clampPosition(lastPosition.current.x, lastPosition.current.y, newInsets, pillW, pillH)
|
|
109
|
+
if (clamped.x !== lastPosition.current.x || clamped.y !== lastPosition.current.y) {
|
|
110
|
+
lastPosition.current = clamped
|
|
111
|
+
pan.setValue(clamped)
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
[pan],
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
const insetsRef = useRef(insets)
|
|
118
|
+
insetsRef.current = insets
|
|
119
|
+
|
|
51
120
|
const panResponder = useRef(
|
|
52
121
|
PanResponder.create({
|
|
53
122
|
onStartShouldSetPanResponder: () => true,
|
|
@@ -72,6 +141,8 @@ export const FloatingPill: React.FC<FloatingPillProps> = ({
|
|
|
72
141
|
if (dragDistance.current > DRAG_THRESHOLD) {
|
|
73
142
|
isDragging.current = true
|
|
74
143
|
}
|
|
144
|
+
|
|
145
|
+
// Allow free dragging — bounce back happens on release
|
|
75
146
|
Animated.event([null, { dx: pan.x, dy: pan.y }], {
|
|
76
147
|
useNativeDriver: false,
|
|
77
148
|
})(_, gestureState)
|
|
@@ -82,6 +153,9 @@ export const FloatingPill: React.FC<FloatingPillProps> = ({
|
|
|
82
153
|
const newPos = clampPosition(
|
|
83
154
|
lastPosition.current.x + gestureState.dx,
|
|
84
155
|
lastPosition.current.y + gestureState.dy,
|
|
156
|
+
insetsRef.current,
|
|
157
|
+
pillW,
|
|
158
|
+
pillH,
|
|
85
159
|
)
|
|
86
160
|
|
|
87
161
|
lastPosition.current = newPos
|
|
@@ -107,33 +181,45 @@ export const FloatingPill: React.FC<FloatingPillProps> = ({
|
|
|
107
181
|
const shadowColor = hasError ? theme.error : theme.accent
|
|
108
182
|
|
|
109
183
|
return (
|
|
110
|
-
|
|
111
|
-
{
|
|
112
|
-
style={
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
184
|
+
<>
|
|
185
|
+
{/* Invisible SafeAreaView to measure insets */}
|
|
186
|
+
<SafeAreaView style={componentStyles.measurer} pointerEvents="none">
|
|
187
|
+
<View style={componentStyles.measurerInner} onLayout={handleSafeAreaLayout} />
|
|
188
|
+
</SafeAreaView>
|
|
189
|
+
|
|
190
|
+
<Animated.View
|
|
191
|
+
testID={TestIDs.floatingPill}
|
|
192
|
+
{...panResponder.panHandlers}
|
|
193
|
+
style={[
|
|
194
|
+
componentStyles.pill,
|
|
195
|
+
{
|
|
196
|
+
minWidth: pillW,
|
|
197
|
+
height: pillH,
|
|
198
|
+
borderRadius: pillH / 2,
|
|
199
|
+
backgroundColor,
|
|
200
|
+
transform: pan.getTranslateTransform(),
|
|
201
|
+
shadowColor,
|
|
202
|
+
},
|
|
203
|
+
propStyles?.pillStyle,
|
|
204
|
+
]}
|
|
205
|
+
>
|
|
206
|
+
<Text
|
|
207
|
+
testID={TestIDs.floatingPillText}
|
|
208
|
+
style={[componentStyles.pillText, propStyles?.pillTextStyle]}
|
|
209
|
+
numberOfLines={1}
|
|
210
|
+
>
|
|
211
|
+
{text}
|
|
212
|
+
</Text>
|
|
213
|
+
</Animated.View>
|
|
214
|
+
</>
|
|
126
215
|
)
|
|
127
216
|
}
|
|
128
217
|
|
|
129
218
|
// ─── Styles ──────────────────────────────────────────────────────────────────
|
|
130
219
|
|
|
131
|
-
const
|
|
220
|
+
const componentStyles = StyleSheet.create({
|
|
132
221
|
pill: {
|
|
133
222
|
position: 'absolute',
|
|
134
|
-
minWidth: PILL_WIDTH,
|
|
135
|
-
height: PILL_HEIGHT,
|
|
136
|
-
borderRadius: PILL_HEIGHT / 2,
|
|
137
223
|
paddingHorizontal: 14,
|
|
138
224
|
alignItems: 'center',
|
|
139
225
|
justifyContent: 'center',
|
|
@@ -150,4 +236,12 @@ const styles = StyleSheet.create({
|
|
|
150
236
|
color: '#FFFFFF',
|
|
151
237
|
letterSpacing: 0.5,
|
|
152
238
|
},
|
|
239
|
+
measurer: {
|
|
240
|
+
...StyleSheet.absoluteFillObject,
|
|
241
|
+
zIndex: -1,
|
|
242
|
+
opacity: 0,
|
|
243
|
+
},
|
|
244
|
+
measurerInner: {
|
|
245
|
+
flex: 1,
|
|
246
|
+
},
|
|
153
247
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useMemo } from 'react'
|
|
2
2
|
import { Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
|
|
3
|
-
import { MonospaceFont } from '../constants'
|
|
3
|
+
import { MonospaceFont, TestIDs } from '../constants'
|
|
4
4
|
import { useBackstageTheme } from '../ThemeContext'
|
|
5
5
|
import { JsonTreeView } from './JsonTreeView'
|
|
6
6
|
import type { AppInfoItem, BackstageStyleOverrides, BackstageTheme, QuickAction } from '../types'
|
|
@@ -57,12 +57,13 @@ export const InfoTab: React.FC<InfoTabProps> = ({
|
|
|
57
57
|
|
|
58
58
|
return (
|
|
59
59
|
<ScrollView
|
|
60
|
+
testID={TestIDs.infoTab.container}
|
|
60
61
|
style={s.container}
|
|
61
62
|
contentContainerStyle={s.scrollContent}
|
|
62
63
|
showsVerticalScrollIndicator={false}
|
|
63
64
|
>
|
|
64
65
|
{/* ── Device Info Section ─────────────────────────────────── */}
|
|
65
|
-
<View style={s.section}>
|
|
66
|
+
<View testID={TestIDs.infoTab.deviceInfo} style={s.section}>
|
|
66
67
|
<Text style={[s.sectionTitle, propStyles?.sectionTitleStyle]}>DEVICE INFO</Text>
|
|
67
68
|
<View style={s.card}>
|
|
68
69
|
{allInfo.map((item, index) => (
|
|
@@ -83,7 +84,7 @@ export const InfoTab: React.FC<InfoTabProps> = ({
|
|
|
83
84
|
|
|
84
85
|
{/* ── State Tree Section ──────────────────────────────────── */}
|
|
85
86
|
{state && Object.keys(state).length > 0 && (
|
|
86
|
-
<View style={s.section}>
|
|
87
|
+
<View testID={TestIDs.infoTab.stateTree} style={s.section}>
|
|
87
88
|
<Text style={[s.sectionTitle, propStyles?.sectionTitleStyle]}>STATE TREE</Text>
|
|
88
89
|
<View style={s.card}>
|
|
89
90
|
<JsonTreeView data={state} hideRoot maxDepth={jsonMaxDepth} />
|
|
@@ -93,7 +94,7 @@ export const InfoTab: React.FC<InfoTabProps> = ({
|
|
|
93
94
|
|
|
94
95
|
{/* ── Quick Actions Section ─────────────────────────────────── */}
|
|
95
96
|
{quickActions.length > 0 && (
|
|
96
|
-
<View style={s.section}>
|
|
97
|
+
<View testID={TestIDs.infoTab.quickActions} style={s.section}>
|
|
97
98
|
<Text style={[s.sectionTitle, propStyles?.sectionTitleStyle]}>QUICK ACTIONS</Text>
|
|
98
99
|
<View style={s.actionsGrid}>
|
|
99
100
|
{quickActions.map((action, index) => {
|
|
@@ -106,7 +107,7 @@ export const InfoTab: React.FC<InfoTabProps> = ({
|
|
|
106
107
|
return (
|
|
107
108
|
<TouchableOpacity
|
|
108
109
|
key={`action_${index}`}
|
|
109
|
-
testID={action.testID}
|
|
110
|
+
testID={action.testID || TestIDs.infoTab.actionButton(index)}
|
|
110
111
|
style={[
|
|
111
112
|
s.actionButton,
|
|
112
113
|
action.destructive && s.actionButtonDestructive,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useMemo, useState } from 'react'
|
|
2
2
|
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'
|
|
3
|
-
import { MonospaceFont } from '../constants'
|
|
3
|
+
import { MonospaceFont, TestIDs } from '../constants'
|
|
4
4
|
import { useBackstageTheme } from '../ThemeContext'
|
|
5
5
|
import { LogLevel } from '../types'
|
|
6
6
|
import type { LogEntry, BackstageStyleOverrides, BackstageTheme } from '../types'
|
|
@@ -74,20 +74,28 @@ export const LogItem: React.FC<LogItemProps> = React.memo(
|
|
|
74
74
|
|
|
75
75
|
return (
|
|
76
76
|
<TouchableOpacity
|
|
77
|
+
testID={TestIDs.logItem.container(item.id)}
|
|
77
78
|
style={[s.container, { backgroundColor: badge.rowBg }]}
|
|
78
79
|
onPress={hasData ? toggleExpand : undefined}
|
|
79
80
|
onLongPress={handleCopy}
|
|
80
81
|
activeOpacity={hasData ? 0.7 : 1}
|
|
81
82
|
>
|
|
82
83
|
<View style={s.headerRow}>
|
|
83
|
-
<View
|
|
84
|
+
<View
|
|
85
|
+
testID={TestIDs.logItem.badge(item.id)}
|
|
86
|
+
style={[s.badge, { backgroundColor: badge.bgColor }]}
|
|
87
|
+
>
|
|
84
88
|
<Text style={[s.badgeText, { color: badge.color }]}>{badge.label}</Text>
|
|
85
89
|
</View>
|
|
86
|
-
<Text
|
|
90
|
+
<Text
|
|
91
|
+
testID={TestIDs.logItem.timestamp(item.id)}
|
|
92
|
+
style={[s.timestamp, propStyles?.logTimestampStyle]}
|
|
93
|
+
>
|
|
87
94
|
{formatTimestamp(item.timestamp)}
|
|
88
95
|
</Text>
|
|
89
96
|
{onCopy && (
|
|
90
97
|
<TouchableOpacity
|
|
98
|
+
testID={TestIDs.logItem.copyButton(item.id)}
|
|
91
99
|
style={s.copyButton}
|
|
92
100
|
onPress={handleCopy}
|
|
93
101
|
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
@@ -98,6 +106,7 @@ export const LogItem: React.FC<LogItemProps> = React.memo(
|
|
|
98
106
|
</View>
|
|
99
107
|
|
|
100
108
|
<Text
|
|
109
|
+
testID={TestIDs.logItem.message(item.id)}
|
|
101
110
|
style={[s.message, propStyles?.logMessageStyle]}
|
|
102
111
|
numberOfLines={expanded ? undefined : 3}
|
|
103
112
|
>
|
|
@@ -105,7 +114,7 @@ export const LogItem: React.FC<LogItemProps> = React.memo(
|
|
|
105
114
|
</Text>
|
|
106
115
|
|
|
107
116
|
{expanded && hasData && (
|
|
108
|
-
<View style={s.dataContainer}>
|
|
117
|
+
<View testID={TestIDs.logItem.dataContainer(item.id)} style={s.dataContainer}>
|
|
109
118
|
<JsonTreeView data={item.data} hideRoot maxDepth={jsonMaxDepth} />
|
|
110
119
|
</View>
|
|
111
120
|
)}
|
|
@@ -69,7 +69,7 @@ export const LogsTab: React.FC<LogsTabProps> = ({
|
|
|
69
69
|
const keyExtractor = useCallback((item: LogEntry) => item.id, [])
|
|
70
70
|
|
|
71
71
|
return (
|
|
72
|
-
<View style={s.container}>
|
|
72
|
+
<View testID={TestIDs.logsTab.container} style={s.container}>
|
|
73
73
|
<View style={s.searchContainer}>
|
|
74
74
|
<View style={s.searchInputWrapper}>
|
|
75
75
|
<Text style={s.searchIcon}>⌕</Text>
|
|
@@ -98,7 +98,7 @@ export const LogsTab: React.FC<LogsTabProps> = ({
|
|
|
98
98
|
)}
|
|
99
99
|
</View>
|
|
100
100
|
|
|
101
|
-
<View style={s.countBar}>
|
|
101
|
+
<View testID={TestIDs.logsTab.statsBar} style={s.countBar}>
|
|
102
102
|
<Text style={s.countText}>
|
|
103
103
|
{filteredLogs.length === logs.length
|
|
104
104
|
? `${logs.length} log${logs.length !== 1 ? 's' : ''}`
|
|
@@ -68,7 +68,7 @@ export const NetworkTab: React.FC<NetworkTabProps> = ({
|
|
|
68
68
|
const keyExtractor = useCallback((item: NetworkEntry) => item.id, [])
|
|
69
69
|
|
|
70
70
|
return (
|
|
71
|
-
<View style={s.container}>
|
|
71
|
+
<View testID={TestIDs.networkTab.container} style={s.container}>
|
|
72
72
|
<View style={s.searchContainer}>
|
|
73
73
|
<View style={s.searchInputWrapper}>
|
|
74
74
|
<Text style={s.searchIcon}>⌕</Text>
|
|
@@ -95,7 +95,7 @@ export const NetworkTab: React.FC<NetworkTabProps> = ({
|
|
|
95
95
|
</TouchableOpacity>
|
|
96
96
|
</View>
|
|
97
97
|
|
|
98
|
-
<View style={s.statsBar}>
|
|
98
|
+
<View testID={TestIDs.networkTab.statsBar} style={s.statsBar}>
|
|
99
99
|
<View style={s.statsRow}>
|
|
100
100
|
<Text style={s.statText}>
|
|
101
101
|
{filteredEntries.length === entries.length
|