@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.
Files changed (129) 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 +94 -1
  6. package/client/pill.tsx +690 -614
  7. package/client/resources-query.ts +1 -1
  8. package/client/settings-events.ts +1 -1
  9. package/client/settings-ui.tsx +86 -9
  10. package/client/surface.tsx +308 -276
  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} +9 -3
  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} +51 -18
  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/{surface.tsx → lifecycle/surface.tsx} +6 -6
  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} +50 -58
  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 -234
  48. package/package.json +7 -4
  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 +2 -0
  59. package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
  60. package/server/vendor/paseo-plugin-helper/rpc-guard.ts +2 -2
  61. package/server/vendor/paseo-plugin-helper/storage-guard.ts +294 -0
  62. package/server/vendor/paseo-plugin-helper/storage.ts +36 -2
  63. package/server/vendor/paseo-plugin-helper/telemetry.ts +486 -0
  64. package/server/vendor/paseo-plugin-helper/tickets.ts +18 -67
  65. package/server/vendor/permission-audit/index.ts +1 -0
  66. package/server/vendor/permission-audit/server.ts +839 -0
  67. package/shared/helper-version.ts +1 -1
  68. package/shared/multi-host.ts +153 -159
  69. package/shared/resources.ts +145 -0
  70. package/shared/vendor/paseo-plugin-helper/async.ts +14 -7
  71. package/shared/vendor/paseo-plugin-helper/features.ts +244 -0
  72. package/shared/vendor/paseo-plugin-helper/forge.ts +27 -0
  73. package/shared/vendor/paseo-plugin-helper/index.ts +1 -0
  74. package/shared/vendor/paseo-plugin-helper/tickets.ts +122 -28
  75. package/shared/vendor/permission-audit/filter.ts +61 -0
  76. package/shared/vendor/permission-audit/index.ts +2 -0
  77. package/shared/vendor/permission-audit/shared.ts +119 -0
  78. package/shared/version.ts +1 -1
  79. package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +0 -493
  80. package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +0 -250
  81. package/client/vendor/paseo-plugin-helper/components/Badge.tsx +0 -168
  82. package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -204
  83. package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -257
  84. package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +0 -196
  85. package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +0 -277
  86. package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +0 -172
  87. package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +0 -180
  88. package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +0 -97
  89. package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +0 -70
  90. package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +0 -88
  91. package/client/vendor/paseo-plugin-helper/components/InteractiveRow.tsx +0 -142
  92. package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +0 -446
  93. package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +0 -247
  94. package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +0 -117
  95. package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +0 -53
  96. package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +0 -132
  97. package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -87
  98. package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -283
  99. package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +0 -80
  100. package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +0 -319
  101. package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +0 -150
  102. package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +0 -163
  103. package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +0 -157
  104. package/client/vendor/paseo-plugin-helper/components/index.ts +0 -26
  105. package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -225
  106. package/client/vendor/paseo-plugin-helper/forge-icon.tsx +0 -79
  107. package/client/vendor/paseo-plugin-helper/icon.tsx +0 -39
  108. package/client/vendor/paseo-plugin-helper/index.ts +0 -30
  109. package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +0 -49
  110. package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +0 -103
  111. package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +0 -65
  112. package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +0 -368
  113. package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +0 -51
  114. package/client/vendor/paseo-plugin-helper/layout/Row.tsx +0 -39
  115. package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +0 -39
  116. package/client/vendor/paseo-plugin-helper/layout/index.ts +0 -7
  117. package/client/vendor/paseo-plugin-helper/styles/index.ts +0 -1
  118. package/client/vendor/paseo-plugin-helper/styles/recipes.ts +0 -550
  119. package/client/vendor/paseo-plugin-helper/theme/flair.ts +0 -76
  120. package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +0 -121
  121. package/client/vendor/paseo-plugin-helper/theme/index.ts +0 -7
  122. package/client/vendor/paseo-plugin-helper/theme/provider.tsx +0 -178
  123. package/client/vendor/paseo-plugin-helper/theme/responsive.ts +0 -213
  124. package/client/vendor/paseo-plugin-helper/theme/tokens.ts +0 -200
  125. package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +0 -57
  126. package/client/vendor/paseo-plugin-helper/tickets.tsx +0 -766
  127. /package/client/vendor/paseo-plugin-helper/{command-center.ts → core/command-center.ts} +0 -0
  128. /package/client/vendor/paseo-plugin-helper/{snapshot.ts → core/snapshot.ts} +0 -0
  129. /package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/haptics.ts +0 -0
