@justanarthur/modo-ui 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 (172) hide show
  1. package/LICENSE +21 -0
  2. package/LICENSE.beui +21 -0
  3. package/LICENSE.fluid-functionalism +21 -0
  4. package/LICENSE.motion-primitives +21 -0
  5. package/LICENSE.sileo +21 -0
  6. package/README.md +608 -2
  7. package/dist/base.css +994 -0
  8. package/dist/blocks/sidebar-app/index.d.ts +34 -0
  9. package/dist/blocks/sidebar-app/index.js +34 -0
  10. package/dist/components/badge/index.d.ts +59 -0
  11. package/dist/components/badge/index.js +18 -0
  12. package/dist/components/button/index.d.ts +59 -0
  13. package/dist/components/button/index.js +16 -0
  14. package/dist/components/card/index.d.ts +141 -0
  15. package/dist/components/card/index.js +44 -0
  16. package/dist/components/inputs/checkbox-group/index.d.ts +50 -0
  17. package/dist/components/inputs/checkbox-group/index.js +23 -0
  18. package/dist/components/inputs/color-picker/index.d.ts +137 -0
  19. package/dist/components/inputs/color-picker/index.js +37 -0
  20. package/dist/components/inputs/combobox/index.d.ts +161 -0
  21. package/dist/components/inputs/combobox/index.js +41 -0
  22. package/dist/components/inputs/date-time/calendar/index.d.ts +45 -0
  23. package/dist/components/inputs/date-time/calendar/index.js +20 -0
  24. package/dist/components/inputs/date-time/date-picker/index.d.ts +82 -0
  25. package/dist/components/inputs/date-time/date-picker/index.js +34 -0
  26. package/dist/components/inputs/date-time/date-range-picker/index.d.ts +64 -0
  27. package/dist/components/inputs/date-time/date-range-picker/index.js +33 -0
  28. package/dist/components/inputs/date-time/duration-picker/index.d.ts +57 -0
  29. package/dist/components/inputs/date-time/duration-picker/index.js +33 -0
  30. package/dist/components/inputs/date-time/month-picker/index.d.ts +57 -0
  31. package/dist/components/inputs/date-time/month-picker/index.js +32 -0
  32. package/dist/components/inputs/date-time/time-picker/index.d.ts +59 -0
  33. package/dist/components/inputs/date-time/time-picker/index.js +33 -0
  34. package/dist/components/inputs/input/index.d.ts +84 -0
  35. package/dist/components/inputs/input/index.js +20 -0
  36. package/dist/components/inputs/input-copy/index.d.ts +44 -0
  37. package/dist/components/inputs/input-copy/index.js +18 -0
  38. package/dist/components/inputs/input-message/index.d.ts +125 -0
  39. package/dist/components/inputs/input-message/index.js +27 -0
  40. package/dist/components/inputs/radio-group/index.d.ts +58 -0
  41. package/dist/components/inputs/radio-group/index.js +22 -0
  42. package/dist/components/inputs/search/index.d.ts +83 -0
  43. package/dist/components/inputs/search/index.js +28 -0
  44. package/dist/components/inputs/select/index.d.ts +105 -0
  45. package/dist/components/inputs/select/index.js +37 -0
  46. package/dist/components/inputs/slider/index.d.ts +108 -0
  47. package/dist/components/inputs/slider/index.js +16 -0
  48. package/dist/components/inputs/switch/index.d.ts +43 -0
  49. package/dist/components/inputs/switch/index.js +16 -0
  50. package/dist/components/navigation/accordion/index.d.ts +117 -0
  51. package/dist/components/navigation/accordion/index.js +30 -0
  52. package/dist/components/navigation/sidebar/index.d.ts +388 -0
  53. package/dist/components/navigation/sidebar/index.js +101 -0
  54. package/dist/components/navigation/tabs/index.d.ts +70 -0
  55. package/dist/components/navigation/tabs/index.js +25 -0
  56. package/dist/components/navigation/tabs-subtle/index.d.ts +73 -0
  57. package/dist/components/navigation/tabs-subtle/index.js +23 -0
  58. package/dist/components/overlays/alert-dialog/index.d.ts +67 -0
  59. package/dist/components/overlays/alert-dialog/index.js +24 -0
  60. package/dist/components/overlays/command/index.d.ts +121 -0
  61. package/dist/components/overlays/command/index.js +39 -0
  62. package/dist/components/overlays/context-menu/index.d.ts +97 -0
  63. package/dist/components/overlays/context-menu/index.js +32 -0
  64. package/dist/components/overlays/dialog/index.d.ts +130 -0
  65. package/dist/components/overlays/dialog/index.js +39 -0
  66. package/dist/components/overlays/dropdown/index.d.ts +222 -0
  67. package/dist/components/overlays/dropdown/index.js +47 -0
  68. package/dist/components/overlays/popover/index.d.ts +87 -0
  69. package/dist/components/overlays/popover/index.js +28 -0
  70. package/dist/components/overlays/preview-card/index.d.ts +75 -0
  71. package/dist/components/overlays/preview-card/index.js +21 -0
  72. package/dist/components/overlays/sheet/index.d.ts +67 -0
  73. package/dist/components/overlays/sheet/index.js +26 -0
  74. package/dist/components/overlays/toast/index.d.ts +107 -0
  75. package/dist/components/overlays/toast/index.js +23 -0
  76. package/dist/components/overlays/tooltip/index.d.ts +75 -0
  77. package/dist/components/overlays/tooltip/index.js +19 -0
  78. package/dist/components/table/index.d.ts +44 -0
  79. package/dist/components/table/index.js +28 -0
  80. package/dist/components/thinking-indicator/index.d.ts +32 -0
  81. package/dist/components/thinking-indicator/index.js +15 -0
  82. package/dist/index.d.ts +3926 -0
  83. package/dist/index.js +513 -0
  84. package/dist/preset.d.ts +104 -0
  85. package/dist/preset.js +124 -0
  86. package/dist/primitives/code/index.d.ts +23 -0
  87. package/dist/primitives/code/index.js +12 -0
  88. package/dist/primitives/craft/index.d.ts +31 -0
  89. package/dist/primitives/craft/index.js +13 -0
  90. package/dist/primitives/fluid-hover/index.d.ts +233 -0
  91. package/dist/primitives/fluid-hover/index.js +27 -0
  92. package/dist/primitives/morph/index.d.ts +127 -0
  93. package/dist/primitives/morph/index.js +21 -0
  94. package/dist/primitives/motion/index.d.ts +115 -0
  95. package/dist/primitives/motion/index.js +20 -0
  96. package/dist/primitives/scroll-area/index.d.ts +41 -0
  97. package/dist/primitives/scroll-area/index.js +13 -0
  98. package/dist/primitives/sizes/index.d.ts +128 -0
  99. package/dist/primitives/sizes/index.js +22 -0
  100. package/dist/primitives/surface/index.d.ts +69 -0
  101. package/dist/primitives/surface/index.js +18 -0
  102. package/dist/shared/components-0f0as45n.js +17 -0
  103. package/dist/shared/components-40j34hdb.js +986 -0
  104. package/dist/shared/components-4hh0gm0v.js +292 -0
  105. package/dist/shared/components-4xxddc44.js +15 -0
  106. package/dist/shared/components-4xxtggzp.js +110 -0
  107. package/dist/shared/components-54nf2x99.js +293 -0
  108. package/dist/shared/components-5pkcfprd.js +232 -0
  109. package/dist/shared/components-5vxtcp88.js +9 -0
  110. package/dist/shared/components-5zry9te1.js +152 -0
  111. package/dist/shared/components-61ckf42z.js +220 -0
  112. package/dist/shared/components-6hzwfmfd.js +58 -0
  113. package/dist/shared/components-6phwsry6.js +361 -0
  114. package/dist/shared/components-6t6nr33r.js +86 -0
  115. package/dist/shared/components-6y1th6k4.js +111 -0
  116. package/dist/shared/components-74c20v2f.js +32 -0
  117. package/dist/shared/components-7cqq1sqy.js +193 -0
  118. package/dist/shared/components-7gv95yfy.js +496 -0
  119. package/dist/shared/components-7nww01w4.js +375 -0
  120. package/dist/shared/components-7zp99w5d.js +62 -0
  121. package/dist/shared/components-8hk9ve3r.js +1200 -0
  122. package/dist/shared/components-8hzbsfbd.js +123 -0
  123. package/dist/shared/components-9p8s8n5q.js +171 -0
  124. package/dist/shared/components-aaq3yk4f.js +304 -0
  125. package/dist/shared/components-bf2d9k02.js +484 -0
  126. package/dist/shared/components-bkvb6nem.js +63 -0
  127. package/dist/shared/components-brnzrp2t.js +262 -0
  128. package/dist/shared/components-csxz9f52.js +42 -0
  129. package/dist/shared/components-de8xsm9y.js +22 -0
  130. package/dist/shared/components-dg0w8g5p.js +26 -0
  131. package/dist/shared/components-dphg9gjr.js +42 -0
  132. package/dist/shared/components-e46kf6cp.js +298 -0
  133. package/dist/shared/components-ed7cmssr.js +105 -0
  134. package/dist/shared/components-ess3ch6w.js +137 -0
  135. package/dist/shared/components-gjfrycb5.js +984 -0
  136. package/dist/shared/components-gndm6am4.js +128 -0
  137. package/dist/shared/components-hjkrv00c.js +191 -0
  138. package/dist/shared/components-hzfp4tjb.js +270 -0
  139. package/dist/shared/components-j65qt1mq.js +719 -0
  140. package/dist/shared/components-jw16snbn.js +19 -0
  141. package/dist/shared/components-jxgznftn.js +146 -0
  142. package/dist/shared/components-kf2ehztc.js +216 -0
  143. package/dist/shared/components-kpbcms10.js +9 -0
  144. package/dist/shared/components-m5br529r.js +141 -0
  145. package/dist/shared/components-nn6xezy2.js +1828 -0
  146. package/dist/shared/components-nnnd2xef.js +9 -0
  147. package/dist/shared/components-p2vmyfe1.js +345 -0
  148. package/dist/shared/components-peg09pzs.js +79 -0
  149. package/dist/shared/components-r22vtgqc.js +45 -0
  150. package/dist/shared/components-r2gke5p6.js +1759 -0
  151. package/dist/shared/components-r43mqnd6.js +160 -0
  152. package/dist/shared/components-r4jt6hgd.js +58 -0
  153. package/dist/shared/components-rc63qsy6.js +159 -0
  154. package/dist/shared/components-s3yfvfkr.js +149 -0
  155. package/dist/shared/components-s5pkbjx1.js +445 -0
  156. package/dist/shared/components-tad5h7ta.js +346 -0
  157. package/dist/shared/components-taxmgbnh.js +120 -0
  158. package/dist/shared/components-tm88ghva.js +215 -0
  159. package/dist/shared/components-v7es8aan.js +144 -0
  160. package/dist/shared/components-vb033rns.js +28 -0
  161. package/dist/shared/components-vc1k7yv2.js +30 -0
  162. package/dist/shared/components-vktws5r6.js +667 -0
  163. package/dist/shared/components-x2xyckd5.js +198 -0
  164. package/dist/shared/components-x3rz4cyn.js +61 -0
  165. package/dist/shared/components-xszag63d.js +141 -0
  166. package/dist/shared/components-y3cqdrgy.js +291 -0
  167. package/dist/shared/components-zvb11ker.js +288 -0
  168. package/dist/shared/components-zx2hr57f.js +124 -0
  169. package/dist/styles.css +1949 -0
  170. package/dist/tailwind.css +219 -0
  171. package/dist/tokens.css +345 -0
  172. package/package.json +102 -3
