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,115 @@
1
+ import { Badge, cx } from "../../ui";
2
+
3
+ // Money and the cart line, shared by the product page's parts. Prices are
4
+ // numbers in the currency's major unit (19.5 is $19.50) — the same shape
5
+ // the cart block reads, so a line made here drops straight into it.
6
+
7
+ const formats = new Map();
8
+
9
+ /** 1290 → "$1,290.00" in the reader's locale. A bad currency code falls
10
+ * back to USD rather than throwing mid-render. */
11
+ export function formatPrice(amount, currency = "USD", locale) {
12
+ const key = `${locale ?? ""}|${currency}`;
13
+ let f = formats.get(key);
14
+ if (!f) {
15
+ try {
16
+ f = new Intl.NumberFormat(locale, { style: "currency", currency });
17
+ } catch {
18
+ f = new Intl.NumberFormat(locale, { style: "currency", currency: "USD" });
19
+ }
20
+ formats.set(key, f);
21
+ }
22
+ return f.format(Number(amount) || 0);
23
+ }
24
+
25
+ const num = (v) => (typeof v === "number" && Number.isFinite(v) ? v : null);
26
+
27
+ /** The price as shown: now, the old price struck through, and how much
28
+ * is off. `size` lg on a product page, md on a card. */
29
+ export function Price({ price, compareAt, currency, locale, size = "lg", className }) {
30
+ const now = num(price);
31
+ const was = num(compareAt);
32
+ if (now === null) return null;
33
+ const off = was !== null && was > now ? Math.round(((was - now) / was) * 100) : 0;
34
+ return (
35
+ <p className={cx("flex flex-wrap items-baseline gap-x-2 gap-y-1", className)}>
36
+ <span data-nums className={cx("font-semibold text-foreground", size === "lg" ? "text-2xl" : "text-base")}>
37
+ {formatPrice(now, currency, locale)}
38
+ </span>
39
+ {off ? (
40
+ <>
41
+ <span data-nums className="text-sm text-muted-foreground line-through">
42
+ <span className="sr-only">was </span>
43
+ {formatPrice(was, currency, locale)}
44
+ </span>
45
+ <Badge tone="success" variant="soft">
46
+ {off}% off
47
+ </Badge>
48
+ </>
49
+ ) : null}
50
+ </p>
51
+ );
52
+ }
53
+
54
+ // An option's values arrive as strings or objects; objects from here on.
55
+ export const valuesOf = (option) =>
56
+ (option?.values ?? []).filter((v) => v != null).map((v) => (typeof v === "object" ? v : { value: String(v), label: String(v) }));
57
+
58
+ export const labelOf = (v) => v?.label ?? String(v?.value ?? "");
59
+
60
+ /** The first choice per option: `product.selected` where it names a
61
+ * value that exists and is not sold out, else the first open value. */
62
+ export function defaultSelection(product) {
63
+ const out = {};
64
+ for (const o of product?.options ?? []) {
65
+ const values = valuesOf(o);
66
+ const given = product?.selected?.[o.key];
67
+ const hit = values.find((v) => v.value === given && !v.disabled);
68
+ out[o.key] = (hit ?? values.find((v) => !v.disabled))?.value ?? null;
69
+ }
70
+ return out;
71
+ }
72
+
73
+ /** What one unit costs with these choices: a chosen value with its own
74
+ * `price` (a plan, a larger size) replaces the product's. */
75
+ export function priceFor(product, selected) {
76
+ let price = product?.price;
77
+ let compareAt = product?.compareAt;
78
+ for (const o of product?.options ?? []) {
79
+ const v = valuesOf(o).find((x) => x.value === selected?.[o.key]);
80
+ if (v && num(v.price) !== null) {
81
+ price = v.price;
82
+ compareAt = v.compareAt;
83
+ }
84
+ }
85
+ return { price, compareAt };
86
+ }
87
+
88
+ /**
89
+ * The cart line for a product with these choices — the shape `cart`
90
+ * takes as an item:
91
+ * { id, key, name, price, compareAt?, currency, image, href?, quantity,
92
+ * selected: { size: "m" }, variant: "M · Sand" }
93
+ * `key` is the product id plus its choices, so the same shirt in two
94
+ * sizes is two lines and the same one added twice is one.
95
+ */
96
+ export function cartLine(product, selected = {}, quantity = 1, currency = "USD") {
97
+ const { price, compareAt } = priceFor(product, selected);
98
+ const chosen = (product?.options ?? [])
99
+ .map((o) => ({ o, v: valuesOf(o).find((x) => x.value === selected[o.key]) }))
100
+ .filter((c) => c.v);
101
+ const first = product?.images?.[0];
102
+ return {
103
+ id: product?.id,
104
+ key: [product?.id, ...chosen.map((c) => `${c.o.key}=${c.v.value}`)].join(":"),
105
+ name: product?.name,
106
+ price,
107
+ ...(num(compareAt) !== null ? { compareAt } : {}),
108
+ currency: product?.currency ?? currency,
109
+ image: product?.image ?? (typeof first === "object" ? first?.src : first) ?? null,
110
+ ...(product?.href ? { href: product.href } : {}),
111
+ quantity,
112
+ selected: Object.fromEntries(chosen.map((c) => [c.o.key, c.v.value])),
113
+ variant: chosen.map((c) => labelOf(c.v)).join(" · "),
114
+ };
115
+ }
@@ -0,0 +1,348 @@
1
+ "use client";
2
+
3
+ import { useEffect, useId, useRef, useState } from "react";
4
+ import {
5
+ Accordion,
6
+ Badge,
7
+ Breadcrumb,
8
+ Button,
9
+ Empty,
10
+ Icon,
11
+ Image,
12
+ Kicker,
13
+ Notice,
14
+ Rating,
15
+ Skeleton,
16
+ TabPanel,
17
+ Tabs,
18
+ Toggle,
19
+ cx,
20
+ } from "../../ui";
21
+ import { Price, cartLine, defaultSelection, priceFor } from "./price";
22
+ import { OptionPicker, QuantityControl } from "./product-options";
23
+ import { ProductMedia, picturesOf } from "./product-media";
24
+
25
+ // One product: its pictures, name, price, rating, options, quantity, add
26
+ // to cart and save, and the details under them. The add goes out through
27
+ // `onAddToCart(line)`, which may fail; the block shows why.
28
+
29
+ async function attempt(fn, ...args) {
30
+ try {
31
+ const r = await fn(...args);
32
+ return r && typeof r === "object" ? r : { ok: Boolean(r) };
33
+ } catch (e) {
34
+ return { ok: false, error: e?.message };
35
+ }
36
+ }
37
+
38
+ function Details({ sections, as }) {
39
+ const id = useId();
40
+ const list = (sections ?? []).filter(Boolean).map((s, i) => ({ key: s.key ?? `s${i}`, ...s }));
41
+ const [tab, setTab] = useState(list[0]?.key);
42
+ if (list.length === 0) return null;
43
+ const body = (s) => (typeof s.content === "string" ? <p className="leading-relaxed text-muted-foreground">{s.content}</p> : s.content);
44
+ if (as === "tabs") {
45
+ const current = list.some((s) => s.key === tab) ? tab : list[0].key;
46
+ return (
47
+ <div className="min-w-0">
48
+ <Tabs id={id} aria-label="Product details" tabs={list.map((s) => ({ key: s.key, label: s.title }))} value={current} onChange={setTab} />
49
+ {list.map((s) => (
50
+ <TabPanel key={s.key} id={id} tab={s.key} value={current} className="text-sm">
51
+ {body(s)}
52
+ </TabPanel>
53
+ ))}
54
+ </div>
55
+ );
56
+ }
57
+ return (
58
+ <Accordion
59
+ aria-label="Product details"
60
+ defaultValue={list[0].key}
61
+ items={list.map((s) => ({ key: s.key, title: s.title, content: body(s) }))}
62
+ />
63
+ );
64
+ }
65
+
66
+ function Perks({ perks }) {
67
+ const list = (perks ?? []).filter(Boolean);
68
+ if (list.length === 0) return null;
69
+ return (
70
+ <ul className="grid gap-2 sm:grid-cols-2">
71
+ {list.map((p, i) => (
72
+ <li key={p.label ?? i} className="flex items-center gap-2 rounded-md bg-muted px-3 py-2.5 text-sm text-foreground">
73
+ {p.icon ? <Icon name={p.icon} size={16} className="shrink-0 text-muted-foreground" /> : null}
74
+ <span className="min-w-0">{p.label}</span>
75
+ </li>
76
+ ))}
77
+ </ul>
78
+ );
79
+ }
80
+
81
+ function Loading({ gallery }) {
82
+ return (
83
+ <div aria-busy="true" aria-label="Loading the product" className={cx("grid gap-8", gallery !== "overlay" && "md:grid-cols-2")}>
84
+ <Skeleton shape="rect" className="aspect-square w-full" />
85
+ <div className="grid content-start gap-4">
86
+ <Skeleton className="w-1/4" />
87
+ <Skeleton className="h-8 w-3/4" />
88
+ <Skeleton className="h-7 w-1/3" />
89
+ <Skeleton className="w-full" />
90
+ <Skeleton className="w-5/6" />
91
+ <Skeleton shape="rect" className="h-11 w-full" />
92
+ <Skeleton shape="rect" className="h-11 w-full" />
93
+ </div>
94
+ </div>
95
+ );
96
+ }
97
+
98
+ export function ProductDetail({
99
+ product,
100
+ gallery = "thumbs",
101
+ details = "accordion",
102
+ onAddToCart,
103
+ onBuyNow,
104
+ onFavorite,
105
+ favorite = false,
106
+ onViewCart,
107
+ breadcrumb,
108
+ perks,
109
+ currency = "USD",
110
+ locale,
111
+ quantity: showQuantity = true,
112
+ maxQuantity = 10,
113
+ imageFit = "cover",
114
+ loading = false,
115
+ error,
116
+ onRetry,
117
+ addLabel = "Add to cart",
118
+ buyLabel = "Buy now",
119
+ headingLevel = 2,
120
+ className,
121
+ }) {
122
+ const [selected, setSelected] = useState(() => defaultSelection(product));
123
+ const [qty, setQty] = useState(1);
124
+ const [status, setStatus] = useState({ state: "idle" }); // idle · busy · added · failed
125
+ const [fav, setFav] = useState(favorite);
126
+ const [favError, setFavError] = useState(null);
127
+ const shown = useRef(product?.id);
128
+
129
+ // A different product: its own first choices, one of it, no old message.
130
+ useEffect(() => {
131
+ if (shown.current === product?.id) return;
132
+ shown.current = product?.id;
133
+ setSelected(defaultSelection(product));
134
+ setQty(1);
135
+ setStatus({ state: "idle" });
136
+ }, [product]);
137
+ useEffect(() => setFav(favorite), [favorite]);
138
+
139
+ if (loading) return <Loading gallery={gallery} />;
140
+ if (error) {
141
+ return (
142
+ <Notice
143
+ tone="danger"
144
+ title="The product did not load"
145
+ action={onRetry ? <Button size="sm" onClick={onRetry}>Try again</Button> : null}
146
+ className={className}
147
+ >
148
+ {typeof error === "string" ? error : null}
149
+ </Notice>
150
+ );
151
+ }
152
+ if (!product) {
153
+ return <Empty icon="package" title="No product here" body="It may have been removed, or the link is out of date." className={className} />;
154
+ }
155
+
156
+ const cur = product.currency ?? currency;
157
+ const { price, compareAt } = priceFor(product, selected);
158
+ const soldOut = product.available === false;
159
+ const max = Math.max(1, Math.min(maxQuantity, typeof product.stock === "number" ? product.stock : maxQuantity));
160
+ const busy = status.state === "busy";
161
+ const H = `h${Math.min(6, Math.max(1, headingLevel))}`;
162
+ const perkList = perks ?? product.perks;
163
+
164
+ function choose(key, value) {
165
+ setSelected((s) => ({ ...s, [key]: value }));
166
+ if (status.state !== "busy") setStatus({ state: "idle" });
167
+ }
168
+
169
+ async function run(which, fn) {
170
+ const line = cartLine(product, selected, showQuantity ? qty : 1, cur);
171
+ setStatus({ state: "busy", which });
172
+ const r = await attempt(fn, line);
173
+ if (r.ok) setStatus({ state: which === "cart" ? "added" : "idle", line });
174
+ else setStatus({ state: "failed", which, error: r.error || (which === "cart" ? "It was not added to your cart." : "The checkout did not open.") });
175
+ }
176
+
177
+ async function toggleFavorite(next) {
178
+ setFav(next);
179
+ setFavError(null);
180
+ if (!onFavorite) return;
181
+ const r = await attempt(onFavorite, product, next);
182
+ if (!r.ok) {
183
+ setFav(!next);
184
+ setFavError(r.error || (next ? "It was not saved." : "It was not removed."));
185
+ }
186
+ }
187
+
188
+ const buyBox = (
189
+ <div className="grid min-w-0 content-start gap-5">
190
+ <div className="grid gap-2">
191
+ {product.badge || product.brand ? (
192
+ <div className="flex flex-wrap items-center gap-2">
193
+ {product.brand ? <Kicker>{product.brand}</Kicker> : null}
194
+ {product.badge ? <Badge variant="outline">{product.badge}</Badge> : null}
195
+ </div>
196
+ ) : null}
197
+ <H className="text-2xl font-semibold leading-tight tracking-tight text-foreground sm:text-3xl">{product.name}</H>
198
+ <div className="flex flex-wrap items-center gap-x-4 gap-y-2">
199
+ <Price price={price} compareAt={compareAt} currency={cur} locale={locale} />
200
+ {typeof product.rating === "number" ? (
201
+ <span className="inline-flex items-center gap-2 text-sm text-muted-foreground">
202
+ <Rating value={product.rating} size="sm" label="Rated" />
203
+ <span data-nums>
204
+ {product.rating.toFixed(1)}
205
+ {typeof product.reviews === "number" ? ` (${product.reviews.toLocaleString(locale)} reviews)` : ""}
206
+ </span>
207
+ </span>
208
+ ) : null}
209
+ </div>
210
+ {product.description ? <p className="max-w-prose text-sm leading-relaxed text-muted-foreground">{product.description}</p> : null}
211
+ </div>
212
+
213
+ {product.stats?.length ? (
214
+ <dl className="flex flex-wrap gap-x-6 gap-y-2 rounded-md bg-muted px-4 py-3 text-sm">
215
+ {product.stats.map((s) => (
216
+ <div key={s.label} className="flex flex-row-reverse items-baseline justify-end gap-1.5">
217
+ <dt className="text-muted-foreground">{s.label}</dt>
218
+ <dd data-nums className="font-semibold text-foreground">
219
+ {s.value}
220
+ </dd>
221
+ </div>
222
+ ))}
223
+ </dl>
224
+ ) : null}
225
+
226
+ {(product.options ?? []).length ? (
227
+ <div className="grid gap-5">
228
+ {product.options.map((o) => (
229
+ <OptionPicker
230
+ key={o.key}
231
+ option={o}
232
+ value={selected[o.key]}
233
+ onChange={(v) => choose(o.key, v)}
234
+ currency={cur}
235
+ locale={locale}
236
+ />
237
+ ))}
238
+ </div>
239
+ ) : null}
240
+
241
+ {showQuantity && !soldOut ? <QuantityControl value={qty} onChange={setQty} max={max} disabled={busy} /> : null}
242
+
243
+ <div className="grid gap-2">
244
+ <div className="flex gap-2">
245
+ <Button
246
+ variant="primary"
247
+ icon={soldOut ? undefined : "shopping-cart"}
248
+ className="min-w-0 flex-1"
249
+ disabled={soldOut || !onAddToCart || (busy && status.which !== "cart")}
250
+ loading={busy && status.which === "cart"}
251
+ onClick={() => run("cart", onAddToCart)}
252
+ >
253
+ {soldOut ? "Sold out" : addLabel}
254
+ </Button>
255
+ {onFavorite ? (
256
+ <Toggle
257
+ icon="heart"
258
+ label={fav ? "Saved — remove from favourites" : "Save to favourites"}
259
+ pressed={fav}
260
+ onPressedChange={toggleFavorite}
261
+ />
262
+ ) : null}
263
+ </div>
264
+ {onBuyNow && !soldOut ? (
265
+ <Button
266
+ variant="secondary"
267
+ className="w-full"
268
+ disabled={busy && status.which !== "buy"}
269
+ loading={busy && status.which === "buy"}
270
+ onClick={() => run("buy", onBuyNow)}
271
+ >
272
+ {buyLabel}
273
+ </Button>
274
+ ) : null}
275
+ <div aria-live="polite" className="empty:hidden">
276
+ {status.state === "added" ? (
277
+ <Notice
278
+ tone="success"
279
+ action={onViewCart ? <Button size="sm" onClick={onViewCart}>View cart</Button> : null}
280
+ onDismiss={() => setStatus({ state: "idle" })}
281
+ >
282
+ {status.line.quantity} × {status.line.name}
283
+ {status.line.variant ? ` (${status.line.variant})` : ""} added to your cart.
284
+ </Notice>
285
+ ) : null}
286
+ </div>
287
+ {status.state === "failed" ? (
288
+ <Notice tone="danger" onDismiss={() => setStatus({ state: "idle" })}>
289
+ {status.error}
290
+ </Notice>
291
+ ) : null}
292
+ {favError ? (
293
+ <Notice tone="danger" onDismiss={() => setFavError(null)}>
294
+ {favError}
295
+ </Notice>
296
+ ) : null}
297
+ </div>
298
+
299
+ <Perks perks={perkList} />
300
+ {gallery !== "overlay" ? <Details sections={product.details} as={details} /> : null}
301
+ </div>
302
+ );
303
+
304
+ const crumbs = breadcrumb?.length ? <Breadcrumb items={breadcrumb} className="mb-6" /> : null;
305
+
306
+ if (gallery === "overlay") {
307
+ const scene = product.scene ?? picturesOf(product)[0];
308
+ const src = typeof scene === "object" ? scene?.src : scene;
309
+ return (
310
+ <section aria-label={product.name} className={cx("min-w-0", className)}>
311
+ {crumbs}
312
+ <div className="grid gap-4 lg:relative lg:block">
313
+ <Image
314
+ src={src}
315
+ alt={typeof scene === "object" && scene?.alt ? scene.alt : `${product.name} in a room`}
316
+ ratio="photo"
317
+ rounded="lg"
318
+ loading="eager"
319
+ className="w-full lg:aspect-auto lg:h-[40rem]"
320
+ />
321
+ <div className="rounded-lg border border-border bg-card p-5 text-card-foreground shadow-lg lg:absolute lg:inset-y-6 lg:end-6 lg:w-[26rem] lg:overflow-y-auto">
322
+ {buyBox}
323
+ </div>
324
+ </div>
325
+ {product.details?.length ? (
326
+ <div className="mt-8 max-w-3xl">
327
+ <Details sections={product.details} as={details} />
328
+ </div>
329
+ ) : null}
330
+ </section>
331
+ );
332
+ }
333
+
334
+ return (
335
+ <section aria-label={product.name} className={cx("min-w-0", className)}>
336
+ {crumbs}
337
+ <div
338
+ className={cx(
339
+ "grid items-start gap-8",
340
+ gallery === "stack" ? "md:grid-cols-[3fr_2fr] lg:gap-12" : "md:grid-cols-2 lg:gap-12",
341
+ )}
342
+ >
343
+ <ProductMedia product={product} layout={gallery} fit={imageFit} />
344
+ <div className={cx("min-w-0", gallery === "stack" && "md:sticky md:top-6")}>{buyBox}</div>
345
+ </div>
346
+ </section>
347
+ );
348
+ }
@@ -0,0 +1,102 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Carousel, Image, cx } from "../../ui";
5
+
6
+ // The product's pictures, four ways:
7
+ // thumbs one large picture, a row of thumbnails under it
8
+ // rail the same, the thumbnails in a column beside it from lg up
9
+ // carousel one at a time, swipe or arrows, dots under it
10
+ // stack every picture shown: the first full width, the rest in pairs
11
+
12
+ /** product.images (strings or { src, alt }) or product.image → [{ src, alt }]. */
13
+ export function picturesOf(product) {
14
+ const list = product?.images?.length ? product.images : product?.image ? [product.image] : [];
15
+ return list
16
+ .filter(Boolean)
17
+ .map((p, i) =>
18
+ typeof p === "object"
19
+ ? { src: p.src, alt: p.alt ?? `${product?.name ?? "Product"}, picture ${i + 1}` }
20
+ : { src: p, alt: `${product?.name ?? "Product"}, picture ${i + 1}` },
21
+ );
22
+ }
23
+
24
+ function Thumbs({ pictures, index, onPick, vertical }) {
25
+ if (pictures.length < 2) return null;
26
+ return (
27
+ <div
28
+ role="group"
29
+ aria-label="Pictures"
30
+ className={cx(
31
+ "flex gap-2",
32
+ vertical ? "flex-wrap lg:max-h-[32rem] lg:flex-col lg:flex-nowrap lg:overflow-y-auto lg:p-0.5" : "flex-wrap",
33
+ )}
34
+ >
35
+ {pictures.map((p, i) => (
36
+ // raw: a thumbnail picker — the kit's ToggleGroup is a text track
37
+ // that neither wraps nor scrolls a row of pictures.
38
+ <button
39
+ key={`${p.src}-${i}`}
40
+ type="button"
41
+ aria-pressed={i === index}
42
+ aria-label={`Show picture ${i + 1} of ${pictures.length}`}
43
+ onClick={() => onPick(i)}
44
+ className={cx(
45
+ "shrink-0 overflow-hidden rounded-md border-2 outline-none transition-colors",
46
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
47
+ i === index ? "border-foreground" : "border-border hover:border-muted-foreground",
48
+ )}
49
+ >
50
+ <Image src={p.src} alt="" ratio="square" rounded="none" className="size-14 sm:size-16" />
51
+ </button>
52
+ ))}
53
+ </div>
54
+ );
55
+ }
56
+
57
+ export function ProductMedia({ product, layout = "thumbs", fit = "cover", className }) {
58
+ const pictures = picturesOf(product);
59
+ const [index, setIndex] = useState(0);
60
+ const at = Math.min(index, Math.max(0, pictures.length - 1));
61
+
62
+ if (pictures.length === 0) {
63
+ return <Image src={null} alt={product?.name ?? "No picture"} ratio="square" rounded="lg" className={cx("w-full", className)} />;
64
+ }
65
+
66
+ if (layout === "carousel" && pictures.length > 1) {
67
+ return (
68
+ <Carousel label={`${product?.name ?? "Product"} pictures`} className={className}>
69
+ {pictures.map((p, i) => (
70
+ <Image key={`${p.src}-${i}`} src={p.src} alt={p.alt} ratio="square" fit={fit} rounded="lg" loading={i === 0 ? "eager" : "lazy"} />
71
+ ))}
72
+ </Carousel>
73
+ );
74
+ }
75
+
76
+ if (layout === "stack") {
77
+ const [first, ...rest] = pictures;
78
+ return (
79
+ <div className={cx("grid gap-3", className)}>
80
+ <Image src={first.src} alt={first.alt} ratio="square" fit={fit} rounded="lg" loading="eager" />
81
+ {rest.length ? (
82
+ <div className="grid grid-cols-2 gap-3">
83
+ {rest.map((p, i) => (
84
+ <Image key={`${p.src}-${i}`} src={p.src} alt={p.alt} ratio="portrait" fit={fit} rounded="lg" />
85
+ ))}
86
+ </div>
87
+ ) : null}
88
+ </div>
89
+ );
90
+ }
91
+
92
+ const rail = layout === "rail";
93
+ const main = pictures[at];
94
+ return (
95
+ <div className={cx("flex min-w-0 flex-col gap-3", rail && "lg:flex-row-reverse", className)}>
96
+ <div className="min-w-0 flex-1">
97
+ <Image src={main.src} alt={main.alt} ratio="square" fit={fit} rounded="lg" loading="eager" />
98
+ </div>
99
+ <Thumbs pictures={pictures} index={at} onPick={setIndex} vertical={rail} />
100
+ </div>
101
+ );
102
+ }