makersclaw 0.2.0 → 0.3.1

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 (475) hide show
  1. package/THIRD-PARTY-NOTICES.md +180 -0
  2. package/package.json +3 -2
  3. package/r/annotate.json +62 -0
  4. package/r/approval-request.json +52 -0
  5. package/r/audio-player.json +74 -0
  6. package/r/auth-form.json +55 -0
  7. package/r/board.json +10 -6
  8. package/r/calendar-view.json +105 -0
  9. package/r/cart.json +62 -0
  10. package/r/chart.json +83 -0
  11. package/r/chat.json +95 -0
  12. package/r/code-editor.json +94 -0
  13. package/r/comment-box.json +64 -0
  14. package/r/contact-section.json +53 -0
  15. package/r/core.json +205 -8
  16. package/r/csv-import.json +63 -0
  17. package/r/data-table.json +69 -0
  18. package/r/diagram.json +74 -0
  19. package/r/doc-editor.json +82 -0
  20. package/r/doc-page.json +61 -0
  21. package/r/email-render.json +73 -0
  22. package/r/faq.json +50 -0
  23. package/r/file-upload.json +54 -0
  24. package/r/flow-canvas.json +93 -0
  25. package/r/footer.json +52 -0
  26. package/r/gantt.json +70 -0
  27. package/r/job-panel.json +12 -7
  28. package/r/json-editor.json +66 -0
  29. package/r/map.json +89 -0
  30. package/r/menubar.json +52 -0
  31. package/r/mind-map.json +67 -0
  32. package/r/navigation-menu.json +52 -0
  33. package/r/order-detail.json +54 -0
  34. package/r/org-chart.json +66 -0
  35. package/r/page-builder.json +105 -0
  36. package/r/payment-methods.json +51 -0
  37. package/r/pdf-document.json +79 -0
  38. package/r/pdf-viewer.json +88 -0
  39. package/r/product-detail.json +76 -0
  40. package/r/product-grid.json +65 -0
  41. package/r/record-form.json +67 -0
  42. package/r/record-notes.json +55 -0
  43. package/r/registry.json +3411 -29
  44. package/r/search-dialog.json +51 -0
  45. package/r/settings-list.json +66 -0
  46. package/r/setup-checklist.json +51 -0
  47. package/r/share-dialog.json +52 -0
  48. package/r/sidebar.json +69 -0
  49. package/r/signature-pad.json +72 -0
  50. package/r/sketch-canvas.json +99 -0
  51. package/r/spreadsheet.json +90 -0
  52. package/r/stat-cards.json +66 -0
  53. package/r/task-list.json +63 -0
  54. package/r/task-model.json +98 -0
  55. package/r/testimonials.json +52 -0
  56. package/r/transaction-list.json +53 -0
  57. package/r/whiteboard.json +66 -0
  58. package/registry/blocks/annotate/README.md +179 -0
  59. package/registry/blocks/annotate/annotate-data.js +250 -0
  60. package/registry/blocks/annotate/annotate.jsx +646 -0
  61. package/registry/blocks/annotate/block.json +10 -0
  62. package/registry/blocks/annotate/demo.jsx +135 -0
  63. package/registry/blocks/annotate/index.js +15 -0
  64. package/registry/blocks/approval-request/README.md +116 -0
  65. package/registry/blocks/approval-request/approval-request.jsx +386 -0
  66. package/registry/blocks/approval-request/block.json +10 -0
  67. package/registry/blocks/approval-request/demo.jsx +127 -0
  68. package/registry/blocks/approval-request/index.js +4 -0
  69. package/registry/blocks/audio-player/README.md +167 -0
  70. package/registry/blocks/audio-player/audio-player.jsx +623 -0
  71. package/registry/blocks/audio-player/block.json +14 -0
  72. package/registry/blocks/audio-player/colors.js +109 -0
  73. package/registry/blocks/audio-player/demo.jsx +209 -0
  74. package/registry/blocks/audio-player/index.js +6 -0
  75. package/registry/blocks/audio-player/time.js +67 -0
  76. package/registry/blocks/audio-player/wavesurfer.js +9 -0
  77. package/registry/blocks/auth-form/README.md +126 -0
  78. package/registry/blocks/auth-form/auth-form.jsx +568 -0
  79. package/registry/blocks/auth-form/block.json +10 -0
  80. package/registry/blocks/auth-form/demo.jsx +131 -0
  81. package/registry/blocks/auth-form/index.js +3 -0
  82. package/registry/blocks/board/README.md +55 -5
  83. package/registry/blocks/board/block.json +2 -2
  84. package/registry/blocks/board/board.jsx +230 -86
  85. package/registry/blocks/board/demo.jsx +103 -24
  86. package/registry/blocks/calendar-view/README.md +180 -0
  87. package/registry/blocks/calendar-view/agenda.jsx +147 -0
  88. package/registry/blocks/calendar-view/block.json +76 -0
  89. package/registry/blocks/calendar-view/calendar-model.js +442 -0
  90. package/registry/blocks/calendar-view/calendar.jsx +627 -0
  91. package/registry/blocks/calendar-view/demo.jsx +195 -0
  92. package/registry/blocks/calendar-view/draft.jsx +49 -0
  93. package/registry/blocks/calendar-view/index.js +39 -0
  94. package/registry/blocks/calendar-view/month.jsx +346 -0
  95. package/registry/blocks/calendar-view/paint.js +69 -0
  96. package/registry/blocks/calendar-view/time-grid.jsx +527 -0
  97. package/registry/blocks/calendar-view/tray.jsx +87 -0
  98. package/registry/blocks/cart/README.md +146 -0
  99. package/registry/blocks/cart/block.json +10 -0
  100. package/registry/blocks/cart/cart.jsx +597 -0
  101. package/registry/blocks/cart/demo.jsx +98 -0
  102. package/registry/blocks/cart/index.js +4 -0
  103. package/registry/blocks/cart/money.js +28 -0
  104. package/registry/blocks/chart/README.md +316 -0
  105. package/registry/blocks/chart/block.json +10 -0
  106. package/registry/blocks/chart/cartesian.jsx +642 -0
  107. package/registry/blocks/chart/core.jsx +262 -0
  108. package/registry/blocks/chart/demo.jsx +357 -0
  109. package/registry/blocks/chart/heatmap.jsx +336 -0
  110. package/registry/blocks/chart/index.js +8 -0
  111. package/registry/blocks/chart/pie.jsx +245 -0
  112. package/registry/blocks/chart/sparkline.jsx +88 -0
  113. package/registry/blocks/chat/README.md +273 -0
  114. package/registry/blocks/chat/attachment.jsx +95 -0
  115. package/registry/blocks/chat/block.json +10 -0
  116. package/registry/blocks/chat/chat.jsx +132 -0
  117. package/registry/blocks/chat/composer.jsx +364 -0
  118. package/registry/blocks/chat/demo.jsx +578 -0
  119. package/registry/blocks/chat/header.jsx +34 -0
  120. package/registry/blocks/chat/index.js +9 -0
  121. package/registry/blocks/chat/message.jsx +450 -0
  122. package/registry/blocks/chat/scroller.jsx +247 -0
  123. package/registry/blocks/chat/text.jsx +104 -0
  124. package/registry/blocks/code-editor/README.md +210 -0
  125. package/registry/blocks/code-editor/block.json +22 -0
  126. package/registry/blocks/code-editor/code-editor.jsx +534 -0
  127. package/registry/blocks/code-editor/codemirror.js +38 -0
  128. package/registry/blocks/code-editor/demo.jsx +205 -0
  129. package/registry/blocks/code-editor/index.js +8 -0
  130. package/registry/blocks/code-editor/json.js +317 -0
  131. package/registry/blocks/code-editor/sql.js +136 -0
  132. package/registry/blocks/code-editor/text.js +101 -0
  133. package/registry/blocks/code-editor/theme.js +186 -0
  134. package/registry/blocks/comment-box/README.md +123 -0
  135. package/registry/blocks/comment-box/block.json +10 -0
  136. package/registry/blocks/comment-box/comment-body.jsx +217 -0
  137. package/registry/blocks/comment-box/comment-box.jsx +198 -0
  138. package/registry/blocks/comment-box/comment-time.jsx +77 -0
  139. package/registry/blocks/comment-box/demo.jsx +141 -0
  140. package/registry/blocks/comment-box/index.js +8 -0
  141. package/registry/blocks/contact-section/README.md +152 -0
  142. package/registry/blocks/contact-section/block.json +11 -0
  143. package/registry/blocks/contact-section/contact-section.jsx +509 -0
  144. package/registry/blocks/contact-section/demo.jsx +93 -0
  145. package/registry/blocks/contact-section/index.js +4 -0
  146. package/registry/blocks/csv-import/README.md +133 -0
  147. package/registry/blocks/csv-import/block.json +13 -0
  148. package/registry/blocks/csv-import/csv-import.jsx +479 -0
  149. package/registry/blocks/csv-import/demo.jsx +106 -0
  150. package/registry/blocks/csv-import/import.js +200 -0
  151. package/registry/blocks/csv-import/index.js +7 -0
  152. package/registry/blocks/data-table/README.md +386 -0
  153. package/registry/blocks/data-table/block.json +10 -0
  154. package/registry/blocks/data-table/data-table.jsx +1164 -0
  155. package/registry/blocks/data-table/demo.jsx +588 -0
  156. package/registry/blocks/data-table/index.js +5 -0
  157. package/registry/blocks/data-table/model.js +169 -0
  158. package/registry/blocks/diagram/README.md +108 -0
  159. package/registry/blocks/diagram/block.json +13 -0
  160. package/registry/blocks/diagram/demo.jsx +113 -0
  161. package/registry/blocks/diagram/diagram.jsx +481 -0
  162. package/registry/blocks/diagram/index.js +7 -0
  163. package/registry/blocks/diagram/mermaid.js +52 -0
  164. package/registry/blocks/diagram/palette.js +313 -0
  165. package/registry/blocks/diagram/source.js +98 -0
  166. package/registry/blocks/doc-editor/README.md +215 -0
  167. package/registry/blocks/doc-editor/block.json +15 -0
  168. package/registry/blocks/doc-editor/demo.jsx +225 -0
  169. package/registry/blocks/doc-editor/doc-editor.jsx +291 -0
  170. package/registry/blocks/doc-editor/doc.js +309 -0
  171. package/registry/blocks/doc-editor/index.js +7 -0
  172. package/registry/blocks/doc-editor/schema.jsx +56 -0
  173. package/registry/blocks/doc-editor/shadcn-classes.js +110 -0
  174. package/registry/blocks/doc-editor/theme.js +82 -0
  175. package/registry/blocks/doc-page/README.md +119 -0
  176. package/registry/blocks/doc-page/autosave.js +104 -0
  177. package/registry/blocks/doc-page/block.json +10 -0
  178. package/registry/blocks/doc-page/demo.jsx +137 -0
  179. package/registry/blocks/doc-page/doc-page.jsx +276 -0
  180. package/registry/blocks/doc-page/index.js +4 -0
  181. package/registry/blocks/email-render/README.md +151 -0
  182. package/registry/blocks/email-render/block.json +14 -0
  183. package/registry/blocks/email-render/demo.jsx +153 -0
  184. package/registry/blocks/email-render/doc-email.jsx +210 -0
  185. package/registry/blocks/email-render/email.jsx +221 -0
  186. package/registry/blocks/email-render/index.js +10 -0
  187. package/registry/blocks/email-render/render.js +50 -0
  188. package/registry/blocks/email-render/theme.js +215 -0
  189. package/registry/blocks/faq/README.md +101 -0
  190. package/registry/blocks/faq/block.json +11 -0
  191. package/registry/blocks/faq/demo.jsx +89 -0
  192. package/registry/blocks/faq/faq.jsx +220 -0
  193. package/registry/blocks/faq/index.js +4 -0
  194. package/registry/blocks/file-upload/README.md +138 -0
  195. package/registry/blocks/file-upload/block.json +10 -0
  196. package/registry/blocks/file-upload/demo.jsx +204 -0
  197. package/registry/blocks/file-upload/file-upload.jsx +635 -0
  198. package/registry/blocks/file-upload/index.js +4 -0
  199. package/registry/blocks/flow-canvas/README.md +420 -0
  200. package/registry/blocks/flow-canvas/block.json +13 -0
  201. package/registry/blocks/flow-canvas/board.js +819 -0
  202. package/registry/blocks/flow-canvas/chrome.jsx +649 -0
  203. package/registry/blocks/flow-canvas/context.jsx +35 -0
  204. package/registry/blocks/flow-canvas/demo.jsx +249 -0
  205. package/registry/blocks/flow-canvas/edges.jsx +107 -0
  206. package/registry/blocks/flow-canvas/flow-canvas.jsx +1329 -0
  207. package/registry/blocks/flow-canvas/index.js +41 -0
  208. package/registry/blocks/flow-canvas/nodes.jsx +392 -0
  209. package/registry/blocks/flow-canvas/theme.js +174 -0
  210. package/registry/blocks/footer/README.md +133 -0
  211. package/registry/blocks/footer/block.json +11 -0
  212. package/registry/blocks/footer/demo.jsx +119 -0
  213. package/registry/blocks/footer/footer.jsx +406 -0
  214. package/registry/blocks/footer/index.js +4 -0
  215. package/registry/blocks/gantt/README.md +220 -0
  216. package/registry/blocks/gantt/block.json +10 -0
  217. package/registry/blocks/gantt/demo.jsx +148 -0
  218. package/registry/blocks/gantt/gantt.jsx +923 -0
  219. package/registry/blocks/gantt/index.js +7 -0
  220. package/registry/blocks/gantt/rows.jsx +468 -0
  221. package/registry/blocks/gantt/schedule.js +278 -0
  222. package/registry/blocks/job-panel/README.md +80 -0
  223. package/registry/blocks/job-panel/block.json +3 -3
  224. package/registry/blocks/job-panel/demo.jsx +230 -3
  225. package/registry/blocks/job-panel/job-panel.jsx +169 -3
  226. package/registry/blocks/json-editor/README.md +133 -0
  227. package/registry/blocks/json-editor/block.json +10 -0
  228. package/registry/blocks/json-editor/demo.jsx +92 -0
  229. package/registry/blocks/json-editor/index.js +7 -0
  230. package/registry/blocks/json-editor/json-editor.jsx +305 -0
  231. package/registry/blocks/json-editor/json-tree.jsx +393 -0
  232. package/registry/blocks/json-editor/tree.js +225 -0
  233. package/registry/blocks/map/README.md +221 -0
  234. package/registry/blocks/map/block.json +14 -0
  235. package/registry/blocks/map/demo.jsx +201 -0
  236. package/registry/blocks/map/geo.js +198 -0
  237. package/registry/blocks/map/index.js +8 -0
  238. package/registry/blocks/map/list.jsx +128 -0
  239. package/registry/blocks/map/map-view.jsx +715 -0
  240. package/registry/blocks/map/maplibre.js +30 -0
  241. package/registry/blocks/map/marks.jsx +146 -0
  242. package/registry/blocks/map/theme.js +142 -0
  243. package/registry/blocks/menubar/README.md +109 -0
  244. package/registry/blocks/menubar/block.json +10 -0
  245. package/registry/blocks/menubar/demo.jsx +128 -0
  246. package/registry/blocks/menubar/index.js +4 -0
  247. package/registry/blocks/menubar/menubar.jsx +199 -0
  248. package/registry/blocks/mind-map/README.md +206 -0
  249. package/registry/blocks/mind-map/block.json +10 -0
  250. package/registry/blocks/mind-map/demo.jsx +103 -0
  251. package/registry/blocks/mind-map/index.js +31 -0
  252. package/registry/blocks/mind-map/mind-map.jsx +1125 -0
  253. package/registry/blocks/mind-map/topic.jsx +143 -0
  254. package/registry/blocks/mind-map/tree.js +645 -0
  255. package/registry/blocks/navigation-menu/README.md +124 -0
  256. package/registry/blocks/navigation-menu/block.json +10 -0
  257. package/registry/blocks/navigation-menu/demo.jsx +84 -0
  258. package/registry/blocks/navigation-menu/index.js +4 -0
  259. package/registry/blocks/navigation-menu/navigation-menu.jsx +448 -0
  260. package/registry/blocks/order-detail/README.md +109 -0
  261. package/registry/blocks/order-detail/block.json +10 -0
  262. package/registry/blocks/order-detail/demo.jsx +88 -0
  263. package/registry/blocks/order-detail/index.js +4 -0
  264. package/registry/blocks/order-detail/order-detail.jsx +354 -0
  265. package/registry/blocks/org-chart/README.md +145 -0
  266. package/registry/blocks/org-chart/block.json +10 -0
  267. package/registry/blocks/org-chart/demo.jsx +94 -0
  268. package/registry/blocks/org-chart/index.js +7 -0
  269. package/registry/blocks/org-chart/org-chart.jsx +631 -0
  270. package/registry/blocks/org-chart/org.js +245 -0
  271. package/registry/blocks/org-chart/person.jsx +96 -0
  272. package/registry/blocks/page-builder/README.md +257 -0
  273. package/registry/blocks/page-builder/block.json +14 -0
  274. package/registry/blocks/page-builder/demo.jsx +145 -0
  275. package/registry/blocks/page-builder/grapes.js +18 -0
  276. package/registry/blocks/page-builder/index.js +11 -0
  277. package/registry/blocks/page-builder/page-builder.jsx +704 -0
  278. package/registry/blocks/page-builder/page-view.jsx +33 -0
  279. package/registry/blocks/page-builder/page.js +118 -0
  280. package/registry/blocks/page-builder/panels.jsx +588 -0
  281. package/registry/blocks/page-builder/sections.js +209 -0
  282. package/registry/blocks/page-builder/theme.js +87 -0
  283. package/registry/blocks/page-builder/thumbs.jsx +189 -0
  284. package/registry/blocks/page-builder/tokens.js +329 -0
  285. package/registry/blocks/payment-methods/README.md +104 -0
  286. package/registry/blocks/payment-methods/block.json +10 -0
  287. package/registry/blocks/payment-methods/demo.jsx +71 -0
  288. package/registry/blocks/payment-methods/index.js +4 -0
  289. package/registry/blocks/payment-methods/payment-methods.jsx +302 -0
  290. package/registry/blocks/pdf-document/README.md +213 -0
  291. package/registry/blocks/pdf-document/block.json +13 -0
  292. package/registry/blocks/pdf-document/demo.jsx +136 -0
  293. package/registry/blocks/pdf-document/index.js +33 -0
  294. package/registry/blocks/pdf-document/money.js +83 -0
  295. package/registry/blocks/pdf-document/pdf-document.jsx +144 -0
  296. package/registry/blocks/pdf-document/primitives.jsx +271 -0
  297. package/registry/blocks/pdf-document/quote.jsx +179 -0
  298. package/registry/blocks/pdf-document/theme.js +193 -0
  299. package/registry/blocks/pdf-viewer/README.md +191 -0
  300. package/registry/blocks/pdf-viewer/block.json +14 -0
  301. package/registry/blocks/pdf-viewer/demo.jsx +252 -0
  302. package/registry/blocks/pdf-viewer/helpers.js +270 -0
  303. package/registry/blocks/pdf-viewer/index.js +8 -0
  304. package/registry/blocks/pdf-viewer/notes.jsx +195 -0
  305. package/registry/blocks/pdf-viewer/pdf-viewer.jsx +1368 -0
  306. package/registry/blocks/pdf-viewer/pdfjs.js +37 -0
  307. package/registry/blocks/pdf-viewer/ssr-shim-end.js +6 -0
  308. package/registry/blocks/pdf-viewer/ssr-shim.js +44 -0
  309. package/registry/blocks/product-detail/README.md +217 -0
  310. package/registry/blocks/product-detail/block.json +10 -0
  311. package/registry/blocks/product-detail/demo.jsx +233 -0
  312. package/registry/blocks/product-detail/index.js +7 -0
  313. package/registry/blocks/product-detail/price.jsx +115 -0
  314. package/registry/blocks/product-detail/product-detail.jsx +348 -0
  315. package/registry/blocks/product-detail/product-media.jsx +102 -0
  316. package/registry/blocks/product-detail/product-options.jsx +144 -0
  317. package/registry/blocks/product-grid/README.md +162 -0
  318. package/registry/blocks/product-grid/block.json +10 -0
  319. package/registry/blocks/product-grid/demo.jsx +255 -0
  320. package/registry/blocks/product-grid/index.js +5 -0
  321. package/registry/blocks/product-grid/product-card.jsx +232 -0
  322. package/registry/blocks/product-grid/product-grid.jsx +235 -0
  323. package/registry/blocks/record-form/README.md +196 -0
  324. package/registry/blocks/record-form/block.json +10 -0
  325. package/registry/blocks/record-form/demo.jsx +525 -0
  326. package/registry/blocks/record-form/index.js +4 -0
  327. package/registry/blocks/record-form/record-fields.jsx +457 -0
  328. package/registry/blocks/record-form/record-form.jsx +415 -0
  329. package/registry/blocks/record-form/rules.js +81 -0
  330. package/registry/blocks/record-notes/README.md +139 -0
  331. package/registry/blocks/record-notes/block.json +10 -0
  332. package/registry/blocks/record-notes/demo.jsx +179 -0
  333. package/registry/blocks/record-notes/index.js +4 -0
  334. package/registry/blocks/record-notes/record-notes.jsx +266 -0
  335. package/registry/blocks/search-dialog/README.md +108 -0
  336. package/registry/blocks/search-dialog/block.json +10 -0
  337. package/registry/blocks/search-dialog/demo.jsx +116 -0
  338. package/registry/blocks/search-dialog/index.js +3 -0
  339. package/registry/blocks/search-dialog/search-dialog.jsx +235 -0
  340. package/registry/blocks/settings-list/README.md +171 -0
  341. package/registry/blocks/settings-list/block.json +10 -0
  342. package/registry/blocks/settings-list/demo.jsx +333 -0
  343. package/registry/blocks/settings-list/index.js +4 -0
  344. package/registry/blocks/settings-list/row-state.jsx +93 -0
  345. package/registry/blocks/settings-list/settings-list.jsx +349 -0
  346. package/registry/blocks/settings-list/settings-matrix.jsx +118 -0
  347. package/registry/blocks/setup-checklist/README.md +142 -0
  348. package/registry/blocks/setup-checklist/block.json +10 -0
  349. package/registry/blocks/setup-checklist/demo.jsx +159 -0
  350. package/registry/blocks/setup-checklist/index.js +4 -0
  351. package/registry/blocks/setup-checklist/setup-checklist.jsx +446 -0
  352. package/registry/blocks/share-dialog/README.md +113 -0
  353. package/registry/blocks/share-dialog/block.json +10 -0
  354. package/registry/blocks/share-dialog/demo.jsx +140 -0
  355. package/registry/blocks/share-dialog/index.js +3 -0
  356. package/registry/blocks/share-dialog/share-dialog.jsx +384 -0
  357. package/registry/blocks/sidebar/README.md +190 -0
  358. package/registry/blocks/sidebar/block.json +43 -0
  359. package/registry/blocks/sidebar/demo.jsx +577 -0
  360. package/registry/blocks/sidebar/index.js +4 -0
  361. package/registry/blocks/sidebar/sidebar-panel.jsx +149 -0
  362. package/registry/blocks/sidebar/sidebar.jsx +864 -0
  363. package/registry/blocks/signature-pad/README.md +137 -0
  364. package/registry/blocks/signature-pad/block.json +13 -0
  365. package/registry/blocks/signature-pad/colors.js +115 -0
  366. package/registry/blocks/signature-pad/demo.jsx +94 -0
  367. package/registry/blocks/signature-pad/index.js +6 -0
  368. package/registry/blocks/signature-pad/pad.js +7 -0
  369. package/registry/blocks/signature-pad/signature-pad.jsx +434 -0
  370. package/registry/blocks/signature-pad/signature.js +68 -0
  371. package/registry/blocks/sketch-canvas/README.md +280 -0
  372. package/registry/blocks/sketch-canvas/block.json +13 -0
  373. package/registry/blocks/sketch-canvas/demo.jsx +159 -0
  374. package/registry/blocks/sketch-canvas/excalidraw.js +40 -0
  375. package/registry/blocks/sketch-canvas/index.js +9 -0
  376. package/registry/blocks/sketch-canvas/palette.js +226 -0
  377. package/registry/blocks/sketch-canvas/sketch-canvas.jsx +1188 -0
  378. package/registry/blocks/sketch-canvas/sketch.js +179 -0
  379. package/registry/blocks/sketch-canvas/ssr-shim-end.js +6 -0
  380. package/registry/blocks/sketch-canvas/ssr-shim.js +45 -0
  381. package/registry/blocks/sketch-canvas/stamps.js +104 -0
  382. package/registry/blocks/sketch-canvas/theme.js +131 -0
  383. package/registry/blocks/spreadsheet/README.md +307 -0
  384. package/registry/blocks/spreadsheet/block.json +13 -0
  385. package/registry/blocks/spreadsheet/cells.jsx +455 -0
  386. package/registry/blocks/spreadsheet/chrome.jsx +325 -0
  387. package/registry/blocks/spreadsheet/demo.jsx +177 -0
  388. package/registry/blocks/spreadsheet/grid.js +387 -0
  389. package/registry/blocks/spreadsheet/index.js +34 -0
  390. package/registry/blocks/spreadsheet/spreadsheet.jsx +1732 -0
  391. package/registry/blocks/spreadsheet/types.js +473 -0
  392. package/registry/blocks/stat-cards/README.md +173 -0
  393. package/registry/blocks/stat-cards/block.json +10 -0
  394. package/registry/blocks/stat-cards/demo.jsx +257 -0
  395. package/registry/blocks/stat-cards/index.js +4 -0
  396. package/registry/blocks/stat-cards/stat-cards.jsx +307 -0
  397. package/registry/blocks/stat-cards/usage.jsx +243 -0
  398. package/registry/blocks/task-list/README.md +176 -0
  399. package/registry/blocks/task-list/block.json +10 -0
  400. package/registry/blocks/task-list/demo.jsx +134 -0
  401. package/registry/blocks/task-list/index.js +5 -0
  402. package/registry/blocks/task-list/task-list.jsx +907 -0
  403. package/registry/blocks/task-model/README.md +455 -0
  404. package/registry/blocks/task-model/block.json +167 -0
  405. package/registry/blocks/task-model/changes.js +89 -0
  406. package/registry/blocks/task-model/dates.js +371 -0
  407. package/registry/blocks/task-model/demo.jsx +215 -0
  408. package/registry/blocks/task-model/dnd.jsx +240 -0
  409. package/registry/blocks/task-model/index.js +97 -0
  410. package/registry/blocks/task-model/model.js +919 -0
  411. package/registry/blocks/task-model/parts.jsx +449 -0
  412. package/registry/blocks/task-model/toolbar.jsx +271 -0
  413. package/registry/blocks/testimonials/README.md +139 -0
  414. package/registry/blocks/testimonials/block.json +11 -0
  415. package/registry/blocks/testimonials/demo.jsx +137 -0
  416. package/registry/blocks/testimonials/index.js +4 -0
  417. package/registry/blocks/testimonials/testimonials.jsx +430 -0
  418. package/registry/blocks/transaction-list/README.md +126 -0
  419. package/registry/blocks/transaction-list/block.json +10 -0
  420. package/registry/blocks/transaction-list/demo.jsx +79 -0
  421. package/registry/blocks/transaction-list/index.js +4 -0
  422. package/registry/blocks/transaction-list/transaction-list.jsx +383 -0
  423. package/registry/blocks/whiteboard/README.md +179 -0
  424. package/registry/blocks/whiteboard/block.json +10 -0
  425. package/registry/blocks/whiteboard/demo.jsx +169 -0
  426. package/registry/blocks/whiteboard/index.js +6 -0
  427. package/registry/blocks/whiteboard/templates.js +150 -0
  428. package/registry/blocks/whiteboard/whiteboard.jsx +721 -0
  429. package/src/cli.mjs +19 -6
  430. package/src/install.mjs +18 -0
  431. package/src/registry.mjs +38 -4
  432. package/src/shadcn.mjs +14 -1
  433. package/templates/next/files/README.md +145 -11
  434. package/templates/next/files/app/globals.css +55 -4
  435. package/templates/next/files/check/run.mjs +3 -3
  436. package/templates/next/files/components/ui/avatar.jsx +141 -0
  437. package/templates/next/files/components/ui/breadcrumb.jsx +149 -0
  438. package/templates/next/files/components/ui/button-group.jsx +70 -0
  439. package/templates/next/files/components/ui/button.jsx +132 -12
  440. package/templates/next/files/components/ui/calendar.jsx +609 -0
  441. package/templates/next/files/components/ui/carousel.jsx +281 -0
  442. package/templates/next/files/components/ui/collapsible.jsx +208 -0
  443. package/templates/next/files/components/ui/combobox.jsx +608 -0
  444. package/templates/next/files/components/ui/command.jsx +577 -0
  445. package/templates/next/files/components/ui/context-menu.jsx +191 -0
  446. package/templates/next/files/components/ui/date-picker.jsx +360 -0
  447. package/templates/next/files/components/ui/distribution.jsx +117 -0
  448. package/templates/next/files/components/ui/feedback.jsx +143 -0
  449. package/templates/next/files/components/ui/field.jsx +115 -3
  450. package/templates/next/files/components/ui/file-input.jsx +234 -0
  451. package/templates/next/files/components/ui/icon.jsx +130 -8
  452. package/templates/next/files/components/ui/image.jsx +143 -0
  453. package/templates/next/files/components/ui/index.js +47 -2
  454. package/templates/next/files/components/ui/input-group.jsx +213 -0
  455. package/templates/next/files/components/ui/input-otp.jsx +170 -0
  456. package/templates/next/files/components/ui/item.jsx +206 -0
  457. package/templates/next/files/components/ui/menu.jsx +454 -82
  458. package/templates/next/files/components/ui/notice.jsx +126 -14
  459. package/templates/next/files/components/ui/pagination.jsx +156 -0
  460. package/templates/next/files/components/ui/popover.jsx +284 -0
  461. package/templates/next/files/components/ui/radio.jsx +193 -0
  462. package/templates/next/files/components/ui/rating.jsx +131 -0
  463. package/templates/next/files/components/ui/resizable.jsx +338 -0
  464. package/templates/next/files/components/ui/scroll-area.jsx +123 -0
  465. package/templates/next/files/components/ui/separator.jsx +119 -0
  466. package/templates/next/files/components/ui/sheet.jsx +96 -11
  467. package/templates/next/files/components/ui/slider.jsx +174 -0
  468. package/templates/next/files/components/ui/stat.jsx +574 -0
  469. package/templates/next/files/components/ui/steps.jsx +338 -0
  470. package/templates/next/files/components/ui/swatch.jsx +33 -0
  471. package/templates/next/files/components/ui/table.jsx +130 -0
  472. package/templates/next/files/components/ui/toast.jsx +440 -0
  473. package/templates/next/files/components/ui/toggle.jsx +195 -0
  474. package/templates/next/files/components/ui/tooltip.jsx +270 -0
  475. package/templates/next/template.json +264 -8
