makersclaw 0.1.0 → 0.3.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 (478) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +41 -1
  3. package/THIRD-PARTY-NOTICES.md +180 -0
  4. package/package.json +15 -8
  5. package/r/annotate.json +62 -0
  6. package/r/approval-request.json +52 -0
  7. package/r/audio-player.json +74 -0
  8. package/r/auth-form.json +55 -0
  9. package/r/board.json +55 -0
  10. package/r/calendar-view.json +105 -0
  11. package/r/cart.json +62 -0
  12. package/r/chart.json +83 -0
  13. package/r/chat.json +95 -0
  14. package/r/code-editor.json +94 -0
  15. package/r/comment-box.json +64 -0
  16. package/r/contact-section.json +53 -0
  17. package/r/core.json +341 -0
  18. package/r/csv-import.json +63 -0
  19. package/r/data-table.json +69 -0
  20. package/r/diagram.json +74 -0
  21. package/r/doc-editor.json +82 -0
  22. package/r/doc-page.json +61 -0
  23. package/r/email-render.json +73 -0
  24. package/r/faq.json +50 -0
  25. package/r/file-upload.json +54 -0
  26. package/r/flow-canvas.json +93 -0
  27. package/r/footer.json +52 -0
  28. package/r/gantt.json +70 -0
  29. package/r/job-panel.json +56 -0
  30. package/r/json-editor.json +66 -0
  31. package/r/map.json +89 -0
  32. package/r/menubar.json +52 -0
  33. package/r/mind-map.json +67 -0
  34. package/r/navigation-menu.json +52 -0
  35. package/r/order-detail.json +54 -0
  36. package/r/org-chart.json +66 -0
  37. package/r/page-builder.json +105 -0
  38. package/r/payment-methods.json +51 -0
  39. package/r/pdf-document.json +79 -0
  40. package/r/pdf-viewer.json +88 -0
  41. package/r/product-detail.json +76 -0
  42. package/r/product-grid.json +65 -0
  43. package/r/record-form.json +67 -0
  44. package/r/record-notes.json +55 -0
  45. package/r/registry.json +3612 -0
  46. package/r/search-dialog.json +51 -0
  47. package/r/settings-list.json +66 -0
  48. package/r/setup-checklist.json +51 -0
  49. package/r/share-dialog.json +52 -0
  50. package/r/sidebar.json +69 -0
  51. package/r/signature-pad.json +72 -0
  52. package/r/sketch-canvas.json +99 -0
  53. package/r/spreadsheet.json +90 -0
  54. package/r/stat-cards.json +66 -0
  55. package/r/task-list.json +63 -0
  56. package/r/task-model.json +98 -0
  57. package/r/testimonials.json +52 -0
  58. package/r/transaction-list.json +53 -0
  59. package/r/whiteboard.json +66 -0
  60. package/registry/blocks/annotate/README.md +179 -0
  61. package/registry/blocks/annotate/annotate-data.js +250 -0
  62. package/registry/blocks/annotate/annotate.jsx +646 -0
  63. package/registry/blocks/annotate/block.json +10 -0
  64. package/registry/blocks/annotate/demo.jsx +135 -0
  65. package/registry/blocks/annotate/index.js +15 -0
  66. package/registry/blocks/approval-request/README.md +116 -0
  67. package/registry/blocks/approval-request/approval-request.jsx +386 -0
  68. package/registry/blocks/approval-request/block.json +10 -0
  69. package/registry/blocks/approval-request/demo.jsx +127 -0
  70. package/registry/blocks/approval-request/index.js +4 -0
  71. package/registry/blocks/audio-player/README.md +167 -0
  72. package/registry/blocks/audio-player/audio-player.jsx +623 -0
  73. package/registry/blocks/audio-player/block.json +14 -0
  74. package/registry/blocks/audio-player/colors.js +109 -0
  75. package/registry/blocks/audio-player/demo.jsx +209 -0
  76. package/registry/blocks/audio-player/index.js +6 -0
  77. package/registry/blocks/audio-player/time.js +67 -0
  78. package/registry/blocks/audio-player/wavesurfer.js +9 -0
  79. package/registry/blocks/auth-form/README.md +126 -0
  80. package/registry/blocks/auth-form/auth-form.jsx +568 -0
  81. package/registry/blocks/auth-form/block.json +10 -0
  82. package/registry/blocks/auth-form/demo.jsx +131 -0
  83. package/registry/blocks/auth-form/index.js +3 -0
  84. package/registry/blocks/board/README.md +64 -13
  85. package/registry/blocks/board/block.json +2 -2
  86. package/registry/blocks/board/board.jsx +237 -94
  87. package/registry/blocks/board/demo.jsx +103 -24
  88. package/registry/blocks/calendar-view/README.md +180 -0
  89. package/registry/blocks/calendar-view/agenda.jsx +147 -0
  90. package/registry/blocks/calendar-view/block.json +76 -0
  91. package/registry/blocks/calendar-view/calendar-model.js +442 -0
  92. package/registry/blocks/calendar-view/calendar.jsx +627 -0
  93. package/registry/blocks/calendar-view/demo.jsx +195 -0
  94. package/registry/blocks/calendar-view/draft.jsx +49 -0
  95. package/registry/blocks/calendar-view/index.js +39 -0
  96. package/registry/blocks/calendar-view/month.jsx +346 -0
  97. package/registry/blocks/calendar-view/paint.js +69 -0
  98. package/registry/blocks/calendar-view/time-grid.jsx +527 -0
  99. package/registry/blocks/calendar-view/tray.jsx +87 -0
  100. package/registry/blocks/cart/README.md +146 -0
  101. package/registry/blocks/cart/block.json +10 -0
  102. package/registry/blocks/cart/cart.jsx +597 -0
  103. package/registry/blocks/cart/demo.jsx +98 -0
  104. package/registry/blocks/cart/index.js +4 -0
  105. package/registry/blocks/cart/money.js +28 -0
  106. package/registry/blocks/chart/README.md +316 -0
  107. package/registry/blocks/chart/block.json +10 -0
  108. package/registry/blocks/chart/cartesian.jsx +642 -0
  109. package/registry/blocks/chart/core.jsx +262 -0
  110. package/registry/blocks/chart/demo.jsx +357 -0
  111. package/registry/blocks/chart/heatmap.jsx +336 -0
  112. package/registry/blocks/chart/index.js +8 -0
  113. package/registry/blocks/chart/pie.jsx +245 -0
  114. package/registry/blocks/chart/sparkline.jsx +88 -0
  115. package/registry/blocks/chat/README.md +273 -0
  116. package/registry/blocks/chat/attachment.jsx +95 -0
  117. package/registry/blocks/chat/block.json +10 -0
  118. package/registry/blocks/chat/chat.jsx +132 -0
  119. package/registry/blocks/chat/composer.jsx +364 -0
  120. package/registry/blocks/chat/demo.jsx +578 -0
  121. package/registry/blocks/chat/header.jsx +34 -0
  122. package/registry/blocks/chat/index.js +9 -0
  123. package/registry/blocks/chat/message.jsx +450 -0
  124. package/registry/blocks/chat/scroller.jsx +247 -0
  125. package/registry/blocks/chat/text.jsx +104 -0
  126. package/registry/blocks/code-editor/README.md +210 -0
  127. package/registry/blocks/code-editor/block.json +22 -0
  128. package/registry/blocks/code-editor/code-editor.jsx +534 -0
  129. package/registry/blocks/code-editor/codemirror.js +38 -0
  130. package/registry/blocks/code-editor/demo.jsx +205 -0
  131. package/registry/blocks/code-editor/index.js +8 -0
  132. package/registry/blocks/code-editor/json.js +317 -0
  133. package/registry/blocks/code-editor/sql.js +136 -0
  134. package/registry/blocks/code-editor/text.js +101 -0
  135. package/registry/blocks/code-editor/theme.js +186 -0
  136. package/registry/blocks/comment-box/README.md +123 -0
  137. package/registry/blocks/comment-box/block.json +10 -0
  138. package/registry/blocks/comment-box/comment-body.jsx +217 -0
  139. package/registry/blocks/comment-box/comment-box.jsx +198 -0
  140. package/registry/blocks/comment-box/comment-time.jsx +77 -0
  141. package/registry/blocks/comment-box/demo.jsx +141 -0
  142. package/registry/blocks/comment-box/index.js +8 -0
  143. package/registry/blocks/contact-section/README.md +152 -0
  144. package/registry/blocks/contact-section/block.json +11 -0
  145. package/registry/blocks/contact-section/contact-section.jsx +509 -0
  146. package/registry/blocks/contact-section/demo.jsx +93 -0
  147. package/registry/blocks/contact-section/index.js +4 -0
  148. package/registry/blocks/csv-import/README.md +133 -0
  149. package/registry/blocks/csv-import/block.json +13 -0
  150. package/registry/blocks/csv-import/csv-import.jsx +479 -0
  151. package/registry/blocks/csv-import/demo.jsx +106 -0
  152. package/registry/blocks/csv-import/import.js +200 -0
  153. package/registry/blocks/csv-import/index.js +7 -0
  154. package/registry/blocks/data-table/README.md +386 -0
  155. package/registry/blocks/data-table/block.json +10 -0
  156. package/registry/blocks/data-table/data-table.jsx +1164 -0
  157. package/registry/blocks/data-table/demo.jsx +588 -0
  158. package/registry/blocks/data-table/index.js +5 -0
  159. package/registry/blocks/data-table/model.js +169 -0
  160. package/registry/blocks/diagram/README.md +108 -0
  161. package/registry/blocks/diagram/block.json +13 -0
  162. package/registry/blocks/diagram/demo.jsx +113 -0
  163. package/registry/blocks/diagram/diagram.jsx +481 -0
  164. package/registry/blocks/diagram/index.js +7 -0
  165. package/registry/blocks/diagram/mermaid.js +52 -0
  166. package/registry/blocks/diagram/palette.js +313 -0
  167. package/registry/blocks/diagram/source.js +98 -0
  168. package/registry/blocks/doc-editor/README.md +215 -0
  169. package/registry/blocks/doc-editor/block.json +15 -0
  170. package/registry/blocks/doc-editor/demo.jsx +225 -0
  171. package/registry/blocks/doc-editor/doc-editor.jsx +291 -0
  172. package/registry/blocks/doc-editor/doc.js +309 -0
  173. package/registry/blocks/doc-editor/index.js +7 -0
  174. package/registry/blocks/doc-editor/schema.jsx +56 -0
  175. package/registry/blocks/doc-editor/shadcn-classes.js +110 -0
  176. package/registry/blocks/doc-editor/theme.js +82 -0
  177. package/registry/blocks/doc-page/README.md +119 -0
  178. package/registry/blocks/doc-page/autosave.js +104 -0
  179. package/registry/blocks/doc-page/block.json +10 -0
  180. package/registry/blocks/doc-page/demo.jsx +137 -0
  181. package/registry/blocks/doc-page/doc-page.jsx +276 -0
  182. package/registry/blocks/doc-page/index.js +4 -0
  183. package/registry/blocks/email-render/README.md +151 -0
  184. package/registry/blocks/email-render/block.json +14 -0
  185. package/registry/blocks/email-render/demo.jsx +153 -0
  186. package/registry/blocks/email-render/doc-email.jsx +210 -0
  187. package/registry/blocks/email-render/email.jsx +221 -0
  188. package/registry/blocks/email-render/index.js +10 -0
  189. package/registry/blocks/email-render/render.js +50 -0
  190. package/registry/blocks/email-render/theme.js +215 -0
  191. package/registry/blocks/faq/README.md +101 -0
  192. package/registry/blocks/faq/block.json +11 -0
  193. package/registry/blocks/faq/demo.jsx +89 -0
  194. package/registry/blocks/faq/faq.jsx +220 -0
  195. package/registry/blocks/faq/index.js +4 -0
  196. package/registry/blocks/file-upload/README.md +138 -0
  197. package/registry/blocks/file-upload/block.json +10 -0
  198. package/registry/blocks/file-upload/demo.jsx +204 -0
  199. package/registry/blocks/file-upload/file-upload.jsx +635 -0
  200. package/registry/blocks/file-upload/index.js +4 -0
  201. package/registry/blocks/flow-canvas/README.md +420 -0
  202. package/registry/blocks/flow-canvas/block.json +13 -0
  203. package/registry/blocks/flow-canvas/board.js +819 -0
  204. package/registry/blocks/flow-canvas/chrome.jsx +649 -0
  205. package/registry/blocks/flow-canvas/context.jsx +35 -0
  206. package/registry/blocks/flow-canvas/demo.jsx +249 -0
  207. package/registry/blocks/flow-canvas/edges.jsx +107 -0
  208. package/registry/blocks/flow-canvas/flow-canvas.jsx +1329 -0
  209. package/registry/blocks/flow-canvas/index.js +41 -0
  210. package/registry/blocks/flow-canvas/nodes.jsx +392 -0
  211. package/registry/blocks/flow-canvas/theme.js +174 -0
  212. package/registry/blocks/footer/README.md +133 -0
  213. package/registry/blocks/footer/block.json +11 -0
  214. package/registry/blocks/footer/demo.jsx +119 -0
  215. package/registry/blocks/footer/footer.jsx +406 -0
  216. package/registry/blocks/footer/index.js +4 -0
  217. package/registry/blocks/gantt/README.md +220 -0
  218. package/registry/blocks/gantt/block.json +10 -0
  219. package/registry/blocks/gantt/demo.jsx +148 -0
  220. package/registry/blocks/gantt/gantt.jsx +923 -0
  221. package/registry/blocks/gantt/index.js +7 -0
  222. package/registry/blocks/gantt/rows.jsx +468 -0
  223. package/registry/blocks/gantt/schedule.js +278 -0
  224. package/registry/blocks/job-panel/README.md +86 -2
  225. package/registry/blocks/job-panel/block.json +3 -3
  226. package/registry/blocks/job-panel/demo.jsx +230 -3
  227. package/registry/blocks/job-panel/job-panel.jsx +169 -3
  228. package/registry/blocks/json-editor/README.md +133 -0
  229. package/registry/blocks/json-editor/block.json +10 -0
  230. package/registry/blocks/json-editor/demo.jsx +92 -0
  231. package/registry/blocks/json-editor/index.js +7 -0
  232. package/registry/blocks/json-editor/json-editor.jsx +305 -0
  233. package/registry/blocks/json-editor/json-tree.jsx +393 -0
  234. package/registry/blocks/json-editor/tree.js +225 -0
  235. package/registry/blocks/map/README.md +221 -0
  236. package/registry/blocks/map/block.json +14 -0
  237. package/registry/blocks/map/demo.jsx +201 -0
  238. package/registry/blocks/map/geo.js +198 -0
  239. package/registry/blocks/map/index.js +8 -0
  240. package/registry/blocks/map/list.jsx +128 -0
  241. package/registry/blocks/map/map-view.jsx +715 -0
  242. package/registry/blocks/map/maplibre.js +30 -0
  243. package/registry/blocks/map/marks.jsx +146 -0
  244. package/registry/blocks/map/theme.js +142 -0
  245. package/registry/blocks/menubar/README.md +109 -0
  246. package/registry/blocks/menubar/block.json +10 -0
  247. package/registry/blocks/menubar/demo.jsx +128 -0
  248. package/registry/blocks/menubar/index.js +4 -0
  249. package/registry/blocks/menubar/menubar.jsx +199 -0
  250. package/registry/blocks/mind-map/README.md +206 -0
  251. package/registry/blocks/mind-map/block.json +10 -0
  252. package/registry/blocks/mind-map/demo.jsx +103 -0
  253. package/registry/blocks/mind-map/index.js +31 -0
  254. package/registry/blocks/mind-map/mind-map.jsx +1125 -0
  255. package/registry/blocks/mind-map/topic.jsx +143 -0
  256. package/registry/blocks/mind-map/tree.js +645 -0
  257. package/registry/blocks/navigation-menu/README.md +124 -0
  258. package/registry/blocks/navigation-menu/block.json +10 -0
  259. package/registry/blocks/navigation-menu/demo.jsx +84 -0
  260. package/registry/blocks/navigation-menu/index.js +4 -0
  261. package/registry/blocks/navigation-menu/navigation-menu.jsx +448 -0
  262. package/registry/blocks/order-detail/README.md +109 -0
  263. package/registry/blocks/order-detail/block.json +10 -0
  264. package/registry/blocks/order-detail/demo.jsx +88 -0
  265. package/registry/blocks/order-detail/index.js +4 -0
  266. package/registry/blocks/order-detail/order-detail.jsx +354 -0
  267. package/registry/blocks/org-chart/README.md +145 -0
  268. package/registry/blocks/org-chart/block.json +10 -0
  269. package/registry/blocks/org-chart/demo.jsx +94 -0
  270. package/registry/blocks/org-chart/index.js +7 -0
  271. package/registry/blocks/org-chart/org-chart.jsx +631 -0
  272. package/registry/blocks/org-chart/org.js +245 -0
  273. package/registry/blocks/org-chart/person.jsx +96 -0
  274. package/registry/blocks/page-builder/README.md +257 -0
  275. package/registry/blocks/page-builder/block.json +14 -0
  276. package/registry/blocks/page-builder/demo.jsx +145 -0
  277. package/registry/blocks/page-builder/grapes.js +18 -0
  278. package/registry/blocks/page-builder/index.js +11 -0
  279. package/registry/blocks/page-builder/page-builder.jsx +704 -0
  280. package/registry/blocks/page-builder/page-view.jsx +33 -0
  281. package/registry/blocks/page-builder/page.js +118 -0
  282. package/registry/blocks/page-builder/panels.jsx +588 -0
  283. package/registry/blocks/page-builder/sections.js +209 -0
  284. package/registry/blocks/page-builder/theme.js +87 -0
  285. package/registry/blocks/page-builder/thumbs.jsx +189 -0
  286. package/registry/blocks/page-builder/tokens.js +329 -0
  287. package/registry/blocks/payment-methods/README.md +104 -0
  288. package/registry/blocks/payment-methods/block.json +10 -0
  289. package/registry/blocks/payment-methods/demo.jsx +71 -0
  290. package/registry/blocks/payment-methods/index.js +4 -0
  291. package/registry/blocks/payment-methods/payment-methods.jsx +302 -0
  292. package/registry/blocks/pdf-document/README.md +213 -0
  293. package/registry/blocks/pdf-document/block.json +13 -0
  294. package/registry/blocks/pdf-document/demo.jsx +136 -0
  295. package/registry/blocks/pdf-document/index.js +33 -0
  296. package/registry/blocks/pdf-document/money.js +83 -0
  297. package/registry/blocks/pdf-document/pdf-document.jsx +144 -0
  298. package/registry/blocks/pdf-document/primitives.jsx +271 -0
  299. package/registry/blocks/pdf-document/quote.jsx +179 -0
  300. package/registry/blocks/pdf-document/theme.js +193 -0
  301. package/registry/blocks/pdf-viewer/README.md +191 -0
  302. package/registry/blocks/pdf-viewer/block.json +14 -0
  303. package/registry/blocks/pdf-viewer/demo.jsx +252 -0
  304. package/registry/blocks/pdf-viewer/helpers.js +270 -0
  305. package/registry/blocks/pdf-viewer/index.js +8 -0
  306. package/registry/blocks/pdf-viewer/notes.jsx +195 -0
  307. package/registry/blocks/pdf-viewer/pdf-viewer.jsx +1368 -0
  308. package/registry/blocks/pdf-viewer/pdfjs.js +37 -0
  309. package/registry/blocks/pdf-viewer/ssr-shim-end.js +6 -0
  310. package/registry/blocks/pdf-viewer/ssr-shim.js +44 -0
  311. package/registry/blocks/product-detail/README.md +217 -0
  312. package/registry/blocks/product-detail/block.json +10 -0
  313. package/registry/blocks/product-detail/demo.jsx +233 -0
  314. package/registry/blocks/product-detail/index.js +7 -0
  315. package/registry/blocks/product-detail/price.jsx +115 -0
  316. package/registry/blocks/product-detail/product-detail.jsx +348 -0
  317. package/registry/blocks/product-detail/product-media.jsx +102 -0
  318. package/registry/blocks/product-detail/product-options.jsx +144 -0
  319. package/registry/blocks/product-grid/README.md +162 -0
  320. package/registry/blocks/product-grid/block.json +10 -0
  321. package/registry/blocks/product-grid/demo.jsx +255 -0
  322. package/registry/blocks/product-grid/index.js +5 -0
  323. package/registry/blocks/product-grid/product-card.jsx +232 -0
  324. package/registry/blocks/product-grid/product-grid.jsx +235 -0
  325. package/registry/blocks/record-form/README.md +196 -0
  326. package/registry/blocks/record-form/block.json +10 -0
  327. package/registry/blocks/record-form/demo.jsx +525 -0
  328. package/registry/blocks/record-form/index.js +4 -0
  329. package/registry/blocks/record-form/record-fields.jsx +457 -0
  330. package/registry/blocks/record-form/record-form.jsx +415 -0
  331. package/registry/blocks/record-form/rules.js +81 -0
  332. package/registry/blocks/record-notes/README.md +139 -0
  333. package/registry/blocks/record-notes/block.json +10 -0
  334. package/registry/blocks/record-notes/demo.jsx +179 -0
  335. package/registry/blocks/record-notes/index.js +4 -0
  336. package/registry/blocks/record-notes/record-notes.jsx +266 -0
  337. package/registry/blocks/search-dialog/README.md +108 -0
  338. package/registry/blocks/search-dialog/block.json +10 -0
  339. package/registry/blocks/search-dialog/demo.jsx +116 -0
  340. package/registry/blocks/search-dialog/index.js +3 -0
  341. package/registry/blocks/search-dialog/search-dialog.jsx +235 -0
  342. package/registry/blocks/settings-list/README.md +171 -0
  343. package/registry/blocks/settings-list/block.json +10 -0
  344. package/registry/blocks/settings-list/demo.jsx +333 -0
  345. package/registry/blocks/settings-list/index.js +4 -0
  346. package/registry/blocks/settings-list/row-state.jsx +93 -0
  347. package/registry/blocks/settings-list/settings-list.jsx +349 -0
  348. package/registry/blocks/settings-list/settings-matrix.jsx +118 -0
  349. package/registry/blocks/setup-checklist/README.md +142 -0
  350. package/registry/blocks/setup-checklist/block.json +10 -0
  351. package/registry/blocks/setup-checklist/demo.jsx +159 -0
  352. package/registry/blocks/setup-checklist/index.js +4 -0
  353. package/registry/blocks/setup-checklist/setup-checklist.jsx +446 -0
  354. package/registry/blocks/share-dialog/README.md +113 -0
  355. package/registry/blocks/share-dialog/block.json +10 -0
  356. package/registry/blocks/share-dialog/demo.jsx +140 -0
  357. package/registry/blocks/share-dialog/index.js +3 -0
  358. package/registry/blocks/share-dialog/share-dialog.jsx +384 -0
  359. package/registry/blocks/sidebar/README.md +190 -0
  360. package/registry/blocks/sidebar/block.json +43 -0
  361. package/registry/blocks/sidebar/demo.jsx +577 -0
  362. package/registry/blocks/sidebar/index.js +4 -0
  363. package/registry/blocks/sidebar/sidebar-panel.jsx +149 -0
  364. package/registry/blocks/sidebar/sidebar.jsx +864 -0
  365. package/registry/blocks/signature-pad/README.md +137 -0
  366. package/registry/blocks/signature-pad/block.json +13 -0
  367. package/registry/blocks/signature-pad/colors.js +115 -0
  368. package/registry/blocks/signature-pad/demo.jsx +94 -0
  369. package/registry/blocks/signature-pad/index.js +6 -0
  370. package/registry/blocks/signature-pad/pad.js +7 -0
  371. package/registry/blocks/signature-pad/signature-pad.jsx +434 -0
  372. package/registry/blocks/signature-pad/signature.js +68 -0
  373. package/registry/blocks/sketch-canvas/README.md +280 -0
  374. package/registry/blocks/sketch-canvas/block.json +13 -0
  375. package/registry/blocks/sketch-canvas/demo.jsx +159 -0
  376. package/registry/blocks/sketch-canvas/excalidraw.js +40 -0
  377. package/registry/blocks/sketch-canvas/index.js +9 -0
  378. package/registry/blocks/sketch-canvas/palette.js +226 -0
  379. package/registry/blocks/sketch-canvas/sketch-canvas.jsx +1188 -0
  380. package/registry/blocks/sketch-canvas/sketch.js +179 -0
  381. package/registry/blocks/sketch-canvas/ssr-shim-end.js +6 -0
  382. package/registry/blocks/sketch-canvas/ssr-shim.js +45 -0
  383. package/registry/blocks/sketch-canvas/stamps.js +104 -0
  384. package/registry/blocks/sketch-canvas/theme.js +131 -0
  385. package/registry/blocks/spreadsheet/README.md +307 -0
  386. package/registry/blocks/spreadsheet/block.json +13 -0
  387. package/registry/blocks/spreadsheet/cells.jsx +455 -0
  388. package/registry/blocks/spreadsheet/chrome.jsx +325 -0
  389. package/registry/blocks/spreadsheet/demo.jsx +177 -0
  390. package/registry/blocks/spreadsheet/grid.js +387 -0
  391. package/registry/blocks/spreadsheet/index.js +34 -0
  392. package/registry/blocks/spreadsheet/spreadsheet.jsx +1732 -0
  393. package/registry/blocks/spreadsheet/types.js +473 -0
  394. package/registry/blocks/stat-cards/README.md +173 -0
  395. package/registry/blocks/stat-cards/block.json +10 -0
  396. package/registry/blocks/stat-cards/demo.jsx +257 -0
  397. package/registry/blocks/stat-cards/index.js +4 -0
  398. package/registry/blocks/stat-cards/stat-cards.jsx +307 -0
  399. package/registry/blocks/stat-cards/usage.jsx +243 -0
  400. package/registry/blocks/task-list/README.md +176 -0
  401. package/registry/blocks/task-list/block.json +10 -0
  402. package/registry/blocks/task-list/demo.jsx +134 -0
  403. package/registry/blocks/task-list/index.js +5 -0
  404. package/registry/blocks/task-list/task-list.jsx +907 -0
  405. package/registry/blocks/task-model/README.md +455 -0
  406. package/registry/blocks/task-model/block.json +167 -0
  407. package/registry/blocks/task-model/changes.js +89 -0
  408. package/registry/blocks/task-model/dates.js +371 -0
  409. package/registry/blocks/task-model/demo.jsx +215 -0
  410. package/registry/blocks/task-model/dnd.jsx +240 -0
  411. package/registry/blocks/task-model/index.js +97 -0
  412. package/registry/blocks/task-model/model.js +919 -0
  413. package/registry/blocks/task-model/parts.jsx +449 -0
  414. package/registry/blocks/task-model/toolbar.jsx +271 -0
  415. package/registry/blocks/testimonials/README.md +139 -0
  416. package/registry/blocks/testimonials/block.json +11 -0
  417. package/registry/blocks/testimonials/demo.jsx +137 -0
  418. package/registry/blocks/testimonials/index.js +4 -0
  419. package/registry/blocks/testimonials/testimonials.jsx +430 -0
  420. package/registry/blocks/transaction-list/README.md +126 -0
  421. package/registry/blocks/transaction-list/block.json +10 -0
  422. package/registry/blocks/transaction-list/demo.jsx +79 -0
  423. package/registry/blocks/transaction-list/index.js +4 -0
  424. package/registry/blocks/transaction-list/transaction-list.jsx +383 -0
  425. package/registry/blocks/whiteboard/README.md +179 -0
  426. package/registry/blocks/whiteboard/block.json +10 -0
  427. package/registry/blocks/whiteboard/demo.jsx +169 -0
  428. package/registry/blocks/whiteboard/index.js +6 -0
  429. package/registry/blocks/whiteboard/templates.js +150 -0
  430. package/registry/blocks/whiteboard/whiteboard.jsx +721 -0
  431. package/src/install.mjs +18 -0
  432. package/src/shadcn.mjs +171 -0
  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/parts/fixture.jsx +1 -1
  437. package/templates/next/files/components/parts/sample.js +15 -0
  438. package/templates/next/files/components/ui/avatar.jsx +141 -0
  439. package/templates/next/files/components/ui/breadcrumb.jsx +149 -0
  440. package/templates/next/files/components/ui/button-group.jsx +70 -0
  441. package/templates/next/files/components/ui/button.jsx +132 -12
  442. package/templates/next/files/components/ui/calendar.jsx +609 -0
  443. package/templates/next/files/components/ui/carousel.jsx +281 -0
  444. package/templates/next/files/components/ui/collapsible.jsx +208 -0
  445. package/templates/next/files/components/ui/combobox.jsx +608 -0
  446. package/templates/next/files/components/ui/command.jsx +577 -0
  447. package/templates/next/files/components/ui/context-menu.jsx +191 -0
  448. package/templates/next/files/components/ui/date-picker.jsx +360 -0
  449. package/templates/next/files/components/ui/distribution.jsx +117 -0
  450. package/templates/next/files/components/ui/feedback.jsx +143 -0
  451. package/templates/next/files/components/ui/field.jsx +115 -3
  452. package/templates/next/files/components/ui/file-input.jsx +234 -0
  453. package/templates/next/files/components/ui/icon.jsx +130 -8
  454. package/templates/next/files/components/ui/image.jsx +143 -0
  455. package/templates/next/files/components/ui/index.js +47 -2
  456. package/templates/next/files/components/ui/input-group.jsx +213 -0
  457. package/templates/next/files/components/ui/input-otp.jsx +170 -0
  458. package/templates/next/files/components/ui/item.jsx +206 -0
  459. package/templates/next/files/components/ui/menu.jsx +454 -82
  460. package/templates/next/files/components/ui/notice.jsx +126 -14
  461. package/templates/next/files/components/ui/pagination.jsx +156 -0
  462. package/templates/next/files/components/ui/popover.jsx +284 -0
  463. package/templates/next/files/components/ui/radio.jsx +193 -0
  464. package/templates/next/files/components/ui/rating.jsx +131 -0
  465. package/templates/next/files/components/ui/resizable.jsx +338 -0
  466. package/templates/next/files/components/ui/scroll-area.jsx +123 -0
  467. package/templates/next/files/components/ui/separator.jsx +119 -0
  468. package/templates/next/files/components/ui/sheet.jsx +96 -11
  469. package/templates/next/files/components/ui/slider.jsx +174 -0
  470. package/templates/next/files/components/ui/stat.jsx +574 -0
  471. package/templates/next/files/components/ui/steps.jsx +338 -0
  472. package/templates/next/files/components/ui/swatch.jsx +33 -0
  473. package/templates/next/files/components/ui/table.jsx +130 -0
  474. package/templates/next/files/components/ui/toast.jsx +440 -0
  475. package/templates/next/files/components/ui/toggle.jsx +195 -0
  476. package/templates/next/files/components/ui/tooltip.jsx +270 -0
  477. package/templates/next/files/lib/sample.js +4 -12
  478. package/templates/next/template.json +264 -8
