@vvedantb/native-ui 0.17.2 → 0.18.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.
Files changed (220) hide show
  1. package/dist/AlertDialog.jsx +123 -0
  2. package/dist/AmbientBackground.jsx +73 -0
  3. package/dist/ArtifactList.jsx +59 -0
  4. package/dist/Avatar.jsx +285 -0
  5. package/dist/Badge.jsx +154 -0
  6. package/dist/BottomSheetContainer.jsx +208 -0
  7. package/dist/BulkActionBar.jsx +717 -0
  8. package/dist/Button.jsx +344 -0
  9. package/dist/CaptureButton.jsx +104 -0
  10. package/dist/Card.jsx +371 -0
  11. package/dist/Checkbox.jsx +177 -0
  12. package/dist/Chip.jsx +79 -0
  13. package/dist/ColorSwatches.jsx +159 -0
  14. package/dist/ConfirmDialog.jsx +157 -0
  15. package/dist/DateTimeField.jsx +204 -0
  16. package/dist/Dialog.jsx +614 -0
  17. package/dist/DividedList.jsx +120 -0
  18. package/dist/DropdownMenu.jsx +849 -0
  19. package/dist/EmptyState.jsx +434 -0
  20. package/dist/ExpoWebRoot.jsx +134 -0
  21. package/dist/FloatingFAB.jsx +95 -0
  22. package/dist/FloatingSurface.jsx +226 -0
  23. package/dist/FloatingTabBar.jsx +403 -0
  24. package/dist/HeaderIconButton.jsx +53 -0
  25. package/dist/IconButton.jsx +157 -0
  26. package/dist/Input.jsx +96 -0
  27. package/dist/Label.jsx +96 -0
  28. package/dist/LabeledSwitchRow.jsx +170 -0
  29. package/dist/LiquidGlass.jsx +2 -0
  30. package/dist/ListRow.jsx +281 -0
  31. package/dist/LoadingState.jsx +205 -0
  32. package/dist/PageIndicator.jsx +128 -0
  33. package/dist/PageTitle.jsx +205 -0
  34. package/dist/Pager.jsx +105 -0
  35. package/dist/PasswordInput.jsx +120 -0
  36. package/dist/PillTabs.jsx +300 -0
  37. package/dist/PinnedBotTile.jsx +212 -0
  38. package/dist/Progress.jsx +147 -0
  39. package/dist/SafeAreaContainer.jsx +38 -0
  40. package/dist/ScreenContainer.jsx +117 -0
  41. package/dist/SearchInput.jsx +150 -0
  42. package/dist/SectionLabel.jsx +128 -0
  43. package/dist/Select.jsx +223 -0
  44. package/dist/Separator.jsx +95 -0
  45. package/dist/Sheet.jsx +343 -0
  46. package/dist/SheetRow.jsx +198 -0
  47. package/dist/Skeleton.jsx +311 -0
  48. package/dist/Spinner.jsx +195 -0
  49. package/dist/StackPageHeader.jsx +160 -0
  50. package/dist/StatGrid.jsx +74 -0
  51. package/dist/StatusDot.jsx +129 -0
  52. package/dist/SurfaceRow.jsx +143 -0
  53. package/dist/SwipeableRow.jsx +50 -0
  54. package/dist/Switch.jsx +130 -0
  55. package/dist/Tabs.jsx +385 -0
  56. package/dist/Textarea.jsx +103 -0
  57. package/dist/ThemeToggle.jsx +78 -0
  58. package/dist/Toast.jsx +396 -0
  59. package/dist/ai/activity-step-detail.jsx +179 -0
  60. package/dist/ai/activity-step-label.js +816 -0
  61. package/dist/ai/activity-step-types.js +14 -0
  62. package/dist/ai/activity-step.jsx +98 -0
  63. package/dist/ai/activity-tasks-utils.js +172 -0
  64. package/dist/ai/activity-timeline.js +47 -0
  65. package/dist/ai/attachments.jsx +258 -0
  66. package/dist/ai/card-button.jsx +188 -0
  67. package/dist/ai/chain-of-thought.jsx +459 -0
  68. package/dist/ai/chat-jump-rail.jsx +57 -0
  69. package/dist/ai/chat-text.js +44 -0
  70. package/dist/ai/chat-thread.jsx +135 -0
  71. package/dist/ai/choice-question.jsx +925 -0
  72. package/dist/ai/code-block.jsx +574 -0
  73. package/dist/ai/collapsible.jsx +275 -0
  74. package/dist/ai/conversation.jsx +723 -0
  75. package/dist/ai/file-chip.jsx +198 -0
  76. package/dist/ai/icon-button.jsx +179 -0
  77. package/dist/ai/index.js +37 -0
  78. package/dist/ai/message-action-sheet.jsx +408 -0
  79. package/dist/ai/message-quote.jsx +241 -0
  80. package/dist/ai/message.jsx +739 -0
  81. package/dist/ai/prompt-input-menu.jsx +365 -0
  82. package/dist/ai/prompt-input.jsx +626 -0
  83. package/dist/ai/reasoning.jsx +446 -0
  84. package/dist/ai/reply-quote-chip.jsx +278 -0
  85. package/dist/ai/response-text.jsx +130 -0
  86. package/dist/ai/shimmer.jsx +149 -0
  87. package/dist/ai/sources.jsx +260 -0
  88. package/dist/ai/subagent-cards.jsx +645 -0
  89. package/dist/ai/suggestion.jsx +186 -0
  90. package/dist/ai/tool-approval-card.jsx +359 -0
  91. package/dist/ai/trace.jsx +728 -0
  92. package/dist/ai/unified-diff.jsx +280 -0
  93. package/dist/ai/use-streaming-disclosure.js +70 -0
  94. package/dist/auth/AuthProviders.jsx +27 -0
  95. package/dist/auth/AuthScreen.jsx +292 -0
  96. package/dist/auth/GoogleSignInButton.jsx +188 -0
  97. package/dist/auth/SecondFactorScreen.jsx +133 -0
  98. package/dist/auth/SignInScreen.jsx +482 -0
  99. package/dist/auth/authErrors.js +131 -0
  100. package/dist/auth/index.js +11 -0
  101. package/dist/auth/totp.js +378 -0
  102. package/dist/blur.js +36 -0
  103. package/dist/brand/AppIcons.jsx +118 -0
  104. package/dist/brand/AppMark.jsx +128 -0
  105. package/dist/brand/AppWordmark.jsx +138 -0
  106. package/dist/brand/GoogleMark.jsx +71 -0
  107. package/dist/brand/ProviderIcon.jsx +192 -0
  108. package/dist/brand/familyApps.js +33 -0
  109. package/dist/brand/index.js +11 -0
  110. package/dist/brand/svg.js +252 -0
  111. package/dist/brand/vProAppBenefits.js +19 -0
  112. package/dist/cn.js +5 -0
  113. package/dist/colors.js +15 -0
  114. package/dist/configureTheme.js +72 -0
  115. package/dist/controlStyles.js +520 -0
  116. package/dist/definiteSize.js +12 -0
  117. package/dist/dialogLayout.js +18 -0
  118. package/dist/entries/ai.js +4 -0
  119. package/dist/entries/ambient.js +3 -0
  120. package/dist/entries/auth.js +5 -0
  121. package/dist/entries/bottom-sheet.js +2 -0
  122. package/dist/entries/brand.js +2 -0
  123. package/dist/entries/button.js +2 -0
  124. package/dist/entries/date-time.js +3 -0
  125. package/dist/entries/expo-router.js +4 -0
  126. package/dist/entries/expo-web-root.js +3 -0
  127. package/dist/entries/floating.js +7 -0
  128. package/dist/entries/gesture-handler.js +4 -0
  129. package/dist/entries/keyboard-controller.js +4 -0
  130. package/dist/entries/layout.js +16 -0
  131. package/dist/entries/markdown.js +4 -0
  132. package/dist/entries/marketing.js +3 -0
  133. package/dist/entries/page-title.js +7 -0
  134. package/dist/entries/password-input.js +3 -0
  135. package/dist/entries/primitives.js +57 -0
  136. package/dist/entries/search-input.js +3 -0
  137. package/dist/entries/tab-bar.js +4 -0
  138. package/dist/entries/theme.js +7 -0
  139. package/dist/entries/toast.js +5 -0
  140. package/dist/entries/video.js +3 -0
  141. package/dist/entries/webview.js +4 -0
  142. package/dist/examples/AiChatGallery.jsx +509 -0
  143. package/dist/examples/ParityGallery.jsx +1307 -0
  144. package/dist/floating.js +62 -0
  145. package/dist/glassBlur.jsx +145 -0
  146. package/dist/gradient.js +17 -0
  147. package/dist/haptics.js +44 -0
  148. package/dist/highlight.js +382 -0
  149. package/dist/index.js +85 -0
  150. package/dist/initials.js +4 -0
  151. package/dist/loadingGrid.js +81 -0
  152. package/dist/markdown/Markdown.jsx +515 -0
  153. package/dist/markdown/MarkdownTable.jsx +443 -0
  154. package/dist/markdown/tableText.js +68 -0
  155. package/dist/marketing/PricingModal.jsx +608 -0
  156. package/dist/marketing/UpgradeToProCard.jsx +164 -0
  157. package/dist/marketing/pricing.js +34 -0
  158. package/dist/media/ImageGallery.jsx +580 -0
  159. package/dist/media/MediaCard.jsx +162 -0
  160. package/dist/media/imageGrid.js +54 -0
  161. package/dist/motion.js +33 -0
  162. package/dist/pagerSwipe.js +104 -0
  163. package/dist/palette.js +104 -0
  164. package/dist/primitives/button.jsx +127 -0
  165. package/dist/primitives/card.jsx +249 -0
  166. package/dist/primitives/icon.jsx +37 -0
  167. package/dist/primitives/input.jsx +56 -0
  168. package/dist/primitives/keyboard-controller.jsx +207 -0
  169. package/dist/primitives/keyboard-parts.jsx +75 -0
  170. package/dist/primitives/native-only-animated-view.jsx +68 -0
  171. package/dist/primitives/overlay.jsx +391 -0
  172. package/dist/primitives/pager-gesture.jsx +131 -0
  173. package/dist/primitives/select-host.jsx +103 -0
  174. package/dist/primitives/select.jsx +555 -0
  175. package/dist/primitives/sheet-drag.jsx +420 -0
  176. package/dist/primitives/sheet-gesture.jsx +219 -0
  177. package/dist/primitives/swipe-row-gesture.jsx +341 -0
  178. package/dist/primitives/switch.jsx +105 -0
  179. package/dist/primitives/text.jsx +145 -0
  180. package/dist/primitives/toast-drag-gesture.jsx +94 -0
  181. package/dist/primitives/use-controllable-state.js +40 -0
  182. package/dist/primitives/web-click-guard.js +69 -0
  183. package/dist/push.js +166 -0
  184. package/dist/relativeTime.js +70 -0
  185. package/dist/router/RouterStackPageHeader.jsx +53 -0
  186. package/dist/selectA11y.js +32 -0
  187. package/dist/settings/MoreFromProjectV.jsx +168 -0
  188. package/dist/settings/SettingsEmptyState.jsx +102 -0
  189. package/dist/settings/SettingsField.jsx +49 -0
  190. package/dist/settings/SettingsSection.jsx +101 -0
  191. package/dist/settings/SettingsStack.jsx +17 -0
  192. package/dist/settings/SettingsToggleRow.jsx +163 -0
  193. package/dist/settings/projectVMobileApps.js +126 -0
  194. package/dist/settings/relaxedSizeStyle.js +11 -0
  195. package/dist/sheetDrag.js +76 -0
  196. package/dist/svgA11y.js +46 -0
  197. package/dist/swipeRow.js +161 -0
  198. package/dist/tabBarRoutes.js +41 -0
  199. package/dist/tabIndicator.js +139 -0
  200. package/dist/tailwind-preset.cjs +255 -0
  201. package/dist/tailwind-preset.d.cts +55 -0
  202. package/dist/theme.jsx +62 -0
  203. package/dist/toastChrome.js +110 -0
  204. package/dist/toastDrag.js +51 -0
  205. package/dist/toastStore.js +54 -0
  206. package/dist/tokens.js +70 -0
  207. package/dist/typography.js +40 -0
  208. package/dist/useReduceMotion.js +32 -0
  209. package/dist/useReduceTransparency.js +27 -0
  210. package/dist/useScheme.js +15 -0
  211. package/dist/video/InlineVideo.jsx +115 -0
  212. package/dist/webview/HtmlView.jsx +169 -0
  213. package/dist/webview/HtmlView.web.jsx +63 -0
  214. package/dist/webview/htmlView.js +43 -0
  215. package/package.json +70 -33
  216. package/src/DropdownMenu.tsx +28 -16
  217. package/src/FloatingTabBar.tsx +78 -41
  218. package/src/ai/conversation.tsx +12 -8
  219. package/src/glassBlur.tsx +47 -22
  220. package/src/primitives/swipe-row-gesture.tsx +11 -2
