@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,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useRef, } from "react";
3
+ import { cn } from "../internal/classes.js";
4
+ import { Button } from "./button.js";
5
+ import { Spinner } from "./spinner.js";
6
+ export const mentionStatusToneNames = ["loading", "empty", "error"];
7
+ /**
8
+ * A suggestion panel for `@` and `/` triggers. The text field keeps focus and
9
+ * owns the arrow keys; it points `aria-activedescendant` at the active option.
10
+ */
11
+ export function MentionList({ id, label, status, announcement, children, className, ...props }) {
12
+ return (_jsxs("div", { "data-slot": "mention-list", className: cn("sprout-mention-list", className), ...props, children: [_jsx("ul", { id: id, role: "listbox", "aria-label": label, className: "sprout-mention-list-options", children: children }), status, _jsx("p", { role: "status", className: "sprout-mention-list-announcement", children: announcement })] }));
13
+ }
14
+ export function MentionOption({ id, active, onSelect, media, title, description, className, ...props }) {
15
+ const option = useRef(null);
16
+ useEffect(() => {
17
+ if (active)
18
+ option.current?.scrollIntoView?.({ block: "nearest" });
19
+ }, [active]);
20
+ return (_jsxs("li", { ref: option, id: id, role: "option", "aria-selected": active, "data-slot": "mention-option", "data-active": active ? "" : undefined, className: cn("sprout-mention-option", className), ...props,
21
+ // Keeps focus (and the caret) in the text field.
22
+ onMouseDown: (event) => event.preventDefault(), onClick: onSelect, children: [media != null && media !== false && (_jsx("span", { "data-slot": "mention-option-media", className: "sprout-mention-option-media", children: media })), _jsxs("span", { className: "sprout-mention-option-text", children: [_jsx("span", { className: "sprout-mention-option-title", title: title, children: title }), description && (_jsx("span", { className: "sprout-mention-option-description", children: description }))] })] }));
23
+ }
24
+ /** A labelled run of options inside the same listbox, so arrow keys move across groups. */
25
+ export function MentionGroup({ label, children }) {
26
+ const id = useId();
27
+ return (_jsxs("li", { role: "presentation", "data-slot": "mention-group", className: "sprout-mention-group", children: [_jsx("span", { id: id, "aria-hidden": "true", className: "sprout-mention-group-label", children: label }), _jsx("ul", { role: "group", "aria-labelledby": id, className: "sprout-mention-group-options", children: children })] }));
28
+ }
29
+ export function MentionStatus({ tone, children, action }) {
30
+ return (_jsxs("div", { "data-slot": "mention-status", "data-tone": tone, role: tone === "error" ? "alert" : "status", className: "sprout-mention-status", children: [tone === "loading" && _jsx(Spinner, { "aria-hidden": "true" }), _jsx("span", { className: "sprout-mention-status-text", children: children }), action && (_jsx(Button, { size: "sm", variant: "outline", className: "sprout-mention-status-action", onMouseDown: (event) => event.preventDefault(), onClick: action.onAction, children: action.label }))] }));
31
+ }
@@ -0,0 +1,64 @@
1
+ import type { ComponentProps } from "react";
2
+ import { Menu as MenuPrimitive } from "@base-ui/react/menu";
3
+ import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar";
4
+ export declare const menubarItemVariantNames: readonly ["default", "destructive"];
5
+ export type MenubarItemVariant = (typeof menubarItemVariantNames)[number];
6
+ export type MenubarProps = MenubarPrimitive.Props;
7
+ /**
8
+ * A row of menus, like an app's File and View menus. Left and Right move
9
+ * between triggers, and Down opens the focused menu.
10
+ */
11
+ export declare function Menubar({ className, ...props }: MenubarProps): import("react").JSX.Element;
12
+ export type MenubarMenuProps = MenuPrimitive.Root.Props;
13
+ /** One menu in the bar. Place a `MenubarTrigger` and `MenubarContent` inside. */
14
+ export declare function MenubarMenu(props: MenubarMenuProps): import("react").JSX.Element;
15
+ export type MenubarPortalProps = MenuPrimitive.Portal.Props;
16
+ export declare function MenubarPortal(props: MenubarPortalProps): import("react").JSX.Element;
17
+ export type MenubarTriggerProps = MenuPrimitive.Trigger.Props;
18
+ export declare function MenubarTrigger({ className, ...props }: MenubarTriggerProps): import("react").JSX.Element;
19
+ type PositionerProps = Pick<MenuPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset" | "collisionPadding" | "collisionAvoidance" | "sticky" | "positionMethod">;
20
+ export interface MenubarContentProps extends MenuPrimitive.Popup.Props, PositionerProps {
21
+ /** Where the portal renders. Defaults to `document.body`. */
22
+ container?: MenuPrimitive.Portal.Props["container"];
23
+ }
24
+ /**
25
+ * The menu surface. It renders its own portal and positioner, so place it
26
+ * directly inside `MenubarMenu`. It lines up with the bar's inner edge.
27
+ */
28
+ export declare function MenubarContent({ align, alignOffset, side, sideOffset, collisionPadding, collisionAvoidance, sticky, positionMethod, container, className, ...props }: MenubarContentProps): import("react").JSX.Element;
29
+ export type MenubarGroupProps = MenuPrimitive.Group.Props;
30
+ export declare function MenubarGroup(props: MenubarGroupProps): import("react").JSX.Element;
31
+ interface Inset {
32
+ /** Indents the text to line up with checkbox, radio, and icon items. */
33
+ inset?: boolean;
34
+ }
35
+ export interface MenubarLabelProps extends MenuPrimitive.GroupLabel.Props, Inset {
36
+ }
37
+ export declare function MenubarLabel({ className, inset, ...props }: MenubarLabelProps): import("react").JSX.Element;
38
+ export interface MenubarItemProps extends MenuPrimitive.Item.Props, Inset {
39
+ variant?: MenubarItemVariant;
40
+ }
41
+ export declare function MenubarItem({ className, inset, variant, ...props }: MenubarItemProps): import("react").JSX.Element;
42
+ export interface MenubarCheckboxItemProps extends MenuPrimitive.CheckboxItem.Props, Inset {
43
+ }
44
+ /** A toggle row. The check sits at the start, so the label lines up with `inset` rows. */
45
+ export declare function MenubarCheckboxItem({ className, inset, children, ...props }: MenubarCheckboxItemProps): import("react").JSX.Element;
46
+ export type MenubarRadioGroupProps = MenuPrimitive.RadioGroup.Props;
47
+ export declare function MenubarRadioGroup(props: MenubarRadioGroupProps): import("react").JSX.Element;
48
+ export interface MenubarRadioItemProps extends MenuPrimitive.RadioItem.Props, Inset {
49
+ }
50
+ export declare function MenubarRadioItem({ className, inset, children, ...props }: MenubarRadioItemProps): import("react").JSX.Element;
51
+ export type MenubarSeparatorProps = MenuPrimitive.Separator.Props;
52
+ export declare function MenubarSeparator({ className, ...props }: MenubarSeparatorProps): import("react").JSX.Element;
53
+ export type MenubarShortcutProps = ComponentProps<"span">;
54
+ /** Right-aligned hint text, such as a keyboard shortcut. It does not bind keys. */
55
+ export declare function MenubarShortcut({ className, ...props }: MenubarShortcutProps): import("react").JSX.Element;
56
+ export type MenubarSubProps = MenuPrimitive.SubmenuRoot.Props;
57
+ export declare function MenubarSub(props: MenubarSubProps): import("react").JSX.Element;
58
+ export interface MenubarSubTriggerProps extends MenuPrimitive.SubmenuTrigger.Props, Inset {
59
+ }
60
+ export declare function MenubarSubTrigger({ className, inset, children, ...props }: MenubarSubTriggerProps): import("react").JSX.Element;
61
+ export type MenubarSubContentProps = MenubarContentProps;
62
+ /** A nested menu surface. It opens to the side and sizes to its content. */
63
+ export declare function MenubarSubContent({ align, alignOffset, side, sideOffset, className, ...props }: MenubarSubContentProps): import("react").JSX.Element;
64
+ export {};
@@ -0,0 +1,68 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Menu as MenuPrimitive } from "@base-ui/react/menu";
4
+ import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar";
5
+ import { Check } from "reicon-react/icons/Check";
6
+ import { ChevronRight } from "reicon-react/icons/ChevronRight";
7
+ import { cn, stateClassName } from "../internal/classes.js";
8
+ export const menubarItemVariantNames = ["default", "destructive"];
9
+ /**
10
+ * A row of menus, like an app's File and View menus. Left and Right move
11
+ * between triggers, and Down opens the focused menu.
12
+ */
13
+ export function Menubar({ className, ...props }) {
14
+ return (_jsx(MenubarPrimitive, { "data-slot": "menubar", ...props, className: stateClassName("sprout-menubar", className) }));
15
+ }
16
+ /** One menu in the bar. Place a `MenubarTrigger` and `MenubarContent` inside. */
17
+ export function MenubarMenu(props) {
18
+ return _jsx(MenuPrimitive.Root, { ...props });
19
+ }
20
+ export function MenubarPortal(props) {
21
+ return _jsx(MenuPrimitive.Portal, { "data-slot": "menubar-portal", ...props });
22
+ }
23
+ export function MenubarTrigger({ className, ...props }) {
24
+ return (_jsx(MenuPrimitive.Trigger, { "data-slot": "menubar-trigger", ...props, className: stateClassName("sprout-menubar-trigger", className) }));
25
+ }
26
+ /**
27
+ * The menu surface. It renders its own portal and positioner, so place it
28
+ * directly inside `MenubarMenu`. It lines up with the bar's inner edge.
29
+ */
30
+ export function MenubarContent({ align = "start", alignOffset = -4, side = "bottom", sideOffset = 8, collisionPadding = 8, collisionAvoidance, sticky, positionMethod, container, className, ...props }) {
31
+ return (_jsx(MenuPrimitive.Portal, { container: container, children: _jsx(MenuPrimitive.Positioner, { className: "sprout-menubar-positioner", align: align, alignOffset: alignOffset, side: side, sideOffset: sideOffset, collisionPadding: collisionPadding, collisionAvoidance: collisionAvoidance, sticky: sticky, positionMethod: positionMethod, children: _jsx(MenuPrimitive.Popup, { "data-slot": "menubar-content", ...props, className: stateClassName("sprout-menubar-content", className) }) }) }));
32
+ }
33
+ export function MenubarGroup(props) {
34
+ return _jsx(MenuPrimitive.Group, { "data-slot": "menubar-group", ...props });
35
+ }
36
+ export function MenubarLabel({ className, inset, ...props }) {
37
+ return (_jsx(MenuPrimitive.GroupLabel, { "data-slot": "menubar-label", "data-inset": inset || undefined, ...props, className: stateClassName("sprout-menubar-label", className) }));
38
+ }
39
+ export function MenubarItem({ className, inset, variant = "default", ...props }) {
40
+ return (_jsx(MenuPrimitive.Item, { "data-slot": "menubar-item", "data-inset": inset || undefined, "data-variant": variant, ...props, className: stateClassName("sprout-menubar-item", className) }));
41
+ }
42
+ /** A toggle row. The check sits at the start, so the label lines up with `inset` rows. */
43
+ export function MenubarCheckboxItem({ className, inset, children, ...props }) {
44
+ return (_jsxs(MenuPrimitive.CheckboxItem, { "data-slot": "menubar-checkbox-item", "data-inset": inset || undefined, ...props, className: stateClassName("sprout-menubar-item sprout-menubar-choice", className), children: [_jsx("span", { className: "sprout-menubar-indicator", "data-slot": "menubar-checkbox-item-indicator", children: _jsx(MenuPrimitive.CheckboxItemIndicator, { children: _jsx(Check, { size: 16, "aria-hidden": "true" }) }) }), children] }));
45
+ }
46
+ export function MenubarRadioGroup(props) {
47
+ return (_jsx(MenuPrimitive.RadioGroup, { "data-slot": "menubar-radio-group", ...props }));
48
+ }
49
+ export function MenubarRadioItem({ className, inset, children, ...props }) {
50
+ return (_jsxs(MenuPrimitive.RadioItem, { "data-slot": "menubar-radio-item", "data-inset": inset || undefined, ...props, className: stateClassName("sprout-menubar-item sprout-menubar-choice", className), children: [_jsx("span", { className: "sprout-menubar-indicator", "data-slot": "menubar-radio-item-indicator", children: _jsx(MenuPrimitive.RadioItemIndicator, { children: _jsx(Check, { size: 16, "aria-hidden": "true" }) }) }), children] }));
51
+ }
52
+ export function MenubarSeparator({ className, ...props }) {
53
+ return (_jsx(MenuPrimitive.Separator, { "data-slot": "menubar-separator", ...props, className: stateClassName("sprout-menubar-separator", className) }));
54
+ }
55
+ /** Right-aligned hint text, such as a keyboard shortcut. It does not bind keys. */
56
+ export function MenubarShortcut({ className, ...props }) {
57
+ return (_jsx("span", { "data-slot": "menubar-shortcut", ...props, className: cn("sprout-menubar-shortcut", className) }));
58
+ }
59
+ export function MenubarSub(props) {
60
+ return _jsx(MenuPrimitive.SubmenuRoot, { ...props });
61
+ }
62
+ export function MenubarSubTrigger({ className, inset, children, ...props }) {
63
+ return (_jsxs(MenuPrimitive.SubmenuTrigger, { "data-slot": "menubar-sub-trigger", "data-inset": inset || undefined, ...props, className: stateClassName("sprout-menubar-item sprout-menubar-sub-trigger", className), children: [children, _jsx(ChevronRight, { size: 16, "aria-hidden": "true", className: "sprout-menubar-sub-chevron" })] }));
64
+ }
65
+ /** A nested menu surface. It opens to the side and sizes to its content. */
66
+ export function MenubarSubContent({ align = "start", alignOffset = -5, side = "right", sideOffset = 0, className, ...props }) {
67
+ return (_jsx(MenubarContent, { "data-slot": "menubar-sub-content", align: align, alignOffset: alignOffset, side: side, sideOffset: sideOffset, ...props, className: stateClassName("sprout-menubar-sub-content", className) }));
68
+ }
@@ -0,0 +1,34 @@
1
+ export declare const messageActionsAuthorNames: readonly ["assistant", "human"];
2
+ export type MessageActionsAuthor = (typeof messageActionsAuthorNames)[number];
3
+ export type MessageFeedback = "up" | "down";
4
+ export interface MessageActionsProps {
5
+ /** The Markdown the message was written in. Copy turns links into `label (url)` plain text. */
6
+ text: string;
7
+ /** Names the actions "Message" for `"human"` and "Response" for `"assistant"`. */
8
+ author?: MessageActionsAuthor;
9
+ /** ISO time the message was sent, shown as a short local time with the full date on hover. */
10
+ createdAt?: string | undefined;
11
+ /** Returns formatted HTML to copy beside the plain text, read when Copy is pressed. */
12
+ html?: (() => string | undefined) | undefined;
13
+ /** Hides the row until the surrounding Message is hovered or anything in it has focus. */
14
+ hover?: boolean;
15
+ /** The rating already given. Pressing it again clears it. */
16
+ feedback?: MessageFeedback | null | undefined;
17
+ /** Shows Helpful and Unhelpful toggles. */
18
+ onFeedback?: ((value: MessageFeedback | null) => void) | undefined;
19
+ /** Shows a Regenerate button. */
20
+ onRetry?: (() => void) | undefined;
21
+ disabled?: boolean;
22
+ className?: string | undefined;
23
+ }
24
+ /** Copy, send time, feedback, and regenerate for one chat message. */
25
+ export declare function MessageActions({ text, author, createdAt, html, hover, feedback, onFeedback, onRetry, disabled, className, }: MessageActionsProps): import("react").JSX.Element;
26
+ export interface MessageBranchProps {
27
+ /** Zero-based version shown now. */
28
+ index: number;
29
+ total: number;
30
+ onChange: (index: number) => void;
31
+ className?: string | undefined;
32
+ }
33
+ /** Steps between regenerated versions of one response. */
34
+ export declare function MessageBranch({ index, total, onChange, className, }: MessageBranchProps): import("react").JSX.Element;
@@ -0,0 +1,68 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { Check } from "reicon-react/icons/Check";
5
+ import { ChevronLeft } from "reicon-react/icons/ChevronLeft";
6
+ import { ChevronRight } from "reicon-react/icons/ChevronRight";
7
+ import { Copy } from "reicon-react/icons/Copy";
8
+ import { ThumbsDown } from "reicon-react/icons/ThumbsDown";
9
+ import { ThumbsUp } from "reicon-react/icons/ThumbsUp";
10
+ import { cn } from "../internal/classes.js";
11
+ import { Button } from "./button.js";
12
+ export const messageActionsAuthorNames = ["assistant", "human"];
13
+ const markdownLink = /\[([^\]]*)\]\(<?([^)\s>]+)>?(?:\s+"[^"]*")?\)/g;
14
+ function plainTextFromMarkdown(markdown) {
15
+ return markdown
16
+ .replace(markdownLink, (_, label, url) => !label || label === url ? url : `${label} (${url})`)
17
+ .replace(/<(https?:\/\/[^>\s]+)>/g, "$1");
18
+ }
19
+ function formatDate(options, date) {
20
+ return Number.isNaN(date.getTime())
21
+ ? date.toString()
22
+ : new Intl.DateTimeFormat([], options).format(date);
23
+ }
24
+ const startOfDay = (date) => new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
25
+ function messageTime(iso, now = new Date()) {
26
+ const date = new Date(iso);
27
+ const time = formatDate({ hour: "numeric", minute: "2-digit" }, date);
28
+ const days = Math.round((startOfDay(now) - startOfDay(date)) / 86_400_000);
29
+ if (days === 0)
30
+ return time;
31
+ if (days === 1)
32
+ return `Yesterday ${time}`;
33
+ const day = formatDate(date.getFullYear() === now.getFullYear()
34
+ ? { month: "short", day: "numeric" }
35
+ : { month: "short", day: "numeric", year: "numeric" }, date);
36
+ return `${day}, ${time}`;
37
+ }
38
+ /** Copy, send time, feedback, and regenerate for one chat message. */
39
+ export function MessageActions({ text, author = "assistant", createdAt, html, hover = false, feedback, onFeedback, onRetry, disabled = false, className, }) {
40
+ const [copy, setCopy] = useState("idle");
41
+ const noun = author === "human" ? "message" : "response";
42
+ const Noun = author === "human" ? "Message" : "Response";
43
+ const copyText = async () => {
44
+ const plain = plainTextFromMarkdown(text);
45
+ const rich = html?.();
46
+ try {
47
+ if (rich && typeof ClipboardItem !== "undefined")
48
+ await navigator.clipboard.write([
49
+ new ClipboardItem({
50
+ "text/html": new Blob([rich], { type: "text/html" }),
51
+ "text/plain": new Blob([plain], { type: "text/plain" }),
52
+ }),
53
+ ]);
54
+ else
55
+ await navigator.clipboard.writeText(plain);
56
+ setCopy("copied");
57
+ }
58
+ catch {
59
+ setCopy("error");
60
+ }
61
+ };
62
+ const rate = (value) => onFeedback?.(feedback === value ? null : value);
63
+ return (_jsxs("div", { "data-slot": "message-actions", "data-author": author, className: cn("sprout-message-actions", className), children: [_jsxs("div", { role: "group", "aria-label": `${Noun} actions`, "data-hover": hover ? "" : undefined, className: "sprout-message-actions-row", children: [_jsx(Button, { variant: "ghost", size: "icon-sm", disabled: disabled, "aria-label": copy === "copied" ? `${Noun} copied` : `Copy ${noun}`, title: `Copy ${noun}`, "data-copied": copy === "copied" ? "" : undefined, onClick: () => void copyText(), children: copy === "copied" ? (_jsx(Check, { size: 16, "aria-hidden": "true" })) : (_jsx(Copy, { size: 16, "aria-hidden": "true" })) }), createdAt && (_jsx("time", { className: "sprout-message-actions-time", dateTime: createdAt, title: formatDate({ dateStyle: "full", timeStyle: "medium" }, new Date(createdAt)), children: messageTime(createdAt) })), onFeedback && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: feedback === "up" ? "secondary" : "ghost", size: "icon-sm", disabled: disabled, "aria-label": "Helpful response", "aria-pressed": feedback === "up", title: "Helpful", onClick: () => rate("up"), children: _jsx(ThumbsUp, { size: 16, "aria-hidden": "true" }) }), _jsx(Button, { variant: feedback === "down" ? "secondary" : "ghost", size: "icon-sm", disabled: disabled, "aria-label": "Unhelpful response", "aria-pressed": feedback === "down", title: "Unhelpful", onClick: () => rate("down"), children: _jsx(ThumbsDown, { size: 16, "aria-hidden": "true" }) })] })), onRetry && (_jsx(Button, { variant: "ghost", size: "sm", disabled: disabled, onClick: onRetry, children: "Regenerate" }))] }), copy === "error" && (_jsxs("p", { role: "alert", className: "sprout-message-actions-error", children: ["Couldn't copy. Select the ", noun, " text or try again."] })), _jsx("span", { role: "status", className: "sprout-message-actions-sr-only", children: copy === "copied" ? `${Noun} copied to clipboard.` : "" })] }));
64
+ }
65
+ /** Steps between regenerated versions of one response. */
66
+ export function MessageBranch({ index, total, onChange, className, }) {
67
+ return (_jsxs("div", { "data-slot": "message-branch", role: "group", "aria-label": "Response versions", className: cn("sprout-message-branch", className), children: [_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Previous response", disabled: index <= 0, onClick: () => onChange(index - 1), children: _jsx(ChevronLeft, { size: 14, "aria-hidden": "true" }) }), _jsxs("span", { "aria-live": "polite", className: "sprout-message-branch-count", children: [index + 1, " / ", total] }), _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Next response", disabled: index >= total - 1, onClick: () => onChange(index + 1), children: _jsx(ChevronRight, { size: 14, "aria-hidden": "true" }) })] }));
68
+ }
@@ -0,0 +1,20 @@
1
+ import { type HTMLAttributes, type ReactNode } from "react";
2
+ /** Renders one Markdown link. Return a plain `<a>` for links you do not handle. */
3
+ export type MessageResponseRenderLink = (href: string, children: ReactNode) => ReactNode;
4
+ export interface MessageTableViewportProps {
5
+ /** Caps every table inside at 12rem tall and scrolls both ways; otherwise tables only scroll sideways. */
6
+ bounded: boolean;
7
+ children?: ReactNode;
8
+ }
9
+ /** Sets how tall the tables in the responses inside it may grow. */
10
+ export declare function MessageTableViewport({ bounded, children, }: MessageTableViewportProps): import("react").JSX.Element;
11
+ export interface MessageResponseProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
12
+ /** Markdown with GitHub extensions. Raw HTML is dropped and images are not shown. */
13
+ children: string;
14
+ /** Marks the response busy and shows a caret after the text. */
15
+ streaming?: boolean | undefined;
16
+ /** Replaces every link, for citations, downloads, or in-app routes. */
17
+ renderLink?: MessageResponseRenderLink | undefined;
18
+ }
19
+ /** Renders an assistant's Markdown reply: headings, lists, quotes, tables, task lists, and code blocks. */
20
+ export declare function MessageResponse({ children, streaming, renderLink, className, ...props }: MessageResponseProps): import("react").JSX.Element;
@@ -0,0 +1,46 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { createContext, useContext, useMemo, } from "react";
4
+ import Markdown, {} from "react-markdown";
5
+ import remarkGfm from "remark-gfm";
6
+ import { cn } from "../internal/classes.js";
7
+ import { CodeBlock } from "./code-block.js";
8
+ const LinkContext = createContext(undefined);
9
+ const TableContext = createContext(false);
10
+ /** Sets how tall the tables in the responses inside it may grow. */
11
+ export function MessageTableViewport({ bounded, children, }) {
12
+ return _jsx(TableContext, { value: bounded, children: children });
13
+ }
14
+ const ResponseTable = ({ children }) => {
15
+ const bounded = useContext(TableContext);
16
+ return (_jsx("div", { className: "sprout-message-response-table", "data-bounded": bounded ? "" : undefined, tabIndex: 0, role: "region", "aria-label": "Response table", children: _jsx("table", { children: children }) }));
17
+ };
18
+ function ResponseLink({ href, children }) {
19
+ const render = useContext(LinkContext);
20
+ return render ? (render(href ?? "", children)) : (_jsx("a", { href: href, target: "_blank", rel: "noopener noreferrer", children: children }));
21
+ }
22
+ const components = {
23
+ pre({ node, children }) {
24
+ const code = node?.children[0];
25
+ if (code?.type !== "element" || code.tagName !== "code")
26
+ return _jsx("pre", { children: children });
27
+ const classes = code.properties.className;
28
+ const language = Array.isArray(classes)
29
+ ? classes.find((value) => typeof value === "string" && value.startsWith("language-"))
30
+ : undefined;
31
+ const text = code.children
32
+ .map((child) => (child.type === "text" ? child.value : ""))
33
+ .join("")
34
+ .replace(/\n$/, "");
35
+ return (_jsx(CodeBlock, { code: text, language: language ? language.slice(9) : "text" }));
36
+ },
37
+ a: ResponseLink,
38
+ table: ResponseTable,
39
+ // GFM task list boxes have no label of their own; the item text follows them.
40
+ input: ({ node: _node, ...props }) => props.type === "checkbox" ? (_jsx("input", { ...props, "aria-label": "Task" })) : (_jsx("input", { ...props })),
41
+ };
42
+ /** Renders an assistant's Markdown reply: headings, lists, quotes, tables, task lists, and code blocks. */
43
+ export function MessageResponse({ children, streaming = false, renderLink, className, ...props }) {
44
+ const markdown = useMemo(() => (_jsx(Markdown, { remarkPlugins: [remarkGfm], components: components, skipHtml: true, disallowedElements: ["img"], children: children })), [children]);
45
+ return (_jsxs("div", { "data-slot": "message-response", "data-streaming": streaming ? "" : undefined, "aria-busy": streaming, ...props, className: cn("sprout-message-response", className), children: [_jsx(LinkContext, { value: renderLink, children: markdown }), streaming && (_jsx("span", { "aria-hidden": "true", className: "sprout-message-response-caret" }))] }));
46
+ }
@@ -0,0 +1,71 @@
1
+ import { type ComponentProps, type ReactNode } from "react";
2
+ import { useRender } from "@base-ui/react/use-render";
3
+ import { type MessageScrollerDefaultScrollPosition, type MessageScrollerScrollable, type MessageScrollerScrollOptions, type MessageScrollerVisibilityState } from "../internal/message-scroller.js";
4
+ import { type ButtonSize, type ButtonVariant } from "./button.js";
5
+ export type { MessageScrollerDefaultScrollPosition, MessageScrollerScrollAlign, MessageScrollerScrollable, MessageScrollerScrollOptions, MessageScrollerVisibilityState, } from "../internal/message-scroller.js";
6
+ export declare const messageScrollerDefaultScrollPositionNames: readonly ["end", "start", "last-anchor"];
7
+ export declare const messageScrollerButtonDirectionNames: readonly ["end", "start"];
8
+ export declare const messageScrollerScrollAlignNames: readonly ["start", "center", "end", "nearest"];
9
+ export type MessageScrollerButtonDirection = (typeof messageScrollerButtonDirectionNames)[number];
10
+ export interface MessageScrollerProviderProps {
11
+ children?: ReactNode;
12
+ /** Keep following the newest content while the reader is at the end. */
13
+ autoScroll?: boolean | undefined;
14
+ /** Where the history opens: the newest message, the oldest, or the last `scrollAnchor` item pinned under the top edge. */
15
+ defaultScrollPosition?: MessageScrollerDefaultScrollPosition | undefined;
16
+ /** Pixels from an edge that still count as “at” it. */
17
+ scrollEdgeThreshold?: number | undefined;
18
+ /** Pixels of the previous message left showing above a pinned turn. */
19
+ scrollPreviousItemPeek?: number | undefined;
20
+ /** Extra pixels kept above a message scrolled into view. */
21
+ scrollMargin?: number | undefined;
22
+ }
23
+ /** Owns the scroll state. Wrap the scroller and anything that calls the hooks, such as a composer. */
24
+ export declare function MessageScrollerProvider({ children, autoScroll, defaultScrollPosition, scrollEdgeThreshold, scrollPreviousItemPeek, scrollMargin, }: MessageScrollerProviderProps): import("react").JSX.Element;
25
+ export type MessageScrollerProps = ComponentProps<"div">;
26
+ /** The positioned frame. Holds the viewport and the jump buttons, and fills its parent. */
27
+ export declare function MessageScroller({ className, ref, ...props }: MessageScrollerProps): import("react").JSX.Element;
28
+ export interface MessageScrollerViewportProps extends ComponentProps<"div"> {
29
+ /** Keep the message you are reading still when older messages load above it. */
30
+ preserveScrollOnPrepend?: boolean | undefined;
31
+ }
32
+ /** The scrolling region. Focusable, named “Messages” unless you pass `aria-label`. */
33
+ export declare function MessageScrollerViewport({ className, ref, role, tabIndex, preserveScrollOnPrepend, onKeyDown, onScroll, onTouchMove, onWheel, "aria-label": ariaLabel, ...props }: MessageScrollerViewportProps): import("react").JSX.Element;
34
+ export interface MessageScrollerContentProps extends ComponentProps<"div"> {
35
+ /** Class for the hidden spacer that lets a pinned turn reach the top edge. */
36
+ spacerClassName?: string | undefined;
37
+ }
38
+ /** The message column, a polite `log` that announces additions. Items sit 24px apart. */
39
+ export declare function MessageScrollerContent({ className, children, ref, role, spacerClassName, "aria-relevant": ariaRelevant, ...props }: MessageScrollerContentProps): import("react").JSX.Element;
40
+ export interface MessageScrollerItemProps extends ComponentProps<"div"> {
41
+ /** Stable id used by `scrollToMessage` and visibility. */
42
+ messageId?: string | undefined;
43
+ /** Pin this item under the top edge when it is added, such as the newest user turn. */
44
+ scrollAnchor?: boolean | undefined;
45
+ }
46
+ /** One message or turn in the column. Off-screen items skip rendering work. */
47
+ export declare function MessageScrollerItem({ className, ref, messageId, scrollAnchor, ...props }: MessageScrollerItemProps): import("react").JSX.Element;
48
+ export interface MessageScrollerButtonState extends Record<string, unknown> {
49
+ active: boolean;
50
+ direction: MessageScrollerButtonDirection;
51
+ }
52
+ export type MessageScrollerButtonProps = useRender.ComponentProps<"button", MessageScrollerButtonState> & {
53
+ /** Which edge the button scrolls to. `"start"` sits at the top with the arrow turned up. */
54
+ direction?: MessageScrollerButtonDirection | undefined;
55
+ /** `"smooth"` by default; becomes `"auto"` when reduced motion is set. */
56
+ behavior?: ScrollBehavior | undefined;
57
+ variant?: ButtonVariant | undefined;
58
+ size?: ButtonSize | undefined;
59
+ };
60
+ /** A floating jump button that shows only while there is more history in its direction. */
61
+ export declare function MessageScrollerButton({ className, children, render, ref, direction, behavior, variant, size, tabIndex, onClick, ...props }: MessageScrollerButtonProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
62
+ /** Scroll the history from outside it, such as after sending or from a “jump to” link. */
63
+ export declare function useMessageScroller(): {
64
+ scrollToEnd: (options?: MessageScrollerScrollOptions) => boolean;
65
+ scrollToMessage: (messageId: string, options?: MessageScrollerScrollOptions) => boolean;
66
+ scrollToStart: (options?: MessageScrollerScrollOptions) => boolean;
67
+ };
68
+ /** Whether there is more history above (`start`) or below (`end`) the current view. */
69
+ export declare function useMessageScrollerScrollable(): MessageScrollerScrollable;
70
+ /** The ids of items in view and the pinned turn under the top edge. Observes only while used. */
71
+ export declare function useMessageScrollerVisibility(): MessageScrollerVisibilityState;
@@ -0,0 +1,217 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore, } from "react";
4
+ import { useRender } from "@base-ui/react/use-render";
5
+ import { mergeProps } from "@base-ui/react/merge-props";
6
+ import { ArrowDown } from "reicon-react/icons/ArrowDown";
7
+ import { cn } from "../internal/classes.js";
8
+ import { MessageScrollerController, scrollIntentKeys, } from "../internal/message-scroller.js";
9
+ import { Button } from "./button.js";
10
+ export const messageScrollerDefaultScrollPositionNames = [
11
+ "end",
12
+ "start",
13
+ "last-anchor",
14
+ ];
15
+ export const messageScrollerButtonDirectionNames = ["end", "start"];
16
+ export const messageScrollerScrollAlignNames = [
17
+ "start",
18
+ "center",
19
+ "end",
20
+ "nearest",
21
+ ];
22
+ const ScrollerContext = createContext(null);
23
+ function useController(part) {
24
+ const controller = useContext(ScrollerContext);
25
+ if (!controller)
26
+ throw new Error(`${part} must be used within a MessageScrollerProvider.`);
27
+ return controller;
28
+ }
29
+ function assignRef(ref, value) {
30
+ if (typeof ref === "function")
31
+ ref(value);
32
+ else if (ref)
33
+ ref.current = value;
34
+ }
35
+ function usePendingDefaultScroll(controller) {
36
+ const store = controller.pendingDefaultStore;
37
+ return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
38
+ }
39
+ /** Owns the scroll state. Wrap the scroller and anything that calls the hooks, such as a composer. */
40
+ export function MessageScrollerProvider({ children, autoScroll = false, defaultScrollPosition = "end", scrollEdgeThreshold = 8, scrollPreviousItemPeek = 64, scrollMargin = 0, }) {
41
+ const options = {
42
+ autoScroll,
43
+ defaultScrollPosition,
44
+ scrollEdgeThreshold,
45
+ scrollPreviousItemPeek,
46
+ scrollMargin,
47
+ };
48
+ const [controller] = useState(() => new MessageScrollerController(options));
49
+ controller.setOptions(options);
50
+ useLayoutEffect(() => controller.applyDefaultOrSettle(), [controller, defaultScrollPosition]);
51
+ useLayoutEffect(() => controller.followOnAutoScroll(), [controller, autoScroll]);
52
+ useEffect(() => controller.dispose, [controller]);
53
+ return (_jsx(ScrollerContext.Provider, { value: controller, children: children }));
54
+ }
55
+ /** The positioned frame. Holds the viewport and the jump buttons, and fills its parent. */
56
+ export function MessageScroller({ className, ref, ...props }) {
57
+ const controller = useController("MessageScroller");
58
+ const pending = usePendingDefaultScroll(controller);
59
+ const setRef = useCallback((element) => {
60
+ controller.setRoot(element);
61
+ assignRef(ref, element);
62
+ }, [controller, ref]);
63
+ return (_jsx("div", { "data-slot": "message-scroller", ref: setRef, className: cn("sprout-message-scroller", className), ...props, ...(pending ? { "data-pending-scroll": "" } : null) }));
64
+ }
65
+ /** The scrolling region. Focusable, named “Messages” unless you pass `aria-label`. */
66
+ export function MessageScrollerViewport({ className, ref, role, tabIndex, preserveScrollOnPrepend = true, onKeyDown, onScroll, onTouchMove, onWheel, "aria-label": ariaLabel, ...props }) {
67
+ const controller = useController("MessageScrollerViewport");
68
+ const pending = usePendingDefaultScroll(controller);
69
+ controller.preserveScrollOnPrepend = preserveScrollOnPrepend;
70
+ const setRef = useCallback((element) => {
71
+ controller.setViewport(element);
72
+ assignRef(ref, element);
73
+ }, [controller, ref]);
74
+ useEffect(() => {
75
+ const viewport = controller.getViewport();
76
+ if (!viewport || typeof ResizeObserver === "undefined")
77
+ return;
78
+ let frame = 0;
79
+ const observer = new ResizeObserver(() => {
80
+ cancelAnimationFrame(frame);
81
+ frame = requestAnimationFrame(controller.handleResize);
82
+ });
83
+ observer.observe(viewport);
84
+ return () => {
85
+ cancelAnimationFrame(frame);
86
+ observer.disconnect();
87
+ };
88
+ }, [controller]);
89
+ return (_jsx("div", { "data-slot": "message-scroller-viewport", ref: setRef, role: role ?? "region", "aria-label": ariaLabel ?? "Messages", tabIndex: tabIndex ?? 0, className: cn("sprout-message-scroller-viewport", className), onKeyDown: (event) => {
90
+ if (scrollIntentKeys.has(event.key))
91
+ controller.userScrollIntent();
92
+ onKeyDown?.(event);
93
+ }, onScroll: (event) => {
94
+ controller.syncAfterScroll();
95
+ onScroll?.(event);
96
+ }, onTouchMove: (event) => {
97
+ controller.userScrollIntent();
98
+ onTouchMove?.(event);
99
+ }, onWheel: (event) => {
100
+ controller.userScrollIntent();
101
+ onWheel?.(event);
102
+ }, ...props, ...(pending ? { "data-pending-scroll": "" } : null) }));
103
+ }
104
+ /** The message column, a polite `log` that announces additions. Items sit 24px apart. */
105
+ export function MessageScrollerContent({ className, children, ref, role, spacerClassName, "aria-relevant": ariaRelevant, ...props }) {
106
+ const controller = useController("MessageScrollerContent");
107
+ const element = useRef(null);
108
+ const setRef = useCallback((node) => {
109
+ element.current = node;
110
+ controller.setContent(node);
111
+ assignRef(ref, node);
112
+ }, [controller, ref]);
113
+ useLayoutEffect(() => {
114
+ const node = element.current;
115
+ if (!node)
116
+ return;
117
+ controller.handleContentChange();
118
+ if (typeof MutationObserver === "undefined")
119
+ return;
120
+ const observer = new MutationObserver(controller.handleContentChange);
121
+ observer.observe(node, { childList: true });
122
+ return () => observer.disconnect();
123
+ }, [controller]);
124
+ useEffect(() => {
125
+ const node = element.current;
126
+ if (!node || typeof ResizeObserver === "undefined")
127
+ return;
128
+ let frame = 0;
129
+ const observer = new ResizeObserver(() => {
130
+ cancelAnimationFrame(frame);
131
+ frame = requestAnimationFrame(controller.handleResize);
132
+ });
133
+ observer.observe(node);
134
+ return () => {
135
+ cancelAnimationFrame(frame);
136
+ observer.disconnect();
137
+ };
138
+ }, [controller]);
139
+ return (_jsxs("div", { "data-slot": "message-scroller-content", ref: setRef, role: role ?? "log", "aria-relevant": ariaRelevant ?? "additions", className: cn("sprout-message-scroller-content", className), ...props, children: [children, _jsx("div", { ref: controller.setSpacer, "aria-hidden": "true", "data-message-scroller-spacer": "", hidden: true, className: spacerClassName })] }));
140
+ }
141
+ /** One message or turn in the column. Off-screen items skip rendering work. */
142
+ export function MessageScrollerItem({ className, ref, messageId, scrollAnchor = false, ...props }) {
143
+ const controller = useController("MessageScrollerItem");
144
+ const previous = useRef(null);
145
+ const setRef = useCallback((node) => {
146
+ const before = previous.current;
147
+ previous.current = node;
148
+ if (messageId)
149
+ controller.registerMessage(messageId, node, before);
150
+ assignRef(ref, node);
151
+ }, [controller, messageId, ref]);
152
+ return (_jsx("div", { "data-slot": "message-scroller-item", ref: setRef, "data-message-id": messageId, "data-scroll-anchor": scrollAnchor ? "true" : "false", className: cn("sprout-message-scroller-item", className), ...props }));
153
+ }
154
+ const prefersReducedMotion = () => typeof matchMedia === "function" &&
155
+ matchMedia("(prefers-reduced-motion: reduce)").matches;
156
+ /** A floating jump button that shows only while there is more history in its direction. */
157
+ export function MessageScrollerButton({ className, children, render, ref, direction = "end", behavior = "smooth", variant = "secondary", size = "icon-sm", tabIndex, onClick, ...props }) {
158
+ const controller = useController("MessageScrollerButton");
159
+ const store = controller.stateStore;
160
+ const read = useCallback(() => direction === "start"
161
+ ? store.getSnapshot().start
162
+ : store.getSnapshot().end, [direction, store]);
163
+ const active = useSyncExternalStore(store.subscribe, read, read);
164
+ const state = useMemo(() => ({ active, direction }), [active, direction]);
165
+ return useRender({
166
+ defaultTagName: "button",
167
+ render: render ?? _jsx(Button, { variant: variant, size: size }),
168
+ ref,
169
+ state,
170
+ stateAttributesMapping: { active: () => null },
171
+ props: mergeProps({
172
+ type: "button",
173
+ "data-slot": "message-scroller-button",
174
+ "data-active": active ? "true" : "false",
175
+ className: cn("sprout-message-scroller-button", className),
176
+ inert: !active,
177
+ tabIndex: active ? tabIndex : -1,
178
+ onClick: (event) => {
179
+ if (!active)
180
+ return;
181
+ onClick?.(event);
182
+ if (event.defaultPrevented)
183
+ return;
184
+ event.currentTarget.blur();
185
+ const scroll = {
186
+ behavior: prefersReducedMotion() ? "auto" : behavior,
187
+ };
188
+ if (direction === "start")
189
+ controller.scrollToStart(scroll);
190
+ else
191
+ controller.scrollToEnd(scroll);
192
+ },
193
+ children: children ?? (_jsxs(_Fragment, { children: [_jsx(ArrowDown, { "aria-hidden": "true" }), _jsx("span", { className: "sprout-message-scroller-sr-only", children: direction === "end" ? "Scroll to end" : "Scroll to start" })] })),
194
+ }, props),
195
+ });
196
+ }
197
+ /** Scroll the history from outside it, such as after sending or from a “jump to” link. */
198
+ export function useMessageScroller() {
199
+ const controller = useController("useMessageScroller");
200
+ return useMemo(() => ({
201
+ scrollToEnd: controller.scrollToEnd,
202
+ scrollToMessage: controller.scrollToMessage,
203
+ scrollToStart: controller.scrollToStart,
204
+ }), [controller]);
205
+ }
206
+ /** Whether there is more history above (`start`) or below (`end`) the current view. */
207
+ export function useMessageScrollerScrollable() {
208
+ const store = useController("useMessageScrollerScrollable").stateStore;
209
+ return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
210
+ }
211
+ /** The ids of items in view and the pinned turn under the top edge. Observes only while used. */
212
+ export function useMessageScrollerVisibility() {
213
+ const controller = useController("useMessageScrollerVisibility");
214
+ const store = controller.visibilityStore;
215
+ const subscribe = useCallback((listener) => store.subscribe(listener, controller.observeVisibility, controller.unobserveVisibility), [controller, store]);
216
+ return useSyncExternalStore(subscribe, store.getSnapshot, store.getSnapshot);
217
+ }