@xpufx/paseo-top 0.4.4 → 0.4.7
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/OVERVIEW.md +18 -0
- package/client/host-ui.tsx +184 -0
- package/client/multi-host-view.tsx +57 -43
- package/client/multi-host.ts +36 -13
- package/client/pill-labels.ts +94 -1
- package/client/pill.tsx +692 -616
- package/client/resources-query.ts +1 -1
- package/client/settings-events.ts +1 -1
- package/client/settings-ui.tsx +86 -9
- package/client/surface.tsx +308 -276
- package/client/telemetry-copy.ts +8 -2
- package/client/telemetry.tsx +372 -321
- package/client/turn-panel.tsx +126 -104
- package/client/vendor/paseo-plugin-helper/{host.ts → core/host.ts} +9 -3
- package/client/vendor/paseo-plugin-helper/core/index.ts +76 -0
- package/client/vendor/paseo-plugin-helper/core/plugin-load-count.ts +89 -0
- package/client/vendor/paseo-plugin-helper/{query-refresh.ts → core/query-refresh.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{query.ts → core/query.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{settings.ts → core/settings.ts} +1 -1
- package/client/vendor/paseo-plugin-helper/{shared-settings.ts → core/shared-settings.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/clipboard.ts +2 -2
- package/client/vendor/paseo-plugin-helper/lifecycle/flair.ts +26 -0
- package/client/vendor/paseo-plugin-helper/{theme/color-utils.ts → lifecycle/host-color.ts} +95 -38
- package/client/vendor/paseo-plugin-helper/lifecycle/host-theme.tsx +109 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/index.ts +23 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/multi-host.ts +66 -0
- package/client/vendor/paseo-plugin-helper/{panel.tsx → lifecycle/panel.tsx} +7 -7
- package/client/vendor/paseo-plugin-helper/{pill.tsx → lifecycle/pill.tsx} +51 -18
- package/client/vendor/paseo-plugin-helper/lifecycle/providers.tsx +46 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/scroll-owner.ts +26 -0
- package/client/vendor/paseo-plugin-helper/{surface.tsx → lifecycle/surface.tsx} +6 -6
- package/client/vendor/paseo-plugin-helper/ui/color.ts +1 -0
- package/client/vendor/paseo-plugin-helper/ui/content.tsx +1326 -0
- package/client/vendor/paseo-plugin-helper/ui/controls.tsx +1018 -0
- package/client/vendor/paseo-plugin-helper/{components/DataTable.tsx → ui/data.tsx} +50 -58
- package/client/vendor/paseo-plugin-helper/ui/index.ts +44 -0
- package/client/vendor/paseo-plugin-helper/ui/layout.tsx +263 -0
- package/client/vendor/paseo-plugin-helper/ui/modal.tsx +111 -0
- package/client/vendor/paseo-plugin-helper/ui/plugin-reload-banner.tsx +119 -0
- package/client/vendor/paseo-plugin-helper/{settings-screen.tsx → ui/settings-screen.tsx} +3 -3
- package/client/vendor/paseo-plugin-helper/ui/settings.ts +36 -0
- package/client/vendor/paseo-plugin-helper/ui/text.tsx +821 -0
- package/client/vendor/paseo-plugin-helper/ui/theme.tsx +1 -0
- package/client/vendor/permission-audit/client.tsx +706 -0
- package/client/vendor/permission-audit/index.tsx +1 -0
- package/index.client.tsx +58 -37
- package/index.server.ts +256 -234
- package/npm-shrinkwrap.json +44 -0
- package/package.json +8 -4
- package/paseo-plugin.json +2 -0
- package/screenshots/1-paseo-top-modal-system.jpg +0 -0
- package/screenshots/2-paseo-top-modal-workspace.jpg +0 -0
- package/screenshots/3-paseo-top-modal-settings.jpg +0 -0
- package/screenshots/4-paseo-top-modal-about.jpg +0 -0
- package/screenshots/paseo-top-dashboard.jpg +0 -0
- package/screenshots/paseo-top-general-view.jpg +0 -0
- package/server/resources.ts +98 -0
- package/server/vendor/paseo-plugin-helper/custom-pills.ts +70 -0
- package/server/vendor/paseo-plugin-helper/index.ts +2 -0
- package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
- package/server/vendor/paseo-plugin-helper/rpc-guard.ts +2 -2
- package/server/vendor/paseo-plugin-helper/storage-guard.ts +294 -0
- package/server/vendor/paseo-plugin-helper/storage.ts +36 -2
- package/server/vendor/paseo-plugin-helper/telemetry.ts +486 -0
- package/server/vendor/paseo-plugin-helper/tickets.ts +18 -67
- package/server/vendor/permission-audit/index.ts +1 -0
- package/server/vendor/permission-audit/server.ts +839 -0
- package/shared/helper-version.ts +1 -1
- package/shared/multi-host.ts +153 -159
- package/shared/resources.ts +145 -0
- package/shared/vendor/paseo-plugin-helper/async.ts +14 -7
- package/shared/vendor/paseo-plugin-helper/features.ts +244 -0
- package/shared/vendor/paseo-plugin-helper/forge.ts +27 -0
- package/shared/vendor/paseo-plugin-helper/index.ts +1 -0
- package/shared/vendor/paseo-plugin-helper/tickets.ts +122 -28
- package/shared/vendor/permission-audit/filter.ts +61 -0
- package/shared/vendor/permission-audit/index.ts +2 -0
- package/shared/vendor/permission-audit/shared.ts +119 -0
- package/shared/version.ts +1 -1
- package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +0 -493
- package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +0 -250
- package/client/vendor/paseo-plugin-helper/components/Badge.tsx +0 -168
- package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -204
- package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -257
- package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +0 -196
- package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +0 -277
- package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +0 -172
- package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +0 -180
- package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +0 -97
- package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +0 -70
- package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +0 -88
- package/client/vendor/paseo-plugin-helper/components/InteractiveRow.tsx +0 -142
- package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +0 -446
- package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +0 -247
- package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +0 -117
- package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +0 -53
- package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +0 -132
- package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -87
- package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -283
- package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +0 -80
- package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +0 -319
- package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +0 -150
- package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +0 -163
- package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +0 -157
- package/client/vendor/paseo-plugin-helper/components/index.ts +0 -26
- package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -225
- package/client/vendor/paseo-plugin-helper/forge-icon.tsx +0 -79
- package/client/vendor/paseo-plugin-helper/icon.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/index.ts +0 -30
- package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +0 -49
- package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +0 -103
- package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +0 -65
- package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +0 -368
- package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +0 -51
- package/client/vendor/paseo-plugin-helper/layout/Row.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/layout/index.ts +0 -7
- package/client/vendor/paseo-plugin-helper/styles/index.ts +0 -1
- package/client/vendor/paseo-plugin-helper/styles/recipes.ts +0 -550
- package/client/vendor/paseo-plugin-helper/theme/flair.ts +0 -76
- package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +0 -121
- package/client/vendor/paseo-plugin-helper/theme/index.ts +0 -7
- package/client/vendor/paseo-plugin-helper/theme/provider.tsx +0 -178
- package/client/vendor/paseo-plugin-helper/theme/responsive.ts +0 -213
- package/client/vendor/paseo-plugin-helper/theme/tokens.ts +0 -200
- package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +0 -57
- package/client/vendor/paseo-plugin-helper/tickets.tsx +0 -766
- /package/client/vendor/paseo-plugin-helper/{command-center.ts → core/command-center.ts} +0 -0
- /package/client/vendor/paseo-plugin-helper/{snapshot.ts → core/snapshot.ts} +0 -0
- /package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/haptics.ts +0 -0
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
import React, { type ReactNode } from "react";
|
|
2
|
-
import { StyleSheet, Text, View, type StyleProp, type TextStyle, type ViewStyle } from "react-native";
|
|
3
|
-
import { getClientHost } from "../host";
|
|
4
|
-
import { usePluginTheme } from "../theme/provider";
|
|
5
|
-
import { HighlightedText } from "./HighlightedText";
|
|
6
|
-
import type { SurfaceStyle } from "../theme/flair";
|
|
7
|
-
|
|
8
|
-
export interface CardProps {
|
|
9
|
-
children: ReactNode;
|
|
10
|
-
variant?: SurfaceStyle;
|
|
11
|
-
style?: StyleProp<ViewStyle>;
|
|
12
|
-
noPadding?: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface CardHeaderProps {
|
|
16
|
-
title: string;
|
|
17
|
-
subtitle?: string;
|
|
18
|
-
value?: string | number | ReactNode;
|
|
19
|
-
badge?: ReactNode;
|
|
20
|
-
action?: ReactNode;
|
|
21
|
-
icon?: string;
|
|
22
|
-
style?: StyleProp<ViewStyle>;
|
|
23
|
-
titleStyle?: StyleProp<TextStyle>;
|
|
24
|
-
subtitleStyle?: StyleProp<TextStyle>;
|
|
25
|
-
/**
|
|
26
|
-
* When set, every case-insensitive (literal, non-regex) occurrence of the
|
|
27
|
-
* query inside `title` is painted with the accent highlight. With no literal
|
|
28
|
-
* or token hit the whole title is marked, so a fuzzy match still reads.
|
|
29
|
-
*/
|
|
30
|
-
highlightQuery?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// TEMPORARY DIAGNOSTIC (#555) — one-shot probe logging the host icon the
|
|
34
|
-
// first CardHeader render resolves, right before it is used; strip once the
|
|
35
|
-
// missing-binding cause is confirmed.
|
|
36
|
-
let cardHeaderIconProbeFired = false;
|
|
37
|
-
|
|
38
|
-
export function CardHeader({
|
|
39
|
-
title,
|
|
40
|
-
subtitle,
|
|
41
|
-
value,
|
|
42
|
-
badge,
|
|
43
|
-
action,
|
|
44
|
-
icon,
|
|
45
|
-
style,
|
|
46
|
-
titleStyle,
|
|
47
|
-
subtitleStyle,
|
|
48
|
-
highlightQuery,
|
|
49
|
-
}: CardHeaderProps) {
|
|
50
|
-
const { Icon } = getClientHost();
|
|
51
|
-
// TEMPORARY DIAGNOSTIC (#555) — strip once the missing-binding cause is
|
|
52
|
-
// confirmed. A probe that throws must never crash the header itself.
|
|
53
|
-
try {
|
|
54
|
-
if (!cardHeaderIconProbeFired) {
|
|
55
|
-
cardHeaderIconProbeFired = true;
|
|
56
|
-
const describe = (value: unknown): string => {
|
|
57
|
-
if (value === undefined) return "undefined";
|
|
58
|
-
if (value === null) return "null";
|
|
59
|
-
if (typeof value === "function") {
|
|
60
|
-
const named = (value as { displayName?: string; name?: string }).displayName
|
|
61
|
-
?? (value as { name?: string }).name;
|
|
62
|
-
return `function(${named || "(anonymous)"})`;
|
|
63
|
-
}
|
|
64
|
-
if (typeof value === "object") {
|
|
65
|
-
const symbolTag = (value as { $$typeof?: unknown }).$$typeof;
|
|
66
|
-
return `object($$typeof=${String(symbolTag)}, render=${typeof (value as { render?: unknown }).render})`;
|
|
67
|
-
}
|
|
68
|
-
return typeof value;
|
|
69
|
-
};
|
|
70
|
-
console.warn(
|
|
71
|
-
"[2fado] CardHeader probe: icon=", icon ?? "(no icon prop)",
|
|
72
|
-
"resolved host Icon=", `typeofIcon=${typeof Icon}`, describe(Icon),
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
} catch (err) {
|
|
76
|
-
console.warn("[2fado] CardHeader probe failed:", err);
|
|
77
|
-
}
|
|
78
|
-
const { colors, flair, typography, padding, isCompact } = usePluginTheme();
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
<View
|
|
82
|
-
style={[
|
|
83
|
-
styles.headerContainer,
|
|
84
|
-
// Density-aware gap below the header. A hardcoded 8 wasted vertical
|
|
85
|
-
// space under the compact preset (xpufx-org/paseo#213).
|
|
86
|
-
{ marginBottom: padding.gap },
|
|
87
|
-
// On a compact/narrow surface, stack the title block above the action
|
|
88
|
-
// row instead of sharing one line. Sharing the line lets a wide action
|
|
89
|
-
// group (badges + toggle + buttons) hold its intrinsic width while the
|
|
90
|
-
// title column collapses to its longest word, wrapping one character
|
|
91
|
-
// per line and spilling the actions past the card (xpufx-org/paseo#202).
|
|
92
|
-
isCompact && styles.headerContainerCompact,
|
|
93
|
-
style,
|
|
94
|
-
]}
|
|
95
|
-
>
|
|
96
|
-
<View style={[styles.headerLeft, isCompact && styles.headerLeftCompact]}>
|
|
97
|
-
{icon ? <Icon name={icon} size={15} color={colors.foregroundMuted} /> : null}
|
|
98
|
-
<View style={styles.titleColumn}>
|
|
99
|
-
<Text
|
|
100
|
-
style={[
|
|
101
|
-
styles.headerTitle,
|
|
102
|
-
{
|
|
103
|
-
color: colors.foreground,
|
|
104
|
-
...typography.heading,
|
|
105
|
-
textTransform:
|
|
106
|
-
flair.headingTransform === "uppercase" ? "uppercase" : "none",
|
|
107
|
-
},
|
|
108
|
-
titleStyle,
|
|
109
|
-
]}
|
|
110
|
-
>
|
|
111
|
-
{highlightQuery ? (
|
|
112
|
-
<HighlightedText text={title} query={highlightQuery} fuzzyFallback />
|
|
113
|
-
) : (
|
|
114
|
-
title
|
|
115
|
-
)}
|
|
116
|
-
</Text>
|
|
117
|
-
{subtitle ? (
|
|
118
|
-
<Text
|
|
119
|
-
style={[
|
|
120
|
-
styles.headerSubtitle,
|
|
121
|
-
{ color: colors.foregroundMuted, ...typography.caption },
|
|
122
|
-
subtitleStyle,
|
|
123
|
-
]}
|
|
124
|
-
>
|
|
125
|
-
{subtitle}
|
|
126
|
-
</Text>
|
|
127
|
-
) : null}
|
|
128
|
-
</View>
|
|
129
|
-
</View>
|
|
130
|
-
|
|
131
|
-
<View style={[styles.headerRight, isCompact && styles.headerRightCompact]}>
|
|
132
|
-
{badge ? <View style={{ marginRight: 6 }}>{badge}</View> : null}
|
|
133
|
-
{typeof value === "string" || typeof value === "number" ? (
|
|
134
|
-
<Text
|
|
135
|
-
style={[
|
|
136
|
-
styles.headerValue,
|
|
137
|
-
{ color: colors.foreground, ...typography.bodyStrong },
|
|
138
|
-
]}
|
|
139
|
-
>
|
|
140
|
-
{value}
|
|
141
|
-
</Text>
|
|
142
|
-
) : (
|
|
143
|
-
value
|
|
144
|
-
)}
|
|
145
|
-
{action}
|
|
146
|
-
</View>
|
|
147
|
-
</View>
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export function Card({ children, variant, style, noPadding = false }: CardProps) {
|
|
152
|
-
const { colors, flair, resolveRadius, padding, alpha } = usePluginTheme();
|
|
153
|
-
|
|
154
|
-
const effectiveVariant = variant || flair.surfaceStyle;
|
|
155
|
-
const radius = resolveRadius("md");
|
|
156
|
-
|
|
157
|
-
let bg = colors.surface0;
|
|
158
|
-
let border = colors.border;
|
|
159
|
-
|
|
160
|
-
if (effectiveVariant === "tinted") {
|
|
161
|
-
bg = alpha(colors.accent, 0.04);
|
|
162
|
-
border = alpha(colors.accent, 0.2);
|
|
163
|
-
} else if (effectiveVariant === "elevated") {
|
|
164
|
-
bg = colors.surface1;
|
|
165
|
-
border = colors.border;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return (
|
|
169
|
-
<View
|
|
170
|
-
style={[
|
|
171
|
-
styles.card,
|
|
172
|
-
{
|
|
173
|
-
backgroundColor: bg,
|
|
174
|
-
borderColor: border,
|
|
175
|
-
borderRadius: radius,
|
|
176
|
-
borderWidth: flair.borderWidth,
|
|
177
|
-
paddingHorizontal: noPadding ? 0 : padding.horizontal,
|
|
178
|
-
paddingVertical: noPadding ? 0 : padding.vertical,
|
|
179
|
-
},
|
|
180
|
-
style,
|
|
181
|
-
]}
|
|
182
|
-
>
|
|
183
|
-
{children}
|
|
184
|
-
</View>
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
Card.Header = CardHeader;
|
|
189
|
-
|
|
190
|
-
const styles = StyleSheet.create({
|
|
191
|
-
card: {
|
|
192
|
-
overflow: "hidden",
|
|
193
|
-
width: "100%",
|
|
194
|
-
},
|
|
195
|
-
headerContainer: {
|
|
196
|
-
flexDirection: "row",
|
|
197
|
-
alignItems: "center",
|
|
198
|
-
justifyContent: "space-between",
|
|
199
|
-
flexWrap: "wrap",
|
|
200
|
-
gap: 8,
|
|
201
|
-
width: "100%",
|
|
202
|
-
},
|
|
203
|
-
headerContainerCompact: {
|
|
204
|
-
flexDirection: "column",
|
|
205
|
-
alignItems: "stretch",
|
|
206
|
-
justifyContent: "flex-start",
|
|
207
|
-
flexWrap: "nowrap",
|
|
208
|
-
},
|
|
209
|
-
headerLeft: {
|
|
210
|
-
flexDirection: "row",
|
|
211
|
-
alignItems: "center",
|
|
212
|
-
gap: 8,
|
|
213
|
-
// `flex: 1` written out: the compact override has to reset the basis too,
|
|
214
|
-
// and `flex` + explicit flex* keys in one flattened style is ambiguous.
|
|
215
|
-
flexGrow: 1,
|
|
216
|
-
flexShrink: 1,
|
|
217
|
-
flexBasis: 0,
|
|
218
|
-
minWidth: 0,
|
|
219
|
-
},
|
|
220
|
-
// Release `flex: 1` (basis 0) in the stacked column so the title block takes
|
|
221
|
-
// the full width and wraps at word boundaries instead of collapsing to its
|
|
222
|
-
// longest word.
|
|
223
|
-
headerLeftCompact: {
|
|
224
|
-
flexGrow: 0,
|
|
225
|
-
flexShrink: 0,
|
|
226
|
-
flexBasis: "auto",
|
|
227
|
-
width: "100%",
|
|
228
|
-
},
|
|
229
|
-
titleColumn: {
|
|
230
|
-
gap: 1,
|
|
231
|
-
flexShrink: 1,
|
|
232
|
-
},
|
|
233
|
-
headerTitle: {
|
|
234
|
-
fontWeight: "600",
|
|
235
|
-
},
|
|
236
|
-
headerSubtitle: {
|
|
237
|
-
fontWeight: "400",
|
|
238
|
-
},
|
|
239
|
-
headerRight: {
|
|
240
|
-
flexDirection: "row",
|
|
241
|
-
alignItems: "center",
|
|
242
|
-
gap: 6,
|
|
243
|
-
flexShrink: 0,
|
|
244
|
-
},
|
|
245
|
-
// The action group may wrap onto multiple lines once the title owns its own
|
|
246
|
-
// row; letting it shrink + wrap keeps a long badge/toggle/button set inside
|
|
247
|
-
// the card instead of overflowing it.
|
|
248
|
-
headerRightCompact: {
|
|
249
|
-
flexShrink: 1,
|
|
250
|
-
flexWrap: "wrap",
|
|
251
|
-
justifyContent: "flex-start",
|
|
252
|
-
minWidth: 0,
|
|
253
|
-
},
|
|
254
|
-
headerValue: {
|
|
255
|
-
fontWeight: "600",
|
|
256
|
-
},
|
|
257
|
-
});
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Platform,
|
|
4
|
-
Pressable,
|
|
5
|
-
ScrollView as FallbackScrollView,
|
|
6
|
-
StyleSheet,
|
|
7
|
-
Text,
|
|
8
|
-
View,
|
|
9
|
-
type StyleProp,
|
|
10
|
-
type TextStyle,
|
|
11
|
-
type ViewStyle,
|
|
12
|
-
} from "react-native";
|
|
13
|
-
import { getClientHost } from "../host";
|
|
14
|
-
import { usePluginTheme } from "../theme/provider";
|
|
15
|
-
import { copyToClipboard } from "../utils/clipboard";
|
|
16
|
-
|
|
17
|
-
export interface CodeBlockProps {
|
|
18
|
-
code: string;
|
|
19
|
-
language?: string;
|
|
20
|
-
title?: string;
|
|
21
|
-
maxHeight?: number;
|
|
22
|
-
copyable?: boolean;
|
|
23
|
-
style?: StyleProp<ViewStyle>;
|
|
24
|
-
textStyle?: StyleProp<TextStyle>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function CodeBlock({
|
|
28
|
-
code,
|
|
29
|
-
language,
|
|
30
|
-
title,
|
|
31
|
-
maxHeight = 320,
|
|
32
|
-
copyable = true,
|
|
33
|
-
style,
|
|
34
|
-
textStyle,
|
|
35
|
-
}: CodeBlockProps) {
|
|
36
|
-
const { Icon, useToast } = getClientHost();
|
|
37
|
-
// Inner snippet scrollers are NEVER the host sheet scroller: the host
|
|
38
|
-
// ScrollView is a sheet-gesture pan controller, so using it horizontally or
|
|
39
|
-
// nested inside the host sheet collapses to height 0 / crashes the gesture
|
|
40
|
-
// handler (#219). Plain React Native ScrollViews own this bounded box.
|
|
41
|
-
const { colors, resolveRadius, isCompact, touchTargetMin, alpha } = usePluginTheme();
|
|
42
|
-
const toast = useToast();
|
|
43
|
-
const [copied, setCopied] = useState(false);
|
|
44
|
-
|
|
45
|
-
const radius = resolveRadius("md");
|
|
46
|
-
|
|
47
|
-
const handleCopy = async () => {
|
|
48
|
-
const ok = await copyToClipboard(code, {
|
|
49
|
-
toast,
|
|
50
|
-
toastMessage: title || "Code",
|
|
51
|
-
});
|
|
52
|
-
if (ok) {
|
|
53
|
-
setCopied(true);
|
|
54
|
-
setTimeout(() => setCopied(false), 2000);
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const fontFamily = Platform.select({
|
|
59
|
-
ios: "Menlo",
|
|
60
|
-
android: "monospace",
|
|
61
|
-
default: "monospace",
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<View
|
|
66
|
-
style={[
|
|
67
|
-
styles.container,
|
|
68
|
-
{
|
|
69
|
-
backgroundColor: colors.surface0,
|
|
70
|
-
borderColor: colors.border,
|
|
71
|
-
borderRadius: radius,
|
|
72
|
-
},
|
|
73
|
-
style,
|
|
74
|
-
]}
|
|
75
|
-
>
|
|
76
|
-
{(title || language || copyable) && (
|
|
77
|
-
<View
|
|
78
|
-
style={[
|
|
79
|
-
styles.header,
|
|
80
|
-
{
|
|
81
|
-
borderBottomColor: alpha(colors.border, 0.7),
|
|
82
|
-
},
|
|
83
|
-
]}
|
|
84
|
-
>
|
|
85
|
-
<View style={styles.headerLeft}>
|
|
86
|
-
{title ? (
|
|
87
|
-
<Text style={[styles.title, { color: colors.foreground }]}>{title}</Text>
|
|
88
|
-
) : language ? (
|
|
89
|
-
<Text style={[styles.language, { color: colors.foregroundMuted }]}>
|
|
90
|
-
{language.toUpperCase()}
|
|
91
|
-
</Text>
|
|
92
|
-
) : null}
|
|
93
|
-
</View>
|
|
94
|
-
{copyable && (
|
|
95
|
-
<Pressable
|
|
96
|
-
onPress={handleCopy}
|
|
97
|
-
hitSlop={Math.max(0, (touchTargetMin - 28) / 2)}
|
|
98
|
-
style={({ pressed }) => [
|
|
99
|
-
styles.copyButton,
|
|
100
|
-
{
|
|
101
|
-
backgroundColor: pressed ? colors.surface2 : colors.surface1,
|
|
102
|
-
borderColor: colors.border,
|
|
103
|
-
borderRadius: radius - 2,
|
|
104
|
-
},
|
|
105
|
-
]}
|
|
106
|
-
>
|
|
107
|
-
<Icon
|
|
108
|
-
name={copied ? "Check" : "Copy"}
|
|
109
|
-
size={12}
|
|
110
|
-
color={copied ? colors.statusSuccess : colors.foregroundMuted}
|
|
111
|
-
/>
|
|
112
|
-
<Text
|
|
113
|
-
style={[
|
|
114
|
-
styles.copyText,
|
|
115
|
-
{ color: copied ? colors.statusSuccess : colors.foregroundMuted },
|
|
116
|
-
]}
|
|
117
|
-
>
|
|
118
|
-
{copied ? "Copied!" : "Copy"}
|
|
119
|
-
</Text>
|
|
120
|
-
</Pressable>
|
|
121
|
-
)}
|
|
122
|
-
</View>
|
|
123
|
-
)}
|
|
124
|
-
|
|
125
|
-
<FallbackScrollView
|
|
126
|
-
nestedScrollEnabled
|
|
127
|
-
style={{ maxHeight }}
|
|
128
|
-
contentContainerStyle={styles.scrollContent}
|
|
129
|
-
>
|
|
130
|
-
<FallbackScrollView horizontal showsHorizontalScrollIndicator>
|
|
131
|
-
<Text
|
|
132
|
-
selectable
|
|
133
|
-
style={[
|
|
134
|
-
styles.codeText,
|
|
135
|
-
{
|
|
136
|
-
color: colors.foreground,
|
|
137
|
-
fontFamily,
|
|
138
|
-
fontSize: isCompact ? 11 : 12,
|
|
139
|
-
},
|
|
140
|
-
textStyle,
|
|
141
|
-
]}
|
|
142
|
-
>
|
|
143
|
-
{code}
|
|
144
|
-
</Text>
|
|
145
|
-
</FallbackScrollView>
|
|
146
|
-
</FallbackScrollView>
|
|
147
|
-
</View>
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const styles = StyleSheet.create({
|
|
152
|
-
container: {
|
|
153
|
-
borderWidth: 1,
|
|
154
|
-
overflow: "hidden",
|
|
155
|
-
},
|
|
156
|
-
header: {
|
|
157
|
-
flexDirection: "row",
|
|
158
|
-
alignItems: "center",
|
|
159
|
-
justifyContent: "space-between",
|
|
160
|
-
paddingHorizontal: 10,
|
|
161
|
-
paddingVertical: 6,
|
|
162
|
-
borderBottomWidth: 1,
|
|
163
|
-
},
|
|
164
|
-
headerLeft: {
|
|
165
|
-
flexDirection: "row",
|
|
166
|
-
alignItems: "center",
|
|
167
|
-
gap: 8,
|
|
168
|
-
},
|
|
169
|
-
title: {
|
|
170
|
-
fontSize: 12,
|
|
171
|
-
fontWeight: "600",
|
|
172
|
-
},
|
|
173
|
-
language: {
|
|
174
|
-
fontSize: 10,
|
|
175
|
-
fontWeight: "700",
|
|
176
|
-
letterSpacing: 0.5,
|
|
177
|
-
},
|
|
178
|
-
copyButton: {
|
|
179
|
-
flexDirection: "row",
|
|
180
|
-
alignItems: "center",
|
|
181
|
-
gap: 4,
|
|
182
|
-
paddingHorizontal: 8,
|
|
183
|
-
paddingVertical: 3,
|
|
184
|
-
borderWidth: 1,
|
|
185
|
-
},
|
|
186
|
-
copyText: {
|
|
187
|
-
fontSize: 11,
|
|
188
|
-
fontWeight: "500",
|
|
189
|
-
},
|
|
190
|
-
scrollContent: {
|
|
191
|
-
padding: 10,
|
|
192
|
-
},
|
|
193
|
-
codeText: {
|
|
194
|
-
lineHeight: 18,
|
|
195
|
-
},
|
|
196
|
-
});
|