@tulipfarm/sprout 0.0.0-stage → 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.
Files changed (314) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +183 -2
  3. package/dist/components/accordion.d.ts +28 -0
  4. package/dist/components/accordion.js +26 -0
  5. package/dist/components/alert-dialog.d.ts +60 -0
  6. package/dist/components/alert-dialog.js +69 -0
  7. package/dist/components/alert.d.ts +30 -0
  8. package/dist/components/alert.js +35 -0
  9. package/dist/components/aspect-ratio.d.ts +7 -0
  10. package/dist/components/aspect-ratio.js +6 -0
  11. package/dist/components/attachment.d.ts +118 -0
  12. package/dist/components/attachment.js +285 -0
  13. package/dist/components/avatar.d.ts +32 -0
  14. package/dist/components/avatar.js +73 -0
  15. package/dist/components/badge.d.ts +21 -0
  16. package/dist/components/badge.js +27 -0
  17. package/dist/components/brand-icon.d.ts +28 -0
  18. package/dist/components/brand-icon.js +82 -0
  19. package/dist/components/breadcrumb.d.ts +29 -0
  20. package/dist/components/breadcrumb.js +44 -0
  21. package/dist/components/button-group.d.ts +28 -0
  22. package/dist/components/button-group.js +36 -0
  23. package/dist/components/button.d.ts +79 -0
  24. package/dist/components/button.js +93 -0
  25. package/dist/components/calendar.d.ts +27 -0
  26. package/dist/components/calendar.js +107 -0
  27. package/dist/components/canvas.d.ts +78 -0
  28. package/dist/components/canvas.js +180 -0
  29. package/dist/components/card.d.ts +43 -0
  30. package/dist/components/card.js +42 -0
  31. package/dist/components/carousel.d.ts +57 -0
  32. package/dist/components/carousel.js +422 -0
  33. package/dist/components/chart.d.ts +112 -0
  34. package/dist/components/chart.js +172 -0
  35. package/dist/components/chat-confirmation.d.ts +42 -0
  36. package/dist/components/chat-confirmation.js +62 -0
  37. package/dist/components/chat-input.d.ts +94 -0
  38. package/dist/components/chat-input.js +279 -0
  39. package/dist/components/chat-plan.d.ts +21 -0
  40. package/dist/components/chat-plan.js +35 -0
  41. package/dist/components/chat-sources.d.ts +64 -0
  42. package/dist/components/chat-sources.js +66 -0
  43. package/dist/components/checkbox.d.ts +16 -0
  44. package/dist/components/checkbox.js +17 -0
  45. package/dist/components/choice-list.d.ts +53 -0
  46. package/dist/components/choice-list.js +36 -0
  47. package/dist/components/code-block.d.ts +35 -0
  48. package/dist/components/code-block.js +64 -0
  49. package/dist/components/collapsible.d.ts +24 -0
  50. package/dist/components/collapsible.js +27 -0
  51. package/dist/components/combobox.d.ts +95 -0
  52. package/dist/components/combobox.js +106 -0
  53. package/dist/components/command.d.ts +94 -0
  54. package/dist/components/command.js +414 -0
  55. package/dist/components/context-menu.d.ts +57 -0
  56. package/dist/components/context-menu.js +60 -0
  57. package/dist/components/dialog.d.ts +60 -0
  58. package/dist/components/dialog.js +79 -0
  59. package/dist/components/direction.d.ts +6 -0
  60. package/dist/components/direction.js +6 -0
  61. package/dist/components/dropdown-menu.d.ts +62 -0
  62. package/dist/components/dropdown-menu.js +59 -0
  63. package/dist/components/field.d.ts +117 -0
  64. package/dist/components/field.js +150 -0
  65. package/dist/components/file-explorer.d.ts +27 -0
  66. package/dist/components/file-explorer.js +300 -0
  67. package/dist/components/file-viewer.d.ts +137 -0
  68. package/dist/components/file-viewer.js +400 -0
  69. package/dist/components/form.d.ts +87 -0
  70. package/dist/components/form.js +55 -0
  71. package/dist/components/glass-banner.d.ts +27 -0
  72. package/dist/components/glass-banner.js +116 -0
  73. package/dist/components/gradient-banner.d.ts +37 -0
  74. package/dist/components/gradient-banner.js +43 -0
  75. package/dist/components/hover-card.d.ts +27 -0
  76. package/dist/components/hover-card.js +26 -0
  77. package/dist/components/input-group.d.ts +68 -0
  78. package/dist/components/input-group.js +88 -0
  79. package/dist/components/input-otp.d.ts +38 -0
  80. package/dist/components/input-otp.js +43 -0
  81. package/dist/components/input.d.ts +16 -0
  82. package/dist/components/input.js +18 -0
  83. package/dist/components/kbd.d.ts +12 -0
  84. package/dist/components/kbd.js +11 -0
  85. package/dist/components/label.d.ts +8 -0
  86. package/dist/components/label.js +10 -0
  87. package/dist/components/mention-list.d.ts +54 -0
  88. package/dist/components/mention-list.js +31 -0
  89. package/dist/components/menubar.d.ts +64 -0
  90. package/dist/components/menubar.js +68 -0
  91. package/dist/components/message-actions.d.ts +34 -0
  92. package/dist/components/message-actions.js +68 -0
  93. package/dist/components/message-response.d.ts +20 -0
  94. package/dist/components/message-response.js +46 -0
  95. package/dist/components/message-scroller.d.ts +71 -0
  96. package/dist/components/message-scroller.js +217 -0
  97. package/dist/components/message.d.ts +49 -0
  98. package/dist/components/message.js +59 -0
  99. package/dist/components/navigation-menu.d.ts +45 -0
  100. package/dist/components/navigation-menu.js +54 -0
  101. package/dist/components/notice-banner.d.ts +14 -0
  102. package/dist/components/notice-banner.js +11 -0
  103. package/dist/components/page-content.d.ts +7 -0
  104. package/dist/components/page-content.js +9 -0
  105. package/dist/components/page-header.d.ts +37 -0
  106. package/dist/components/page-header.js +53 -0
  107. package/dist/components/pagination.d.ts +29 -0
  108. package/dist/components/pagination.js +29 -0
  109. package/dist/components/plan-outline.d.ts +60 -0
  110. package/dist/components/plan-outline.js +108 -0
  111. package/dist/components/popover.d.ts +34 -0
  112. package/dist/components/popover.js +35 -0
  113. package/dist/components/progress.d.ts +27 -0
  114. package/dist/components/progress.js +35 -0
  115. package/dist/components/prompt-list.d.ts +23 -0
  116. package/dist/components/prompt-list.js +11 -0
  117. package/dist/components/questionnaire.d.ts +148 -0
  118. package/dist/components/questionnaire.js +1002 -0
  119. package/dist/components/radio-group.d.ts +16 -0
  120. package/dist/components/radio-group.js +21 -0
  121. package/dist/components/reasoning-effort.d.ts +24 -0
  122. package/dist/components/reasoning-effort.js +269 -0
  123. package/dist/components/resizable.d.ts +67 -0
  124. package/dist/components/resizable.js +536 -0
  125. package/dist/components/result-card.d.ts +58 -0
  126. package/dist/components/result-card.js +88 -0
  127. package/dist/components/review-card.d.ts +62 -0
  128. package/dist/components/review-card.js +49 -0
  129. package/dist/components/rich-text-editor.d.ts +51 -0
  130. package/dist/components/rich-text-editor.js +248 -0
  131. package/dist/components/schema-diagram.d.ts +52 -0
  132. package/dist/components/schema-diagram.js +226 -0
  133. package/dist/components/scroll-area.d.ts +22 -0
  134. package/dist/components/scroll-area.js +19 -0
  135. package/dist/components/search-dialog.d.ts +103 -0
  136. package/dist/components/search-dialog.js +65 -0
  137. package/dist/components/search-field.d.ts +17 -0
  138. package/dist/components/search-field.js +27 -0
  139. package/dist/components/select.d.ts +57 -0
  140. package/dist/components/select.js +57 -0
  141. package/dist/components/selection-slot.d.ts +18 -0
  142. package/dist/components/selection-slot.js +53 -0
  143. package/dist/components/separator.d.ts +48 -0
  144. package/dist/components/separator.js +54 -0
  145. package/dist/components/settings-list.d.ts +26 -0
  146. package/dist/components/settings-list.js +17 -0
  147. package/dist/components/sheet.d.ts +70 -0
  148. package/dist/components/sheet.js +77 -0
  149. package/dist/components/sidebar.d.ts +70 -0
  150. package/dist/components/sidebar.js +187 -0
  151. package/dist/components/skeleton.d.ts +31 -0
  152. package/dist/components/skeleton.js +32 -0
  153. package/dist/components/slider.d.ts +17 -0
  154. package/dist/components/slider.js +24 -0
  155. package/dist/components/spinner.d.ts +37 -0
  156. package/dist/components/spinner.js +43 -0
  157. package/dist/components/state-panel-samples.d.ts +25 -0
  158. package/dist/components/state-panel-samples.js +423 -0
  159. package/dist/components/state-panel.d.ts +35 -0
  160. package/dist/components/state-panel.js +29 -0
  161. package/dist/components/state-sample.d.ts +63 -0
  162. package/dist/components/state-sample.js +42 -0
  163. package/dist/components/switch.d.ts +12 -0
  164. package/dist/components/switch.js +14 -0
  165. package/dist/components/table-nuqs.d.ts +16 -0
  166. package/dist/components/table-nuqs.js +59 -0
  167. package/dist/components/table.d.ts +120 -0
  168. package/dist/components/table.js +663 -0
  169. package/dist/components/tabs.d.ts +37 -0
  170. package/dist/components/tabs.js +36 -0
  171. package/dist/components/textarea.d.ts +18 -0
  172. package/dist/components/textarea.js +19 -0
  173. package/dist/components/toast.d.ts +45 -0
  174. package/dist/components/toast.js +83 -0
  175. package/dist/components/toggle-group.d.ts +30 -0
  176. package/dist/components/toggle-group.js +39 -0
  177. package/dist/components/toggle.d.ts +27 -0
  178. package/dist/components/toggle.js +21 -0
  179. package/dist/components/tool-activity.d.ts +79 -0
  180. package/dist/components/tool-activity.js +226 -0
  181. package/dist/components/toolbar-status.d.ts +21 -0
  182. package/dist/components/toolbar-status.js +14 -0
  183. package/dist/components/tooltip.d.ts +31 -0
  184. package/dist/components/tooltip.js +99 -0
  185. package/dist/components/typed-text.d.ts +17 -0
  186. package/dist/components/typed-text.js +56 -0
  187. package/dist/index.d.ts +90 -0
  188. package/dist/index.js +90 -0
  189. package/dist/internal/brand-glyphs.d.ts +370 -0
  190. package/dist/internal/brand-glyphs.js +262 -0
  191. package/dist/internal/classes.d.ts +3 -0
  192. package/dist/internal/classes.js +20 -0
  193. package/dist/internal/code-tokens.d.ts +4 -0
  194. package/dist/internal/code-tokens.js +34 -0
  195. package/dist/internal/fieldset.d.ts +6 -0
  196. package/dist/internal/fieldset.js +38 -0
  197. package/dist/internal/file-kind.d.ts +4 -0
  198. package/dist/internal/file-kind.js +62 -0
  199. package/dist/internal/languages.d.ts +7 -0
  200. package/dist/internal/languages.js +1010 -0
  201. package/dist/internal/message-scroller.d.ts +102 -0
  202. package/dist/internal/message-scroller.js +637 -0
  203. package/dist/internal/pressable.d.ts +2 -0
  204. package/dist/internal/pressable.js +29 -0
  205. package/dist/internal/syntax.d.ts +14 -0
  206. package/dist/internal/syntax.js +137 -0
  207. package/dist/styles/accordion.css +102 -0
  208. package/dist/styles/alert-dialog.css +198 -0
  209. package/dist/styles/alert.css +149 -0
  210. package/dist/styles/aspect-ratio.css +8 -0
  211. package/dist/styles/attachment.css +700 -0
  212. package/dist/styles/avatar.css +186 -0
  213. package/dist/styles/badge.css +178 -0
  214. package/dist/styles/brand-icon.css +104 -0
  215. package/dist/styles/breadcrumb.css +121 -0
  216. package/dist/styles/button-group.css +113 -0
  217. package/dist/styles/button.css +380 -0
  218. package/dist/styles/calendar.css +295 -0
  219. package/dist/styles/canvas.css +313 -0
  220. package/dist/styles/card.css +146 -0
  221. package/dist/styles/carousel.css +107 -0
  222. package/dist/styles/chart.css +227 -0
  223. package/dist/styles/chat-confirmation.css +29 -0
  224. package/dist/styles/chat-input.css +240 -0
  225. package/dist/styles/chat-plan.css +109 -0
  226. package/dist/styles/chat-sources.css +191 -0
  227. package/dist/styles/checkbox.css +122 -0
  228. package/dist/styles/choice-list.css +343 -0
  229. package/dist/styles/code-block.css +211 -0
  230. package/dist/styles/collapsible.css +72 -0
  231. package/dist/styles/combobox.css +347 -0
  232. package/dist/styles/command.css +233 -0
  233. package/dist/styles/context-menu.css +151 -0
  234. package/dist/styles/dialog.css +237 -0
  235. package/dist/styles/dropdown-menu.css +150 -0
  236. package/dist/styles/field.css +340 -0
  237. package/dist/styles/file-explorer.css +517 -0
  238. package/dist/styles/file-viewer.css +375 -0
  239. package/dist/styles/form.css +155 -0
  240. package/dist/styles/glass-banner.css +308 -0
  241. package/dist/styles/gradient-banner.css +256 -0
  242. package/dist/styles/hover-card.css +95 -0
  243. package/dist/styles/index.css +268 -0
  244. package/dist/styles/input-group.css +285 -0
  245. package/dist/styles/input-otp.css +138 -0
  246. package/dist/styles/input.css +98 -0
  247. package/dist/styles/kbd.css +59 -0
  248. package/dist/styles/label.css +89 -0
  249. package/dist/styles/mention-list.css +166 -0
  250. package/dist/styles/menubar.css +226 -0
  251. package/dist/styles/message-actions.css +83 -0
  252. package/dist/styles/message-response.css +176 -0
  253. package/dist/styles/message-scroller.css +136 -0
  254. package/dist/styles/message.css +310 -0
  255. package/dist/styles/navigation-menu.css +291 -0
  256. package/dist/styles/notice-banner.css +68 -0
  257. package/dist/styles/page-content.css +21 -0
  258. package/dist/styles/page-header.css +156 -0
  259. package/dist/styles/pagination.css +72 -0
  260. package/dist/styles/plan-outline.css +393 -0
  261. package/dist/styles/popover.css +140 -0
  262. package/dist/styles/progress.css +93 -0
  263. package/dist/styles/prompt-list.css +165 -0
  264. package/dist/styles/questionnaire.css +308 -0
  265. package/dist/styles/radio-group.css +119 -0
  266. package/dist/styles/reasoning-effort.css +157 -0
  267. package/dist/styles/resizable.css +108 -0
  268. package/dist/styles/result-card.css +215 -0
  269. package/dist/styles/review-card.css +235 -0
  270. package/dist/styles/rich-text-editor.css +169 -0
  271. package/dist/styles/schema-diagram.css +419 -0
  272. package/dist/styles/scroll-area.css +77 -0
  273. package/dist/styles/search-dialog.css +280 -0
  274. package/dist/styles/search-field.css +17 -0
  275. package/dist/styles/select.css +285 -0
  276. package/dist/styles/selection-slot.css +31 -0
  277. package/dist/styles/separator.css +206 -0
  278. package/dist/styles/settings-list.css +81 -0
  279. package/dist/styles/sheet.css +361 -0
  280. package/dist/styles/sidebar.css +363 -0
  281. package/dist/styles/skeleton.css +80 -0
  282. package/dist/styles/slider.css +142 -0
  283. package/dist/styles/spinner.css +39 -0
  284. package/dist/styles/state-panel-samples.css +41 -0
  285. package/dist/styles/state-panel.css +173 -0
  286. package/dist/styles/state-sample.css +285 -0
  287. package/dist/styles/switch.css +128 -0
  288. package/dist/styles/table.css +604 -0
  289. package/dist/styles/tabs.css +223 -0
  290. package/dist/styles/textarea.css +77 -0
  291. package/dist/styles/themes/chatgpt.css +125 -0
  292. package/dist/styles/themes/claude.css +125 -0
  293. package/dist/styles/themes/cloudflare.css +125 -0
  294. package/dist/styles/themes/discord.css +125 -0
  295. package/dist/styles/themes/github.css +125 -0
  296. package/dist/styles/themes/grok.css +125 -0
  297. package/dist/styles/themes/linear.css +126 -0
  298. package/dist/styles/themes/neutral.css +125 -0
  299. package/dist/styles/themes/notion.css +125 -0
  300. package/dist/styles/themes/perplexity.css +125 -0
  301. package/dist/styles/themes/raycast.css +125 -0
  302. package/dist/styles/themes/spotify.css +125 -0
  303. package/dist/styles/themes/stripe.css +125 -0
  304. package/dist/styles/themes/supabase.css +125 -0
  305. package/dist/styles/themes/tulipfarm.css +115 -0
  306. package/dist/styles/themes/vercel.css +126 -0
  307. package/dist/styles/toast.css +197 -0
  308. package/dist/styles/toggle-group.css +161 -0
  309. package/dist/styles/toggle.css +119 -0
  310. package/dist/styles/tool-activity.css +396 -0
  311. package/dist/styles/toolbar-status.css +52 -0
  312. package/dist/styles/tooltip.css +122 -0
  313. package/dist/styles/typed-text.css +17 -0
  314. package/package.json +452 -4
