@xpufx/paseo-top 0.4.3 → 0.4.5
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 +17 -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 +124 -7
- package/client/pill.tsx +724 -669
- package/client/resources-query.ts +1 -1
- package/client/settings-events.ts +33 -0
- package/client/settings-ui.tsx +86 -9
- package/client/surface.tsx +310 -274
- 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} +21 -61
- 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} +118 -338
- 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/lifecycle/surface.tsx +140 -0
- 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} +55 -47
- 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 -227
- package/package.json +14 -2
- 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 +4 -0
- package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
- package/server/vendor/paseo-plugin-helper/mcp-injection.ts +20 -2
- package/server/vendor/paseo-plugin-helper/plugins.ts +184 -19
- 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 +1232 -0
- package/server/vendor/permission-audit/index.ts +1 -0
- package/server/vendor/permission-audit/server.ts +839 -0
- package/shared/helper-version.ts +20 -0
- package/shared/multi-host.ts +156 -155
- package/shared/resources.ts +146 -1
- 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 +2 -0
- package/shared/vendor/paseo-plugin-helper/tickets.ts +1104 -0
- 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 -156
- package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -184
- package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -225
- 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 -75
- 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 -118
- package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -80
- package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -215
- 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 -25
- package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -224
- 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 -29
- 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 -378
- 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/surface.tsx +0 -80
- 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/{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,172 +0,0 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Platform,
|
|
4
|
-
Pressable,
|
|
5
|
-
StyleSheet,
|
|
6
|
-
Text,
|
|
7
|
-
View,
|
|
8
|
-
type StyleProp,
|
|
9
|
-
type TextStyle,
|
|
10
|
-
type ViewStyle,
|
|
11
|
-
} from "react-native";
|
|
12
|
-
import { getClientHost } from "../host";
|
|
13
|
-
import { usePluginTheme } from "../theme/provider";
|
|
14
|
-
import { copyToClipboard } from "../utils/clipboard";
|
|
15
|
-
|
|
16
|
-
export interface CommandBoxProps {
|
|
17
|
-
/** argv array — program is rendered bold, args muted */
|
|
18
|
-
argv?: string[];
|
|
19
|
-
/** Optional override to display a pre-joined string instead of argv */
|
|
20
|
-
command?: string;
|
|
21
|
-
style?: StyleProp<ViewStyle>;
|
|
22
|
-
textStyle?: StyleProp<TextStyle>;
|
|
23
|
-
/** Accessibility label override for the copy button */
|
|
24
|
-
copyLabel?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function formatCommandLine(argv: string[]): string {
|
|
28
|
-
return argv.map((arg) => (arg.includes(" ") ? JSON.stringify(arg) : arg)).join(" ");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function CommandBox({
|
|
32
|
-
argv = [],
|
|
33
|
-
command,
|
|
34
|
-
style,
|
|
35
|
-
textStyle,
|
|
36
|
-
copyLabel = "Copy command",
|
|
37
|
-
}: CommandBoxProps): React.ReactElement | null {
|
|
38
|
-
const { colors, resolveRadius } = usePluginTheme();
|
|
39
|
-
const { Icon, useToast } = getClientHost();
|
|
40
|
-
const toast = useToast();
|
|
41
|
-
const [copied, setCopied] = useState(false);
|
|
42
|
-
|
|
43
|
-
const fullCommand = command ?? formatCommandLine(argv);
|
|
44
|
-
const [prog, ...rest] = argv;
|
|
45
|
-
|
|
46
|
-
const handleCopy = async () => {
|
|
47
|
-
if (!fullCommand) return;
|
|
48
|
-
const ok = await copyToClipboard(fullCommand, {
|
|
49
|
-
toast,
|
|
50
|
-
toastMessage: "Command",
|
|
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
|
-
const radius = resolveRadius("sm");
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<View
|
|
68
|
-
style={[
|
|
69
|
-
styles.container,
|
|
70
|
-
{
|
|
71
|
-
backgroundColor: colors.surface2,
|
|
72
|
-
borderColor: colors.border,
|
|
73
|
-
borderRadius: radius,
|
|
74
|
-
},
|
|
75
|
-
style,
|
|
76
|
-
]}
|
|
77
|
-
>
|
|
78
|
-
<View style={styles.textContainer}>
|
|
79
|
-
<Text
|
|
80
|
-
style={[
|
|
81
|
-
styles.prompt,
|
|
82
|
-
{
|
|
83
|
-
color: colors.statusWarning,
|
|
84
|
-
fontFamily,
|
|
85
|
-
},
|
|
86
|
-
]}
|
|
87
|
-
>
|
|
88
|
-
$
|
|
89
|
-
</Text>
|
|
90
|
-
<Text
|
|
91
|
-
selectable
|
|
92
|
-
numberOfLines={2}
|
|
93
|
-
style={[
|
|
94
|
-
styles.commandText,
|
|
95
|
-
{
|
|
96
|
-
color: colors.foreground,
|
|
97
|
-
fontFamily,
|
|
98
|
-
},
|
|
99
|
-
textStyle,
|
|
100
|
-
]}
|
|
101
|
-
>
|
|
102
|
-
{prog ? (
|
|
103
|
-
<>
|
|
104
|
-
<Text style={styles.bold}>{prog}</Text>
|
|
105
|
-
{rest.length > 0 ? (
|
|
106
|
-
<Text style={{ color: colors.foregroundMuted }}>
|
|
107
|
-
{" " + rest.map((a) => (a.includes(" ") ? JSON.stringify(a) : a)).join(" ")}
|
|
108
|
-
</Text>
|
|
109
|
-
) : null}
|
|
110
|
-
</>
|
|
111
|
-
) : (
|
|
112
|
-
command ?? ""
|
|
113
|
-
)}
|
|
114
|
-
</Text>
|
|
115
|
-
</View>
|
|
116
|
-
<Pressable
|
|
117
|
-
onPress={handleCopy}
|
|
118
|
-
accessibilityRole="button"
|
|
119
|
-
accessibilityLabel={copyLabel}
|
|
120
|
-
hitSlop={8}
|
|
121
|
-
style={({ pressed }) => [
|
|
122
|
-
styles.copyButton,
|
|
123
|
-
{
|
|
124
|
-
backgroundColor: pressed ? colors.surface1 : "transparent",
|
|
125
|
-
borderRadius: Math.max(2, radius - 2),
|
|
126
|
-
},
|
|
127
|
-
]}
|
|
128
|
-
>
|
|
129
|
-
<Icon
|
|
130
|
-
name={copied ? "Check" : "Copy"}
|
|
131
|
-
size={13}
|
|
132
|
-
color={copied ? colors.statusSuccess : colors.foregroundMuted}
|
|
133
|
-
/>
|
|
134
|
-
</Pressable>
|
|
135
|
-
</View>
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const styles = StyleSheet.create({
|
|
140
|
-
container: {
|
|
141
|
-
flexDirection: "row",
|
|
142
|
-
alignItems: "center",
|
|
143
|
-
justifyContent: "space-between",
|
|
144
|
-
borderWidth: 1,
|
|
145
|
-
paddingHorizontal: 10,
|
|
146
|
-
paddingVertical: 6,
|
|
147
|
-
gap: 8,
|
|
148
|
-
},
|
|
149
|
-
textContainer: {
|
|
150
|
-
flexDirection: "row",
|
|
151
|
-
alignItems: "center",
|
|
152
|
-
flex: 1,
|
|
153
|
-
overflow: "hidden",
|
|
154
|
-
gap: 6,
|
|
155
|
-
},
|
|
156
|
-
prompt: {
|
|
157
|
-
fontWeight: "700",
|
|
158
|
-
fontSize: 12,
|
|
159
|
-
},
|
|
160
|
-
commandText: {
|
|
161
|
-
fontSize: 12,
|
|
162
|
-
flex: 1,
|
|
163
|
-
},
|
|
164
|
-
bold: {
|
|
165
|
-
fontWeight: "700",
|
|
166
|
-
},
|
|
167
|
-
copyButton: {
|
|
168
|
-
padding: 4,
|
|
169
|
-
alignItems: "center",
|
|
170
|
-
justifyContent: "center",
|
|
171
|
-
},
|
|
172
|
-
});
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Pressable,
|
|
4
|
-
StyleSheet,
|
|
5
|
-
Text,
|
|
6
|
-
type StyleProp,
|
|
7
|
-
type TextStyle,
|
|
8
|
-
type ViewStyle,
|
|
9
|
-
} from "react-native";
|
|
10
|
-
import { getClientHost } from "../host";
|
|
11
|
-
import { usePluginTheme } from "../theme/provider";
|
|
12
|
-
import { copyToClipboard } from "../utils/clipboard";
|
|
13
|
-
|
|
14
|
-
export type CopyButtonSize = "sm" | "md";
|
|
15
|
-
export type CopyButtonVariant = "ghost" | "secondary";
|
|
16
|
-
|
|
17
|
-
export interface CopyButtonFeedback {
|
|
18
|
-
icon: string;
|
|
19
|
-
label: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Pure idle/copied visual state, split out so the feedback contract is
|
|
24
|
-
* testable without a renderer or a clipboard. Callers may override the idle
|
|
25
|
-
* `icon`/`label`; the copied state is always the Check/"Copied!" affordance.
|
|
26
|
-
*/
|
|
27
|
-
export function resolveCopyButtonFeedback(
|
|
28
|
-
copied: boolean,
|
|
29
|
-
options: { icon?: string; label?: string; copiedLabel?: string } = {},
|
|
30
|
-
): CopyButtonFeedback {
|
|
31
|
-
const idleIcon = options.icon ?? "Copy";
|
|
32
|
-
const idleLabel = options.label ?? "Copy";
|
|
33
|
-
const copiedLabel = options.copiedLabel ?? "Copied!";
|
|
34
|
-
return copied ? { icon: "Check", label: copiedLabel } : { icon: idleIcon, label: idleLabel };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface CopyButtonProps {
|
|
38
|
-
/** Literal text to copy. Ignored when `getText` is provided. */
|
|
39
|
-
text?: string;
|
|
40
|
-
/** Lazy/Promise text source, resolved at press time. Takes precedence over `text`. */
|
|
41
|
-
getText?: () => string | Promise<string>;
|
|
42
|
-
/** Idle button label. Defaults to "Copy". */
|
|
43
|
-
label?: string;
|
|
44
|
-
/** Label shown after a successful copy. Defaults to "Copied!". */
|
|
45
|
-
copiedLabel?: string;
|
|
46
|
-
/** Icon name for the idle state. Defaults to "Copy". */
|
|
47
|
-
icon?: string;
|
|
48
|
-
size?: CopyButtonSize;
|
|
49
|
-
variant?: CopyButtonVariant;
|
|
50
|
-
accessibilityLabel?: string;
|
|
51
|
-
/** Forwarded to the clipboard toast (e.g. "timeline card"). */
|
|
52
|
-
toastMessage?: string;
|
|
53
|
-
/** How long the Check/"Copied!" feedback stays up. Defaults to 2000ms. */
|
|
54
|
-
feedbackDurationMs?: number;
|
|
55
|
-
disabled?: boolean;
|
|
56
|
-
style?: StyleProp<ViewStyle>;
|
|
57
|
-
textStyle?: StyleProp<TextStyle>;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Explicit copy affordance for styled/plugin surfaces.
|
|
62
|
-
*
|
|
63
|
-
* Web's selection-copy handler only rebuilds clipboard content for
|
|
64
|
-
* `[data-testid="assistant-message"]` selections, so styled timeline, telemetry,
|
|
65
|
-
* and panel content copies nothing (xpufx-org/paseo#278). This bypasses that
|
|
66
|
-
* gate with the helper's host `copyText` / `copyToClipboard` path and shows
|
|
67
|
-
* Check/"Copied!" feedback, matching CodeBlock's pattern.
|
|
68
|
-
*/
|
|
69
|
-
export function CopyButton({
|
|
70
|
-
text,
|
|
71
|
-
getText,
|
|
72
|
-
label,
|
|
73
|
-
copiedLabel,
|
|
74
|
-
icon,
|
|
75
|
-
size = "sm",
|
|
76
|
-
variant = "ghost",
|
|
77
|
-
accessibilityLabel,
|
|
78
|
-
toastMessage,
|
|
79
|
-
feedbackDurationMs = 2000,
|
|
80
|
-
disabled = false,
|
|
81
|
-
style,
|
|
82
|
-
textStyle,
|
|
83
|
-
}: CopyButtonProps): React.ReactElement | null {
|
|
84
|
-
const { Icon, useToast } = getClientHost();
|
|
85
|
-
const { colors, resolveRadius, touchTargetMin, alpha } = usePluginTheme();
|
|
86
|
-
const toast = useToast();
|
|
87
|
-
const [copied, setCopied] = useState(false);
|
|
88
|
-
const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
89
|
-
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
return () => {
|
|
92
|
-
if (timer.current) clearTimeout(timer.current);
|
|
93
|
-
};
|
|
94
|
-
}, []);
|
|
95
|
-
|
|
96
|
-
const hasSource = getText !== undefined || text !== undefined;
|
|
97
|
-
if (!hasSource) return null;
|
|
98
|
-
|
|
99
|
-
const feedback = resolveCopyButtonFeedback(copied, { icon, label, copiedLabel });
|
|
100
|
-
const iconNode = (
|
|
101
|
-
<Icon
|
|
102
|
-
name={feedback.icon}
|
|
103
|
-
size={size === "sm" ? 12 : 14}
|
|
104
|
-
color={copied ? colors.statusSuccess : colors.foregroundMuted}
|
|
105
|
-
/>
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
const secondary = variant === "secondary";
|
|
109
|
-
const bg = secondary ? colors.surface1 : "transparent";
|
|
110
|
-
const border = secondary ? colors.border : "transparent";
|
|
111
|
-
const radius = resolveRadius(size === "sm" ? "sm" : "md");
|
|
112
|
-
|
|
113
|
-
const handleCopy = async () => {
|
|
114
|
-
if (disabled) return;
|
|
115
|
-
let value: string | undefined;
|
|
116
|
-
try {
|
|
117
|
-
value = getText ? await getText() : text;
|
|
118
|
-
} catch {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
if (value === undefined || value === null) return;
|
|
122
|
-
|
|
123
|
-
const ok = await copyToClipboard(String(value), { toast, toastMessage });
|
|
124
|
-
if (!ok) return;
|
|
125
|
-
|
|
126
|
-
setCopied(true);
|
|
127
|
-
if (timer.current) clearTimeout(timer.current);
|
|
128
|
-
timer.current = setTimeout(() => setCopied(false), feedbackDurationMs);
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
<Pressable
|
|
133
|
-
onPress={handleCopy}
|
|
134
|
-
disabled={disabled}
|
|
135
|
-
accessibilityRole="button"
|
|
136
|
-
accessibilityLabel={accessibilityLabel || label || "Copy"}
|
|
137
|
-
hitSlop={Math.max(0, (touchTargetMin - 28) / 2)}
|
|
138
|
-
style={({ pressed }) => [
|
|
139
|
-
styles.base,
|
|
140
|
-
{
|
|
141
|
-
backgroundColor: pressed && !disabled ? alpha(colors.surface2, secondary ? 1 : 0.7) : bg,
|
|
142
|
-
borderColor: border,
|
|
143
|
-
borderWidth: border !== "transparent" ? 1 : 0,
|
|
144
|
-
borderRadius: radius,
|
|
145
|
-
paddingVertical: size === "sm" ? 3 : 5,
|
|
146
|
-
paddingHorizontal: size === "sm" ? 8 : 10,
|
|
147
|
-
opacity: disabled ? 0.45 : 1,
|
|
148
|
-
},
|
|
149
|
-
style,
|
|
150
|
-
]}
|
|
151
|
-
>
|
|
152
|
-
{iconNode}
|
|
153
|
-
{feedback.label !== "" ? (
|
|
154
|
-
<Text
|
|
155
|
-
style={[
|
|
156
|
-
styles.text,
|
|
157
|
-
{
|
|
158
|
-
fontSize: size === "sm" ? 11 : 12,
|
|
159
|
-
color: copied ? colors.statusSuccess : colors.foregroundMuted,
|
|
160
|
-
},
|
|
161
|
-
textStyle,
|
|
162
|
-
]}
|
|
163
|
-
>
|
|
164
|
-
{feedback.label}
|
|
165
|
-
</Text>
|
|
166
|
-
) : null}
|
|
167
|
-
</Pressable>
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const styles = StyleSheet.create({
|
|
172
|
-
base: {
|
|
173
|
-
flexDirection: "row",
|
|
174
|
-
alignItems: "center",
|
|
175
|
-
gap: 4,
|
|
176
|
-
},
|
|
177
|
-
text: {
|
|
178
|
-
fontWeight: "500",
|
|
179
|
-
},
|
|
180
|
-
});
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import React, { type ReactNode } from "react";
|
|
2
|
-
import { StyleSheet, Text, View, type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
-
import { getClientHost } from "../host";
|
|
4
|
-
import { usePluginTheme } from "../theme/provider";
|
|
5
|
-
import { Button, type ButtonProps } from "./Button";
|
|
6
|
-
|
|
7
|
-
export interface EmptyStateProps {
|
|
8
|
-
icon?: string | ReactNode;
|
|
9
|
-
title: string;
|
|
10
|
-
description?: string;
|
|
11
|
-
action?: ButtonProps;
|
|
12
|
-
actionLabel?: string;
|
|
13
|
-
onAction?: () => void;
|
|
14
|
-
style?: StyleProp<ViewStyle>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function EmptyState({
|
|
18
|
-
icon = "Inbox",
|
|
19
|
-
title,
|
|
20
|
-
description,
|
|
21
|
-
action,
|
|
22
|
-
actionLabel,
|
|
23
|
-
onAction,
|
|
24
|
-
style,
|
|
25
|
-
}: EmptyStateProps) {
|
|
26
|
-
const { Icon } = getClientHost();
|
|
27
|
-
const { colors, isCompact, typography, padding } = usePluginTheme();
|
|
28
|
-
|
|
29
|
-
const resolvedAction: ButtonProps | undefined = action
|
|
30
|
-
? action
|
|
31
|
-
: actionLabel && onAction
|
|
32
|
-
? { label: actionLabel, onPress: onAction, variant: "secondary" }
|
|
33
|
-
: undefined;
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<View style={[styles.container, { padding: isCompact ? padding.horizontal + 8 : padding.horizontal * 2 }, style]}>
|
|
37
|
-
{icon ? (
|
|
38
|
-
typeof icon === "string" ? (
|
|
39
|
-
<View style={[styles.iconWrapper, { backgroundColor: colors.surface1 }]}>
|
|
40
|
-
<Icon name={icon} size={isCompact ? 24 : 32} color={colors.foregroundMuted} />
|
|
41
|
-
</View>
|
|
42
|
-
) : (
|
|
43
|
-
icon
|
|
44
|
-
)
|
|
45
|
-
) : null}
|
|
46
|
-
|
|
47
|
-
<Text style={[styles.title, { color: colors.foreground, ...typography.title }]}>
|
|
48
|
-
{title}
|
|
49
|
-
</Text>
|
|
50
|
-
|
|
51
|
-
{description ? (
|
|
52
|
-
<Text
|
|
53
|
-
style={[
|
|
54
|
-
styles.description,
|
|
55
|
-
{ color: colors.foregroundMuted, ...typography.body },
|
|
56
|
-
]}
|
|
57
|
-
>
|
|
58
|
-
{description}
|
|
59
|
-
</Text>
|
|
60
|
-
) : null}
|
|
61
|
-
|
|
62
|
-
{resolvedAction ? (
|
|
63
|
-
<View style={styles.actionRow}>
|
|
64
|
-
<Button size={isCompact ? "sm" : "md"} {...resolvedAction} />
|
|
65
|
-
</View>
|
|
66
|
-
) : null}
|
|
67
|
-
</View>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const styles = StyleSheet.create({
|
|
72
|
-
container: {
|
|
73
|
-
alignItems: "center",
|
|
74
|
-
justifyContent: "center",
|
|
75
|
-
gap: 8,
|
|
76
|
-
},
|
|
77
|
-
iconWrapper: {
|
|
78
|
-
width: 56,
|
|
79
|
-
height: 56,
|
|
80
|
-
borderRadius: 28,
|
|
81
|
-
alignItems: "center",
|
|
82
|
-
justifyContent: "center",
|
|
83
|
-
marginBottom: 4,
|
|
84
|
-
},
|
|
85
|
-
title: {
|
|
86
|
-
fontWeight: "600",
|
|
87
|
-
textAlign: "center",
|
|
88
|
-
},
|
|
89
|
-
description: {
|
|
90
|
-
textAlign: "center",
|
|
91
|
-
maxWidth: 280,
|
|
92
|
-
lineHeight: 18,
|
|
93
|
-
},
|
|
94
|
-
actionRow: {
|
|
95
|
-
marginTop: 8,
|
|
96
|
-
},
|
|
97
|
-
});
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import React, { useMemo } from "react";
|
|
2
|
-
import { Text, type StyleProp, type TextStyle } from "react-native";
|
|
3
|
-
import { splitHighlightParts } from "../../../../shared/vendor/paseo-plugin-helper/highlight";
|
|
4
|
-
import { usePluginTheme } from "../theme/provider";
|
|
5
|
-
|
|
6
|
-
export interface HighlightedTextProps {
|
|
7
|
-
/** Source text rendered as-is when no query is active. */
|
|
8
|
-
text: string;
|
|
9
|
-
/**
|
|
10
|
-
* Active search query; matched case-insensitively and never as a regex. When
|
|
11
|
-
* the literal query is absent the query's tokens are highlighted, so a fuzzy
|
|
12
|
-
* result still reads.
|
|
13
|
-
*/
|
|
14
|
-
query: string;
|
|
15
|
-
style?: StyleProp<TextStyle>;
|
|
16
|
-
/**
|
|
17
|
-
* Overrides the matched-run style. Defaults to the theme accent background
|
|
18
|
-
* with `accentForeground` text so a match reads as selected.
|
|
19
|
-
*/
|
|
20
|
-
highlightStyle?: StyleProp<TextStyle>;
|
|
21
|
-
numberOfLines?: number;
|
|
22
|
-
selectable?: boolean;
|
|
23
|
-
/**
|
|
24
|
-
* When true, a query with no literal or token hit marks the whole text, so a
|
|
25
|
-
* fuzzy search result still reads as matched. Use it on the primary label
|
|
26
|
-
* (row/detail title) rather than every small field.
|
|
27
|
-
*/
|
|
28
|
-
fuzzyFallback?: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* `<Text>` that paints every case-insensitive occurrence of `query` with the
|
|
33
|
-
* accent background/foreground. The query is matched literally, so user input
|
|
34
|
-
* is never evaluated as a regular expression. When the literal query is absent
|
|
35
|
-
* the splitter highlights the query's tokens; `fuzzyFallback` additionally
|
|
36
|
-
* marks the whole text when even a token misses, so a fuzzy result is not left
|
|
37
|
-
* silently unmarked. Renders the plain text when the query is empty or absent.
|
|
38
|
-
*/
|
|
39
|
-
export function HighlightedText({
|
|
40
|
-
text,
|
|
41
|
-
query,
|
|
42
|
-
style,
|
|
43
|
-
highlightStyle,
|
|
44
|
-
numberOfLines,
|
|
45
|
-
selectable,
|
|
46
|
-
fuzzyFallback = false,
|
|
47
|
-
}: HighlightedTextProps) {
|
|
48
|
-
const { colors } = usePluginTheme();
|
|
49
|
-
const parts = useMemo(
|
|
50
|
-
() => splitHighlightParts(text, query, { fallbackToWholeField: fuzzyFallback }),
|
|
51
|
-
[text, query, fuzzyFallback],
|
|
52
|
-
);
|
|
53
|
-
const matchStyle: StyleProp<TextStyle> = [
|
|
54
|
-
{ backgroundColor: colors.accent, color: colors.accentForeground },
|
|
55
|
-
highlightStyle,
|
|
56
|
-
];
|
|
57
|
-
return (
|
|
58
|
-
<Text style={style} numberOfLines={numberOfLines} selectable={selectable}>
|
|
59
|
-
{parts.map((part, index) =>
|
|
60
|
-
part.matched ? (
|
|
61
|
-
<Text key={`m${index}`} style={matchStyle}>
|
|
62
|
-
{part.text}
|
|
63
|
-
</Text>
|
|
64
|
-
) : (
|
|
65
|
-
part.text
|
|
66
|
-
),
|
|
67
|
-
)}
|
|
68
|
-
</Text>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import React, { type ReactNode } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Pressable,
|
|
4
|
-
StyleSheet,
|
|
5
|
-
Text,
|
|
6
|
-
type StyleProp,
|
|
7
|
-
type TextStyle,
|
|
8
|
-
type ViewStyle,
|
|
9
|
-
} from "react-native";
|
|
10
|
-
import { getClientHost } from "../host";
|
|
11
|
-
import { usePluginTheme } from "../theme/provider";
|
|
12
|
-
|
|
13
|
-
export interface InlineButtonProps {
|
|
14
|
-
label: string;
|
|
15
|
-
onPress?: () => void | Promise<void>;
|
|
16
|
-
icon?: string | ReactNode;
|
|
17
|
-
disabled?: boolean;
|
|
18
|
-
accessibilityLabel?: string;
|
|
19
|
-
accessibilityRole?: "button" | "link";
|
|
20
|
-
style?: StyleProp<ViewStyle>;
|
|
21
|
-
textStyle?: StyleProp<TextStyle>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Compact text/link action for inline cards and timeline content. */
|
|
25
|
-
export function InlineButton({
|
|
26
|
-
label,
|
|
27
|
-
onPress,
|
|
28
|
-
icon,
|
|
29
|
-
disabled = false,
|
|
30
|
-
accessibilityLabel,
|
|
31
|
-
accessibilityRole = "button",
|
|
32
|
-
style,
|
|
33
|
-
textStyle,
|
|
34
|
-
}: InlineButtonProps) {
|
|
35
|
-
const { Icon } = getClientHost();
|
|
36
|
-
const { colors, touchTargetMin, alpha } = usePluginTheme();
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<Pressable
|
|
40
|
-
onPress={onPress}
|
|
41
|
-
disabled={disabled}
|
|
42
|
-
accessibilityRole={accessibilityRole}
|
|
43
|
-
accessibilityLabel={accessibilityLabel || label}
|
|
44
|
-
hitSlop={Math.max(0, (touchTargetMin - 24) / 2)}
|
|
45
|
-
style={({ pressed }) => [
|
|
46
|
-
styles.base,
|
|
47
|
-
{
|
|
48
|
-
opacity: disabled ? 0.45 : 1,
|
|
49
|
-
backgroundColor: pressed && !disabled ? alpha(colors.accent, 0.12) : "transparent",
|
|
50
|
-
},
|
|
51
|
-
style,
|
|
52
|
-
]}
|
|
53
|
-
>
|
|
54
|
-
{typeof icon === "string" ? <Icon name={icon} size={13} color={colors.accent} /> : icon}
|
|
55
|
-
<Text style={[styles.text, { color: colors.accent }, textStyle]}>{label}</Text>
|
|
56
|
-
</Pressable>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const styles = StyleSheet.create({
|
|
61
|
-
base: {
|
|
62
|
-
alignSelf: "flex-start",
|
|
63
|
-
flexDirection: "row",
|
|
64
|
-
alignItems: "center",
|
|
65
|
-
gap: 4,
|
|
66
|
-
minHeight: 24,
|
|
67
|
-
paddingHorizontal: 3,
|
|
68
|
-
paddingVertical: 2,
|
|
69
|
-
borderRadius: 4,
|
|
70
|
-
},
|
|
71
|
-
text: {
|
|
72
|
-
fontSize: 12,
|
|
73
|
-
fontWeight: "600",
|
|
74
|
-
},
|
|
75
|
-
});
|