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,221 @@
1
+ # MapView
2
+
3
+ Places on an interactive map, with their list beside it. Every row that
4
+ has a position becomes a marker — a dot in a token tone, or your own kit
5
+ content. Markers that crowd merge into a count; click it to zoom in. Click
6
+ a marker and a popup opens with kit parts. The list on the left is
7
+ searchable and shares one selection and one hover with the map: pick a
8
+ row and the map moves to it, pick a marker and the list scrolls to it.
9
+ Zoom buttons and "Show all" sit top right. Rows with no position are
10
+ listed under "Not on the map", never lost.
11
+
12
+ On a phone the map fills the block, a strip of cards sits under it, and a
13
+ "List" button swaps to the full list.
14
+
15
+ It is [MapLibre GL JS](https://github.com/maplibre/maplibre-gl-js)
16
+ (BSD-3-Clause) underneath, through
17
+ [react-map-gl](https://github.com/visgl/react-map-gl) (MIT): its tiles,
18
+ camera, gestures and keyboard (arrows pan, + and − zoom). The markers,
19
+ clusters, popup, list and controls are ours, in the kit and the
20
+ workspace's tokens. The blocks built on this one — `location-map`,
21
+ `store-locator`, the Map view of `work-views` — import it, never the map
22
+ packages.
23
+
24
+ ```jsx
25
+ import { MapView, useMapView } from "../components/blocks/map"; // a Makersclaw app ("./blocks/map" from components/)
26
+ import { MapView, useMapView } from "@/components/makersclaw/blocks/map"; // a shadcn project
27
+ ```
28
+
29
+ `MapView` is a client component: render it from a `"use client"`
30
+ component, and load the rows in the server surface (README §4). The
31
+ helpers (`readPosition`, `boundsOf`, `distanceKm`…) are plain functions and
32
+ work on the server.
33
+
34
+ `makersclaw add map` adds `maplibre-gl` and `react-map-gl` (pinned, exact)
35
+ to package.json: run `npm install` after. They load with the page (no
36
+ `import()`); the map mounts after the first render. Until then — on the
37
+ server — the frame, the list and a quiet "Loading the map…" show.
38
+
39
+ ## What it fetches at run time
40
+
41
+ A map cannot ship its world inside the app. In the browser it fetches:
42
+
43
+ - **The tiles, style and fonts** from the style URL. The default is
44
+ [OpenFreeMap](https://openfreemap.org): free, no key, no sign-up,
45
+ OpenStreetMap data. `positron` (light) or `dark`, picked from the
46
+ workspace's scheme and swapped when it changes. The attribution line
47
+ (bottom right) is the data licence's condition: it stays.
48
+ - **MapLibre's tile worker** from jsDelivr, at exactly the installed
49
+ version (`https://cdn.jsdelivr.net/npm/maplibre-gl@<version>/dist/maplibre-gl-worker.mjs`).
50
+ MapLibre 6 finds its worker beside its own file, which a bundled app
51
+ does not have. Host the file yourself and pass `workerUrl` to avoid
52
+ the CDN.
53
+
54
+ Nothing else leaves the page: no geocoding, no analytics.
55
+
56
+ ## The data
57
+
58
+ Rows are your records. A row's position is read by `positionOf`; the
59
+ default reads any of these:
60
+
61
+ ```js
62
+ { lat: 38.71, lng: -9.14 } // or latitude / longitude, or lon
63
+ { location: { lat: 38.71, lng: -9.14, label } } // the task-model shape
64
+ { position: [-9.14, 38.71] } // a GeoJSON pair: [lng, lat]
65
+ ```
66
+
67
+ Numbers or numeric strings, in degrees (WGS 84 — what GPS and every
68
+ geocoder give). A row with no valid position is listed, not mapped.
69
+
70
+ Tones are token names, never colours: `primary` (the default), `success`,
71
+ `warning`, `destructive`, `info`, `chart-1` … `chart-5`, `muted`. A tone
72
+ means something — status, owner, type — so say it in words too (a badge
73
+ in `asideOf`).
74
+
75
+ ## Props
76
+
77
+ - `items` — the rows. Pass `useFixture(rows, SAMPLE).rows` to open with an
78
+ example.
79
+ - `positionOf(row)` — `{ lat, lng }` or null. Default: `readPosition` (above).
80
+ - `idOf(row)` — default `row.id`. Ids are compared as strings.
81
+ - `titleOf(row)` — the name on the marker's label, the list row and the
82
+ popup. Default `row.title ?? row.name ?? row.label ?? row.location.label`.
83
+ - `metaOf(row)` — a muted line under the title (list, card, popup): a
84
+ city, an address. A string, or a node.
85
+ - `asideOf(row)` — a node at the row's end: a `Badge`, a distance.
86
+ - `toneOf(row)` — the marker's tone (above). Default `primary`.
87
+ - `searchOf(row)` — the words the search finds a row by. Default: title
88
+ and meta. Every word typed must appear.
89
+ - `renderMarker(row, { selected, hovered, tone })` — your own marker
90
+ content (an `Avatar`, a price, an icon), kit parts only. It sits inside
91
+ the block's button, so clicks, labels and selection still work.
92
+ - `renderPopup(row, { close })` — the popup's content: kit parts and
93
+ tokens. Default: title, meta and aside. The box and its close button
94
+ are the block's.
95
+ - `selectedId` · `onSelect(id, row)` — control the selection (the row
96
+ your form edits, a URL param). `id` is a string, or null when cleared.
97
+ Leave `selectedId` out and the block keeps its own; `defaultSelectedId`
98
+ sets where it starts. A selection set from outside moves the map to it.
99
+ - `onMove(row, { lat, lng })` — markers become draggable. The marker stays
100
+ where it was dropped at once; return `{ ok: true }` or
101
+ `{ ok: false, error }` (a throw counts as failed) and a failed save puts
102
+ it back with a toast saying why. The block keeps the new spot until
103
+ `positionOf(row)` reports a different one.
104
+ - `onMapClick({ lat, lng })` — a click on the map, not a marker ("add a
105
+ place here"). The block also clears the selection.
106
+ - `onViewChange({ center, zoom, bounds })` — after the map stops moving
107
+ (and once on load). `bounds` is `{ west, south, east, north }`: search
108
+ this area, load what is in view.
109
+ - `areas` — tinted regions: `[{ id, label?, tone?, geometry }]`, where
110
+ `geometry` is GeoJSON (`Polygon`, `MultiPolygon`, `LineString`). A soft
111
+ fill and a line in the tone, repainted when the scheme changes.
112
+ - `onAreaClick(area)` — an area is clicked.
113
+ - `list` — `"side"` (default: the list beside the map, the card strip on
114
+ a phone) or `"none"` (the map alone; markers then take Tab focus).
115
+ - `search` — the search field over the list. Default `true`.
116
+ - `cluster` — merge crowded markers. Default `true`. `clusterRadius` —
117
+ how close in pixels, default `48`.
118
+ - `popup` — open a popup for the selected row. Default `true`.
119
+ - `styleUrl` — the tiles: one URL, or `{ light, dark }` to follow the
120
+ scheme. Default `DEFAULT_STYLE` (OpenFreeMap positron / dark).
121
+ `MAP_STYLES` has OpenFreeMap's five: `positron`, `liberty`, `bright`,
122
+ `dark`, `fiord`. Any MapLibre style URL works (MapTiler, Stadia, your
123
+ own), with the provider's key in the URL if it needs one.
124
+ - `scheme` — `"auto"` (default: the workspace's light or dark), `"light"`
125
+ or `"dark"`.
126
+ - `workerUrl` — where MapLibre's worker loads from (above).
127
+ - `initialView` — `{ lat, lng, zoom }` when there are no positions to
128
+ fit. Default: the whole world.
129
+ - `fitOnChange` — refit to the places when the set on the map changes
130
+ (new rows, a search). Default `true`. `fitMaxZoom` — how close a fit
131
+ may go, default `14`. `maxZoom` — default `18`.
132
+ - `scrollZoom` — the wheel zooms. Default `true`; set `false` for a map
133
+ inside a long page (buttons and pinch still zoom).
134
+ - `controls` — the zoom and "Show all" buttons. Default `true`.
135
+ - `height` — the block's height: a number (px) or a CSS length. Default
136
+ `520`. The map needs one; the block never sizes itself to its content.
137
+ - `noun` · `nounPlural` — what a row is called: "account" → "Search
138
+ accounts", "12 accounts", "No accounts yet". Default `place`.
139
+ - `emptyTitle` · `emptyBody` · `emptyAction` — the empty list's words and
140
+ its one button (a `Button`), when `items` is empty.
141
+ - `label` — what the region is called to a screen reader. Default `Map`.
142
+ - `mapRef` — the handle (below).
143
+ - `className` — layout around it (width, margin). Never colour.
144
+
145
+ ## The handle
146
+
147
+ ```jsx
148
+ const map = useMapView();
149
+ <MapView mapRef={map} items={rows} />
150
+ map.current?.fitAll(); // every place on the map in view
151
+ map.current?.select("acc-07"); // select and move to it
152
+ map.current?.flyTo({ lat: 48.86, lng: 2.35, zoom: 11 });
153
+ map.current?.fitBounds({ west, south, east, north });
154
+ map.current?.zoomIn(); map.current?.zoomOut();
155
+ map.current?.getView(); // { center, zoom, bounds } or null before load
156
+ map.current?.getMap(); // the maplibre Map, for what the props do not cover
157
+ ```
158
+
159
+ ## Helpers
160
+
161
+ - `readPosition(row)` → `{ lat, lng }` or null.
162
+ - `boundsOf(positions)` → `{ west, south, east, north }` or null;
163
+ `inBounds(bounds, position)`.
164
+ - `distanceKm(a, b)` — great-circle distance; `formatDistance(km, "km" | "mi")`
165
+ → "350 m", "4.2 km", "24 mi".
166
+ - `sortByDistance(rows, origin, positionOf?)` → `[{ row, distanceKm }]`,
167
+ nearest first, rows without a position last.
168
+ - `clusterPoints(points, zoom, radius?)` and `zoomToReveal(points, id, zoom)`
169
+ — the clustering the map uses, for a list that groups the same way.
170
+
171
+ ## Example
172
+
173
+ ```jsx
174
+ // components/Customers.jsx — client: the map, the list, the popup
175
+ "use client";
176
+ import { Badge, Button } from "./ui";
177
+ import { FixtureNotice, useFixture } from "./parts";
178
+ import { MapView } from "./blocks/map";
179
+
180
+ const SAMPLE = [
181
+ { name: "Northwind Café", city: "Lisbon", lat: 38.7107, lng: -9.1422, status: "active" },
182
+ { name: "Acme Bakery", city: "Porto", lat: 41.1405, lng: -8.6132, status: "at risk" },
183
+ ];
184
+
185
+ export default function Customers({ customers }) {
186
+ const fixture = useFixture(customers, SAMPLE);
187
+ return (
188
+ <div className="grid gap-4">
189
+ {fixture.isFixture ? <FixtureNotice onClear={fixture.clear} /> : null}
190
+ <MapView
191
+ items={fixture.rows}
192
+ noun="customer"
193
+ titleOf={(c) => c.name}
194
+ metaOf={(c) => c.city}
195
+ toneOf={(c) => (c.status === "active" ? "success" : "warning")}
196
+ asideOf={(c) => <Badge tone={c.status === "active" ? "success" : "warning"} variant="soft">{c.status}</Badge>}
197
+ renderPopup={(c) => (
198
+ <div className="grid gap-2 pr-8">
199
+ <p className="font-medium">{c.name}</p>
200
+ <p className="text-muted-foreground">{c.city}</p>
201
+ <Button size="sm" variant="primary" href={`/customers/${c.id}`}>Open</Button>
202
+ </div>
203
+ )}
204
+ />
205
+ </div>
206
+ );
207
+ }
208
+ ```
209
+
210
+ ## What it will not do
211
+
212
+ - Geocode. It maps coordinates; turning an address into them is a
213
+ server job (store the result on the row).
214
+ - Heatmaps, routes, 3D buildings, satellite imagery. Pass `styleUrl` for
215
+ another base map; `getMap()` reaches maplibre for the rest.
216
+ - Move a marker from the keyboard. Dragging is pointer-only; keep a
217
+ coordinates field in the row's form for keyboard users.
218
+ - Scale past a few thousand markers. Each marker is a DOM node; for tens
219
+ of thousands, use maplibre layers through `getMap()`.
220
+ - Work offline or without WebGL: the list stays, and the map says why it
221
+ cannot show.
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "map",
3
+ "title": "MapView",
4
+ "description": "An interactive map (MapLibre) in the workspace theme: places from rows as markers in token tones or your own kit content, clustered when they crowd, a popup with kit parts, a searchable list beside the map that shares one selection and hover with it, fit-to-all, zoom controls, draggable markers with an optimistic save, and tinted areas for territories. Light and dark tiles follow the workspace scheme (OpenFreeMap by default, no key); `styleUrl` swaps them. Helpers for bounds, clustering and distances run anywhere.",
5
+ "useWhen": "A screen shows records that have a place: customers, stores, offices, field visits, deliveries, events, territories. The location-map and store-locator blocks and the Map view of work-views are built on it. Not for a chart of numbers by region (chart) or a hand-drawn diagram (sketch-canvas, flow-canvas).",
6
+ "tags": ["map", "maps", "location", "geo", "markers", "pins", "cluster", "maplibre", "openstreetmap", "store locator", "territory", "places", "coordinates", "latitude", "longitude", "distance"],
7
+ "exports": ["MapView", "useMapView", "MAP_STYLES", "DEFAULT_STYLE", "TONES", "readPosition", "boundsOf", "inBounds", "clusterPoints", "zoomToReveal", "distanceKm", "formatDistance", "sortByDistance"],
8
+ "files": ["index.js", "map-view.jsx", "list.jsx", "marks.jsx", "maplibre.js", "theme.js", "geo.js"],
9
+ "requires": [],
10
+ "dependencies": {
11
+ "maplibre-gl": "6.11.1",
12
+ "react-map-gl": "8.1.3"
13
+ }
14
+ }
@@ -0,0 +1,201 @@
1
+ "use client";
2
+
3
+ // MapView three ways — the gallery's and the smoke test's surface. Not
4
+ // installed into apps.
5
+ // Accounts a roaster's wholesale cafés: tones by status, a cluster in
6
+ // Lisbon, a popup with kit parts, the list beside the map,
7
+ // markers that drag (with a switch that makes the save fail),
8
+ // one account with no location yet.
9
+ // Territories the reps' regions as tinted areas, each rep's visits as
10
+ // an avatar marker (renderMarker), no list, a handle that
11
+ // flies between regions.
12
+ // Empty a new map: the friendly empty state and its one action.
13
+ import { useState } from "react";
14
+ import { Avatar, Badge, Button, Switch, TabPanel, Tabs, toast } from "../components/ui";
15
+ import { FixtureNotice, useFixture } from "../components/parts";
16
+ import { MapView, useMapView } from "../components/blocks/map";
17
+
18
+ const STATUS = {
19
+ active: { label: "Active", tone: "success", badge: "success" },
20
+ risk: { label: "At risk", tone: "warning", badge: "warning" },
21
+ trial: { label: "Trial", tone: "info", badge: "info" },
22
+ };
23
+
24
+ const ACCOUNTS = [
25
+ { id: "acc-01", name: "Fábrica do Grão", city: "Lisbon · Chiado", lat: 38.7107, lng: -9.1422, status: "active", mrr: 1840, bags: 64 },
26
+ { id: "acc-02", name: "Copo Alto", city: "Lisbon · Príncipe Real", lat: 38.7167, lng: -9.1489, status: "active", mrr: 1210, bags: 41 },
27
+ { id: "acc-03", name: "Rua Nova Espresso", city: "Lisbon · Cais do Sodré", lat: 38.7066, lng: -9.1447, status: "risk", mrr: 690, bags: 22 },
28
+ { id: "acc-04", name: "Alfama Torra", city: "Lisbon · Alfama", lat: 38.7118, lng: -9.1301, status: "trial", mrr: 0, bags: 6 },
29
+ { id: "acc-05", name: "Estrela Café", city: "Lisbon · Estrela", lat: 38.7131, lng: -9.1597, status: "active", mrr: 980, bags: 33 },
30
+ { id: "acc-06", name: "Ribeira Roasters", city: "Porto · Ribeira", lat: 41.1405, lng: -8.6132, status: "active", mrr: 1560, bags: 52 },
31
+ { id: "acc-07", name: "Mercado 14", city: "Madrid · Malasaña", lat: 40.4262, lng: -3.7041, status: "risk", mrr: 740, bags: 25 },
32
+ { id: "acc-08", name: "Café Brisa", city: "Barcelona · Gràcia", lat: 41.4036, lng: 2.1567, status: "active", mrr: 2120, bags: 71 },
33
+ { id: "acc-09", name: "Le Comptoir Bleu", city: "Paris · Canal Saint-Martin", lat: 48.8717, lng: 2.3645, status: "trial", mrr: 0, bags: 8 },
34
+ { id: "acc-10", name: "Maison Sillon", city: "Lyon · Croix-Rousse", lat: 45.7745, lng: 4.8311, status: "active", mrr: 1320, bags: 44 },
35
+ { id: "acc-11", name: "Kaffeehaus Nord", city: "Berlin · Prenzlauer Berg", lat: 52.5388, lng: 13.4244, status: "active", mrr: 1750, bags: 58 },
36
+ { id: "acc-12", name: "Grachtbar", city: "Amsterdam · Jordaan", lat: 52.3752, lng: 4.8836, status: "risk", mrr: 560, bags: 19 },
37
+ { id: "acc-13", name: "Mill & Pour", city: "London · Hackney", lat: 51.5450, lng: -0.0553, status: "active", mrr: 2410, bags: 80 },
38
+ { id: "acc-14", name: "Liffey Cup", city: "Dublin", status: "trial", mrr: 0, bags: 4 },
39
+ ];
40
+
41
+ const euro = (n) => (n ? `€${n.toLocaleString("en")}/mo` : "Trial");
42
+
43
+ export default function MapDemo() {
44
+ const [tab, setTab] = useState("accounts");
45
+ return (
46
+ <div className="grid gap-4">
47
+ <Tabs
48
+ aria-label="Map examples"
49
+ tabs={[
50
+ { key: "accounts", label: "Accounts" },
51
+ { key: "territories", label: "Territories" },
52
+ { key: "empty", label: "Empty" },
53
+ ]}
54
+ value={tab}
55
+ onChange={setTab}
56
+ />
57
+ <TabPanel tab="accounts" value={tab}>
58
+ <Accounts />
59
+ </TabPanel>
60
+ <TabPanel tab="territories" value={tab}>
61
+ {tab === "territories" ? <Territories /> : null}
62
+ </TabPanel>
63
+ <TabPanel tab="empty" value={tab}>
64
+ {tab === "empty" ? (
65
+ <MapView
66
+ items={[]}
67
+ noun="account"
68
+ emptyTitle="No accounts on the map yet"
69
+ emptyBody="Add an account with an address and it lands here, with its neighbours."
70
+ emptyAction={
71
+ <Button variant="primary" size="sm" icon="plus" onClick={() => toast("A new account form would open here")}>
72
+ Add account
73
+ </Button>
74
+ }
75
+ height={420}
76
+ />
77
+ ) : null}
78
+ </TabPanel>
79
+ </div>
80
+ );
81
+ }
82
+
83
+ function Accounts() {
84
+ const fixture = useFixture([], ACCOUNTS);
85
+ const [moved, setMoved] = useState({});
86
+ const [failSaves, setFailSaves] = useState(false);
87
+ const [selected, setSelected] = useState(null);
88
+ const rows = fixture.rows.map((r) => (moved[r.id] ? { ...r, ...moved[r.id] } : r));
89
+
90
+ async function onMove(row, pos) {
91
+ await new Promise((res) => setTimeout(res, 500));
92
+ if (failSaves) return { ok: false, error: "The server said no (the failing-save switch is on)." };
93
+ setMoved((m) => ({ ...m, [row.id]: pos }));
94
+ toast.success(`${row.name} moved`);
95
+ return { ok: true };
96
+ }
97
+
98
+ return (
99
+ <div className="grid gap-3">
100
+ {fixture.isFixture ? <FixtureNotice onClear={fixture.clear} /> : null}
101
+ <MapView
102
+ items={rows}
103
+ noun="account"
104
+ titleOf={(r) => r.name}
105
+ metaOf={(r) => r.city}
106
+ toneOf={(r) => STATUS[r.status].tone}
107
+ asideOf={(r) => (
108
+ <Badge tone={STATUS[r.status].badge} variant="soft">
109
+ {STATUS[r.status].label}
110
+ </Badge>
111
+ )}
112
+ searchOf={(r) => `${r.name} ${r.city} ${STATUS[r.status].label}`}
113
+ selectedId={selected}
114
+ onSelect={(id) => setSelected(id)}
115
+ onMove={onMove}
116
+ renderPopup={(r, { close }) => (
117
+ <div className="grid gap-3 pr-8">
118
+ <div className="grid gap-0.5">
119
+ <p className="font-medium leading-5">{r.name}</p>
120
+ <p className="text-muted-foreground">{r.city}</p>
121
+ </div>
122
+ <div className="flex items-center gap-3 text-sm">
123
+ <Badge tone={STATUS[r.status].badge} variant="soft">
124
+ {STATUS[r.status].label}
125
+ </Badge>
126
+ <span className="tabular-nums">{euro(r.mrr)}</span>
127
+ <span className="tabular-nums text-muted-foreground">{r.bags} bags</span>
128
+ </div>
129
+ <div className="flex gap-2">
130
+ <Button
131
+ size="sm"
132
+ variant="primary"
133
+ onClick={() => {
134
+ toast(`Opening ${r.name}`);
135
+ close();
136
+ }}
137
+ >
138
+ Open account
139
+ </Button>
140
+ <Button size="sm" variant="quiet" icon="calendar" onClick={() => toast(`Visit planned at ${r.name}`)}>
141
+ Plan a visit
142
+ </Button>
143
+ </div>
144
+ </div>
145
+ )}
146
+ />
147
+ <Switch id="map-fail" checked={failSaves} onChange={(e) => setFailSaves(e.target.checked)} label="Make the next drag fail to save" />
148
+ </div>
149
+ );
150
+ }
151
+
152
+ const REGIONS = [
153
+ { id: "iberia", label: "Iberia · Inês", tone: "chart-2", center: { lat: 40.2, lng: -5.5, zoom: 4.6 }, ring: [[-9.6, 36.9], [3.3, 36.9], [3.3, 43.8], [-9.6, 43.8]] },
154
+ { id: "france", label: "France · Hugo", tone: "chart-3", center: { lat: 46.6, lng: 2.4, zoom: 4.8 }, ring: [[-4.8, 42.4], [7.9, 42.4], [7.9, 51.1], [-4.8, 51.1]] },
155
+ { id: "north", label: "North · Femke", tone: "chart-4", center: { lat: 52.4, lng: 7.5, zoom: 4.8 }, ring: [[-6.5, 49.9], [15.1, 49.9], [15.1, 55.2], [-6.5, 55.2]] },
156
+ ];
157
+
158
+ const AREAS = REGIONS.map((r) => ({ id: r.id, label: r.label, tone: r.tone, geometry: { type: "Polygon", coordinates: [[...r.ring, r.ring[0]]] } }));
159
+
160
+ const VISITS = [
161
+ { id: "v-1", rep: "Inês Duarte", where: "Porto", lat: 41.15, lng: -8.61, region: "iberia" },
162
+ { id: "v-2", rep: "Inês Duarte", where: "Seville", lat: 37.39, lng: -5.98, region: "iberia" },
163
+ { id: "v-3", rep: "Hugo Marchal", where: "Bordeaux", lat: 44.84, lng: -0.58, region: "france" },
164
+ { id: "v-4", rep: "Hugo Marchal", where: "Lille", lat: 50.63, lng: 3.06, region: "france" },
165
+ { id: "v-5", rep: "Femke de Vries", where: "Hamburg", lat: 53.55, lng: 9.99, region: "north" },
166
+ { id: "v-6", rep: "Femke de Vries", where: "Utrecht", lat: 52.09, lng: 5.12, region: "north" },
167
+ ];
168
+
169
+ function Territories() {
170
+ const map = useMapView();
171
+ const visits = useFixture([], VISITS);
172
+ return (
173
+ <div className="grid gap-3">
174
+ <div className="flex flex-wrap gap-2">
175
+ {REGIONS.map((r) => (
176
+ <Button key={r.id} size="sm" variant="secondary" onClick={() => map.current?.flyTo(r.center)}>
177
+ {r.label}
178
+ </Button>
179
+ ))}
180
+ <Button size="sm" variant="quiet" icon="maximize" onClick={() => map.current?.fitAll()}>
181
+ All regions
182
+ </Button>
183
+ </div>
184
+ <MapView
185
+ mapRef={map}
186
+ items={visits.rows}
187
+ list="none"
188
+ cluster={false}
189
+ noun="visit"
190
+ titleOf={(v) => `${v.rep} · ${v.where}`}
191
+ metaOf={(v) => `Next visit in ${v.where}`}
192
+ toneOf={(v) => REGIONS.find((r) => r.id === v.region)?.tone}
193
+ renderMarker={(v, { selected }) => <Avatar name={v.rep} size="xs" className={selected ? "ring-2 ring-primary" : "ring-2 ring-background"} />}
194
+ areas={AREAS}
195
+ onAreaClick={(a) => toast(a.label)}
196
+ height={440}
197
+ label="Sales territories"
198
+ />
199
+ </div>
200
+ );
201
+ }
@@ -0,0 +1,198 @@
1
+ // The map's arithmetic: plain functions of plain objects, so they run in
2
+ // node, on the server and in the blocks built on this one (location-map,
3
+ // store-locator, the Map view of work-views). No maplibre in here.
4
+ //
5
+ // A position is { lat, lng } in degrees (WGS 84, what every geocoder and
6
+ // GPS gives). Longitude first only where maplibre wants it ([lng, lat]).
7
+
8
+ const EARTH_KM = 6371.0088;
9
+ const MAX_LAT = 85.05112878; // web mercator stops here
10
+
11
+ const num = (v) => (typeof v === "string" && v.trim() !== "" ? Number(v) : v);
12
+ const isLat = (v) => typeof v === "number" && Number.isFinite(v) && v >= -90 && v <= 90;
13
+ const isLng = (v) => typeof v === "number" && Number.isFinite(v) && v >= -180 && v <= 180;
14
+
15
+ /** A valid { lat, lng } from whatever a row holds, or null. Reads
16
+ * `lat`/`lng`, `latitude`/`longitude`, `lon`, `location: { lat, lng }`
17
+ * (the task-model shape), `position` and `[lng, lat]` pairs (GeoJSON order). */
18
+ export function readPosition(item) {
19
+ if (!item || typeof item !== "object") return null;
20
+ if (Array.isArray(item)) return toPos(num(item[1]), num(item[0]));
21
+ const direct = toPos(num(item.lat ?? item.latitude), num(item.lng ?? item.lon ?? item.longitude));
22
+ if (direct) return direct;
23
+ for (const key of ["location", "position", "coords", "geo"]) {
24
+ const inner = item[key];
25
+ if (inner && typeof inner === "object" && inner !== item) {
26
+ const p = readPosition(inner);
27
+ if (p) return p;
28
+ }
29
+ }
30
+ return null;
31
+ }
32
+
33
+ function toPos(lat, lng) {
34
+ return isLat(lat) && isLng(lng) ? { lat, lng } : null;
35
+ }
36
+
37
+ /** [lng, lat] → a point on the web-mercator world at `zoom`, in pixels
38
+ * (the world is 512 px wide at zoom 0, as maplibre draws it). */
39
+ export function project({ lat, lng }, zoom) {
40
+ const size = 512 * 2 ** zoom;
41
+ const clamped = Math.max(-MAX_LAT, Math.min(MAX_LAT, lat));
42
+ const s = Math.sin((clamped * Math.PI) / 180);
43
+ return {
44
+ x: ((lng + 180) / 360) * size,
45
+ y: (0.5 - Math.log((1 + s) / (1 - s)) / (4 * Math.PI)) * size,
46
+ };
47
+ }
48
+
49
+ /** The box around some positions: { west, south, east, north }, or null
50
+ * for none. One position gives a box of no size (fitBounds pads it). */
51
+ export function boundsOf(positions) {
52
+ let west = Infinity;
53
+ let south = Infinity;
54
+ let east = -Infinity;
55
+ let north = -Infinity;
56
+ for (const p of positions ?? []) {
57
+ if (!p) continue;
58
+ if (p.lng < west) west = p.lng;
59
+ if (p.lng > east) east = p.lng;
60
+ if (p.lat < south) south = p.lat;
61
+ if (p.lat > north) north = p.lat;
62
+ }
63
+ return west === Infinity ? null : { west, south, east, north };
64
+ }
65
+
66
+ /** Is the box a single spot (every position the same, give or take a metre)? */
67
+ export function isPointBounds(b) {
68
+ return Boolean(b) && Math.abs(b.east - b.west) < 1e-5 && Math.abs(b.north - b.south) < 1e-5;
69
+ }
70
+
71
+ /** Does `b` hold the position? */
72
+ export function inBounds(b, p) {
73
+ if (!b || !p) return false;
74
+ const lngIn = b.west <= b.east ? p.lng >= b.west && p.lng <= b.east : p.lng >= b.west || p.lng <= b.east; // across the date line
75
+ return lngIn && p.lat >= b.south && p.lat <= b.north;
76
+ }
77
+
78
+ /**
79
+ * Group points that would overlap on screen at `zoom`.
80
+ *
81
+ * points [{ id, lat, lng }]
82
+ * radius how close, in screen pixels, two points may sit before they
83
+ * merge (default 48 — a marker and its label)
84
+ *
85
+ * Returns, in the input order of each group's first point:
86
+ * { type: "point", id, lat, lng }
87
+ * { type: "cluster", id, ids, count, lat, lng, bounds }
88
+ *
89
+ * Greedy, like supercluster's single level: each point not yet taken
90
+ * gathers every free point within `radius` of it (a grid of radius-sized
91
+ * cells finds them), and the group sits at their mean. It depends on the
92
+ * zoom only, never on the pan, so the map recomputes it when the zoom
93
+ * crosses a step, not on every frame. Thousands of points take a few ms.
94
+ */
95
+ export function clusterPoints(points, zoom, radius = 48) {
96
+ const list = (points ?? []).filter((p) => p && isLat(p.lat) && isLng(p.lng));
97
+ const px = list.map((p) => project(p, zoom));
98
+ const cell = Math.max(1, radius);
99
+ const grid = new Map();
100
+ px.forEach((q, i) => {
101
+ const key = `${Math.floor(q.x / cell)}:${Math.floor(q.y / cell)}`;
102
+ const bucket = grid.get(key);
103
+ if (bucket) bucket.push(i);
104
+ else grid.set(key, [i]);
105
+ });
106
+ const taken = new Uint8Array(list.length);
107
+ const out = [];
108
+ const r2 = radius * radius;
109
+ for (let i = 0; i < list.length; i++) {
110
+ if (taken[i]) continue;
111
+ taken[i] = 1;
112
+ const members = [i];
113
+ const cx = Math.floor(px[i].x / cell);
114
+ const cy = Math.floor(px[i].y / cell);
115
+ for (let gx = cx - 1; gx <= cx + 1; gx++) {
116
+ for (let gy = cy - 1; gy <= cy + 1; gy++) {
117
+ for (const j of grid.get(`${gx}:${gy}`) ?? []) {
118
+ if (taken[j]) continue;
119
+ const dx = px[j].x - px[i].x;
120
+ const dy = px[j].y - px[i].y;
121
+ if (dx * dx + dy * dy <= r2) {
122
+ taken[j] = 1;
123
+ members.push(j);
124
+ }
125
+ }
126
+ }
127
+ }
128
+ if (members.length === 1) {
129
+ const p = list[i];
130
+ out.push({ type: "point", id: p.id, lat: p.lat, lng: p.lng });
131
+ continue;
132
+ }
133
+ const ps = members.map((m) => list[m]);
134
+ out.push({
135
+ type: "cluster",
136
+ id: `cluster:${ps[0].id}:${ps.length}`,
137
+ ids: ps.map((p) => p.id),
138
+ count: ps.length,
139
+ lat: ps.reduce((s, p) => s + p.lat, 0) / ps.length,
140
+ lng: ps.reduce((s, p) => s + p.lng, 0) / ps.length,
141
+ bounds: boundsOf(ps),
142
+ });
143
+ }
144
+ return out;
145
+ }
146
+
147
+ /** The lowest zoom, from `zoom` up in half steps to `maxZoom`, at which
148
+ * point `id` stands on its own — where the map must go to show it. Null
149
+ * when it never does (it shares its spot with another point). */
150
+ export function zoomToReveal(points, id, zoom, { radius = 48, maxZoom = 18 } = {}) {
151
+ for (let z = Math.max(0, Math.ceil(zoom * 2) / 2); z <= maxZoom; z += 0.5) {
152
+ const groups = clusterPoints(points, z, radius);
153
+ if (groups.some((g) => g.type === "point" && g.id === id)) return z;
154
+ }
155
+ return null;
156
+ }
157
+
158
+ /** Great-circle distance between two positions, in kilometres. */
159
+ export function distanceKm(a, b) {
160
+ if (!a || !b) return NaN;
161
+ const rad = Math.PI / 180;
162
+ const dLat = (b.lat - a.lat) * rad;
163
+ const dLng = (b.lng - a.lng) * rad;
164
+ const h = Math.sin(dLat / 2) ** 2 + Math.cos(a.lat * rad) * Math.cos(b.lat * rad) * Math.sin(dLng / 2) ** 2;
165
+ return 2 * EARTH_KM * Math.asin(Math.min(1, Math.sqrt(h)));
166
+ }
167
+
168
+ /** "350 m", "4.2 km", "38 km" — or miles with unit "mi" ("0.3 mi", "24 mi"). */
169
+ export function formatDistance(km, unit = "km") {
170
+ if (!Number.isFinite(km)) return "";
171
+ if (unit === "mi") {
172
+ const mi = km / 1.609344;
173
+ return mi < 10 ? `${mi.toFixed(1)} mi` : `${Math.round(mi).toLocaleString("en")} mi`;
174
+ }
175
+ if (km < 1) return `${Math.max(10, Math.round((km * 1000) / 10) * 10)} m`;
176
+ return km < 10 ? `${km.toFixed(1)} km` : `${Math.round(km).toLocaleString("en")} km`;
177
+ }
178
+
179
+ /** Rows nearest first from `origin`, each with its `distanceKm`; rows
180
+ * with no position go last (distance NaN). `positionOf` reads a row. */
181
+ export function sortByDistance(rows, origin, positionOf = readPosition) {
182
+ return (rows ?? [])
183
+ .map((row) => ({ row, distanceKm: distanceKm(origin, positionOf(row)) }))
184
+ .sort((a, b) => (Number.isNaN(a.distanceKm) ? 1 : 0) - (Number.isNaN(b.distanceKm) ? 1 : 0) || a.distanceKm - b.distanceKm);
185
+ }
186
+
187
+ /** A cheap key for a set of ids: equal keys, the same set. */
188
+ export function idsKey(ids) {
189
+ return [...ids].map(String).sort().join("|");
190
+ }
191
+
192
+ /** Lower-cased words a row is found by: the title and the meta line. */
193
+ export function matches(text, query) {
194
+ const q = String(query ?? "").trim().toLowerCase();
195
+ if (!q) return true;
196
+ const hay = String(text ?? "").toLowerCase();
197
+ return q.split(/\s+/).every((w) => hay.includes(w));
198
+ }
@@ -0,0 +1,8 @@
1
+ // The Map block — `makersclaw add map`. Fixed: compose it through its
2
+ // props (README.md beside this file). The helpers (geo.js) are plain
3
+ // functions and work on the server; the blocks built on this one
4
+ // (location-map, store-locator, the Map view of work-views) import it,
5
+ // never maplibre-gl or react-map-gl directly.
6
+ export { MapView, useMapView } from "./map-view";
7
+ export { MAP_STYLES, DEFAULT_STYLE, TONES } from "./theme";
8
+ export { readPosition, boundsOf, inBounds, clusterPoints, zoomToReveal, distanceKm, formatDistance, sortByDistance } from "./geo";