@@ -0,0 +1,646 @@
1
+ "use client";
2
+
3
+ // Annotate — mark up a screenshot and say what each mark means.
4
+ //
5
+ // Drop, pick or paste a screenshot; it lies locked at the back of a
6
+ // SketchCanvas (the foundation: Excalidraw, themed). On top: arrows, boxes,
7
+ // freehand, text, a highlight band, a redaction bar and numbered notes.
8
+ // Every numbered note is a pin on the image AND a row in the side list,
9
+ // where its words are typed. The pin is the truth for which notes exist:
10
+ // delete it on the canvas and the row goes (an undo brings both back).
11
+ //
12
+ // Out: `onChange(annotation)` / `onSave` (async → { ok }) with the whole
13
+ // annotation as plain JSON, and `onSubmit({ image, notes, sketch, … })`
14
+ // (async → { ok }) with a PNG of the marked-up screenshot.
15
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
16
+ import { Badge, Button, Confirm, FileDrop, FileInput, Icon, IconButton, Kbd, Textarea, Tooltip, cx } from "../../ui";
17
+ import { SketchCanvas, emptySketch, useSketchCanvas } from "../sketch-canvas";
18
+ import {
19
+ ANNOTATE_TEMPLATES,
20
+ ANNOTATION_VERSION,
21
+ annotationNotes,
22
+ annotationToText,
23
+ highlightSkeleton,
24
+ markerSize,
25
+ markerSkeleton,
26
+ nextNoteNumber,
27
+ noteMarkers,
28
+ redactSkeleton,
29
+ renumberDuplicates,
30
+ templateMarks,
31
+ toAnnotation,
32
+ } from "./annotate-data";
33
+
34
+ /** The canvas tools Annotate offers, in rail order (SketchCanvas ids). */
35
+ export const ANNOTATE_TOOLS = ["select", "hand", "arrow", "rectangle", "ellipse", "draw", "text", "eraser"];
36
+
37
+ // Tailwind needs whole class names in the source: one per marker tone.
38
+ const CHIP = {
39
+ destructive: "bg-destructive/15 text-destructive border-destructive/40",
40
+ primary: "bg-primary/15 text-primary border-primary/40",
41
+ warning: "bg-warning/15 text-warning border-warning/40",
42
+ success: "bg-success/15 text-success border-success/40",
43
+ info: "bg-info/15 text-info border-info/40",
44
+ "chart-2": "bg-chart-2/15 text-chart-2 border-chart-2/40",
45
+ "chart-3": "bg-chart-3/15 text-chart-3 border-chart-3/40",
46
+ "chart-4": "bg-chart-4/15 text-chart-4 border-chart-4/40",
47
+ "chart-5": "bg-chart-5/15 text-chart-5 border-chart-5/40",
48
+ };
49
+
50
+ const isTyping = (t) => t?.tagName === "TEXTAREA" || t?.tagName === "INPUT" || t?.tagName === "SELECT" || t?.isContentEditable;
51
+ const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
52
+ const wordsFrom = (notes) => Object.fromEntries((notes ?? []).map((x) => [x.n, { text: x.text ?? "", ...(x.hint ? { hint: x.hint } : {}) }]));
53
+ const wordsList = (words) => Object.entries(words).map(([n, w]) => ({ n: Number(n), ...w }));
54
+
55
+ /** A File → a data URL no larger than `max` px on its long side. */
56
+ function readImage(file, max) {
57
+ return new Promise((resolve, reject) => {
58
+ const reader = new FileReader();
59
+ reader.onerror = () => reject(new Error("That file couldn't be read."));
60
+ reader.onload = () => {
61
+ const src = String(reader.result);
62
+ const img = new window.Image();
63
+ img.onerror = () => reject(new Error("That file isn't an image we can show."));
64
+ img.onload = () => {
65
+ const w = img.naturalWidth;
66
+ const h = img.naturalHeight;
67
+ const k = max > 0 ? Math.min(1, max / Math.max(w, h)) : 1;
68
+ if (k >= 1) return resolve({ src, width: w, height: h });
69
+ const canvas = document.createElement("canvas");
70
+ canvas.width = Math.round(w * k);
71
+ canvas.height = Math.round(h * k);
72
+ const ctx = canvas.getContext("2d");
73
+ if (!ctx) return resolve({ src, width: w, height: h });
74
+ ctx.imageSmoothingQuality = "high";
75
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
76
+ const type = file.type === "image/jpeg" ? "image/jpeg" : "image/png";
77
+ resolve({ src: canvas.toDataURL(type, 0.9), width: canvas.width, height: canvas.height });
78
+ };
79
+ img.src = src;
80
+ };
81
+ reader.readAsDataURL(file);
82
+ });
83
+ }
84
+
85
+ /** An image URL's natural size. */
86
+ function measure(src) {
87
+ return new Promise((resolve, reject) => {
88
+ const img = new window.Image();
89
+ img.onerror = () => reject(new Error("The screenshot didn't load."));
90
+ img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });
91
+ img.src = src;
92
+ });
93
+ }
94
+
95
+ export function Annotate({
96
+ value,
97
+ defaultValue,
98
+ onChange,
99
+ onSave,
100
+ onSubmit,
101
+ submitLabel = "Send",
102
+ uploadImage,
103
+ maxImageSize = 1600,
104
+ title = "Annotate a screenshot",
105
+ readOnly = false,
106
+ tone = "destructive",
107
+ tools = ANNOTATE_TOOLS,
108
+ canvasRef,
109
+ label,
110
+ className,
111
+ }) {
112
+ const own = useSketchCanvas();
113
+ const canvas = canvasRef ?? own;
114
+ const first = useRef(null);
115
+ if (!first.current) first.current = toAnnotation(value ?? defaultValue);
116
+
117
+ const [image, setImage] = useState(first.current.image);
118
+ const [sketch, setSketch] = useState(first.current.sketch);
119
+ const [words, setWords] = useState(() => wordsFrom(first.current.notes));
120
+ const [intake, setIntake] = useState({ state: "idle" });
121
+ const [save, setSave] = useState({ state: "idle" });
122
+ const [send, setSend] = useState({ state: "idle" });
123
+ const [restart, setRestart] = useState(false);
124
+ const [focusN, setFocusN] = useState(null);
125
+ const tone_ = CHIP[tone] ? tone : "destructive";
126
+
127
+ // Latest state for callbacks that outlive a render.
128
+ const live = useRef({});
129
+ live.current = { image, sketch, words };
130
+ const cbs = useRef({});
131
+ cbs.current = { onChange, onSave };
132
+
133
+ // ---- output ---------------------------------------------------------------
134
+
135
+ const lastOut = useRef(value);
136
+ const compose = useCallback(() => {
137
+ const { image: img, sketch: sk, words: w } = live.current;
138
+ const notes = annotationNotes(sk, wordsList(w)).map(({ n, text, hint }) => ({ n, text, ...(hint ? { hint } : {}) }));
139
+ return { type: "annotation", version: ANNOTATION_VERSION, image: img, sketch: sk, notes };
140
+ }, []);
141
+
142
+ const saveSeq = useRef(0);
143
+ const runSave = useCallback(async (v) => {
144
+ const fn = cbs.current.onSave;
145
+ if (!fn) return;
146
+ const seq = ++saveSeq.current;
147
+ setSave({ state: "saving" });
148
+ let res;
149
+ try {
150
+ res = await fn(v);
151
+ } catch (e) {
152
+ res = { ok: false, error: e?.message };
153
+ }
154
+ if (seq === saveSeq.current) setSave(res?.ok ? { state: "saved" } : { state: "error", error: res?.error, value: v });
155
+ }, []);
156
+
157
+ const pending = useRef(false);
158
+ const emit = useCallback(() => {
159
+ pending.current = false;
160
+ const v = compose();
161
+ lastOut.current = v;
162
+ cbs.current.onChange?.(v);
163
+ runSave(v);
164
+ }, [compose, runSave]);
165
+
166
+ // A user change marks the annotation dirty; it goes out after `delay`
167
+ // ms of quiet (the canvas has already debounced its own edits).
168
+ const [dirty, setDirty] = useState(0);
169
+ const delay = useRef(0);
170
+ const touch = useCallback((ms = 0) => {
171
+ delay.current = ms;
172
+ pending.current = true;
173
+ setDirty((d) => d + 1);
174
+ }, []);
175
+ useEffect(() => {
176
+ if (!dirty) return;
177
+ const t = setTimeout(emit, delay.current);
178
+ return () => clearTimeout(t);
179
+ }, [dirty, emit]);
180
+ const flush = useRef(emit);
181
+ flush.current = emit;
182
+ useEffect(() => () => pending.current && flush.current(), []);
183
+
184
+ // Controlled: a `value` that is not what we last sent loads another record.
185
+ useEffect(() => {
186
+ if (value === undefined || value === lastOut.current) return;
187
+ lastOut.current = value;
188
+ const a = toAnnotation(value);
189
+ setImage(a.image);
190
+ setSketch(a.sketch);
191
+ setWords(wordsFrom(a.notes));
192
+ seen.current = new Set(noteMarkers(a.sketch).map((m) => m.n));
193
+ }, [value]);
194
+
195
+ // A saved image without its size: measure it (templates and pins scale by it).
196
+ useEffect(() => {
197
+ if (!image?.src || (image.width && image.height)) return;
198
+ let gone = false;
199
+ measure(image.src)
200
+ .then((d) => !gone && setImage((cur) => (cur?.src === image.src ? { ...cur, ...d } : cur)))
201
+ .catch(() => {});
202
+ return () => {
203
+ gone = true;
204
+ };
205
+ }, [image]);
206
+
207
+ // ---- the screenshot -------------------------------------------------------
208
+
209
+ const takeFile = useCallback(
210
+ async (file) => {
211
+ if (!file) return;
212
+ if (!/^image\//.test(file.type ?? "")) {
213
+ setIntake({ state: "error", error: "That isn't an image. Try a PNG, JPG or WebP screenshot." });
214
+ return;
215
+ }
216
+ setIntake({ state: "busy" });
217
+ try {
218
+ let next;
219
+ if (uploadImage) {
220
+ const res = await uploadImage(file);
221
+ if (!res?.ok || !res.src) throw new Error(res?.error || "The screenshot couldn't be uploaded.");
222
+ next = { src: res.src, ...(await measure(res.src)) };
223
+ } else next = await readImage(file, maxImageSize);
224
+ const hadImage = !!live.current.image;
225
+ setImage(next);
226
+ setIntake({ state: "idle" });
227
+ touch(0);
228
+ if (hadImage) setTimeout(() => canvas.current?.zoomToFit(), 80);
229
+ } catch (e) {
230
+ setIntake({ state: "error", error: e?.message || "The screenshot couldn't be read." });
231
+ }
232
+ },
233
+ [uploadImage, maxImageSize, touch, canvas],
234
+ );
235
+
236
+ // Paste a screenshot while there is none yet (once there is one, a paste
237
+ // lands on the canvas as Excalidraw's own image).
238
+ useEffect(() => {
239
+ if (image || readOnly) return;
240
+ const onPaste = (e) => {
241
+ if (isTyping(e.target)) return;
242
+ const file = [...(e.clipboardData?.files ?? [])].find((f) => /^image\//.test(f.type));
243
+ if (!file) return;
244
+ e.preventDefault();
245
+ takeFile(file);
246
+ };
247
+ document.addEventListener("paste", onPaste);
248
+ return () => document.removeEventListener("paste", onPaste);
249
+ }, [image, readOnly, takeFile]);
250
+
251
+ // ---- the marks ------------------------------------------------------------
252
+
253
+ const seen = useRef(null);
254
+ if (!seen.current) seen.current = new Set(noteMarkers(first.current.sketch).map((m) => m.n));
255
+
256
+ const onSketch = useCallback(
257
+ (v) => {
258
+ // A last edit flushed by a canvas that is closing (Start over): not ours.
259
+ if (!live.current.image) return;
260
+ // A duplicate (Cmd+D) or a paste copies a pin's number: give the copy
261
+ // the next free one, quietly, so undo still undoes the duplicate.
262
+ const fixed = renumberDuplicates(v);
263
+ const api = canvas.current?.api;
264
+ if (fixed && api) {
265
+ const byId = new Map(fixed.elements.map((el) => [el.id, el]));
266
+ api.updateScene({ elements: api.getSceneElementsIncludingDeleted().map((el) => byId.get(el.id) ?? el), captureUpdate: "NEVER" });
267
+ return;
268
+ }
269
+ const ns = noteMarkers(v).map((m) => m.n);
270
+ const added = ns.filter((n) => !seen.current.has(n));
271
+ seen.current = new Set(ns);
272
+ setSketch(v);
273
+ if (added.length) setFocusN(Math.min(...added));
274
+ touch(0);
275
+ },
276
+ [canvas, touch],
277
+ );
278
+
279
+ const size = markerSize(image?.width);
280
+ const place = useRef({});
281
+ place.current = { size, tone: tone_ };
282
+ const stamps = useMemo(
283
+ () => [
284
+ {
285
+ id: "note",
286
+ label: "Numbered note",
287
+ icon: "pin",
288
+ create: () => markerSkeleton(nextNoteNumber(canvas.current?.getScene() ?? live.current.sketch), { size: place.current.size, tone: place.current.tone }),
289
+ },
290
+ { id: "highlight", label: "Highlight", icon: "sparkles", create: () => highlightSkeleton({ width: place.current.size * 7, height: Math.round(place.current.size * 1.2) }) },
291
+ { id: "redact", label: "Hide something private", icon: "eye-off", create: () => redactSkeleton({ width: place.current.size * 5, height: Math.round(place.current.size * 0.8) }) },
292
+ ],
293
+ [canvas],
294
+ );
295
+
296
+ const addNote = () => {
297
+ const c = canvas.current;
298
+ if (!c) return;
299
+ c.insertElements(markerSkeleton(nextNoteNumber(c.getScene()), { size, tone: tone_ }));
300
+ };
301
+
302
+ const applyTemplate = (id) => {
303
+ const c = canvas.current;
304
+ if (!c || !image) return;
305
+ const { steps, hints } = templateMarks(id, { width: image.width, height: image.height, tone: tone_, first: nextNoteNumber(c.getScene()) });
306
+ setWords((w) => {
307
+ const next = { ...w };
308
+ for (const [n, hint] of Object.entries(hints)) next[n] = { text: next[n]?.text ?? "", hint };
309
+ return next;
310
+ });
311
+ c.insertElements(
312
+ steps.flatMap((s) => s.skeletons),
313
+ { at: false, select: false },
314
+ );
315
+ };
316
+
317
+ const locate = (m) => {
318
+ const api = canvas.current?.api;
319
+ const el = api?.getSceneElements().find((e) => e.id === m.id);
320
+ if (!el) return;
321
+ api.updateScene({ appState: { selectedElementIds: { [el.id]: true } } });
322
+ api.scrollToContent(el, { animate: true });
323
+ canvas.current?.focus();
324
+ };
325
+
326
+ const removeNote = (n) => {
327
+ const c = canvas.current;
328
+ if (!c) return;
329
+ const s = c.getScene();
330
+ c.setScene({ ...s, elements: s.elements.filter((el) => el.customData?.note !== n) });
331
+ };
332
+
333
+ const setText = (n, text) => {
334
+ setWords((w) => ({ ...w, [n]: { ...(w[n] ?? {}), text } }));
335
+ touch(500);
336
+ };
337
+
338
+ // New pin: its row's field takes the focus (not on a phone — the keyboard
339
+ // would cover the screenshot being marked).
340
+ const fields = useRef(new Map());
341
+ useEffect(() => {
342
+ if (focusN == null) return;
343
+ const el = fields.current.get(focusN);
344
+ if (el && typeof matchMedia !== "undefined" && matchMedia("(min-width: 768px)").matches) el.focus({ preventScroll: true });
345
+ setFocusN(null);
346
+ }, [focusN]);
347
+
348
+ // N: the numbered-note tool.
349
+ const keyDown = (e) => {
350
+ if (readOnly || !image || isTyping(e.target) || e.metaKey || e.ctrlKey || e.altKey) return;
351
+ if (e.key === "n" || e.key === "N") {
352
+ e.preventDefault();
353
+ e.stopPropagation();
354
+ canvas.current?.setTool("note");
355
+ }
356
+ };
357
+
358
+ // ---- send -----------------------------------------------------------------
359
+
360
+ const submit = async () => {
361
+ const c = canvas.current;
362
+ if (!c || !image || !onSubmit) return;
363
+ setSend({ state: "sending" });
364
+ let res;
365
+ try {
366
+ const blob = await c.exportImage({ padding: 0 });
367
+ const sk = c.getScene();
368
+ const notes = annotationNotes(sk, wordsList(live.current.words));
369
+ res = await onSubmit({ image: blob, notes, sketch: sk, screenshot: image, text: annotationToText({ sketch: sk, notes }) });
370
+ } catch (e) {
371
+ res = { ok: false, error: e?.message };
372
+ }
373
+ setSend(res?.ok ? { state: "sent" } : { state: "error", error: res?.error || "It didn't send." });
374
+ };
375
+
376
+ const startOver = () => {
377
+ setImage(null);
378
+ setSketch(emptySketch());
379
+ setWords({});
380
+ seen.current = new Set();
381
+ setSend({ state: "idle" });
382
+ setRestart(false);
383
+ touch(0);
384
+ };
385
+
386
+ // ---- render ---------------------------------------------------------------
387
+
388
+ const markers = noteMarkers(sketch);
389
+ const notes = annotationNotes(sketch, wordsList(words));
390
+ const sized = /(^|\s)(h-|min-h-|size-|flex-1|grow)/.test(className ?? "");
391
+
392
+ return (
393
+ // raw: a plain box that hears N on its way into the canvas (capture), to
394
+ // pick the numbered-note tool; no control of its own.
395
+ <section
396
+ aria-label={label ?? title}
397
+ onKeyDownCapture={keyDown}
398
+ className={cx("flex flex-col overflow-hidden rounded-lg border border-border bg-card text-card-foreground", !sized && "md:h-[36rem]", className)}
399
+ >
400
+ <header className="flex flex-wrap items-center gap-2 border-b border-border px-3 py-2">
401
+ <div className="min-w-0 flex-1">
402
+ <h2 className="truncate text-sm font-semibold">{title}</h2>
403
+ <StatusLine count={markers.length} image={!!image} save={save} send={send} onRetry={() => runSave(save.value ?? compose())} />
404
+ </div>
405
+ {image && !readOnly ? (
406
+ <>
407
+ <FileInput accept="image/*" onFiles={([f]) => takeFile(f)} variant="secondary" size="sm" icon="image" disabled={intake.state === "busy"}>
408
+ Replace
409
+ </FileInput>
410
+ <Tooltip label="Start over">
411
+ <IconButton icon="refresh" label="Start over" size="sm" onClick={() => setRestart(true)} />
412
+ </Tooltip>
413
+ </>
414
+ ) : null}
415
+ {onSubmit && !readOnly ? (
416
+ <Button variant="primary" size="sm" icon="send" loading={send.state === "sending"} disabled={!image} onClick={submit}>
417
+ {submitLabel}
418
+ </Button>
419
+ ) : null}
420
+ </header>
421
+
422
+ {intake.state === "error" && image ? (
423
+ <p role="alert" className="border-b border-border px-3 py-2 text-xs text-destructive">
424
+ {intake.error}
425
+ </p>
426
+ ) : null}
427
+
428
+ {!image ? (
429
+ <Intake readOnly={readOnly} intake={intake} onFile={takeFile} onReject={() => setIntake({ state: "error", error: "That isn't an image. Try a PNG, JPG or WebP screenshot." })} />
430
+ ) : (
431
+ <div className="flex min-h-0 flex-1 flex-col md:flex-row">
432
+ <div className="h-[26rem] min-w-0 md:h-auto md:min-h-0 md:flex-1">
433
+ <SketchCanvas
434
+ label={`${title}: the screenshot and its marks`}
435
+ value={sketch}
436
+ onChange={onSketch}
437
+ changeDelay={250}
438
+ background={image}
439
+ readOnly={readOnly}
440
+ tools={tools}
441
+ stamps={readOnly ? undefined : stamps}
442
+ grid={false}
443
+ canvasRef={canvas}
444
+ empty={<Starter onTemplate={applyTemplate} />}
445
+ className="h-full rounded-none border-0"
446
+ />
447
+ </div>
448
+ <NotesPanel
449
+ notes={notes}
450
+ markers={markers}
451
+ tone={tone_}
452
+ readOnly={readOnly}
453
+ fields={fields}
454
+ onText={setText}
455
+ onLocate={locate}
456
+ onRemove={removeNote}
457
+ onAdd={addNote}
458
+ />
459
+ </div>
460
+ )}
461
+
462
+ <Confirm
463
+ open={restart}
464
+ onClose={() => setRestart(false)}
465
+ onConfirm={startOver}
466
+ title="Start over?"
467
+ body="The screenshot, every mark and every note are removed."
468
+ confirmLabel="Start over"
469
+ tone="danger"
470
+ />
471
+ </section>
472
+ );
473
+ }
474
+
475
+ function StatusLine({ count, image, save, send, onRetry }) {
476
+ const bits = [];
477
+ if (image) bits.push(plural(count, "note", "notes"));
478
+ return (
479
+ <div className="flex flex-wrap items-center gap-x-2 text-xs text-muted-foreground">
480
+ {bits.length ? <span>{bits.join(" · ")}</span> : <span>No screenshot yet</span>}
481
+ {save.state === "saving" ? <span role="status">Saving…</span> : null}
482
+ {save.state === "saved" ? <span role="status">Saved</span> : null}
483
+ {save.state === "error" ? (
484
+ <span role="alert" className="inline-flex items-center gap-1 text-destructive">
485
+ {save.error || "Couldn't save"}
486
+ <Button variant="link" size="sm" onClick={onRetry}>
487
+ Retry
488
+ </Button>
489
+ </span>
490
+ ) : null}
491
+ {send.state === "sent" ? (
492
+ <span role="status" className="inline-flex items-center gap-1 text-success">
493
+ <Icon name="circle-check" size={12} /> Sent
494
+ </span>
495
+ ) : null}
496
+ {send.state === "error" ? (
497
+ <span role="alert" className="text-destructive">
498
+ {send.error}
499
+ </span>
500
+ ) : null}
501
+ </div>
502
+ );
503
+ }
504
+
505
+ /** No screenshot yet: a big drop zone, and how it works. */
506
+ function Intake({ readOnly, intake, onFile, onReject }) {
507
+ if (readOnly) {
508
+ return (
509
+ <div className="grid min-h-[20rem] flex-1 place-items-center p-6 text-sm text-muted-foreground">
510
+ <p>No screenshot to show.</p>
511
+ </div>
512
+ );
513
+ }
514
+ return (
515
+ <div className="flex min-h-[24rem] flex-1 flex-col items-center justify-center gap-5 p-4 sm:p-8">
516
+ <FileDrop
517
+ accept="image/*"
518
+ onFiles={([f]) => onFile(f)}
519
+ onReject={onReject}
520
+ disabled={intake.state === "busy"}
521
+ invalid={intake.state === "error"}
522
+ label={intake.state === "busy" ? "Reading the screenshot…" : "Drop a screenshot here, or browse"}
523
+ hint="PNG, JPG or WebP. Or paste one: Cmd/Ctrl+V."
524
+ className="w-full max-w-xl"
525
+ />
526
+ {intake.state === "error" ? (
527
+ <p role="alert" className="text-sm text-destructive">
528
+ {intake.error}
529
+ </p>
530
+ ) : null}
531
+ <ol className="grid w-full max-w-xl gap-3 text-sm sm:grid-cols-3">
532
+ {[
533
+ ["image", "Add a screenshot", "Drop it, pick it or paste it."],
534
+ ["pin", "Mark it up", "Pins, arrows, boxes, highlights."],
535
+ ["message-circle", "Say what you mean", "A line of words for every pin."],
536
+ ].map(([icon, head, body], i) => (
537
+ <li key={head} className="flex items-start gap-2.5">
538
+ <span className="grid size-8 shrink-0 place-items-center rounded-full bg-muted text-muted-foreground">
539
+ <Icon name={icon} size={16} />
540
+ </span>
541
+ <span className="min-w-0">
542
+ <span className="block font-medium">
543
+ {i + 1}. {head}
544
+ </span>
545
+ <span className="block text-muted-foreground">{body}</span>
546
+ </span>
547
+ </li>
548
+ ))}
549
+ </ol>
550
+ </div>
551
+ );
552
+ }
553
+
554
+ /** A screenshot with nothing on it yet: how to start, and three starters.
555
+ * Sits low over the image so the screenshot stays in view. */
556
+ function Starter({ onTemplate }) {
557
+ return (
558
+ <div className="pointer-events-auto mb-[7.5rem] flex max-w-sm flex-col items-center gap-2 self-end rounded-xl border border-border bg-popover px-4 py-3 text-center text-popover-foreground shadow-lg md:mb-12">
559
+ <p className="text-sm">
560
+ Press <Kbd>N</Kbd> for a numbered note, <Kbd>A</Kbd> for an arrow, <Kbd>R</Kbd> for a box.
561
+ </p>
562
+ <div className="flex flex-wrap justify-center gap-1.5">
563
+ {ANNOTATE_TEMPLATES.map((t) => (
564
+ <Tooltip key={t.id} label={t.description}>
565
+ <Button variant="secondary" size="sm" icon={t.icon} onClick={() => onTemplate(t.id)}>
566
+ {t.label}
567
+ </Button>
568
+ </Tooltip>
569
+ ))}
570
+ </div>
571
+ </div>
572
+ );
573
+ }
574
+
575
+ /** The numbered notes, in number order: the pin's chip (shows it on the
576
+ * screenshot), its words, delete. Also the text view of the annotation. */
577
+ function NotesPanel({ notes, markers, tone, readOnly, fields, onText, onLocate, onRemove, onAdd }) {
578
+ const byN = new Map(markers.map((m) => [m.n, m]));
579
+ return (
580
+ <aside aria-label="Notes" className="flex max-h-[26rem] shrink-0 flex-col border-t border-border md:max-h-none md:w-80 md:border-l md:border-t-0">
581
+ <div className="flex items-center gap-2 px-3 py-2">
582
+ <h3 className="text-sm font-semibold">Notes</h3>
583
+ {notes.length ? <Badge>{notes.length}</Badge> : null}
584
+ <span className="flex-1" />
585
+ {!readOnly ? (
586
+ <Button variant="quiet" size="sm" icon="plus" onClick={onAdd}>
587
+ Add note
588
+ </Button>
589
+ ) : null}
590
+ </div>
591
+ {notes.length ? (
592
+ <ol className="grid min-h-0 flex-1 content-start gap-3 overflow-y-auto px-3 pb-3">
593
+ {notes.map((x) => (
594
+ <li key={x.n} className="flex items-start gap-2">
595
+ <Tooltip label="Show on the screenshot" side="left">
596
+ <Button
597
+ variant="quiet"
598
+ size="sm"
599
+ iconOnly
600
+ aria-label={`Note ${x.n}: show on the screenshot`}
601
+ onClick={() => byN.has(x.n) && onLocate(byN.get(x.n))}
602
+ className="shrink-0"
603
+ >
604
+ <span className={cx("grid size-7 place-items-center rounded-full border text-xs font-semibold tabular-nums", CHIP[tone])}>{x.n}</span>
605
+ </Button>
606
+ </Tooltip>
607
+ {readOnly ? (
608
+ <p className={cx("min-w-0 flex-1 whitespace-pre-wrap break-words pt-2 text-sm", !x.text.trim() && "text-muted-foreground")}>{x.text.trim() || "No words."}</p>
609
+ ) : (
610
+ <>
611
+ <Textarea
612
+ ref={(el) => {
613
+ if (el) fields.current.set(x.n, el);
614
+ else fields.current.delete(x.n);
615
+ }}
616
+ aria-label={`Note ${x.n}`}
617
+ placeholder={x.hint || "What should change here?"}
618
+ value={x.text}
619
+ onChange={(e) => onText(x.n, e.target.value)}
620
+ autoGrow
621
+ minRows={2}
622
+ maxRows={8}
623
+ className="min-w-0 flex-1 text-sm"
624
+ />
625
+ <Tooltip label="Delete note">
626
+ <IconButton icon="trash" label={`Delete note ${x.n}`} size="sm" onClick={() => onRemove(x.n)} className="shrink-0" />
627
+ </Tooltip>
628
+ </>
629
+ )}
630
+ </li>
631
+ ))}
632
+ </ol>
633
+ ) : (
634
+ <div className="px-3 pb-4 text-sm text-muted-foreground">
635
+ {readOnly ? (
636
+ <p>No numbered notes.</p>
637
+ ) : (
638
+ <p>
639
+ Press <Kbd>N</Kbd> or pick the pin in the rail, then click the screenshot. Each pin gets a number, and its words go here.
640
+ </p>
641
+ )}
642
+ </div>
643
+ )}
644
+ </aside>
645
+ );
646
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "annotate",
3
+ "title": "Annotate",
4
+ "description": "Mark up a screenshot: drop, pick or paste one, then draw arrows, boxes, highlights, redaction bars and numbered notes on it (Excalidraw underneath, via sketch-canvas). A side list holds the words of each numbered note; a click on a note's number shows its pin. Starter templates (bug report, design review, walkthrough) on a fresh screenshot. The annotation is plain JSON saved through onChange / an async onSave; onSubmit hands over a PNG of the marked-up screenshot with the notes and the sketch.",
5
+ "useWhen": "Someone needs to point at parts of a screen and say something about each: a bug report, design feedback, a support question, a walkthrough, a request to an agent about what to change. Not a blank drawing board (whiteboard or sketch-canvas), not a wireframe (sketch-to-app), not an image upload alone (file-upload).",
6
+ "tags": ["annotate", "annotation", "screenshot", "markup", "mark up", "feedback", "bug report", "design review", "pins", "numbered notes", "arrows", "highlight", "redact", "excalidraw", "png"],
7
+ "exports": ["Annotate", "ANNOTATE_TOOLS", "ANNOTATE_TEMPLATES", "emptyAnnotation", "toAnnotation", "annotationNotes", "annotationToText", "noteMarkers", "nextNoteNumber", "markerSkeleton"],
8
+ "files": ["index.js", "annotate.jsx", "annotate-data.js"],
9
+ "requires": ["sketch-canvas"]
10
+ }