@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,97 +0,0 @@
|
|
|
1
|
-
import React, { type ReactNode } from "react";
|
|
2
|
-
import { StyleSheet, Text, View, type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
-
import { getClientHost } from "../host";
|
|
4
|
-
import { usePluginTheme } from "../theme/provider";
|
|
5
|
-
import { Button, type ButtonProps } from "./Button";
|
|
6
|
-
|
|
7
|
-
export interface EmptyStateProps {
|
|
8
|
-
icon?: string | ReactNode;
|
|
9
|
-
title: string;
|
|
10
|
-
description?: string;
|
|
11
|
-
action?: ButtonProps;
|
|
12
|
-
actionLabel?: string;
|
|
13
|
-
onAction?: () => void;
|
|
14
|
-
style?: StyleProp<ViewStyle>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function EmptyState({
|
|
18
|
-
icon = "Inbox",
|
|
19
|
-
title,
|
|
20
|
-
description,
|
|
21
|
-
action,
|
|
22
|
-
actionLabel,
|
|
23
|
-
onAction,
|
|
24
|
-
style,
|
|
25
|
-
}: EmptyStateProps) {
|
|
26
|
-
const { Icon } = getClientHost();
|
|
27
|
-
const { colors, isCompact, typography, padding } = usePluginTheme();
|
|
28
|
-
|
|
29
|
-
const resolvedAction: ButtonProps | undefined = action
|
|
30
|
-
? action
|
|
31
|
-
: actionLabel && onAction
|
|
32
|
-
? { label: actionLabel, onPress: onAction, variant: "secondary" }
|
|
33
|
-
: undefined;
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<View style={[styles.container, { padding: isCompact ? padding.horizontal + 8 : padding.horizontal * 2 }, style]}>
|
|
37
|
-
{icon ? (
|
|
38
|
-
typeof icon === "string" ? (
|
|
39
|
-
<View style={[styles.iconWrapper, { backgroundColor: colors.surface1 }]}>
|
|
40
|
-
<Icon name={icon} size={isCompact ? 24 : 32} color={colors.foregroundMuted} />
|
|
41
|
-
</View>
|
|
42
|
-
) : (
|
|
43
|
-
icon
|
|
44
|
-
)
|
|
45
|
-
) : null}
|
|
46
|
-
|
|
47
|
-
<Text style={[styles.title, { color: colors.foreground, ...typography.title }]}>
|
|
48
|
-
{title}
|
|
49
|
-
</Text>
|
|
50
|
-
|
|
51
|
-
{description ? (
|
|
52
|
-
<Text
|
|
53
|
-
style={[
|
|
54
|
-
styles.description,
|
|
55
|
-
{ color: colors.foregroundMuted, ...typography.body },
|
|
56
|
-
]}
|
|
57
|
-
>
|
|
58
|
-
{description}
|
|
59
|
-
</Text>
|
|
60
|
-
) : null}
|
|
61
|
-
|
|
62
|
-
{resolvedAction ? (
|
|
63
|
-
<View style={styles.actionRow}>
|
|
64
|
-
<Button size={isCompact ? "sm" : "md"} {...resolvedAction} />
|
|
65
|
-
</View>
|
|
66
|
-
) : null}
|
|
67
|
-
</View>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const styles = StyleSheet.create({
|
|
72
|
-
container: {
|
|
73
|
-
alignItems: "center",
|
|
74
|
-
justifyContent: "center",
|
|
75
|
-
gap: 8,
|
|
76
|
-
},
|
|
77
|
-
iconWrapper: {
|
|
78
|
-
width: 56,
|
|
79
|
-
height: 56,
|
|
80
|
-
borderRadius: 28,
|
|
81
|
-
alignItems: "center",
|
|
82
|
-
justifyContent: "center",
|
|
83
|
-
marginBottom: 4,
|
|
84
|
-
},
|
|
85
|
-
title: {
|
|
86
|
-
fontWeight: "600",
|
|
87
|
-
textAlign: "center",
|
|
88
|
-
},
|
|
89
|
-
description: {
|
|
90
|
-
textAlign: "center",
|
|
91
|
-
maxWidth: 280,
|
|
92
|
-
lineHeight: 18,
|
|
93
|
-
},
|
|
94
|
-
actionRow: {
|
|
95
|
-
marginTop: 8,
|
|
96
|
-
},
|
|
97
|
-
});
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import React, { useMemo } from "react";
|
|
2
|
-
import { Text, type StyleProp, type TextStyle } from "react-native";
|
|
3
|
-
import { splitHighlightParts } from "../../../../shared/vendor/paseo-plugin-helper/highlight";
|
|
4
|
-
import { usePluginTheme } from "../theme/provider";
|
|
5
|
-
|
|
6
|
-
export interface HighlightedTextProps {
|
|
7
|
-
/** Source text rendered as-is when no query is active. */
|
|
8
|
-
text: string;
|
|
9
|
-
/**
|
|
10
|
-
* Active search query; matched case-insensitively and never as a regex. When
|
|
11
|
-
* the literal query is absent the query's tokens are highlighted, so a fuzzy
|
|
12
|
-
* result still reads.
|
|
13
|
-
*/
|
|
14
|
-
query: string;
|
|
15
|
-
style?: StyleProp<TextStyle>;
|
|
16
|
-
/**
|
|
17
|
-
* Overrides the matched-run style. Defaults to the theme accent background
|
|
18
|
-
* with `accentForeground` text so a match reads as selected.
|
|
19
|
-
*/
|
|
20
|
-
highlightStyle?: StyleProp<TextStyle>;
|
|
21
|
-
numberOfLines?: number;
|
|
22
|
-
selectable?: boolean;
|
|
23
|
-
/**
|
|
24
|
-
* When true, a query with no literal or token hit marks the whole text, so a
|
|
25
|
-
* fuzzy search result still reads as matched. Use it on the primary label
|
|
26
|
-
* (row/detail title) rather than every small field.
|
|
27
|
-
*/
|
|
28
|
-
fuzzyFallback?: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* `<Text>` that paints every case-insensitive occurrence of `query` with the
|
|
33
|
-
* accent background/foreground. The query is matched literally, so user input
|
|
34
|
-
* is never evaluated as a regular expression. When the literal query is absent
|
|
35
|
-
* the splitter highlights the query's tokens; `fuzzyFallback` additionally
|
|
36
|
-
* marks the whole text when even a token misses, so a fuzzy result is not left
|
|
37
|
-
* silently unmarked. Renders the plain text when the query is empty or absent.
|
|
38
|
-
*/
|
|
39
|
-
export function HighlightedText({
|
|
40
|
-
text,
|
|
41
|
-
query,
|
|
42
|
-
style,
|
|
43
|
-
highlightStyle,
|
|
44
|
-
numberOfLines,
|
|
45
|
-
selectable,
|
|
46
|
-
fuzzyFallback = false,
|
|
47
|
-
}: HighlightedTextProps) {
|
|
48
|
-
const { colors } = usePluginTheme();
|
|
49
|
-
const parts = useMemo(
|
|
50
|
-
() => splitHighlightParts(text, query, { fallbackToWholeField: fuzzyFallback }),
|
|
51
|
-
[text, query, fuzzyFallback],
|
|
52
|
-
);
|
|
53
|
-
const matchStyle: StyleProp<TextStyle> = [
|
|
54
|
-
{ backgroundColor: colors.accent, color: colors.accentForeground },
|
|
55
|
-
highlightStyle,
|
|
56
|
-
];
|
|
57
|
-
return (
|
|
58
|
-
<Text style={style} numberOfLines={numberOfLines} selectable={selectable}>
|
|
59
|
-
{parts.map((part, index) =>
|
|
60
|
-
part.matched ? (
|
|
61
|
-
<Text key={`m${index}`} style={matchStyle}>
|
|
62
|
-
{part.text}
|
|
63
|
-
</Text>
|
|
64
|
-
) : (
|
|
65
|
-
part.text
|
|
66
|
-
),
|
|
67
|
-
)}
|
|
68
|
-
</Text>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import React, { type ReactNode } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Pressable,
|
|
4
|
-
StyleSheet,
|
|
5
|
-
Text,
|
|
6
|
-
type StyleProp,
|
|
7
|
-
type TextStyle,
|
|
8
|
-
type ViewStyle,
|
|
9
|
-
} from "react-native";
|
|
10
|
-
import { getClientHost } from "../host";
|
|
11
|
-
import { usePluginTheme } from "../theme/provider";
|
|
12
|
-
|
|
13
|
-
export interface InlineButtonProps {
|
|
14
|
-
label: string;
|
|
15
|
-
onPress?: () => void | Promise<void>;
|
|
16
|
-
icon?: string | ReactNode;
|
|
17
|
-
disabled?: boolean;
|
|
18
|
-
accessibilityLabel?: string;
|
|
19
|
-
accessibilityRole?: "button" | "link";
|
|
20
|
-
style?: StyleProp<ViewStyle>;
|
|
21
|
-
textStyle?: StyleProp<TextStyle>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Compact text/link action for inline cards and timeline content. */
|
|
25
|
-
export function InlineButton({
|
|
26
|
-
label,
|
|
27
|
-
onPress,
|
|
28
|
-
icon,
|
|
29
|
-
disabled = false,
|
|
30
|
-
accessibilityLabel,
|
|
31
|
-
accessibilityRole = "button",
|
|
32
|
-
style,
|
|
33
|
-
textStyle,
|
|
34
|
-
}: InlineButtonProps) {
|
|
35
|
-
const { Icon } = getClientHost();
|
|
36
|
-
const { colors, touchTargetMin, alpha } = usePluginTheme();
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<Pressable
|
|
40
|
-
onPress={onPress}
|
|
41
|
-
disabled={disabled}
|
|
42
|
-
accessibilityRole={accessibilityRole}
|
|
43
|
-
accessibilityLabel={accessibilityLabel || label}
|
|
44
|
-
hitSlop={Math.max(0, (touchTargetMin - 24) / 2)}
|
|
45
|
-
style={({ pressed }) => [
|
|
46
|
-
styles.base,
|
|
47
|
-
{
|
|
48
|
-
opacity: disabled ? 0.45 : 1,
|
|
49
|
-
backgroundColor: pressed && !disabled ? alpha(colors.accent, 0.12) : "transparent",
|
|
50
|
-
},
|
|
51
|
-
style,
|
|
52
|
-
]}
|
|
53
|
-
>
|
|
54
|
-
{typeof icon === "string" ? <Icon name={icon} size={13} color={colors.accent} /> : icon}
|
|
55
|
-
<Text
|
|
56
|
-
accessibilityLabel={label}
|
|
57
|
-
numberOfLines={1}
|
|
58
|
-
ellipsizeMode="tail"
|
|
59
|
-
style={[styles.text, { color: colors.accent }, textStyle]}
|
|
60
|
-
>
|
|
61
|
-
{label}
|
|
62
|
-
</Text>
|
|
63
|
-
</Pressable>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const styles = StyleSheet.create({
|
|
68
|
-
base: {
|
|
69
|
-
alignSelf: "flex-start",
|
|
70
|
-
flexDirection: "row",
|
|
71
|
-
alignItems: "center",
|
|
72
|
-
gap: 4,
|
|
73
|
-
minHeight: 24,
|
|
74
|
-
paddingHorizontal: 3,
|
|
75
|
-
paddingVertical: 2,
|
|
76
|
-
borderRadius: 4,
|
|
77
|
-
// Caller-supplied labels are unbounded; without a shrink budget the control
|
|
78
|
-
// claims its full text width and overflows narrow viewports.
|
|
79
|
-
flexShrink: 1,
|
|
80
|
-
maxWidth: "100%",
|
|
81
|
-
},
|
|
82
|
-
text: {
|
|
83
|
-
fontSize: 12,
|
|
84
|
-
fontWeight: "600",
|
|
85
|
-
// The label must be allowed to compress for `numberOfLines` to engage.
|
|
86
|
-
flexShrink: 1,
|
|
87
|
-
},
|
|
88
|
-
});
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useState, type ReactNode } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Pressable,
|
|
4
|
-
type AccessibilityRole,
|
|
5
|
-
type GestureResponderEvent,
|
|
6
|
-
type StyleProp,
|
|
7
|
-
type ViewStyle,
|
|
8
|
-
} from "react-native";
|
|
9
|
-
import { usePluginTheme } from "../theme/provider";
|
|
10
|
-
|
|
11
|
-
export interface InteractiveRowProps {
|
|
12
|
-
/** Arbitrary row content — status dots, badges, text, metric readouts. */
|
|
13
|
-
children?: ReactNode;
|
|
14
|
-
/**
|
|
15
|
-
* Press handler. Receives the raw event so callers can control propagation
|
|
16
|
-
* (e.g. a nested status light calling `event.stopPropagation()`).
|
|
17
|
-
*/
|
|
18
|
-
onPress?: (event: GestureResponderEvent) => void;
|
|
19
|
-
/** Native / RN-web tooltip text attached to the hit area. */
|
|
20
|
-
title?: string;
|
|
21
|
-
disabled?: boolean;
|
|
22
|
-
accessibilityRole?: AccessibilityRole;
|
|
23
|
-
accessibilityLabel?: string;
|
|
24
|
-
accessibilityHint?: string;
|
|
25
|
-
testID?: string;
|
|
26
|
-
/** Base layout style, merged before the interaction layers. */
|
|
27
|
-
style?: StyleProp<ViewStyle>;
|
|
28
|
-
/** Extra style layered only while hovered (and not disabled). */
|
|
29
|
-
hoverStyle?: StyleProp<ViewStyle>;
|
|
30
|
-
/** Apply a subtle theme accent tint while hovered. Default: false. */
|
|
31
|
-
hoverTint?: boolean;
|
|
32
|
-
/** Accent opacity used by `hoverTint`. Default: 0.05. */
|
|
33
|
-
hoverTintOpacity?: number;
|
|
34
|
-
/** Opacity while pressed. Default: 0.7. */
|
|
35
|
-
pressedOpacity?: number;
|
|
36
|
-
/** Opacity while hovered but not pressed. Defaults to `opacity`. */
|
|
37
|
-
hoveredOpacity?: number;
|
|
38
|
-
/** Opacity at rest. Default: 1. */
|
|
39
|
-
opacity?: number;
|
|
40
|
-
/** Opacity when disabled. Default: 0.45. */
|
|
41
|
-
disabledOpacity?: number;
|
|
42
|
-
/** Expanded touch target beyond the visual bounds. */
|
|
43
|
-
hitSlop?: number;
|
|
44
|
-
/** Notified whenever the hover state flips, for callers that tint their own children. */
|
|
45
|
-
onHoverChange?: (hovered: boolean) => void;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Hover-aware, pressable row container for dense interactive content (#580).
|
|
50
|
-
*
|
|
51
|
-
* Owns the interaction concerns a labeled `Button` cannot: a hover state (with
|
|
52
|
-
* optional accent tint), an RN-web `title` tooltip, pressed opacity, a pointer
|
|
53
|
-
* cursor, and an `onPress` that forwards the event for `stopPropagation`.
|
|
54
|
-
* Content is arbitrary, so a status dot, badges, and metric readouts compose
|
|
55
|
-
* inside without wrapping each in its own control.
|
|
56
|
-
*/
|
|
57
|
-
export function InteractiveRow({
|
|
58
|
-
children,
|
|
59
|
-
onPress,
|
|
60
|
-
title,
|
|
61
|
-
disabled = false,
|
|
62
|
-
accessibilityRole,
|
|
63
|
-
accessibilityLabel,
|
|
64
|
-
accessibilityHint,
|
|
65
|
-
testID,
|
|
66
|
-
style,
|
|
67
|
-
hoverStyle,
|
|
68
|
-
hoverTint = false,
|
|
69
|
-
hoverTintOpacity = 0.05,
|
|
70
|
-
pressedOpacity = 0.7,
|
|
71
|
-
hoveredOpacity,
|
|
72
|
-
opacity = 1,
|
|
73
|
-
disabledOpacity = 0.45,
|
|
74
|
-
hitSlop,
|
|
75
|
-
onHoverChange,
|
|
76
|
-
}: InteractiveRowProps) {
|
|
77
|
-
const { colors, alpha } = usePluginTheme();
|
|
78
|
-
const [hovered, setHovered] = useState(false);
|
|
79
|
-
const [pressed, setPressed] = useState(false);
|
|
80
|
-
|
|
81
|
-
const handleMouseEnter = useCallback(() => {
|
|
82
|
-
if (disabled) return;
|
|
83
|
-
setHovered(true);
|
|
84
|
-
onHoverChange?.(true);
|
|
85
|
-
}, [disabled, onHoverChange]);
|
|
86
|
-
|
|
87
|
-
const handleMouseLeave = useCallback(() => {
|
|
88
|
-
setHovered(false);
|
|
89
|
-
onHoverChange?.(false);
|
|
90
|
-
}, [onHoverChange]);
|
|
91
|
-
|
|
92
|
-
// `title` is an RN-web DOM passthrough absent from the native prop types, and
|
|
93
|
-
// `onMouseEnter`/`onMouseLeave` are the RN-web hover hooks the dense rows
|
|
94
|
-
// already relied on; attach both via an untyped spread to keep the runtime
|
|
95
|
-
// semantics identical to the hand-rolled rows (#580).
|
|
96
|
-
const webProps = {
|
|
97
|
-
onMouseEnter: handleMouseEnter,
|
|
98
|
-
onMouseLeave: handleMouseLeave,
|
|
99
|
-
...(title ? { title } : {}),
|
|
100
|
-
} as any;
|
|
101
|
-
|
|
102
|
-
const interactive = hovered && !disabled;
|
|
103
|
-
const hoverBackground = interactive && hoverTint
|
|
104
|
-
? { backgroundColor: alpha(colors.accent, hoverTintOpacity) }
|
|
105
|
-
: null;
|
|
106
|
-
const restingOpacity = interactive ? (hoveredOpacity ?? opacity) : opacity;
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<Pressable
|
|
110
|
-
{...webProps}
|
|
111
|
-
onPress={onPress}
|
|
112
|
-
onPressIn={() => setPressed(true)}
|
|
113
|
-
onPressOut={() => setPressed(false)}
|
|
114
|
-
disabled={disabled}
|
|
115
|
-
accessibilityRole={accessibilityRole ?? (onPress ? "button" : undefined)}
|
|
116
|
-
accessibilityLabel={accessibilityLabel}
|
|
117
|
-
accessibilityHint={accessibilityHint}
|
|
118
|
-
testID={testID}
|
|
119
|
-
hitSlop={hitSlop}
|
|
120
|
-
style={[
|
|
121
|
-
{ cursor: onPress && !disabled ? "pointer" : "auto" },
|
|
122
|
-
// A pressable wraps arbitrary content (titles, badges, metric readouts)
|
|
123
|
-
// and must never widen its parent: Yoga defaults `flexShrink` to 0, so
|
|
124
|
-
// without a shrink budget a long label forces the whole row past the
|
|
125
|
-
// viewport. Declared before `style` so callers can still override.
|
|
126
|
-
{ flexShrink: 1, maxWidth: "100%" },
|
|
127
|
-
style,
|
|
128
|
-
hoverBackground,
|
|
129
|
-
interactive ? hoverStyle : null,
|
|
130
|
-
{
|
|
131
|
-
opacity: disabled
|
|
132
|
-
? disabledOpacity
|
|
133
|
-
: pressed
|
|
134
|
-
? pressedOpacity
|
|
135
|
-
: restingOpacity,
|
|
136
|
-
},
|
|
137
|
-
]}
|
|
138
|
-
>
|
|
139
|
-
{children}
|
|
140
|
-
</Pressable>
|
|
141
|
-
);
|
|
142
|
-
}
|