@tulipfarm/sprout 0.0.0-stage → 0.1.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 (306) 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 +78 -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-plan.d.ts +21 -0
  38. package/dist/components/chat-plan.js +35 -0
  39. package/dist/components/chat-sources.d.ts +64 -0
  40. package/dist/components/chat-sources.js +66 -0
  41. package/dist/components/checkbox.d.ts +16 -0
  42. package/dist/components/checkbox.js +17 -0
  43. package/dist/components/choice-list.d.ts +53 -0
  44. package/dist/components/choice-list.js +36 -0
  45. package/dist/components/code-block.d.ts +35 -0
  46. package/dist/components/code-block.js +95 -0
  47. package/dist/components/collapsible.d.ts +24 -0
  48. package/dist/components/collapsible.js +27 -0
  49. package/dist/components/combobox.d.ts +95 -0
  50. package/dist/components/combobox.js +106 -0
  51. package/dist/components/command.d.ts +94 -0
  52. package/dist/components/command.js +414 -0
  53. package/dist/components/context-menu.d.ts +57 -0
  54. package/dist/components/context-menu.js +60 -0
  55. package/dist/components/dialog.d.ts +60 -0
  56. package/dist/components/dialog.js +79 -0
  57. package/dist/components/direction.d.ts +6 -0
  58. package/dist/components/direction.js +6 -0
  59. package/dist/components/dropdown-menu.d.ts +62 -0
  60. package/dist/components/dropdown-menu.js +59 -0
  61. package/dist/components/field.d.ts +117 -0
  62. package/dist/components/field.js +150 -0
  63. package/dist/components/file-viewer.d.ts +120 -0
  64. package/dist/components/file-viewer.js +373 -0
  65. package/dist/components/form.d.ts +87 -0
  66. package/dist/components/form.js +55 -0
  67. package/dist/components/glass-banner.d.ts +27 -0
  68. package/dist/components/glass-banner.js +116 -0
  69. package/dist/components/gradient-banner.d.ts +37 -0
  70. package/dist/components/gradient-banner.js +43 -0
  71. package/dist/components/hover-card.d.ts +27 -0
  72. package/dist/components/hover-card.js +26 -0
  73. package/dist/components/input-group.d.ts +68 -0
  74. package/dist/components/input-group.js +88 -0
  75. package/dist/components/input-otp.d.ts +38 -0
  76. package/dist/components/input-otp.js +43 -0
  77. package/dist/components/input.d.ts +16 -0
  78. package/dist/components/input.js +18 -0
  79. package/dist/components/kbd.d.ts +12 -0
  80. package/dist/components/kbd.js +11 -0
  81. package/dist/components/label.d.ts +8 -0
  82. package/dist/components/label.js +10 -0
  83. package/dist/components/mention-list.d.ts +54 -0
  84. package/dist/components/mention-list.js +31 -0
  85. package/dist/components/menubar.d.ts +64 -0
  86. package/dist/components/menubar.js +68 -0
  87. package/dist/components/message-actions.d.ts +34 -0
  88. package/dist/components/message-actions.js +68 -0
  89. package/dist/components/message-response.d.ts +20 -0
  90. package/dist/components/message-response.js +46 -0
  91. package/dist/components/message-scroller.d.ts +71 -0
  92. package/dist/components/message-scroller.js +217 -0
  93. package/dist/components/message.d.ts +49 -0
  94. package/dist/components/message.js +59 -0
  95. package/dist/components/navigation-menu.d.ts +45 -0
  96. package/dist/components/navigation-menu.js +54 -0
  97. package/dist/components/notice-banner.d.ts +14 -0
  98. package/dist/components/notice-banner.js +11 -0
  99. package/dist/components/page-content.d.ts +7 -0
  100. package/dist/components/page-content.js +9 -0
  101. package/dist/components/page-header.d.ts +37 -0
  102. package/dist/components/page-header.js +53 -0
  103. package/dist/components/pagination.d.ts +29 -0
  104. package/dist/components/pagination.js +29 -0
  105. package/dist/components/plan-outline.d.ts +60 -0
  106. package/dist/components/plan-outline.js +108 -0
  107. package/dist/components/popover.d.ts +34 -0
  108. package/dist/components/popover.js +35 -0
  109. package/dist/components/progress.d.ts +27 -0
  110. package/dist/components/progress.js +35 -0
  111. package/dist/components/prompt-list.d.ts +23 -0
  112. package/dist/components/prompt-list.js +11 -0
  113. package/dist/components/questionnaire.d.ts +148 -0
  114. package/dist/components/questionnaire.js +1001 -0
  115. package/dist/components/radio-group.d.ts +16 -0
  116. package/dist/components/radio-group.js +21 -0
  117. package/dist/components/reasoning-effort.d.ts +24 -0
  118. package/dist/components/reasoning-effort.js +269 -0
  119. package/dist/components/resizable.d.ts +67 -0
  120. package/dist/components/resizable.js +536 -0
  121. package/dist/components/result-card.d.ts +58 -0
  122. package/dist/components/result-card.js +88 -0
  123. package/dist/components/review-card.d.ts +62 -0
  124. package/dist/components/review-card.js +49 -0
  125. package/dist/components/rich-text-editor.d.ts +51 -0
  126. package/dist/components/rich-text-editor.js +248 -0
  127. package/dist/components/schema-diagram.d.ts +52 -0
  128. package/dist/components/schema-diagram.js +226 -0
  129. package/dist/components/scroll-area.d.ts +22 -0
  130. package/dist/components/scroll-area.js +19 -0
  131. package/dist/components/search-dialog.d.ts +103 -0
  132. package/dist/components/search-dialog.js +64 -0
  133. package/dist/components/search-field.d.ts +17 -0
  134. package/dist/components/search-field.js +27 -0
  135. package/dist/components/select.d.ts +57 -0
  136. package/dist/components/select.js +57 -0
  137. package/dist/components/selection-slot.d.ts +18 -0
  138. package/dist/components/selection-slot.js +53 -0
  139. package/dist/components/separator.d.ts +48 -0
  140. package/dist/components/separator.js +54 -0
  141. package/dist/components/settings-list.d.ts +26 -0
  142. package/dist/components/settings-list.js +17 -0
  143. package/dist/components/sheet.d.ts +70 -0
  144. package/dist/components/sheet.js +77 -0
  145. package/dist/components/sidebar.d.ts +70 -0
  146. package/dist/components/sidebar.js +187 -0
  147. package/dist/components/skeleton.d.ts +31 -0
  148. package/dist/components/skeleton.js +32 -0
  149. package/dist/components/slider.d.ts +17 -0
  150. package/dist/components/slider.js +24 -0
  151. package/dist/components/spinner.d.ts +37 -0
  152. package/dist/components/spinner.js +43 -0
  153. package/dist/components/state-panel-samples.d.ts +25 -0
  154. package/dist/components/state-panel-samples.js +423 -0
  155. package/dist/components/state-panel.d.ts +35 -0
  156. package/dist/components/state-panel.js +29 -0
  157. package/dist/components/state-sample.d.ts +63 -0
  158. package/dist/components/state-sample.js +42 -0
  159. package/dist/components/switch.d.ts +12 -0
  160. package/dist/components/switch.js +14 -0
  161. package/dist/components/table-nuqs.d.ts +16 -0
  162. package/dist/components/table-nuqs.js +59 -0
  163. package/dist/components/table.d.ts +120 -0
  164. package/dist/components/table.js +663 -0
  165. package/dist/components/tabs.d.ts +37 -0
  166. package/dist/components/tabs.js +36 -0
  167. package/dist/components/textarea.d.ts +18 -0
  168. package/dist/components/textarea.js +19 -0
  169. package/dist/components/toast.d.ts +45 -0
  170. package/dist/components/toast.js +83 -0
  171. package/dist/components/toggle-group.d.ts +30 -0
  172. package/dist/components/toggle-group.js +39 -0
  173. package/dist/components/toggle.d.ts +27 -0
  174. package/dist/components/toggle.js +21 -0
  175. package/dist/components/tool-activity.d.ts +79 -0
  176. package/dist/components/tool-activity.js +226 -0
  177. package/dist/components/toolbar-status.d.ts +21 -0
  178. package/dist/components/toolbar-status.js +14 -0
  179. package/dist/components/tooltip.d.ts +31 -0
  180. package/dist/components/tooltip.js +99 -0
  181. package/dist/components/typed-text.d.ts +17 -0
  182. package/dist/components/typed-text.js +56 -0
  183. package/dist/index.d.ts +88 -0
  184. package/dist/index.js +88 -0
  185. package/dist/internal/brand-glyphs.d.ts +342 -0
  186. package/dist/internal/brand-glyphs.js +238 -0
  187. package/dist/internal/classes.d.ts +3 -0
  188. package/dist/internal/classes.js +20 -0
  189. package/dist/internal/fieldset.d.ts +6 -0
  190. package/dist/internal/fieldset.js +38 -0
  191. package/dist/internal/file-kind.d.ts +4 -0
  192. package/dist/internal/file-kind.js +62 -0
  193. package/dist/internal/languages.d.ts +7 -0
  194. package/dist/internal/languages.js +1010 -0
  195. package/dist/internal/message-scroller.d.ts +102 -0
  196. package/dist/internal/message-scroller.js +637 -0
  197. package/dist/internal/pressable.d.ts +2 -0
  198. package/dist/internal/pressable.js +29 -0
  199. package/dist/internal/syntax.d.ts +14 -0
  200. package/dist/internal/syntax.js +137 -0
  201. package/dist/styles/accordion.css +102 -0
  202. package/dist/styles/alert-dialog.css +198 -0
  203. package/dist/styles/alert.css +149 -0
  204. package/dist/styles/aspect-ratio.css +8 -0
  205. package/dist/styles/attachment.css +720 -0
  206. package/dist/styles/avatar.css +186 -0
  207. package/dist/styles/badge.css +178 -0
  208. package/dist/styles/brand-icon.css +104 -0
  209. package/dist/styles/breadcrumb.css +121 -0
  210. package/dist/styles/button-group.css +113 -0
  211. package/dist/styles/button.css +380 -0
  212. package/dist/styles/calendar.css +295 -0
  213. package/dist/styles/canvas.css +327 -0
  214. package/dist/styles/card.css +146 -0
  215. package/dist/styles/carousel.css +107 -0
  216. package/dist/styles/chart.css +227 -0
  217. package/dist/styles/chat-confirmation.css +29 -0
  218. package/dist/styles/chat-plan.css +109 -0
  219. package/dist/styles/chat-sources.css +191 -0
  220. package/dist/styles/checkbox.css +123 -0
  221. package/dist/styles/choice-list.css +343 -0
  222. package/dist/styles/code-block.css +211 -0
  223. package/dist/styles/collapsible.css +72 -0
  224. package/dist/styles/combobox.css +347 -0
  225. package/dist/styles/command.css +233 -0
  226. package/dist/styles/context-menu.css +151 -0
  227. package/dist/styles/dialog.css +237 -0
  228. package/dist/styles/dropdown-menu.css +150 -0
  229. package/dist/styles/field.css +341 -0
  230. package/dist/styles/file-viewer.css +278 -0
  231. package/dist/styles/form.css +155 -0
  232. package/dist/styles/glass-banner.css +308 -0
  233. package/dist/styles/gradient-banner.css +256 -0
  234. package/dist/styles/hover-card.css +95 -0
  235. package/dist/styles/index.css +262 -0
  236. package/dist/styles/input-group.css +290 -0
  237. package/dist/styles/input-otp.css +138 -0
  238. package/dist/styles/input.css +99 -0
  239. package/dist/styles/kbd.css +59 -0
  240. package/dist/styles/label.css +89 -0
  241. package/dist/styles/mention-list.css +166 -0
  242. package/dist/styles/menubar.css +226 -0
  243. package/dist/styles/message-actions.css +83 -0
  244. package/dist/styles/message-response.css +176 -0
  245. package/dist/styles/message-scroller.css +136 -0
  246. package/dist/styles/message.css +310 -0
  247. package/dist/styles/navigation-menu.css +291 -0
  248. package/dist/styles/notice-banner.css +68 -0
  249. package/dist/styles/page-content.css +21 -0
  250. package/dist/styles/page-header.css +156 -0
  251. package/dist/styles/pagination.css +72 -0
  252. package/dist/styles/plan-outline.css +393 -0
  253. package/dist/styles/popover.css +140 -0
  254. package/dist/styles/progress.css +93 -0
  255. package/dist/styles/prompt-list.css +165 -0
  256. package/dist/styles/questionnaire.css +323 -0
  257. package/dist/styles/radio-group.css +120 -0
  258. package/dist/styles/reasoning-effort.css +157 -0
  259. package/dist/styles/resizable.css +108 -0
  260. package/dist/styles/result-card.css +215 -0
  261. package/dist/styles/review-card.css +235 -0
  262. package/dist/styles/rich-text-editor.css +169 -0
  263. package/dist/styles/schema-diagram.css +419 -0
  264. package/dist/styles/scroll-area.css +77 -0
  265. package/dist/styles/search-dialog.css +363 -0
  266. package/dist/styles/search-field.css +17 -0
  267. package/dist/styles/select.css +285 -0
  268. package/dist/styles/selection-slot.css +31 -0
  269. package/dist/styles/separator.css +206 -0
  270. package/dist/styles/settings-list.css +81 -0
  271. package/dist/styles/sheet.css +361 -0
  272. package/dist/styles/sidebar.css +363 -0
  273. package/dist/styles/skeleton.css +80 -0
  274. package/dist/styles/slider.css +142 -0
  275. package/dist/styles/spinner.css +39 -0
  276. package/dist/styles/state-panel-samples.css +41 -0
  277. package/dist/styles/state-panel.css +182 -0
  278. package/dist/styles/state-sample.css +285 -0
  279. package/dist/styles/switch.css +128 -0
  280. package/dist/styles/table.css +604 -0
  281. package/dist/styles/tabs.css +223 -0
  282. package/dist/styles/textarea.css +78 -0
  283. package/dist/styles/themes/chatgpt.css +125 -0
  284. package/dist/styles/themes/claude.css +125 -0
  285. package/dist/styles/themes/cloudflare.css +125 -0
  286. package/dist/styles/themes/discord.css +125 -0
  287. package/dist/styles/themes/github.css +125 -0
  288. package/dist/styles/themes/grok.css +125 -0
  289. package/dist/styles/themes/linear.css +126 -0
  290. package/dist/styles/themes/neutral.css +125 -0
  291. package/dist/styles/themes/notion.css +125 -0
  292. package/dist/styles/themes/perplexity.css +125 -0
  293. package/dist/styles/themes/raycast.css +125 -0
  294. package/dist/styles/themes/spotify.css +125 -0
  295. package/dist/styles/themes/stripe.css +125 -0
  296. package/dist/styles/themes/supabase.css +125 -0
  297. package/dist/styles/themes/tulipfarm.css +125 -0
  298. package/dist/styles/themes/vercel.css +126 -0
  299. package/dist/styles/toast.css +197 -0
  300. package/dist/styles/toggle-group.css +161 -0
  301. package/dist/styles/toggle.css +119 -0
  302. package/dist/styles/tool-activity.css +396 -0
  303. package/dist/styles/toolbar-status.css +52 -0
  304. package/dist/styles/tooltip.css +122 -0
  305. package/dist/styles/typed-text.css +17 -0
  306. package/package.json +443 -4
