@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.
Files changed (130) hide show
  1. package/OVERVIEW.md +17 -0
  2. package/client/host-ui.tsx +184 -0
  3. package/client/multi-host-view.tsx +57 -43
  4. package/client/multi-host.ts +36 -13
  5. package/client/pill-labels.ts +124 -7
  6. package/client/pill.tsx +724 -669
  7. package/client/resources-query.ts +1 -1
  8. package/client/settings-events.ts +33 -0
  9. package/client/settings-ui.tsx +86 -9
  10. package/client/surface.tsx +310 -274
  11. package/client/telemetry-copy.ts +8 -2
  12. package/client/telemetry.tsx +372 -321
  13. package/client/turn-panel.tsx +126 -104
  14. package/client/vendor/paseo-plugin-helper/{host.ts → core/host.ts} +21 -61
  15. package/client/vendor/paseo-plugin-helper/core/index.ts +76 -0
  16. package/client/vendor/paseo-plugin-helper/core/plugin-load-count.ts +89 -0
  17. package/client/vendor/paseo-plugin-helper/{query-refresh.ts → core/query-refresh.ts} +2 -2
  18. package/client/vendor/paseo-plugin-helper/{query.ts → core/query.ts} +2 -2
  19. package/client/vendor/paseo-plugin-helper/{settings.ts → core/settings.ts} +1 -1
  20. package/client/vendor/paseo-plugin-helper/{shared-settings.ts → core/shared-settings.ts} +2 -2
  21. package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/clipboard.ts +2 -2
  22. package/client/vendor/paseo-plugin-helper/lifecycle/flair.ts +26 -0
  23. package/client/vendor/paseo-plugin-helper/{theme/color-utils.ts → lifecycle/host-color.ts} +95 -38
  24. package/client/vendor/paseo-plugin-helper/lifecycle/host-theme.tsx +109 -0
  25. package/client/vendor/paseo-plugin-helper/lifecycle/index.ts +23 -0
  26. package/client/vendor/paseo-plugin-helper/lifecycle/multi-host.ts +66 -0
  27. package/client/vendor/paseo-plugin-helper/{panel.tsx → lifecycle/panel.tsx} +7 -7
  28. package/client/vendor/paseo-plugin-helper/{pill.tsx → lifecycle/pill.tsx} +118 -338
  29. package/client/vendor/paseo-plugin-helper/lifecycle/providers.tsx +46 -0
  30. package/client/vendor/paseo-plugin-helper/lifecycle/scroll-owner.ts +26 -0
  31. package/client/vendor/paseo-plugin-helper/lifecycle/surface.tsx +140 -0
  32. package/client/vendor/paseo-plugin-helper/ui/color.ts +1 -0
  33. package/client/vendor/paseo-plugin-helper/ui/content.tsx +1326 -0
  34. package/client/vendor/paseo-plugin-helper/ui/controls.tsx +1018 -0
  35. package/client/vendor/paseo-plugin-helper/{components/DataTable.tsx → ui/data.tsx} +55 -47
  36. package/client/vendor/paseo-plugin-helper/ui/index.ts +44 -0
  37. package/client/vendor/paseo-plugin-helper/ui/layout.tsx +263 -0
  38. package/client/vendor/paseo-plugin-helper/ui/modal.tsx +111 -0
  39. package/client/vendor/paseo-plugin-helper/ui/plugin-reload-banner.tsx +119 -0
  40. package/client/vendor/paseo-plugin-helper/{settings-screen.tsx → ui/settings-screen.tsx} +3 -3
  41. package/client/vendor/paseo-plugin-helper/ui/settings.ts +36 -0
  42. package/client/vendor/paseo-plugin-helper/ui/text.tsx +821 -0
  43. package/client/vendor/paseo-plugin-helper/ui/theme.tsx +1 -0
  44. package/client/vendor/permission-audit/client.tsx +706 -0
  45. package/client/vendor/permission-audit/index.tsx +1 -0
  46. package/index.client.tsx +58 -37
  47. package/index.server.ts +256 -227
  48. package/package.json +14 -2
  49. package/paseo-plugin.json +2 -0
  50. package/screenshots/1-paseo-top-modal-system.jpg +0 -0
  51. package/screenshots/2-paseo-top-modal-workspace.jpg +0 -0
  52. package/screenshots/3-paseo-top-modal-settings.jpg +0 -0
  53. package/screenshots/4-paseo-top-modal-about.jpg +0 -0
  54. package/screenshots/paseo-top-dashboard.jpg +0 -0
  55. package/screenshots/paseo-top-general-view.jpg +0 -0
  56. package/server/resources.ts +98 -0
  57. package/server/vendor/paseo-plugin-helper/custom-pills.ts +70 -0
  58. package/server/vendor/paseo-plugin-helper/index.ts +4 -0
  59. package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
  60. package/server/vendor/paseo-plugin-helper/mcp-injection.ts +20 -2
  61. package/server/vendor/paseo-plugin-helper/plugins.ts +184 -19
  62. package/server/vendor/paseo-plugin-helper/rpc-guard.ts +2 -2
  63. package/server/vendor/paseo-plugin-helper/storage-guard.ts +294 -0
  64. package/server/vendor/paseo-plugin-helper/storage.ts +36 -2
  65. package/server/vendor/paseo-plugin-helper/telemetry.ts +486 -0
  66. package/server/vendor/paseo-plugin-helper/tickets.ts +1232 -0
  67. package/server/vendor/permission-audit/index.ts +1 -0
  68. package/server/vendor/permission-audit/server.ts +839 -0
  69. package/shared/helper-version.ts +20 -0
  70. package/shared/multi-host.ts +156 -155
  71. package/shared/resources.ts +146 -1
  72. package/shared/vendor/paseo-plugin-helper/async.ts +14 -7
  73. package/shared/vendor/paseo-plugin-helper/features.ts +244 -0
  74. package/shared/vendor/paseo-plugin-helper/forge.ts +27 -0
  75. package/shared/vendor/paseo-plugin-helper/index.ts +2 -0
  76. package/shared/vendor/paseo-plugin-helper/tickets.ts +1104 -0
  77. package/shared/vendor/permission-audit/filter.ts +61 -0
  78. package/shared/vendor/permission-audit/index.ts +2 -0
  79. package/shared/vendor/permission-audit/shared.ts +119 -0
  80. package/shared/version.ts +1 -1
  81. package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +0 -493
  82. package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +0 -250
  83. package/client/vendor/paseo-plugin-helper/components/Badge.tsx +0 -156
  84. package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -184
  85. package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -225
  86. package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +0 -196
  87. package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +0 -277
  88. package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +0 -172
  89. package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +0 -180
  90. package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +0 -97
  91. package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +0 -70
  92. package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +0 -75
  93. package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +0 -446
  94. package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +0 -247
  95. package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +0 -117
  96. package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +0 -53
  97. package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +0 -118
  98. package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -80
  99. package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -215
  100. package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +0 -80
  101. package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +0 -319
  102. package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +0 -150
  103. package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +0 -163
  104. package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +0 -157
  105. package/client/vendor/paseo-plugin-helper/components/index.ts +0 -25
  106. package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -224
  107. package/client/vendor/paseo-plugin-helper/forge-icon.tsx +0 -79
  108. package/client/vendor/paseo-plugin-helper/icon.tsx +0 -39
  109. package/client/vendor/paseo-plugin-helper/index.ts +0 -29
  110. package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +0 -49
  111. package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +0 -103
  112. package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +0 -65
  113. package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +0 -378
  114. package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +0 -51
  115. package/client/vendor/paseo-plugin-helper/layout/Row.tsx +0 -39
  116. package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +0 -39
  117. package/client/vendor/paseo-plugin-helper/layout/index.ts +0 -7
  118. package/client/vendor/paseo-plugin-helper/styles/index.ts +0 -1
  119. package/client/vendor/paseo-plugin-helper/styles/recipes.ts +0 -550
  120. package/client/vendor/paseo-plugin-helper/surface.tsx +0 -80
  121. package/client/vendor/paseo-plugin-helper/theme/flair.ts +0 -76
  122. package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +0 -121
  123. package/client/vendor/paseo-plugin-helper/theme/index.ts +0 -7
  124. package/client/vendor/paseo-plugin-helper/theme/provider.tsx +0 -178
  125. package/client/vendor/paseo-plugin-helper/theme/responsive.ts +0 -213
  126. package/client/vendor/paseo-plugin-helper/theme/tokens.ts +0 -200
  127. package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +0 -57
  128. /package/client/vendor/paseo-plugin-helper/{command-center.ts → core/command-center.ts} +0 -0
  129. /package/client/vendor/paseo-plugin-helper/{snapshot.ts → core/snapshot.ts} +0 -0
  130. /package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/haptics.ts +0 -0
