@xpufx/paseo-top 0.4.4 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/OVERVIEW.md +18 -0
- package/client/host-ui.tsx +184 -0
- package/client/multi-host-view.tsx +57 -43
- package/client/multi-host.ts +36 -13
- package/client/pill-labels.ts +94 -1
- package/client/pill.tsx +692 -616
- package/client/resources-query.ts +1 -1
- package/client/settings-events.ts +1 -1
- package/client/settings-ui.tsx +86 -9
- package/client/surface.tsx +308 -276
- package/client/telemetry-copy.ts +8 -2
- package/client/telemetry.tsx +372 -321
- package/client/turn-panel.tsx +126 -104
- package/client/vendor/paseo-plugin-helper/{host.ts → core/host.ts} +9 -3
- package/client/vendor/paseo-plugin-helper/core/index.ts +76 -0
- package/client/vendor/paseo-plugin-helper/core/plugin-load-count.ts +89 -0
- package/client/vendor/paseo-plugin-helper/{query-refresh.ts → core/query-refresh.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{query.ts → core/query.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{settings.ts → core/settings.ts} +1 -1
- package/client/vendor/paseo-plugin-helper/{shared-settings.ts → core/shared-settings.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/clipboard.ts +2 -2
- package/client/vendor/paseo-plugin-helper/lifecycle/flair.ts +26 -0
- package/client/vendor/paseo-plugin-helper/{theme/color-utils.ts → lifecycle/host-color.ts} +95 -38
- package/client/vendor/paseo-plugin-helper/lifecycle/host-theme.tsx +109 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/index.ts +23 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/multi-host.ts +66 -0
- package/client/vendor/paseo-plugin-helper/{panel.tsx → lifecycle/panel.tsx} +7 -7
- package/client/vendor/paseo-plugin-helper/{pill.tsx → lifecycle/pill.tsx} +51 -18
- package/client/vendor/paseo-plugin-helper/lifecycle/providers.tsx +46 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/scroll-owner.ts +26 -0
- package/client/vendor/paseo-plugin-helper/{surface.tsx → lifecycle/surface.tsx} +6 -6
- package/client/vendor/paseo-plugin-helper/ui/color.ts +1 -0
- package/client/vendor/paseo-plugin-helper/ui/content.tsx +1326 -0
- package/client/vendor/paseo-plugin-helper/ui/controls.tsx +1018 -0
- package/client/vendor/paseo-plugin-helper/{components/DataTable.tsx → ui/data.tsx} +50 -58
- package/client/vendor/paseo-plugin-helper/ui/index.ts +44 -0
- package/client/vendor/paseo-plugin-helper/ui/layout.tsx +263 -0
- package/client/vendor/paseo-plugin-helper/ui/modal.tsx +111 -0
- package/client/vendor/paseo-plugin-helper/ui/plugin-reload-banner.tsx +119 -0
- package/client/vendor/paseo-plugin-helper/{settings-screen.tsx → ui/settings-screen.tsx} +3 -3
- package/client/vendor/paseo-plugin-helper/ui/settings.ts +36 -0
- package/client/vendor/paseo-plugin-helper/ui/text.tsx +821 -0
- package/client/vendor/paseo-plugin-helper/ui/theme.tsx +1 -0
- package/client/vendor/permission-audit/client.tsx +706 -0
- package/client/vendor/permission-audit/index.tsx +1 -0
- package/index.client.tsx +58 -37
- package/index.server.ts +256 -234
- package/npm-shrinkwrap.json +44 -0
- package/package.json +8 -4
- package/paseo-plugin.json +2 -0
- package/screenshots/1-paseo-top-modal-system.jpg +0 -0
- package/screenshots/2-paseo-top-modal-workspace.jpg +0 -0
- package/screenshots/3-paseo-top-modal-settings.jpg +0 -0
- package/screenshots/4-paseo-top-modal-about.jpg +0 -0
- package/screenshots/paseo-top-dashboard.jpg +0 -0
- package/screenshots/paseo-top-general-view.jpg +0 -0
- package/server/resources.ts +98 -0
- package/server/vendor/paseo-plugin-helper/custom-pills.ts +70 -0
- package/server/vendor/paseo-plugin-helper/index.ts +2 -0
- package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
- package/server/vendor/paseo-plugin-helper/rpc-guard.ts +2 -2
- package/server/vendor/paseo-plugin-helper/storage-guard.ts +294 -0
- package/server/vendor/paseo-plugin-helper/storage.ts +36 -2
- package/server/vendor/paseo-plugin-helper/telemetry.ts +486 -0
- package/server/vendor/paseo-plugin-helper/tickets.ts +18 -67
- package/server/vendor/permission-audit/index.ts +1 -0
- package/server/vendor/permission-audit/server.ts +839 -0
- package/shared/helper-version.ts +1 -1
- package/shared/multi-host.ts +153 -159
- package/shared/resources.ts +145 -0
- package/shared/vendor/paseo-plugin-helper/async.ts +14 -7
- package/shared/vendor/paseo-plugin-helper/features.ts +244 -0
- package/shared/vendor/paseo-plugin-helper/forge.ts +27 -0
- package/shared/vendor/paseo-plugin-helper/index.ts +1 -0
- package/shared/vendor/paseo-plugin-helper/tickets.ts +122 -28
- package/shared/vendor/permission-audit/filter.ts +61 -0
- package/shared/vendor/permission-audit/index.ts +2 -0
- package/shared/vendor/permission-audit/shared.ts +119 -0
- package/shared/version.ts +1 -1
- package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +0 -493
- package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +0 -250
- package/client/vendor/paseo-plugin-helper/components/Badge.tsx +0 -168
- package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -204
- package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -257
- package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +0 -196
- package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +0 -277
- package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +0 -172
- package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +0 -180
- package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +0 -97
- package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +0 -70
- package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +0 -88
- package/client/vendor/paseo-plugin-helper/components/InteractiveRow.tsx +0 -142
- package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +0 -446
- package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +0 -247
- package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +0 -117
- package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +0 -53
- package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +0 -132
- package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -87
- package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -283
- package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +0 -80
- package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +0 -319
- package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +0 -150
- package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +0 -163
- package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +0 -157
- package/client/vendor/paseo-plugin-helper/components/index.ts +0 -26
- package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -225
- package/client/vendor/paseo-plugin-helper/forge-icon.tsx +0 -79
- package/client/vendor/paseo-plugin-helper/icon.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/index.ts +0 -30
- package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +0 -49
- package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +0 -103
- package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +0 -65
- package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +0 -368
- package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +0 -51
- package/client/vendor/paseo-plugin-helper/layout/Row.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/layout/index.ts +0 -7
- package/client/vendor/paseo-plugin-helper/styles/index.ts +0 -1
- package/client/vendor/paseo-plugin-helper/styles/recipes.ts +0 -550
- package/client/vendor/paseo-plugin-helper/theme/flair.ts +0 -76
- package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +0 -121
- package/client/vendor/paseo-plugin-helper/theme/index.ts +0 -7
- package/client/vendor/paseo-plugin-helper/theme/provider.tsx +0 -178
- package/client/vendor/paseo-plugin-helper/theme/responsive.ts +0 -213
- package/client/vendor/paseo-plugin-helper/theme/tokens.ts +0 -200
- package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +0 -57
- package/client/vendor/paseo-plugin-helper/tickets.tsx +0 -766
- /package/client/vendor/paseo-plugin-helper/{command-center.ts → core/command-center.ts} +0 -0
- /package/client/vendor/paseo-plugin-helper/{snapshot.ts → core/snapshot.ts} +0 -0
- /package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/haptics.ts +0 -0
|
@@ -1,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,132 +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
|
-
/**
|
|
22
|
-
* Painted box height, opt-in per call site. Unset keeps the original
|
|
23
|
-
* `isCompact ? 36 : 40` box so no untouched surface changes appearance.
|
|
24
|
-
* 26 matches the compact `size="sm"` button recipe (~26px) so a search field
|
|
25
|
-
* can sit beside filter pills in one row without inflating it (#645); a
|
|
26
|
-
* height of 26 or less also switches the input to the 12px `sm` label size,
|
|
27
|
-
* anything taller keeps the prior 13/14.
|
|
28
|
-
*/
|
|
29
|
-
height?: number;
|
|
30
|
-
style?: StyleProp<ViewStyle>;
|
|
31
|
-
inputStyle?: StyleProp<TextStyle>;
|
|
32
|
-
testID?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Standardized search input with search icon, clear button, and theme support.
|
|
37
|
-
*/
|
|
38
|
-
export function SearchInput({
|
|
39
|
-
value,
|
|
40
|
-
onChangeText,
|
|
41
|
-
placeholder = "Search...",
|
|
42
|
-
onClear,
|
|
43
|
-
height,
|
|
44
|
-
style,
|
|
45
|
-
inputStyle,
|
|
46
|
-
testID,
|
|
47
|
-
}: SearchInputProps) {
|
|
48
|
-
const { Icon } = getClientHost();
|
|
49
|
-
const ResolvedInput = (getOptionalClientHost()?.TextInput ??
|
|
50
|
-
RNTextInput) as ComponentType<RNTextInputProps & { ref?: Ref<RNTextInputInstance> }>;
|
|
51
|
-
const { colors, resolveRadius, isCompact } = usePluginTheme();
|
|
52
|
-
const radius = resolveRadius("sm");
|
|
53
|
-
// Unspecified keeps the original compact-aware default so every call site
|
|
54
|
-
// that does not opt in renders exactly as before (#645's scope was the
|
|
55
|
-
// uppidi-fleet filter rows only).
|
|
56
|
-
const resolvedHeight = height ?? (isCompact ? 36 : 40);
|
|
57
|
-
|
|
58
|
-
const handleClear = () => {
|
|
59
|
-
onChangeText("");
|
|
60
|
-
if (onClear) onClear();
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<View
|
|
65
|
-
style={[
|
|
66
|
-
styles.container,
|
|
67
|
-
{
|
|
68
|
-
backgroundColor: colors.surface1,
|
|
69
|
-
borderColor: colors.border,
|
|
70
|
-
borderRadius: radius,
|
|
71
|
-
height: resolvedHeight,
|
|
72
|
-
},
|
|
73
|
-
style,
|
|
74
|
-
]}
|
|
75
|
-
>
|
|
76
|
-
<View style={styles.iconWrapper}>
|
|
77
|
-
<Icon name="Search" size={16} color={colors.foregroundMuted} />
|
|
78
|
-
</View>
|
|
79
|
-
<ResolvedInput
|
|
80
|
-
testID={testID}
|
|
81
|
-
value={value}
|
|
82
|
-
onChangeText={onChangeText}
|
|
83
|
-
placeholder={placeholder}
|
|
84
|
-
placeholderTextColor={colors.foregroundMuted}
|
|
85
|
-
style={[
|
|
86
|
-
styles.input,
|
|
87
|
-
{
|
|
88
|
-
color: colors.foreground,
|
|
89
|
-
fontSize: height !== undefined && height <= 26 ? 12 : isCompact ? 13 : 14,
|
|
90
|
-
},
|
|
91
|
-
inputStyle,
|
|
92
|
-
]}
|
|
93
|
-
returnKeyType="search"
|
|
94
|
-
autoCapitalize="none"
|
|
95
|
-
autoCorrect={false}
|
|
96
|
-
/>
|
|
97
|
-
{Boolean(value) && (
|
|
98
|
-
<Pressable
|
|
99
|
-
onPress={handleClear}
|
|
100
|
-
style={styles.clearButton}
|
|
101
|
-
hitSlop={8}
|
|
102
|
-
accessibilityLabel="Clear search"
|
|
103
|
-
>
|
|
104
|
-
<Icon name="X" size={14} color={colors.foregroundMuted} />
|
|
105
|
-
</Pressable>
|
|
106
|
-
)}
|
|
107
|
-
</View>
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const styles = StyleSheet.create({
|
|
112
|
-
container: {
|
|
113
|
-
flexDirection: "row",
|
|
114
|
-
alignItems: "center",
|
|
115
|
-
borderWidth: 1,
|
|
116
|
-
paddingHorizontal: 10,
|
|
117
|
-
},
|
|
118
|
-
iconWrapper: {
|
|
119
|
-
marginRight: 8,
|
|
120
|
-
alignItems: "center",
|
|
121
|
-
justifyContent: "center",
|
|
122
|
-
},
|
|
123
|
-
input: {
|
|
124
|
-
flex: 1,
|
|
125
|
-
paddingVertical: 0,
|
|
126
|
-
outlineWidth: 0,
|
|
127
|
-
},
|
|
128
|
-
clearButton: {
|
|
129
|
-
padding: 4,
|
|
130
|
-
marginLeft: 4,
|
|
131
|
-
},
|
|
132
|
-
});
|
|
@@ -1,87 +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
|
-
numberOfLines={1}
|
|
45
|
-
ellipsizeMode="tail"
|
|
46
|
-
style={[
|
|
47
|
-
styles.title,
|
|
48
|
-
{
|
|
49
|
-
color: theme.colors.foregroundMuted,
|
|
50
|
-
textTransform: headingTransform,
|
|
51
|
-
fontSize: titleType.fontSize,
|
|
52
|
-
lineHeight: titleType.lineHeight,
|
|
53
|
-
fontWeight: titleType.fontWeight,
|
|
54
|
-
},
|
|
55
|
-
textStyle,
|
|
56
|
-
]}
|
|
57
|
-
>
|
|
58
|
-
{title}
|
|
59
|
-
</Text>
|
|
60
|
-
{count !== undefined ? (
|
|
61
|
-
<Badge
|
|
62
|
-
label={String(count)}
|
|
63
|
-
variant={badgeVariant ?? (count > 0 ? "warning" : "neutral")}
|
|
64
|
-
styleVariant={count > 0 ? "solid" : "tinted"}
|
|
65
|
-
/>
|
|
66
|
-
) : null}
|
|
67
|
-
</View>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const styles = StyleSheet.create({
|
|
72
|
-
container: {
|
|
73
|
-
flexDirection: "row",
|
|
74
|
-
alignItems: "center",
|
|
75
|
-
gap: 8,
|
|
76
|
-
marginTop: 8,
|
|
77
|
-
marginBottom: 2,
|
|
78
|
-
flexShrink: 1,
|
|
79
|
-
maxWidth: "100%",
|
|
80
|
-
},
|
|
81
|
-
title: {
|
|
82
|
-
letterSpacing: 0.8,
|
|
83
|
-
// Section titles are caller-supplied and unbounded; the title must be
|
|
84
|
-
// allowed to compress for `numberOfLines` to engage.
|
|
85
|
-
flexShrink: 1,
|
|
86
|
-
},
|
|
87
|
-
});
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
import React, { useRef, useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Modal,
|
|
4
|
-
Pressable,
|
|
5
|
-
ScrollView as FallbackScrollView,
|
|
6
|
-
StyleSheet,
|
|
7
|
-
Text,
|
|
8
|
-
TouchableWithoutFeedback,
|
|
9
|
-
View,
|
|
10
|
-
type StyleProp,
|
|
11
|
-
type ViewStyle,
|
|
12
|
-
} from "react-native";
|
|
13
|
-
import { getClientHost } from "../host";
|
|
14
|
-
import { usePluginTheme } from "../theme/provider";
|
|
15
|
-
import { spacing } from "../theme/tokens";
|
|
16
|
-
import type { BadgeSize } from "./Badge";
|
|
17
|
-
|
|
18
|
-
export interface SelectOption {
|
|
19
|
-
label: string;
|
|
20
|
-
value: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface SelectProps {
|
|
24
|
-
value: string;
|
|
25
|
-
options: SelectOption[];
|
|
26
|
-
onValueChange: (value: string) => void;
|
|
27
|
-
label?: string;
|
|
28
|
-
/** Compact/pill scale shared with {@link Badge}: `"md"` (default) or `"sm"`. */
|
|
29
|
-
size?: BadgeSize;
|
|
30
|
-
placeholder?: string;
|
|
31
|
-
disabled?: boolean;
|
|
32
|
-
style?: StyleProp<ViewStyle>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const OPTION_LIST_MAX_HEIGHT = 216;
|
|
36
|
-
|
|
37
|
-
interface TriggerCoords {
|
|
38
|
-
x: number;
|
|
39
|
-
y: number;
|
|
40
|
-
width: number;
|
|
41
|
-
height: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// When `measureInWindow` is unavailable or never invokes its callback (node
|
|
45
|
-
// test renderers, host shims), the menu still opens anchored to a zeroed box
|
|
46
|
-
// rather than swallowing the press.
|
|
47
|
-
const FALLBACK_COORDS: TriggerCoords = { x: 0, y: 0, width: 0, height: 0 };
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Compact single-choice picker sized to sit inside a {@link FormRow}. The
|
|
51
|
-
* closed trigger stays one line tall; opening mounts the menu in a root
|
|
52
|
-
* transparent `<Modal>` overlay portal, so a long list never expands the
|
|
53
|
-
* trigger's parent container, is never clipped by an ancestor
|
|
54
|
-
* `overflow: "hidden"` (Card/Tabs/Modal), and always paints above later
|
|
55
|
-
* siblings regardless of local stacking context.
|
|
56
|
-
*/
|
|
57
|
-
export function Select({
|
|
58
|
-
value,
|
|
59
|
-
options,
|
|
60
|
-
onValueChange,
|
|
61
|
-
label,
|
|
62
|
-
size = "md",
|
|
63
|
-
placeholder = "Select…",
|
|
64
|
-
disabled = false,
|
|
65
|
-
style,
|
|
66
|
-
}: SelectProps) {
|
|
67
|
-
const { Icon } = getClientHost();
|
|
68
|
-
// The option list is mounted in the root modal overlay, so it scrolls with
|
|
69
|
-
// a plain React Native ScrollView — never the host sheet-gesture scroller
|
|
70
|
-
// (#219) and never a scroller owned by the trigger's parent.
|
|
71
|
-
const { colors, resolveRadius, typography, isCompact, touchTargetMin, alpha } =
|
|
72
|
-
usePluginTheme();
|
|
73
|
-
const [open, setOpen] = useState(false);
|
|
74
|
-
const [coords, setCoords] = useState<TriggerCoords | null>(null);
|
|
75
|
-
const triggerRef = useRef<View>(null);
|
|
76
|
-
|
|
77
|
-
const caption = typography?.caption ?? {
|
|
78
|
-
fontSize: 11,
|
|
79
|
-
lineHeight: 15,
|
|
80
|
-
fontWeight: "400" as const,
|
|
81
|
-
};
|
|
82
|
-
const fontSize = size === "sm" ? 10 : caption.fontSize;
|
|
83
|
-
const lineHeight = size === "sm" ? 12 : caption.lineHeight;
|
|
84
|
-
const paddingVertical = size === "sm" ? 3 : 6;
|
|
85
|
-
const paddingHorizontal = size === "sm" ? 8 : 10;
|
|
86
|
-
const iconSize = size === "sm" ? 12 : 14;
|
|
87
|
-
const radius = resolveRadius("md");
|
|
88
|
-
|
|
89
|
-
// A free-text value (advanced entry) may not be one of the options; surface
|
|
90
|
-
// it on the trigger rather than masking it behind the placeholder.
|
|
91
|
-
const selected = options.find((option) => option.value === value);
|
|
92
|
-
const display = selected?.label ?? (value ? value : placeholder);
|
|
93
|
-
const canOpen = !disabled && options.length > 0;
|
|
94
|
-
const isOpen = open && canOpen;
|
|
95
|
-
|
|
96
|
-
const triggerMinHeight = size === "sm" ? 28 : Math.max(34, touchTargetMin);
|
|
97
|
-
|
|
98
|
-
const openAt = (next: TriggerCoords) => {
|
|
99
|
-
setCoords(next);
|
|
100
|
-
setOpen(true);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const handleToggle = () => {
|
|
104
|
-
if (isOpen) {
|
|
105
|
-
setOpen(false);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const node = triggerRef.current;
|
|
110
|
-
// A node/shim without `measureInWindow` (node test renderers, stripped
|
|
111
|
-
// host shims) still opens anchored to a zeroed box rather than swallowing
|
|
112
|
-
// the press (#520).
|
|
113
|
-
if (!node || typeof node.measureInWindow !== "function") {
|
|
114
|
-
openAt(FALLBACK_COORDS);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
node.measureInWindow((x, y, width, height) => {
|
|
119
|
-
openAt({ x, y, width, height });
|
|
120
|
-
});
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
<View style={[styles.container, style]}>
|
|
125
|
-
<Pressable
|
|
126
|
-
ref={triggerRef}
|
|
127
|
-
accessibilityRole="button"
|
|
128
|
-
accessibilityLabel={label ? `${label}: ${display}` : display}
|
|
129
|
-
accessibilityState={{ expanded: isOpen, disabled }}
|
|
130
|
-
disabled={!canOpen}
|
|
131
|
-
onPress={handleToggle}
|
|
132
|
-
style={({ pressed }) => [
|
|
133
|
-
styles.trigger,
|
|
134
|
-
{
|
|
135
|
-
minHeight: triggerMinHeight,
|
|
136
|
-
borderRadius: radius,
|
|
137
|
-
borderColor: isOpen ? colors.accent : colors.border,
|
|
138
|
-
backgroundColor:
|
|
139
|
-
disabled || !canOpen
|
|
140
|
-
? alpha(colors.surface1, 0.5)
|
|
141
|
-
: pressed
|
|
142
|
-
? colors.surface2
|
|
143
|
-
: colors.surface0,
|
|
144
|
-
paddingVertical,
|
|
145
|
-
paddingHorizontal,
|
|
146
|
-
},
|
|
147
|
-
]}
|
|
148
|
-
>
|
|
149
|
-
<Text
|
|
150
|
-
numberOfLines={1}
|
|
151
|
-
style={[
|
|
152
|
-
styles.triggerText,
|
|
153
|
-
{
|
|
154
|
-
color: disabled || !canOpen ? colors.foregroundMuted : colors.foreground,
|
|
155
|
-
fontSize,
|
|
156
|
-
lineHeight,
|
|
157
|
-
fontWeight: selected ? "600" : "500",
|
|
158
|
-
},
|
|
159
|
-
]}
|
|
160
|
-
>
|
|
161
|
-
{display}
|
|
162
|
-
</Text>
|
|
163
|
-
<Icon
|
|
164
|
-
name={isOpen ? "ChevronUp" : "ChevronDown"}
|
|
165
|
-
size={iconSize}
|
|
166
|
-
color={colors.foregroundMuted}
|
|
167
|
-
/>
|
|
168
|
-
</Pressable>
|
|
169
|
-
|
|
170
|
-
{isOpen && coords ? (
|
|
171
|
-
<Modal
|
|
172
|
-
transparent
|
|
173
|
-
visible={isOpen}
|
|
174
|
-
animationType="none"
|
|
175
|
-
onRequestClose={() => setOpen(false)}
|
|
176
|
-
>
|
|
177
|
-
<TouchableWithoutFeedback
|
|
178
|
-
accessibilityRole="button"
|
|
179
|
-
accessibilityLabel="Dismiss options"
|
|
180
|
-
onPress={() => setOpen(false)}
|
|
181
|
-
>
|
|
182
|
-
<View style={StyleSheet.absoluteFill} />
|
|
183
|
-
</TouchableWithoutFeedback>
|
|
184
|
-
|
|
185
|
-
<View
|
|
186
|
-
style={[
|
|
187
|
-
styles.overlayList,
|
|
188
|
-
{
|
|
189
|
-
top: coords.y + coords.height + spacing.xs,
|
|
190
|
-
left: coords.x,
|
|
191
|
-
minWidth: coords.width,
|
|
192
|
-
borderRadius: radius,
|
|
193
|
-
borderColor: colors.border,
|
|
194
|
-
backgroundColor: colors.surface0,
|
|
195
|
-
},
|
|
196
|
-
]}
|
|
197
|
-
>
|
|
198
|
-
<FallbackScrollView
|
|
199
|
-
nestedScrollEnabled
|
|
200
|
-
keyboardShouldPersistTaps="handled"
|
|
201
|
-
style={{ maxHeight: OPTION_LIST_MAX_HEIGHT }}
|
|
202
|
-
>
|
|
203
|
-
{options.map((option) => {
|
|
204
|
-
const isSelected = option.value === value;
|
|
205
|
-
return (
|
|
206
|
-
<Pressable
|
|
207
|
-
key={option.value}
|
|
208
|
-
accessibilityRole="button"
|
|
209
|
-
accessibilityLabel={option.label}
|
|
210
|
-
accessibilityState={{ selected: isSelected }}
|
|
211
|
-
onPress={() => {
|
|
212
|
-
onValueChange(option.value);
|
|
213
|
-
setOpen(false);
|
|
214
|
-
}}
|
|
215
|
-
style={({ pressed }) => [
|
|
216
|
-
styles.option,
|
|
217
|
-
{
|
|
218
|
-
paddingVertical,
|
|
219
|
-
paddingHorizontal,
|
|
220
|
-
backgroundColor: isSelected
|
|
221
|
-
? colors.surface2
|
|
222
|
-
: pressed
|
|
223
|
-
? alpha(colors.surface2, 0.5)
|
|
224
|
-
: "transparent",
|
|
225
|
-
},
|
|
226
|
-
]}
|
|
227
|
-
>
|
|
228
|
-
<Text
|
|
229
|
-
numberOfLines={1}
|
|
230
|
-
style={[
|
|
231
|
-
styles.optionText,
|
|
232
|
-
{
|
|
233
|
-
color: isSelected ? colors.foreground : colors.foregroundMuted,
|
|
234
|
-
fontSize: isCompact ? fontSize : fontSize + 1,
|
|
235
|
-
lineHeight,
|
|
236
|
-
fontWeight: isSelected ? "600" : "400",
|
|
237
|
-
},
|
|
238
|
-
]}
|
|
239
|
-
>
|
|
240
|
-
{option.label}
|
|
241
|
-
</Text>
|
|
242
|
-
</Pressable>
|
|
243
|
-
);
|
|
244
|
-
})}
|
|
245
|
-
</FallbackScrollView>
|
|
246
|
-
</View>
|
|
247
|
-
</Modal>
|
|
248
|
-
) : null}
|
|
249
|
-
</View>
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const styles = StyleSheet.create({
|
|
254
|
-
container: {
|
|
255
|
-
width: "100%",
|
|
256
|
-
},
|
|
257
|
-
trigger: {
|
|
258
|
-
flexDirection: "row",
|
|
259
|
-
alignItems: "center",
|
|
260
|
-
justifyContent: "space-between",
|
|
261
|
-
gap: spacing.sm,
|
|
262
|
-
borderWidth: 1,
|
|
263
|
-
},
|
|
264
|
-
triggerText: {
|
|
265
|
-
flexShrink: 1,
|
|
266
|
-
minWidth: 0,
|
|
267
|
-
},
|
|
268
|
-
overlayList: {
|
|
269
|
-
position: "absolute",
|
|
270
|
-
borderWidth: 1,
|
|
271
|
-
overflow: "hidden",
|
|
272
|
-
elevation: 20,
|
|
273
|
-
shadowColor: "#000",
|
|
274
|
-
shadowOffset: { width: 0, height: 4 },
|
|
275
|
-
shadowOpacity: 0.25,
|
|
276
|
-
shadowRadius: 8,
|
|
277
|
-
},
|
|
278
|
-
option: {},
|
|
279
|
-
optionText: {
|
|
280
|
-
flexShrink: 1,
|
|
281
|
-
minWidth: 0,
|
|
282
|
-
},
|
|
283
|
-
});
|