@xpufx/paseo-top 0.4.4 → 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 +94 -1
- package/client/pill.tsx +690 -614
- 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/package.json +7 -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,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,168 +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
|
-
accessibilityLabel={label}
|
|
116
|
-
numberOfLines={1}
|
|
117
|
-
ellipsizeMode="tail"
|
|
118
|
-
style={[
|
|
119
|
-
styles.text,
|
|
120
|
-
{
|
|
121
|
-
color: textColor,
|
|
122
|
-
fontSize,
|
|
123
|
-
lineHeight,
|
|
124
|
-
textTransform: flair.headingTransform === "uppercase" ? "uppercase" : "none",
|
|
125
|
-
},
|
|
126
|
-
textStyle,
|
|
127
|
-
]}
|
|
128
|
-
>
|
|
129
|
-
{highlightQuery ? (
|
|
130
|
-
<HighlightedText
|
|
131
|
-
text={label}
|
|
132
|
-
query={highlightQuery}
|
|
133
|
-
fuzzyFallback={highlightFuzzyFallback}
|
|
134
|
-
numberOfLines={1}
|
|
135
|
-
/>
|
|
136
|
-
) : (
|
|
137
|
-
label
|
|
138
|
-
)}
|
|
139
|
-
</Text>
|
|
140
|
-
</View>
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const styles = StyleSheet.create({
|
|
145
|
-
badge: {
|
|
146
|
-
flexDirection: "row",
|
|
147
|
-
alignItems: "center",
|
|
148
|
-
alignSelf: "flex-start",
|
|
149
|
-
borderWidth: 1,
|
|
150
|
-
gap: 4,
|
|
151
|
-
// A badge carries operator-supplied strings (ids, paths, refs, model names)
|
|
152
|
-
// of unbounded length. Yoga defaults `flexShrink` to 0, so without this the
|
|
153
|
-
// chip insists on its full text width and pushes its row past the viewport
|
|
154
|
-
// instead of truncating.
|
|
155
|
-
flexShrink: 1,
|
|
156
|
-
maxWidth: "100%",
|
|
157
|
-
},
|
|
158
|
-
text: {
|
|
159
|
-
fontWeight: "600",
|
|
160
|
-
// Truncation only engages if the label itself is allowed to compress.
|
|
161
|
-
flexShrink: 1,
|
|
162
|
-
},
|
|
163
|
-
dot: {
|
|
164
|
-
width: 6,
|
|
165
|
-
height: 6,
|
|
166
|
-
borderRadius: 3,
|
|
167
|
-
},
|
|
168
|
-
});
|
|
@@ -1,204 +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
|
-
// Accessibility touch targets are met with `hitSlop`, which expands the
|
|
116
|
-
// tappable area without moving a single neighbouring pixel. They are NOT met
|
|
117
|
-
// by inflating the painted box: a `minHeight` floor forced `size="sm"` to
|
|
118
|
-
// render 40px tall on mobile against a ~26px recipe, because the floor was
|
|
119
|
-
// computed from the touch target rather than from the size (#647).
|
|
120
|
-
//
|
|
121
|
-
// The slop has to be derived from this button's own height, not a fixed 32 —
|
|
122
|
-
// a 32px baseline silently under-serves every smaller recipe, so `sm` got
|
|
123
|
-
// both a too-tall box and too little slop.
|
|
124
|
-
const contentHeight = Math.round(fontSize * 1.2) + py * 2;
|
|
125
|
-
const hitSlopSide = Math.max(0, (touchTargetMin - contentHeight) / 2);
|
|
126
|
-
|
|
127
|
-
const pressable = (
|
|
128
|
-
<Pressable
|
|
129
|
-
onPress={onPress}
|
|
130
|
-
disabled={disabled || loading}
|
|
131
|
-
accessibilityRole={accessibilityRole}
|
|
132
|
-
accessibilityLabel={accessibilityLabel || label}
|
|
133
|
-
hitSlop={hitSlopSide}
|
|
134
|
-
style={({ pressed }) => [
|
|
135
|
-
styles.base,
|
|
136
|
-
{
|
|
137
|
-
backgroundColor: pressed && !disabled ? alpha(bg, 0.8) : bg,
|
|
138
|
-
borderColor: border,
|
|
139
|
-
borderWidth: border !== "transparent" ? 1 : 0,
|
|
140
|
-
borderRadius: radius,
|
|
141
|
-
paddingVertical: py,
|
|
142
|
-
paddingHorizontal: px,
|
|
143
|
-
opacity: disabled ? 0.45 : 1,
|
|
144
|
-
},
|
|
145
|
-
style,
|
|
146
|
-
]}
|
|
147
|
-
>
|
|
148
|
-
{loading ? (
|
|
149
|
-
<ActivityIndicator size="small" color={textColor} />
|
|
150
|
-
) : children ? (
|
|
151
|
-
children
|
|
152
|
-
) : (
|
|
153
|
-
<>
|
|
154
|
-
{iconPosition === "left" && renderIcon()}
|
|
155
|
-
{label ? (
|
|
156
|
-
<Text
|
|
157
|
-
numberOfLines={1}
|
|
158
|
-
ellipsizeMode="tail"
|
|
159
|
-
style={[
|
|
160
|
-
styles.text,
|
|
161
|
-
{
|
|
162
|
-
color: textColor,
|
|
163
|
-
fontSize,
|
|
164
|
-
},
|
|
165
|
-
textStyle,
|
|
166
|
-
]}
|
|
167
|
-
>
|
|
168
|
-
{label}
|
|
169
|
-
</Text>
|
|
170
|
-
) : null}
|
|
171
|
-
{iconPosition === "right" && renderIcon()}
|
|
172
|
-
</>
|
|
173
|
-
)}
|
|
174
|
-
</Pressable>
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
if (!attentionMode) return pressable;
|
|
178
|
-
|
|
179
|
-
return (
|
|
180
|
-
<AttentionBeacon mode={attentionMode} tone={resolveButtonAttentionTone(variant)}>
|
|
181
|
-
{pressable}
|
|
182
|
-
</AttentionBeacon>
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const styles = StyleSheet.create({
|
|
187
|
-
base: {
|
|
188
|
-
flexDirection: "row",
|
|
189
|
-
alignItems: "center",
|
|
190
|
-
justifyContent: "center",
|
|
191
|
-
gap: 6,
|
|
192
|
-
// Labels are caller-supplied and unbounded (issue titles, branch names,
|
|
193
|
-
// sort labels). Yoga defaults `flexShrink` to 0, so an unconstrained
|
|
194
|
-
// button claims its full label width and overflows narrow viewports.
|
|
195
|
-
flexShrink: 1,
|
|
196
|
-
maxWidth: "100%",
|
|
197
|
-
},
|
|
198
|
-
text: {
|
|
199
|
-
fontWeight: "600",
|
|
200
|
-
textAlign: "center",
|
|
201
|
-
// The label must be allowed to compress for `numberOfLines` to engage.
|
|
202
|
-
flexShrink: 1,
|
|
203
|
-
},
|
|
204
|
-
});
|