@xpufx/paseo-helper-demo 0.2.0
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/LICENSE +21 -0
- package/README.md +57 -0
- package/client/pill.tsx +1249 -0
- package/client/suite-settings.tsx +77 -0
- package/client/vendor/paseo-plugin-helper/command-center.ts +43 -0
- package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +493 -0
- package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +250 -0
- package/client/vendor/paseo-plugin-helper/components/Badge.tsx +156 -0
- package/client/vendor/paseo-plugin-helper/components/Button.tsx +178 -0
- package/client/vendor/paseo-plugin-helper/components/Card.tsx +225 -0
- package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +196 -0
- package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +277 -0
- package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +172 -0
- package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +180 -0
- package/client/vendor/paseo-plugin-helper/components/DataTable.tsx +200 -0
- package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +97 -0
- package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +70 -0
- package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +73 -0
- package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +446 -0
- package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +247 -0
- package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +117 -0
- package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +53 -0
- package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +118 -0
- package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/components/Select.tsx +215 -0
- package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +319 -0
- package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +150 -0
- package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +163 -0
- package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +157 -0
- package/client/vendor/paseo-plugin-helper/components/index.ts +25 -0
- package/client/vendor/paseo-plugin-helper/core/index.ts +64 -0
- package/client/vendor/paseo-plugin-helper/custom-pills.tsx +224 -0
- package/client/vendor/paseo-plugin-helper/forge-icon.tsx +79 -0
- package/client/vendor/paseo-plugin-helper/host.ts +277 -0
- package/client/vendor/paseo-plugin-helper/icon.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/index.ts +28 -0
- package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +49 -0
- package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +103 -0
- package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +65 -0
- package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +378 -0
- package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +49 -0
- package/client/vendor/paseo-plugin-helper/layout/Row.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/layout/index.ts +7 -0
- package/client/vendor/paseo-plugin-helper/panel.tsx +81 -0
- package/client/vendor/paseo-plugin-helper/pill.tsx +884 -0
- package/client/vendor/paseo-plugin-helper/query-refresh.ts +79 -0
- package/client/vendor/paseo-plugin-helper/query.ts +66 -0
- package/client/vendor/paseo-plugin-helper/settings-screen.tsx +372 -0
- package/client/vendor/paseo-plugin-helper/settings.ts +181 -0
- package/client/vendor/paseo-plugin-helper/shared-settings.ts +46 -0
- package/client/vendor/paseo-plugin-helper/snapshot.ts +68 -0
- package/client/vendor/paseo-plugin-helper/surface.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/theme/color-utils.ts +118 -0
- package/client/vendor/paseo-plugin-helper/theme/flair.ts +76 -0
- package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +121 -0
- package/client/vendor/paseo-plugin-helper/theme/index.ts +7 -0
- package/client/vendor/paseo-plugin-helper/theme/provider.tsx +214 -0
- package/client/vendor/paseo-plugin-helper/theme/responsive.ts +213 -0
- package/client/vendor/paseo-plugin-helper/theme/tokens.ts +161 -0
- package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +57 -0
- package/client/vendor/paseo-plugin-helper/ui/index.ts +25 -0
- package/client/vendor/paseo-plugin-helper/ui/modal.tsx +105 -0
- package/client/vendor/paseo-plugin-helper/ui/settings.ts +36 -0
- package/client/vendor/paseo-plugin-helper/utils/clipboard.ts +149 -0
- package/client/vendor/paseo-plugin-helper/utils/haptics.ts +34 -0
- package/package.json +35 -0
- package/paseo-plugin.json +6 -0
- package/server/demo.ts +195 -0
- package/server/suite-settings.ts +23 -0
- package/server/vendor/paseo-plugin-helper/agent.ts +85 -0
- package/server/vendor/paseo-plugin-helper/custom-pills.ts +344 -0
- package/server/vendor/paseo-plugin-helper/index.ts +18 -0
- package/server/vendor/paseo-plugin-helper/jsonc.ts +78 -0
- package/server/vendor/paseo-plugin-helper/logger.ts +210 -0
- package/server/vendor/paseo-plugin-helper/mcp-config.ts +367 -0
- package/server/vendor/paseo-plugin-helper/mcp-injection.ts +85 -0
- package/server/vendor/paseo-plugin-helper/network.ts +91 -0
- package/server/vendor/paseo-plugin-helper/plugins.ts +160 -0
- package/server/vendor/paseo-plugin-helper/process.ts +186 -0
- package/server/vendor/paseo-plugin-helper/redact.ts +86 -0
- package/server/vendor/paseo-plugin-helper/rpc-guard.ts +77 -0
- package/server/vendor/paseo-plugin-helper/settings.ts +97 -0
- package/server/vendor/paseo-plugin-helper/shared-settings.ts +243 -0
- package/server/vendor/paseo-plugin-helper/storage.ts +244 -0
- package/server/vendor/paseo-plugin-helper/system.ts +128 -0
- package/server/vendor/paseo-plugin-helper/task.ts +116 -0
- package/server/vendor/paseo-plugin-helper/version.ts +153 -0
- package/server/vendor/paseo-plugin-helper/workspace-beacon.ts +418 -0
- package/shared/demo.ts +155 -0
- package/shared/vendor/paseo-plugin-helper/README.md +11 -0
- package/shared/vendor/paseo-plugin-helper/async.ts +35 -0
- package/shared/vendor/paseo-plugin-helper/custom-pills.ts +169 -0
- package/shared/vendor/paseo-plugin-helper/forge.ts +110 -0
- package/shared/vendor/paseo-plugin-helper/formatters.ts +271 -0
- package/shared/vendor/paseo-plugin-helper/highlight.ts +184 -0
- package/shared/vendor/paseo-plugin-helper/index.ts +10 -0
- package/shared/vendor/paseo-plugin-helper/rpc.ts +72 -0
- package/shared/vendor/paseo-plugin-helper/settings.ts +138 -0
- package/shared/vendor/paseo-plugin-helper/suite-settings.ts +17 -0
- package/shared/vendor/paseo-plugin-helper/suppressed.ts +31 -0
- package/shared/vendor/paseo-plugin-helper/types.ts +36 -0
- package/shared/version.ts +2 -0
package/client/pill.tsx
ADDED
|
@@ -0,0 +1,1249 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { Text, View } from "react-native";
|
|
3
|
+
import { useRpc } from "@getpaseo/plugin/client";
|
|
4
|
+
import { Icon, Modal, useToast, ScrollView, FlatList, TextInput as HostTextInput, copyText } from "@getpaseo/plugin/client/react-native";
|
|
5
|
+
import {
|
|
6
|
+
initClientHelpers,
|
|
7
|
+
type ComposerPillRegistrar,
|
|
8
|
+
} from "paseo-plugin-helper/client";
|
|
9
|
+
|
|
10
|
+
initClientHelpers({ Icon, Modal, useRpc, useToast, copyText, ScrollView, FlatList, TextInput: HostTextInput });
|
|
11
|
+
import {
|
|
12
|
+
registerComposerPill,
|
|
13
|
+
PluginThemeProvider,
|
|
14
|
+
ActionBar,
|
|
15
|
+
Card,
|
|
16
|
+
Badge,
|
|
17
|
+
StatusDot,
|
|
18
|
+
Button,
|
|
19
|
+
KeyValue,
|
|
20
|
+
KeyValueGroup,
|
|
21
|
+
MetricGauge,
|
|
22
|
+
ProgressBar,
|
|
23
|
+
DataTable,
|
|
24
|
+
Tabs,
|
|
25
|
+
SearchInput,
|
|
26
|
+
EmptyState,
|
|
27
|
+
CodeBlock,
|
|
28
|
+
Toggle,
|
|
29
|
+
FormRow,
|
|
30
|
+
TextInput,
|
|
31
|
+
AboutSection,
|
|
32
|
+
AttentionBeacon,
|
|
33
|
+
Collapsible,
|
|
34
|
+
SectionHeader,
|
|
35
|
+
triggerHaptic,
|
|
36
|
+
usePluginTheme,
|
|
37
|
+
useResponsive,
|
|
38
|
+
type RenderModalProps,
|
|
39
|
+
type RenderPillProps,
|
|
40
|
+
type VisualFlair,
|
|
41
|
+
type AttentionBeaconMode,
|
|
42
|
+
type AttentionBeaconTone,
|
|
43
|
+
} from "paseo-plugin-helper/client";
|
|
44
|
+
import {
|
|
45
|
+
useAutoRefreshQuery,
|
|
46
|
+
useRpcMutation,
|
|
47
|
+
usePluginSettings,
|
|
48
|
+
} from "paseo-plugin-helper/core";
|
|
49
|
+
import { HostModalSection } from "paseo-plugin-helper/ui";
|
|
50
|
+
import { formatBytes, formatUptime } from "paseo-plugin-helper/shared";
|
|
51
|
+
import {
|
|
52
|
+
getDemoDataRpc,
|
|
53
|
+
triggerDemoActionRpc,
|
|
54
|
+
demoAgentIdentityContract,
|
|
55
|
+
demoBeaconSetContract,
|
|
56
|
+
demoBeaconBlinkContract,
|
|
57
|
+
demoBeaconClearContract,
|
|
58
|
+
demoSettingsContract,
|
|
59
|
+
type DemoData,
|
|
60
|
+
} from "../shared/demo.js";
|
|
61
|
+
import { SharedSuiteCard } from "./suite-settings.js";
|
|
62
|
+
import { PLUGIN_VERSION } from "../shared/version.js";
|
|
63
|
+
|
|
64
|
+
const EMPTY_PARAMS = {};
|
|
65
|
+
|
|
66
|
+
function DemoPill({ isOpen }: RenderPillProps) {
|
|
67
|
+
const { colors, typography } = usePluginTheme();
|
|
68
|
+
const { isCompact } = useResponsive();
|
|
69
|
+
const { settings } = usePluginSettings(demoSettingsContract);
|
|
70
|
+
const { data, isLoading } = useAutoRefreshQuery(getDemoDataRpc, EMPTY_PARAMS, {
|
|
71
|
+
defaultRate: settings.pollingRate,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const cpu = data?.cpuUsagePercent ?? 0;
|
|
75
|
+
const isAlert = cpu > settings.highCpuThreshold;
|
|
76
|
+
|
|
77
|
+
// Raw View/Text composition: the host composer bar has no helper surface for
|
|
78
|
+
// pill content, so only the compositor row and inline text spans stay local.
|
|
79
|
+
return (
|
|
80
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 6, paddingHorizontal: 6 }}>
|
|
81
|
+
<StatusDot variant={isAlert ? "danger" : "success"} pulse={isAlert} />
|
|
82
|
+
{isCompact ? (
|
|
83
|
+
// Mobile / Compact track: ultra-compact layout to prevent truncation!
|
|
84
|
+
<Text
|
|
85
|
+
numberOfLines={1}
|
|
86
|
+
style={[
|
|
87
|
+
{ ...typography.caption, color: colors.foreground, fontWeight: "600", flexShrink: 1 },
|
|
88
|
+
isOpen && { opacity: 0.85 },
|
|
89
|
+
]}
|
|
90
|
+
>
|
|
91
|
+
{isLoading ? "..." : `${cpu}%`}
|
|
92
|
+
</Text>
|
|
93
|
+
) : (
|
|
94
|
+
// Desktop wide track: full descriptive label
|
|
95
|
+
<Text
|
|
96
|
+
numberOfLines={1}
|
|
97
|
+
style={[{ ...typography.caption, flexShrink: 1 }, isOpen && { opacity: 0.85 }]}
|
|
98
|
+
>
|
|
99
|
+
<Text style={{ color: colors.accent, fontWeight: "600" }}>
|
|
100
|
+
{settings.accentPillLabel}
|
|
101
|
+
</Text>
|
|
102
|
+
{settings.showCpuUsage && (
|
|
103
|
+
<>
|
|
104
|
+
<Text style={{ color: colors.foregroundMuted }}>{" · "}</Text>
|
|
105
|
+
<Text style={{ color: colors.foreground }}>{isLoading ? "..." : `${cpu}% CPU`}</Text>
|
|
106
|
+
</>
|
|
107
|
+
)}
|
|
108
|
+
</Text>
|
|
109
|
+
)}
|
|
110
|
+
</View>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function DemoModal({ close, workspaceId }: RenderModalProps) {
|
|
115
|
+
const { colors, theme, layout, typography, touchTargetMin } = usePluginTheme();
|
|
116
|
+
const { isCompact } = useResponsive();
|
|
117
|
+
const toast = useToast();
|
|
118
|
+
const [activeTab, setActiveTab] = useState<string>("gauges");
|
|
119
|
+
const [navigationOpen, setNavigationOpen] = useState<boolean>(false);
|
|
120
|
+
|
|
121
|
+
const showcaseTabs = [
|
|
122
|
+
{ id: "gauges", label: "Gauges & Hardware", shortLabel: "Gauges" },
|
|
123
|
+
{ id: "flair", label: "Visual Flair Studio", shortLabel: "Flair" },
|
|
124
|
+
{ id: "data", label: "Data Table", shortLabel: "Data" },
|
|
125
|
+
{ id: "controls", label: "Interactive Controls", shortLabel: "Controls" },
|
|
126
|
+
{ id: "attention", label: "Attention & Beacons", shortLabel: "Beacon" },
|
|
127
|
+
{ id: "settings", label: "Plugin Settings", shortLabel: "Settings" },
|
|
128
|
+
{ id: "network", label: "Network Diagnostics", shortLabel: "Net" },
|
|
129
|
+
{ id: "logs", label: "System Logs", shortLabel: "Logs" },
|
|
130
|
+
{ id: "about", label: "About Plugin", shortLabel: "About" },
|
|
131
|
+
];
|
|
132
|
+
const [searchQuery, setSearchQuery] = useState<string>("");
|
|
133
|
+
const [liveStream, setLiveStream] = useState<boolean>(true);
|
|
134
|
+
const [actionFeedback, setActionFeedback] = useState<string | null>(null);
|
|
135
|
+
const [attentionOn, setAttentionOn] = useState<boolean>(true);
|
|
136
|
+
const [beaconMode, setBeaconMode] = useState<AttentionBeaconMode>("radar");
|
|
137
|
+
const [beaconTone, setBeaconTone] = useState<AttentionBeaconTone>("warning");
|
|
138
|
+
const [beaconPresses, setBeaconPresses] = useState<number>(0);
|
|
139
|
+
|
|
140
|
+
const {
|
|
141
|
+
settings,
|
|
142
|
+
updateSettings,
|
|
143
|
+
resetSettings,
|
|
144
|
+
isUpdating: isSettingsUpdating,
|
|
145
|
+
} = usePluginSettings(demoSettingsContract);
|
|
146
|
+
|
|
147
|
+
// An open in-flow dropdown must not survive a navigation-style switch with
|
|
148
|
+
// stale state; the menu unmounts but the flag would reopen it on return.
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
setNavigationOpen(false);
|
|
151
|
+
}, [settings.navigationStyle]);
|
|
152
|
+
|
|
153
|
+
const {
|
|
154
|
+
data,
|
|
155
|
+
isLoading,
|
|
156
|
+
refetch,
|
|
157
|
+
rate,
|
|
158
|
+
setRate,
|
|
159
|
+
isPolling,
|
|
160
|
+
} = useAutoRefreshQuery(getDemoDataRpc, EMPTY_PARAMS, {
|
|
161
|
+
defaultRate: "2s",
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const { mutate: runAction, isPending: isActionPending } = useRpcMutation(
|
|
165
|
+
triggerDemoActionRpc,
|
|
166
|
+
{
|
|
167
|
+
onSuccess: (res) => {
|
|
168
|
+
triggerHaptic("success");
|
|
169
|
+
setActionFeedback(res.message);
|
|
170
|
+
toast.show(res.message, { variant: "success" });
|
|
171
|
+
refetch();
|
|
172
|
+
},
|
|
173
|
+
onError: (err) => {
|
|
174
|
+
toast.error(err.message);
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const [beaconFeedback, setBeaconFeedback] = useState<string | null>(null);
|
|
180
|
+
const [showEnvelope, setShowEnvelope] = useState<boolean>(false);
|
|
181
|
+
|
|
182
|
+
const { data: agentData, isLoading: isAgentLoading } = useAutoRefreshQuery(
|
|
183
|
+
demoAgentIdentityContract,
|
|
184
|
+
EMPTY_PARAMS,
|
|
185
|
+
{ defaultRate: "5s" }
|
|
186
|
+
);
|
|
187
|
+
const agentIdentity = agentData?.identity ?? null;
|
|
188
|
+
|
|
189
|
+
const { mutate: setBeacon, isPending: isBeaconSetPending } = useRpcMutation(
|
|
190
|
+
demoBeaconSetContract,
|
|
191
|
+
{
|
|
192
|
+
onSuccess: (res) => {
|
|
193
|
+
triggerHaptic("success");
|
|
194
|
+
setBeaconFeedback(res.message);
|
|
195
|
+
toast.show(res.message, { variant: "success" });
|
|
196
|
+
},
|
|
197
|
+
onError: (err) => {
|
|
198
|
+
toast.error(err.message);
|
|
199
|
+
},
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const { mutate: blinkBeacon, isPending: isBeaconBlinkPending } = useRpcMutation(
|
|
204
|
+
demoBeaconBlinkContract,
|
|
205
|
+
{
|
|
206
|
+
onSuccess: (res) => {
|
|
207
|
+
triggerHaptic("success");
|
|
208
|
+
setBeaconFeedback(res.message);
|
|
209
|
+
toast.show(res.message, { variant: "success" });
|
|
210
|
+
},
|
|
211
|
+
onError: (err) => {
|
|
212
|
+
toast.error(err.message);
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const { mutate: clearBeacon, isPending: isBeaconClearPending } = useRpcMutation(
|
|
218
|
+
demoBeaconClearContract,
|
|
219
|
+
{
|
|
220
|
+
onSuccess: (res) => {
|
|
221
|
+
triggerHaptic("light");
|
|
222
|
+
setBeaconFeedback(res.message);
|
|
223
|
+
toast.show(res.message, { variant: "info" });
|
|
224
|
+
},
|
|
225
|
+
onError: (err) => {
|
|
226
|
+
toast.error(err.message);
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const items = data?.items ?? [];
|
|
232
|
+
const filteredItems = items.filter(
|
|
233
|
+
(item) =>
|
|
234
|
+
item.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
235
|
+
item.category.toLowerCase().includes(searchQuery.toLowerCase())
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
const activeFlair: Partial<VisualFlair> = {
|
|
239
|
+
radius: settings.flairRadius,
|
|
240
|
+
density: settings.flairDensity,
|
|
241
|
+
surfaceStyle: settings.flairSurface,
|
|
242
|
+
borderWidth: settings.flairBorderWidth,
|
|
243
|
+
headingTransform: settings.flairUppercase ? "uppercase" : "none",
|
|
244
|
+
accentColor: settings.flairAccentColor,
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<PluginThemeProvider
|
|
249
|
+
theme={{
|
|
250
|
+
...theme,
|
|
251
|
+
colors: {
|
|
252
|
+
...theme.colors,
|
|
253
|
+
accent: settings.flairAccentColor,
|
|
254
|
+
},
|
|
255
|
+
}}
|
|
256
|
+
layout={layout}
|
|
257
|
+
flair={activeFlair}
|
|
258
|
+
>
|
|
259
|
+
{/* Pill-embedded content (#219): the pill host already provides the one
|
|
260
|
+
<Modal.Content> (modal paths) or no modal at all (0.8 popover), so
|
|
261
|
+
this must stay a plain fluid section — a nested <Modal.Content>
|
|
262
|
+
violates the host contract and crashes the plugin. hostScroll: true
|
|
263
|
+
below leaves the scroller to the host; manual refresh lives in the
|
|
264
|
+
banner card. */}
|
|
265
|
+
<HostModalSection>
|
|
266
|
+
<View
|
|
267
|
+
style={{
|
|
268
|
+
width: "100%",
|
|
269
|
+
backgroundColor: colors.surface0,
|
|
270
|
+
paddingHorizontal: 12,
|
|
271
|
+
paddingTop: 12,
|
|
272
|
+
paddingBottom: 6,
|
|
273
|
+
}}
|
|
274
|
+
>
|
|
275
|
+
{settings.navigationStyle === "dropdown" ? (
|
|
276
|
+
// Dropdown navigation is composed from helper primitives: the
|
|
277
|
+
// helper has no Select/menu primitive, so Collapsible provides the
|
|
278
|
+
// disclosure header and Button rows make the options selectable.
|
|
279
|
+
<Collapsible
|
|
280
|
+
title={showcaseTabs.find((tab) => tab.id === activeTab)?.label ?? activeTab}
|
|
281
|
+
icon="Sliders"
|
|
282
|
+
isExpanded={navigationOpen}
|
|
283
|
+
onToggle={(expanded) => {
|
|
284
|
+
triggerHaptic("light");
|
|
285
|
+
setNavigationOpen(expanded);
|
|
286
|
+
}}
|
|
287
|
+
style={{ width: "100%", marginTop: 10 }}
|
|
288
|
+
>
|
|
289
|
+
<ActionBar direction="column" align="flex-start" style={{ marginTop: 0, gap: 4 }}>
|
|
290
|
+
{showcaseTabs.map((tab) => (
|
|
291
|
+
<Button
|
|
292
|
+
key={tab.id}
|
|
293
|
+
label={tab.label}
|
|
294
|
+
size="sm"
|
|
295
|
+
variant={tab.id === activeTab ? "primary" : "ghost"}
|
|
296
|
+
accessibilityLabel={`Show ${tab.label}`}
|
|
297
|
+
style={{ width: "100%" }}
|
|
298
|
+
onPress={() => {
|
|
299
|
+
triggerHaptic("light");
|
|
300
|
+
setActiveTab(tab.id);
|
|
301
|
+
setNavigationOpen(false);
|
|
302
|
+
}}
|
|
303
|
+
/>
|
|
304
|
+
))}
|
|
305
|
+
</ActionBar>
|
|
306
|
+
</Collapsible>
|
|
307
|
+
) : (
|
|
308
|
+
<Tabs
|
|
309
|
+
tabs={showcaseTabs}
|
|
310
|
+
activeTab={activeTab}
|
|
311
|
+
onTabChange={(tab) => {
|
|
312
|
+
triggerHaptic("light");
|
|
313
|
+
setActiveTab(tab);
|
|
314
|
+
}}
|
|
315
|
+
mode="scroll"
|
|
316
|
+
/>
|
|
317
|
+
)}
|
|
318
|
+
</View>
|
|
319
|
+
{/* Top Banner Card */}
|
|
320
|
+
<Card variant="elevated">
|
|
321
|
+
<Card.Header
|
|
322
|
+
title="Helper Showcase"
|
|
323
|
+
subtitle="v0.2 UI Design System & Daemon Primitives"
|
|
324
|
+
badge={<Badge variant="accent" label={`Polling ${rate}`} />}
|
|
325
|
+
/>
|
|
326
|
+
|
|
327
|
+
{/* Refresh Interval Selector */}
|
|
328
|
+
<FormRow label="Auto Refresh" description={`Polling ${rate}`}>
|
|
329
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
330
|
+
{(["1s", "2s", "5s", "paused"] as const).map((r) => (
|
|
331
|
+
<Button
|
|
332
|
+
key={r}
|
|
333
|
+
label={r}
|
|
334
|
+
size="sm"
|
|
335
|
+
variant={rate === r ? "primary" : "ghost"}
|
|
336
|
+
onPress={() => {
|
|
337
|
+
triggerHaptic("light");
|
|
338
|
+
setRate(r);
|
|
339
|
+
}}
|
|
340
|
+
/>
|
|
341
|
+
))}
|
|
342
|
+
<Button
|
|
343
|
+
label={isLoading ? "Refreshing..." : "Refresh"}
|
|
344
|
+
size="sm"
|
|
345
|
+
variant="secondary"
|
|
346
|
+
onPress={async () => {
|
|
347
|
+
triggerHaptic("light");
|
|
348
|
+
setNavigationOpen(false);
|
|
349
|
+
await refetch();
|
|
350
|
+
}}
|
|
351
|
+
/>
|
|
352
|
+
</ActionBar>
|
|
353
|
+
</FormRow>
|
|
354
|
+
</Card>
|
|
355
|
+
|
|
356
|
+
{/* TAB 1: GAUGES & HARDWARE */}
|
|
357
|
+
{activeTab === "gauges" && (
|
|
358
|
+
<>
|
|
359
|
+
<Card variant="elevated">
|
|
360
|
+
<Card.Header title="Metric Gauges" />
|
|
361
|
+
<ActionBar align="center" direction="row" style={{ marginTop: 0 }}>
|
|
362
|
+
<MetricGauge
|
|
363
|
+
value={data?.cpuUsagePercent ?? 0}
|
|
364
|
+
label="CPU Load"
|
|
365
|
+
size={74}
|
|
366
|
+
/>
|
|
367
|
+
<MetricGauge
|
|
368
|
+
value={data?.memoryUsedPercent ?? 0}
|
|
369
|
+
label="RAM Used"
|
|
370
|
+
size={74}
|
|
371
|
+
/>
|
|
372
|
+
<MetricGauge
|
|
373
|
+
value={38}
|
|
374
|
+
label="Disk I/O"
|
|
375
|
+
size={74}
|
|
376
|
+
/>
|
|
377
|
+
</ActionBar>
|
|
378
|
+
</Card>
|
|
379
|
+
|
|
380
|
+
<Card variant="elevated">
|
|
381
|
+
<Card.Header title="Linear Progress" />
|
|
382
|
+
<ProgressBar
|
|
383
|
+
value={data?.cpuUsagePercent ?? 0}
|
|
384
|
+
height={8}
|
|
385
|
+
/>
|
|
386
|
+
<ProgressBar
|
|
387
|
+
value={data?.memoryUsedPercent ?? 0}
|
|
388
|
+
height={8}
|
|
389
|
+
/>
|
|
390
|
+
</Card>
|
|
391
|
+
|
|
392
|
+
<Card variant="elevated">
|
|
393
|
+
<Card.Header title="Host & Network" />
|
|
394
|
+
<KeyValueGroup columns={isCompact ? 1 : 2}>
|
|
395
|
+
<KeyValue
|
|
396
|
+
label="Hostname"
|
|
397
|
+
value={data?.hostname ?? "..."}
|
|
398
|
+
copyable
|
|
399
|
+
/>
|
|
400
|
+
<KeyValue
|
|
401
|
+
label="Verified Daemon Port"
|
|
402
|
+
value={data?.daemonPort ? String(data.daemonPort) : "..."}
|
|
403
|
+
copyable
|
|
404
|
+
/>
|
|
405
|
+
</KeyValueGroup>
|
|
406
|
+
<KeyValue
|
|
407
|
+
label="Processor"
|
|
408
|
+
value={data?.cpuModel ?? "..."}
|
|
409
|
+
subValue="Multi-line wrap verified"
|
|
410
|
+
copyable
|
|
411
|
+
/>
|
|
412
|
+
<KeyValue
|
|
413
|
+
label="System Uptime"
|
|
414
|
+
value={data ? formatUptime(data.uptimeSeconds) : "..."}
|
|
415
|
+
/>
|
|
416
|
+
</Card>
|
|
417
|
+
</>
|
|
418
|
+
)}
|
|
419
|
+
|
|
420
|
+
{/* TAB: VISUAL FLAIR STUDIO */}
|
|
421
|
+
{activeTab === "flair" && (
|
|
422
|
+
<>
|
|
423
|
+
<Card variant={settings.flairSurface}>
|
|
424
|
+
<Card.Header
|
|
425
|
+
title="Visual Flair Studio"
|
|
426
|
+
subtitle="Live theme & flair customizer backed by atomic settings"
|
|
427
|
+
/>
|
|
428
|
+
|
|
429
|
+
{/* Corner Radius Selector */}
|
|
430
|
+
<FormRow
|
|
431
|
+
label="Corner Radius"
|
|
432
|
+
description={`Active preset: "${settings.flairRadius}"`}
|
|
433
|
+
>
|
|
434
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
435
|
+
{(["sharp", "rounded", "pill"] as const).map((r) => (
|
|
436
|
+
<Button
|
|
437
|
+
key={r}
|
|
438
|
+
size="sm"
|
|
439
|
+
label={r.toUpperCase()}
|
|
440
|
+
variant={settings.flairRadius === r ? "primary" : "ghost"}
|
|
441
|
+
onPress={() => {
|
|
442
|
+
triggerHaptic("light");
|
|
443
|
+
updateSettings({ flairRadius: r });
|
|
444
|
+
}}
|
|
445
|
+
/>
|
|
446
|
+
))}
|
|
447
|
+
</ActionBar>
|
|
448
|
+
</FormRow>
|
|
449
|
+
|
|
450
|
+
{/* Information Density Selector */}
|
|
451
|
+
<FormRow
|
|
452
|
+
label="Layout Density"
|
|
453
|
+
description={`Active density: "${settings.flairDensity}"`}
|
|
454
|
+
>
|
|
455
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
456
|
+
{(["compact", "comfortable", "spacious"] as const).map((d) => (
|
|
457
|
+
<Button
|
|
458
|
+
key={d}
|
|
459
|
+
size="sm"
|
|
460
|
+
label={d.charAt(0).toUpperCase() + d.slice(1)}
|
|
461
|
+
variant={settings.flairDensity === d ? "primary" : "ghost"}
|
|
462
|
+
onPress={() => {
|
|
463
|
+
triggerHaptic("light");
|
|
464
|
+
updateSettings({ flairDensity: d });
|
|
465
|
+
}}
|
|
466
|
+
/>
|
|
467
|
+
))}
|
|
468
|
+
</ActionBar>
|
|
469
|
+
</FormRow>
|
|
470
|
+
|
|
471
|
+
{/* Surface Styling Selector */}
|
|
472
|
+
<FormRow
|
|
473
|
+
label="Surface Treatment"
|
|
474
|
+
description={`Active surface: "${settings.flairSurface}"`}
|
|
475
|
+
>
|
|
476
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
477
|
+
{(["flat", "tinted", "elevated"] as const).map((s) => (
|
|
478
|
+
<Button
|
|
479
|
+
key={s}
|
|
480
|
+
size="sm"
|
|
481
|
+
label={s.charAt(0).toUpperCase() + s.slice(1)}
|
|
482
|
+
variant={settings.flairSurface === s ? "primary" : "ghost"}
|
|
483
|
+
onPress={() => {
|
|
484
|
+
triggerHaptic("light");
|
|
485
|
+
updateSettings({ flairSurface: s });
|
|
486
|
+
}}
|
|
487
|
+
/>
|
|
488
|
+
))}
|
|
489
|
+
</ActionBar>
|
|
490
|
+
</FormRow>
|
|
491
|
+
|
|
492
|
+
{/* Border Width Stepper / Selector */}
|
|
493
|
+
<FormRow
|
|
494
|
+
label="Border Width"
|
|
495
|
+
description={`Container outline stroke: ${settings.flairBorderWidth}px`}
|
|
496
|
+
>
|
|
497
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
498
|
+
{[0, 1, 2, 3].map((w) => (
|
|
499
|
+
<Button
|
|
500
|
+
key={w}
|
|
501
|
+
size="sm"
|
|
502
|
+
label={`${w}px`}
|
|
503
|
+
variant={settings.flairBorderWidth === w ? "primary" : "ghost"}
|
|
504
|
+
onPress={() => {
|
|
505
|
+
triggerHaptic("light");
|
|
506
|
+
updateSettings({ flairBorderWidth: w });
|
|
507
|
+
}}
|
|
508
|
+
/>
|
|
509
|
+
))}
|
|
510
|
+
</ActionBar>
|
|
511
|
+
</FormRow>
|
|
512
|
+
|
|
513
|
+
{/* Brand Accent Color Swatches */}
|
|
514
|
+
<FormRow
|
|
515
|
+
label="Brand Accent Color"
|
|
516
|
+
description={`Current accent: ${settings.flairAccentColor}`}
|
|
517
|
+
>
|
|
518
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
519
|
+
{[
|
|
520
|
+
{ label: "Indigo", color: "#6366f1" },
|
|
521
|
+
{ label: "Emerald", color: "#10b981" },
|
|
522
|
+
{ label: "Violet", color: "#8b5cf6" },
|
|
523
|
+
{ label: "Amber", color: "#f59e0b" },
|
|
524
|
+
{ label: "Rose", color: "#f43f5e" },
|
|
525
|
+
{ label: "Cyan", color: "#06b6d4" },
|
|
526
|
+
].map((swatch) => {
|
|
527
|
+
const isSelected = settings.flairAccentColor.toLowerCase() === swatch.color.toLowerCase();
|
|
528
|
+
return (
|
|
529
|
+
<Button
|
|
530
|
+
key={swatch.color}
|
|
531
|
+
size="sm"
|
|
532
|
+
accessibilityLabel={`Select ${swatch.label} accent color`}
|
|
533
|
+
onPress={() => {
|
|
534
|
+
triggerHaptic("light");
|
|
535
|
+
updateSettings({ flairAccentColor: swatch.color });
|
|
536
|
+
}}
|
|
537
|
+
style={{
|
|
538
|
+
backgroundColor: swatch.color,
|
|
539
|
+
borderColor: isSelected ? colors.foreground : "transparent",
|
|
540
|
+
borderWidth: 2,
|
|
541
|
+
borderRadius: 9999,
|
|
542
|
+
width: touchTargetMin,
|
|
543
|
+
height: touchTargetMin,
|
|
544
|
+
minWidth: touchTargetMin,
|
|
545
|
+
minHeight: touchTargetMin,
|
|
546
|
+
paddingHorizontal: 0,
|
|
547
|
+
paddingVertical: 0,
|
|
548
|
+
}}
|
|
549
|
+
/>
|
|
550
|
+
);
|
|
551
|
+
})}
|
|
552
|
+
</ActionBar>
|
|
553
|
+
</FormRow>
|
|
554
|
+
|
|
555
|
+
{/* Uppercase Header Switch */}
|
|
556
|
+
<FormRow
|
|
557
|
+
label="Uppercase Section Headings"
|
|
558
|
+
description="Transform component section titles to uppercase"
|
|
559
|
+
>
|
|
560
|
+
<Toggle
|
|
561
|
+
value={settings.flairUppercase}
|
|
562
|
+
onValueChange={(val) => {
|
|
563
|
+
triggerHaptic("light");
|
|
564
|
+
updateSettings({ flairUppercase: val });
|
|
565
|
+
}}
|
|
566
|
+
/>
|
|
567
|
+
</FormRow>
|
|
568
|
+
|
|
569
|
+
{/* Live Component Preview Card */}
|
|
570
|
+
<Card variant={settings.flairSurface} style={{ marginTop: 8 }}>
|
|
571
|
+
<Card.Header
|
|
572
|
+
title="Live Component Preview"
|
|
573
|
+
subtitle="Reflects your active Visual Flair in real-time"
|
|
574
|
+
/>
|
|
575
|
+
<KeyValueGroup columns={isCompact ? 1 : 2}>
|
|
576
|
+
<KeyValue label="Status" value="Production Ready" />
|
|
577
|
+
<KeyValue label="Radius Mode" value={settings.flairRadius} />
|
|
578
|
+
</KeyValueGroup>
|
|
579
|
+
<ActionBar align="flex-start">
|
|
580
|
+
<Button label="Primary Button" variant="primary" size="sm" />
|
|
581
|
+
<Button label="Secondary" variant="secondary" size="sm" />
|
|
582
|
+
<Badge label="Adaptive Badge" variant="accent" />
|
|
583
|
+
</ActionBar>
|
|
584
|
+
</Card>
|
|
585
|
+
</Card>
|
|
586
|
+
</>
|
|
587
|
+
)}
|
|
588
|
+
|
|
589
|
+
{/* TAB 2: DATA TABLE */}
|
|
590
|
+
{activeTab === "data" && (
|
|
591
|
+
<Card variant="elevated">
|
|
592
|
+
<Card.Header title={`Services Table (${isCompact ? "Compact 2-Col" : "Desktop 3-Col"})`} />
|
|
593
|
+
<SearchInput
|
|
594
|
+
value={searchQuery}
|
|
595
|
+
onChangeText={setSearchQuery}
|
|
596
|
+
placeholder="Search service name or category..."
|
|
597
|
+
/>
|
|
598
|
+
|
|
599
|
+
<DataTable
|
|
600
|
+
data={filteredItems}
|
|
601
|
+
keyExtractor={(item) => item.id}
|
|
602
|
+
emptyState={
|
|
603
|
+
<EmptyState
|
|
604
|
+
icon="Search"
|
|
605
|
+
title="No Services Found"
|
|
606
|
+
description="Try clearing your search query."
|
|
607
|
+
/>
|
|
608
|
+
}
|
|
609
|
+
columns={
|
|
610
|
+
isCompact
|
|
611
|
+
? [
|
|
612
|
+
{
|
|
613
|
+
key: "name",
|
|
614
|
+
header: "Service",
|
|
615
|
+
flex: 2,
|
|
616
|
+
render: (item) => (
|
|
617
|
+
// Raw View/Text: DataTable column render slots have no
|
|
618
|
+
// helper cell-text primitive, so this composite stays local.
|
|
619
|
+
<View>
|
|
620
|
+
<Text style={[{ color: colors.foreground, ...typography.bodyStrong }]}>
|
|
621
|
+
{item.name}
|
|
622
|
+
</Text>
|
|
623
|
+
<Text style={[{ color: colors.foregroundMuted, ...typography.caption }]}>
|
|
624
|
+
{item.category}
|
|
625
|
+
</Text>
|
|
626
|
+
</View>
|
|
627
|
+
),
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
key: "load",
|
|
631
|
+
header: "Load",
|
|
632
|
+
align: "right",
|
|
633
|
+
render: (item) => (
|
|
634
|
+
<Text style={[{ color: colors.foreground, ...typography.bodyStrong }]}>
|
|
635
|
+
{item.loadPercent}%
|
|
636
|
+
</Text>
|
|
637
|
+
),
|
|
638
|
+
},
|
|
639
|
+
]
|
|
640
|
+
: [
|
|
641
|
+
{
|
|
642
|
+
key: "name",
|
|
643
|
+
header: "Service",
|
|
644
|
+
flex: 2,
|
|
645
|
+
render: (item) => (
|
|
646
|
+
// Raw View/Text: DataTable column render slots have no
|
|
647
|
+
// helper cell-text primitive, so this composite stays local.
|
|
648
|
+
<View>
|
|
649
|
+
<Text style={[{ color: colors.foreground, ...typography.bodyStrong }]}>
|
|
650
|
+
{item.name}
|
|
651
|
+
</Text>
|
|
652
|
+
<Text style={[{ color: colors.foregroundMuted, ...typography.caption }]}>
|
|
653
|
+
{item.category}
|
|
654
|
+
</Text>
|
|
655
|
+
</View>
|
|
656
|
+
),
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
key: "status",
|
|
660
|
+
header: "Status",
|
|
661
|
+
align: "center",
|
|
662
|
+
render: (item) => (
|
|
663
|
+
<Badge
|
|
664
|
+
label={item.status}
|
|
665
|
+
variant={item.status === "running" ? "success" : "warning"}
|
|
666
|
+
/>
|
|
667
|
+
),
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
key: "load",
|
|
671
|
+
header: "Load",
|
|
672
|
+
align: "right",
|
|
673
|
+
render: (item) => (
|
|
674
|
+
<Text style={[{ color: colors.foreground, ...typography.bodyStrong }]}>
|
|
675
|
+
{item.loadPercent}%
|
|
676
|
+
</Text>
|
|
677
|
+
),
|
|
678
|
+
},
|
|
679
|
+
]
|
|
680
|
+
}
|
|
681
|
+
/>
|
|
682
|
+
</Card>
|
|
683
|
+
)}
|
|
684
|
+
|
|
685
|
+
{/* TAB 3: CONTROLS & CODE */}
|
|
686
|
+
{activeTab === "controls" && (
|
|
687
|
+
<>
|
|
688
|
+
<Card variant="elevated">
|
|
689
|
+
<Card.Header title="Toggles & Actions" />
|
|
690
|
+
<Toggle
|
|
691
|
+
label="Live Log Streaming"
|
|
692
|
+
description="Stream background task ticks to console"
|
|
693
|
+
value={liveStream}
|
|
694
|
+
onValueChange={(val) => {
|
|
695
|
+
triggerHaptic("medium");
|
|
696
|
+
setLiveStream(val);
|
|
697
|
+
}}
|
|
698
|
+
/>
|
|
699
|
+
|
|
700
|
+
<Button
|
|
701
|
+
label={isActionPending ? "Running RPC..." : "Trigger Background RPC Action"}
|
|
702
|
+
variant="primary"
|
|
703
|
+
onPress={() => runAction({ actionName: "Showcase Trigger" })}
|
|
704
|
+
/>
|
|
705
|
+
|
|
706
|
+
{actionFeedback ? (
|
|
707
|
+
<Text style={{ color: colors.statusSuccess, ...typography.bodyStrong, marginTop: 6 }}>
|
|
708
|
+
{actionFeedback}
|
|
709
|
+
</Text>
|
|
710
|
+
) : null}
|
|
711
|
+
</Card>
|
|
712
|
+
|
|
713
|
+
<Card variant="elevated">
|
|
714
|
+
<Card.Header title="Sample Code Block" />
|
|
715
|
+
<CodeBlock
|
|
716
|
+
language="typescript"
|
|
717
|
+
code={`import { createPluginPill, MetricGauge } from "paseo-plugin-helper/client";\n\n// Renders circular ring\n<MetricGauge value={75} label="CPU Load" />`}
|
|
718
|
+
copyable
|
|
719
|
+
/>
|
|
720
|
+
</Card>
|
|
721
|
+
</>
|
|
722
|
+
)}
|
|
723
|
+
|
|
724
|
+
{/* TAB: ATTENTION & BEACONS */}
|
|
725
|
+
{activeTab === "attention" && (
|
|
726
|
+
<>
|
|
727
|
+
<Card variant="elevated">
|
|
728
|
+
<Card.Header
|
|
729
|
+
title="Workspace Beacon"
|
|
730
|
+
subtitle="Live workspace-row status ticker driven by createWorkspaceBeacon()"
|
|
731
|
+
/>
|
|
732
|
+
{/* Raw Text: the helper has no standalone caption/paragraph
|
|
733
|
+
primitive, so explanatory copy stays local and uses typography. */}
|
|
734
|
+
<Text style={{ color: colors.foregroundMuted, ...typography.caption, marginTop: 8 }}>
|
|
735
|
+
Targets this workspace ({workspaceId}). Watch the workspace row chip and title while
|
|
736
|
+
triggering.
|
|
737
|
+
</Text>
|
|
738
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
739
|
+
<Button
|
|
740
|
+
label={isBeaconSetPending ? "Setting..." : "Set Status Beacon"}
|
|
741
|
+
variant="primary"
|
|
742
|
+
size="sm"
|
|
743
|
+
onPress={() => {
|
|
744
|
+
triggerHaptic("medium");
|
|
745
|
+
setBeacon({ workspaceId, name: "DEMO:ACTIVE", color: "sky" });
|
|
746
|
+
}}
|
|
747
|
+
/>
|
|
748
|
+
<Button
|
|
749
|
+
label={isBeaconBlinkPending ? "Blinking..." : "Blink Beacon (5 rounds)"}
|
|
750
|
+
variant="secondary"
|
|
751
|
+
size="sm"
|
|
752
|
+
onPress={() => {
|
|
753
|
+
triggerHaptic("medium");
|
|
754
|
+
blinkBeacon({ workspaceId, rounds: 5 });
|
|
755
|
+
}}
|
|
756
|
+
/>
|
|
757
|
+
<Button
|
|
758
|
+
label={isBeaconClearPending ? "Clearing..." : "Clear Beacon"}
|
|
759
|
+
variant="ghost"
|
|
760
|
+
size="sm"
|
|
761
|
+
onPress={() => {
|
|
762
|
+
triggerHaptic("light");
|
|
763
|
+
clearBeacon({ workspaceId, name: "DEMO:ACTIVE" });
|
|
764
|
+
}}
|
|
765
|
+
/>
|
|
766
|
+
</ActionBar>
|
|
767
|
+
{beaconFeedback ? (
|
|
768
|
+
<Text style={{ color: colors.statusSuccess, ...typography.bodyStrong, marginTop: 6 }}>
|
|
769
|
+
{beaconFeedback}
|
|
770
|
+
</Text>
|
|
771
|
+
) : null}
|
|
772
|
+
</Card>
|
|
773
|
+
|
|
774
|
+
<Card variant="elevated">
|
|
775
|
+
<Card.Header
|
|
776
|
+
title="Attention Playground"
|
|
777
|
+
subtitle="Live toggles for beacon animation, mode, and tone"
|
|
778
|
+
/>
|
|
779
|
+
<FormRow
|
|
780
|
+
label="Attention Active"
|
|
781
|
+
description={attentionOn ? "Beacons animating" : "Static wrappers (reduced-motion safe)"}
|
|
782
|
+
>
|
|
783
|
+
<Toggle
|
|
784
|
+
value={attentionOn}
|
|
785
|
+
onValueChange={(val) => {
|
|
786
|
+
triggerHaptic("light");
|
|
787
|
+
setAttentionOn(val);
|
|
788
|
+
}}
|
|
789
|
+
/>
|
|
790
|
+
</FormRow>
|
|
791
|
+
|
|
792
|
+
<FormRow
|
|
793
|
+
label="Beacon Mode"
|
|
794
|
+
description={`Active mode: "${beaconMode}"`}
|
|
795
|
+
>
|
|
796
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
797
|
+
{(["radar", "glow", "badge", "bounce"] as const).map((m) => (
|
|
798
|
+
<Button
|
|
799
|
+
key={m}
|
|
800
|
+
size="sm"
|
|
801
|
+
label={m}
|
|
802
|
+
variant={beaconMode === m ? "primary" : "ghost"}
|
|
803
|
+
onPress={() => {
|
|
804
|
+
triggerHaptic("light");
|
|
805
|
+
setBeaconMode(m);
|
|
806
|
+
}}
|
|
807
|
+
/>
|
|
808
|
+
))}
|
|
809
|
+
</ActionBar>
|
|
810
|
+
</FormRow>
|
|
811
|
+
|
|
812
|
+
<FormRow
|
|
813
|
+
label="Beacon Tone"
|
|
814
|
+
description={`Active tone token: "${beaconTone === "warning" ? "statusWarning" : beaconTone}"`}
|
|
815
|
+
>
|
|
816
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
817
|
+
{(
|
|
818
|
+
[
|
|
819
|
+
{ token: "statusWarning", tone: "warning" },
|
|
820
|
+
{ token: "accent", tone: "accent" },
|
|
821
|
+
{ token: "statusDanger", tone: "danger" },
|
|
822
|
+
] as const
|
|
823
|
+
).map(({ token, tone }) => (
|
|
824
|
+
<Button
|
|
825
|
+
key={token}
|
|
826
|
+
size="sm"
|
|
827
|
+
label={token}
|
|
828
|
+
variant={beaconTone === tone ? "primary" : "ghost"}
|
|
829
|
+
onPress={() => {
|
|
830
|
+
triggerHaptic("light");
|
|
831
|
+
setBeaconTone(tone);
|
|
832
|
+
}}
|
|
833
|
+
/>
|
|
834
|
+
))}
|
|
835
|
+
</ActionBar>
|
|
836
|
+
</FormRow>
|
|
837
|
+
</Card>
|
|
838
|
+
|
|
839
|
+
<Card variant="elevated">
|
|
840
|
+
<Card.Header
|
|
841
|
+
title="Button attention prop"
|
|
842
|
+
subtitle="Direct attention across button variants"
|
|
843
|
+
/>
|
|
844
|
+
<SectionHeader title="Primary" />
|
|
845
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
846
|
+
<Button label="Radar" variant="primary" size="sm" attention={attentionOn ? "radar" : undefined} />
|
|
847
|
+
<Button label="Glow" variant="primary" size="sm" attention={attentionOn ? "glow" : undefined} />
|
|
848
|
+
<Button label="Bounce" variant="primary" size="sm" attention={attentionOn ? "bounce" : undefined} />
|
|
849
|
+
</ActionBar>
|
|
850
|
+
<SectionHeader title="Danger" />
|
|
851
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
852
|
+
<Button label="Radar" variant="danger" size="sm" attention={attentionOn ? "radar" : undefined} />
|
|
853
|
+
<Button label="Glow" variant="danger" size="sm" attention={attentionOn ? "glow" : undefined} />
|
|
854
|
+
<Button label="Bounce" variant="danger" size="sm" attention={attentionOn ? "bounce" : undefined} />
|
|
855
|
+
</ActionBar>
|
|
856
|
+
<SectionHeader title="Secondary / Ghost" />
|
|
857
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
858
|
+
<Button label="Radar" variant="secondary" size="sm" attention={attentionOn ? "radar" : undefined} />
|
|
859
|
+
<Button label="Default (true)" variant="ghost" size="sm" attention={attentionOn ? true : undefined} />
|
|
860
|
+
</ActionBar>
|
|
861
|
+
<Text style={{ color: colors.foregroundMuted, ...typography.caption, marginTop: 8 }}>
|
|
862
|
+
Button accepts boolean | radar | glow | bounce. badge and ring are wrapper-only via AttentionBeacon.
|
|
863
|
+
</Text>
|
|
864
|
+
</Card>
|
|
865
|
+
|
|
866
|
+
<Card variant="elevated">
|
|
867
|
+
<Card.Header
|
|
868
|
+
title="Beacon Wrappers"
|
|
869
|
+
subtitle="AttentionBeacon around arbitrary components (playground-driven)"
|
|
870
|
+
/>
|
|
871
|
+
<AttentionBeacon mode={beaconMode} tone={beaconTone} active={attentionOn}>
|
|
872
|
+
<Card>
|
|
873
|
+
<Card.Header
|
|
874
|
+
title="Urgent Review Required"
|
|
875
|
+
subtitle="Simulated verdict awaiting operator"
|
|
876
|
+
/>
|
|
877
|
+
<KeyValue
|
|
878
|
+
label="Awaiting"
|
|
879
|
+
value="Command execution verdict"
|
|
880
|
+
subValue="twofado-style urgent prompt"
|
|
881
|
+
/>
|
|
882
|
+
<ActionBar align="flex-start">
|
|
883
|
+
<Button label="Approve" variant="primary" size="sm" />
|
|
884
|
+
<Button label="Deny" variant="danger" size="sm" />
|
|
885
|
+
</ActionBar>
|
|
886
|
+
</Card>
|
|
887
|
+
</AttentionBeacon>
|
|
888
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
889
|
+
<AttentionBeacon mode={beaconMode} tone={beaconTone} active={attentionOn}>
|
|
890
|
+
<Badge label="Wrapped Badge" variant="warning" />
|
|
891
|
+
</AttentionBeacon>
|
|
892
|
+
<AttentionBeacon mode={beaconMode} tone={beaconTone} active={attentionOn}>
|
|
893
|
+
<Button
|
|
894
|
+
label={`Custom clickable (${beaconPresses})`}
|
|
895
|
+
variant="secondary"
|
|
896
|
+
size="sm"
|
|
897
|
+
accessibilityLabel="Custom beacon-wrapped clickable"
|
|
898
|
+
onPress={() => {
|
|
899
|
+
triggerHaptic("medium");
|
|
900
|
+
setBeaconPresses((n) => n + 1);
|
|
901
|
+
}}
|
|
902
|
+
/>
|
|
903
|
+
</AttentionBeacon>
|
|
904
|
+
</ActionBar>
|
|
905
|
+
</Card>
|
|
906
|
+
|
|
907
|
+
<Card variant="elevated">
|
|
908
|
+
<Card.Header
|
|
909
|
+
title="Mode x Tone Matrix"
|
|
910
|
+
subtitle="All four modes side by side in the playground tone"
|
|
911
|
+
/>
|
|
912
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
913
|
+
{(["radar", "glow", "badge", "bounce"] as const).map((m) => (
|
|
914
|
+
<AttentionBeacon key={m} mode={m} tone={beaconTone} active={attentionOn}>
|
|
915
|
+
<Badge label={m} variant="accent" />
|
|
916
|
+
</AttentionBeacon>
|
|
917
|
+
))}
|
|
918
|
+
</ActionBar>
|
|
919
|
+
<SectionHeader title="Tones in playground mode" />
|
|
920
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
921
|
+
{(["warning", "accent", "danger"] as const).map((t) => (
|
|
922
|
+
<AttentionBeacon key={t} mode={beaconMode} tone={t} active={attentionOn}>
|
|
923
|
+
<Badge label={t === "warning" ? "statusWarning" : t} variant={t} />
|
|
924
|
+
</AttentionBeacon>
|
|
925
|
+
))}
|
|
926
|
+
</ActionBar>
|
|
927
|
+
<SectionHeader title="Ring alias equivalence" />
|
|
928
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
929
|
+
<AttentionBeacon mode="radar" tone={beaconTone} active={attentionOn}>
|
|
930
|
+
<Badge label="radar" variant="accent" />
|
|
931
|
+
</AttentionBeacon>
|
|
932
|
+
<AttentionBeacon mode="ring" tone={beaconTone} active={attentionOn}>
|
|
933
|
+
<Badge label="ring" variant="accent" />
|
|
934
|
+
</AttentionBeacon>
|
|
935
|
+
</ActionBar>
|
|
936
|
+
<Text style={{ color: colors.foregroundMuted, ...typography.caption, marginTop: 8 }}>
|
|
937
|
+
ring normalizes to radar; both halos pulse identically.
|
|
938
|
+
</Text>
|
|
939
|
+
<SectionHeader title="Badge with icon" />
|
|
940
|
+
<Text style={{ color: colors.foregroundMuted, ...typography.caption, marginTop: 8 }}>
|
|
941
|
+
Badge accepts string host icons and custom emoji/graphic nodes via the icon prop, plus a dot variant.
|
|
942
|
+
</Text>
|
|
943
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
944
|
+
<Badge icon="Bell" label="Bell Icon" variant="danger" />
|
|
945
|
+
<Badge icon="Flame" label="Flame Icon" variant="warning" />
|
|
946
|
+
<Badge
|
|
947
|
+
icon={<Text style={{ fontSize: typography.caption.fontSize }}>🚀</Text>}
|
|
948
|
+
label="Emoji Graphic"
|
|
949
|
+
variant="accent"
|
|
950
|
+
/>
|
|
951
|
+
<Badge
|
|
952
|
+
icon={<Text style={{ fontSize: typography.caption.fontSize }}>⚡</Text>}
|
|
953
|
+
label="Zap Graphic"
|
|
954
|
+
variant="success"
|
|
955
|
+
/>
|
|
956
|
+
<Badge dot label="Status Dot" variant="info" />
|
|
957
|
+
</ActionBar>
|
|
958
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
959
|
+
<AttentionBeacon mode="badge" tone="danger" active={attentionOn}>
|
|
960
|
+
<Badge icon="Bell" label="Bell Icon" variant="danger" />
|
|
961
|
+
</AttentionBeacon>
|
|
962
|
+
<AttentionBeacon mode="badge" tone="warning" active={attentionOn}>
|
|
963
|
+
<Badge icon="Flame" label="Flame Icon" variant="warning" />
|
|
964
|
+
</AttentionBeacon>
|
|
965
|
+
<AttentionBeacon mode="badge" tone="accent" active={attentionOn}>
|
|
966
|
+
<Badge
|
|
967
|
+
icon={<Text style={{ fontSize: typography.caption.fontSize }}>🚀</Text>}
|
|
968
|
+
label="Emoji Graphic"
|
|
969
|
+
variant="accent"
|
|
970
|
+
/>
|
|
971
|
+
</AttentionBeacon>
|
|
972
|
+
<AttentionBeacon mode="badge" tone="accent" active={attentionOn}>
|
|
973
|
+
<Badge
|
|
974
|
+
icon={<Text style={{ fontSize: typography.caption.fontSize }}>⚡</Text>}
|
|
975
|
+
label="Zap Graphic"
|
|
976
|
+
variant="success"
|
|
977
|
+
/>
|
|
978
|
+
</AttentionBeacon>
|
|
979
|
+
<AttentionBeacon mode="badge" tone={beaconTone} active={attentionOn}>
|
|
980
|
+
<Badge dot label="Status Dot" variant="info" />
|
|
981
|
+
</AttentionBeacon>
|
|
982
|
+
</ActionBar>
|
|
983
|
+
</Card>
|
|
984
|
+
</>
|
|
985
|
+
)}
|
|
986
|
+
|
|
987
|
+
{/* TAB: SETTINGS & STORAGE */}
|
|
988
|
+
{activeTab === "settings" && (
|
|
989
|
+
<>
|
|
990
|
+
<SharedSuiteCard />
|
|
991
|
+
<Card variant="elevated">
|
|
992
|
+
<Card.Header
|
|
993
|
+
title="Plugin Settings"
|
|
994
|
+
subtitle="Type-safe Zod storage with optimistic React Query updates"
|
|
995
|
+
/>
|
|
996
|
+
<FormRow
|
|
997
|
+
label="Show CPU in Pill"
|
|
998
|
+
description="Toggle whether the CPU usage percent is visible in the composer bar"
|
|
999
|
+
>
|
|
1000
|
+
<Toggle
|
|
1001
|
+
value={settings.showCpuUsage}
|
|
1002
|
+
onValueChange={(val) => {
|
|
1003
|
+
triggerHaptic("light");
|
|
1004
|
+
updateSettings({ showCpuUsage: val });
|
|
1005
|
+
}}
|
|
1006
|
+
/>
|
|
1007
|
+
</FormRow>
|
|
1008
|
+
|
|
1009
|
+
<FormRow
|
|
1010
|
+
label="Navigation Style"
|
|
1011
|
+
description="Choose tabs or a readable dropdown for the showcase navbar"
|
|
1012
|
+
>
|
|
1013
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
1014
|
+
<Button
|
|
1015
|
+
label="Tabs"
|
|
1016
|
+
size="sm"
|
|
1017
|
+
variant={settings.navigationStyle === "tabs" ? "primary" : "secondary"}
|
|
1018
|
+
onPress={() => {
|
|
1019
|
+
triggerHaptic("light");
|
|
1020
|
+
updateSettings({ navigationStyle: "tabs" });
|
|
1021
|
+
}}
|
|
1022
|
+
/>
|
|
1023
|
+
<Button
|
|
1024
|
+
label="Dropdown"
|
|
1025
|
+
size="sm"
|
|
1026
|
+
variant={settings.navigationStyle === "dropdown" ? "primary" : "secondary"}
|
|
1027
|
+
onPress={() => {
|
|
1028
|
+
triggerHaptic("light");
|
|
1029
|
+
updateSettings({ navigationStyle: "dropdown" });
|
|
1030
|
+
}}
|
|
1031
|
+
/>
|
|
1032
|
+
</ActionBar>
|
|
1033
|
+
</FormRow>
|
|
1034
|
+
|
|
1035
|
+
<FormRow
|
|
1036
|
+
label="Pill Accent Label"
|
|
1037
|
+
description="Custom label displayed at the front of the composer pill"
|
|
1038
|
+
>
|
|
1039
|
+
<TextInput
|
|
1040
|
+
value={settings.accentPillLabel}
|
|
1041
|
+
onChangeText={(text) => updateSettings({ accentPillLabel: text })}
|
|
1042
|
+
placeholder="demo"
|
|
1043
|
+
/>
|
|
1044
|
+
</FormRow>
|
|
1045
|
+
|
|
1046
|
+
<FormRow
|
|
1047
|
+
label="Alert Threshold"
|
|
1048
|
+
description={`Turns the status dot red when CPU exceeds this percent (Current: ${settings.highCpuThreshold}%)`}
|
|
1049
|
+
>
|
|
1050
|
+
<TextInput
|
|
1051
|
+
value={String(settings.highCpuThreshold)}
|
|
1052
|
+
keyboardType="numeric"
|
|
1053
|
+
onChangeText={(text) => {
|
|
1054
|
+
const val = parseInt(text, 10);
|
|
1055
|
+
if (!isNaN(val)) updateSettings({ highCpuThreshold: val });
|
|
1056
|
+
}}
|
|
1057
|
+
/>
|
|
1058
|
+
</FormRow>
|
|
1059
|
+
|
|
1060
|
+
<ActionBar align="space-between">
|
|
1061
|
+
<Text style={{ color: colors.foregroundMuted, ...typography.caption }}>
|
|
1062
|
+
{isSettingsUpdating ? "Saving to disk..." : "Saved to settings.json atomically"}
|
|
1063
|
+
</Text>
|
|
1064
|
+
<Button
|
|
1065
|
+
label="Reset Defaults"
|
|
1066
|
+
variant="secondary"
|
|
1067
|
+
size="sm"
|
|
1068
|
+
onPress={async () => {
|
|
1069
|
+
triggerHaptic("warning");
|
|
1070
|
+
await resetSettings();
|
|
1071
|
+
}}
|
|
1072
|
+
/>
|
|
1073
|
+
</ActionBar>
|
|
1074
|
+
</Card>
|
|
1075
|
+
</>
|
|
1076
|
+
)}
|
|
1077
|
+
|
|
1078
|
+
{/* TAB 4: NETWORK DIAGNOSTICS */}
|
|
1079
|
+
{activeTab === "network" && (
|
|
1080
|
+
<Card variant="elevated">
|
|
1081
|
+
<Card.Header
|
|
1082
|
+
title="Network & Ports"
|
|
1083
|
+
subtitle="Daemon network diagnostics"
|
|
1084
|
+
/>
|
|
1085
|
+
<KeyValueGroup>
|
|
1086
|
+
<KeyValue
|
|
1087
|
+
label="Daemon TCP Port"
|
|
1088
|
+
value={`${data?.daemonPort ?? 4280}`}
|
|
1089
|
+
subValue="Local verified socket"
|
|
1090
|
+
copyable
|
|
1091
|
+
/>
|
|
1092
|
+
<KeyValue
|
|
1093
|
+
label="Network Connectivity"
|
|
1094
|
+
value="Active (Loopback)"
|
|
1095
|
+
/>
|
|
1096
|
+
</KeyValueGroup>
|
|
1097
|
+
</Card>
|
|
1098
|
+
)}
|
|
1099
|
+
|
|
1100
|
+
{/* TAB 5: SYSTEM LOGS */}
|
|
1101
|
+
{activeTab === "logs" && (
|
|
1102
|
+
<Card variant="elevated">
|
|
1103
|
+
<Card.Header
|
|
1104
|
+
title="System Logs"
|
|
1105
|
+
subtitle={`Background task tick #${data?.backgroundTicks ?? 0}`}
|
|
1106
|
+
/>
|
|
1107
|
+
<CodeBlock
|
|
1108
|
+
language="bash"
|
|
1109
|
+
code={`[INFO] Server running on ${data?.hostname} (${data?.platform})\n[INFO] Background task alive, uptime: ${Math.round(data?.uptimeSeconds ?? 0)}s\n[INFO] Periodic health check OK`}
|
|
1110
|
+
/>
|
|
1111
|
+
</Card>
|
|
1112
|
+
)}
|
|
1113
|
+
|
|
1114
|
+
{/* TAB 6: ABOUT PLUGIN */}
|
|
1115
|
+
{activeTab === "about" && (
|
|
1116
|
+
<>
|
|
1117
|
+
<Card variant="elevated">
|
|
1118
|
+
<Card.Header
|
|
1119
|
+
title="Agent Identity & Session"
|
|
1120
|
+
subtitle="Self-inspection via getAgentIdentity()"
|
|
1121
|
+
/>
|
|
1122
|
+
{isAgentLoading ? (
|
|
1123
|
+
<Text style={{ color: colors.foregroundMuted, ...typography.caption, marginTop: 8 }}>
|
|
1124
|
+
Resolving agent identity...
|
|
1125
|
+
</Text>
|
|
1126
|
+
) : !agentIdentity ? (
|
|
1127
|
+
<EmptyState
|
|
1128
|
+
icon="Bot"
|
|
1129
|
+
title="No Active Agent Session"
|
|
1130
|
+
description="Running outside an active agent session. Identity resolves via getAgentIdentity() when a session envelope is present."
|
|
1131
|
+
/>
|
|
1132
|
+
) : (
|
|
1133
|
+
<>
|
|
1134
|
+
<KeyValueGroup columns={isCompact ? 1 : 2}>
|
|
1135
|
+
<KeyValue
|
|
1136
|
+
label="Active Agent ID"
|
|
1137
|
+
value={agentIdentity.id ?? "—"}
|
|
1138
|
+
copyable={Boolean(agentIdentity.id)}
|
|
1139
|
+
/>
|
|
1140
|
+
<KeyValue
|
|
1141
|
+
label="Session Name"
|
|
1142
|
+
value={agentIdentity.name ?? "—"}
|
|
1143
|
+
copyable={Boolean(agentIdentity.name)}
|
|
1144
|
+
/>
|
|
1145
|
+
<KeyValue
|
|
1146
|
+
label="Model"
|
|
1147
|
+
value={agentIdentity.model ?? "—"}
|
|
1148
|
+
copyable={Boolean(agentIdentity.model)}
|
|
1149
|
+
/>
|
|
1150
|
+
<KeyValue
|
|
1151
|
+
label="Provider"
|
|
1152
|
+
value={agentIdentity.provider ?? "—"}
|
|
1153
|
+
copyable={Boolean(agentIdentity.provider)}
|
|
1154
|
+
/>
|
|
1155
|
+
<KeyValue
|
|
1156
|
+
label="Repo"
|
|
1157
|
+
value={agentIdentity.repo ?? "—"}
|
|
1158
|
+
copyable={Boolean(agentIdentity.repo)}
|
|
1159
|
+
/>
|
|
1160
|
+
<KeyValue
|
|
1161
|
+
label="Branch"
|
|
1162
|
+
value={agentIdentity.branch ?? "—"}
|
|
1163
|
+
copyable={Boolean(agentIdentity.branch)}
|
|
1164
|
+
/>
|
|
1165
|
+
</KeyValueGroup>
|
|
1166
|
+
{agentIdentity.envelopeText ? (
|
|
1167
|
+
<>
|
|
1168
|
+
<ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
|
|
1169
|
+
<Badge label="Audit envelope present" variant="success" />
|
|
1170
|
+
<Button
|
|
1171
|
+
label={showEnvelope ? "Hide Envelope" : "Inspect Envelope"}
|
|
1172
|
+
size="sm"
|
|
1173
|
+
variant="secondary"
|
|
1174
|
+
onPress={() => {
|
|
1175
|
+
triggerHaptic("light");
|
|
1176
|
+
setShowEnvelope((v) => !v);
|
|
1177
|
+
}}
|
|
1178
|
+
/>
|
|
1179
|
+
</ActionBar>
|
|
1180
|
+
{showEnvelope ? (
|
|
1181
|
+
<CodeBlock
|
|
1182
|
+
language="bash"
|
|
1183
|
+
code={agentIdentity.envelopeText}
|
|
1184
|
+
copyable
|
|
1185
|
+
/>
|
|
1186
|
+
) : null}
|
|
1187
|
+
</>
|
|
1188
|
+
) : null}
|
|
1189
|
+
</>
|
|
1190
|
+
)}
|
|
1191
|
+
</Card>
|
|
1192
|
+
<AboutSection
|
|
1193
|
+
name="Paseo Helper Demo"
|
|
1194
|
+
description="Interactive design system showcase and daemon runtime verification suite for paseo-plugin-helper."
|
|
1195
|
+
version={data?.version ?? PLUGIN_VERSION}
|
|
1196
|
+
author="xpufx"
|
|
1197
|
+
repository="https://github.com/xpufx/paseo-helper-demo"
|
|
1198
|
+
issues="https://github.com/xpufx/paseo-helper-demo/issues"
|
|
1199
|
+
license="MIT"
|
|
1200
|
+
extraItems={[
|
|
1201
|
+
{ label: "Daemon Verified Port", value: `${data?.daemonPort ?? 4280}`, copyable: true },
|
|
1202
|
+
{ label: "Host Platform", value: `${data?.platform ?? "unknown"}`, copyable: true },
|
|
1203
|
+
{ label: "Background Uptime", value: `${Math.round(data?.uptimeSeconds ?? 0)}s` },
|
|
1204
|
+
]}
|
|
1205
|
+
/>
|
|
1206
|
+
</>
|
|
1207
|
+
)}
|
|
1208
|
+
|
|
1209
|
+
{/* Footer */}
|
|
1210
|
+
<ActionBar align="flex-end">
|
|
1211
|
+
<Button
|
|
1212
|
+
label="Close"
|
|
1213
|
+
variant="ghost"
|
|
1214
|
+
onPress={() => {
|
|
1215
|
+
triggerHaptic("light");
|
|
1216
|
+
close();
|
|
1217
|
+
}}
|
|
1218
|
+
/>
|
|
1219
|
+
</ActionBar>
|
|
1220
|
+
|
|
1221
|
+
<View style={{ alignItems: "center", paddingVertical: 8 }}>
|
|
1222
|
+
<Text style={{ color: colors.foregroundMuted, ...typography.caption, fontFamily: "monospace" }}>
|
|
1223
|
+
helper-demo v{data?.version ?? PLUGIN_VERSION} (tick #{data?.backgroundTicks ?? 0})
|
|
1224
|
+
</Text>
|
|
1225
|
+
</View>
|
|
1226
|
+
</HostModalSection>
|
|
1227
|
+
</PluginThemeProvider>
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
export function contributeClient(client: ComposerPillRegistrar) {
|
|
1232
|
+
const removePill = registerComposerPill(client, {
|
|
1233
|
+
id: "helper-demo",
|
|
1234
|
+
title: "demo",
|
|
1235
|
+
modalTitle: "Showcase Demo",
|
|
1236
|
+
modalIcon: "Sliders",
|
|
1237
|
+
hostScroll: true,
|
|
1238
|
+
flair: {
|
|
1239
|
+
radius: "rounded",
|
|
1240
|
+
density: "comfortable",
|
|
1241
|
+
accentColor: "#6366f1",
|
|
1242
|
+
},
|
|
1243
|
+
renderPill: (props) => <DemoPill {...props} />,
|
|
1244
|
+
renderModal: (props) => <DemoModal {...props} />,
|
|
1245
|
+
});
|
|
1246
|
+
return () => {
|
|
1247
|
+
removePill();
|
|
1248
|
+
};
|
|
1249
|
+
}
|