@xpufx/paseo-top 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/OVERVIEW.md +17 -0
  2. package/client/host-ui.tsx +184 -0
  3. package/client/multi-host-view.tsx +57 -43
  4. package/client/multi-host.ts +36 -13
  5. package/client/pill-labels.ts +124 -7
  6. package/client/pill.tsx +724 -669
  7. package/client/resources-query.ts +1 -1
  8. package/client/settings-events.ts +33 -0
  9. package/client/settings-ui.tsx +86 -9
  10. package/client/surface.tsx +310 -274
  11. package/client/telemetry-copy.ts +8 -2
  12. package/client/telemetry.tsx +372 -321
  13. package/client/turn-panel.tsx +126 -104
  14. package/client/vendor/paseo-plugin-helper/{host.ts → core/host.ts} +21 -61
  15. package/client/vendor/paseo-plugin-helper/core/index.ts +76 -0
  16. package/client/vendor/paseo-plugin-helper/core/plugin-load-count.ts +89 -0
  17. package/client/vendor/paseo-plugin-helper/{query-refresh.ts → core/query-refresh.ts} +2 -2
  18. package/client/vendor/paseo-plugin-helper/{query.ts → core/query.ts} +2 -2
  19. package/client/vendor/paseo-plugin-helper/{settings.ts → core/settings.ts} +1 -1
  20. package/client/vendor/paseo-plugin-helper/{shared-settings.ts → core/shared-settings.ts} +2 -2
  21. package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/clipboard.ts +2 -2
  22. package/client/vendor/paseo-plugin-helper/lifecycle/flair.ts +26 -0
  23. package/client/vendor/paseo-plugin-helper/{theme/color-utils.ts → lifecycle/host-color.ts} +95 -38
  24. package/client/vendor/paseo-plugin-helper/lifecycle/host-theme.tsx +109 -0
  25. package/client/vendor/paseo-plugin-helper/lifecycle/index.ts +23 -0
  26. package/client/vendor/paseo-plugin-helper/lifecycle/multi-host.ts +66 -0
  27. package/client/vendor/paseo-plugin-helper/{panel.tsx → lifecycle/panel.tsx} +7 -7
  28. package/client/vendor/paseo-plugin-helper/{pill.tsx → lifecycle/pill.tsx} +118 -338
  29. package/client/vendor/paseo-plugin-helper/lifecycle/providers.tsx +46 -0
  30. package/client/vendor/paseo-plugin-helper/lifecycle/scroll-owner.ts +26 -0
  31. package/client/vendor/paseo-plugin-helper/lifecycle/surface.tsx +140 -0
  32. package/client/vendor/paseo-plugin-helper/ui/color.ts +1 -0
  33. package/client/vendor/paseo-plugin-helper/ui/content.tsx +1326 -0
  34. package/client/vendor/paseo-plugin-helper/ui/controls.tsx +1018 -0
  35. package/client/vendor/paseo-plugin-helper/{components/DataTable.tsx → ui/data.tsx} +55 -47
  36. package/client/vendor/paseo-plugin-helper/ui/index.ts +44 -0
  37. package/client/vendor/paseo-plugin-helper/ui/layout.tsx +263 -0
  38. package/client/vendor/paseo-plugin-helper/ui/modal.tsx +111 -0
  39. package/client/vendor/paseo-plugin-helper/ui/plugin-reload-banner.tsx +119 -0
  40. package/client/vendor/paseo-plugin-helper/{settings-screen.tsx → ui/settings-screen.tsx} +3 -3
  41. package/client/vendor/paseo-plugin-helper/ui/settings.ts +36 -0
  42. package/client/vendor/paseo-plugin-helper/ui/text.tsx +821 -0
  43. package/client/vendor/paseo-plugin-helper/ui/theme.tsx +1 -0
  44. package/client/vendor/permission-audit/client.tsx +706 -0
  45. package/client/vendor/permission-audit/index.tsx +1 -0
  46. package/index.client.tsx +58 -37
  47. package/index.server.ts +256 -227
  48. package/package.json +14 -2
  49. package/paseo-plugin.json +2 -0
  50. package/screenshots/1-paseo-top-modal-system.jpg +0 -0
  51. package/screenshots/2-paseo-top-modal-workspace.jpg +0 -0
  52. package/screenshots/3-paseo-top-modal-settings.jpg +0 -0
  53. package/screenshots/4-paseo-top-modal-about.jpg +0 -0
  54. package/screenshots/paseo-top-dashboard.jpg +0 -0
  55. package/screenshots/paseo-top-general-view.jpg +0 -0
  56. package/server/resources.ts +98 -0
  57. package/server/vendor/paseo-plugin-helper/custom-pills.ts +70 -0
  58. package/server/vendor/paseo-plugin-helper/index.ts +4 -0
  59. package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
  60. package/server/vendor/paseo-plugin-helper/mcp-injection.ts +20 -2
  61. package/server/vendor/paseo-plugin-helper/plugins.ts +184 -19
  62. package/server/vendor/paseo-plugin-helper/rpc-guard.ts +2 -2
  63. package/server/vendor/paseo-plugin-helper/storage-guard.ts +294 -0
  64. package/server/vendor/paseo-plugin-helper/storage.ts +36 -2
  65. package/server/vendor/paseo-plugin-helper/telemetry.ts +486 -0
  66. package/server/vendor/paseo-plugin-helper/tickets.ts +1232 -0
  67. package/server/vendor/permission-audit/index.ts +1 -0
  68. package/server/vendor/permission-audit/server.ts +839 -0
  69. package/shared/helper-version.ts +20 -0
  70. package/shared/multi-host.ts +156 -155
  71. package/shared/resources.ts +146 -1
  72. package/shared/vendor/paseo-plugin-helper/async.ts +14 -7
  73. package/shared/vendor/paseo-plugin-helper/features.ts +244 -0
  74. package/shared/vendor/paseo-plugin-helper/forge.ts +27 -0
  75. package/shared/vendor/paseo-plugin-helper/index.ts +2 -0
  76. package/shared/vendor/paseo-plugin-helper/tickets.ts +1104 -0
  77. package/shared/vendor/permission-audit/filter.ts +61 -0
  78. package/shared/vendor/permission-audit/index.ts +2 -0
  79. package/shared/vendor/permission-audit/shared.ts +119 -0
  80. package/shared/version.ts +1 -1
  81. package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +0 -493
  82. package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +0 -250
  83. package/client/vendor/paseo-plugin-helper/components/Badge.tsx +0 -156
  84. package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -184
  85. package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -225
  86. package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +0 -196
  87. package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +0 -277
  88. package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +0 -172
  89. package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +0 -180
  90. package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +0 -97
  91. package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +0 -70
  92. package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +0 -75
  93. package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +0 -446
  94. package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +0 -247
  95. package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +0 -117
  96. package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +0 -53
  97. package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +0 -118
  98. package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -80
  99. package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -215
  100. package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +0 -80
  101. package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +0 -319
  102. package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +0 -150
  103. package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +0 -163
  104. package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +0 -157
  105. package/client/vendor/paseo-plugin-helper/components/index.ts +0 -25
  106. package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -224
  107. package/client/vendor/paseo-plugin-helper/forge-icon.tsx +0 -79
  108. package/client/vendor/paseo-plugin-helper/icon.tsx +0 -39
  109. package/client/vendor/paseo-plugin-helper/index.ts +0 -29
  110. package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +0 -49
  111. package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +0 -103
  112. package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +0 -65
  113. package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +0 -378
  114. package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +0 -51
  115. package/client/vendor/paseo-plugin-helper/layout/Row.tsx +0 -39
  116. package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +0 -39
  117. package/client/vendor/paseo-plugin-helper/layout/index.ts +0 -7
  118. package/client/vendor/paseo-plugin-helper/styles/index.ts +0 -1
  119. package/client/vendor/paseo-plugin-helper/styles/recipes.ts +0 -550
  120. package/client/vendor/paseo-plugin-helper/surface.tsx +0 -80
  121. package/client/vendor/paseo-plugin-helper/theme/flair.ts +0 -76
  122. package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +0 -121
  123. package/client/vendor/paseo-plugin-helper/theme/index.ts +0 -7
  124. package/client/vendor/paseo-plugin-helper/theme/provider.tsx +0 -178
  125. package/client/vendor/paseo-plugin-helper/theme/responsive.ts +0 -213
  126. package/client/vendor/paseo-plugin-helper/theme/tokens.ts +0 -200
  127. package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +0 -57
  128. /package/client/vendor/paseo-plugin-helper/{command-center.ts → core/command-center.ts} +0 -0
  129. /package/client/vendor/paseo-plugin-helper/{snapshot.ts → core/snapshot.ts} +0 -0
  130. /package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/haptics.ts +0 -0