@@ -0,0 +1,984 @@
1
+ import {
2
+ Tooltip
3
+ } from "./components-gndm6am4.js";
4
+ import {
5
+ SizeProvider
6
+ } from "./components-vb033rns.js";
7
+ import {
8
+ Button
9
+ } from "./components-5pkcfprd.js";
10
+ import {
11
+ useIcon
12
+ } from "./components-m5br529r.js";
13
+ import {
14
+ SurfaceProvider,
15
+ surfaceClasses
16
+ } from "./components-csxz9f52.js";
17
+ import {
18
+ FluidHoverHighlight
19
+ } from "./components-r22vtgqc.js";
20
+ import {
21
+ useFluidHover,
22
+ useRegisterFluidHoverItem
23
+ } from "./components-e46kf6cp.js";
24
+ import {
25
+ useShape
26
+ } from "./components-7zp99w5d.js";
27
+ import {
28
+ useControllableState
29
+ } from "./components-dg0w8g5p.js";
30
+ import {
31
+ spring
32
+ } from "./components-vc1k7yv2.js";
33
+ import {
34
+ useSize
35
+ } from "./components-x3rz4cyn.js";
36
+ import {
37
+ cn
38
+ } from "./components-0f0as45n.js";
39
+ import {
40
+ __require
41
+ } from "./components-kpbcms10.js";
42
+
43
+ // components/inputs/input-message/index.tsx
44
+ import { AnimatePresence, motion, Reorder, useReducedMotion } from "motion/react";
45
+ import {
46
+ forwardRef,
47
+ useCallback,
48
+ useEffect as useEffect2,
49
+ useId,
50
+ useLayoutEffect,
51
+ useMemo,
52
+ useRef,
53
+ useState as useState2
54
+ } from "react";
55
+
56
+ // components/inputs/input-message/file-thumbnail.tsx
57
+ import { useEffect, useState } from "react";
58
+ import { jsx, jsxs } from "react/jsx-runtime";
59
+ var pdfjsPromise = null;
60
+ async function loadPdfjs() {
61
+ if (!pdfjsPromise) {
62
+ pdfjsPromise = import("pdfjs-dist").then((mod) => {
63
+ if (!mod.GlobalWorkerOptions.workerSrc) {
64
+ mod.GlobalWorkerOptions.workerSrc = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${mod.version}/build/pdf.worker.min.mjs`;
65
+ }
66
+ return mod;
67
+ });
68
+ }
69
+ return pdfjsPromise;
70
+ }
71
+ async function renderPdfFirstPage(file, targetWidth) {
72
+ const pdfjs = await loadPdfjs();
73
+ const buffer = await file.arrayBuffer();
74
+ const pdf = await pdfjs.getDocument({ data: buffer }).promise;
75
+ const page = await pdf.getPage(1);
76
+ const baseViewport = page.getViewport({ scale: 1 });
77
+ const scale = targetWidth * 2 / baseViewport.width;
78
+ const viewport = page.getViewport({ scale });
79
+ const canvas = document.createElement("canvas");
80
+ canvas.width = viewport.width;
81
+ canvas.height = viewport.height;
82
+ await page.render({ canvas, viewport }).promise;
83
+ return canvas.toDataURL("image/png");
84
+ }
85
+ function FileThumbnail({ file, size, className }) {
86
+ const shape = useShape();
87
+ const isImage = file.type.startsWith("image/");
88
+ const isPdf = file.type === "application/pdf";
89
+ const [imageUrl, setImageUrl] = useState(null);
90
+ useEffect(() => {
91
+ if (!isImage) {
92
+ setImageUrl(null);
93
+ return;
94
+ }
95
+ const url = URL.createObjectURL(file);
96
+ setImageUrl(url);
97
+ return () => URL.revokeObjectURL(url);
98
+ }, [isImage, file]);
99
+ const [pdfUrl, setPdfUrl] = useState(null);
100
+ const [pdfError, setPdfError] = useState(false);
101
+ useEffect(() => {
102
+ setPdfError(false);
103
+ if (!isPdf) {
104
+ setPdfUrl(null);
105
+ return;
106
+ }
107
+ let cancelled = false;
108
+ renderPdfFirstPage(file, size).then((url) => {
109
+ if (!cancelled)
110
+ setPdfUrl(url);
111
+ }).catch(() => {
112
+ if (!cancelled)
113
+ setPdfError(true);
114
+ });
115
+ return () => {
116
+ cancelled = true;
117
+ };
118
+ }, [file, isPdf, size]);
119
+ const previewUrl = imageUrl ?? pdfUrl;
120
+ const isPending = isImage && !imageUrl || isPdf && !pdfUrl && !pdfError;
121
+ return /* @__PURE__ */ jsx("div", {
122
+ className: cn("relative shrink-0 overflow-hidden bg-accent border border-border", shape.bg, className),
123
+ style: { width: size, height: size },
124
+ children: previewUrl ? /* @__PURE__ */ jsx("img", {
125
+ src: previewUrl,
126
+ alt: file.name,
127
+ className: "absolute inset-0 w-full h-full object-cover"
128
+ }) : isPending ? /* @__PURE__ */ jsx("div", {
129
+ className: "absolute inset-0 flex items-center justify-center",
130
+ children: /* @__PURE__ */ jsx("div", {
131
+ className: "w-6 h-6 rounded-full border-2 border-border border-t-muted-foreground animate-spin",
132
+ "aria-label": "Loading preview",
133
+ role: "status"
134
+ })
135
+ }) : /* @__PURE__ */ jsx("div", {
136
+ className: "absolute inset-0 flex items-center justify-center text-muted-foreground",
137
+ role: "img",
138
+ "aria-label": file.name,
139
+ children: /* @__PURE__ */ jsxs("svg", {
140
+ width: Math.max(16, size * 0.35),
141
+ height: Math.max(16, size * 0.35),
142
+ viewBox: "0 0 24 24",
143
+ fill: "none",
144
+ stroke: "currentColor",
145
+ strokeWidth: "1.5",
146
+ strokeLinecap: "round",
147
+ strokeLinejoin: "round",
148
+ "aria-hidden": "true",
149
+ children: [
150
+ /* @__PURE__ */ jsx("path", {
151
+ d: "M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"
152
+ }),
153
+ /* @__PURE__ */ jsx("path", {
154
+ d: "M14 3v5h5"
155
+ })
156
+ ]
157
+ })
158
+ })
159
+ });
160
+ }
161
+
162
+ // components/inputs/input-message/index.tsx
163
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
164
+ var useIsoLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect2;
165
+ function useRegionHeight() {
166
+ const roRef = useRef(null);
167
+ const [height, setHeight] = useState2(null);
168
+ const ref = useCallback((el) => {
169
+ roRef.current?.disconnect();
170
+ roRef.current = null;
171
+ if (!el)
172
+ return;
173
+ const sync = () => {
174
+ const next = el.parentElement?.scrollHeight ?? el.offsetHeight;
175
+ if (next > 0)
176
+ setHeight(next);
177
+ };
178
+ sync();
179
+ const ro = new ResizeObserver(sync);
180
+ ro.observe(el);
181
+ roRef.current = ro;
182
+ }, []);
183
+ return [ref, height];
184
+ }
185
+ function useIsTouch() {
186
+ const [isTouch, setIsTouch] = useState2(false);
187
+ useEffect2(() => {
188
+ const mq = window.matchMedia("(hover: none)");
189
+ const update = () => setIsTouch(mq.matches);
190
+ update();
191
+ mq.addEventListener("change", update);
192
+ return () => mq.removeEventListener("change", update);
193
+ }, []);
194
+ return isTouch;
195
+ }
196
+ var DEFAULT_ACCEPT = "image/png,image/jpeg,application/pdf";
197
+ var NO_FILES = [];
198
+ var NO_QUEUE = [];
199
+ var FilePreviewTile = forwardRef(function FilePreviewTile2({ file, onRemove, size }, ref) {
200
+ const XIcon = useIcon("x");
201
+ return /* @__PURE__ */ jsxs2(motion.div, {
202
+ ref,
203
+ layout: true,
204
+ initial: { opacity: 0, scale: 0.9 },
205
+ animate: { opacity: 1, scale: 1 },
206
+ exit: { opacity: 0, scale: 0.9, transition: spring.fast.exit },
207
+ transition: spring.fast,
208
+ className: "relative shrink-0 cursor-default group/tile",
209
+ children: [
210
+ /* @__PURE__ */ jsx2(FileThumbnail, {
211
+ file,
212
+ size
213
+ }),
214
+ /* @__PURE__ */ jsx2(Tooltip, {
215
+ content: "Remove",
216
+ side: "top",
217
+ children: /* @__PURE__ */ jsx2("button", {
218
+ type: "button",
219
+ onClick: (e) => {
220
+ e.stopPropagation();
221
+ onRemove();
222
+ },
223
+ "aria-label": `Remove ${file.name}`,
224
+ className: "light absolute top-1 right-1 w-5 h-5 rounded-full bg-foreground text-background opacity-0 group-hover/tile:opacity-100 transition-opacity duration-fast flex items-center justify-center cursor-pointer outline-none focus-visible:opacity-100 focus-visible:ring-1 focus-visible:ring-focus-ring",
225
+ children: /* @__PURE__ */ jsx2(XIcon, {
226
+ size: 12,
227
+ strokeWidth: 2.5
228
+ })
229
+ })
230
+ })
231
+ ]
232
+ });
233
+ });
234
+ function QueuedRow({ item, index, total, reduceMotion, isTouch, onEdit, onRemove, onMove }) {
235
+ const XIcon = useIcon("x");
236
+ const ImageIcon = useIcon("image");
237
+ const compactStep = useSize().variant === "compact";
238
+ const fileCount = item.files.length;
239
+ const label = item.text || `${fileCount} attachment${fileCount === 1 ? "" : "s"}`;
240
+ return /* @__PURE__ */ jsxs2(Reorder.Item, {
241
+ value: item,
242
+ layout: true,
243
+ initial: reduceMotion ? { opacity: 0 } : { opacity: 0, scale: 0.97 },
244
+ animate: { opacity: 1, scale: 1 },
245
+ exit: reduceMotion ? { opacity: 0 } : { opacity: 0, scale: 0.97, transition: spring.fast.exit },
246
+ transition: spring.fast,
247
+ "aria-label": `Queued message ${index + 1} of ${total}: ${label}`,
248
+ tabIndex: 0,
249
+ onDoubleClick: () => onEdit(item),
250
+ onKeyDown: (e) => {
251
+ if (e.key === "Enter" || e.key === "F2") {
252
+ e.preventDefault();
253
+ onEdit(item);
254
+ } else if (e.key === "Delete" || e.key === "Backspace") {
255
+ e.preventDefault();
256
+ onRemove(item);
257
+ } else if (e.altKey && (e.key === "ArrowUp" || e.key === "ArrowDown")) {
258
+ e.preventDefault();
259
+ onMove(item, e.key === "ArrowUp" ? -1 : 1);
260
+ }
261
+ },
262
+ className: cn("group/qrow flex items-center gap-2 rounded-lg bg-muted", compactStep ? "h-7 px-2 text-body-compact" : "h-8 px-2.5 text-body", "text-foreground/85 select-none outline-none", "cursor-grab active:cursor-grabbing", "focus-visible:ring-1 focus-visible:ring-focus-ring", "weight-normal"),
263
+ children: [
264
+ fileCount > 0 && /* @__PURE__ */ jsxs2("span", {
265
+ className: "flex shrink-0 items-center gap-0.5 text-muted-foreground",
266
+ children: [
267
+ /* @__PURE__ */ jsx2(ImageIcon, {
268
+ size: 13
269
+ }),
270
+ item.text && /* @__PURE__ */ jsx2("span", {
271
+ className: "tabular-nums",
272
+ children: fileCount
273
+ })
274
+ ]
275
+ }),
276
+ /* @__PURE__ */ jsx2("span", {
277
+ className: "min-w-0 flex-1 truncate [text-box:trim-both_cap_alphabetic] py-1 -my-1",
278
+ children: label
279
+ }),
280
+ /* @__PURE__ */ jsx2(Tooltip, {
281
+ content: "Remove",
282
+ side: "top",
283
+ children: /* @__PURE__ */ jsx2("button", {
284
+ type: "button",
285
+ onPointerDown: (e) => e.stopPropagation(),
286
+ onClick: (e) => {
287
+ e.stopPropagation();
288
+ onRemove(item);
289
+ },
290
+ "aria-label": `Remove queued message: ${label}`,
291
+ className: cn("shrink-0 flex h-5 w-5 items-center justify-center rounded-full", "text-muted-foreground hover:text-foreground hover:bg-hover", isTouch ? "opacity-100" : "opacity-0 group-hover/qrow:opacity-100 focus-visible:opacity-100", "transition-opacity duration-fast cursor-pointer outline-none", "focus-visible:ring-1 focus-visible:ring-focus-ring"),
292
+ children: /* @__PURE__ */ jsx2(XIcon, {
293
+ size: 13,
294
+ strokeWidth: 2.5
295
+ })
296
+ })
297
+ })
298
+ ]
299
+ });
300
+ }
301
+ function SuggestionRow({ text, index, active, keyHint, optionId, registerItem, onSelect }) {
302
+ const EnterIcon = useIcon("corner-down-left");
303
+ const ArrowDownIcon = useIcon("arrow-down");
304
+ const compactStep = useSize().variant === "compact";
305
+ const ref = useRef(null);
306
+ useRegisterFluidHoverItem(registerItem, index, ref);
307
+ return /* @__PURE__ */ jsxs2("div", {
308
+ ref,
309
+ id: optionId,
310
+ role: "option",
311
+ "aria-selected": active,
312
+ onClick: onSelect,
313
+ className: cn("relative flex cursor-pointer items-center gap-2", compactStep ? "h-7 px-2 text-subtitle-compact" : "h-8 px-2.5 text-subtitle", "text-muted-foreground transition-colors duration-fast", active && "text-foreground", "weight-normal"),
314
+ children: [
315
+ /* @__PURE__ */ jsx2("span", {
316
+ className: "min-w-0 flex-1 truncate [text-box:trim-both_cap_alphabetic] py-1 -my-1",
317
+ children: text
318
+ }),
319
+ !active && keyHint ? /* @__PURE__ */ jsx2(ArrowDownIcon, {
320
+ size: 13,
321
+ className: "shrink-0 text-muted-foreground/70 transition-opacity duration-fast"
322
+ }) : /* @__PURE__ */ jsx2(EnterIcon, {
323
+ size: 13,
324
+ className: cn("shrink-0 transition-opacity duration-fast", active ? "opacity-100" : "opacity-0")
325
+ })
326
+ ]
327
+ });
328
+ }
329
+ var InputMessage = forwardRef(({
330
+ size,
331
+ value: valueProp,
332
+ defaultValue,
333
+ onValueChange,
334
+ onSend,
335
+ placeholder = "Ask me anything…",
336
+ leftSlot,
337
+ rightSlot,
338
+ disabled,
339
+ minRows = 1,
340
+ maxRows = 8,
341
+ clickToFocus = true,
342
+ sendLabel = "Send",
343
+ files: filesProp,
344
+ defaultFiles,
345
+ onFilesChange,
346
+ accept = DEFAULT_ACCEPT,
347
+ maxFiles,
348
+ filePreviewSize = 80,
349
+ textareaProps,
350
+ status: statusProp,
351
+ defaultStatus,
352
+ onStatusChange,
353
+ onStop,
354
+ queue: queueProp,
355
+ defaultQueue,
356
+ onQueueChange,
357
+ showQueue = true,
358
+ history = [],
359
+ placeholderSuggestion,
360
+ suggestions,
361
+ className,
362
+ style,
363
+ ...props
364
+ }, ref) => {
365
+ const shape = useShape();
366
+ const compactStep = useSize(size).variant === "compact";
367
+ const ArrowUpIcon = useIcon("arrow-up");
368
+ const reduceMotion = useReducedMotion() ?? false;
369
+ const isTouch = useIsTouch();
370
+ const textareaRef = useRef(null);
371
+ const fileInputRef = useRef(null);
372
+ const [focusVisible, setFocusVisible] = useState2(false);
373
+ const [dragOver, setDragOver] = useState2(false);
374
+ const [hovered, setHovered] = useState2(false);
375
+ const {
376
+ onFocus: _textareaOnFocus,
377
+ onBlur: _textareaOnBlur,
378
+ "aria-describedby": textareaDescribedBy,
379
+ ...restTextareaProps
380
+ } = textareaProps ?? {};
381
+ const valueControlled = valueProp !== undefined;
382
+ const [value, setValue] = useControllableState(valueProp, defaultValue ?? "", onValueChange);
383
+ const filesControlled = filesProp !== undefined;
384
+ const supportsFiles = onFilesChange !== undefined || defaultFiles !== undefined;
385
+ const [filesArr, setFiles] = useControllableState(filesProp, defaultFiles ?? NO_FILES, onFilesChange);
386
+ const statusEnabled = statusProp !== undefined || defaultStatus !== undefined;
387
+ const [statusValue, setStatus] = useControllableState(statusProp, defaultStatus ?? "idle", onStatusChange);
388
+ const status = statusEnabled ? statusValue : undefined;
389
+ const streaming = status === "streaming";
390
+ const [queueArr, setQueue] = useControllableState(queueProp, defaultQueue ?? NO_QUEUE, onQueueChange);
391
+ const queueRef = useRef(queueArr);
392
+ queueRef.current = queueArr;
393
+ const supportsQueue = statusEnabled && (onQueueChange !== undefined || defaultQueue !== undefined);
394
+ const [liveMsg, setLiveMsg] = useState2("");
395
+ const [historyIndex, setHistoryIndex] = useState2(null);
396
+ const draftBeforeHistory = useRef("");
397
+ const suggestionsArr = useMemo(() => suggestions ?? [], [suggestions]);
398
+ const suggestionsOpen = suggestionsArr.length > 0 && value === "";
399
+ const suggestionListRef = useRef(null);
400
+ const [filesRegionRef, filesRegionHeight] = useRegionHeight();
401
+ const [queueRegionRef, queueRegionHeight] = useRegionHeight();
402
+ const [suggestionsRegionRef, suggestionsRegionHeight] = useRegionHeight();
403
+ const suggestionHover = useFluidHover(suggestionListRef);
404
+ const {
405
+ activeIndex: activeSuggestion,
406
+ setActiveIndex: setActiveSuggestion,
407
+ handlers: suggestionHandlers,
408
+ registerItem: registerSuggestion,
409
+ remeasure
410
+ } = suggestionHover;
411
+ useEffect2(() => {
412
+ if (suggestionsOpen)
413
+ remeasure();
414
+ }, [suggestionsOpen, remeasure]);
415
+ const suggestionListId = useId();
416
+ const ghostHintId = useId();
417
+ const showGhost = !!placeholderSuggestion && value === "" && !(dragOver && supportsFiles);
418
+ useEffect2(() => {
419
+ if (!suggestionsOpen)
420
+ setActiveSuggestion(null);
421
+ }, [suggestionsOpen, setActiveSuggestion]);
422
+ const acceptSuggestion = useCallback((text) => {
423
+ setActiveSuggestion(null);
424
+ setHistoryIndex(null);
425
+ setValue(text);
426
+ requestAnimationFrame(() => {
427
+ const el = textareaRef.current;
428
+ if (!el)
429
+ return;
430
+ el.focus();
431
+ el.setSelectionRange(el.value.length, el.value.length);
432
+ });
433
+ }, [setValue, setActiveSuggestion]);
434
+ const lineHeightCache = useRef(null);
435
+ const resizeTextarea = useCallback(() => {
436
+ const el = textareaRef.current;
437
+ if (!el)
438
+ return;
439
+ el.style.height = "auto";
440
+ let cache = lineHeightCache.current;
441
+ if (!cache || cache.el !== el) {
442
+ const lineHeight = parseFloat(getComputedStyle(el).lineHeight);
443
+ cache = { el, value: Number.isNaN(lineHeight) ? 20 : lineHeight };
444
+ lineHeightCache.current = cache;
445
+ }
446
+ const min = cache.value * minRows;
447
+ const max = cache.value * maxRows;
448
+ const next = Math.min(Math.max(el.scrollHeight, min), max);
449
+ el.style.height = `${next}px`;
450
+ el.style.overflowY = el.scrollHeight > max ? "auto" : "hidden";
451
+ }, [minRows, maxRows]);
452
+ useIsoLayoutEffect(() => {
453
+ resizeTextarea();
454
+ }, [value, resizeTextarea]);
455
+ useEffect2(() => {
456
+ const el = textareaRef.current;
457
+ if (!el || typeof ResizeObserver === "undefined")
458
+ return;
459
+ let lastWidth = el.offsetWidth;
460
+ const ro = new ResizeObserver(() => {
461
+ const width = el.offsetWidth;
462
+ if (width === lastWidth)
463
+ return;
464
+ lastWidth = width;
465
+ resizeTextarea();
466
+ });
467
+ ro.observe(el);
468
+ return () => ro.disconnect();
469
+ }, [resizeTextarea]);
470
+ const trimmed = value.trim();
471
+ const canSend = !disabled && (trimmed.length > 0 || filesArr.length > 0);
472
+ const EDGE_DROP = "0 1px 1px -0.5px var(--shadow-color)";
473
+ const edgeShadow = dragOver ? `0 0 0 1px var(--brand), ${EDGE_DROP}` : focusVisible ? `0 0 0 1px color-mix(in oklab, var(--foreground) 20%, transparent), ${EDGE_DROP}` : hovered && clickToFocus && !disabled ? `0 0 0 1px var(--border), ${EDGE_DROP}` : undefined;
474
+ const handleSend = useCallback(() => {
475
+ if (!canSend)
476
+ return;
477
+ setHistoryIndex(null);
478
+ if (streaming && supportsQueue) {
479
+ const item = {
480
+ id: crypto.randomUUID(),
481
+ text: trimmed,
482
+ files: filesArr
483
+ };
484
+ setQueue([...queueRef.current, item]);
485
+ setValue("");
486
+ if (supportsFiles)
487
+ setFiles([]);
488
+ requestAnimationFrame(() => textareaRef.current?.focus());
489
+ return;
490
+ }
491
+ onSend?.(trimmed, filesArr);
492
+ if (!valueControlled)
493
+ setValue("");
494
+ if (supportsFiles && !filesControlled)
495
+ setFiles([]);
496
+ }, [
497
+ canSend,
498
+ streaming,
499
+ supportsQueue,
500
+ onSend,
501
+ trimmed,
502
+ filesArr,
503
+ setQueue,
504
+ setValue,
505
+ supportsFiles,
506
+ setFiles,
507
+ valueControlled,
508
+ filesControlled
509
+ ]);
510
+ const statusControlled = statusProp !== undefined;
511
+ const handleStop = useCallback(() => {
512
+ onStop?.();
513
+ if (!statusControlled)
514
+ setStatus("idle");
515
+ }, [onStop, statusControlled, setStatus]);
516
+ const prevStatusRef = useRef(status);
517
+ useEffect2(() => {
518
+ const prev = prevStatusRef.current;
519
+ prevStatusRef.current = status;
520
+ if (!supportsQueue)
521
+ return;
522
+ if (prev === "streaming" && status === "idle" && queueArr.length > 0) {
523
+ const [next, ...rest] = queueArr;
524
+ if (!next)
525
+ return;
526
+ setQueue(rest);
527
+ onSend?.(next.text, next.files, { queuedId: next.id });
528
+ setLiveMsg(`Message sent.${rest.length ? ` ${rest.length} still queued.` : ""}`);
529
+ }
530
+ }, [status, supportsQueue, queueArr, setQueue, onSend]);
531
+ const editQueued = useCallback((item) => {
532
+ if (!supportsQueue)
533
+ return;
534
+ setHistoryIndex(null);
535
+ setValue(item.text);
536
+ if (supportsFiles) {
537
+ setFiles(maxFiles != null ? item.files.slice(0, maxFiles) : item.files);
538
+ }
539
+ setQueue(queueRef.current.filter((q) => q.id !== item.id));
540
+ requestAnimationFrame(() => {
541
+ const el = textareaRef.current;
542
+ if (!el)
543
+ return;
544
+ el.focus();
545
+ el.setSelectionRange(el.value.length, el.value.length);
546
+ });
547
+ }, [supportsQueue, supportsFiles, setValue, setFiles, maxFiles, setQueue]);
548
+ const removeQueued = useCallback((item) => setQueue(queueRef.current.filter((q) => q.id !== item.id)), [setQueue]);
549
+ const moveQueued = useCallback((item, dir) => {
550
+ const cur = queueRef.current;
551
+ const i = cur.findIndex((q) => q.id === item.id);
552
+ const j = i + dir;
553
+ if (i < 0 || j < 0 || j >= cur.length)
554
+ return;
555
+ const next = [...cur];
556
+ const a = next[i];
557
+ const b = next[j];
558
+ if (!a || !b)
559
+ return;
560
+ next[i] = b;
561
+ next[j] = a;
562
+ setQueue(next);
563
+ }, [setQueue]);
564
+ const canStop = onStop !== undefined || !statusControlled;
565
+ const buttonMode = !streaming ? "send" : canSend && supportsQueue ? "queue" : canStop ? "stop" : "send";
566
+ const buttonLabel = buttonMode === "stop" ? "Stop" : buttonMode === "queue" ? "Queue message" : sendLabel;
567
+ const setCaretEnd = useCallback(() => {
568
+ requestAnimationFrame(() => {
569
+ const el = textareaRef.current;
570
+ if (el)
571
+ el.setSelectionRange(el.value.length, el.value.length);
572
+ });
573
+ }, []);
574
+ const handleKeyDown = useCallback((e) => {
575
+ if (e.nativeEvent.isComposing)
576
+ return;
577
+ if (suggestionsOpen && !e.shiftKey && !e.altKey && !e.metaKey && !e.ctrlKey) {
578
+ if (e.key === "ArrowDown") {
579
+ e.preventDefault();
580
+ setActiveSuggestion((prev) => prev == null ? 0 : Math.min(prev + 1, suggestionsArr.length - 1));
581
+ return;
582
+ }
583
+ if (activeSuggestion != null) {
584
+ if (e.key === "ArrowUp") {
585
+ e.preventDefault();
586
+ setActiveSuggestion(activeSuggestion === 0 ? null : activeSuggestion - 1);
587
+ return;
588
+ }
589
+ if (e.key === "Enter") {
590
+ e.preventDefault();
591
+ const picked = suggestionsArr[activeSuggestion];
592
+ if (picked !== undefined)
593
+ acceptSuggestion(picked);
594
+ return;
595
+ }
596
+ if (e.key === "Escape") {
597
+ e.preventDefault();
598
+ setActiveSuggestion(null);
599
+ return;
600
+ }
601
+ }
602
+ }
603
+ if (e.key === "Tab" && !e.shiftKey && placeholderSuggestion && value === "") {
604
+ e.preventDefault();
605
+ acceptSuggestion(placeholderSuggestion);
606
+ return;
607
+ }
608
+ if (history.length > 0 && (e.key === "ArrowUp" || e.key === "ArrowDown") && !e.shiftKey && !e.altKey && !e.metaKey && !e.ctrlKey) {
609
+ const el = e.currentTarget;
610
+ const caret = el.selectionStart ?? 0;
611
+ const end = el.selectionEnd ?? caret;
612
+ if (e.key === "ArrowUp" && !value.slice(0, caret).includes(`
613
+ `)) {
614
+ const start = historyIndex == null ? history.length : historyIndex;
615
+ if (start > 0) {
616
+ e.preventDefault();
617
+ if (historyIndex == null)
618
+ draftBeforeHistory.current = value;
619
+ const ni = start - 1;
620
+ setHistoryIndex(ni);
621
+ setValue(history[ni] ?? "");
622
+ setCaretEnd();
623
+ }
624
+ return;
625
+ }
626
+ if (e.key === "ArrowDown" && historyIndex != null && !value.slice(end).includes(`
627
+ `)) {
628
+ e.preventDefault();
629
+ const ni = historyIndex + 1;
630
+ if (ni >= history.length) {
631
+ setHistoryIndex(null);
632
+ setValue(draftBeforeHistory.current);
633
+ } else {
634
+ setHistoryIndex(ni);
635
+ setValue(history[ni] ?? "");
636
+ }
637
+ setCaretEnd();
638
+ return;
639
+ }
640
+ }
641
+ if (e.key === "Enter" && !e.shiftKey) {
642
+ e.preventDefault();
643
+ handleSend();
644
+ }
645
+ }, [
646
+ history,
647
+ value,
648
+ historyIndex,
649
+ setValue,
650
+ setCaretEnd,
651
+ handleSend,
652
+ suggestionsOpen,
653
+ suggestionsArr,
654
+ activeSuggestion,
655
+ setActiveSuggestion,
656
+ acceptSuggestion,
657
+ placeholderSuggestion
658
+ ]);
659
+ const handleContainerMouseDown = useCallback((e) => {
660
+ if (!clickToFocus || disabled)
661
+ return;
662
+ const target = e.target;
663
+ if (target === textareaRef.current)
664
+ return;
665
+ if (target.closest('button, a, input, select, textarea, [contenteditable], [role="button"], [data-im-queue]')) {
666
+ return;
667
+ }
668
+ e.preventDefault();
669
+ textareaRef.current?.focus();
670
+ }, [clickToFocus, disabled]);
671
+ const acceptTokens = useMemo(() => accept.split(",").map((s) => s.trim()).filter(Boolean), [accept]);
672
+ const matchesAccept = useCallback((file) => acceptTokens.some((token) => {
673
+ if (token.endsWith("/*"))
674
+ return file.type.startsWith(token.slice(0, -1));
675
+ if (token.startsWith("."))
676
+ return file.name.toLowerCase().endsWith(token.toLowerCase());
677
+ return file.type === token;
678
+ }), [acceptTokens]);
679
+ const addFiles = useCallback((incoming) => {
680
+ if (!supportsFiles)
681
+ return;
682
+ const fingerprint = (f) => `${f.name}-${f.size}-${f.lastModified}`;
683
+ const existing = new Set(filesArr.map(fingerprint));
684
+ const accepted = [];
685
+ for (const f of incoming) {
686
+ if (!matchesAccept(f))
687
+ continue;
688
+ const fp = fingerprint(f);
689
+ if (existing.has(fp))
690
+ continue;
691
+ existing.add(fp);
692
+ accepted.push(f);
693
+ }
694
+ if (!accepted.length)
695
+ return;
696
+ const next = [...filesArr, ...accepted];
697
+ setFiles(maxFiles != null ? next.slice(0, maxFiles) : next);
698
+ }, [supportsFiles, setFiles, filesArr, matchesAccept, maxFiles]);
699
+ const removeFile = useCallback((idx) => {
700
+ if (!supportsFiles)
701
+ return;
702
+ setFiles(filesArr.filter((_, i) => i !== idx));
703
+ }, [supportsFiles, setFiles, filesArr]);
704
+ const openFilePicker = useCallback((overrideAccept) => {
705
+ const el = fileInputRef.current;
706
+ if (!el)
707
+ return;
708
+ if (overrideAccept) {
709
+ el.accept = overrideAccept;
710
+ el.click();
711
+ queueMicrotask(() => {
712
+ if (fileInputRef.current)
713
+ fileInputRef.current.accept = accept;
714
+ });
715
+ return;
716
+ }
717
+ el.click();
718
+ }, [accept]);
719
+ const slotCtx = useMemo(() => ({ openFilePicker, files: filesArr }), [openFilePicker, filesArr]);
720
+ const leftContent = typeof leftSlot === "function" ? leftSlot(slotCtx) : leftSlot;
721
+ const rightContent = typeof rightSlot === "function" ? rightSlot(slotCtx) : rightSlot;
722
+ const handleDragOver = useCallback((e) => {
723
+ if (!supportsFiles || disabled)
724
+ return;
725
+ if (!Array.from(e.dataTransfer.types).includes("Files"))
726
+ return;
727
+ e.preventDefault();
728
+ e.dataTransfer.dropEffect = "copy";
729
+ setDragOver(true);
730
+ }, [supportsFiles, disabled]);
731
+ const handleDragLeave = useCallback((e) => {
732
+ const wrapper = e.currentTarget;
733
+ const next = e.relatedTarget;
734
+ if (next && wrapper.contains(next))
735
+ return;
736
+ setDragOver(false);
737
+ }, []);
738
+ const handleDrop = useCallback((e) => {
739
+ e.preventDefault();
740
+ setDragOver(false);
741
+ if (!supportsFiles || disabled)
742
+ return;
743
+ addFiles(Array.from(e.dataTransfer.files));
744
+ }, [supportsFiles, disabled, addFiles]);
745
+ const handleFileInputChange = useCallback((e) => {
746
+ if (!e.target.files)
747
+ return;
748
+ addFiles(Array.from(e.target.files));
749
+ e.target.value = "";
750
+ }, [addFiles]);
751
+ const composer = /* @__PURE__ */ jsx2("div", {
752
+ ref,
753
+ onMouseDown: handleContainerMouseDown,
754
+ onDragOver: handleDragOver,
755
+ onDragLeave: handleDragLeave,
756
+ onDrop: handleDrop,
757
+ className: cn("flex flex-col gap-1 p-2 transition-[box-shadow,color] duration-fast", surfaceClasses(2, 2), shape.container, clickToFocus && !disabled && "cursor-text", disabled && "opacity-50 pointer-events-none", className),
758
+ style: edgeShadow ? { boxShadow: edgeShadow, ...style } : style,
759
+ onMouseEnter: () => setHovered(true),
760
+ onMouseLeave: () => setHovered(false),
761
+ ...props,
762
+ children: /* @__PURE__ */ jsxs2(SurfaceProvider, {
763
+ value: 2,
764
+ children: [
765
+ supportsFiles && /* @__PURE__ */ jsx2("input", {
766
+ ref: fileInputRef,
767
+ type: "file",
768
+ accept,
769
+ multiple: maxFiles == null || maxFiles > 1,
770
+ className: "hidden",
771
+ onChange: handleFileInputChange,
772
+ "aria-hidden": "true",
773
+ tabIndex: -1
774
+ }),
775
+ /* @__PURE__ */ jsx2(AnimatePresence, {
776
+ initial: false,
777
+ children: filesArr.length > 0 && /* @__PURE__ */ jsx2(motion.div, {
778
+ initial: { height: 0, opacity: 0 },
779
+ animate: { height: filesRegionHeight ?? 0, opacity: 1 },
780
+ exit: { height: 0, opacity: 0 },
781
+ transition: { ...spring.moderate, bounce: 0 },
782
+ className: "overflow-hidden",
783
+ children: /* @__PURE__ */ jsx2("div", {
784
+ ref: filesRegionRef,
785
+ className: "flex flex-wrap gap-2 pb-1",
786
+ children: /* @__PURE__ */ jsx2(AnimatePresence, {
787
+ initial: false,
788
+ mode: "popLayout",
789
+ children: filesArr.map((file, i) => /* @__PURE__ */ jsx2(FilePreviewTile, {
790
+ file,
791
+ onRemove: () => removeFile(i),
792
+ size: filePreviewSize
793
+ }, `${file.name}-${file.size}-${file.lastModified}`))
794
+ })
795
+ })
796
+ }, "preview-row")
797
+ }),
798
+ supportsQueue && showQueue && /* @__PURE__ */ jsx2(AnimatePresence, {
799
+ initial: false,
800
+ children: queueArr.length > 0 && /* @__PURE__ */ jsx2(motion.div, {
801
+ initial: { height: 0, opacity: 0 },
802
+ animate: { height: queueRegionHeight ?? 0, opacity: 1 },
803
+ exit: { height: 0, opacity: 0 },
804
+ transition: { ...spring.moderate, bounce: 0 },
805
+ className: "overflow-hidden",
806
+ children: /* @__PURE__ */ jsx2(Reorder.Group, {
807
+ ref: queueRegionRef,
808
+ axis: "y",
809
+ values: queueArr,
810
+ onReorder: (next) => setQueue(next),
811
+ "data-im-queue": true,
812
+ className: "flex flex-col gap-1 pb-1",
813
+ children: /* @__PURE__ */ jsx2(AnimatePresence, {
814
+ initial: false,
815
+ children: queueArr.map((item, i) => /* @__PURE__ */ jsx2(QueuedRow, {
816
+ item,
817
+ index: i,
818
+ total: queueArr.length,
819
+ reduceMotion,
820
+ isTouch,
821
+ onEdit: editQueued,
822
+ onRemove: removeQueued,
823
+ onMove: moveQueued
824
+ }, item.id))
825
+ })
826
+ })
827
+ }, "queue-row")
828
+ }),
829
+ /* @__PURE__ */ jsxs2("div", {
830
+ className: "relative",
831
+ children: [
832
+ /* @__PURE__ */ jsx2("textarea", {
833
+ ref: textareaRef,
834
+ value,
835
+ onChange: (e) => {
836
+ setHistoryIndex(null);
837
+ setActiveSuggestion(null);
838
+ setValue(e.target.value);
839
+ },
840
+ onKeyDown: handleKeyDown,
841
+ onFocus: (e) => {
842
+ if (e.target.matches(":focus-visible"))
843
+ setFocusVisible(true);
844
+ textareaProps?.onFocus?.(e);
845
+ },
846
+ onBlur: (e) => {
847
+ setFocusVisible(false);
848
+ setActiveSuggestion(null);
849
+ textareaProps?.onBlur?.(e);
850
+ },
851
+ placeholder: dragOver && supportsFiles ? "Drop files here to add to chat" : placeholderSuggestion ? undefined : placeholder,
852
+ disabled,
853
+ rows: minRows,
854
+ "aria-label": textareaProps?.["aria-label"] ?? "Message",
855
+ "aria-describedby": [showGhost ? ghostHintId : null, textareaDescribedBy].filter(Boolean).join(" ") || undefined,
856
+ "aria-activedescendant": activeSuggestion != null ? `${suggestionListId}-${activeSuggestion}` : undefined,
857
+ className: cn("w-full resize-none rounded-none bg-transparent outline-none", "text-foreground placeholder:text-muted-foreground", compactStep ? "text-subtitle-compact leading-4.5 px-1.5 py-1.5" : "text-subtitle leading-5 px-2 py-2", "weight-normal"),
858
+ ...restTextareaProps
859
+ }),
860
+ showGhost && /* @__PURE__ */ jsx2("div", {
861
+ "aria-hidden": "true",
862
+ className: cn("pointer-events-none absolute inset-0 overflow-hidden text-muted-foreground", compactStep ? "text-subtitle-compact leading-4.5 px-1.5 py-1.5" : "text-subtitle leading-5 px-2 py-2", "weight-normal"),
863
+ children: /* @__PURE__ */ jsxs2("span", {
864
+ className: "flex max-w-full items-center gap-1.5",
865
+ children: [
866
+ /* @__PURE__ */ jsx2("span", {
867
+ className: "min-w-0 truncate",
868
+ children: placeholderSuggestion
869
+ }),
870
+ /* @__PURE__ */ jsx2("kbd", {
871
+ className: cn("inline-flex shrink-0 -translate-y-px items-center rounded-box border border-border bg-background px-1 font-sans text-muted-foreground", compactStep ? "h-4 text-micro-compact" : "h-[18px] text-micro"),
872
+ children: "Tab"
873
+ })
874
+ ]
875
+ })
876
+ }),
877
+ showGhost && /* @__PURE__ */ jsxs2("span", {
878
+ id: ghostHintId,
879
+ className: "sr-only",
880
+ children: [
881
+ "Suggested prompt: ",
882
+ placeholderSuggestion,
883
+ ". Press Tab to fill the composer with it."
884
+ ]
885
+ })
886
+ ]
887
+ }),
888
+ /* @__PURE__ */ jsxs2("div", {
889
+ className: cn("flex items-center justify-between", compactStep ? "gap-1.5 [&_button]:h-6 [&_button.w-7]:w-6 [&_button]:text-caption-compact" : "gap-2"),
890
+ children: [
891
+ /* @__PURE__ */ jsx2("div", {
892
+ className: "flex items-center gap-1.5 min-w-0",
893
+ children: leftContent
894
+ }),
895
+ /* @__PURE__ */ jsxs2("div", {
896
+ className: "flex items-center gap-1.5 shrink-0",
897
+ children: [
898
+ rightContent,
899
+ /* @__PURE__ */ jsx2(Button, {
900
+ type: "button",
901
+ variant: "primary",
902
+ size: "icon-sm",
903
+ onClick: buttonMode === "stop" ? handleStop : handleSend,
904
+ disabled: buttonMode === "stop" ? disabled : !canSend,
905
+ "aria-label": buttonLabel,
906
+ children: /* @__PURE__ */ jsx2(AnimatePresence, {
907
+ mode: "wait",
908
+ initial: false,
909
+ children: /* @__PURE__ */ jsx2(motion.span, {
910
+ initial: reduceMotion ? { opacity: 0 } : { opacity: 0, scale: 0.6 },
911
+ animate: { opacity: 1, scale: 1 },
912
+ exit: reduceMotion ? { opacity: 0 } : { opacity: 0, scale: 0.6, transition: spring.fast.exit },
913
+ transition: spring.fast,
914
+ className: "flex items-center justify-center leading-none",
915
+ children: buttonMode === "stop" ? /* @__PURE__ */ jsx2("span", {
916
+ className: "h-3 w-3 rounded-glyph bg-current"
917
+ }) : /* @__PURE__ */ jsx2(ArrowUpIcon, {
918
+ size: compactStep ? 15 : 19,
919
+ className: cn("block", compactStep ? "!h-[15px] !w-[15px]" : "!h-[19px] !w-[19px]")
920
+ })
921
+ }, buttonMode === "stop" ? "stop" : "arrow")
922
+ })
923
+ })
924
+ ]
925
+ })
926
+ ]
927
+ }),
928
+ suggestionsArr.length > 0 && /* @__PURE__ */ jsx2(AnimatePresence, {
929
+ initial: false,
930
+ children: suggestionsOpen && /* @__PURE__ */ jsx2(motion.div, {
931
+ initial: { height: 0, opacity: 0 },
932
+ animate: { height: suggestionsRegionHeight ?? 0, opacity: 1 },
933
+ exit: { height: 0 },
934
+ transition: { ...spring.moderate, bounce: 0 },
935
+ className: "-mx-2 -mt-1 overflow-hidden",
936
+ children: /* @__PURE__ */ jsxs2("div", {
937
+ ref: (el) => {
938
+ suggestionListRef.current = el;
939
+ suggestionsRegionRef(el);
940
+ },
941
+ role: "listbox",
942
+ id: suggestionListId,
943
+ "aria-label": "Suggested prompts",
944
+ onMouseEnter: suggestionHandlers.onMouseEnter,
945
+ onMouseMove: suggestionHandlers.onMouseMove,
946
+ onMouseLeave: suggestionHandlers.onMouseLeave,
947
+ onClick: suggestionHandlers.onClick,
948
+ className: "relative mt-2 flex flex-col border-t border-border/60 px-1.5 pt-1.5",
949
+ children: [
950
+ /* @__PURE__ */ jsx2(FluidHoverHighlight, {
951
+ hover: suggestionHover,
952
+ className: shape.bg
953
+ }),
954
+ suggestionsArr.map((s, i) => /* @__PURE__ */ jsx2(SuggestionRow, {
955
+ text: s,
956
+ index: i,
957
+ active: i === activeSuggestion,
958
+ keyHint: i === 0 && activeSuggestion == null,
959
+ optionId: `${suggestionListId}-${i}`,
960
+ registerItem: registerSuggestion,
961
+ onSelect: () => acceptSuggestion(s)
962
+ }, `${s}-${i}`))
963
+ ]
964
+ })
965
+ }, "suggestions")
966
+ }),
967
+ /* @__PURE__ */ jsx2("span", {
968
+ className: "sr-only",
969
+ role: "status",
970
+ "aria-live": "polite",
971
+ children: liveMsg
972
+ })
973
+ ]
974
+ })
975
+ });
976
+ return size ? /* @__PURE__ */ jsx2(SizeProvider, {
977
+ size,
978
+ children: composer
979
+ }) : composer;
980
+ });
981
+ InputMessage.displayName = "InputMessage";
982
+ var input_message_default = InputMessage;
983
+
984
+ export { FileThumbnail, InputMessage, input_message_default };