@@ -0,0 +1,450 @@
1
+ "use client";
2
+
3
+ import { useEffect, useId, useRef, useState } from "react";
4
+ import { Avatar, Button, Collapsible, IconButton, Icon, Menu, Spinner, Textarea, Toggle, Tooltip, cx } from "../../ui";
5
+ import { Attachment } from "./attachment";
6
+ import { ChatText } from "./text";
7
+
8
+ // Message and Bubble — one turn of a conversation. A message is its
9
+ // author (avatar, name, time — shown once for a run of messages from the
10
+ // same author), its words in a Bubble, its files, its state (sending,
11
+ // streaming, failed with Retry) and the actions that appear on hover or
12
+ // focus (copy, retry, a Menu of the surface's own). An assistant reply can
13
+ // carry its reasoning (folded under "Thought for 4s") and take a thumbs up
14
+ // or down; a user message can be edited in place.
15
+
16
+ /** The text of a message: `content` when it is a string, else the text
17
+ * parts of `parts` / `content` ([{ type: "text", text }]), joined. */
18
+ export function textOf(message) {
19
+ const c = message?.content;
20
+ if (typeof c === "string") return c;
21
+ const parts = Array.isArray(c) ? c : Array.isArray(message?.parts) ? message.parts : [];
22
+ return parts
23
+ .filter((p) => p && (p.type === "text" || typeof p === "string"))
24
+ .map((p) => (typeof p === "string" ? p : p.text ?? ""))
25
+ .join("");
26
+ }
27
+
28
+ export function toDate(value) {
29
+ if (value == null || value === "") return null;
30
+ const d = value instanceof Date ? value : new Date(value);
31
+ return Number.isNaN(d.getTime()) ? null : d;
32
+ }
33
+
34
+ export function formatTime(date) {
35
+ return date.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" });
36
+ }
37
+
38
+ /**
39
+ * The container for a message's words.
40
+ * from user · assistant — user bubbles are filled and sit at the end
41
+ * variant bubble · plain (assistant text on the page, no box)
42
+ * first / last its place in a run from one author: the corner on the
43
+ * author's side tightens between messages of one run
44
+ * text a string, drawn by the safe renderer; or pass children
45
+ */
46
+ export function Bubble({ from = "assistant", variant = "bubble", first = true, last = true, text, streaming, className, children }) {
47
+ const user = from === "user";
48
+ const plain = !user && variant === "plain";
49
+ const caret = streaming ? (
50
+ <span aria-hidden="true" className="ml-0.5 inline-block h-[1em] w-1.5 translate-y-0.5 animate-pulse rounded-sm bg-current opacity-60" />
51
+ ) : null;
52
+ const body = text !== undefined ? <ChatText text={text} tone={user ? "primary" : undefined} trailing={caret} /> : children;
53
+ return (
54
+ <div
55
+ className={cx(
56
+ "min-w-0 max-w-full text-sm leading-relaxed",
57
+ plain
58
+ ? "py-0.5"
59
+ : cx(
60
+ "rounded-2xl px-3.5 py-2.5",
61
+ user ? "bg-primary text-primary-foreground" : "bg-muted text-foreground",
62
+ user ? [!first && "rounded-tr-md", !last && "rounded-br-md"] : [!first && "rounded-tl-md", !last && "rounded-bl-md"],
63
+ ),
64
+ className,
65
+ )}
66
+ >
67
+ {body}
68
+ {text === undefined ? caret : null}
69
+ </div>
70
+ );
71
+ }
72
+
73
+ export function formatSeconds(s) {
74
+ const n = Math.max(0, Math.round(Number(s) || 0));
75
+ return n < 60 ? `${n}s` : `${Math.floor(n / 60)}m ${n % 60}s`;
76
+ }
77
+
78
+ /** The folding reasoning over an assistant reply: "Thinking…" while it
79
+ * streams with no answer yet, then "Thought for 4s". Closed by default. */
80
+ function Reasoning({ text, seconds, thinking }) {
81
+ const [open, setOpen] = useState(false);
82
+ const title = thinking ? "Thinking…" : seconds ? `Thought for ${formatSeconds(seconds)}` : "Reasoning";
83
+ return (
84
+ <Collapsible
85
+ open={open}
86
+ onOpenChange={setOpen}
87
+ chevron="right"
88
+ title={<span className={cx("font-normal text-muted-foreground", thinking && "animate-pulse motion-reduce:animate-none")}>{title}</span>}
89
+ className="w-full max-w-prose"
90
+ >
91
+ <div className="border-l-2 border-border pl-3 text-xs leading-relaxed text-muted-foreground">
92
+ {text ? <ChatText text={text} /> : <span>Nothing to show yet.</span>}
93
+ </div>
94
+ </Collapsible>
95
+ );
96
+ }
97
+
98
+ /** A sent message's words, back in a field: Enter (or Save) saves through
99
+ * `onSave(text)`, Esc (or Cancel) leaves it as it was. A failed save keeps
100
+ * the field open with the reason under it. */
101
+ function EditBox({ initial, onSave, onCancel }) {
102
+ const id = useId();
103
+ const [text, setText] = useState(initial);
104
+ const [saving, setSaving] = useState(false);
105
+ const [error, setError] = useState(null);
106
+
107
+ async function save() {
108
+ const next = text.trim();
109
+ if (!next || saving) return;
110
+ if (next === initial.trim()) return onCancel();
111
+ setSaving(true);
112
+ setError(null);
113
+ let r;
114
+ try {
115
+ r = await onSave(next);
116
+ } catch (e) {
117
+ r = { ok: false, error: e?.message ?? String(e) };
118
+ }
119
+ setSaving(false);
120
+ if (r && r.ok === false) setError(r.error ?? "The change was not saved");
121
+ }
122
+
123
+ return (
124
+ <div className="grid w-[32rem] max-w-full gap-2">
125
+ <label htmlFor={id} className="sr-only">
126
+ Edit message
127
+ </label>
128
+ <Textarea
129
+ id={id}
130
+ autoFocus
131
+ autoGrow
132
+ minRows={2}
133
+ maxRows={10}
134
+ value={text}
135
+ invalid={Boolean(error)}
136
+ aria-describedby={error ? `${id}-error` : undefined}
137
+ onChange={(e) => setText(e.target.value)}
138
+ onKeyDown={(e) => {
139
+ if (e.key === "Escape") {
140
+ e.preventDefault();
141
+ onCancel();
142
+ } else if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
143
+ e.preventDefault();
144
+ save();
145
+ }
146
+ }}
147
+ />
148
+ {error ? (
149
+ <p id={`${id}-error`} role="alert" className="text-xs text-destructive">
150
+ {error}
151
+ </p>
152
+ ) : null}
153
+ <div className="flex justify-end gap-2">
154
+ <Button size="sm" variant="quiet" onClick={onCancel} disabled={saving}>
155
+ Cancel
156
+ </Button>
157
+ <Button size="sm" variant="primary" onClick={save} loading={saving} disabled={!text.trim()}>
158
+ Save
159
+ </Button>
160
+ </div>
161
+ </div>
162
+ );
163
+ }
164
+
165
+ function Thinking({ label = "Thinking" }) {
166
+ return (
167
+ <span role="status" className="inline-flex h-5 items-center gap-1" aria-label={label}>
168
+ {[0, 150, 300].map((d) => (
169
+ <span key={d} aria-hidden="true" className="size-1.5 animate-pulse rounded-full bg-muted-foreground" style={{ animationDelay: `${d}ms` }} />
170
+ ))}
171
+ </span>
172
+ );
173
+ }
174
+
175
+ /**
176
+ * One message.
177
+ * message { id, role, content | parts, createdAt?, status?, error?,
178
+ * author?: { name, src }, attachments?: [{ id?, name, size?, type?, url? }] }
179
+ * status: sending · streaming · failed · (anything else: sent)
180
+ * first / last its place in a run from one author (MessageScroller
181
+ * works these out); `first` shows the avatar, name and time
182
+ * latest the newest reply: its actions stay in view, not only on hover
183
+ * message.reasoning (string) · message.reasoningDuration (seconds) — the
184
+ * folding reasoning over an assistant reply
185
+ * message.feedback "up" · "down" — the thumbs already chosen
186
+ * onFeedback(message, "up" | "down" | null) thumbs on assistant replies;
187
+ * may return { ok: false, error }: the choice goes back
188
+ * onEdit(message, text) an edit button on user messages; may return
189
+ * { ok: false, error }: the field stays open with the reason
190
+ * editing / onEditingChange(next) control the edit (ChatThread does,
191
+ * for ArrowUp in an empty composer)
192
+ */
193
+ export function Message({
194
+ message,
195
+ first = true,
196
+ last = true,
197
+ latest = false,
198
+ variant = "plain",
199
+ assistantName = "Assistant",
200
+ assistantAvatar,
201
+ userName = "You",
202
+ userAvatar,
203
+ showUserAvatar = false,
204
+ onRetry,
205
+ onRegenerate,
206
+ onCopy,
207
+ actionsOf,
208
+ onFeedback,
209
+ onEdit,
210
+ editing: editingProp,
211
+ onEditingChange,
212
+ formatTime: timeOf = formatTime,
213
+ className,
214
+ }) {
215
+ const [copied, setCopied] = useState(false);
216
+ const copiedTimer = useRef(0);
217
+ useEffect(() => () => clearTimeout(copiedTimer.current), []);
218
+ const [ownEditing, setOwnEditing] = useState(false);
219
+ const editing = editingProp ?? ownEditing;
220
+ const setEditing = (next) => {
221
+ if (editingProp === undefined) setOwnEditing(next);
222
+ onEditingChange?.(next);
223
+ };
224
+ // The thumbs: shown at once, taken back if the save fails.
225
+ const [choice, setChoice] = useState(undefined);
226
+ const [feedbackError, setFeedbackError] = useState(null);
227
+ const [feedbackBusy, setFeedbackBusy] = useState(false);
228
+ const given = message?.feedback ?? null;
229
+ useEffect(() => setChoice(undefined), [given]);
230
+
231
+ if (!message) return null;
232
+ const role = message.role === "user" ? "user" : message.role === "system" ? "system" : "assistant";
233
+ const text = textOf(message);
234
+ const status = message.status;
235
+ const streaming = status === "streaming";
236
+ const failed = status === "failed";
237
+ const sending = status === "sending";
238
+ const date = toDate(message.createdAt);
239
+
240
+ if (role === "system") {
241
+ return (
242
+ <div className={cx("flex justify-center px-2 py-1", className)}>
243
+ <ChatText text={text} className="max-w-prose text-balance text-center text-xs text-muted-foreground" />
244
+ </div>
245
+ );
246
+ }
247
+
248
+ const user = role === "user";
249
+ const name = message.author?.name ?? (user ? userName : assistantName);
250
+ const src = message.author?.src ?? (user ? userAvatar : assistantAvatar);
251
+ const showAvatar = !user || showUserAvatar;
252
+ const files = (message.attachments ?? []).filter(Boolean);
253
+
254
+ async function copy() {
255
+ try {
256
+ if (onCopy) await onCopy(message, text);
257
+ else await navigator.clipboard.writeText(text);
258
+ setCopied(true);
259
+ clearTimeout(copiedTimer.current);
260
+ copiedTimer.current = setTimeout(() => setCopied(false), 1500);
261
+ } catch {
262
+ // No clipboard (an insecure origin): nothing to say beyond not ticking.
263
+ }
264
+ }
265
+
266
+ const feedback = choice !== undefined ? choice : given;
267
+ async function giveFeedback(v) {
268
+ const before = feedback;
269
+ const next = before === v ? null : v;
270
+ setChoice(next);
271
+ setFeedbackError(null);
272
+ setFeedbackBusy(true);
273
+ let r;
274
+ try {
275
+ r = await onFeedback(message, next);
276
+ } catch (e) {
277
+ r = { ok: false, error: e?.message ?? String(e) };
278
+ }
279
+ setFeedbackBusy(false);
280
+ if (r && r.ok === false) {
281
+ setChoice(before);
282
+ setFeedbackError(r.error ?? "Your feedback was not saved");
283
+ }
284
+ }
285
+
286
+ const reasoning = typeof message.reasoning === "string" ? message.reasoning : null;
287
+ const thinking = streaming && !text;
288
+
289
+ const extra = actionsOf?.(message) ?? [];
290
+ const canRegenerate = !user && onRegenerate && !streaming;
291
+ const hasActions = !sending && !streaming && !failed;
292
+
293
+ // The actions sit under the words on touch, where they always show.
294
+ const toolbar =
295
+ hasActions && (text || extra.length) ? (
296
+ <div
297
+ className={cx(
298
+ "-mx-1 flex items-center gap-0.5 transition-opacity",
299
+ latest && !user
300
+ ? "opacity-100"
301
+ : "pointer-events-none opacity-0 group-hover/message:pointer-events-auto group-hover/message:opacity-100 group-focus-within/message:pointer-events-auto group-focus-within/message:opacity-100 [@media(hover:none)]:pointer-events-auto [@media(hover:none)]:opacity-100",
302
+ // On a pointer that hovers, the actions float instead of holding
303
+ // a row open under every message: beside a user's bubble, over
304
+ // the top corner of an assistant's (the newest reply keeps its row).
305
+ user
306
+ ? "flex-row-reverse [@media(hover:hover)]:absolute [@media(hover:hover)]:right-full [@media(hover:hover)]:top-0 [@media(hover:hover)]:mx-0 [@media(hover:hover)]:mr-1"
307
+ : !latest &&
308
+ "[@media(hover:hover)]:absolute [@media(hover:hover)]:-top-3 [@media(hover:hover)]:right-0 [@media(hover:hover)]:z-10 [@media(hover:hover)]:mx-0 [@media(hover:hover)]:rounded-md [@media(hover:hover)]:border [@media(hover:hover)]:border-border [@media(hover:hover)]:bg-card [@media(hover:hover)]:p-0.5 [@media(hover:hover)]:shadow-sm",
309
+ )}
310
+ >
311
+ {text ? (
312
+ <Tooltip label={copied ? "Copied" : "Copy"}>
313
+ <IconButton icon={copied ? "check" : "copy"} label={copied ? "Copied" : "Copy message"} size="sm" onClick={copy} />
314
+ </Tooltip>
315
+ ) : null}
316
+ {canRegenerate ? (
317
+ <Tooltip label="Regenerate">
318
+ <IconButton icon="refresh" label="Regenerate reply" size="sm" onClick={() => onRegenerate(message)} />
319
+ </Tooltip>
320
+ ) : null}
321
+ {!user && onFeedback && text ? (
322
+ <>
323
+ <Toggle
324
+ variant="quiet"
325
+ size="sm"
326
+ icon="thumbs-up"
327
+ label="Good reply"
328
+ pressed={feedback === "up"}
329
+ disabled={feedbackBusy}
330
+ onPressedChange={() => giveFeedback("up")}
331
+ />
332
+ <Toggle
333
+ variant="quiet"
334
+ size="sm"
335
+ icon="thumbs-down"
336
+ label="Bad reply"
337
+ pressed={feedback === "down"}
338
+ disabled={feedbackBusy}
339
+ onPressedChange={() => giveFeedback("down")}
340
+ />
341
+ </>
342
+ ) : null}
343
+ {user && onEdit && text ? (
344
+ <Tooltip label="Edit">
345
+ <IconButton icon="pencil" label="Edit message" size="sm" onClick={() => setEditing(true)} />
346
+ </Tooltip>
347
+ ) : null}
348
+ <Menu items={extra} label="More actions for this message" align={user ? "end" : "start"} />
349
+ </div>
350
+ ) : null;
351
+
352
+ return (
353
+ <article
354
+ aria-label={`Message from ${name}`}
355
+ data-role={role}
356
+ data-status={status || undefined}
357
+ className={cx("group/message relative flex gap-2.5", user && "flex-row-reverse", first ? "mt-4" : "mt-1", className)}
358
+ >
359
+ {showAvatar ? (
360
+ <div className="w-8 shrink-0">
361
+ {first ? <Avatar name={name} src={src} size="sm" shape={user ? "round" : "square"} /> : null}
362
+ </div>
363
+ ) : null}
364
+ <div
365
+ className={cx(
366
+ "flex min-w-0 flex-col gap-1",
367
+ user ? "max-w-[85%] items-end" : variant === "plain" ? "flex-1 items-start" : "max-w-[85%] items-start",
368
+ )}
369
+ >
370
+ {first ? (
371
+ <p className={cx("flex items-baseline gap-2 text-xs", user && "flex-row-reverse")}>
372
+ <span className="font-medium text-foreground">{name}</span>
373
+ {date ? (
374
+ <time dateTime={date.toISOString()} suppressHydrationWarning className="text-muted-foreground">
375
+ {timeOf(date)}
376
+ </time>
377
+ ) : null}
378
+ </p>
379
+ ) : null}
380
+
381
+ {!user && (reasoning || (thinking && reasoning !== null)) ? (
382
+ <Reasoning text={reasoning} seconds={message.reasoningDuration} thinking={thinking} />
383
+ ) : null}
384
+
385
+ {editing && user && onEdit ? (
386
+ <EditBox
387
+ initial={text}
388
+ onCancel={() => setEditing(false)}
389
+ onSave={async (next) => {
390
+ const r = await onEdit(message, next);
391
+ if (!(r && r.ok === false)) setEditing(false);
392
+ return r;
393
+ }}
394
+ />
395
+ ) : text || (streaming && reasoning === null) ? (
396
+ <div className={cx("flex max-w-full flex-col gap-1", user ? "relative items-end" : "items-start")}>
397
+ <Bubble
398
+ from={role}
399
+ variant={variant}
400
+ first={first}
401
+ last={last}
402
+ text={text || undefined}
403
+ streaming={streaming && Boolean(text)}
404
+ className={cx(failed && "opacity-70", sending && "opacity-80")}
405
+ >
406
+ {text ? null : <Thinking />}
407
+ </Bubble>
408
+ {toolbar}
409
+ </div>
410
+ ) : null}
411
+
412
+ {feedbackError ? (
413
+ <p role="alert" className="text-xs text-destructive">
414
+ {feedbackError}
415
+ </p>
416
+ ) : null}
417
+
418
+ {files.length ? (
419
+ <ul className={cx("flex max-w-full flex-wrap gap-2", user && "justify-end")}>
420
+ {files.map((f, i) => (
421
+ <li key={f.id ?? `${f.name}-${i}`} className="min-w-0 max-w-full">
422
+ <Attachment name={f.name} size={f.size} type={f.type} url={f.url} href={f.href} error={f.error} />
423
+ </li>
424
+ ))}
425
+ </ul>
426
+ ) : null}
427
+
428
+ {sending ? (
429
+ <p className="flex items-center gap-1.5 text-xs text-muted-foreground">
430
+ <Spinner size="sm" label={null} className="size-3" />
431
+ Sending…
432
+ </p>
433
+ ) : null}
434
+
435
+ {failed ? (
436
+ <div role="alert" className="flex flex-wrap items-center gap-x-2 text-xs text-destructive">
437
+ <Icon name="warning" size={14} />
438
+ <span>{message.error ? `Not sent: ${message.error}` : "Not sent"}</span>
439
+ {onRetry ? (
440
+ <Button variant="link" size="sm" className="min-h-9 text-xs" onClick={() => onRetry(message)}>
441
+ Retry
442
+ </Button>
443
+ ) : null}
444
+ </div>
445
+ ) : null}
446
+
447
+ </div>
448
+ </article>
449
+ );
450
+ }
@@ -0,0 +1,247 @@
1
+ "use client";
2
+
3
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
4
+ import { Button, ScrollArea, Separator, Spinner, cx } from "../../ui";
5
+ import { Message, textOf, toDate } from "./message";
6
+
7
+ // MessageScroller — the conversation's scrolling list. It keeps to the
8
+ // bottom while messages arrive and stream, unless the reader has scrolled
9
+ // up: then it stays where they are and offers "Jump to latest". It puts a
10
+ // day between messages from different days, works out which messages run
11
+ // together from one author, loads older messages when the reader reaches
12
+ // the top (keeping their place), and says each finished assistant reply
13
+ // once to a screen reader.
14
+
15
+ const NEAR_BOTTOM_PX = 64;
16
+ const NEAR_TOP_PX = 120;
17
+ const RUN_GAP_MS = 5 * 60 * 1000;
18
+
19
+ // useLayoutEffect warns on the server; the scroll work only matters in a browser.
20
+ const useIsoLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
21
+
22
+ // Days are the reader's own, but the server does not know their time
23
+ // zone: the first render (server and hydration alike) groups by UTC, and
24
+ // the browser regroups by local days once it is running. Same markup on
25
+ // both sides, so no hydration mismatch.
26
+ function dayKey(d, local) {
27
+ return local ? `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}` : d.toISOString().slice(0, 10);
28
+ }
29
+
30
+ export function formatDay(date, now = new Date()) {
31
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
32
+ const that = new Date(date.getFullYear(), date.getMonth(), date.getDate());
33
+ const days = Math.round((today - that) / 86400000);
34
+ if (days === 0) return "Today";
35
+ if (days === 1) return "Yesterday";
36
+ return date.toLocaleDateString(undefined, {
37
+ weekday: "short",
38
+ day: "numeric",
39
+ month: "short",
40
+ year: date.getFullYear() === now.getFullYear() ? undefined : "numeric",
41
+ });
42
+ }
43
+
44
+ function authorKey(m) {
45
+ return `${m.role}|${m.author?.name ?? ""}`;
46
+ }
47
+
48
+ /** Messages with their day separators and run positions worked out. */
49
+ function layout(messages, idOf, local) {
50
+ const out = [];
51
+ let prev = null;
52
+ let prevDay = null;
53
+ messages.forEach((m, i) => {
54
+ const date = toDate(m.createdAt);
55
+ const day = date ? dayKey(date, local) : prevDay;
56
+ if (date && day !== prevDay) out.push({ type: "day", key: `day-${day}`, date });
57
+ const prevDate = prev ? toDate(prev.createdAt) : null;
58
+ const joins =
59
+ prev &&
60
+ m.role !== "system" &&
61
+ authorKey(prev) === authorKey(m) &&
62
+ day === prevDay &&
63
+ !(date && prevDate && date - prevDate > RUN_GAP_MS);
64
+ if (joins) out[out.length - 1].last = false;
65
+ out.push({ type: "message", key: idOf(m, i), message: m, first: !joins, last: true });
66
+ prev = m;
67
+ prevDay = day;
68
+ });
69
+ // The newest reply keeps its actions in view: it is the one people copy
70
+ // or regenerate.
71
+ for (let i = out.length - 1; i >= 0; i--) {
72
+ if (out[i].type === "message" && out[i].message.role === "assistant") {
73
+ out[i].latest = true;
74
+ break;
75
+ }
76
+ }
77
+ return out;
78
+ }
79
+
80
+ export function MessageScroller({
81
+ messages,
82
+ renderMessage,
83
+ idOf = (m, i) => String(m.id ?? i),
84
+ onLoadMore,
85
+ hasMore = false,
86
+ loadingMore = false,
87
+ loadMoreLabel = "Load earlier messages",
88
+ jumpLabel = "Jump to latest",
89
+ emptyState = null,
90
+ formatDay: dayOf = formatDay,
91
+ announce = (m) => `${m.author?.name ?? "Assistant"} replied: ${textOf(m).slice(0, 280)}`,
92
+ label = "Conversation",
93
+ className,
94
+ children,
95
+ }) {
96
+ const scroller = useRef(null);
97
+ const atBottom = useRef(true);
98
+ const anchor = useRef(null);
99
+ const settled = useRef(new Map());
100
+ const primed = useRef(false);
101
+ const [away, setAway] = useState(false);
102
+ const [unseen, setUnseen] = useState(false);
103
+ const [said, setSaid] = useState("");
104
+ const [local, setLocal] = useState(false);
105
+ useEffect(() => setLocal(true), []);
106
+
107
+ const list = messages ?? [];
108
+ const rows = layout(list, idOf, local);
109
+ const lastMsg = list[list.length - 1];
110
+ const firstId = list.length ? idOf(list[0], 0) : null;
111
+ // What changes when the conversation grows at the bottom: the last
112
+ // message, its status, and how much of it has streamed in.
113
+ const tail = lastMsg ? `${idOf(lastMsg, list.length - 1)}|${lastMsg.status ?? ""}|${textOf(lastMsg).length}` : "";
114
+
115
+ function toBottom(smooth) {
116
+ const el = scroller.current;
117
+ if (!el) return;
118
+ el.scrollTo({ top: el.scrollHeight, behavior: smooth ? "smooth" : "auto" });
119
+ }
120
+
121
+ function onScroll() {
122
+ const el = scroller.current;
123
+ if (!el) return;
124
+ const bottom = el.scrollHeight - el.scrollTop - el.clientHeight <= NEAR_BOTTOM_PX;
125
+ atBottom.current = bottom;
126
+ setAway(!bottom);
127
+ if (bottom) setUnseen(false);
128
+ if (el.scrollTop <= NEAR_TOP_PX && hasMore && !loadingMore && onLoadMore) loadMore();
129
+ }
130
+
131
+ function loadMore() {
132
+ const el = scroller.current;
133
+ if (el) anchor.current = { id: firstId, height: el.scrollHeight, top: el.scrollTop };
134
+ onLoadMore?.();
135
+ }
136
+
137
+ // Older messages landed above: keep the reader on the message they were reading.
138
+ useIsoLayoutEffect(() => {
139
+ const el = scroller.current;
140
+ const a = anchor.current;
141
+ if (!el || !a || a.id === firstId) return;
142
+ el.scrollTop = a.top + (el.scrollHeight - a.height);
143
+ anchor.current = null;
144
+ }, [firstId]);
145
+
146
+ // Newer messages: follow them if the reader was at the bottom, or if the
147
+ // newest is their own; otherwise flag that there is something below.
148
+ useIsoLayoutEffect(() => {
149
+ if (!lastMsg) {
150
+ atBottom.current = true;
151
+ setAway(false);
152
+ setUnseen(false);
153
+ return;
154
+ }
155
+ if (!primed.current) {
156
+ primed.current = true;
157
+ toBottom(false);
158
+ return;
159
+ }
160
+ if (atBottom.current || lastMsg.role === "user") {
161
+ toBottom(false);
162
+ atBottom.current = true;
163
+ setAway(false);
164
+ setUnseen(false);
165
+ } else {
166
+ setUnseen(true);
167
+ }
168
+ }, [tail]); // eslint-disable-line react-hooks/exhaustive-deps
169
+
170
+ // Say a reply once, when it is whole — never token by token.
171
+ useEffect(() => {
172
+ const seen = settled.current;
173
+ const first = seen.size === 0;
174
+ list.forEach((m, i) => {
175
+ const id = idOf(m, i);
176
+ const done = m.status !== "streaming" && m.status !== "sending";
177
+ const was = seen.get(id);
178
+ seen.set(id, done);
179
+ if (!first && m.role === "assistant" && done && was !== true && textOf(m)) setSaid(announce(m));
180
+ });
181
+ }, [tail]); // eslint-disable-line react-hooks/exhaustive-deps
182
+
183
+ const draw = renderMessage ?? ((m, ctx) => <Message message={m} first={ctx.first} last={ctx.last} latest={ctx.latest} />);
184
+
185
+ return (
186
+ <div className={cx("relative flex min-h-0 flex-col", className)}>
187
+ <ScrollArea
188
+ ref={scroller}
189
+ onScroll={onScroll}
190
+ label={label}
191
+ aria-busy={loadingMore || undefined}
192
+ className="flex-1"
193
+ >
194
+ <div className="flex min-h-full flex-col justify-end px-3 py-4 sm:px-4">
195
+ {hasMore || loadingMore ? (
196
+ <div className="flex justify-center pb-3">
197
+ {loadingMore ? (
198
+ <span className="flex min-h-9 items-center gap-2 text-xs text-muted-foreground">
199
+ <Spinner size="sm" label={null} />
200
+ Loading earlier messages…
201
+ </span>
202
+ ) : (
203
+ <Button variant="quiet" size="sm" onClick={loadMore}>
204
+ {loadMoreLabel}
205
+ </Button>
206
+ )}
207
+ </div>
208
+ ) : null}
209
+ {list.length === 0 && !loadingMore ? emptyState : null}
210
+ <ol className="flex flex-col [&>li:first-child>*]:mt-0">
211
+ {rows.map((row) =>
212
+ row.type === "day" ? (
213
+ <li key={row.key} className="mt-6 first:mt-0">
214
+ <Separator label={<span suppressHydrationWarning>{dayOf(row.date)}</span>} />
215
+ </li>
216
+ ) : (
217
+ <li key={row.key} className="flex flex-col">
218
+ {draw(row.message, { first: row.first, last: row.last, latest: Boolean(row.latest) })}
219
+ </li>
220
+ ),
221
+ )}
222
+ </ol>
223
+ {children}
224
+ </div>
225
+ </ScrollArea>
226
+ {away ? (
227
+ <div className="pointer-events-none absolute inset-x-0 bottom-3 flex justify-center">
228
+ <Button
229
+ variant="secondary"
230
+ size="sm"
231
+ icon="chevron-down"
232
+ className="pointer-events-auto rounded-full shadow-md"
233
+ onClick={() => {
234
+ toBottom(true);
235
+ setUnseen(false);
236
+ }}
237
+ >
238
+ {unseen ? "New messages" : jumpLabel}
239
+ </Button>
240
+ </div>
241
+ ) : null}
242
+ <p aria-live="polite" className="sr-only">
243
+ {said}
244
+ </p>
245
+ </div>
246
+ );
247
+ }