@@ -0,0 +1,14 @@
1
+ // Pure step types for the activity timeline: no React, so `@vvedantb/native-ui`
2
+ // keeps a byte-identical copy (`src/ai/activity-step-types.ts`), with the step
3
+ // labels and grouping that read them.
4
+
5
+ /** One item in a todo checklist step (type "todos"). */
6
+
7
+ /** One choice offered by an AskUserQuestion prompt. */
8
+
9
+ /** One question of an AskUserQuestion prompt, with the choices shown. */
10
+
11
+ /** True when the step has expandable rich detail to show. */
12
+ export function activityStepHasRichDetail(step) {
13
+ return Boolean(step.command || step.output || step.edits && step.edits.length > 0 || step.files && step.files.length > 0 || step.contentPreview || step.hasHiddenDetail);
14
+ }
@@ -0,0 +1,98 @@
1
+ import { c as _c } from "react/compiler-runtime";
2
+ import { IconAlertTriangle, IconCheck } from "@tabler/icons-react-native";
3
+ import { Spinner } from "../Spinner";
4
+ import { useLargerText } from "../theme";
5
+ import { AgentTraceDescription, AgentTraceIcon, AgentTraceLine, AgentTraceTitle } from "./trace";
6
+ /**
7
+ * One tool or activity step as a single trace line: icon, label, status and
8
+ * an optional description. A light stand-in for the web `ActivityTasks`, built
9
+ * on the `AgentTrace` parts. Its accessibility label reads the label, the
10
+ * status and the description.
11
+ */
12
+ export function ActivityStep(t0) {
13
+ const $ = _c(26);
14
+ const {
15
+ label,
16
+ icon,
17
+ status: t1,
18
+ description,
19
+ testID
20
+ } = t0;
21
+ const status = t1 === undefined ? "complete" : t1;
22
+ const largerText = useLargerText();
23
+ const running = status === "running";
24
+ const state = running ? "in progress" : status === "error" ? "failed" : "done";
25
+ const t2 = status === "error" ? "error" : undefined;
26
+ let t3;
27
+ if ($[0] !== description || $[1] !== label || $[2] !== state) {
28
+ t3 = [label, state, description].filter(Boolean);
29
+ $[0] = description;
30
+ $[1] = label;
31
+ $[2] = state;
32
+ $[3] = t3;
33
+ } else {
34
+ t3 = $[3];
35
+ }
36
+ const t4 = t3.join(", ");
37
+ let t5;
38
+ if ($[4] !== running) {
39
+ t5 = {
40
+ busy: running
41
+ };
42
+ $[4] = running;
43
+ $[5] = t5;
44
+ } else {
45
+ t5 = $[5];
46
+ }
47
+ let t6;
48
+ if ($[6] !== icon) {
49
+ t6 = icon ? <AgentTraceIcon icon={icon} /> : null;
50
+ $[6] = icon;
51
+ $[7] = t6;
52
+ } else {
53
+ t6 = $[7];
54
+ }
55
+ let t7;
56
+ if ($[8] !== label || $[9] !== running) {
57
+ t7 = <AgentTraceTitle shimmer={running}>{label}</AgentTraceTitle>;
58
+ $[8] = label;
59
+ $[9] = running;
60
+ $[10] = t7;
61
+ } else {
62
+ t7 = $[10];
63
+ }
64
+ let t8;
65
+ if ($[11] !== description) {
66
+ t8 = description ? <AgentTraceDescription>{description}</AgentTraceDescription> : null;
67
+ $[11] = description;
68
+ $[12] = t8;
69
+ } else {
70
+ t8 = $[12];
71
+ }
72
+ let t9;
73
+ if ($[13] !== largerText || $[14] !== running || $[15] !== status) {
74
+ t9 = running ? <AgentTraceIcon><Spinner size={largerText ? "md" : "sm"} decorative={true} /></AgentTraceIcon> : <AgentTraceIcon icon={status === "error" ? IconAlertTriangle : IconCheck} />;
75
+ $[13] = largerText;
76
+ $[14] = running;
77
+ $[15] = status;
78
+ $[16] = t9;
79
+ } else {
80
+ t9 = $[16];
81
+ }
82
+ let t10;
83
+ if ($[17] !== t2 || $[18] !== t4 || $[19] !== t5 || $[20] !== t6 || $[21] !== t7 || $[22] !== t8 || $[23] !== t9 || $[24] !== testID) {
84
+ t10 = <AgentTraceLine testID={testID} status={t2} accessible={true} accessibilityLabel={t4} accessibilityState={t5}>{t6}{t7}{t8}{t9}</AgentTraceLine>;
85
+ $[17] = t2;
86
+ $[18] = t4;
87
+ $[19] = t5;
88
+ $[20] = t6;
89
+ $[21] = t7;
90
+ $[22] = t8;
91
+ $[23] = t9;
92
+ $[24] = testID;
93
+ $[25] = t10;
94
+ } else {
95
+ t10 = $[25];
96
+ }
97
+ return t10;
98
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * One row in the activity timeline. Subtask rows may nest child rows that ran
3
+ * inside that agent (keyed by parentToolUseId → toolUseId).
4
+ */
5
+
6
+ /**
7
+ * Status-filler thinking labels emitted by older callback versions (and by
8
+ * prod until the current callback deploys). The response/reasoning text is
9
+ * shown directly, so these narration rows are pure noise — hide them.
10
+ */
11
+ const LEGACY_NOISE_LABELS = new Set(["Generating response...", "Generated response", "Streaming response...", "Streamed response", "Finalizing response..."]);
12
+
13
+ /** Thinking labels that mark model reasoning rather than status narration. */
14
+ const REASONING_LABELS = new Set(["Thinking...", "Thought"]);
15
+
16
+ /** Placeholder details older callbacks attached to reasoning-label steps. */
17
+ const FILLER_DETAILS = new Set(["Claude is reasoning...", "Codex is reasoning...", "Opencode is reasoning...", "Cursor is reasoning...", "Receiving reply..."]);
18
+
19
+ /**
20
+ * Older callbacks stored reasoning as thinking steps labelled
21
+ * "Thinking..."/"Thought". Remap those with real text to reasoning blocks;
22
+ * drop the bare placeholder ones (nothing to show).
23
+ */
24
+ export function normalizeStep(step) {
25
+ if (step.type !== "thinking") return step;
26
+ if (LEGACY_NOISE_LABELS.has(step.label)) return null;
27
+ if (REASONING_LABELS.has(step.label)) {
28
+ if (step.detail && !FILLER_DETAILS.has(step.detail)) {
29
+ return {
30
+ ...step,
31
+ type: "reasoning"
32
+ };
33
+ }
34
+ return null;
35
+ }
36
+ return step;
37
+ }
38
+
39
+ /** Builds one row per normalized step (no consecutive-type merging). */
40
+ function stepsToRows(steps) {
41
+ const rows = [];
42
+ for (const raw of steps) {
43
+ const step = normalizeStep(raw);
44
+ if (!step) continue;
45
+ rows.push({
46
+ step
47
+ });
48
+ }
49
+ return rows;
50
+ }
51
+
52
+ /**
53
+ * Row types that read as narration rather than work: they already summarise
54
+ * themselves, so folding them into an action group would hide their only text.
55
+ */
56
+ const STANDALONE_TYPES = new Set(["todos", "notice", "status", "question", "steer"]);
57
+
58
+ /** One block in the activity timeline: prose, a lone row, or a folded run. */
59
+
60
+ /**
61
+ * Folds rows into the read-then-act rhythm of the turn: each reasoning block
62
+ * stays prose, and the run of tool calls it produced collapses behind a single
63
+ * summary line. Without this the whole turn is one undifferentiated wall of
64
+ * per-call rows and the thinking that motivated them is lost in it.
65
+ */
66
+ export function groupActivityRows(rows) {
67
+ const segments = [];
68
+ for (const row of rows) {
69
+ if (row.step.type === "reasoning") {
70
+ segments.push({
71
+ kind: "reasoning",
72
+ step: row.step
73
+ });
74
+ continue;
75
+ }
76
+ // Mid-turn reply text: it ends the tool run before it, like reasoning.
77
+ if (row.step.type === "response") {
78
+ segments.push({
79
+ kind: "response",
80
+ step: row.step
81
+ });
82
+ continue;
83
+ }
84
+ if (STANDALONE_TYPES.has(row.step.type)) {
85
+ segments.push({
86
+ kind: "row",
87
+ row
88
+ });
89
+ continue;
90
+ }
91
+ const last = segments.at(-1);
92
+ if (last?.kind === "actions") {
93
+ last.rows.push(row);
94
+ continue;
95
+ }
96
+ segments.push({
97
+ kind: "actions",
98
+ rows: [row]
99
+ });
100
+ }
101
+ return segments;
102
+ }
103
+
104
+ /**
105
+ * React key for one activity step. `label` and `status` are omitted so a
106
+ * streaming update (Running → Ran, error → recovered) keeps the same instance.
107
+ * `toolUseId` is the identity the callback already stamps; the fallback is
108
+ * type + path/command + occurrence in the list being keyed (never the visible
109
+ * overflow slice — callers must pass the full-list index).
110
+ */
111
+ export function activityStepKey(step, occurrence) {
112
+ const id = step.toolUseId;
113
+ if (id !== undefined && id.length > 0) {
114
+ return `id:${id}`;
115
+ }
116
+ const stable = step.path ?? step.command ?? "";
117
+ return `fb:${step.type}:${stable}:${occurrence}`;
118
+ }
119
+ export function activityRowKey(row, occurrence) {
120
+ return activityStepKey(row.step, occurrence);
121
+ }
122
+
123
+ /** React key for a timeline segment. Action groups key off the first row. */
124
+ export function activitySegmentKey(segment, occurrence) {
125
+ if (segment.kind === "reasoning" || segment.kind === "response") {
126
+ return `${segment.kind}:${activityStepKey(segment.step, occurrence)}`;
127
+ }
128
+ if (segment.kind === "row") {
129
+ return `row:${activityRowKey(segment.row, occurrence)}`;
130
+ }
131
+ const first = segment.rows[0];
132
+ if (first === undefined) {
133
+ return `actions:${occurrence}`;
134
+ }
135
+ return `actions:${activityRowKey(first, occurrence)}`;
136
+ }
137
+
138
+ /**
139
+ * Builds per-call activity rows. Steps with `parentToolUseId` nest under the
140
+ * matching subtask `toolUseId`. Orphans whose parent was never found append
141
+ * at the top level (never drop).
142
+ */
143
+ export function buildActivityRows(steps) {
144
+ const childrenByParent = new Map();
145
+ const topLevel = [];
146
+ for (const step of steps) {
147
+ const parentId = step.parentToolUseId;
148
+ if (parentId) {
149
+ const existing = childrenByParent.get(parentId);
150
+ if (existing) existing.push(step);else childrenByParent.set(parentId, [step]);
151
+ } else {
152
+ topLevel.push(step);
153
+ }
154
+ }
155
+ const rows = stepsToRows(topLevel);
156
+ const attached = new Set();
157
+ for (const row of rows) {
158
+ if (row.step.type !== "subtask") continue;
159
+ const toolUseId = row.step.toolUseId;
160
+ if (!toolUseId) continue;
161
+ const kids = childrenByParent.get(toolUseId);
162
+ if (kids && kids.length > 0) {
163
+ row.children = stepsToRows(kids);
164
+ attached.add(toolUseId);
165
+ }
166
+ }
167
+ for (const [parentId, kids] of childrenByParent) {
168
+ if (attached.has(parentId)) continue;
169
+ rows.push(...stepsToRows(kids));
170
+ }
171
+ return rows;
172
+ }
@@ -0,0 +1,47 @@
1
+ // The folded (Codex-style) rules of the web `ActivityTasks`
2
+ // (`@vvedantb/ui` `activity-tasks.tsx`), as pure logic for an app that draws
3
+ // the rows with the `AgentTrace` parts. The row text, the grouping and the
4
+ // step types are byte-identical copies of the web files
5
+ // (`activity-step-label.ts`, `activity-tasks-utils.ts`, `activity-step-types.ts`).
6
+
7
+ import { buildActivityRows, groupActivityRows } from "./activity-tasks-utils";
8
+ /** Blocks a settled timeline shows before "Show N more" (web `MAX_VISIBLE_ROWS`). */
9
+ export const ACTIVITY_MAX_VISIBLE_BLOCKS = 8;
10
+
11
+ /** Child rows a subagent step shows before "+N more" (web `MAX_VISIBLE_CHILDREN`). */
12
+ export const ACTIVITY_MAX_VISIBLE_CHILDREN = 8;
13
+
14
+ /** Step types the folded layout hides: the reply text and the status thinking lines. */
15
+ const HIDDEN_TYPES = new Set(["thinking", "response"]);
16
+
17
+ /**
18
+ * A turn's steps as the blocks of the folded timeline: each reasoning step as
19
+ * prose, each run of tool calls behind one summary line, narration (to-dos,
20
+ * notices, questions) as lone rows, and the steps a subagent ran nested under
21
+ * its row. Reply text and status thinking lines are dropped, as on the web.
22
+ */
23
+ export function foldedActivitySegments(steps) {
24
+ return groupActivityRows(buildActivityRows(steps).filter(row => !HIDDEN_TYPES.has(row.step.type)));
25
+ }
26
+
27
+ /**
28
+ * The child rows a subagent row shows (its reply text belongs to its own
29
+ * thread, so it is dropped), capped at `ACTIVITY_MAX_VISIBLE_CHILDREN`, and
30
+ * how many are left for "+N more".
31
+ */
32
+ export function activityChildRows(row) {
33
+ const children = (row.children ?? []).filter(child => !HIDDEN_TYPES.has(child.step.type));
34
+ const overflow = Math.max(0, children.length - ACTIVITY_MAX_VISIBLE_CHILDREN);
35
+ return {
36
+ shown: children.slice(0, ACTIVITY_MAX_VISIBLE_CHILDREN),
37
+ total: children.length,
38
+ overflow
39
+ };
40
+ }
41
+
42
+ /** Whole seconds a finished step took, for the faded "4s" after its row; undefined when unknown or under 1s. */
43
+ export function activityStepSeconds(step) {
44
+ if (step.status !== "complete" || !step.durationMs) return undefined;
45
+ const seconds = Math.round(step.durationMs / 1000);
46
+ return seconds >= 1 ? seconds : undefined;
47
+ }
@@ -0,0 +1,258 @@
1
+ import { c as _c } from "react/compiler-runtime";
2
+ import { createContext, useContext } from "react";
3
+ import { Image, Pressable, View } from "react-native";
4
+ import { IconFileText, IconMusic, IconPaperclip, IconPhoto, IconVideo, IconWorld, IconX } from "@tabler/icons-react-native";
5
+ import { cn } from "../cn";
6
+ import { touchSlop } from "../controlStyles";
7
+ import { useLargerText } from "../theme";
8
+ import { tokens } from "../tokens";
9
+ import { useScheme } from "../useScheme";
10
+ import { ResponseText } from "./response-text";
11
+
12
+ /**
13
+ * A file or source attached to a prompt or message. The fields the web reads
14
+ * from the `ai` SDK `FileUIPart` and `SourceDocumentUIPart`, without the SDK.
15
+ */
16
+
17
+ const MEDIA_ICONS = {
18
+ audio: IconMusic,
19
+ document: IconFileText,
20
+ image: IconPhoto,
21
+ source: IconWorld,
22
+ unknown: IconPaperclip,
23
+ video: IconVideo
24
+ };
25
+
26
+ /** The kind of attachment, from its type and MIME type, as on the web. */
27
+ export function getMediaCategory(data) {
28
+ if (data.type === "source-document") return "source";
29
+ const mediaType = data.mediaType ?? "";
30
+ if (mediaType.startsWith("image/")) return "image";
31
+ if (mediaType.startsWith("video/")) return "video";
32
+ if (mediaType.startsWith("audio/")) return "audio";
33
+ if (mediaType.startsWith("application/") || mediaType.startsWith("text/")) return "document";
34
+ return "unknown";
35
+ }
36
+
37
+ /** The name to show and read, as on the web: the title or file name, else "Source", "Image" or "Attachment". */
38
+ export function getAttachmentLabel(data) {
39
+ if (data.type === "source-document") return data.title || data.filename || "Source";
40
+ return data.filename || (getMediaCategory(data) === "image" ? "Image" : "Attachment");
41
+ }
42
+
43
+ /** `inline` (default) wraps chips in rows; `list` stacks them full width. The web `grid` previews are not ported. */
44
+
45
+ const AttachmentsContext = createContext("inline");
46
+ /** The attachments of a prompt or message. */
47
+ export function Attachments(t0) {
48
+ const $ = _c(13);
49
+ let className;
50
+ let props;
51
+ let t1;
52
+ if ($[0] !== t0) {
53
+ ({
54
+ variant: t1,
55
+ className,
56
+ ...props
57
+ } = t0);
58
+ $[0] = t0;
59
+ $[1] = className;
60
+ $[2] = props;
61
+ $[3] = t1;
62
+ } else {
63
+ className = $[1];
64
+ props = $[2];
65
+ t1 = $[3];
66
+ }
67
+ const variant = t1 === undefined ? "inline" : t1;
68
+ const t2 = variant === "list" ? "gap-2" : "flex-row flex-wrap items-start gap-2";
69
+ let t3;
70
+ if ($[4] !== className || $[5] !== t2) {
71
+ t3 = cn(t2, className);
72
+ $[4] = className;
73
+ $[5] = t2;
74
+ $[6] = t3;
75
+ } else {
76
+ t3 = $[6];
77
+ }
78
+ let t4;
79
+ if ($[7] !== props || $[8] !== t3) {
80
+ t4 = <View className={t3} {...props} />;
81
+ $[7] = props;
82
+ $[8] = t3;
83
+ $[9] = t4;
84
+ } else {
85
+ t4 = $[9];
86
+ }
87
+ let t5;
88
+ if ($[10] !== t4 || $[11] !== variant) {
89
+ t5 = <AttachmentsContext.Provider value={variant}>{t4}</AttachmentsContext.Provider>;
90
+ $[10] = t4;
91
+ $[11] = variant;
92
+ $[12] = t5;
93
+ } else {
94
+ t5 = $[12];
95
+ }
96
+ return t5;
97
+ }
98
+ /** The hit target of a pressable chip, in points, on each axis. */
99
+ export const ATTACHMENT_PRESS_TARGET = 48;
100
+
101
+ /** A remove cross of 20 in a 32 row: the slop brings it to 44. */
102
+ const REMOVE_SIZE = 20;
103
+
104
+ /**
105
+ * One attachment as a chip: a 20 thumbnail for an image with a `url`, else
106
+ * its media icon, then the name and an optional remove button. Flat, on the
107
+ * web `flatPanelSubtle` fill (`bg-surface-secondary`). No hover card. With
108
+ * `onPress` the thumbnail or icon and the name are one button with a 48 point
109
+ * hit target (the chip stays 32, or 44 with larger text, and `hitSlop` does the
110
+ * rest); the remove button is a separate target that never calls `onPress`.
111
+ */
112
+ export function Attachment(t0) {
113
+ const $ = _c(40);
114
+ const {
115
+ data,
116
+ onRemove,
117
+ onPress,
118
+ accessibilityLabel,
119
+ testID
120
+ } = t0;
121
+ const variant = useContext(AttachmentsContext);
122
+ const scheme = useScheme();
123
+ const largerText = useLargerText();
124
+ let t1;
125
+ if ($[0] !== data) {
126
+ t1 = getMediaCategory(data);
127
+ $[0] = data;
128
+ $[1] = t1;
129
+ } else {
130
+ t1 = $[1];
131
+ }
132
+ const category = t1;
133
+ let t2;
134
+ if ($[2] !== data) {
135
+ t2 = getAttachmentLabel(data);
136
+ $[2] = data;
137
+ $[3] = t2;
138
+ } else {
139
+ t2 = $[3];
140
+ }
141
+ const label = t2;
142
+ const Icon = MEDIA_ICONS[category];
143
+ const iconSize = largerText ? 20 : 16;
144
+ const muted = tokens.color.muted[scheme];
145
+ let t3;
146
+ if ($[4] !== Icon || $[5] !== category || $[6] !== data || $[7] !== iconSize || $[8] !== muted) {
147
+ t3 = category === "image" && data.url ? <Image source={{
148
+ uri: data.url
149
+ }} resizeMode="cover" style={{
150
+ width: 20,
151
+ height: 20,
152
+ borderRadius: 4
153
+ }} /> : <Icon size={iconSize} color={muted} />;
154
+ $[4] = Icon;
155
+ $[5] = category;
156
+ $[6] = data;
157
+ $[7] = iconSize;
158
+ $[8] = muted;
159
+ $[9] = t3;
160
+ } else {
161
+ t3 = $[9];
162
+ }
163
+ const t4 = variant === "list" ? "flex-1" : "shrink";
164
+ let t5;
165
+ if ($[10] !== label || $[11] !== t4) {
166
+ t5 = <ResponseText size="small" selectable={false} numberOfLines={1} ellipsizeMode="middle" className={t4}>{label}</ResponseText>;
167
+ $[10] = label;
168
+ $[11] = t4;
169
+ $[12] = t5;
170
+ } else {
171
+ t5 = $[12];
172
+ }
173
+ let t6;
174
+ if ($[13] !== t3 || $[14] !== t5) {
175
+ t6 = <>{t3}{t5}</>;
176
+ $[13] = t3;
177
+ $[14] = t5;
178
+ $[15] = t6;
179
+ } else {
180
+ t6 = $[15];
181
+ }
182
+ const content = t6;
183
+ const chipHeight = largerText ? 44 : 32;
184
+ const slop = Math.max(0, Math.ceil((ATTACHMENT_PRESS_TARGET - chipHeight) / 2));
185
+ const t7 = largerText ? "min-h-11" : "min-h-8";
186
+ const t8 = variant === "list" ? "w-full" : "max-w-full";
187
+ let t9;
188
+ if ($[16] !== t7 || $[17] !== t8) {
189
+ t9 = cn("min-w-0 flex-row items-center gap-1.5 px-2", t7, t8);
190
+ $[16] = t7;
191
+ $[17] = t8;
192
+ $[18] = t9;
193
+ } else {
194
+ t9 = $[18];
195
+ }
196
+ const t10 = tokens.color.surfaceSecondary[scheme];
197
+ let t11;
198
+ if ($[19] !== t10) {
199
+ t11 = {
200
+ borderRadius: tokens.radius.sm,
201
+ backgroundColor: t10
202
+ };
203
+ $[19] = t10;
204
+ $[20] = t11;
205
+ } else {
206
+ t11 = $[20];
207
+ }
208
+ let t12;
209
+ if ($[21] !== accessibilityLabel || $[22] !== content || $[23] !== label || $[24] !== onPress || $[25] !== slop || $[26] !== variant) {
210
+ t12 = onPress ? <Pressable accessibilityRole="button" accessibilityLabel={accessibilityLabel ?? label} hitSlop={{
211
+ top: slop,
212
+ bottom: slop,
213
+ left: 0,
214
+ right: 0
215
+ }} onPress={onPress} className={cn("min-w-0 flex-row items-center gap-1.5 self-stretch active:opacity-70", variant === "list" ? "flex-1" : "shrink")}>{content}</Pressable> : content;
216
+ $[21] = accessibilityLabel;
217
+ $[22] = content;
218
+ $[23] = label;
219
+ $[24] = onPress;
220
+ $[25] = slop;
221
+ $[26] = variant;
222
+ $[27] = t12;
223
+ } else {
224
+ t12 = $[27];
225
+ }
226
+ let t13;
227
+ if ($[28] !== label || $[29] !== largerText || $[30] !== muted || $[31] !== onPress || $[32] !== onRemove) {
228
+ t13 = onRemove ? <Pressable accessibilityRole="button" accessibilityLabel={`Remove ${label}`} hitSlop={{
229
+ ...touchSlop(REMOVE_SIZE),
230
+ left: onPress ? 3 : touchSlop(REMOVE_SIZE).left,
231
+ right: 8
232
+ }} onPress={onRemove} className="items-center justify-center rounded-full active:opacity-70" style={{
233
+ width: REMOVE_SIZE,
234
+ height: REMOVE_SIZE
235
+ }}><IconX size={largerText ? 16 : 14} color={muted} /></Pressable> : null;
236
+ $[28] = label;
237
+ $[29] = largerText;
238
+ $[30] = muted;
239
+ $[31] = onPress;
240
+ $[32] = onRemove;
241
+ $[33] = t13;
242
+ } else {
243
+ t13 = $[33];
244
+ }
245
+ let t14;
246
+ if ($[34] !== t11 || $[35] !== t12 || $[36] !== t13 || $[37] !== t9 || $[38] !== testID) {
247
+ t14 = <View testID={testID} className={t9} style={t11}>{t12}{t13}</View>;
248
+ $[34] = t11;
249
+ $[35] = t12;
250
+ $[36] = t13;
251
+ $[37] = t9;
252
+ $[38] = testID;
253
+ $[39] = t14;
254
+ } else {
255
+ t14 = $[39];
256
+ }
257
+ return t14;
258
+ }