@@ -1,319 +0,0 @@
1
- import React, { useEffect, useRef, useState, type ComponentType, type Ref } from "react";
2
- import {
3
- Platform,
4
- Pressable,
5
- ScrollView as FallbackScrollView,
6
- StyleSheet,
7
- Text,
8
- View,
9
- type LayoutChangeEvent,
10
- type ScrollView as ScrollViewInstance,
11
- type StyleProp,
12
- type ViewStyle,
13
- } from "react-native";
14
- import { getClientHost } from "../host";
15
- import { usePluginTheme } from "../theme/provider";
16
- import {
17
- FALLBACK_ACCENT_FOREGROUND,
18
- spacing,
19
- } from "../theme/tokens";
20
-
21
- export interface TabItem {
22
- id: string;
23
- label: string;
24
- /**
25
- * Optional abbreviated label for compact viewports in fit mode.
26
- * e.g. label: "Interactive Controls", shortLabel: "Controls"
27
- */
28
- shortLabel?: string;
29
- icon?: string;
30
- badge?: string | number;
31
- }
32
-
33
- export interface TabsProps {
34
- tabs: TabItem[];
35
- activeTab: string;
36
- onTabChange: (tabId: string) => void;
37
- mode?: "auto" | "fit" | "scroll";
38
- style?: StyleProp<ViewStyle>;
39
- }
40
-
41
- /** Minimal structural views of the DOM scroll node react-native-web exposes. */
42
- interface WheelScrollEvent {
43
- deltaX?: number;
44
- deltaY?: number;
45
- preventDefault(): void;
46
- }
47
-
48
- interface WheelScrollNode {
49
- scrollLeft: number;
50
- clientWidth: number;
51
- scrollWidth: number;
52
- addEventListener(type: "wheel", listener: (event: WheelScrollEvent) => void, options?: { passive?: boolean }): void;
53
- removeEventListener(type: "wheel", listener: (event: WheelScrollEvent) => void): void;
54
- }
55
-
56
- interface WheelScrollInstance {
57
- getScrollableNode?(): WheelScrollNode | null;
58
- }
59
-
60
- export function Tabs({
61
- tabs,
62
- activeTab,
63
- onTabChange,
64
- mode = "auto",
65
- style,
66
- }: TabsProps) {
67
- const { Icon } = getClientHost();
68
- // The tab strip scrolls horizontally, so it must be a plain React Native
69
- // ScrollView: the host ScrollView is a vertical sheet-gesture controller
70
- // and collapses/crashes when used horizontally (#219).
71
- const { colors, resolveRadius, touchTargetMin, isCompact, alpha } = usePluginTheme();
72
- const scrollRef = useRef<ScrollViewInstance>(null);
73
- const tabLayouts = useRef<Record<string, { x: number; width: number }>>({});
74
- const [viewportWidth, setViewportWidth] = useState<number>(0);
75
-
76
- const radius = resolveRadius("sm");
77
-
78
- // On compact/mobile or with <= 4 tabs, auto mode defaults to full-width fitting track
79
- const shouldFit = mode === "fit" || (mode === "auto" && (isCompact || tabs.length <= 4));
80
-
81
- // Accurately center the active tab inside the viewport ONLY when activeTab changes
82
- const prevActiveTab = useRef<string>(activeTab);
83
- useEffect(() => {
84
- if (!shouldFit && scrollRef.current && tabLayouts.current[activeTab] && viewportWidth > 0) {
85
- if (prevActiveTab.current !== activeTab) {
86
- prevActiveTab.current = activeTab;
87
- const { x, width } = tabLayouts.current[activeTab];
88
- const targetX = Math.max(0, x - (viewportWidth - width) / 2);
89
- scrollRef.current.scrollTo({
90
- x: targetX,
91
- animated: true,
92
- });
93
- }
94
- }
95
- }, [activeTab, shouldFit, viewportWidth]);
96
-
97
- const handleTabLayout = (tabId: string, event: LayoutChangeEvent) => {
98
- const { x, width } = event.nativeEvent.layout;
99
- tabLayouts.current[tabId] = { x, width };
100
- };
101
-
102
- const handleContainerLayout = (event: LayoutChangeEvent) => {
103
- const width = event.nativeEvent.layout.width;
104
- setViewportWidth(width);
105
- };
106
-
107
- // Translate vertical mouse-wheel motion into horizontal tab-strip scrolling
108
- // on web, while allowing the surrounding modal to scroll at either edge.
109
- useEffect(() => {
110
- if (shouldFit || Platform.OS !== "web") return;
111
- const instance = scrollRef.current as unknown as WheelScrollInstance | null;
112
- const node = instance?.getScrollableNode?.() ?? (instance as unknown as WheelScrollNode | null);
113
- if (!node || typeof node.addEventListener !== "function") return;
114
- const onWheel = (event: WheelScrollEvent) => {
115
- const deltaX = event.deltaX ?? 0;
116
- const deltaY = event.deltaY ?? 0;
117
- if (Math.abs(deltaY) <= Math.abs(deltaX)) return;
118
- const max = node.scrollWidth - node.clientWidth;
119
- const next = Math.min(Math.max(0, node.scrollLeft + deltaY), max);
120
- if (next === node.scrollLeft) return;
121
- event.preventDefault();
122
- node.scrollLeft = next;
123
- };
124
- node.addEventListener("wheel", onWheel, { passive: false });
125
- return () => node.removeEventListener("wheel", onWheel);
126
- }, [shouldFit]);
127
-
128
- const renderTab = (tab: TabItem) => {
129
- const isActive = tab.id === activeTab;
130
- const displayLabel = shouldFit && isCompact && tab.shortLabel ? tab.shortLabel : tab.label;
131
-
132
- return (
133
- <Pressable
134
- key={tab.id}
135
- onPress={() => {
136
- onTabChange(tab.id);
137
- }}
138
- onLayout={(e) => handleTabLayout(tab.id, e)}
139
- accessibilityRole="tab"
140
- accessibilityState={{ selected: isActive }}
141
- style={({ pressed }) => [
142
- styles.tab,
143
- shouldFit ? styles.tabFit : styles.tabScroll,
144
- {
145
- borderRadius: radius - 2,
146
- minHeight: Math.max(30, touchTargetMin - 8),
147
- backgroundColor: isActive
148
- ? colors.surface2
149
- : pressed
150
- ? alpha(colors.surface2, 0.5)
151
- : "transparent",
152
- paddingHorizontal: shouldFit
153
- ? isCompact
154
- ? (tabs.length > 3 ? spacing.xs : spacing.sm)
155
- : (tabs.length >= 3 ? spacing.sm : spacing.md)
156
- : spacing.md,
157
- paddingVertical: isCompact ? spacing.xs : spacing.sm,
158
- },
159
- ]}
160
- >
161
- {tab.icon ? (
162
- <Icon
163
- name={tab.icon}
164
- size={isCompact ? 11 : 13}
165
- color={isActive ? colors.foreground : colors.foregroundMuted}
166
- />
167
- ) : null}
168
- <Text
169
- numberOfLines={1}
170
- style={[
171
- styles.tabText,
172
- {
173
- color: isActive ? colors.foreground : colors.foregroundMuted,
174
- fontSize: isCompact ? 11 : 12,
175
- fontWeight: isActive ? "600" : "500",
176
- },
177
- ]}
178
- >
179
- {displayLabel}
180
- </Text>
181
- {tab.badge !== undefined ? (
182
- <View
183
- style={[
184
- styles.badge,
185
- {
186
- backgroundColor: isActive ? colors.accent : alpha(colors.foregroundMuted, 0.2),
187
- },
188
- ]}
189
- >
190
- <Text
191
- style={[
192
- styles.badgeText,
193
- {
194
- color: isActive
195
- ? colors.accentForeground || FALLBACK_ACCENT_FOREGROUND
196
- : colors.foregroundMuted,
197
- },
198
- ]}
199
- >
200
- {tab.badge}
201
- </Text>
202
- </View>
203
- ) : null}
204
- </Pressable>
205
- );
206
- };
207
-
208
- // 1. FIT MODE: Closed bounded track frame
209
- if (shouldFit) {
210
- return (
211
- <View
212
- style={[
213
- styles.frame,
214
- {
215
- backgroundColor: colors.surface1,
216
- borderRadius: radius,
217
- borderColor: colors.border,
218
- },
219
- style,
220
- ]}
221
- >
222
- <View style={styles.trackFit}>
223
- {tabs.map((tab) => renderTab(tab))}
224
- </View>
225
- </View>
226
- );
227
- }
228
-
229
- // 2. SCROLL MODE: plain React Native horizontal scroller.
230
- return (
231
- <View
232
- onLayout={handleContainerLayout}
233
- style={[
234
- styles.frame,
235
- {
236
- backgroundColor: colors.surface1,
237
- borderRadius: radius,
238
- borderColor: colors.border,
239
- },
240
- style,
241
- ]}
242
- >
243
- <FallbackScrollView
244
- ref={scrollRef}
245
- horizontal
246
- nestedScrollEnabled={true}
247
- directionalLockEnabled={true}
248
- keyboardShouldPersistTaps="handled"
249
- showsHorizontalScrollIndicator={!isCompact}
250
- style={styles.scrollView}
251
- contentContainerStyle={styles.scrollContent}
252
- >
253
- {tabs.map((tab) => renderTab(tab))}
254
- </FallbackScrollView>
255
-
256
- </View>
257
- );
258
- }
259
-
260
- const styles = StyleSheet.create({
261
- frame: {
262
- width: "100%",
263
- maxWidth: "100%",
264
- borderWidth: 1,
265
- overflow: "hidden",
266
- position: "relative",
267
- justifyContent: "center",
268
- },
269
- trackFit: {
270
- flexDirection: "row",
271
- flexWrap: "nowrap",
272
- alignItems: "center",
273
- width: "100%",
274
- padding: 3,
275
- gap: 2,
276
- },
277
- scrollView: {
278
- width: "100%",
279
- maxWidth: "100%",
280
- },
281
- scrollContent: {
282
- flexDirection: "row",
283
- alignItems: "center",
284
- padding: 3,
285
- gap: 4,
286
- },
287
- tab: {
288
- flexDirection: "row",
289
- alignItems: "center",
290
- justifyContent: "center",
291
- gap: 4,
292
- overflow: "hidden",
293
- },
294
- tabFit: {
295
- flex: 1,
296
- flexShrink: 1,
297
- minWidth: 0,
298
- },
299
- tabScroll: {
300
- flexShrink: 0,
301
- },
302
- tabText: {
303
- textAlign: "center",
304
- flexShrink: 1,
305
- minWidth: 0,
306
- },
307
- badge: {
308
- borderRadius: 9999,
309
- paddingHorizontal: 5,
310
- paddingVertical: 1,
311
- minWidth: 16,
312
- alignItems: "center",
313
- justifyContent: "center",
314
- },
315
- badgeText: {
316
- fontSize: 10,
317
- fontWeight: "700",
318
- },
319
- });
@@ -1,150 +0,0 @@
1
- import React, { useState, type ComponentType, type Ref } from "react";
2
- import {
3
- StyleSheet,
4
- Text,
5
- TextInput as RNTextInput,
6
- View,
7
- type KeyboardTypeOptions,
8
- type StyleProp,
9
- type TextInputProps as RNTextInputProps,
10
- type TextInput as RNTextInputInstance,
11
- type TextStyle,
12
- type ViewStyle,
13
- } from "react-native";
14
- import { getOptionalClientHost } from "../host";
15
- import { usePluginTheme } from "../theme/provider";
16
-
17
- export interface TextInputProps {
18
- value: string;
19
- onChangeText: (text: string) => void;
20
- label?: string;
21
- placeholder?: string;
22
- helperText?: string;
23
- errorText?: string;
24
- secureTextEntry?: boolean;
25
- keyboardType?: KeyboardTypeOptions;
26
- autoCapitalize?: "none" | "sentences" | "words" | "characters";
27
- autoCorrect?: boolean;
28
- disabled?: boolean;
29
- mono?: boolean;
30
- multiline?: boolean;
31
- numberOfLines?: number;
32
- style?: StyleProp<ViewStyle>;
33
- inputStyle?: StyleProp<TextStyle>;
34
- onSubmitEditing?: () => void;
35
- }
36
-
37
- export function TextInput({
38
- value,
39
- onChangeText,
40
- label,
41
- placeholder,
42
- helperText,
43
- errorText,
44
- secureTextEntry = false,
45
- keyboardType = "default",
46
- autoCapitalize = "none",
47
- autoCorrect = false,
48
- disabled = false,
49
- mono = false,
50
- multiline = false,
51
- numberOfLines = 1,
52
- style,
53
- inputStyle,
54
- onSubmitEditing,
55
- }: TextInputProps) {
56
- const { colors, resolveRadius, isCompact, touchTargetMin, alpha } = usePluginTheme();
57
- const [isFocused, setIsFocused] = useState(false);
58
- const ResolvedInput = (getOptionalClientHost()?.TextInput ??
59
- RNTextInput) as ComponentType<RNTextInputProps & { ref?: Ref<RNTextInputInstance> }>;
60
-
61
- const radius = resolveRadius("md");
62
- const hasError = Boolean(errorText);
63
-
64
- const borderColor = hasError
65
- ? colors.statusDanger
66
- : isFocused
67
- ? colors.accent
68
- : colors.border;
69
-
70
- const minHeight = multiline ? Math.max(touchTargetMin * 1.5, 64) : touchTargetMin;
71
-
72
- return (
73
- <View style={[styles.container, style]}>
74
- {label ? (
75
- <Text
76
- style={[
77
- styles.label,
78
- {
79
- color: hasError ? colors.statusDanger : colors.foreground,
80
- fontSize: isCompact ? 12 : 13,
81
- },
82
- ]}
83
- >
84
- {label}
85
- </Text>
86
- ) : null}
87
-
88
- <ResolvedInput
89
- value={value}
90
- onChangeText={onChangeText}
91
- placeholder={placeholder}
92
- placeholderTextColor={colors.foregroundMuted}
93
- secureTextEntry={secureTextEntry}
94
- keyboardType={keyboardType}
95
- autoCapitalize={autoCapitalize}
96
- autoCorrect={autoCorrect}
97
- editable={!disabled}
98
- multiline={multiline}
99
- numberOfLines={numberOfLines}
100
- onFocus={() => setIsFocused(true)}
101
- onBlur={() => setIsFocused(false)}
102
- onSubmitEditing={onSubmitEditing}
103
- style={[
104
- styles.input,
105
- {
106
- color: disabled ? colors.foregroundMuted : colors.foreground,
107
- backgroundColor: disabled ? alpha(colors.surface1, 0.5) : colors.surface0,
108
- borderColor,
109
- borderRadius: radius,
110
- minHeight,
111
- paddingVertical: multiline ? 8 : 6,
112
- paddingHorizontal: 10,
113
- fontSize: isCompact ? 13 : 14,
114
- fontFamily: mono ? "monospace" : undefined,
115
- },
116
- inputStyle,
117
- ]}
118
- />
119
-
120
- {(errorText || helperText) && (
121
- <Text
122
- style={[
123
- styles.hint,
124
- {
125
- color: hasError ? colors.statusDanger : colors.foregroundMuted,
126
- fontSize: 11,
127
- },
128
- ]}
129
- >
130
- {errorText || helperText}
131
- </Text>
132
- )}
133
- </View>
134
- );
135
- }
136
-
137
- const styles = StyleSheet.create({
138
- container: {
139
- gap: 4,
140
- },
141
- label: {
142
- fontWeight: "600",
143
- },
144
- input: {
145
- borderWidth: 1,
146
- },
147
- hint: {
148
- marginTop: 2,
149
- },
150
- });
@@ -1,163 +0,0 @@
1
- import React from "react";
2
- import {
3
- Pressable,
4
- StyleSheet,
5
- Text,
6
- View,
7
- type StyleProp,
8
- type TextStyle,
9
- type ViewStyle,
10
- } from "react-native";
11
- import { usePluginTheme } from "../theme/provider";
12
- import { resolveElevation } from "../theme/tokens";
13
-
14
- export interface ToggleProps {
15
- value: boolean;
16
- onValueChange: (next: boolean) => void;
17
- label?: string;
18
- description?: string;
19
- disabled?: boolean;
20
- style?: StyleProp<ViewStyle>;
21
- labelStyle?: StyleProp<TextStyle>;
22
- }
23
-
24
- export function Toggle({
25
- value,
26
- onValueChange,
27
- label,
28
- description,
29
- disabled = false,
30
- style,
31
- labelStyle,
32
- }: ToggleProps) {
33
- const { colors, touchTargetMin, isCompact, alpha } = usePluginTheme();
34
-
35
- const handlePress = () => {
36
- if (!disabled) {
37
- onValueChange(!value);
38
- }
39
- };
40
-
41
- const trackWidth = 38;
42
- const trackHeight = 22;
43
- const thumbSize = 16;
44
- const thumbPadding = 3;
45
-
46
- const trackColor = value
47
- ? colors.accent
48
- : alpha(colors.foregroundMuted, 0.35);
49
-
50
- const thumbPosition = value
51
- ? trackWidth - thumbSize - thumbPadding
52
- : thumbPadding;
53
-
54
- const hasText = Boolean(label || description);
55
-
56
- return (
57
- <Pressable
58
- onPress={handlePress}
59
- disabled={disabled}
60
- hitSlop={Math.max(0, (touchTargetMin - trackHeight) / 2)}
61
- style={({ pressed }) => [
62
- styles.container,
63
- !hasText && styles.bareContainer,
64
- {
65
- minHeight: touchTargetMin,
66
- opacity: disabled ? 0.5 : pressed ? 0.8 : 1,
67
- },
68
- style,
69
- ]}
70
- >
71
- {(label || description) && (
72
- <View style={styles.textContainer}>
73
- {label && (
74
- <Text
75
- style={[
76
- styles.label,
77
- {
78
- color: colors.foreground,
79
- fontSize: isCompact ? 13 : 14,
80
- },
81
- labelStyle,
82
- ]}
83
- >
84
- {label}
85
- </Text>
86
- )}
87
- {description && (
88
- <Text
89
- style={[
90
- styles.description,
91
- {
92
- color: colors.foregroundMuted,
93
- fontSize: 11,
94
- },
95
- ]}
96
- >
97
- {description}
98
- </Text>
99
- )}
100
- </View>
101
- )}
102
-
103
- <View
104
- style={[
105
- styles.track,
106
- {
107
- width: trackWidth,
108
- height: trackHeight,
109
- borderRadius: trackHeight / 2,
110
- backgroundColor: trackColor,
111
- },
112
- ]}
113
- >
114
- <View
115
- style={[
116
- styles.thumb,
117
- {
118
- width: thumbSize,
119
- height: thumbSize,
120
- borderRadius: thumbSize / 2,
121
- backgroundColor: colors.surface0,
122
- transform: [{ translateX: thumbPosition }],
123
- },
124
- ]}
125
- />
126
- </View>
127
- </Pressable>
128
- );
129
- }
130
-
131
- const styles = StyleSheet.create({
132
- container: {
133
- flexDirection: "row",
134
- alignItems: "center",
135
- justifyContent: "space-between",
136
- gap: 12,
137
- width: "100%",
138
- },
139
- bareContainer: {
140
- width: "auto",
141
- alignSelf: "flex-start",
142
- flexShrink: 0,
143
- justifyContent: "flex-start",
144
- },
145
- textContainer: {
146
- flex: 1,
147
- minWidth: 0,
148
- gap: 2,
149
- },
150
- label: {
151
- fontWeight: "500",
152
- },
153
- description: {
154
- lineHeight: 15,
155
- },
156
- track: {
157
- justifyContent: "center",
158
- flexShrink: 0,
159
- },
160
- thumb: {
161
- ...resolveElevation("sm"),
162
- },
163
- });