@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,117 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { StyleSheet, Text, View, type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
-
import { usePluginTheme } from "../theme/provider";
|
|
4
|
-
import { resolveMetricStatus, type MetricThresholds } from "../../../../shared/vendor/paseo-plugin-helper/formatters";
|
|
5
|
-
|
|
6
|
-
export interface ProgressBarProps {
|
|
7
|
-
value: number; // 0 to 100
|
|
8
|
-
color?: string;
|
|
9
|
-
autoStatusColor?: boolean;
|
|
10
|
-
thresholds?: MetricThresholds;
|
|
11
|
-
label?: string;
|
|
12
|
-
showValueText?: boolean;
|
|
13
|
-
height?: number;
|
|
14
|
-
style?: StyleProp<ViewStyle>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function ProgressBar({
|
|
18
|
-
value,
|
|
19
|
-
color,
|
|
20
|
-
autoStatusColor = true,
|
|
21
|
-
thresholds,
|
|
22
|
-
label,
|
|
23
|
-
showValueText = false,
|
|
24
|
-
height = 8,
|
|
25
|
-
style,
|
|
26
|
-
}: ProgressBarProps) {
|
|
27
|
-
const { colors, resolveRadius, typography } = usePluginTheme();
|
|
28
|
-
|
|
29
|
-
const clamped = Math.max(0, Math.min(100, Number.isFinite(value) ? value : 0));
|
|
30
|
-
const radius = resolveRadius("pill");
|
|
31
|
-
|
|
32
|
-
let barColor = color || colors.accent;
|
|
33
|
-
if (!color && autoStatusColor) {
|
|
34
|
-
const status = resolveMetricStatus(clamped, thresholds);
|
|
35
|
-
if (status === "danger") {
|
|
36
|
-
barColor = colors.statusDanger;
|
|
37
|
-
} else if (status === "warning") {
|
|
38
|
-
barColor = colors.statusWarning;
|
|
39
|
-
} else {
|
|
40
|
-
barColor = colors.statusSuccess;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<View style={[styles.container, style]}>
|
|
46
|
-
{(label || showValueText) && (
|
|
47
|
-
<View style={styles.labelRow}>
|
|
48
|
-
{label ? (
|
|
49
|
-
<Text
|
|
50
|
-
style={[
|
|
51
|
-
styles.labelText,
|
|
52
|
-
{ color: colors.foregroundMuted, ...typography.caption },
|
|
53
|
-
]}
|
|
54
|
-
>
|
|
55
|
-
{label}
|
|
56
|
-
</Text>
|
|
57
|
-
) : null}
|
|
58
|
-
{showValueText ? (
|
|
59
|
-
<Text
|
|
60
|
-
style={[
|
|
61
|
-
styles.valueText,
|
|
62
|
-
{ color: colors.foreground, ...typography.caption, fontWeight: "600" },
|
|
63
|
-
]}
|
|
64
|
-
>
|
|
65
|
-
{Math.round(clamped)}%
|
|
66
|
-
</Text>
|
|
67
|
-
) : null}
|
|
68
|
-
</View>
|
|
69
|
-
)}
|
|
70
|
-
<View
|
|
71
|
-
style={[
|
|
72
|
-
styles.track,
|
|
73
|
-
{
|
|
74
|
-
backgroundColor: colors.surface2,
|
|
75
|
-
height,
|
|
76
|
-
borderRadius: radius,
|
|
77
|
-
},
|
|
78
|
-
]}
|
|
79
|
-
>
|
|
80
|
-
<View
|
|
81
|
-
style={[
|
|
82
|
-
styles.fill,
|
|
83
|
-
{
|
|
84
|
-
width: `${clamped}%`,
|
|
85
|
-
backgroundColor: barColor,
|
|
86
|
-
borderRadius: radius,
|
|
87
|
-
},
|
|
88
|
-
]}
|
|
89
|
-
/>
|
|
90
|
-
</View>
|
|
91
|
-
</View>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const styles = StyleSheet.create({
|
|
96
|
-
container: {
|
|
97
|
-
gap: 4,
|
|
98
|
-
},
|
|
99
|
-
labelRow: {
|
|
100
|
-
flexDirection: "row",
|
|
101
|
-
justifyContent: "space-between",
|
|
102
|
-
alignItems: "center",
|
|
103
|
-
},
|
|
104
|
-
labelText: {
|
|
105
|
-
fontWeight: "500",
|
|
106
|
-
},
|
|
107
|
-
valueText: {
|
|
108
|
-
fontWeight: "600",
|
|
109
|
-
},
|
|
110
|
-
track: {
|
|
111
|
-
width: "100%",
|
|
112
|
-
overflow: "hidden",
|
|
113
|
-
},
|
|
114
|
-
fill: {
|
|
115
|
-
height: "100%",
|
|
116
|
-
},
|
|
117
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import React, { type ReactNode } from "react";
|
|
2
|
-
import { useResponsive, type UseResponsiveResult } from "../theme/useResponsive";
|
|
3
|
-
|
|
4
|
-
export interface ResponsiveProps {
|
|
5
|
-
/**
|
|
6
|
-
* Content to render on desktop / wide viewports.
|
|
7
|
-
*/
|
|
8
|
-
desktop?: ReactNode;
|
|
9
|
-
/**
|
|
10
|
-
* Content to render on mobile platforms ('ios' | 'android').
|
|
11
|
-
*/
|
|
12
|
-
mobile?: ReactNode;
|
|
13
|
-
/**
|
|
14
|
-
* Content to render when in compact mode (narrow trackbar, mobile bottom sheet, or split view).
|
|
15
|
-
*/
|
|
16
|
-
compact?: ReactNode;
|
|
17
|
-
/**
|
|
18
|
-
* Content to render when in wide mode (not compact).
|
|
19
|
-
*/
|
|
20
|
-
wide?: ReactNode;
|
|
21
|
-
/**
|
|
22
|
-
* Render function taking `UseResponsiveResult` or children.
|
|
23
|
-
*/
|
|
24
|
-
children?: ReactNode | ((responsive: UseResponsiveResult) => ReactNode);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Declarative component for rendering different UI elements across desktop, mobile, and compact layouts.
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* ```tsx
|
|
32
|
-
* <Responsive
|
|
33
|
-
* desktop={<DataTable columns={["ID", "Name", "Status", "Latency"]} data={items} />}
|
|
34
|
-
* mobile={<DataTable columns={["Name", "Status"]} data={items} />}
|
|
35
|
-
* />
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
|
-
export function Responsive({ desktop, mobile, compact, wide, children }: ResponsiveProps) {
|
|
39
|
-
const responsive = useResponsive();
|
|
40
|
-
|
|
41
|
-
if (typeof children === "function") {
|
|
42
|
-
return <>{children(responsive)}</>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const selected = responsive.select<ReactNode>({
|
|
46
|
-
desktop,
|
|
47
|
-
mobile,
|
|
48
|
-
compact,
|
|
49
|
-
wide,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
return <>{selected ?? children ?? null}</>;
|
|
53
|
-
}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import React, { type ComponentType, type Ref } from "react";
|
|
2
|
-
import {
|
|
3
|
-
StyleSheet,
|
|
4
|
-
TextInput as RNTextInput,
|
|
5
|
-
View,
|
|
6
|
-
Pressable,
|
|
7
|
-
type StyleProp,
|
|
8
|
-
type ViewStyle,
|
|
9
|
-
type TextStyle,
|
|
10
|
-
type TextInputProps as RNTextInputProps,
|
|
11
|
-
type TextInput as RNTextInputInstance,
|
|
12
|
-
} from "react-native";
|
|
13
|
-
import { getClientHost, getOptionalClientHost } from "../host";
|
|
14
|
-
import { usePluginTheme } from "../theme/provider";
|
|
15
|
-
|
|
16
|
-
export interface SearchInputProps {
|
|
17
|
-
value: string;
|
|
18
|
-
onChangeText: (text: string) => void;
|
|
19
|
-
placeholder?: string;
|
|
20
|
-
onClear?: () => void;
|
|
21
|
-
style?: StyleProp<ViewStyle>;
|
|
22
|
-
inputStyle?: StyleProp<TextStyle>;
|
|
23
|
-
testID?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Standardized search input with search icon, clear button, and theme support.
|
|
28
|
-
*/
|
|
29
|
-
export function SearchInput({
|
|
30
|
-
value,
|
|
31
|
-
onChangeText,
|
|
32
|
-
placeholder = "Search...",
|
|
33
|
-
onClear,
|
|
34
|
-
style,
|
|
35
|
-
inputStyle,
|
|
36
|
-
testID,
|
|
37
|
-
}: SearchInputProps) {
|
|
38
|
-
const { Icon } = getClientHost();
|
|
39
|
-
const ResolvedInput = (getOptionalClientHost()?.TextInput ??
|
|
40
|
-
RNTextInput) as ComponentType<RNTextInputProps & { ref?: Ref<RNTextInputInstance> }>;
|
|
41
|
-
const { colors, resolveRadius, isCompact } = usePluginTheme();
|
|
42
|
-
const radius = resolveRadius("sm");
|
|
43
|
-
|
|
44
|
-
const handleClear = () => {
|
|
45
|
-
onChangeText("");
|
|
46
|
-
if (onClear) onClear();
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<View
|
|
51
|
-
style={[
|
|
52
|
-
styles.container,
|
|
53
|
-
{
|
|
54
|
-
backgroundColor: colors.surface1,
|
|
55
|
-
borderColor: colors.border,
|
|
56
|
-
borderRadius: radius,
|
|
57
|
-
height: isCompact ? 36 : 40,
|
|
58
|
-
},
|
|
59
|
-
style,
|
|
60
|
-
]}
|
|
61
|
-
>
|
|
62
|
-
<View style={styles.iconWrapper}>
|
|
63
|
-
<Icon name="Search" size={16} color={colors.foregroundMuted} />
|
|
64
|
-
</View>
|
|
65
|
-
<ResolvedInput
|
|
66
|
-
testID={testID}
|
|
67
|
-
value={value}
|
|
68
|
-
onChangeText={onChangeText}
|
|
69
|
-
placeholder={placeholder}
|
|
70
|
-
placeholderTextColor={colors.foregroundMuted}
|
|
71
|
-
style={[
|
|
72
|
-
styles.input,
|
|
73
|
-
{
|
|
74
|
-
color: colors.foreground,
|
|
75
|
-
fontSize: isCompact ? 13 : 14,
|
|
76
|
-
},
|
|
77
|
-
inputStyle,
|
|
78
|
-
]}
|
|
79
|
-
returnKeyType="search"
|
|
80
|
-
autoCapitalize="none"
|
|
81
|
-
autoCorrect={false}
|
|
82
|
-
/>
|
|
83
|
-
{Boolean(value) && (
|
|
84
|
-
<Pressable
|
|
85
|
-
onPress={handleClear}
|
|
86
|
-
style={styles.clearButton}
|
|
87
|
-
hitSlop={8}
|
|
88
|
-
accessibilityLabel="Clear search"
|
|
89
|
-
>
|
|
90
|
-
<Icon name="X" size={14} color={colors.foregroundMuted} />
|
|
91
|
-
</Pressable>
|
|
92
|
-
)}
|
|
93
|
-
</View>
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const styles = StyleSheet.create({
|
|
98
|
-
container: {
|
|
99
|
-
flexDirection: "row",
|
|
100
|
-
alignItems: "center",
|
|
101
|
-
borderWidth: 1,
|
|
102
|
-
paddingHorizontal: 10,
|
|
103
|
-
},
|
|
104
|
-
iconWrapper: {
|
|
105
|
-
marginRight: 8,
|
|
106
|
-
alignItems: "center",
|
|
107
|
-
justifyContent: "center",
|
|
108
|
-
},
|
|
109
|
-
input: {
|
|
110
|
-
flex: 1,
|
|
111
|
-
paddingVertical: 0,
|
|
112
|
-
outlineWidth: 0,
|
|
113
|
-
},
|
|
114
|
-
clearButton: {
|
|
115
|
-
padding: 4,
|
|
116
|
-
marginLeft: 4,
|
|
117
|
-
},
|
|
118
|
-
});
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import {
|
|
3
|
-
StyleSheet,
|
|
4
|
-
Text,
|
|
5
|
-
View,
|
|
6
|
-
type StyleProp,
|
|
7
|
-
type TextStyle,
|
|
8
|
-
type ViewStyle,
|
|
9
|
-
} from "react-native";
|
|
10
|
-
import { usePluginTheme } from "../theme/provider";
|
|
11
|
-
import type { StatusVariant } from "../../../../shared/vendor/paseo-plugin-helper/types";
|
|
12
|
-
import { Badge } from "./Badge";
|
|
13
|
-
|
|
14
|
-
export interface SectionHeaderProps {
|
|
15
|
-
title: string;
|
|
16
|
-
/** Optional count — shown as a Badge (warning variant when > 0, neutral otherwise) */
|
|
17
|
-
count?: number;
|
|
18
|
-
/** Badge variant override */
|
|
19
|
-
badgeVariant?: StatusVariant;
|
|
20
|
-
style?: StyleProp<ViewStyle>;
|
|
21
|
-
textStyle?: StyleProp<TextStyle>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function SectionHeader({
|
|
25
|
-
title,
|
|
26
|
-
count,
|
|
27
|
-
badgeVariant,
|
|
28
|
-
style,
|
|
29
|
-
textStyle,
|
|
30
|
-
}: SectionHeaderProps): React.ReactElement | null {
|
|
31
|
-
const theme = usePluginTheme();
|
|
32
|
-
const titleType = theme.typography?.bodyStrong ?? {
|
|
33
|
-
fontSize: 13,
|
|
34
|
-
lineHeight: 19,
|
|
35
|
-
fontWeight: "600" as const,
|
|
36
|
-
};
|
|
37
|
-
const headingTransform = (theme.flair?.headingTransform ?? "none") === "uppercase"
|
|
38
|
-
? "uppercase"
|
|
39
|
-
: "none";
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<View style={[styles.container, style]}>
|
|
43
|
-
<Text
|
|
44
|
-
style={[
|
|
45
|
-
styles.title,
|
|
46
|
-
{
|
|
47
|
-
color: theme.colors.foregroundMuted,
|
|
48
|
-
textTransform: headingTransform,
|
|
49
|
-
fontSize: titleType.fontSize,
|
|
50
|
-
lineHeight: titleType.lineHeight,
|
|
51
|
-
fontWeight: titleType.fontWeight,
|
|
52
|
-
},
|
|
53
|
-
textStyle,
|
|
54
|
-
]}
|
|
55
|
-
>
|
|
56
|
-
{title}
|
|
57
|
-
</Text>
|
|
58
|
-
{count !== undefined ? (
|
|
59
|
-
<Badge
|
|
60
|
-
label={String(count)}
|
|
61
|
-
variant={badgeVariant ?? (count > 0 ? "warning" : "neutral")}
|
|
62
|
-
styleVariant={count > 0 ? "solid" : "tinted"}
|
|
63
|
-
/>
|
|
64
|
-
) : null}
|
|
65
|
-
</View>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const styles = StyleSheet.create({
|
|
70
|
-
container: {
|
|
71
|
-
flexDirection: "row",
|
|
72
|
-
alignItems: "center",
|
|
73
|
-
gap: 8,
|
|
74
|
-
marginTop: 8,
|
|
75
|
-
marginBottom: 2,
|
|
76
|
-
},
|
|
77
|
-
title: {
|
|
78
|
-
letterSpacing: 0.8,
|
|
79
|
-
},
|
|
80
|
-
});
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Pressable,
|
|
4
|
-
ScrollView as FallbackScrollView,
|
|
5
|
-
StyleSheet,
|
|
6
|
-
Text,
|
|
7
|
-
View,
|
|
8
|
-
type StyleProp,
|
|
9
|
-
type ViewStyle,
|
|
10
|
-
} from "react-native";
|
|
11
|
-
import { getClientHost } from "../host";
|
|
12
|
-
import { usePluginTheme } from "../theme/provider";
|
|
13
|
-
import { spacing } from "../theme/tokens";
|
|
14
|
-
import type { BadgeSize } from "./Badge";
|
|
15
|
-
|
|
16
|
-
export interface SelectOption {
|
|
17
|
-
label: string;
|
|
18
|
-
value: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface SelectProps {
|
|
22
|
-
value: string;
|
|
23
|
-
options: SelectOption[];
|
|
24
|
-
onValueChange: (value: string) => void;
|
|
25
|
-
label?: string;
|
|
26
|
-
/** Compact/pill scale shared with {@link Badge}: `"md"` (default) or `"sm"`. */
|
|
27
|
-
size?: BadgeSize;
|
|
28
|
-
placeholder?: string;
|
|
29
|
-
disabled?: boolean;
|
|
30
|
-
style?: StyleProp<ViewStyle>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const OPTION_LIST_MAX_HEIGHT = 216;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Compact single-choice picker sized to sit inside a {@link FormRow}. The
|
|
37
|
-
* closed trigger stays one line tall; opening reveals a bounded, scrollable
|
|
38
|
-
* option list, so a long list degrades to scrolling rather than overflow.
|
|
39
|
-
*/
|
|
40
|
-
export function Select({
|
|
41
|
-
value,
|
|
42
|
-
options,
|
|
43
|
-
onValueChange,
|
|
44
|
-
label,
|
|
45
|
-
size = "md",
|
|
46
|
-
placeholder = "Select…",
|
|
47
|
-
disabled = false,
|
|
48
|
-
style,
|
|
49
|
-
}: SelectProps) {
|
|
50
|
-
const { Icon } = getClientHost();
|
|
51
|
-
// The dropdown option list is a bounded box nested inside the host sheet,
|
|
52
|
-
// so it scrolls with a plain React Native ScrollView — never the host
|
|
53
|
-
// sheet-gesture scroller (#219).
|
|
54
|
-
const { colors, resolveRadius, typography, isCompact, touchTargetMin, alpha } =
|
|
55
|
-
usePluginTheme();
|
|
56
|
-
const [open, setOpen] = useState(false);
|
|
57
|
-
|
|
58
|
-
const caption = typography?.caption ?? {
|
|
59
|
-
fontSize: 11,
|
|
60
|
-
lineHeight: 15,
|
|
61
|
-
fontWeight: "400" as const,
|
|
62
|
-
};
|
|
63
|
-
const fontSize = size === "sm" ? 10 : caption.fontSize;
|
|
64
|
-
const lineHeight = size === "sm" ? 12 : caption.lineHeight;
|
|
65
|
-
const paddingVertical = size === "sm" ? 3 : 6;
|
|
66
|
-
const paddingHorizontal = size === "sm" ? 8 : 10;
|
|
67
|
-
const iconSize = size === "sm" ? 12 : 14;
|
|
68
|
-
const radius = resolveRadius("md");
|
|
69
|
-
|
|
70
|
-
// A free-text value (advanced entry) may not be one of the options; surface
|
|
71
|
-
// it on the trigger rather than masking it behind the placeholder.
|
|
72
|
-
const selected = options.find((option) => option.value === value);
|
|
73
|
-
const display = selected?.label ?? (value ? value : placeholder);
|
|
74
|
-
const canOpen = !disabled && options.length > 0;
|
|
75
|
-
const isOpen = open && canOpen;
|
|
76
|
-
|
|
77
|
-
const triggerMinHeight = size === "sm" ? 28 : Math.max(34, touchTargetMin);
|
|
78
|
-
|
|
79
|
-
return (
|
|
80
|
-
<View style={[styles.container, style]}>
|
|
81
|
-
<Pressable
|
|
82
|
-
accessibilityRole="button"
|
|
83
|
-
accessibilityLabel={label ? `${label}: ${display}` : display}
|
|
84
|
-
accessibilityState={{ expanded: isOpen, disabled }}
|
|
85
|
-
disabled={!canOpen}
|
|
86
|
-
onPress={() => setOpen((prev) => !prev)}
|
|
87
|
-
style={({ pressed }) => [
|
|
88
|
-
styles.trigger,
|
|
89
|
-
{
|
|
90
|
-
minHeight: triggerMinHeight,
|
|
91
|
-
borderRadius: radius,
|
|
92
|
-
borderColor: isOpen ? colors.accent : colors.border,
|
|
93
|
-
backgroundColor:
|
|
94
|
-
disabled || !canOpen
|
|
95
|
-
? alpha(colors.surface1, 0.5)
|
|
96
|
-
: pressed
|
|
97
|
-
? colors.surface2
|
|
98
|
-
: colors.surface0,
|
|
99
|
-
paddingVertical,
|
|
100
|
-
paddingHorizontal,
|
|
101
|
-
},
|
|
102
|
-
]}
|
|
103
|
-
>
|
|
104
|
-
<Text
|
|
105
|
-
numberOfLines={1}
|
|
106
|
-
style={[
|
|
107
|
-
styles.triggerText,
|
|
108
|
-
{
|
|
109
|
-
color: disabled || !canOpen ? colors.foregroundMuted : colors.foreground,
|
|
110
|
-
fontSize,
|
|
111
|
-
lineHeight,
|
|
112
|
-
fontWeight: selected ? "600" : "500",
|
|
113
|
-
},
|
|
114
|
-
]}
|
|
115
|
-
>
|
|
116
|
-
{display}
|
|
117
|
-
</Text>
|
|
118
|
-
<Icon
|
|
119
|
-
name={isOpen ? "ChevronUp" : "ChevronDown"}
|
|
120
|
-
size={iconSize}
|
|
121
|
-
color={colors.foregroundMuted}
|
|
122
|
-
/>
|
|
123
|
-
</Pressable>
|
|
124
|
-
|
|
125
|
-
{isOpen ? (
|
|
126
|
-
<View
|
|
127
|
-
style={[
|
|
128
|
-
styles.optionList,
|
|
129
|
-
{
|
|
130
|
-
borderRadius: radius,
|
|
131
|
-
borderColor: colors.border,
|
|
132
|
-
backgroundColor: colors.surface0,
|
|
133
|
-
marginTop: spacing.xs,
|
|
134
|
-
},
|
|
135
|
-
]}
|
|
136
|
-
>
|
|
137
|
-
<FallbackScrollView
|
|
138
|
-
nestedScrollEnabled
|
|
139
|
-
keyboardShouldPersistTaps="handled"
|
|
140
|
-
style={{ maxHeight: OPTION_LIST_MAX_HEIGHT }}
|
|
141
|
-
>
|
|
142
|
-
{options.map((option) => {
|
|
143
|
-
const isSelected = option.value === value;
|
|
144
|
-
return (
|
|
145
|
-
<Pressable
|
|
146
|
-
key={option.value}
|
|
147
|
-
accessibilityRole="button"
|
|
148
|
-
accessibilityLabel={option.label}
|
|
149
|
-
accessibilityState={{ selected: isSelected }}
|
|
150
|
-
onPress={() => {
|
|
151
|
-
onValueChange(option.value);
|
|
152
|
-
setOpen(false);
|
|
153
|
-
}}
|
|
154
|
-
style={({ pressed }) => [
|
|
155
|
-
styles.option,
|
|
156
|
-
{
|
|
157
|
-
paddingVertical,
|
|
158
|
-
paddingHorizontal,
|
|
159
|
-
backgroundColor: isSelected
|
|
160
|
-
? colors.surface2
|
|
161
|
-
: pressed
|
|
162
|
-
? alpha(colors.surface2, 0.5)
|
|
163
|
-
: "transparent",
|
|
164
|
-
},
|
|
165
|
-
]}
|
|
166
|
-
>
|
|
167
|
-
<Text
|
|
168
|
-
numberOfLines={1}
|
|
169
|
-
style={[
|
|
170
|
-
styles.optionText,
|
|
171
|
-
{
|
|
172
|
-
color: isSelected ? colors.foreground : colors.foregroundMuted,
|
|
173
|
-
fontSize: isCompact ? fontSize : fontSize + 1,
|
|
174
|
-
lineHeight,
|
|
175
|
-
fontWeight: isSelected ? "600" : "400",
|
|
176
|
-
},
|
|
177
|
-
]}
|
|
178
|
-
>
|
|
179
|
-
{option.label}
|
|
180
|
-
</Text>
|
|
181
|
-
</Pressable>
|
|
182
|
-
);
|
|
183
|
-
})}
|
|
184
|
-
</FallbackScrollView>
|
|
185
|
-
</View>
|
|
186
|
-
) : null}
|
|
187
|
-
</View>
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const styles = StyleSheet.create({
|
|
192
|
-
container: {
|
|
193
|
-
width: "100%",
|
|
194
|
-
},
|
|
195
|
-
trigger: {
|
|
196
|
-
flexDirection: "row",
|
|
197
|
-
alignItems: "center",
|
|
198
|
-
justifyContent: "space-between",
|
|
199
|
-
gap: spacing.sm,
|
|
200
|
-
borderWidth: 1,
|
|
201
|
-
},
|
|
202
|
-
triggerText: {
|
|
203
|
-
flexShrink: 1,
|
|
204
|
-
minWidth: 0,
|
|
205
|
-
},
|
|
206
|
-
optionList: {
|
|
207
|
-
borderWidth: 1,
|
|
208
|
-
overflow: "hidden",
|
|
209
|
-
},
|
|
210
|
-
option: {},
|
|
211
|
-
optionText: {
|
|
212
|
-
flexShrink: 1,
|
|
213
|
-
minWidth: 0,
|
|
214
|
-
},
|
|
215
|
-
});
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from "react";
|
|
2
|
-
import { Animated, StyleSheet, View, type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
-
import { usePluginTheme } from "../theme/provider";
|
|
4
|
-
import type { StatusVariant } from "../../../../shared/vendor/paseo-plugin-helper/types";
|
|
5
|
-
|
|
6
|
-
export interface StatusDotProps {
|
|
7
|
-
variant?: StatusVariant;
|
|
8
|
-
size?: "sm" | "md" | "lg";
|
|
9
|
-
pulse?: boolean;
|
|
10
|
-
style?: StyleProp<ViewStyle>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function StatusDot({ variant = "neutral", size = "md", pulse = false, style }: StatusDotProps) {
|
|
14
|
-
const { getStatusColor, alpha } = usePluginTheme();
|
|
15
|
-
const color = getStatusColor(variant);
|
|
16
|
-
const pulseAnim = useRef(new Animated.Value(1)).current;
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (!pulse) return;
|
|
20
|
-
|
|
21
|
-
const loop = Animated.loop(
|
|
22
|
-
Animated.sequence([
|
|
23
|
-
Animated.timing(pulseAnim, {
|
|
24
|
-
toValue: 0.35,
|
|
25
|
-
duration: 900,
|
|
26
|
-
useNativeDriver: true,
|
|
27
|
-
}),
|
|
28
|
-
Animated.timing(pulseAnim, {
|
|
29
|
-
toValue: 1,
|
|
30
|
-
duration: 900,
|
|
31
|
-
useNativeDriver: true,
|
|
32
|
-
}),
|
|
33
|
-
]),
|
|
34
|
-
);
|
|
35
|
-
loop.start();
|
|
36
|
-
|
|
37
|
-
return () => loop.stop();
|
|
38
|
-
}, [pulse, pulseAnim]);
|
|
39
|
-
|
|
40
|
-
const dimension = size === "sm" ? 6 : size === "lg" ? 10 : 8;
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<View
|
|
44
|
-
style={[
|
|
45
|
-
styles.container,
|
|
46
|
-
{
|
|
47
|
-
width: dimension,
|
|
48
|
-
height: dimension,
|
|
49
|
-
},
|
|
50
|
-
style,
|
|
51
|
-
]}
|
|
52
|
-
>
|
|
53
|
-
<Animated.View
|
|
54
|
-
style={[
|
|
55
|
-
styles.dot,
|
|
56
|
-
{
|
|
57
|
-
width: dimension,
|
|
58
|
-
height: dimension,
|
|
59
|
-
borderRadius: dimension / 2,
|
|
60
|
-
backgroundColor: color,
|
|
61
|
-
shadowColor: color,
|
|
62
|
-
opacity: pulseAnim,
|
|
63
|
-
},
|
|
64
|
-
]}
|
|
65
|
-
/>
|
|
66
|
-
</View>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const styles = StyleSheet.create({
|
|
71
|
-
container: {
|
|
72
|
-
alignItems: "center",
|
|
73
|
-
justifyContent: "center",
|
|
74
|
-
},
|
|
75
|
-
dot: {
|
|
76
|
-
shadowOffset: { width: 0, height: 0 },
|
|
77
|
-
shadowOpacity: 0.4,
|
|
78
|
-
shadowRadius: 3,
|
|
79
|
-
},
|
|
80
|
-
});
|