@@ -1,8 +1,17 @@
1
1
  import React, { type ReactNode } from "react";
2
2
  import { StyleSheet, Text, View, type StyleProp, type ViewStyle } from "react-native";
3
- import { usePluginTheme } from "../theme/provider";
3
+ import { useHostTheme, useHostLayout } from "./theme";
4
+ import { spacing } from "./layout";
4
5
 
5
- export interface DataColumn<T> {
6
+ /**
7
+ * Paseo Plugin Helper — UI data adapters (`paseo-plugin-helper/ui`).
8
+ *
9
+ * Thin, host-delegating data adapters
10
+ * (DataTable). Same contract: host theme colors only, no scroll ownership, no
11
+ * design-system machinery.
12
+ */
13
+
14
+ export interface HostDataColumn<T> {
6
15
  key: string;
7
16
  header: string;
8
17
  flex?: number;
@@ -11,33 +20,33 @@ export interface DataColumn<T> {
11
20
  render: (item: T) => ReactNode;
12
21
  }
13
22
 
14
- export interface DataTableProps<T> {
23
+ export interface HostDataTableProps<T> {
15
24
  data: T[];
16
- columns: DataColumn<T>[];
25
+ columns: HostDataColumn<T>[];
17
26
  keyExtractor: (item: T, index: number) => string;
18
27
  emptyState?: ReactNode;
19
28
  style?: StyleProp<ViewStyle>;
20
29
  }
21
30
 
22
31
  /**
23
- * Responsive data table that automatically reflows between a traditional table
24
- * on desktop and structured card list on mobile / compact viewports.
32
+ * Responsive data table that reflows between a traditional table on wide
33
+ * surfaces and a structured card list on compact/mobile layouts.
25
34
  */
26
- export function DataTable<T>({
35
+ export function HostDataTable<T>({
27
36
  data,
28
37
  columns,
29
38
  keyExtractor,
30
39
  emptyState,
31
40
  style,
32
- }: DataTableProps<T>) {
33
- const { colors, resolveRadius, isCompact } = usePluginTheme();
34
- const radius = resolveRadius("sm");
41
+ }: HostDataTableProps<T>) {
42
+ const { colors } = useHostTheme();
43
+ const layout = useHostLayout();
44
+ const isCompact = layout.compact || layout.platform === "ios" || layout.platform === "android";
35
45
 
36
46
  if (!data || data.length === 0) {
37
47
  return emptyState ? <View style={style}>{emptyState}</View> : null;
38
48
  }
39
49
 
40
- // Mobile / compact card-list layout
41
50
  if (isCompact) {
42
51
  return (
43
52
  <View style={[styles.compactContainer, style]}>
@@ -49,7 +58,6 @@ export function DataTable<T>({
49
58
  {
50
59
  backgroundColor: colors.surface1,
51
60
  borderColor: colors.border,
52
- borderRadius: radius,
53
61
  },
54
62
  ]}
55
63
  >
@@ -67,20 +75,17 @@ export function DataTable<T>({
67
75
  );
68
76
  }
69
77
 
70
- // Desktop tabular layout
71
78
  return (
72
79
  <View
73
80
  style={[
74
81
  styles.table,
75
82
  {
76
83
  borderColor: colors.border,
77
- borderRadius: radius,
78
84
  backgroundColor: colors.surface0,
79
85
  },
80
86
  style,
81
87
  ]}
82
88
  >
83
- {/* Header Row */}
84
89
  <View
85
90
  style={[
86
91
  styles.headerRow,
@@ -100,8 +105,8 @@ export function DataTable<T>({
100
105
  col.align === "right"
101
106
  ? styles.alignRight
102
107
  : col.align === "center"
103
- ? styles.alignCenter
104
- : styles.alignLeft,
108
+ ? styles.alignCenter
109
+ : styles.alignLeft,
105
110
  ]}
106
111
  >
107
112
  <Text
@@ -115,7 +120,6 @@ export function DataTable<T>({
115
120
  ))}
116
121
  </View>
117
122
 
118
- {/* Rows */}
119
123
  {data.map((item, idx) => (
120
124
  <View
121
125
  key={keyExtractor(item, idx)}
@@ -134,8 +138,8 @@ export function DataTable<T>({
134
138
  col.align === "right"
135
139
  ? styles.alignRight
136
140
  : col.align === "center"
137
- ? styles.alignCenter
138
- : styles.alignLeft,
141
+ ? styles.alignCenter
142
+ : styles.alignLeft,
139
143
  ]}
140
144
  >
141
145
  {col.render(item)}
@@ -148,29 +152,43 @@ export function DataTable<T>({
148
152
  }
149
153
 
150
154
  const styles = StyleSheet.create({
155
+ compactContainer: {
156
+ gap: spacing.sm,
157
+ },
158
+ compactCard: {
159
+ borderWidth: 1,
160
+ borderRadius: 10,
161
+ padding: spacing.md,
162
+ gap: spacing.xs,
163
+ },
164
+ compactRow: {
165
+ flexDirection: "row",
166
+ justifyContent: "space-between",
167
+ gap: spacing.sm,
168
+ },
169
+ compactHeader: {
170
+ fontSize: 11,
171
+ flexShrink: 1,
172
+ },
173
+ compactValue: {
174
+ flexShrink: 1,
175
+ minWidth: 0,
176
+ },
151
177
  table: {
152
178
  borderWidth: 1,
179
+ borderRadius: 10,
153
180
  overflow: "hidden",
154
181
  },
155
182
  headerRow: {
156
183
  flexDirection: "row",
157
184
  borderBottomWidth: 1,
158
- paddingVertical: 8,
159
- paddingHorizontal: 12,
160
185
  },
161
186
  row: {
162
187
  flexDirection: "row",
163
- paddingVertical: 10,
164
- paddingHorizontal: 12,
165
- alignItems: "center",
166
188
  },
167
189
  cell: {
168
- justifyContent: "center",
169
- // Yoga defaults `flexShrink` to 0, so without this a cell whose content
170
- // carries a long string (a title, a path) refuses to give up width and
171
- // squeezes its sibling columns instead of truncating.
172
- flexShrink: 1,
173
- maxWidth: "100%",
190
+ paddingHorizontal: spacing.md,
191
+ paddingVertical: spacing.sm,
174
192
  },
175
193
  alignLeft: {
176
194
  alignItems: "flex-start",
@@ -184,33 +202,7 @@ const styles = StyleSheet.create({
184
202
  headerText: {
185
203
  fontSize: 11,
186
204
  fontWeight: "600",
205
+ letterSpacing: 0.3,
187
206
  textTransform: "uppercase",
188
- letterSpacing: 0.5,
189
- flexShrink: 1,
190
- },
191
- compactContainer: {
192
- gap: 8,
193
- },
194
- compactCard: {
195
- borderWidth: 1,
196
- padding: 12,
197
- gap: 6,
198
- },
199
- compactRow: {
200
- flexDirection: "row",
201
- justifyContent: "space-between",
202
- alignItems: "center",
203
- gap: 8,
204
- },
205
- compactHeader: {
206
- fontSize: 12,
207
- flexShrink: 1,
208
- },
209
- compactValue: {
210
- alignItems: "flex-end",
211
- // The value side carries the actual data, so it takes the compressible
212
- // slot and the fixed label beside it keeps its intrinsic width.
213
- flexShrink: 1,
214
- maxWidth: "100%",
215
207
  },
216
208
  });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Paseo Plugin Helper — UI Kit / Adapters (`paseo-plugin-helper/ui`).
3
+ *
4
+ * Thin, composable adapters strictly conforming to upstream native
5
+ * primitives. Rules for everything in this entry:
6
+ *
7
+ * - Scroll ownership stays with the host (`Modal.Content` default
8
+ * `scrollable`; explicit host `ScrollView` only where the host supplies
9
+ * no scroller). Never force `scrollable={false}` plus a helper-owned
10
+ * replacement scroller. No scroll-ownership state machine.
11
+ * - No artificial width caps: no `maxContentWidth`, no `size="large"` with
12
+ * hardcoded `minWidth`. Fill the host-allocated frame fluidly.
13
+ * - No DOM CSS variable scrapers: colors arrive via the host `theme` prop
14
+ * through `HostThemeProvider` — never from `getComputedStyle`.
15
+ * - Upstream settings primitives come from the host bundle
16
+ * (`@getpaseo/plugin/client/ui`), injected by the plugin — never
17
+ * re-implemented here.
18
+ *
19
+ * This is the only UI the package ships: the legacy bespoke kit was removed
20
+ * in #938, and the CLI audit flags new `maxContentWidth` / `scrollable={false}`
21
+ * uses with pointers here.
22
+ *
23
+ * Module map:
24
+ * - `ui/theme.tsx` — `HostThemeProvider` / `useHostTheme` (host colors only)
25
+ * - `ui/color.ts` — `alpha()` and status color helpers (pure)
26
+ * - `ui/modal.tsx` — `HostModalContent` / `HostScroll` / `HostModalSection`
27
+ * - `ui/layout.tsx` — `HostStack` / `HostRow` / `HostGrid` / `HostActionBar` / `HostFormRow`
28
+ * - `ui/content.tsx` — `HostCard` / `HostTabs` / `HostBadge` / `HostKeyValue` / …
29
+ * - `ui/controls.tsx` — `HostButton` / `HostToggle` / `HostSelect` / `HostTextInput` / …
30
+ * - `ui/text.tsx` — `HostCodeBlock` / `HostCommandBox` / `HostTruncatedText` / …
31
+ * - `ui/data.tsx` — `HostDataTable`
32
+ * - `ui/settings.ts` — settings screen adapters over the host ui bundle
33
+ */
34
+
35
+ export * from "./theme";
36
+ export * from "./color";
37
+ export * from "./modal";
38
+ export * from "./layout";
39
+ export * from "./content";
40
+ export * from "./controls";
41
+ export * from "./text";
42
+ export * from "./data";
43
+ export * from "./settings";
44
+ export { PluginReloadBanner } from "./plugin-reload-banner";
@@ -0,0 +1,263 @@
1
+ import React, { type ReactNode } from "react";
2
+ import { StyleSheet, Text, View, type StyleProp, type ViewStyle } from "react-native";
3
+ import { useHostTheme } from "./theme";
4
+
5
+ /** Static spacing scale (px). The host owns the design language; this is just layout. */
6
+ export const spacing = {
7
+ xs: 4,
8
+ sm: 8,
9
+ md: 12,
10
+ lg: 16,
11
+ xl: 24,
12
+ } as const;
13
+
14
+ export type SpacingValue = keyof typeof spacing | number;
15
+
16
+ export function resolveSpacing(value: SpacingValue | undefined, fallback: number): number {
17
+ if (value === undefined) return fallback;
18
+ if (typeof value === "number") return value;
19
+ return spacing[value] ?? fallback;
20
+ }
21
+
22
+ export interface HostRowProps {
23
+ children: ReactNode;
24
+ gap?: number;
25
+ align?: "start" | "center" | "end" | "stretch";
26
+ justify?: "start" | "center" | "end" | "between";
27
+ wrap?: boolean;
28
+ style?: StyleProp<ViewStyle>;
29
+ }
30
+
31
+ /**
32
+ * Host-delegating horizontal row for `paseo-plugin-helper/ui`.
33
+ *
34
+ * Colors come from `useHostTheme()` (provided by `HostThemeProvider`) — no
35
+ * theme scraping.
36
+ */
37
+ export function HostRow({
38
+ children,
39
+ gap = 8,
40
+ align = "center",
41
+ justify = "start",
42
+ wrap = false,
43
+ style,
44
+ }: HostRowProps) {
45
+ return (
46
+ <View
47
+ style={[
48
+ styles.row,
49
+ {
50
+ gap,
51
+ alignItems: alignMap[align],
52
+ justifyContent: justifyMap[justify],
53
+ flexWrap: wrap ? "wrap" : "nowrap",
54
+ },
55
+ style,
56
+ ]}
57
+ >
58
+ {children}
59
+ </View>
60
+ );
61
+ }
62
+
63
+ export interface HostStackProps {
64
+ children: ReactNode;
65
+ gap?: number;
66
+ align?: "start" | "center" | "end" | "stretch";
67
+ justify?: "start" | "center" | "end" | "between";
68
+ grow?: boolean;
69
+ style?: StyleProp<ViewStyle>;
70
+ }
71
+
72
+ /**
73
+ * Host-delegating vertical stack for `paseo-plugin-helper/ui`.
74
+ *
75
+ * Colors come from `useHostTheme()` (provided by `HostThemeProvider`) — no
76
+ * theme scraping.
77
+ */
78
+ export function HostStack({
79
+ children,
80
+ gap = 8,
81
+ align = "stretch",
82
+ justify = "start",
83
+ grow = false,
84
+ style,
85
+ }: HostStackProps) {
86
+ return (
87
+ <View
88
+ style={[
89
+ styles.stack,
90
+ {
91
+ gap,
92
+ alignItems: alignMap[align],
93
+ justifyContent: justifyMap[justify],
94
+ ...(grow ? { flex: 1, minHeight: 0 } : null),
95
+ },
96
+ style,
97
+ ]}
98
+ >
99
+ {children}
100
+ </View>
101
+ );
102
+ }
103
+
104
+ export interface HostGridProps {
105
+ children: ReactNode;
106
+ columns?: number;
107
+ gap?: number;
108
+ style?: StyleProp<ViewStyle>;
109
+ }
110
+
111
+ /**
112
+ * Host-delegating wrapping grid for `paseo-plugin-helper/ui`.
113
+ *
114
+ * Colors come from `useHostTheme()` (provided by `HostThemeProvider`) — no
115
+ * theme scraping.
116
+ */
117
+ export function HostGrid({
118
+ children,
119
+ columns = 2,
120
+ gap = 8,
121
+ style,
122
+ }: HostGridProps) {
123
+ return (
124
+ <View style={[styles.grid, { gap }, style]}>
125
+ {React.Children.map(children, (child) => (
126
+ <View style={{ flexBasis: `${100 / columns}%`, flexGrow: 0, flexShrink: 1, minWidth: 0 }}>
127
+ {child}
128
+ </View>
129
+ ))}
130
+ </View>
131
+ );
132
+ }
133
+
134
+ export interface HostActionBarProps {
135
+ children: ReactNode;
136
+ style?: StyleProp<ViewStyle>;
137
+ }
138
+
139
+ /**
140
+ * Host-delegating action bar for `paseo-plugin-helper/ui`.
141
+ *
142
+ * Colors come from `useHostTheme()` (provided by `HostThemeProvider`) — no
143
+ * theme scraping.
144
+ */
145
+ export function HostActionBar({
146
+ children,
147
+ style,
148
+ }: HostActionBarProps) {
149
+ return (
150
+ <View style={[styles.actionBar, style]}>
151
+ {children}
152
+ </View>
153
+ );
154
+ }
155
+
156
+ export interface HostFormRowProps {
157
+ label: string;
158
+ description?: string;
159
+ children: ReactNode;
160
+ layout?: "stacked" | "inline";
161
+ style?: StyleProp<ViewStyle>;
162
+ }
163
+
164
+ /**
165
+ * Host-delegating form row for `paseo-plugin-helper/ui`.
166
+ *
167
+ * Colors come from `useHostTheme()` (provided by `HostThemeProvider`) — no
168
+ * theme scraping.
169
+ */
170
+ export function HostFormRow({
171
+ label,
172
+ description,
173
+ children,
174
+ layout = "stacked",
175
+ style,
176
+ }: HostFormRowProps) {
177
+ const { colors } = useHostTheme();
178
+
179
+ return (
180
+ <View
181
+ style={[
182
+ styles.formRow,
183
+ layout === "inline" ? styles.formRowInline : styles.formRowStacked,
184
+ style,
185
+ ]}
186
+ >
187
+ <View style={styles.formLabel}>
188
+ <Text style={[styles.formLabelText, { color: colors.foreground }]}>{label}</Text>
189
+ {description ? (
190
+ <Text style={[styles.formDescription, { color: colors.foregroundMuted }]}>
191
+ {description}
192
+ </Text>
193
+ ) : null}
194
+ </View>
195
+ <View style={styles.formControl}>{children}</View>
196
+ </View>
197
+ );
198
+ }
199
+
200
+ const alignMap = {
201
+ start: "flex-start",
202
+ center: "center",
203
+ end: "flex-end",
204
+ stretch: "stretch",
205
+ } as const;
206
+
207
+ const justifyMap = {
208
+ start: "flex-start",
209
+ center: "center",
210
+ end: "flex-end",
211
+ between: "space-between",
212
+ } as const;
213
+
214
+ const styles = StyleSheet.create({
215
+ row: {
216
+ flexDirection: "row",
217
+ width: "100%",
218
+ },
219
+ stack: {
220
+ flexDirection: "column",
221
+ width: "100%",
222
+ },
223
+ grid: {
224
+ flexDirection: "row",
225
+ flexWrap: "wrap",
226
+ width: "100%",
227
+ },
228
+ actionBar: {
229
+ flexDirection: "row",
230
+ alignItems: "center",
231
+ justifyContent: "flex-end",
232
+ gap: 8,
233
+ width: "100%",
234
+ flexWrap: "wrap",
235
+ },
236
+ formRow: {
237
+ gap: 4,
238
+ width: "100%",
239
+ },
240
+ formRowStacked: {
241
+ flexDirection: "column",
242
+ },
243
+ formRowInline: {
244
+ flexDirection: "row",
245
+ alignItems: "center",
246
+ justifyContent: "space-between",
247
+ },
248
+ formLabel: {
249
+ gap: 2,
250
+ flexShrink: 1,
251
+ },
252
+ formLabelText: {
253
+ fontSize: 13,
254
+ fontWeight: "600",
255
+ },
256
+ formDescription: {
257
+ fontSize: 11,
258
+ },
259
+ formControl: {
260
+ flexShrink: 1,
261
+ minWidth: 0,
262
+ },
263
+ });
@@ -0,0 +1,111 @@
1
+ import React, { type ReactNode } from "react";
2
+ import {
3
+ ScrollView as FallbackScrollView,
4
+ StyleSheet,
5
+ View,
6
+ type ScrollViewProps,
7
+ type StyleProp,
8
+ type ViewStyle,
9
+ } from "react-native";
10
+ import {
11
+ getClientHost,
12
+ getOptionalClientHost,
13
+ selectHostScrollView,
14
+ type HostScrollView,
15
+ } from "../core/host";
16
+
17
+ /**
18
+ * Host-delegating modal content for `paseo-plugin-helper/ui`.
19
+ *
20
+ * Contract — ONLY for content rendered directly inside your OWN host
21
+ * `<Modal>` (e.g. x-comms style own-modal surfaces):
22
+ * - Scroll ownership stays with the host: renders the injected host
23
+ * `<Modal.Content>` with its default `scrollable` behavior and adds NO
24
+ * helper-owned scroller, so sheet gestures, keyboard avoidance, and
25
+ * safe-area clearance keep working on compact/mobile and desktop alike.
26
+ * - No width opinions: there is deliberately NO `maxContentWidth` /
27
+ * `size="large"` prop. The host allocates the dialog frame; this wrapper
28
+ * fills it fluidly (`width: "100%"`).
29
+ * - No theme scraping: colors come from the host `theme` prop via
30
+ * `PluginThemeProvider`, never from DOM CSS variables.
31
+ * - NEVER render this inside `registerComposerPill` `renderModal`: the pill
32
+ * host already provides the one `<Modal.Content>` (or no modal at all on
33
+ * 0.8 popovers), so a nested `<Modal.Content>` violates the host contract
34
+ * and crashes the plugin. Use `HostModalSection` there instead.
35
+ */
36
+ export function HostModalContent({
37
+ children,
38
+ style,
39
+ contentContainerStyle,
40
+ }: {
41
+ children: ReactNode;
42
+ style?: StyleProp<ViewStyle>;
43
+ contentContainerStyle?: StyleProp<ViewStyle>;
44
+ }) {
45
+ const { Modal } = getClientHost();
46
+ return (
47
+ <Modal.Content>
48
+ <View style={[styles.fluid, style, contentContainerStyle]}>{children}</View>
49
+ </Modal.Content>
50
+ );
51
+ }
52
+
53
+ /**
54
+ * Explicit single scroll owner for surfaces where the host supplies NO
55
+ * scroller (sidebar surfaces, settings screens). Prefers the injected host
56
+ * ScrollView (sheet-gesture integrated on Paseo v0.8) and falls back to plain
57
+ * React Native ScrollView. Callers own the decision to scroll; this component
58
+ * never nests itself inside another scroller.
59
+ */
60
+ export function HostScroll({
61
+ children,
62
+ style,
63
+ contentContainerStyle,
64
+ ...props
65
+ }: {
66
+ children: ReactNode;
67
+ style?: StyleProp<ViewStyle>;
68
+ contentContainerStyle?: StyleProp<ViewStyle>;
69
+ } & ScrollViewProps) {
70
+ const ResolvedScrollView = selectHostScrollView(
71
+ getOptionalClientHost(),
72
+ FallbackScrollView as unknown as HostScrollView,
73
+ );
74
+ return (
75
+ <ResolvedScrollView
76
+ {...(props as ScrollViewProps)}
77
+ style={[styles.fluid, style]}
78
+ contentContainerStyle={contentContainerStyle}
79
+ >
80
+ {children}
81
+ </ResolvedScrollView>
82
+ );
83
+ }
84
+
85
+ const styles = StyleSheet.create({
86
+ fluid: {
87
+ width: "100%",
88
+ },
89
+ });
90
+
91
+ /**
92
+ * Fluid inner content for pill-embedded modals (`registerComposerPill`
93
+ * `renderModal`).
94
+ *
95
+ * The pill host already provides the one `<Modal.Content>` (legacy/centered
96
+ * modal paths) or no modal at all (0.8 popover path, where the host owns the
97
+ * outer scroll). This renders a plain fluid `<View>` — no `<Modal.Content>`,
98
+ * no scroller, no width caps — so exactly one `<Modal.Content>` exists and
99
+ * nothing nests. Pair with `hostScroll: true` on the pill registration so
100
+ * the wrapper leaves scrolling to the host; without it the wrapper bounds
101
+ * the dialog (`scrollable={false}`) for legacy `ModalBody` content.
102
+ */
103
+ export function HostModalSection({
104
+ children,
105
+ style,
106
+ }: {
107
+ children: ReactNode;
108
+ style?: StyleProp<ViewStyle>;
109
+ }) {
110
+ return <View style={[styles.fluid, style]}>{children}</View>;
111
+ }