@@ -0,0 +1,536 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Children, createContext, Fragment, isValidElement, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore, } from "react";
4
+ import { cn } from "../internal/classes.js";
5
+ export const resizableOrientationNames = ["horizontal", "vertical"];
6
+ const GroupContext = createContext(null);
7
+ function useGroup(part) {
8
+ const context = useContext(GroupContext);
9
+ if (!context)
10
+ throw new Error(`${part} must be used inside ResizablePanelGroup.`);
11
+ return context;
12
+ }
13
+ const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
14
+ const KEYBOARD_STEP = 5;
15
+ const EPSILON = 0.001;
16
+ /** Percent value of a size that can be resolved without measuring, or undefined. */
17
+ function staticPercent(size) {
18
+ if (typeof size !== "string")
19
+ return undefined;
20
+ const value = size.trim();
21
+ if (/^-?[\d.]+%?$/.test(value))
22
+ return Number.parseFloat(value);
23
+ return undefined;
24
+ }
25
+ function toPercent(size, available, element) {
26
+ if (size === undefined)
27
+ return undefined;
28
+ const percent = staticPercent(size);
29
+ if (percent !== undefined)
30
+ return percent;
31
+ if (available <= 0)
32
+ return undefined;
33
+ let pixels;
34
+ if (typeof size === "number")
35
+ pixels = size;
36
+ else {
37
+ const value = Number.parseFloat(size);
38
+ const unit = size.trim().replace(/^-?[\d.]+/, "");
39
+ const view = element.ownerDocument.defaultView;
40
+ const font = (node) => Number.parseFloat(view?.getComputedStyle(node).fontSize ?? "") || 16;
41
+ if (unit === "px")
42
+ pixels = value;
43
+ else if (unit === "rem")
44
+ pixels = value * font(element.ownerDocument.documentElement);
45
+ else if (unit === "em")
46
+ pixels = value * font(element);
47
+ else if (unit === "vw")
48
+ pixels = (value * (view?.innerWidth ?? 0)) / 100;
49
+ else if (unit === "vh")
50
+ pixels = (value * (view?.innerHeight ?? 0)) / 100;
51
+ else
52
+ return undefined;
53
+ }
54
+ return (pixels / available) * 100;
55
+ }
56
+ const clamp = (value, { min, max }) => Math.min(Math.max(value, min), max);
57
+ const total = (sizes) => sizes.reduce((sum, size) => sum + size, 0);
58
+ /** Clamp each size to its bounds, then spread any remainder over panels with room. */
59
+ function validate(sizes, bounds) {
60
+ const next = sizes.map((size, index) => clamp(size, bounds[index]));
61
+ for (let pass = 0; pass < 8; pass += 1) {
62
+ const diff = 100 - total(next);
63
+ if (Math.abs(diff) < EPSILON)
64
+ break;
65
+ const open = next.flatMap((size, index) => (diff > 0 ? size < bounds[index].max : size > bounds[index].min)
66
+ ? [index]
67
+ : []);
68
+ if (!open.length)
69
+ break;
70
+ for (const index of open)
71
+ next[index] = clamp(next[index] + diff / open.length, bounds[index]);
72
+ }
73
+ const sum = total(next);
74
+ return Math.abs(sum - 100) < EPSILON || sum <= 0
75
+ ? next
76
+ : next.map((size) => (size / sum) * 100);
77
+ }
78
+ /**
79
+ * Move the boundary after panel `pivot` by `delta` percent. The adjacent panel
80
+ * on the growing side takes the space; panels on the shrinking side give it up
81
+ * nearest first, each down to its minimum.
82
+ */
83
+ function resize(base, bounds, pivot, delta) {
84
+ const next = [...base];
85
+ if (Math.abs(delta) < EPSILON)
86
+ return next;
87
+ const grow = delta > 0 ? pivot : pivot + 1;
88
+ const shrink = delta > 0
89
+ ? next.map((_, index) => index).slice(pivot + 1)
90
+ : next
91
+ .map((_, index) => index)
92
+ .slice(0, pivot + 1)
93
+ .reverse();
94
+ const room = Math.min(bounds[grow].max - next[grow], total(shrink.map((index) => next[index] - bounds[index].min)));
95
+ let remaining = Math.max(0, Math.min(Math.abs(delta), room));
96
+ next[grow] = next[grow] + remaining;
97
+ for (const index of shrink) {
98
+ const take = Math.min(remaining, next[index] - bounds[index].min);
99
+ next[index] = next[index] - take;
100
+ remaining -= take;
101
+ if (remaining < EPSILON)
102
+ break;
103
+ }
104
+ return next;
105
+ }
106
+ function sameLayout(a, b) {
107
+ const keys = Object.keys(a);
108
+ return (keys.length === Object.keys(b).length &&
109
+ keys.every((key) => key in b && Math.abs(a[key] - b[key]) < EPSILON));
110
+ }
111
+ function collectPanelProps(children, found = []) {
112
+ Children.forEach(children, (child) => {
113
+ if (!isValidElement(child))
114
+ return;
115
+ if (child.type === ResizablePanel)
116
+ found.push(child.props);
117
+ else if (child.type === Fragment)
118
+ collectPanelProps(child.props.children, found);
119
+ });
120
+ return found;
121
+ }
122
+ function assignRef(ref, value) {
123
+ if (typeof ref === "function")
124
+ ref(value);
125
+ else if (ref)
126
+ ref.current = value;
127
+ }
128
+ /** A row or column of resizable panels separated by `ResizableHandle`s. */
129
+ export function ResizablePanelGroup({ orientation = "horizontal", defaultLayout, onLayoutChange, onLayoutChanged, className, children, ref, ...props }) {
130
+ const element = useRef(null);
131
+ const panels = useRef(new Map());
132
+ const separators = useRef(new Map());
133
+ const [version, setVersion] = useState(0);
134
+ const [extent, setExtent] = useState(0);
135
+ const [layout, setLayout] = useState({});
136
+ const [bounds, setBounds] = useState({});
137
+ const [links, setLinks] = useState({});
138
+ const [dragging, setDragging] = useState(null);
139
+ const layoutRef = useRef(layout);
140
+ const panelKey = useRef(null);
141
+ const defaultsKey = useRef(null);
142
+ const interacted = useRef(false);
143
+ const callbacks = useRef({ onLayoutChange, onLayoutChanged });
144
+ callbacks.current = { onLayoutChange, onLayoutChanged };
145
+ const horizontal = orientation === "horizontal";
146
+ const autoSize = useMemo(() => {
147
+ const declared = collectPanelProps(children).map((panel) => staticPercent(panel.defaultSize));
148
+ const unknown = declared.filter((size) => size === undefined).length;
149
+ const known = total(declared.filter((size) => size !== undefined));
150
+ return unknown ? Math.max(0, 100 - known) / unknown : 0;
151
+ }, [children]);
152
+ const ordered = useCallback(() => [...panels.current.values()].sort((a, b) => a.element.compareDocumentPosition(b.element) &
153
+ Node.DOCUMENT_POSITION_FOLLOWING
154
+ ? -1
155
+ : 1), []);
156
+ const available = useCallback(() => {
157
+ const root = element.current;
158
+ if (!root)
159
+ return 0;
160
+ let size = horizontal ? root.clientWidth : root.clientHeight;
161
+ for (const separator of separators.current.values())
162
+ size -= horizontal ? separator.offsetWidth : separator.offsetHeight;
163
+ return Math.max(0, size);
164
+ }, [horizontal]);
165
+ const measureBounds = useCallback((entries) => {
166
+ const space = available();
167
+ return entries.map((entry) => {
168
+ const min = toPercent(entry.minSize, space, entry.element) ?? 0;
169
+ const max = toPercent(entry.maxSize, space, entry.element) ?? 100;
170
+ return {
171
+ min: Math.min(Math.max(min, 0), 100),
172
+ max: Math.max(Math.min(max, 100), Math.min(Math.max(min, 0), 100)),
173
+ };
174
+ });
175
+ }, [available]);
176
+ const commit = useCallback((ids, sizes, settled) => {
177
+ const next = Object.fromEntries(ids.map((id, index) => [id, sizes[index]]));
178
+ if (!sameLayout(next, layoutRef.current)) {
179
+ layoutRef.current = next;
180
+ setLayout(next);
181
+ callbacks.current.onLayoutChange?.(next);
182
+ }
183
+ else if (!settled)
184
+ return;
185
+ if (settled)
186
+ callbacks.current.onLayoutChanged?.(next, settled);
187
+ }, []);
188
+ const defaultLayoutKey = defaultLayout ? JSON.stringify(defaultLayout) : "";
189
+ useIsomorphicLayoutEffect(() => {
190
+ const entries = ordered();
191
+ const ids = entries.map((entry) => entry.id);
192
+ const key = ids.join("\u0000");
193
+ const limits = measureBounds(entries);
194
+ const changedPanels = key !== panelKey.current;
195
+ const changedDefaults = defaultLayoutKey !== defaultsKey.current;
196
+ if (changedPanels)
197
+ interacted.current = false;
198
+ let sizes;
199
+ if (changedPanels || (changedDefaults && !interacted.current)) {
200
+ const saved = defaultLayout &&
201
+ ids.length &&
202
+ ids.every((id) => typeof defaultLayout[id] === "number")
203
+ ? ids.map((id) => defaultLayout[id])
204
+ : null;
205
+ if (saved)
206
+ sizes = saved;
207
+ else {
208
+ const space = available();
209
+ const declared = entries.map((entry) => toPercent(entry.defaultSize, space, entry.element));
210
+ const unknown = declared.filter((size) => size === undefined).length;
211
+ const rest = Math.max(0, 100 -
212
+ total(declared.filter((size) => size !== undefined))) / Math.max(unknown, 1);
213
+ sizes = declared.map((size) => size ?? rest);
214
+ }
215
+ }
216
+ else
217
+ sizes = ids.map((id) => layoutRef.current[id] ?? 0);
218
+ const before = layoutRef.current;
219
+ const next = validate(sizes, limits);
220
+ const nextLayout = Object.fromEntries(ids.map((id, index) => [id, next[index]]));
221
+ commit(ids, next, null);
222
+ if (changedPanels || !sameLayout(before, nextLayout))
223
+ callbacks.current.onLayoutChanged?.(nextLayout, {
224
+ isUserInteraction: false,
225
+ });
226
+ panelKey.current = key;
227
+ defaultsKey.current = defaultLayoutKey;
228
+ const nextBounds = Object.fromEntries(ids.map((id, index) => [id, limits[index]]));
229
+ setBounds((current) => JSON.stringify(current) === JSON.stringify(nextBounds)
230
+ ? current
231
+ : nextBounds);
232
+ const nextLinks = {};
233
+ for (const [separatorId, separator] of separators.current) {
234
+ let previous;
235
+ let following;
236
+ for (const entry of entries) {
237
+ if (separator.compareDocumentPosition(entry.element) &
238
+ Node.DOCUMENT_POSITION_PRECEDING)
239
+ previous = entry;
240
+ else {
241
+ following = entry;
242
+ break;
243
+ }
244
+ }
245
+ if (previous && following)
246
+ nextLinks[separatorId] = { before: previous.id, after: following.id };
247
+ }
248
+ setLinks((current) => JSON.stringify(current) === JSON.stringify(nextLinks)
249
+ ? current
250
+ : nextLinks);
251
+ }, [
252
+ version,
253
+ extent,
254
+ defaultLayoutKey,
255
+ ordered,
256
+ measureBounds,
257
+ available,
258
+ commit,
259
+ ]);
260
+ useEffect(() => {
261
+ const root = element.current;
262
+ if (!root || typeof ResizeObserver === "undefined")
263
+ return;
264
+ const observer = new ResizeObserver(() => setExtent(Math.round(available())));
265
+ observer.observe(root);
266
+ return () => observer.disconnect();
267
+ }, [available]);
268
+ const registerPanel = useCallback((entry) => {
269
+ panels.current.set(entry.id, entry);
270
+ setVersion((value) => value + 1);
271
+ return () => {
272
+ if (panels.current.get(entry.id) === entry)
273
+ panels.current.delete(entry.id);
274
+ setVersion((value) => value + 1);
275
+ };
276
+ }, []);
277
+ const registerSeparator = useCallback((id, node) => {
278
+ separators.current.set(id, node);
279
+ setVersion((value) => value + 1);
280
+ return () => {
281
+ separators.current.delete(id);
282
+ setVersion((value) => value + 1);
283
+ };
284
+ }, []);
285
+ const pivotFor = useCallback((separatorId) => {
286
+ const separator = separators.current.get(separatorId);
287
+ if (!separator)
288
+ return null;
289
+ const entries = ordered();
290
+ let pivot = -1;
291
+ entries.forEach((entry, index) => {
292
+ if (separator.compareDocumentPosition(entry.element) &
293
+ Node.DOCUMENT_POSITION_PRECEDING)
294
+ pivot = index;
295
+ });
296
+ if (pivot < 0 || pivot >= entries.length - 1)
297
+ return null;
298
+ const ids = entries.map((entry) => entry.id);
299
+ return {
300
+ ids,
301
+ pivot,
302
+ base: ids.map((id) => layoutRef.current[id] ?? 0),
303
+ limits: measureBounds(entries),
304
+ };
305
+ }, [ordered, measureBounds]);
306
+ const reversed = useCallback(() => horizontal &&
307
+ !!element.current &&
308
+ getComputedStyle(element.current).direction === "rtl", [horizontal]);
309
+ const startDrag = useCallback((separatorId, event) => {
310
+ const target = pivotFor(separatorId);
311
+ if (!target)
312
+ return;
313
+ event.preventDefault();
314
+ const handle = event.currentTarget;
315
+ const pointerId = event.pointerId;
316
+ const origin = horizontal ? event.clientX : event.clientY;
317
+ const space = available();
318
+ const direction = reversed() ? -1 : 1;
319
+ const ownerDocument = handle.ownerDocument;
320
+ const root = ownerDocument.documentElement;
321
+ const previousCursor = root.style.cursor;
322
+ const previousSelect = root.style.userSelect;
323
+ root.style.cursor = horizontal ? "col-resize" : "row-resize";
324
+ root.style.userSelect = "none";
325
+ handle.setPointerCapture?.(pointerId);
326
+ handle.focus({ preventScroll: true });
327
+ setDragging(separatorId);
328
+ let latest = target.base;
329
+ const move = (moveEvent) => {
330
+ if (moveEvent.pointerId !== pointerId || space <= 0)
331
+ return;
332
+ const position = horizontal ? moveEvent.clientX : moveEvent.clientY;
333
+ const delta = (((position - origin) * direction) / space) * 100;
334
+ latest = resize(target.base, target.limits, target.pivot, delta);
335
+ commit(target.ids, latest, null);
336
+ };
337
+ const end = (endEvent) => {
338
+ if (endEvent.pointerId !== pointerId)
339
+ return;
340
+ handle.removeEventListener("pointermove", move);
341
+ handle.removeEventListener("pointerup", end);
342
+ handle.removeEventListener("pointercancel", end);
343
+ handle.releasePointerCapture?.(pointerId);
344
+ root.style.cursor = previousCursor;
345
+ root.style.userSelect = previousSelect;
346
+ setDragging(null);
347
+ interacted.current = true;
348
+ commit(target.ids, latest, { isUserInteraction: true });
349
+ };
350
+ handle.addEventListener("pointermove", move);
351
+ handle.addEventListener("pointerup", end);
352
+ handle.addEventListener("pointercancel", end);
353
+ }, [pivotFor, horizontal, available, reversed, commit]);
354
+ const resizeWithKey = useCallback((separatorId, key) => {
355
+ const target = pivotFor(separatorId);
356
+ if (!target)
357
+ return false;
358
+ const { base, limits, pivot } = target;
359
+ const forward = reversed() ? -KEYBOARD_STEP : KEYBOARD_STEP;
360
+ let delta;
361
+ if (key === (horizontal ? "ArrowRight" : "ArrowDown"))
362
+ delta = forward;
363
+ else if (key === (horizontal ? "ArrowLeft" : "ArrowUp"))
364
+ delta = -forward;
365
+ else if (key === "Home")
366
+ delta = limits[pivot].min - base[pivot];
367
+ else if (key === "End")
368
+ delta = limits[pivot].max - base[pivot];
369
+ else
370
+ return false;
371
+ interacted.current = true;
372
+ commit(target.ids, resize(base, limits, pivot, delta), {
373
+ isUserInteraction: true,
374
+ });
375
+ return true;
376
+ }, [pivotFor, horizontal, reversed, commit]);
377
+ const context = useMemo(() => ({
378
+ orientation,
379
+ layout,
380
+ autoSize,
381
+ bounds,
382
+ links,
383
+ dragging,
384
+ registerPanel,
385
+ registerSeparator,
386
+ startDrag,
387
+ resizeWithKey,
388
+ }), [
389
+ orientation,
390
+ layout,
391
+ autoSize,
392
+ bounds,
393
+ links,
394
+ dragging,
395
+ registerPanel,
396
+ registerSeparator,
397
+ startDrag,
398
+ resizeWithKey,
399
+ ]);
400
+ return (_jsx(GroupContext, { value: context, children: _jsx("div", { "data-slot": "resizable-panel-group", "data-orientation": orientation, "data-dragging": dragging ? "" : undefined, className: cn("sprout-resizable-group", className), ref: (node) => {
401
+ element.current = node;
402
+ assignRef(ref, node);
403
+ }, ...props, children: children }) }));
404
+ }
405
+ /** One resizable region. It must be a direct child of `ResizablePanelGroup`. */
406
+ export function ResizablePanel({ id: idProp, defaultSize, minSize, maxSize, className, style, ref, ...props }) {
407
+ const group = useGroup("ResizablePanel");
408
+ const generated = useId();
409
+ const id = idProp ?? generated;
410
+ const element = useRef(null);
411
+ const { registerPanel } = group;
412
+ useIsomorphicLayoutEffect(() => {
413
+ if (!element.current)
414
+ return;
415
+ return registerPanel({
416
+ id,
417
+ element: element.current,
418
+ defaultSize,
419
+ minSize,
420
+ maxSize,
421
+ });
422
+ }, [registerPanel, id, defaultSize, minSize, maxSize]);
423
+ const size = group.layout[id] ?? staticPercent(defaultSize) ?? group.autoSize;
424
+ const flex = { flexGrow: size, flexShrink: 1, flexBasis: 0 };
425
+ return (_jsx("div", { "data-slot": "resizable-panel", "data-panel-id": id, id: id, className: cn("sprout-resizable-panel", className), ref: (node) => {
426
+ element.current = node;
427
+ assignRef(ref, node);
428
+ }, ...props, style: { ...style, ...flex } }));
429
+ }
430
+ /**
431
+ * The divider between two panels. Drag it, or focus it and use the arrow keys.
432
+ * Home and End move it to the first panel's minimum and maximum size.
433
+ */
434
+ export function ResizableHandle({ withHandle = false, className, children, onPointerDown, onKeyDown, ref, ...props }) {
435
+ const group = useGroup("ResizableHandle");
436
+ const id = useId();
437
+ const element = useRef(null);
438
+ const { registerSeparator } = group;
439
+ useIsomorphicLayoutEffect(() => {
440
+ if (!element.current)
441
+ return;
442
+ return registerSeparator(id, element.current);
443
+ }, [registerSeparator, id]);
444
+ const link = group.links[id];
445
+ const value = link ? group.layout[link.before] : undefined;
446
+ let limits;
447
+ if (link && group.bounds[link.before]) {
448
+ const own = group.bounds[link.before];
449
+ const others = Object.entries(group.bounds).filter(([key]) => key !== link.before);
450
+ limits = {
451
+ min: Math.max(own.min, 100 - total(others.map(([, bound]) => bound.max))),
452
+ max: Math.min(own.max, 100 - total(others.map(([, bound]) => bound.min))),
453
+ };
454
+ }
455
+ const lineOrientation = group.orientation === "horizontal" ? "vertical" : "horizontal";
456
+ return (_jsxs("div", { role: "separator", tabIndex: 0, "aria-orientation": lineOrientation, "aria-controls": link?.before, "aria-valuenow": value === undefined ? undefined : Math.round(value), "aria-valuemin": limits ? Math.round(limits.min) : 0, "aria-valuemax": limits ? Math.round(limits.max) : 100, "data-slot": "resizable-handle", "data-orientation": lineOrientation, "data-with-handle": withHandle ? "" : undefined, "data-dragging": group.dragging === id ? "" : undefined, className: cn("sprout-resizable-handle", className), ref: (node) => {
457
+ element.current = node;
458
+ assignRef(ref, node);
459
+ }, onPointerDown: (event) => {
460
+ onPointerDown?.(event);
461
+ if (event.defaultPrevented || event.button !== 0)
462
+ return;
463
+ group.startDrag(id, event);
464
+ }, onKeyDown: (event) => {
465
+ onKeyDown?.(event);
466
+ if (event.defaultPrevented)
467
+ return;
468
+ if (group.resizeWithKey(id, event.key))
469
+ event.preventDefault();
470
+ }, ...props, children: [withHandle && (_jsx("span", { "aria-hidden": "true", "data-slot": "resizable-handle-grip", className: "sprout-resizable-grip" })), children] }));
471
+ }
472
+ const storageListeners = new Set();
473
+ function browserStorage() {
474
+ try {
475
+ return typeof window === "undefined" ? undefined : window.localStorage;
476
+ }
477
+ catch {
478
+ return undefined;
479
+ }
480
+ }
481
+ /**
482
+ * Remembers a group's layout between visits. Spread the result onto
483
+ * `ResizablePanelGroup` as `defaultLayout` and `onLayoutChanged`.
484
+ */
485
+ export function useResizableLayout({ id, panelIds, onlySaveAfterUserInteractions = false, storage, }) {
486
+ const key = `sprout-resizable:${id}${panelIds ? `:${panelIds.join(",")}` : ""}`;
487
+ const subscribe = useCallback((listener) => {
488
+ storageListeners.add(listener);
489
+ window.addEventListener("storage", listener);
490
+ return () => {
491
+ storageListeners.delete(listener);
492
+ window.removeEventListener("storage", listener);
493
+ };
494
+ }, []);
495
+ const raw = useSyncExternalStore(subscribe, () => {
496
+ try {
497
+ return (storage ?? browserStorage())?.getItem(key) ?? null;
498
+ }
499
+ catch {
500
+ return null;
501
+ }
502
+ }, () => null);
503
+ const defaultLayout = useMemo(() => {
504
+ if (!raw)
505
+ return undefined;
506
+ try {
507
+ const parsed = JSON.parse(raw);
508
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
509
+ return undefined;
510
+ const entries = Object.entries(parsed);
511
+ return entries.every(([, size]) => typeof size === "number" && Number.isFinite(size))
512
+ ? Object.fromEntries(entries)
513
+ : undefined;
514
+ }
515
+ catch {
516
+ return undefined;
517
+ }
518
+ }, [raw]);
519
+ const onLayoutChanged = useCallback((layout, details) => {
520
+ if (onlySaveAfterUserInteractions && !details.isUserInteraction)
521
+ return;
522
+ const value = JSON.stringify(layout);
523
+ try {
524
+ const target = storage ?? browserStorage();
525
+ if (!target || target.getItem(key) === value)
526
+ return;
527
+ target.setItem(key, value);
528
+ }
529
+ catch {
530
+ return;
531
+ }
532
+ for (const listener of storageListeners)
533
+ listener();
534
+ }, [key, onlySaveAfterUserInteractions, storage]);
535
+ return { defaultLayout, onLayoutChanged };
536
+ }
@@ -0,0 +1,58 @@
1
+ import type { ComponentProps, ReactNode } from "react";
2
+ import { type Brand } from "./brand-icon.js";
3
+ export declare const resultCardKindNames: readonly ["File", "Plan", "Workflow", "Action", "Calendar", "Table", "Activity", "Agent", "Skill"];
4
+ export declare const resultCardStateNames: readonly ["default", "loading", "running", "disabled", "error", "success"];
5
+ export declare const resultAppActionStateNames: readonly ["completed", "pending", "running", "failed", "unknown", "blocked", "cancelled", "skipped"];
6
+ export type ResultCardKind = (typeof resultCardKindNames)[number];
7
+ export type ResultCardState = (typeof resultCardStateNames)[number];
8
+ export type ResultAppActionState = (typeof resultAppActionStateNames)[number];
9
+ export type ResultAppAction = {
10
+ label: string;
11
+ state: ResultAppActionState;
12
+ };
13
+ /** One app that took part in a result. `brand: null` shows a generic plug. */
14
+ export type ResultApp = {
15
+ brand: Brand | null;
16
+ name: string;
17
+ actions: readonly ResultAppAction[];
18
+ };
19
+ export type ResultCardAction = {
20
+ label: string;
21
+ onClick: () => void;
22
+ disabled?: boolean | undefined;
23
+ };
24
+ export interface ResultCardProps extends Omit<ComponentProps<"section">, "title" | "children"> {
25
+ title: string;
26
+ kind: ResultCardKind;
27
+ /** Apps used. Repeated brands merge; two tiles show, the rest fold into “+N”. */
28
+ apps: readonly ResultApp[];
29
+ status: string;
30
+ /**
31
+ * `loading` and `running` show a spinner and set `aria-busy`; `loading`
32
+ * also disables the action. `disabled` disables the action and app tiles.
33
+ * `success` and `error` tint the status and add a check or warning icon.
34
+ */
35
+ state?: ResultCardState | undefined;
36
+ /** One outline button, beside the text from 28rem wide and below it under. */
37
+ action?: ResultCardAction | undefined;
38
+ /** Shown in a tile when no app is listed. */
39
+ icon?: ReactNode;
40
+ /** Extra content below the header, such as progress or controls. */
41
+ children?: ReactNode;
42
+ }
43
+ /**
44
+ * A compact card for something an agent made or did: a file, plan, workflow,
45
+ * action, calendar read, or table result. It names the apps used, shows a
46
+ * live status, and offers one action such as “Open in Canvas”.
47
+ */
48
+ export declare function ResultCard({ title, kind, apps, status, state, action, icon, children, className, ...props }: ResultCardProps): import("react").JSX.Element;
49
+ export interface ResultAppActionsProps extends Omit<ComponentProps<"div">, "children"> {
50
+ apps: readonly ResultApp[];
51
+ /** Show each app’s icon and name above its actions. Defaults to `true`. */
52
+ showAppNames?: boolean | undefined;
53
+ }
54
+ /**
55
+ * The actions each app took, grouped by app. A check marks completed
56
+ * actions; every other state is spelled out, such as “Unconfirmed:”.
57
+ */
58
+ export declare function ResultAppActions({ apps, showAppNames, className, ...props }: ResultAppActionsProps): import("react").JSX.Element;
@@ -0,0 +1,88 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Check } from "reicon-react/icons/Check";
4
+ import { Danger } from "reicon-react/icons/Danger";
5
+ import { Plug } from "reicon-react/icons/Plug";
6
+ import { cn } from "../internal/classes.js";
7
+ import { BrandIcon, BrandTile } from "./brand-icon.js";
8
+ import { Button } from "./button.js";
9
+ import { Popover, PopoverContent, PopoverTitle, PopoverTrigger, } from "./popover.js";
10
+ import { Spinner } from "./spinner.js";
11
+ export const resultCardKindNames = [
12
+ "File",
13
+ "Plan",
14
+ "Workflow",
15
+ "Action",
16
+ "Calendar",
17
+ "Table",
18
+ "Activity",
19
+ "Agent",
20
+ "Skill",
21
+ ];
22
+ export const resultCardStateNames = [
23
+ "default",
24
+ "loading",
25
+ "running",
26
+ "disabled",
27
+ "error",
28
+ "success",
29
+ ];
30
+ export const resultAppActionStateNames = [
31
+ "completed",
32
+ "pending",
33
+ "running",
34
+ "failed",
35
+ "unknown",
36
+ "blocked",
37
+ "cancelled",
38
+ "skipped",
39
+ ];
40
+ const actionPrefixes = {
41
+ failed: "Failed:",
42
+ unknown: "Unconfirmed:",
43
+ pending: "Pending:",
44
+ running: "In progress:",
45
+ blocked: "Blocked:",
46
+ cancelled: "Cancelled:",
47
+ skipped: "Skipped:",
48
+ };
49
+ function mergeApps(apps) {
50
+ const byKey = new Map();
51
+ for (const app of apps) {
52
+ const key = app.brand ?? app.name;
53
+ const existing = byKey.get(key);
54
+ byKey.set(key, existing
55
+ ? { ...existing, actions: [...existing.actions, ...app.actions] }
56
+ : app);
57
+ }
58
+ return [...byKey.values()];
59
+ }
60
+ /**
61
+ * A compact card for something an agent made or did: a file, plan, workflow,
62
+ * action, calendar read, or table result. It names the apps used, shows a
63
+ * live status, and offers one action such as “Open in Canvas”.
64
+ */
65
+ export function ResultCard({ title, kind, apps, status, state = "default", action, icon, children, className, ...props }) {
66
+ const unique = mergeApps(apps);
67
+ const visible = unique.slice(0, 2);
68
+ const hidden = unique.slice(2);
69
+ const busy = state === "loading" || state === "running";
70
+ const disabled = state === "disabled";
71
+ return (_jsxs("section", { "data-slot": "result-card", "data-state": state, "data-kind": kind, "aria-label": `${kind}: ${title}`, "aria-busy": busy, ...props, className: cn("sprout-result-card", className), children: [_jsxs("div", { className: "sprout-result-card-header", children: [visible.length > 0 ? (_jsxs("div", { role: "group", "aria-label": "Apps used", className: "sprout-result-card-apps", children: [visible.map((app) => (_jsx("span", { "data-result-app": app.brand ?? "connected-app", className: "sprout-result-card-app", children: _jsx(AppActionsPopover, { apps: [app], disabled: disabled }) }, app.brand ?? app.name))), hidden.length > 0 && (_jsx("span", { "data-result-overflow": hidden.length, className: "sprout-result-card-app", children: _jsx(AppActionsPopover, { apps: hidden, overflow: true, disabled: disabled }) }))] })) : (icon && (_jsx(BrandTile, { "aria-hidden": "true", className: "sprout-result-card-icon", children: icon }))), _jsxs("div", { className: "sprout-result-card-main", children: [_jsxs("div", { className: "sprout-result-card-text", children: [_jsx("h3", { className: "sprout-result-card-title", children: title }), _jsxs("div", { className: "sprout-result-card-meta", children: [_jsx("span", { children: kind }), _jsx("span", { "aria-hidden": "true", children: "\u00B7" }), _jsxs("span", { role: "status", "data-tone": state === "success" || state === "error" ? state : undefined, className: "sprout-result-card-status", children: [busy ? (_jsx(Spinner, { "aria-hidden": "true" })) : state === "success" ? (_jsx(Check, { size: 14, "aria-hidden": "true" })) : state === "error" ? (_jsx(Danger, { size: 14, "aria-hidden": "true" })) : null, status] })] })] }), action && (_jsx("div", { className: "sprout-result-card-action", children: _jsx(Button, { variant: "outline", size: "result-card", disabled: state === "loading" || disabled || action.disabled === true, onClick: action.onClick, children: action.label }) }))] })] }), children && _jsx("div", { className: "sprout-result-card-body", children: children })] }));
72
+ }
73
+ function AppActionsPopover({ apps, overflow = false, disabled, }) {
74
+ const first = apps[0];
75
+ if (!first)
76
+ return null;
77
+ const title = overflow
78
+ ? `${apps.length} more ${apps.length === 1 ? "app" : "apps"}`
79
+ : `${first.name} actions`;
80
+ return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { openOnHover: true, delay: 800, closeDelay: 200, render: _jsx(BrandTile, { size: "action", render: _jsx(Button, { variant: "ghost", size: "icon", disabled: disabled }) }), "aria-label": title, children: overflow ? (_jsxs("span", { className: "sprout-result-card-overflow", children: ["+", apps.length] })) : first.brand ? (_jsx(BrandIcon, { brand: first.brand, size: "fill" })) : (_jsx(Plug, { size: 18, "aria-hidden": "true" })) }), _jsxs(PopoverContent, { size: "app-actions", align: "start", sideOffset: 8, initialFocus: (interaction) => interaction !== "mouse", children: [_jsx(PopoverTitle, { children: title }), _jsx(ResultAppActions, { apps: apps, showAppNames: overflow })] })] }));
81
+ }
82
+ /**
83
+ * The actions each app took, grouped by app. A check marks completed
84
+ * actions; every other state is spelled out, such as “Unconfirmed:”.
85
+ */
86
+ export function ResultAppActions({ apps, showAppNames = true, className, ...props }) {
87
+ return (_jsx("div", { "data-slot": "result-app-actions", ...props, className: cn("sprout-result-app-actions", className), children: apps.map((app) => (_jsxs("section", { "aria-label": app.name, className: "sprout-result-app", children: [showAppNames && (_jsxs("div", { className: "sprout-result-app-name", children: [app.brand ? (_jsx(BrandIcon, { brand: app.brand, size: "glyph" })) : (_jsx(Plug, { size: 20, "aria-hidden": "true" })), _jsx("h4", { children: app.name })] })), app.actions.length === 0 ? (_jsx("p", { className: "sprout-result-app-empty", children: "No action details were recorded." })) : (_jsx("ul", { className: "sprout-result-app-list", children: app.actions.map((action, index) => (_jsxs("li", { "data-state": action.state, className: "sprout-result-app-action", children: [action.state === "completed" ? (_jsxs("span", { className: "sprout-result-app-check", children: [_jsx(Check, { size: 16, "aria-hidden": "true" }), _jsx("span", { className: "sprout-result-sr-only", children: "Completed:" })] })) : (_jsx("span", { className: "sprout-result-app-prefix", children: actionPrefixes[action.state] })), _jsx("span", { className: "sprout-result-app-label", children: action.label })] }, `${action.label}:${index}`))) }))] }, app.brand ?? app.name))) }));
88
+ }