@xpufx/paseo-top 0.4.4 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/OVERVIEW.md +18 -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 +692 -616
  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/npm-shrinkwrap.json +44 -0
  49. package/package.json +8 -4
  50. package/paseo-plugin.json +2 -0
  51. package/screenshots/1-paseo-top-modal-system.jpg +0 -0
  52. package/screenshots/2-paseo-top-modal-workspace.jpg +0 -0
  53. package/screenshots/3-paseo-top-modal-settings.jpg +0 -0
  54. package/screenshots/4-paseo-top-modal-about.jpg +0 -0
  55. package/screenshots/paseo-top-dashboard.jpg +0 -0
  56. package/screenshots/paseo-top-general-view.jpg +0 -0
  57. package/server/resources.ts +98 -0
  58. package/server/vendor/paseo-plugin-helper/custom-pills.ts +70 -0
  59. package/server/vendor/paseo-plugin-helper/index.ts +2 -0
  60. package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
  61. package/server/vendor/paseo-plugin-helper/rpc-guard.ts +2 -2
  62. package/server/vendor/paseo-plugin-helper/storage-guard.ts +294 -0
  63. package/server/vendor/paseo-plugin-helper/storage.ts +36 -2
  64. package/server/vendor/paseo-plugin-helper/telemetry.ts +486 -0
  65. package/server/vendor/paseo-plugin-helper/tickets.ts +18 -67
  66. package/server/vendor/permission-audit/index.ts +1 -0
  67. package/server/vendor/permission-audit/server.ts +839 -0
  68. package/shared/helper-version.ts +1 -1
  69. package/shared/multi-host.ts +153 -159
  70. package/shared/resources.ts +145 -0
  71. package/shared/vendor/paseo-plugin-helper/async.ts +14 -7
  72. package/shared/vendor/paseo-plugin-helper/features.ts +244 -0
  73. package/shared/vendor/paseo-plugin-helper/forge.ts +27 -0
  74. package/shared/vendor/paseo-plugin-helper/index.ts +1 -0
  75. package/shared/vendor/paseo-plugin-helper/tickets.ts +122 -28
  76. package/shared/vendor/permission-audit/filter.ts +61 -0
  77. package/shared/vendor/permission-audit/index.ts +2 -0
  78. package/shared/vendor/permission-audit/shared.ts +119 -0
  79. package/shared/version.ts +1 -1
  80. package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +0 -493
  81. package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +0 -250
  82. package/client/vendor/paseo-plugin-helper/components/Badge.tsx +0 -168
  83. package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -204
  84. package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -257
  85. package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +0 -196
  86. package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +0 -277
  87. package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +0 -172
  88. package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +0 -180
  89. package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +0 -97
  90. package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +0 -70
  91. package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +0 -88
  92. package/client/vendor/paseo-plugin-helper/components/InteractiveRow.tsx +0 -142
  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 -132
  98. package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -87
  99. package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -283
  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 -26
  106. package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -225
  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 -30
  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 -368
  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/theme/flair.ts +0 -76
  121. package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +0 -121
  122. package/client/vendor/paseo-plugin-helper/theme/index.ts +0 -7
  123. package/client/vendor/paseo-plugin-helper/theme/provider.tsx +0 -178
  124. package/client/vendor/paseo-plugin-helper/theme/responsive.ts +0 -213
  125. package/client/vendor/paseo-plugin-helper/theme/tokens.ts +0 -200
  126. package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +0 -57
  127. package/client/vendor/paseo-plugin-helper/tickets.tsx +0 -766
  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,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
+ }