react-native-network-lens 0.1.1 → 0.2.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 +254 -98
- package/lib/module/components/BodyViewer.js +30 -24
- package/lib/module/components/BodyViewer.js.map +1 -1
- package/lib/module/components/JsonTree/JsonTreeView.js +10 -6
- package/lib/module/components/JsonTree/JsonTreeView.js.map +1 -1
- package/lib/module/components/NetworkLens.js +4 -8
- package/lib/module/components/NetworkLens.js.map +1 -1
- package/lib/module/components/NetworkLensOverlay.js +67 -22
- package/lib/module/components/NetworkLensOverlay.js.map +1 -1
- package/lib/module/components/RawTextView.js +10 -6
- package/lib/module/components/RawTextView.js.map +1 -1
- package/lib/module/components/RequestDetails/RequestDetails.js +35 -17
- package/lib/module/components/RequestDetails/RequestDetails.js.map +1 -1
- package/lib/module/components/RequestList/RequestList.js +4 -5
- package/lib/module/components/RequestList/RequestList.js.map +1 -1
- package/lib/module/components/ui/Button.js +5 -3
- package/lib/module/components/ui/Button.js.map +1 -1
- package/lib/module/components/ui/Pill.js +13 -6
- package/lib/module/components/ui/Pill.js.map +1 -1
- package/lib/module/settings.js +48 -0
- package/lib/module/settings.js.map +1 -0
- package/lib/module/theme.js +4 -4
- package/lib/typescript/src/components/BodyViewer.d.ts.map +1 -1
- package/lib/typescript/src/components/JsonTree/JsonTreeView.d.ts.map +1 -1
- package/lib/typescript/src/components/NetworkLens.d.ts +0 -3
- package/lib/typescript/src/components/NetworkLens.d.ts.map +1 -1
- package/lib/typescript/src/components/NetworkLensOverlay.d.ts +1 -1
- package/lib/typescript/src/components/NetworkLensOverlay.d.ts.map +1 -1
- package/lib/typescript/src/components/RawTextView.d.ts.map +1 -1
- package/lib/typescript/src/components/RequestDetails/RequestDetails.d.ts.map +1 -1
- package/lib/typescript/src/components/RequestList/RequestList.d.ts +1 -4
- package/lib/typescript/src/components/RequestList/RequestList.d.ts.map +1 -1
- package/lib/typescript/src/components/ui/Button.d.ts +3 -1
- package/lib/typescript/src/components/ui/Button.d.ts.map +1 -1
- package/lib/typescript/src/components/ui/Pill.d.ts +1 -0
- package/lib/typescript/src/components/ui/Pill.d.ts.map +1 -1
- package/lib/typescript/src/settings.d.ts +18 -0
- package/lib/typescript/src/settings.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/BodyViewer.tsx +36 -28
- package/src/components/JsonTree/JsonTreeView.tsx +11 -5
- package/src/components/NetworkLens.tsx +2 -11
- package/src/components/NetworkLensOverlay.tsx +54 -23
- package/src/components/RawTextView.tsx +11 -6
- package/src/components/RequestDetails/RequestDetails.tsx +26 -16
- package/src/components/RequestList/RequestList.tsx +1 -5
- package/src/components/ui/Button.tsx +17 -5
- package/src/components/ui/Pill.tsx +14 -5
- package/src/settings.ts +55 -0
- package/src/theme.ts +2 -2
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
useImperativeHandle,
|
|
6
6
|
useMemo,
|
|
7
7
|
useState,
|
|
8
|
-
type ReactNode,
|
|
9
8
|
} from 'react';
|
|
10
9
|
import { BackHandler, Share, StyleSheet, View } from 'react-native';
|
|
11
10
|
import { setLensBackHandler } from '../backHandler';
|
|
@@ -40,8 +39,6 @@ export interface NetworkLensProps {
|
|
|
40
39
|
* `copyToClipboard={Clipboard.setString}`.
|
|
41
40
|
*/
|
|
42
41
|
copyToClipboard?: (text: string) => void;
|
|
43
|
-
/** Rendered above the request list's search bar (the overlay uses it for its title bar). */
|
|
44
|
-
header?: ReactNode;
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
export interface NetworkLensHandle {
|
|
@@ -58,7 +55,6 @@ const NetworkLens = forwardRef<NetworkLensHandle, NetworkLensProps>(
|
|
|
58
55
|
compact = false,
|
|
59
56
|
maxRows,
|
|
60
57
|
copyToClipboard,
|
|
61
|
-
header,
|
|
62
58
|
},
|
|
63
59
|
ref
|
|
64
60
|
) {
|
|
@@ -71,7 +67,6 @@ const NetworkLens = forwardRef<NetworkLensHandle, NetworkLensProps>(
|
|
|
71
67
|
compact={compact}
|
|
72
68
|
maxRows={maxRows}
|
|
73
69
|
copyToClipboard={copyToClipboard}
|
|
74
|
-
header={header}
|
|
75
70
|
/>
|
|
76
71
|
</ThemeContext.Provider>
|
|
77
72
|
);
|
|
@@ -79,13 +74,10 @@ const NetworkLens = forwardRef<NetworkLensHandle, NetworkLensProps>(
|
|
|
79
74
|
);
|
|
80
75
|
|
|
81
76
|
type InspectorProps = Required<Pick<NetworkLensProps, 'sort' | 'compact'>> &
|
|
82
|
-
Pick<NetworkLensProps, 'maxRows' | 'copyToClipboard'
|
|
77
|
+
Pick<NetworkLensProps, 'maxRows' | 'copyToClipboard'>;
|
|
83
78
|
|
|
84
79
|
const Inspector = forwardRef<NetworkLensHandle, InspectorProps>(
|
|
85
|
-
function InspectorInner(
|
|
86
|
-
{ sort, compact, maxRows, copyToClipboard, header },
|
|
87
|
-
ref
|
|
88
|
-
) {
|
|
80
|
+
function InspectorInner({ sort, compact, maxRows, copyToClipboard }, ref) {
|
|
89
81
|
const theme = useTheme();
|
|
90
82
|
const requests = useNetworkRequests();
|
|
91
83
|
const loggingEnabled = useLoggerEnabled();
|
|
@@ -163,7 +155,6 @@ const Inspector = forwardRef<NetworkLensHandle, InspectorProps>(
|
|
|
163
155
|
loggingEnabled={loggingEnabled}
|
|
164
156
|
onPressItem={setSelectedId}
|
|
165
157
|
menuActions={menuActions}
|
|
166
|
-
header={header}
|
|
167
158
|
/>
|
|
168
159
|
)}
|
|
169
160
|
</View>
|
|
@@ -11,15 +11,16 @@ import {
|
|
|
11
11
|
} from 'react-native';
|
|
12
12
|
import { useLoggerEnabled, useNetworkRequests } from '../hooks';
|
|
13
13
|
import { ThemeContext, useTheme, type ThemeInput } from '../theme';
|
|
14
|
+
|
|
15
|
+
const HIT_SLOP = { top: 8, bottom: 8, left: 8, right: 8 };
|
|
14
16
|
import NetworkLens, {
|
|
15
17
|
type NetworkLensHandle,
|
|
16
18
|
type NetworkLensProps,
|
|
17
19
|
} from './NetworkLens';
|
|
18
|
-
import { IconButton } from './ui';
|
|
19
20
|
|
|
20
21
|
export interface NetworkLensOverlayProps extends Omit<
|
|
21
22
|
NetworkLensProps,
|
|
22
|
-
'enabled' | 'theme'
|
|
23
|
+
'enabled' | 'theme'
|
|
23
24
|
> {
|
|
24
25
|
/**
|
|
25
26
|
* Render nothing when `false`, e.g.
|
|
@@ -88,17 +89,6 @@ const Overlay = ({
|
|
|
88
89
|
[position.endsWith('right') ? 'right' : 'left']: offset?.x ?? 16,
|
|
89
90
|
};
|
|
90
91
|
|
|
91
|
-
const header = (
|
|
92
|
-
<View style={styles.header}>
|
|
93
|
-
<Text style={[styles.title, { color: t.colors.text }]}>{title}</Text>
|
|
94
|
-
<IconButton
|
|
95
|
-
glyph="✕"
|
|
96
|
-
accessibilityLabel="Close network inspector"
|
|
97
|
-
onPress={() => setOpen(false)}
|
|
98
|
-
/>
|
|
99
|
-
</View>
|
|
100
|
-
);
|
|
101
|
-
|
|
102
92
|
return (
|
|
103
93
|
<>
|
|
104
94
|
{!hideButton && (
|
|
@@ -135,12 +125,43 @@ const Overlay = ({
|
|
|
135
125
|
}}
|
|
136
126
|
>
|
|
137
127
|
<View style={[styles.sheet, { backgroundColor: t.colors.surface }]}>
|
|
138
|
-
<
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
128
|
+
<View
|
|
129
|
+
style={[
|
|
130
|
+
styles.header,
|
|
131
|
+
{
|
|
132
|
+
backgroundColor: t.colors.surface,
|
|
133
|
+
borderBottomColor: t.colors.border,
|
|
134
|
+
},
|
|
135
|
+
]}
|
|
136
|
+
>
|
|
137
|
+
<View style={styles.headerSide} />
|
|
138
|
+
<Text
|
|
139
|
+
style={[styles.title, { color: t.colors.text }]}
|
|
140
|
+
numberOfLines={1}
|
|
141
|
+
>
|
|
142
|
+
{title}
|
|
143
|
+
</Text>
|
|
144
|
+
<View style={styles.headerSide}>
|
|
145
|
+
<Pressable
|
|
146
|
+
accessibilityRole="button"
|
|
147
|
+
accessibilityLabel="Close network inspector"
|
|
148
|
+
onPress={() => setOpen(false)}
|
|
149
|
+
hitSlop={HIT_SLOP}
|
|
150
|
+
style={({ pressed }) => [
|
|
151
|
+
styles.close,
|
|
152
|
+
{ backgroundColor: t.colors.surfaceAlt },
|
|
153
|
+
pressed && styles.pressed,
|
|
154
|
+
]}
|
|
155
|
+
>
|
|
156
|
+
<Text
|
|
157
|
+
style={[styles.closeGlyph, { color: t.colors.textMuted }]}
|
|
158
|
+
>
|
|
159
|
+
✕
|
|
160
|
+
</Text>
|
|
161
|
+
</Pressable>
|
|
162
|
+
</View>
|
|
163
|
+
</View>
|
|
164
|
+
<NetworkLens ref={lensRef} {...lensProps} theme={theme} />
|
|
144
165
|
</View>
|
|
145
166
|
</Modal>
|
|
146
167
|
</>
|
|
@@ -175,13 +196,23 @@ const styles = StyleSheet.create({
|
|
|
175
196
|
paddingTop: Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) : 0,
|
|
176
197
|
},
|
|
177
198
|
header: {
|
|
199
|
+
height: 52,
|
|
178
200
|
flexDirection: 'row',
|
|
179
201
|
alignItems: 'center',
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
202
|
+
paddingHorizontal: 12,
|
|
203
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
204
|
+
},
|
|
205
|
+
headerSide: { width: 44, alignItems: 'flex-end', justifyContent: 'center' },
|
|
206
|
+
title: { flex: 1, textAlign: 'center', fontSize: 17, fontWeight: '600' },
|
|
207
|
+
close: {
|
|
208
|
+
width: 30,
|
|
209
|
+
height: 30,
|
|
210
|
+
borderRadius: 15,
|
|
211
|
+
alignItems: 'center',
|
|
212
|
+
justifyContent: 'center',
|
|
183
213
|
},
|
|
184
|
-
|
|
214
|
+
closeGlyph: { fontSize: 15, fontWeight: '700' },
|
|
215
|
+
pressed: { opacity: 0.6 },
|
|
185
216
|
});
|
|
186
217
|
|
|
187
218
|
export default NetworkLensOverlay;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo } from 'react';
|
|
2
2
|
import { StyleSheet, Text, View } from 'react-native';
|
|
3
|
+
import { useCodeFont } from '../settings';
|
|
3
4
|
import { useTheme, type Theme } from '../theme';
|
|
4
5
|
import { findLineMatches, splitHighlights, toLines } from '../utils/search';
|
|
5
6
|
import { CodeList } from './CodeList';
|
|
@@ -22,6 +23,7 @@ const RawTextView = ({
|
|
|
22
23
|
onMatchCountChange,
|
|
23
24
|
}: Props) => {
|
|
24
25
|
const theme = useTheme();
|
|
26
|
+
const font = useCodeFont(theme);
|
|
25
27
|
const lines = useMemo(() => toLines(text), [text]);
|
|
26
28
|
const matches = useMemo(() => findLineMatches(lines, query), [lines, query]);
|
|
27
29
|
|
|
@@ -37,14 +39,14 @@ const RawTextView = ({
|
|
|
37
39
|
matches[Math.min(activeMatch, Math.max(matches.length - 1, 0))]?.line;
|
|
38
40
|
|
|
39
41
|
const gutterChars = String(lines.length).length;
|
|
40
|
-
const charWidth =
|
|
42
|
+
const charWidth = font.charWidth;
|
|
41
43
|
const contentWidth = useMemo(() => {
|
|
42
44
|
let max = 0;
|
|
43
45
|
for (const line of lines) if (line.length > max) max = line.length;
|
|
44
46
|
return (max + gutterChars + 3) * charWidth + 32;
|
|
45
47
|
}, [lines, gutterChars, charWidth]);
|
|
46
48
|
|
|
47
|
-
const rowHeight =
|
|
49
|
+
const rowHeight = font.lineHeight;
|
|
48
50
|
|
|
49
51
|
const renderItem = useCallback(
|
|
50
52
|
({ item, index }: { item: string; index: number }) => (
|
|
@@ -53,13 +55,14 @@ const RawTextView = ({
|
|
|
53
55
|
index={index}
|
|
54
56
|
gutterChars={gutterChars}
|
|
55
57
|
theme={theme}
|
|
58
|
+
font={font}
|
|
56
59
|
query={query}
|
|
57
60
|
matched={matchedLines.has(index)}
|
|
58
61
|
active={index === activeLine}
|
|
59
62
|
height={rowHeight}
|
|
60
63
|
/>
|
|
61
64
|
),
|
|
62
|
-
[gutterChars, theme, query, matchedLines, activeLine, rowHeight]
|
|
65
|
+
[gutterChars, theme, font, query, matchedLines, activeLine, rowHeight]
|
|
63
66
|
);
|
|
64
67
|
|
|
65
68
|
return (
|
|
@@ -82,6 +85,7 @@ const Line = React.memo(function LineInner({
|
|
|
82
85
|
index,
|
|
83
86
|
gutterChars,
|
|
84
87
|
theme,
|
|
88
|
+
font,
|
|
85
89
|
query,
|
|
86
90
|
matched,
|
|
87
91
|
active,
|
|
@@ -91,6 +95,7 @@ const Line = React.memo(function LineInner({
|
|
|
91
95
|
index: number;
|
|
92
96
|
gutterChars: number;
|
|
93
97
|
theme: Theme;
|
|
98
|
+
font: ReturnType<typeof useCodeFont>;
|
|
94
99
|
query: string;
|
|
95
100
|
matched: boolean;
|
|
96
101
|
active: boolean;
|
|
@@ -98,8 +103,8 @@ const Line = React.memo(function LineInner({
|
|
|
98
103
|
}) {
|
|
99
104
|
const codeStyle = {
|
|
100
105
|
fontFamily: theme.fonts.mono,
|
|
101
|
-
fontSize:
|
|
102
|
-
lineHeight:
|
|
106
|
+
fontSize: font.fontSize,
|
|
107
|
+
lineHeight: font.lineHeight,
|
|
103
108
|
};
|
|
104
109
|
const segments = matched ? splitHighlights(text, query) : null;
|
|
105
110
|
return (
|
|
@@ -120,7 +125,7 @@ const Line = React.memo(function LineInner({
|
|
|
120
125
|
styles.gutter,
|
|
121
126
|
{
|
|
122
127
|
color: theme.colors.textMuted,
|
|
123
|
-
width: (gutterChars + 1) *
|
|
128
|
+
width: (gutterChars + 1) * font.charWidth + 8,
|
|
124
129
|
},
|
|
125
130
|
]}
|
|
126
131
|
>
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
} from '../../utils/format';
|
|
19
19
|
import BodyViewer from '../BodyViewer';
|
|
20
20
|
import {
|
|
21
|
-
IconButton,
|
|
22
21
|
KeyValueList,
|
|
23
22
|
Pill,
|
|
24
23
|
Section,
|
|
@@ -76,7 +75,22 @@ const RequestDetails = ({ request, onClose, copyToClipboard }: Props) => {
|
|
|
76
75
|
},
|
|
77
76
|
]}
|
|
78
77
|
>
|
|
79
|
-
<View style={styles.
|
|
78
|
+
<View style={styles.navRow}>
|
|
79
|
+
<TextButton
|
|
80
|
+
label="‹ Back"
|
|
81
|
+
onPress={onClose}
|
|
82
|
+
plain
|
|
83
|
+
style={styles.back}
|
|
84
|
+
textStyle={styles.backLabel}
|
|
85
|
+
/>
|
|
86
|
+
<Text
|
|
87
|
+
style={[styles.summary, { color: theme.colors.textMuted }]}
|
|
88
|
+
numberOfLines={1}
|
|
89
|
+
>
|
|
90
|
+
{summary}
|
|
91
|
+
</Text>
|
|
92
|
+
</View>
|
|
93
|
+
<View style={styles.pillRow}>
|
|
80
94
|
<Pill
|
|
81
95
|
label={request.method}
|
|
82
96
|
color={methodColor(theme, request.method)}
|
|
@@ -86,18 +100,6 @@ const RequestDetails = ({ request, onClose, copyToClipboard }: Props) => {
|
|
|
86
100
|
<Pill
|
|
87
101
|
label={statusLabel(request)}
|
|
88
102
|
color={statusColor(theme, request.status, failed)}
|
|
89
|
-
style={styles.headerPill}
|
|
90
|
-
/>
|
|
91
|
-
<Text
|
|
92
|
-
style={[styles.summary, { color: theme.colors.textMuted }]}
|
|
93
|
-
numberOfLines={1}
|
|
94
|
-
>
|
|
95
|
-
{summary}
|
|
96
|
-
</Text>
|
|
97
|
-
<IconButton
|
|
98
|
-
glyph="✕"
|
|
99
|
-
accessibilityLabel="Back to list"
|
|
100
|
-
onPress={onClose}
|
|
101
103
|
/>
|
|
102
104
|
</View>
|
|
103
105
|
<Pressable
|
|
@@ -294,9 +296,17 @@ const styles = StyleSheet.create({
|
|
|
294
296
|
paddingVertical: 10,
|
|
295
297
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
296
298
|
},
|
|
297
|
-
|
|
299
|
+
navRow: {
|
|
300
|
+
flexDirection: 'row',
|
|
301
|
+
alignItems: 'center',
|
|
302
|
+
justifyContent: 'space-between',
|
|
303
|
+
marginLeft: -8,
|
|
304
|
+
},
|
|
305
|
+
back: { paddingHorizontal: 8, paddingVertical: 4 },
|
|
306
|
+
backLabel: { fontSize: 16 },
|
|
307
|
+
pillRow: { flexDirection: 'row', alignItems: 'center', marginTop: 6 },
|
|
298
308
|
headerPill: { marginRight: 8 },
|
|
299
|
-
summary: {
|
|
309
|
+
summary: { flexShrink: 1, fontSize: 12, fontVariant: ['tabular-nums'] },
|
|
300
310
|
url: { fontSize: 14, marginTop: 8, lineHeight: 19 },
|
|
301
311
|
gql: { fontSize: 12, marginTop: 4, fontWeight: '600' },
|
|
302
312
|
tabs: { marginTop: 10 },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useMemo, useState
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
2
|
import { FlatList, Share, StyleSheet, Text, View } from 'react-native';
|
|
3
3
|
import type NetworkRequest from '../../core/NetworkRequest';
|
|
4
4
|
import { useTheme } from '../../theme';
|
|
@@ -30,8 +30,6 @@ interface Props {
|
|
|
30
30
|
loggingEnabled: boolean;
|
|
31
31
|
onPressItem: (id: string) => void;
|
|
32
32
|
menuActions: ActionItem[];
|
|
33
|
-
/** Optional row rendered above the search bar (used by the overlay for title + close). */
|
|
34
|
-
header?: ReactNode;
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
const keyExtractor = (row: NetworkRequestRow) => row.id;
|
|
@@ -52,7 +50,6 @@ const RequestList = ({
|
|
|
52
50
|
loggingEnabled,
|
|
53
51
|
onPressItem,
|
|
54
52
|
menuActions,
|
|
55
|
-
header,
|
|
56
53
|
}: Props) => {
|
|
57
54
|
const theme = useTheme();
|
|
58
55
|
const [search, setSearch] = useState('');
|
|
@@ -134,7 +131,6 @@ const RequestList = ({
|
|
|
134
131
|
},
|
|
135
132
|
]}
|
|
136
133
|
>
|
|
137
|
-
{header}
|
|
138
134
|
<View style={styles.searchRow}>
|
|
139
135
|
<SearchInput
|
|
140
136
|
value={search}
|
|
@@ -17,6 +17,8 @@ interface TextButtonProps {
|
|
|
17
17
|
onPress?: () => void;
|
|
18
18
|
disabled?: boolean;
|
|
19
19
|
active?: boolean;
|
|
20
|
+
/** Text-only button in the primary colour, for navigation actions like Back. */
|
|
21
|
+
plain?: boolean;
|
|
20
22
|
style?: StyleProp<ViewStyle>;
|
|
21
23
|
textStyle?: StyleProp<TextStyle>;
|
|
22
24
|
}
|
|
@@ -26,6 +28,7 @@ export const TextButton = ({
|
|
|
26
28
|
onPress,
|
|
27
29
|
disabled,
|
|
28
30
|
active,
|
|
31
|
+
plain,
|
|
29
32
|
style,
|
|
30
33
|
textStyle,
|
|
31
34
|
}: TextButtonProps) => {
|
|
@@ -39,9 +42,11 @@ export const TextButton = ({
|
|
|
39
42
|
style={({ pressed }) => [
|
|
40
43
|
styles.button,
|
|
41
44
|
{
|
|
42
|
-
backgroundColor:
|
|
43
|
-
?
|
|
44
|
-
:
|
|
45
|
+
backgroundColor: plain
|
|
46
|
+
? 'transparent'
|
|
47
|
+
: active
|
|
48
|
+
? theme.colors.primary
|
|
49
|
+
: theme.colors.surfaceAlt,
|
|
45
50
|
},
|
|
46
51
|
disabled ? styles.disabled : pressed && styles.pressed,
|
|
47
52
|
style,
|
|
@@ -50,7 +55,13 @@ export const TextButton = ({
|
|
|
50
55
|
<Text
|
|
51
56
|
style={[
|
|
52
57
|
styles.label,
|
|
53
|
-
{
|
|
58
|
+
{
|
|
59
|
+
color: plain
|
|
60
|
+
? theme.colors.primary
|
|
61
|
+
: active
|
|
62
|
+
? theme.colors.onPrimary
|
|
63
|
+
: theme.colors.text,
|
|
64
|
+
},
|
|
54
65
|
textStyle,
|
|
55
66
|
]}
|
|
56
67
|
>
|
|
@@ -112,7 +123,8 @@ const styles = StyleSheet.create({
|
|
|
112
123
|
},
|
|
113
124
|
label: { fontSize: 13, fontWeight: '600' },
|
|
114
125
|
icon: {
|
|
115
|
-
|
|
126
|
+
minWidth: 32,
|
|
127
|
+
paddingHorizontal: 6,
|
|
116
128
|
height: 32,
|
|
117
129
|
borderRadius: 8,
|
|
118
130
|
alignItems: 'center',
|
|
@@ -14,6 +14,7 @@ interface Props {
|
|
|
14
14
|
style?: StyleProp<ViewStyle>;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/** Fixed-height badge so filled and outlined variants align on one row. */
|
|
17
18
|
export const Pill = ({ label, color, filled, small, style }: Props) => (
|
|
18
19
|
<View
|
|
19
20
|
style={[
|
|
@@ -39,15 +40,23 @@ export const Pill = ({ label, color, filled, small, style }: Props) => (
|
|
|
39
40
|
|
|
40
41
|
const styles = StyleSheet.create({
|
|
41
42
|
pill: {
|
|
43
|
+
height: 24,
|
|
44
|
+
minWidth: 44,
|
|
45
|
+
paddingHorizontal: 8,
|
|
42
46
|
borderWidth: 1,
|
|
43
47
|
borderRadius: 6,
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
alignItems: 'center',
|
|
49
|
+
justifyContent: 'center',
|
|
46
50
|
alignSelf: 'flex-start',
|
|
47
51
|
},
|
|
48
|
-
pillSmall: {
|
|
52
|
+
pillSmall: {
|
|
53
|
+
height: 20,
|
|
54
|
+
minWidth: 40,
|
|
55
|
+
paddingHorizontal: 6,
|
|
56
|
+
borderRadius: 5,
|
|
57
|
+
},
|
|
49
58
|
outline: { backgroundColor: 'transparent' },
|
|
50
|
-
text: { fontSize: 12, fontWeight: '700', letterSpacing: 0.3 },
|
|
51
|
-
textSmall: { fontSize: 11 },
|
|
59
|
+
text: { fontSize: 12, lineHeight: 14, fontWeight: '700', letterSpacing: 0.3 },
|
|
60
|
+
textSmall: { fontSize: 11, lineHeight: 13 },
|
|
52
61
|
textFilled: { color: '#ffffff' },
|
|
53
62
|
});
|
package/src/settings.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
import type { Theme } from './theme';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tiny in-memory preferences shared by every viewer in the session, e.g. the
|
|
6
|
+
* code font scale the user picks with the "Aa" button.
|
|
7
|
+
*/
|
|
8
|
+
export type CodeFontScale = 'small' | 'medium' | 'large';
|
|
9
|
+
|
|
10
|
+
const SCALE_FACTOR: Record<CodeFontScale, number> = {
|
|
11
|
+
small: 0.85,
|
|
12
|
+
medium: 1,
|
|
13
|
+
large: 1.2,
|
|
14
|
+
};
|
|
15
|
+
const ORDER: CodeFontScale[] = ['small', 'medium', 'large'];
|
|
16
|
+
|
|
17
|
+
let scale: CodeFontScale = 'medium';
|
|
18
|
+
const listeners = new Set<() => void>();
|
|
19
|
+
|
|
20
|
+
const subscribe = (listener: () => void) => {
|
|
21
|
+
listeners.add(listener);
|
|
22
|
+
return () => {
|
|
23
|
+
listeners.delete(listener);
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
const getScale = () => scale;
|
|
27
|
+
|
|
28
|
+
export const setCodeFontScale = (next: CodeFontScale) => {
|
|
29
|
+
if (scale === next) return;
|
|
30
|
+
scale = next;
|
|
31
|
+
listeners.forEach((listener) => listener());
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const cycleCodeFontScale = () => {
|
|
35
|
+
setCodeFontScale(
|
|
36
|
+
ORDER[(ORDER.indexOf(scale) + 1) % ORDER.length] ?? 'medium'
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const useCodeFontScale = (): CodeFontScale =>
|
|
41
|
+
useSyncExternalStore(subscribe, getScale, getScale);
|
|
42
|
+
|
|
43
|
+
/** Font metrics for code views: the theme's base size times the chosen scale. */
|
|
44
|
+
export const useCodeFont = (theme: Theme) => {
|
|
45
|
+
const current = useCodeFontScale();
|
|
46
|
+
const factor = SCALE_FACTOR[current];
|
|
47
|
+
const fontSize = Math.round(theme.sizes.codeFontSize * factor);
|
|
48
|
+
return {
|
|
49
|
+
scale: current,
|
|
50
|
+
fontSize,
|
|
51
|
+
lineHeight: Math.round(fontSize * 1.5),
|
|
52
|
+
/** Average glyph advance for monospace fonts; used to size horizontal scroll areas. */
|
|
53
|
+
charWidth: fontSize * 0.62,
|
|
54
|
+
};
|
|
55
|
+
};
|
package/src/theme.ts
CHANGED
|
@@ -80,7 +80,7 @@ export const lightTheme: Theme = {
|
|
|
80
80
|
onHighlight: '#1f2937',
|
|
81
81
|
},
|
|
82
82
|
fonts: { mono: mono ?? 'monospace' },
|
|
83
|
-
sizes: { codeFontSize:
|
|
83
|
+
sizes: { codeFontSize: 14, codeLineHeight: 21 },
|
|
84
84
|
};
|
|
85
85
|
|
|
86
86
|
export const darkTheme: Theme = {
|
|
@@ -114,7 +114,7 @@ export const darkTheme: Theme = {
|
|
|
114
114
|
onHighlight: '#fef3c7',
|
|
115
115
|
},
|
|
116
116
|
fonts: { mono: mono ?? 'monospace' },
|
|
117
|
-
sizes: { codeFontSize:
|
|
117
|
+
sizes: { codeFontSize: 14, codeLineHeight: 21 },
|
|
118
118
|
};
|
|
119
119
|
|
|
120
120
|
export type ThemeInput = ThemeName | DeepPartial<Theme>;
|