@@ -1,250 +0,0 @@
1
- import React, { useEffect, useRef, type ReactNode } from "react";
2
- import {
3
- Animated,
4
- Easing,
5
- StyleSheet,
6
- View,
7
- type StyleProp,
8
- type ViewStyle,
9
- } from "react-native";
10
- import { getClientHost } from "../host";
11
- import { usePluginTheme } from "../theme/provider";
12
- import { FALLBACK_ACCENT_FOREGROUND } from "../theme/tokens";
13
- import type { ThemeColors } from "../../../../shared/vendor/paseo-plugin-helper/types";
14
-
15
- export type AttentionBeaconMode = "radar" | "ring" | "glow" | "badge" | "bounce" | "pulse";
16
- export type AttentionBeaconTone = "warning" | "accent" | "danger";
17
-
18
- export interface AttentionBeaconProps {
19
- children: ReactNode;
20
- mode?: AttentionBeaconMode;
21
- tone?: AttentionBeaconTone;
22
- color?: string;
23
- active?: boolean;
24
- style?: StyleProp<ViewStyle>;
25
- haloStyle?: StyleProp<ViewStyle>;
26
- badgeStyle?: StyleProp<ViewStyle>;
27
- accessibilityLabel?: string;
28
- testID?: string;
29
- badgeIcon?: string | ReactNode;
30
- duration?: number;
31
- easing?: (value: number) => number;
32
- }
33
-
34
- export function normalizeBeaconMode(mode?: AttentionBeaconMode): "radar" | "glow" | "badge" | "bounce" | "pulse" {
35
- if (mode === "ring") return "radar";
36
- if (mode === "glow" || mode === "badge" || mode === "bounce" || mode === "pulse" || mode === "radar") return mode;
37
- return "radar";
38
- }
39
-
40
- export function resolveBeaconToneColor(
41
- colors: ThemeColors,
42
- tone: AttentionBeaconTone = "warning",
43
- customColor?: string,
44
- ): string {
45
- if (customColor) return customColor;
46
- if (tone === "danger") return colors.statusDanger;
47
- if (tone === "accent") return colors.accent;
48
- return colors.statusWarning;
49
- }
50
-
51
- function useLoop(driver: Animated.Value, toValue: number, duration: number, active: boolean): void {
52
- useEffect(() => {
53
- if (!active) return;
54
- driver.setValue(0);
55
- const animation = Animated.loop(
56
- Animated.sequence([
57
- Animated.timing(driver, { toValue, duration, easing: Easing.linear, useNativeDriver: true }),
58
- Animated.timing(driver, { toValue: 0, duration: 0, useNativeDriver: true }),
59
- ]),
60
- );
61
- animation.start();
62
- return () => animation.stop();
63
- }, [driver, toValue, duration, active]);
64
- }
65
-
66
- function usePingPong(
67
- driver: Animated.Value,
68
- duration: number,
69
- active: boolean,
70
- easing: (value: number) => number = Easing.linear,
71
- ): void {
72
- useEffect(() => {
73
- if (!active) return;
74
- driver.setValue(0);
75
- const animation = Animated.loop(
76
- Animated.sequence([
77
- Animated.timing(driver, { toValue: 1, duration, easing, useNativeDriver: true }),
78
- Animated.timing(driver, { toValue: 0, duration, easing, useNativeDriver: true }),
79
- ]),
80
- );
81
- animation.start();
82
- return () => animation.stop();
83
- }, [driver, duration, active, easing]);
84
- }
85
-
86
- export function AttentionBeacon({
87
- children,
88
- mode = "radar",
89
- tone = "warning",
90
- color,
91
- active = true,
92
- style,
93
- haloStyle,
94
- badgeStyle,
95
- accessibilityLabel,
96
- testID,
97
- badgeIcon,
98
- duration = 900,
99
- easing = Easing.linear,
100
- }: AttentionBeaconProps) {
101
- const { Icon } = getClientHost();
102
- const { colors } = usePluginTheme();
103
- const resolved = normalizeBeaconMode(mode);
104
- const beaconColor = resolveBeaconToneColor(colors, tone, color);
105
-
106
- const radar = useRef(new Animated.Value(0)).current;
107
- const breath = useRef(new Animated.Value(0)).current;
108
- const pip = useRef(new Animated.Value(0)).current;
109
- const jiggle = useRef(new Animated.Value(0)).current;
110
- const pulse = useRef(new Animated.Value(0)).current;
111
-
112
- const isRadar = resolved === "radar" && active;
113
- const isGlow = resolved === "glow" && active;
114
- const isBadge = resolved === "badge" && active;
115
- const isBounce = resolved === "bounce" && active;
116
- const isPulse = resolved === "pulse" && active;
117
-
118
- useLoop(radar, 1, 1600, isRadar);
119
- usePingPong(breath, 900, isGlow);
120
- usePingPong(pip, 900, isBadge);
121
- usePingPong(jiggle, 350, isBounce);
122
- usePingPong(pulse, duration, isPulse, easing);
123
-
124
- if (!active) {
125
- return <View style={[styles.wrapper, style]}>{children}</View>;
126
- }
127
-
128
- if (resolved === "glow") {
129
- const opacity = breath.interpolate({ inputRange: [0, 1], outputRange: [0.35, 1] });
130
- return (
131
- <View style={[styles.wrapper, style]} accessibilityLabel={accessibilityLabel} testID={testID}>
132
- {children}
133
- <Animated.View
134
- pointerEvents="none"
135
- testID={testID ? `${testID}-glow` : undefined}
136
- style={[styles.glowHalo, { borderColor: beaconColor, opacity }, haloStyle]}
137
- />
138
- </View>
139
- );
140
- }
141
-
142
- if (resolved === "badge") {
143
- const opacity = pip.interpolate({ inputRange: [0, 1], outputRange: [0.5, 1] });
144
- const scale = pip.interpolate({ inputRange: [0, 1], outputRange: [0.85, 1.15] });
145
- const hasIcon = badgeIcon !== undefined && badgeIcon !== null && badgeIcon !== "";
146
- const iconColor = colors.accentForeground || FALLBACK_ACCENT_FOREGROUND;
147
- return (
148
- <View style={[styles.wrapper, style]} accessibilityLabel={accessibilityLabel} testID={testID}>
149
- {children}
150
- <Animated.View
151
- pointerEvents="none"
152
- testID={testID ? `${testID}-badge` : undefined}
153
- style={[
154
- styles.pip,
155
- { backgroundColor: beaconColor, opacity, transform: [{ scale }] },
156
- hasIcon && styles.pipWithIcon,
157
- badgeStyle,
158
- ]}
159
- >
160
- {hasIcon ? (
161
- typeof badgeIcon === "string" ? (
162
- <Icon name={badgeIcon} size={10} color={iconColor} />
163
- ) : (
164
- badgeIcon
165
- )
166
- ) : null}
167
- </Animated.View>
168
- </View>
169
- );
170
- }
171
-
172
- if (resolved === "bounce") {
173
- const translateY = jiggle.interpolate({ inputRange: [0, 1], outputRange: [0, -5] });
174
- return (
175
- <View style={[styles.wrapper, style]} accessibilityLabel={accessibilityLabel} testID={testID}>
176
- <Animated.View style={{ transform: [{ translateY }] }}>{children}</Animated.View>
177
- </View>
178
- );
179
- }
180
-
181
- if (resolved === "pulse") {
182
- const opacity = pulse.interpolate({ inputRange: [0, 1], outputRange: [0.35, 1] });
183
- return (
184
- <View style={[styles.wrapper, style]} accessibilityLabel={accessibilityLabel} testID={testID}>
185
- <Animated.View
186
- testID={testID ? `${testID}-pulse` : undefined}
187
- style={{ opacity }}
188
- >
189
- {children}
190
- </Animated.View>
191
- </View>
192
- );
193
- }
194
-
195
- const scale = radar.interpolate({ inputRange: [0, 1], outputRange: [1, 1.5] });
196
- const opacity = radar.interpolate({ inputRange: [0, 1], outputRange: [0.55, 0] });
197
- return (
198
- <View style={[styles.wrapper, style]} accessibilityLabel={accessibilityLabel} testID={testID}>
199
- <Animated.View
200
- pointerEvents="none"
201
- testID={testID ? `${testID}-halo` : undefined}
202
- style={[
203
- styles.radarHalo,
204
- { borderColor: beaconColor, opacity, transform: [{ scale }] },
205
- haloStyle,
206
- ]}
207
- />
208
- {children}
209
- </View>
210
- );
211
- }
212
-
213
- const styles = StyleSheet.create({
214
- wrapper: {
215
- position: "relative",
216
- },
217
- radarHalo: {
218
- position: "absolute",
219
- top: -4,
220
- left: -4,
221
- right: -4,
222
- bottom: -4,
223
- borderWidth: 2,
224
- borderRadius: 12,
225
- },
226
- glowHalo: {
227
- position: "absolute",
228
- top: -2,
229
- left: -2,
230
- right: -2,
231
- bottom: -2,
232
- borderWidth: 2,
233
- borderRadius: 10,
234
- },
235
- pip: {
236
- position: "absolute",
237
- top: -4,
238
- right: -4,
239
- width: 10,
240
- height: 10,
241
- borderRadius: 5,
242
- },
243
- pipWithIcon: {
244
- width: 16,
245
- height: 16,
246
- borderRadius: 8,
247
- alignItems: "center",
248
- justifyContent: "center",
249
- },
250
- });
@@ -1,156 +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 { FALLBACK_ACCENT_FOREGROUND } from "../theme/tokens";
6
- import { HighlightedText } from "./HighlightedText";
7
- import type { StatusVariant } from "../../../../shared/vendor/paseo-plugin-helper/types";
8
-
9
- export type BadgeStyle = "tinted" | "outline" | "solid";
10
- export type BadgeSize = "sm" | "md";
11
-
12
- export interface BadgeProps {
13
- label: string;
14
- variant?: StatusVariant;
15
- styleVariant?: BadgeStyle;
16
- size?: BadgeSize;
17
- icon?: string | ReactNode;
18
- dot?: boolean;
19
- style?: StyleProp<ViewStyle>;
20
- textStyle?: StyleProp<TextStyle>;
21
- /**
22
- * When set, every case-insensitive occurrence of the query inside `label` is
23
- * painted with the accent highlight. The query is matched literally, never as
24
- * a regular expression.
25
- */
26
- highlightQuery?: string;
27
- /**
28
- * With `highlightQuery`, marks the whole label when the query has neither a
29
- * literal nor a token hit — for a single primary chip, not a chip list.
30
- */
31
- highlightFuzzyFallback?: boolean;
32
- }
33
-
34
- export function Badge({
35
- label,
36
- variant = "neutral",
37
- styleVariant = "tinted",
38
- size = "md",
39
- icon,
40
- dot = false,
41
- style,
42
- textStyle,
43
- highlightQuery,
44
- highlightFuzzyFallback,
45
- }: BadgeProps) {
46
- const { Icon } = getClientHost();
47
- const { colors, flair, resolveRadius, getVariantPalette, getStatusColor, typography } =
48
- usePluginTheme();
49
- const caption = typography?.caption ?? {
50
- fontSize: 11,
51
- lineHeight: 15,
52
- fontWeight: "400" as const,
53
- };
54
-
55
- const fontSize = size === "sm" ? 10 : caption.fontSize;
56
- const lineHeight = size === "sm" ? 12 : caption.lineHeight;
57
- const paddingVertical = size === "sm" ? 1 : Math.max(2, Math.floor(caption.lineHeight / 5));
58
- const paddingHorizontal = size === "sm" ? 5 : caption.fontSize < 11 ? 6 : 8;
59
- const iconSize = size === "sm" ? 10 : caption.fontSize < 11 ? 10 : 11;
60
-
61
- const radius = resolveRadius("pill");
62
- const palette = getVariantPalette(variant);
63
- const solidColor = getStatusColor(variant);
64
-
65
- let bg = palette.bg;
66
- let border = palette.border;
67
- let textColor = palette.text;
68
-
69
- if (styleVariant === "outline") {
70
- bg = "transparent";
71
- border = palette.border;
72
- textColor = palette.text;
73
- } else if (styleVariant === "solid") {
74
- bg = solidColor;
75
- border = "transparent";
76
- textColor = colors.accentForeground || FALLBACK_ACCENT_FOREGROUND;
77
- }
78
-
79
- const renderIcon = () => {
80
- if (dot) {
81
- return (
82
- <View
83
- style={[
84
- styles.dot,
85
- {
86
- backgroundColor: textColor,
87
- },
88
- ]}
89
- />
90
- );
91
- }
92
- if (!icon) return null;
93
- if (typeof icon === "string") {
94
- return <Icon name={icon} size={iconSize} color={textColor} />;
95
- }
96
- return icon;
97
- };
98
-
99
- return (
100
- <View
101
- style={[
102
- styles.badge,
103
- {
104
- backgroundColor: bg,
105
- borderColor: border,
106
- borderRadius: radius,
107
- paddingVertical,
108
- paddingHorizontal,
109
- },
110
- style,
111
- ]}
112
- >
113
- {renderIcon()}
114
- <Text
115
- style={[
116
- styles.text,
117
- {
118
- color: textColor,
119
- fontSize,
120
- lineHeight,
121
- textTransform: flair.headingTransform === "uppercase" ? "uppercase" : "none",
122
- },
123
- textStyle,
124
- ]}
125
- >
126
- {highlightQuery ? (
127
- <HighlightedText
128
- text={label}
129
- query={highlightQuery}
130
- fuzzyFallback={highlightFuzzyFallback}
131
- />
132
- ) : (
133
- label
134
- )}
135
- </Text>
136
- </View>
137
- );
138
- }
139
-
140
- const styles = StyleSheet.create({
141
- badge: {
142
- flexDirection: "row",
143
- alignItems: "center",
144
- alignSelf: "flex-start",
145
- borderWidth: 1,
146
- gap: 4,
147
- },
148
- text: {
149
- fontWeight: "600",
150
- },
151
- dot: {
152
- width: 6,
153
- height: 6,
154
- borderRadius: 3,
155
- },
156
- });
@@ -1,184 +0,0 @@
1
- import React, { type ReactNode } from "react";
2
- import {
3
- ActivityIndicator,
4
- Pressable,
5
- StyleSheet,
6
- Text,
7
- type StyleProp,
8
- type TextStyle,
9
- type ViewStyle,
10
- } from "react-native";
11
- import { getClientHost } from "../host";
12
- import { usePluginTheme } from "../theme/provider";
13
- import { FALLBACK_ACCENT_FOREGROUND } from "../theme/tokens";
14
- import { AttentionBeacon, type AttentionBeaconMode, type AttentionBeaconTone } from "./AttentionBeacon";
15
-
16
- export type ButtonVariant = "primary" | "secondary" | "danger" | "ghost";
17
- export type ButtonSize = "sm" | "md" | "lg";
18
- export type ButtonAttention = boolean | "radar" | "glow" | "bounce";
19
-
20
- export function resolveButtonAttentionMode(
21
- attention?: ButtonAttention,
22
- ): AttentionBeaconMode | null {
23
- if (!attention) return null;
24
- if (attention === true) return "radar";
25
- return attention;
26
- }
27
-
28
- export function resolveButtonAttentionTone(variant: ButtonVariant): AttentionBeaconTone {
29
- if (variant === "danger") return "danger";
30
- if (variant === "primary") return "accent";
31
- return "warning";
32
- }
33
-
34
- export interface ButtonProps {
35
- label?: string;
36
- children?: ReactNode;
37
- variant?: ButtonVariant;
38
- size?: ButtonSize;
39
- icon?: string | ReactNode;
40
- iconPosition?: "left" | "right";
41
- onPress?: () => void | Promise<void>;
42
- disabled?: boolean;
43
- loading?: boolean;
44
- style?: StyleProp<ViewStyle>;
45
- textStyle?: StyleProp<TextStyle>;
46
- accessibilityLabel?: string;
47
- accessibilityRole?: "button" | "link";
48
- attention?: ButtonAttention;
49
- }
50
-
51
- export function Button({
52
- label,
53
- children,
54
- variant = "secondary",
55
- size = "md",
56
- icon,
57
- iconPosition = "left",
58
- onPress,
59
- disabled = false,
60
- loading = false,
61
- style,
62
- textStyle,
63
- accessibilityLabel,
64
- accessibilityRole = "button",
65
- attention,
66
- }: ButtonProps) {
67
- const { Icon } = getClientHost();
68
- const { colors, resolveRadius, touchTargetMin, isCompact, alpha } = usePluginTheme();
69
-
70
- const radius = resolveRadius(size === "sm" ? "sm" : size === "lg" ? "lg" : "md");
71
-
72
- // Determine sizing
73
- const py = size === "sm" ? (isCompact ? 5 : 6) : size === "lg" ? 12 : isCompact ? 8 : 10;
74
- const px = size === "sm" ? (isCompact ? 8 : 10) : size === "lg" ? 18 : isCompact ? 12 : 14;
75
- const fontSize = size === "sm" ? 12 : size === "lg" ? 15 : 13;
76
- const iconSize = size === "sm" ? 12 : size === "lg" ? 16 : 14;
77
-
78
- // Determine colors by variant
79
- let bg = "transparent";
80
- let border = "transparent";
81
- let textColor = colors.foreground;
82
-
83
- switch (variant) {
84
- case "primary":
85
- bg = colors.accent;
86
- textColor = colors.accentForeground || FALLBACK_ACCENT_FOREGROUND;
87
- break;
88
- case "danger":
89
- bg = alpha(colors.statusDanger, 0.15);
90
- border = alpha(colors.statusDanger, 0.4);
91
- textColor = colors.statusDanger;
92
- break;
93
- case "ghost":
94
- bg = "transparent";
95
- textColor = colors.foregroundMuted;
96
- break;
97
- case "secondary":
98
- default:
99
- bg = colors.surface1;
100
- border = colors.border;
101
- textColor = colors.foreground;
102
- break;
103
- }
104
-
105
- const renderIcon = () => {
106
- if (!icon) return null;
107
- if (typeof icon === "string") {
108
- return <Icon name={icon} size={iconSize} color={textColor} />;
109
- }
110
- return icon;
111
- };
112
-
113
- const attentionMode = resolveButtonAttentionMode(attention);
114
-
115
- const pressable = (
116
- <Pressable
117
- onPress={onPress}
118
- disabled={disabled || loading}
119
- accessibilityRole={accessibilityRole}
120
- accessibilityLabel={accessibilityLabel || label}
121
- hitSlop={Math.max(0, (touchTargetMin - 32) / 2)}
122
- style={({ pressed }) => [
123
- styles.base,
124
- {
125
- backgroundColor: pressed && !disabled ? alpha(bg, 0.8) : bg,
126
- borderColor: border,
127
- borderWidth: border !== "transparent" ? 1 : 0,
128
- borderRadius: radius,
129
- paddingVertical: py,
130
- paddingHorizontal: px,
131
- minHeight: Math.max(30, touchTargetMin - 4),
132
- opacity: disabled ? 0.45 : 1,
133
- },
134
- style,
135
- ]}
136
- >
137
- {loading ? (
138
- <ActivityIndicator size="small" color={textColor} />
139
- ) : children ? (
140
- children
141
- ) : (
142
- <>
143
- {iconPosition === "left" && renderIcon()}
144
- {label ? (
145
- <Text
146
- style={[
147
- styles.text,
148
- {
149
- color: textColor,
150
- fontSize,
151
- },
152
- textStyle,
153
- ]}
154
- >
155
- {label}
156
- </Text>
157
- ) : null}
158
- {iconPosition === "right" && renderIcon()}
159
- </>
160
- )}
161
- </Pressable>
162
- );
163
-
164
- if (!attentionMode) return pressable;
165
-
166
- return (
167
- <AttentionBeacon mode={attentionMode} tone={resolveButtonAttentionTone(variant)}>
168
- {pressable}
169
- </AttentionBeacon>
170
- );
171
- }
172
-
173
- const styles = StyleSheet.create({
174
- base: {
175
- flexDirection: "row",
176
- alignItems: "center",
177
- justifyContent: "center",
178
- gap: 6,
179
- },
180
- text: {
181
- fontWeight: "600",
182
- textAlign: "center",
183
- },
184
- });