@@ -0,0 +1,28 @@
1
+ import type { ComponentProps } from "react";
2
+ import { useRender } from "@base-ui/react/use-render";
3
+ import { type SeparatorLineProps } from "./separator.js";
4
+ export declare const buttonGroupOrientationNames: readonly ["horizontal", "vertical"];
5
+ export type ButtonGroupOrientation = (typeof buttonGroupOrientationNames)[number];
6
+ export interface ButtonGroupVariantOptions {
7
+ orientation?: ButtonGroupOrientation | null;
8
+ className?: string;
9
+ }
10
+ export declare function buttonGroupVariants({ orientation, className, }?: ButtonGroupVariantOptions): string;
11
+ export interface ButtonGroupProps extends ComponentProps<"div"> {
12
+ /** `"vertical"` stacks the controls and joins them top to bottom. */
13
+ orientation?: ButtonGroupOrientation | null;
14
+ }
15
+ /**
16
+ * Joins buttons, text, and separators into one control. Give it an
17
+ * `aria-label` that names what the buttons act on.
18
+ */
19
+ export declare function ButtonGroup({ className, orientation, ...props }: ButtonGroupProps): import("react").JSX.Element;
20
+ export type ButtonGroupTextProps = useRender.ComponentProps<"div">;
21
+ /** A muted, non-interactive cell for a label or value inside a group. */
22
+ export declare function ButtonGroupText({ className, render, ref, ...props }: ButtonGroupTextProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
23
+ export type ButtonGroupSeparatorProps = SeparatorLineProps;
24
+ /**
25
+ * A line between grouped controls. It runs across the group, so it is
26
+ * `"vertical"` (the default) in a row and `"horizontal"` in a column.
27
+ */
28
+ export declare function ButtonGroupSeparator({ className, orientation, tone, ...props }: ButtonGroupSeparatorProps): import("react").JSX.Element;
@@ -0,0 +1,36 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { mergeProps } from "@base-ui/react/merge-props";
4
+ import { useRender } from "@base-ui/react/use-render";
5
+ import { cn } from "../internal/classes.js";
6
+ import { Separator } from "./separator.js";
7
+ export const buttonGroupOrientationNames = ["horizontal", "vertical"];
8
+ export function buttonGroupVariants({ orientation, className, } = {}) {
9
+ return cn("sprout-button-group", `sprout-button-group-${orientation ?? "horizontal"}`, className);
10
+ }
11
+ /**
12
+ * Joins buttons, text, and separators into one control. Give it an
13
+ * `aria-label` that names what the buttons act on.
14
+ */
15
+ export function ButtonGroup({ className, orientation, ...props }) {
16
+ return (_jsx("div", { role: "group", "data-slot": "button-group", "data-orientation": orientation ?? "horizontal", className: buttonGroupVariants({ orientation, className }), ...props }));
17
+ }
18
+ /** A muted, non-interactive cell for a label or value inside a group. */
19
+ export function ButtonGroupText({ className, render, ref, ...props }) {
20
+ return useRender({
21
+ defaultTagName: "div",
22
+ ref,
23
+ render,
24
+ props: mergeProps({
25
+ "data-slot": "button-group-text",
26
+ className: cn("sprout-button-group-text", className),
27
+ }, props),
28
+ });
29
+ }
30
+ /**
31
+ * A line between grouped controls. It runs across the group, so it is
32
+ * `"vertical"` (the default) in a row and `"horizontal"` in a column.
33
+ */
34
+ export function ButtonGroupSeparator({ className, orientation = "vertical", tone = "input", ...props }) {
35
+ return (_jsx(Separator, { "data-slot": "button-group-separator", orientation: orientation, tone: tone, className: (state) => cn("sprout-button-group-separator", typeof className === "function" ? className(state) : className), ...props }));
36
+ }
@@ -0,0 +1,79 @@
1
+ import type { Button as BaseButton } from "@base-ui/react/button";
2
+ import { type ReactElement } from "react";
3
+ export declare const buttonVariantNames: readonly ["default", "outline", "secondary", "ghost", "destructive", "link", "scope"];
4
+ export declare const buttonSizeNames: readonly ["default", "xs", "sm", "lg", "icon", "icon-xs", "icon-sm", "icon-lg", "back", "onboarding", "bud", "bud-labeled", "chat-row", "task-link", "card-title", "result-card", "calendar-title", "calendar", "calendar-icon", "nav", "column-header", "column-header-end", "table-link", "badge"];
5
+ export type ButtonVariant = (typeof buttonVariantNames)[number];
6
+ export type ButtonSize = (typeof buttonSizeNames)[number];
7
+ export interface ButtonVariantOptions {
8
+ variant?: ButtonVariant | null;
9
+ size?: ButtonSize | null;
10
+ className?: string;
11
+ }
12
+ export declare function buttonVariants({ variant, size, className, }?: ButtonVariantOptions): string;
13
+ export type ButtonBlockerFix =
14
+ /** An element id on this page; scrolled into view and focused (or, when it has no tabindex, its first focusable descendant). */
15
+ {
16
+ kind: "jump";
17
+ label: string;
18
+ target: string;
19
+ }
20
+ /** A link element, such as a router `<Link>`, rendered through Button `render`. */
21
+ | {
22
+ kind: "link";
23
+ label: string;
24
+ link: ReactElement;
25
+ } | {
26
+ kind: "action";
27
+ label: string;
28
+ onClick: () => void;
29
+ };
30
+ export type ButtonState = {
31
+ kind: "ready";
32
+ } | {
33
+ kind: "busy";
34
+ } | {
35
+ kind: "blocked";
36
+ reason: string;
37
+ fix?: ButtonBlockerFix | undefined;
38
+ };
39
+ export interface ButtonProps extends BaseButton.Props {
40
+ variant?: ButtonVariant | null;
41
+ size?: ButtonSize | null;
42
+ /**
43
+ * Wraps the button in `data-slot="button-action"`, which also holds the
44
+ * blocked reason; `className` then styles that wrapper. Busy and blocked
45
+ * stay focusable. Keep passing a state (even `ready`) for the button's
46
+ * lifetime so changing kinds never remounts it.
47
+ */
48
+ state?: ButtonState | undefined;
49
+ /** Replaces the label, with a spinner, while `state` is busy. */
50
+ pendingLabel?: string | undefined;
51
+ }
52
+ export declare function Button({ state, pendingLabel, ...props }: ButtonProps): import("react").JSX.Element;
53
+ export interface ActionGroupItem {
54
+ label: string;
55
+ onClick: () => void;
56
+ variant?: ButtonVariant | undefined;
57
+ }
58
+ export interface ActionGroupProps {
59
+ /** One state for every action, with one shared reason under the row. */
60
+ state: ButtonState;
61
+ actions: readonly ActionGroupItem[];
62
+ className?: string | undefined;
63
+ }
64
+ /** A row of actions that share one busy or blocked state and one reason. */
65
+ export declare function ActionGroup({ state, actions, className }: ActionGroupProps): import("react").JSX.Element;
66
+ export declare function jumpTo(target: string | Element): void;
67
+ /**
68
+ * Busy and blocked actions stay focusable so the reason is reachable.
69
+ * Absent ARIA keys are left out so spreading `button` keeps a caller's own.
70
+ */
71
+ export declare function useActionControl(state: ButtonState): {
72
+ button: {
73
+ "aria-describedby"?: string | undefined;
74
+ "aria-busy"?: true | undefined;
75
+ disabled: boolean;
76
+ focusableWhenDisabled: boolean;
77
+ };
78
+ reason: import("react").JSX.Element | null;
79
+ };
@@ -0,0 +1,93 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useId } from "react";
4
+ import { cn, stateClassName } from "../internal/classes.js";
5
+ import { Pressable } from "../internal/pressable.js";
6
+ import { Spinner } from "./spinner.js";
7
+ export const buttonVariantNames = [
8
+ "default",
9
+ "outline",
10
+ "secondary",
11
+ "ghost",
12
+ "destructive",
13
+ "link",
14
+ "scope",
15
+ ];
16
+ export const buttonSizeNames = [
17
+ "default",
18
+ "xs",
19
+ "sm",
20
+ "lg",
21
+ "icon",
22
+ "icon-xs",
23
+ "icon-sm",
24
+ "icon-lg",
25
+ "back",
26
+ "onboarding",
27
+ "bud",
28
+ "bud-labeled",
29
+ "chat-row",
30
+ "task-link",
31
+ "card-title",
32
+ "result-card",
33
+ "calendar-title",
34
+ "calendar",
35
+ "calendar-icon",
36
+ "nav",
37
+ "column-header",
38
+ "column-header-end",
39
+ "table-link",
40
+ "badge",
41
+ ];
42
+ export function buttonVariants({ variant, size, className, } = {}) {
43
+ return cn("sprout-button", `sprout-button-variant-${variant ?? "default"}`, `sprout-button-size-${size ?? "default"}`, className);
44
+ }
45
+ export function Button({ state, pendingLabel, ...props }) {
46
+ return state ? (_jsx(StatefulButton, { state: state, pendingLabel: pendingLabel, ...props })) : (_jsx(ButtonElement, { ...props }));
47
+ }
48
+ function ButtonElement({ variant, size, className, ...props }) {
49
+ return (_jsx(Pressable, { "data-slot": "button", "data-variant": variant ?? "default", "data-size": size ?? "default", ...props, className: stateClassName(buttonVariants({ variant, size }), className) }));
50
+ }
51
+ function StatefulButton({ state, pendingLabel, className, children, ...props }) {
52
+ const action = useActionControl(state);
53
+ const pending = state.kind === "busy" && pendingLabel;
54
+ return (_jsxs("div", { "data-slot": "button-action", "data-state": state.kind, className: cn("sprout-button-action", className), children: [_jsx(ButtonElement, { ...props, ...action.button, disabled: action.button.disabled || props.disabled, children: pending ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { "aria-hidden": "true" }), pendingLabel] })) : (children) }), action.reason] }));
55
+ }
56
+ /** A row of actions that share one busy or blocked state and one reason. */
57
+ export function ActionGroup({ state, actions, className }) {
58
+ const action = useActionControl(state);
59
+ return (_jsxs("div", { "data-slot": "action-group", "data-state": state.kind, className: cn("sprout-action-group", className), children: [_jsx("div", { className: "sprout-action-group-row", children: actions.map((item) => (_jsx(Button, { variant: item.variant, onClick: item.onClick, ...action.button, children: item.label }, item.label))) }), action.reason] }));
60
+ }
61
+ const focusable = ':is(a[href], button, input, select, textarea, [tabindex]):not([disabled], [tabindex="-1"], [type="hidden"])';
62
+ export function jumpTo(target) {
63
+ const element = typeof target === "string" ? document.getElementById(target) : target;
64
+ if (!element)
65
+ return;
66
+ const control = element.matches(focusable) || element.hasAttribute("tabindex")
67
+ ? element
68
+ : element.querySelector(focusable);
69
+ (control ?? element).scrollIntoView({ block: "center" });
70
+ if (control instanceof HTMLElement)
71
+ control.focus({ preventScroll: true });
72
+ }
73
+ /**
74
+ * Busy and blocked actions stay focusable so the reason is reachable.
75
+ * Absent ARIA keys are left out so spreading `button` keeps a caller's own.
76
+ */
77
+ export function useActionControl(state) {
78
+ const reasonId = useId();
79
+ return {
80
+ button: {
81
+ disabled: state.kind !== "ready",
82
+ focusableWhenDisabled: true,
83
+ ...(state.kind === "busy" && { "aria-busy": true }),
84
+ ...(state.kind === "blocked" && { "aria-describedby": reasonId }),
85
+ },
86
+ reason: state.kind === "blocked" ? (_jsxs("p", { "data-slot": "action-reason", className: "sprout-action-reason", children: [_jsx("span", { id: reasonId, children: state.reason }), state.fix && _jsx(FixButton, { fix: state.fix })] })) : null,
87
+ };
88
+ }
89
+ function FixButton({ fix }) {
90
+ if (fix.kind === "link")
91
+ return (_jsx(Button, { variant: "link", size: "xs", nativeButton: false, render: fix.link, children: fix.label }));
92
+ return (_jsx(Button, { variant: "link", size: "xs", onClick: fix.kind === "jump" ? () => jumpTo(fix.target) : fix.onClick, children: fix.label }));
93
+ }
@@ -0,0 +1,27 @@
1
+ import { type ComponentProps } from "react";
2
+ import { DayPicker, type DayButtonProps } from "react-day-picker";
3
+ import { type ButtonVariant } from "./button.js";
4
+ export type { DateRange, Matcher } from "react-day-picker";
5
+ export declare const calendarCaptionLayoutNames: readonly ["label", "dropdown", "dropdown-months", "dropdown-years"];
6
+ export type CalendarCaptionLayout = (typeof calendarCaptionLayoutNames)[number];
7
+ export type CalendarProps = ComponentProps<typeof DayPicker> & {
8
+ /** The Button variant for the previous and next month buttons. Defaults to `ghost`. */
9
+ buttonVariant?: ButtonVariant;
10
+ };
11
+ type DayPickerLocale = ComponentProps<typeof DayPicker>["locale"];
12
+ /**
13
+ * A month grid for picking one date, several dates, or a range. It takes every
14
+ * React DayPicker prop, including `mode`, `selected`, `onSelect`,
15
+ * `defaultMonth`, `disabled`, `weekStartsOn`, `showWeekNumber`, and
16
+ * `numberOfMonths`.
17
+ */
18
+ export declare function Calendar({ className, classNames, showOutsideDays, captionLayout, buttonVariant, locale, formatters, components, ...props }: CalendarProps): import("react").JSX.Element;
19
+ export type CalendarDayButtonProps = DayButtonProps & {
20
+ locale?: DayPickerLocale;
21
+ };
22
+ /**
23
+ * The button inside each day cell. Use it in `components.DayButton` to add
24
+ * content under the date, such as an event count in a `span`. `locale`
25
+ * defaults to the calendar's locale.
26
+ */
27
+ export declare function CalendarDayButton({ className, day, modifiers, locale, ...props }: CalendarDayButtonProps): import("react").JSX.Element;
@@ -0,0 +1,107 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, } from "react";
4
+ import { DayPicker, getDefaultClassNames, useDayPicker, } from "react-day-picker";
5
+ import { ChevronDown } from "reicon-react/icons/ChevronDown";
6
+ import { ChevronLeft } from "reicon-react/icons/ChevronLeft";
7
+ import { ChevronRight } from "reicon-react/icons/ChevronRight";
8
+ import { cn } from "../internal/classes.js";
9
+ import { Button, buttonVariants } from "./button.js";
10
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "./select.js";
11
+ export const calendarCaptionLayoutNames = [
12
+ "label",
13
+ "dropdown",
14
+ "dropdown-months",
15
+ "dropdown-years",
16
+ ];
17
+ function CalendarRoot({ className, rootRef, ...props }) {
18
+ return (_jsx("div", { "data-slot": "calendar", ref: rootRef, className: className, ...props }));
19
+ }
20
+ function CalendarChevron({ className, orientation, size: _size, disabled: _disabled, ...props }) {
21
+ const Icon = orientation === "left"
22
+ ? ChevronLeft
23
+ : orientation === "right"
24
+ ? ChevronRight
25
+ : ChevronDown;
26
+ return (_jsx(Icon, { "aria-hidden": "true", className: cn("sprout-calendar-chevron", className), ...props }));
27
+ }
28
+ function CalendarWeekNumber({ children, week: _week, ...props }) {
29
+ return (_jsx("th", { ...props, children: _jsx("div", { className: "sprout-calendar-week-number-cell", children: children }) }));
30
+ }
31
+ /** Month and year pickers use a Base UI select, never a native `<select>`. */
32
+ function CalendarDropdown({ options = [], value, onChange, disabled, className, style, "aria-label": label, }) {
33
+ const items = options.map((option) => ({
34
+ value: option.value,
35
+ label: option.label,
36
+ }));
37
+ return (_jsxs(Select, { items: items, value: Number(value), disabled: disabled, onValueChange: (next) => {
38
+ if (next === null)
39
+ return;
40
+ onChange?.({
41
+ target: { value: String(next) },
42
+ });
43
+ }, children: [_jsx(SelectTrigger, { size: "sm", "aria-label": label, style: style, className: cn("sprout-calendar-dropdown", className), children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { className: "sprout-calendar-dropdown-content", children: options.map((option) => (_jsx(SelectItem, { value: option.value, disabled: option.disabled, children: option.label }, option.value))) })] }));
44
+ }
45
+ /**
46
+ * A month grid for picking one date, several dates, or a range. It takes every
47
+ * React DayPicker prop, including `mode`, `selected`, `onSelect`,
48
+ * `defaultMonth`, `disabled`, `weekStartsOn`, `showWeekNumber`, and
49
+ * `numberOfMonths`.
50
+ */
51
+ export function Calendar({ className, classNames, showOutsideDays = true, captionLayout = "label", buttonVariant = "ghost", locale, formatters, components, ...props }) {
52
+ const defaults = getDefaultClassNames();
53
+ const navButton = cn(buttonVariants({ variant: buttonVariant, size: "icon-sm" }), "sprout-calendar-nav-button");
54
+ return (_jsx(DayPicker, { showOutsideDays: showOutsideDays, className: cn("sprout-calendar", className), "data-button-variant": buttonVariant, "data-caption-layout": captionLayout, captionLayout: captionLayout, locale: locale, formatters: {
55
+ formatMonthDropdown: (date) => date.toLocaleString(locale?.code, { month: "short" }),
56
+ ...formatters,
57
+ }, classNames: {
58
+ root: cn("sprout-calendar-root", defaults.root),
59
+ months: cn("sprout-calendar-months", defaults.months),
60
+ month: cn("sprout-calendar-month", defaults.month),
61
+ nav: cn("sprout-calendar-nav", defaults.nav),
62
+ button_previous: cn(navButton, defaults.button_previous),
63
+ button_next: cn(navButton, defaults.button_next),
64
+ month_caption: cn("sprout-calendar-caption", defaults.month_caption),
65
+ dropdowns: cn("sprout-calendar-dropdowns", defaults.dropdowns),
66
+ dropdown_root: cn("sprout-calendar-dropdown-root", defaults.dropdown_root),
67
+ dropdown: cn(defaults.dropdown),
68
+ caption_label: cn("sprout-calendar-caption-label", defaults.caption_label),
69
+ month_grid: cn("sprout-calendar-grid", defaults.month_grid),
70
+ weekdays: cn("sprout-calendar-weekdays", defaults.weekdays),
71
+ weekday: cn("sprout-calendar-weekday", defaults.weekday),
72
+ week: cn("sprout-calendar-week", defaults.week),
73
+ week_number_header: cn("sprout-calendar-week-number-header", defaults.week_number_header),
74
+ week_number: cn("sprout-calendar-week-number", defaults.week_number),
75
+ day: cn("sprout-calendar-day", defaults.day),
76
+ range_start: cn("sprout-calendar-range-start", defaults.range_start),
77
+ range_middle: cn("sprout-calendar-range-middle", defaults.range_middle),
78
+ range_end: cn("sprout-calendar-range-end", defaults.range_end),
79
+ today: cn("sprout-calendar-today", defaults.today),
80
+ outside: cn("sprout-calendar-outside", defaults.outside),
81
+ disabled: cn("sprout-calendar-disabled", defaults.disabled),
82
+ hidden: cn("sprout-calendar-hidden", defaults.hidden),
83
+ ...classNames,
84
+ }, components: {
85
+ Root: CalendarRoot,
86
+ Chevron: CalendarChevron,
87
+ Dropdown: CalendarDropdown,
88
+ DayButton: CalendarDayButton,
89
+ WeekNumber: CalendarWeekNumber,
90
+ ...components,
91
+ }, ...props }));
92
+ }
93
+ /**
94
+ * The button inside each day cell. Use it in `components.DayButton` to add
95
+ * content under the date, such as an event count in a `span`. `locale`
96
+ * defaults to the calendar's locale.
97
+ */
98
+ export function CalendarDayButton({ className, day, modifiers, locale, ...props }) {
99
+ const calendarLocale = useDayPicker().dayPickerProps.locale;
100
+ const ref = useRef(null);
101
+ useEffect(() => {
102
+ if (modifiers.focused)
103
+ ref.current?.focus();
104
+ }, [modifiers.focused]);
105
+ const range = modifiers.range_start || modifiers.range_end || modifiers.range_middle;
106
+ return (_jsx(Button, { ref: ref, variant: "ghost", size: "icon", "data-slot": "calendar-day-button", "data-day": day.date.toLocaleDateString((locale ?? calendarLocale)?.code), "data-selected-single": Boolean(modifiers.selected && !range), "data-range-start": Boolean(modifiers.range_start), "data-range-end": Boolean(modifiers.range_end), "data-range-middle": Boolean(modifiers.range_middle), className: cn("sprout-calendar-day-button", getDefaultClassNames().day_button, className), ...props }));
107
+ }
@@ -0,0 +1,78 @@
1
+ import { type ComponentType, type ReactNode } from "react";
2
+ /** Below this split width (px), the canvas opens as a full-screen sheet. */
3
+ export declare const canvasSplitMinWidth = 720;
4
+ export interface CanvasTab {
5
+ id: string;
6
+ title: string;
7
+ icon?: ComponentType<{
8
+ size?: number;
9
+ }> | undefined;
10
+ /** The result is still loading. Shows a spinner and reads “loading”. */
11
+ busy?: boolean | undefined;
12
+ }
13
+ export interface ChatSplitProps {
14
+ open: boolean;
15
+ onOpenChange: (open: boolean) => void;
16
+ chat: ReactNode;
17
+ canvas: ReactNode;
18
+ /** Saves the person's split sizes under this key. Use one per screen. */
19
+ layoutId?: string | undefined;
20
+ className?: string | undefined;
21
+ }
22
+ /**
23
+ * Chat beside a resizable canvas. Under 768px viewports, or when the split is
24
+ * narrower than `canvasSplitMinWidth`, the canvas opens as a full-screen
25
+ * sheet. The canvas moves between both without remounting, so drafts,
26
+ * reviews, and scroll survive.
27
+ */
28
+ export declare function ChatSplit({ open, onOpenChange, chat, canvas, layoutId, className, }: ChatSplitProps): import("react").JSX.Element;
29
+ export interface CanvasPanelProps {
30
+ /** Open results. With none, the header reads “Canvas”. */
31
+ tabs: readonly CanvasTab[];
32
+ activeId: string | null;
33
+ onSelect: (id: string) => void;
34
+ onCloseTab: (id: string) => void;
35
+ /** Hides the canvas. Called by Escape, the close button, and “Back to chat”. */
36
+ onClose: () => void;
37
+ children: ReactNode;
38
+ /** A strip under the tabs, such as “A Google action needs your review”. */
39
+ activity?: ReactNode;
40
+ className?: string | undefined;
41
+ }
42
+ /**
43
+ * The canvas surface: a tab per open result, a close button, and the active
44
+ * result below. Arrow keys, Home, and End move between tabs; Delete closes
45
+ * the focused tab; Escape closes the canvas.
46
+ */
47
+ export declare function CanvasPanel({ tabs, activeId, onSelect, onCloseTab, onClose, children, activity, className, }: CanvasPanelProps): import("react").JSX.Element;
48
+ export interface CanvasToggleProps {
49
+ open: boolean;
50
+ /** Open results. Shown as a badge when above zero. */
51
+ count: number;
52
+ onToggle: () => void;
53
+ className?: string | undefined;
54
+ }
55
+ /** A header button that shows or hides the canvas. */
56
+ export declare function CanvasToggle({ open, count, onToggle, className, }: CanvasToggleProps): import("react").JSX.Element;
57
+ export interface CanvasSectionProps {
58
+ title?: string | undefined;
59
+ /** A short line under the title, such as a row count, source, or URL. */
60
+ meta?: string | undefined;
61
+ /** Buttons beside the title. They wrap below it when space runs out. */
62
+ actions?: ReactNode;
63
+ children: ReactNode;
64
+ className?: string | undefined;
65
+ }
66
+ /** Padded content for one result, with an optional title, meta, and actions. */
67
+ export declare function CanvasSection({ title, meta, actions, children, className, }: CanvasSectionProps): import("react").JSX.Element;
68
+ export interface CanvasFrameProps {
69
+ src: string;
70
+ /** Names the frame for assistive tech, such as the page title. */
71
+ title: string;
72
+ className?: string | undefined;
73
+ }
74
+ /**
75
+ * A sandboxed page preview. Scripts and popups run; same-origin access,
76
+ * forms, and top navigation are blocked, and no referrer is sent.
77
+ */
78
+ export declare function CanvasFrame({ src, title, className }: CanvasFrameProps): import("react").JSX.Element;
@@ -0,0 +1,180 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { createContext, useContext, useEffect, useId, useLayoutEffect, useRef, useState, useSyncExternalStore, } from "react";
4
+ import { createPortal } from "react-dom";
5
+ import { ArrowLeft } from "reicon-react/icons/ArrowLeft";
6
+ import { Loader } from "reicon-react/icons/Loader";
7
+ import { SidebarRight } from "reicon-react/icons/SidebarRight";
8
+ import { X } from "reicon-react/icons/X";
9
+ import { cn } from "../internal/classes.js";
10
+ import { Button } from "./button.js";
11
+ import { ResizableHandle, ResizablePanel, ResizablePanelGroup, useResizableLayout, } from "./resizable.js";
12
+ import { Sheet, SheetContent, SheetDescription, SheetTitle } from "./sheet.js";
13
+ /** Below this split width (px), the canvas opens as a full-screen sheet. */
14
+ export const canvasSplitMinWidth = 720;
15
+ const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
16
+ const mobileQuery = "(max-width: 767px)";
17
+ function subscribeMobile(callback) {
18
+ const media = window.matchMedia(mobileQuery);
19
+ media.addEventListener("change", callback);
20
+ return () => media.removeEventListener("change", callback);
21
+ }
22
+ function useViewportMobile() {
23
+ return useSyncExternalStore(subscribeMobile, () => window.matchMedia(mobileQuery).matches, () => false);
24
+ }
25
+ const CanvasNarrowContext = createContext(null);
26
+ /**
27
+ * Chat beside a resizable canvas. Under 768px viewports, or when the split is
28
+ * narrower than `canvasSplitMinWidth`, the canvas opens as a full-screen
29
+ * sheet. The canvas moves between both without remounting, so drafts,
30
+ * reviews, and scroll survive.
31
+ */
32
+ export function ChatSplit({ open, onOpenChange, chat, canvas, layoutId = "chat-canvas", className, }) {
33
+ const viewportMobile = useViewportMobile();
34
+ const container = useRef(null);
35
+ const [availableWidth, setAvailableWidth] = useState(null);
36
+ const mobile = viewportMobile ||
37
+ (availableWidth !== null && availableWidth < canvasSplitMinWidth);
38
+ const split = open && !mobile;
39
+ useIsomorphicLayoutEffect(() => {
40
+ const element = container.current;
41
+ if (!element)
42
+ return;
43
+ const measure = () => setAvailableWidth(element.clientWidth);
44
+ measure();
45
+ const observer = new ResizeObserver(measure);
46
+ observer.observe(element);
47
+ return () => observer.disconnect();
48
+ }, []);
49
+ const layout = useResizableLayout({
50
+ id: layoutId,
51
+ panelIds: split ? ["chat", "canvas"] : ["chat"],
52
+ onlySaveAfterUserInteractions: true,
53
+ });
54
+ const [desktopHost, setDesktopHost] = useState(null);
55
+ const [mobileHost, setMobileHost] = useState(null);
56
+ const [target, setTarget] = useState(null);
57
+ useIsomorphicLayoutEffect(() => {
58
+ const element = document.createElement("div");
59
+ element.className = "sprout-canvas-host";
60
+ setTarget(element);
61
+ }, []);
62
+ useIsomorphicLayoutEffect(() => {
63
+ if (!target || !open)
64
+ return;
65
+ (mobile ? mobileHost : desktopHost)?.append(target);
66
+ return () => target.remove();
67
+ }, [mobile, open, target, mobileHost, desktopHost]);
68
+ return (_jsx(CanvasNarrowContext, { value: mobile, children: _jsxs("div", { ref: container, "data-slot": "chat-split", "data-layout": mobile ? "sheet" : "split", "data-open": open ? "" : undefined, className: cn("sprout-chat-split", className), children: [target && createPortal(canvas, target), _jsxs(ResizablePanelGroup, { orientation: "horizontal", defaultLayout: layout.defaultLayout, onLayoutChanged: layout.onLayoutChanged, children: [_jsx(ResizablePanel, { id: "chat", minSize: "40%", defaultSize: split ? "55%" : "100%", children: _jsx("div", { className: "sprout-chat-split-pane", children: chat }) }), split && (_jsxs(_Fragment, { children: [_jsx(ResizableHandle, { withHandle: true, "aria-label": "Resize canvas" }), _jsx(ResizablePanel, { id: "canvas", minSize: "320px", defaultSize: "45%", children: _jsx("div", { ref: setDesktopHost, className: "sprout-chat-split-pane" }) })] }))] }), _jsx(Sheet, { open: open && mobile, onOpenChange: (next) => onOpenChange(next), children: _jsxs(SheetContent, { side: "right", size: "full", showCloseButton: false, keepMounted: true, finalFocus: !open, className: "sprout-canvas-sheet", children: [_jsxs("div", { className: "sprout-canvas-sr-only", children: [_jsx(SheetTitle, { children: "Canvas" }), _jsx(SheetDescription, { children: "Detailed result. Return to chat to continue the conversation." })] }), _jsx("div", { ref: setMobileHost, className: "sprout-chat-split-pane" })] }) })] }) }));
69
+ }
70
+ /**
71
+ * The canvas surface: a tab per open result, a close button, and the active
72
+ * result below. Arrow keys, Home, and End move between tabs; Delete closes
73
+ * the focused tab; Escape closes the canvas.
74
+ */
75
+ export function CanvasPanel({ tabs, activeId, onSelect, onCloseTab, onClose, children, activity, className, }) {
76
+ const viewportMobile = useViewportMobile();
77
+ const narrow = useContext(CanvasNarrowContext);
78
+ const mobile = narrow ?? viewportMobile;
79
+ const base = useId();
80
+ const list = useRef(null);
81
+ const tabButtons = useRef(new Map());
82
+ const closeButton = useRef(null);
83
+ const heading = useRef(null);
84
+ const refocus = useRef(null);
85
+ const [hovered, setHovered] = useState(null);
86
+ const activeIndex = tabs.findIndex((tab) => tab.id === activeId);
87
+ const tabId = (index) => `${base}-tab-${index}`;
88
+ const bodyId = `${base}-body`;
89
+ useEffect(() => {
90
+ const scroller = list.current;
91
+ const tab = activeId ? tabButtons.current.get(activeId) : null;
92
+ if (!scroller || !tab)
93
+ return;
94
+ const start = tab.offsetLeft;
95
+ const end = start + tab.offsetWidth + 28;
96
+ if (start < scroller.scrollLeft)
97
+ scroller.scrollLeft = start;
98
+ else if (end > scroller.scrollLeft + scroller.clientWidth)
99
+ scroller.scrollLeft = end - scroller.clientWidth;
100
+ }, [activeId, tabs.length]);
101
+ useEffect(() => {
102
+ const index = refocus.current;
103
+ if (index === null)
104
+ return;
105
+ refocus.current = null;
106
+ const next = tabs[Math.min(index, tabs.length - 1)];
107
+ const target = (next && tabButtons.current.get(next.id)) ??
108
+ closeButton.current ??
109
+ heading.current;
110
+ target?.focus();
111
+ }, [tabs]);
112
+ const closeTab = (index) => {
113
+ const tab = tabs[index];
114
+ if (!tab)
115
+ return;
116
+ refocus.current = index;
117
+ onCloseTab(tab.id);
118
+ };
119
+ const onTabKeyDown = (event, index) => {
120
+ if (event.key === "Delete") {
121
+ event.preventDefault();
122
+ closeTab(index);
123
+ return;
124
+ }
125
+ const next = event.key === "ArrowRight"
126
+ ? index + 1
127
+ : event.key === "ArrowLeft"
128
+ ? index - 1
129
+ : event.key === "Home"
130
+ ? 0
131
+ : event.key === "End"
132
+ ? tabs.length - 1
133
+ : null;
134
+ if (next === null || tabs.length === 0)
135
+ return;
136
+ event.preventDefault();
137
+ const target = tabs[(next + tabs.length) % tabs.length];
138
+ if (!target)
139
+ return;
140
+ onSelect(target.id);
141
+ tabButtons.current.get(target.id)?.focus();
142
+ };
143
+ return (_jsxs("section", { "data-slot": "canvas", "data-layout": mobile ? "sheet" : "split", "aria-label": "Canvas", className: cn("sprout-canvas", className), onKeyDown: (event) => {
144
+ if (event.key === "Escape" && !event.defaultPrevented) {
145
+ event.preventDefault();
146
+ onClose();
147
+ }
148
+ }, children: [mobile && (_jsx("div", { className: "sprout-canvas-back", children: _jsxs(Button, { variant: "ghost", size: "sm", onClick: onClose, children: [_jsx(ArrowLeft, { size: 16, "aria-hidden": "true" }), "Back to chat"] }) })), _jsxs("header", { className: "sprout-canvas-header", children: [tabs.length === 0 ? (_jsx("h2", { ref: heading, tabIndex: -1, className: "sprout-canvas-heading", children: "Canvas" })) : (_jsxs("div", { ref: list, className: "sprout-canvas-tabs", children: [_jsx("div", { role: "tablist", "aria-label": "Open in canvas", className: "sprout-canvas-tablist", children: tabs.map((tab, index) => {
149
+ const active = index === activeIndex;
150
+ const Icon = tab.icon;
151
+ return (_jsxs("button", { type: "button", role: "tab", id: tabId(index), ref: (node) => {
152
+ if (node)
153
+ tabButtons.current.set(tab.id, node);
154
+ else
155
+ tabButtons.current.delete(tab.id);
156
+ }, "data-slot": "canvas-tab", "data-active": active ? "" : undefined, "data-busy": tab.busy ? "" : undefined, "data-hover": hovered === tab.id ? "" : undefined, "aria-label": tab.busy ? `${tab.title}, loading` : undefined, "aria-selected": active, "aria-controls": bodyId, tabIndex: active || (activeIndex === -1 && index === 0) ? 0 : -1, style: { order: index * 2 }, onClick: () => onSelect(tab.id), onKeyDown: (event) => onTabKeyDown(event, index), onPointerEnter: () => setHovered(tab.id), onPointerLeave: () => setHovered(null), className: "sprout-canvas-tab", children: [tab.busy ? (_jsx(Loader, { size: 14, "aria-hidden": "true", className: "sprout-canvas-tab-spinner" })) : (Icon && (_jsx("span", { "aria-hidden": "true", className: "sprout-canvas-tab-icon", children: _jsx(Icon, { size: 14 }) }))), _jsx("span", { className: "sprout-canvas-tab-title", children: tab.title })] }, tab.id));
157
+ }) }), tabs.map((tab, index) => {
158
+ const active = index === activeIndex;
159
+ return (_jsx("button", { type: "button", "aria-label": `Close ${tab.title}`, "data-active": active ? "" : undefined, "data-hover": hovered === tab.id ? "" : undefined, tabIndex: active ? 0 : -1, style: { order: index * 2 + 1 }, onClick: () => closeTab(index), onPointerEnter: () => setHovered(tab.id), onPointerLeave: () => setHovered(null), className: "sprout-canvas-tab-close", children: _jsx(X, { size: 12, "aria-hidden": "true" }) }, tab.id));
160
+ })] })), !mobile && (_jsx(Button, { ref: closeButton, variant: "ghost", size: "icon-sm", "aria-label": "Close canvas", title: "Close canvas (Esc)", onClick: onClose, children: _jsx(X, { size: 16, "aria-hidden": "true" }) }))] }), activity, _jsx("div", { id: bodyId, role: tabs.length ? "tabpanel" : undefined, "aria-labelledby": activeIndex === -1 ? undefined : tabId(activeIndex), tabIndex: tabs.length ? 0 : undefined, className: "sprout-canvas-body", children: children })] }));
161
+ }
162
+ /** A header button that shows or hides the canvas. */
163
+ export function CanvasToggle({ open, count, onToggle, className, }) {
164
+ return (_jsxs(Button, { variant: "ghost", size: "sm", "data-slot": "canvas-toggle", "aria-pressed": open, "aria-label": open
165
+ ? "Hide canvas"
166
+ : count
167
+ ? `Show canvas, ${count} open`
168
+ : "Show canvas", title: "Open detailed results without leaving your chat", onClick: onToggle, className: cn("sprout-canvas-toggle", className), children: [_jsx(SidebarRight, { size: 16, "aria-hidden": "true" }), _jsx("span", { className: "sprout-canvas-toggle-label", children: open ? "Hide results" : "View results" }), count > 0 && (_jsx("span", { "aria-hidden": "true", className: "sprout-canvas-toggle-count", children: count }))] }));
169
+ }
170
+ /** Padded content for one result, with an optional title, meta, and actions. */
171
+ export function CanvasSection({ title, meta, actions, children, className, }) {
172
+ return (_jsxs("div", { "data-slot": "canvas-section", className: cn("sprout-canvas-section", className), children: [(title || actions) && (_jsxs("div", { className: "sprout-canvas-section-header", children: [_jsxs("div", { className: "sprout-canvas-section-text", children: [title && _jsx("h3", { className: "sprout-canvas-section-title", children: title }), meta && _jsx("p", { className: "sprout-canvas-section-meta", children: meta })] }), actions && (_jsx("div", { className: "sprout-canvas-section-actions", children: actions }))] })), children] }));
173
+ }
174
+ /**
175
+ * A sandboxed page preview. Scripts and popups run; same-origin access,
176
+ * forms, and top navigation are blocked, and no referrer is sent.
177
+ */
178
+ export function CanvasFrame({ src, title, className }) {
179
+ return (_jsx("div", { "data-slot": "canvas-frame", className: cn("sprout-canvas-frame", className), children: _jsx("iframe", { src: src, title: title, sandbox: "allow-scripts allow-popups", referrerPolicy: "no-referrer", loading: "lazy" }) }));
180
+ }