love-ui 1.2.12 → 1.2.14

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 (988) hide show
  1. package/dist/index.js +8 -7
  2. package/dist/mcp-server.js +1 -1
  3. package/package.json +3 -2
  4. package/packages/loveui-skills/SKILL.md +99 -0
  5. package/packages/loveui-skills/agents/openai.yaml +11 -0
  6. package/packages/loveui-skills/references/design-directions.md +60 -0
  7. package/packages/loveui-skills/references/mcp-catalog-workflow.md +68 -0
  8. package/packages/loveui-skills/references/page-blueprints.md +76 -0
  9. package/packages/loveui-skills/references/quality-gates.md +51 -0
  10. package/public/r/accordion-controlled.json +1 -1
  11. package/public/r/accordion-demo.json +1 -1
  12. package/public/r/accordion-multiple.json +1 -1
  13. package/public/r/accordion-single.json +1 -1
  14. package/public/r/alert-demo.json +1 -1
  15. package/public/r/alert-dialog-demo.json +1 -1
  16. package/public/r/alert-error.json +1 -1
  17. package/public/r/alert-info.json +1 -1
  18. package/public/r/alert-success.json +1 -1
  19. package/public/r/alert-warning.json +1 -1
  20. package/public/r/alert-with-icon-action.json +1 -1
  21. package/public/r/alert-with-icon.json +1 -1
  22. package/public/r/announcement-tagless.json +1 -1
  23. package/public/r/announcement-themes.json +1 -1
  24. package/public/r/announcement.json +1 -1
  25. package/public/r/autocomplete-async.json +1 -1
  26. package/public/r/autocomplete-autohighlight.json +1 -1
  27. package/public/r/autocomplete-demo.json +1 -1
  28. package/public/r/autocomplete-disabled.json +1 -1
  29. package/public/r/autocomplete-form.json +1 -1
  30. package/public/r/autocomplete-grouped.json +1 -1
  31. package/public/r/autocomplete-inline.json +1 -1
  32. package/public/r/autocomplete-lg.json +1 -1
  33. package/public/r/autocomplete-limit.json +1 -1
  34. package/public/r/autocomplete-sm.json +1 -1
  35. package/public/r/autocomplete-with-clear.json +1 -1
  36. package/public/r/autocomplete-with-label.json +1 -1
  37. package/public/r/autocomplete-with-trigger-clear.json +1 -1
  38. package/public/r/avatar-demo.json +1 -1
  39. package/public/r/avatar-fallback.json +1 -1
  40. package/public/r/avatar-group.json +1 -1
  41. package/public/r/avatar-radius.json +1 -1
  42. package/public/r/avatar-size.json +1 -1
  43. package/public/r/avatar-stack-hover.json +1 -1
  44. package/public/r/avatar-stack.json +1 -1
  45. package/public/r/badge-demo.json +1 -1
  46. package/public/r/badge-destructive.json +1 -1
  47. package/public/r/badge-error.json +1 -1
  48. package/public/r/badge-info.json +1 -1
  49. package/public/r/badge-lg.json +1 -1
  50. package/public/r/badge-outline.json +1 -1
  51. package/public/r/badge-secondary.json +1 -1
  52. package/public/r/badge-sm.json +1 -1
  53. package/public/r/badge-success.json +1 -1
  54. package/public/r/badge-warning.json +1 -1
  55. package/public/r/badge-with-icon.json +1 -1
  56. package/public/r/badge-with-link.json +1 -1
  57. package/public/r/banner-inset.json +1 -1
  58. package/public/r/banner-themes.json +1 -1
  59. package/public/r/banner.json +1 -1
  60. package/public/r/bookmarks.json +206 -0
  61. package/public/r/breadcrumb-custom-separator.json +1 -1
  62. package/public/r/breadcrumb-demo.json +1 -1
  63. package/public/r/button-demo.json +1 -1
  64. package/public/r/button-destructive-outline.json +1 -1
  65. package/public/r/button-destructive.json +1 -1
  66. package/public/r/button-disabled.json +1 -1
  67. package/public/r/button-ghost.json +1 -1
  68. package/public/r/button-lg.json +1 -1
  69. package/public/r/button-link.json +1 -1
  70. package/public/r/button-loading.json +1 -1
  71. package/public/r/button-outline.json +1 -1
  72. package/public/r/button-secondary.json +1 -1
  73. package/public/r/button-sm.json +1 -1
  74. package/public/r/button-with-icon.json +1 -1
  75. package/public/r/button-with-link.json +1 -1
  76. package/public/r/button-xl.json +1 -1
  77. package/public/r/button-xs.json +1 -1
  78. package/public/r/calendar-headless.json +1 -1
  79. package/public/r/calendar-rac.json +16 -0
  80. package/public/r/calendar.json +1 -1
  81. package/public/r/card-demo.json +1 -1
  82. package/public/r/chart.json +15 -0
  83. package/public/r/checkbox-card.json +1 -1
  84. package/public/r/checkbox-demo.json +1 -1
  85. package/public/r/checkbox-disabled.json +1 -1
  86. package/public/r/checkbox-form.json +1 -1
  87. package/public/r/checkbox-group-demo.json +1 -1
  88. package/public/r/checkbox-group-disabled.json +1 -1
  89. package/public/r/checkbox-group-form.json +1 -1
  90. package/public/r/checkbox-group-nested-parent.json +1 -1
  91. package/public/r/checkbox-group-parent.json +1 -1
  92. package/public/r/checkbox-tree.json +12 -0
  93. package/public/r/checkbox-with-description.json +1 -1
  94. package/public/r/choicebox-inline.json +1 -1
  95. package/public/r/choicebox.json +1 -1
  96. package/public/r/codebase.json +1 -1
  97. package/public/r/collaborative-canvas.json +1 -1
  98. package/public/r/collapsible-demo.json +1 -1
  99. package/public/r/combobox-autohighlight.json +1 -1
  100. package/public/r/combobox-controlled.json +1 -1
  101. package/public/r/combobox-create-new.json +1 -1
  102. package/public/r/combobox-demo.json +1 -1
  103. package/public/r/combobox-disabled.json +1 -1
  104. package/public/r/combobox-fixed-width.json +1 -1
  105. package/public/r/combobox-form.json +1 -1
  106. package/public/r/combobox-grouped.json +1 -1
  107. package/public/r/combobox-lg.json +1 -1
  108. package/public/r/combobox-multiple-form.json +1 -1
  109. package/public/r/combobox-multiple.json +1 -1
  110. package/public/r/combobox-sm.json +1 -1
  111. package/public/r/combobox-with-clear.json +1 -1
  112. package/public/r/combobox-with-inner-input.json +1 -1
  113. package/public/r/combobox-with-label.json +1 -1
  114. package/public/r/command.json +18 -0
  115. package/public/r/comp-01.json +22 -0
  116. package/public/r/comp-02.json +23 -0
  117. package/public/r/comp-03.json +23 -0
  118. package/public/r/comp-04.json +23 -0
  119. package/public/r/comp-05.json +22 -0
  120. package/public/r/comp-06.json +23 -0
  121. package/public/r/comp-07.json +22 -0
  122. package/public/r/comp-08.json +23 -0
  123. package/public/r/comp-09.json +22 -0
  124. package/public/r/comp-10.json +22 -0
  125. package/public/r/comp-100.json +23 -0
  126. package/public/r/comp-101.json +23 -0
  127. package/public/r/comp-102.json +23 -0
  128. package/public/r/comp-103.json +23 -0
  129. package/public/r/comp-104.json +23 -0
  130. package/public/r/comp-105.json +23 -0
  131. package/public/r/comp-106.json +22 -0
  132. package/public/r/comp-107.json +22 -0
  133. package/public/r/comp-108.json +23 -0
  134. package/public/r/comp-109.json +22 -0
  135. package/public/r/comp-11.json +22 -0
  136. package/public/r/comp-110.json +22 -0
  137. package/public/r/comp-111.json +21 -0
  138. package/public/r/comp-112.json +21 -0
  139. package/public/r/comp-113.json +22 -0
  140. package/public/r/comp-114.json +23 -0
  141. package/public/r/comp-115.json +22 -0
  142. package/public/r/comp-116.json +22 -0
  143. package/public/r/comp-117.json +23 -0
  144. package/public/r/comp-118.json +23 -0
  145. package/public/r/comp-119.json +27 -0
  146. package/public/r/comp-12.json +22 -0
  147. package/public/r/comp-120.json +27 -0
  148. package/public/r/comp-121.json +27 -0
  149. package/public/r/comp-122.json +27 -0
  150. package/public/r/comp-123.json +22 -0
  151. package/public/r/comp-124.json +22 -0
  152. package/public/r/comp-125.json +31 -0
  153. package/public/r/comp-126.json +31 -0
  154. package/public/r/comp-127.json +21 -0
  155. package/public/r/comp-128.json +21 -0
  156. package/public/r/comp-129.json +24 -0
  157. package/public/r/comp-13.json +22 -0
  158. package/public/r/comp-130.json +23 -0
  159. package/public/r/comp-131.json +23 -0
  160. package/public/r/comp-132.json +23 -0
  161. package/public/r/comp-133.json +23 -0
  162. package/public/r/comp-134.json +23 -0
  163. package/public/r/comp-135.json +24 -0
  164. package/public/r/comp-136.json +23 -0
  165. package/public/r/comp-137.json +23 -0
  166. package/public/r/comp-138.json +25 -0
  167. package/public/r/comp-139.json +23 -0
  168. package/public/r/comp-14.json +22 -0
  169. package/public/r/comp-140.json +23 -0
  170. package/public/r/comp-141.json +23 -0
  171. package/public/r/comp-142.json +25 -0
  172. package/public/r/comp-143.json +23 -0
  173. package/public/r/comp-144.json +24 -0
  174. package/public/r/comp-145.json +24 -0
  175. package/public/r/comp-146.json +24 -0
  176. package/public/r/comp-147.json +24 -0
  177. package/public/r/comp-148.json +25 -0
  178. package/public/r/comp-149.json +24 -0
  179. package/public/r/comp-15.json +22 -0
  180. package/public/r/comp-150.json +21 -0
  181. package/public/r/comp-151.json +23 -0
  182. package/public/r/comp-152.json +23 -0
  183. package/public/r/comp-153.json +23 -0
  184. package/public/r/comp-154.json +23 -0
  185. package/public/r/comp-155.json +23 -0
  186. package/public/r/comp-156.json +25 -0
  187. package/public/r/comp-157.json +27 -0
  188. package/public/r/comp-158.json +24 -0
  189. package/public/r/comp-159.json +24 -0
  190. package/public/r/comp-16.json +22 -0
  191. package/public/r/comp-160.json +24 -0
  192. package/public/r/comp-161.json +24 -0
  193. package/public/r/comp-162.json +24 -0
  194. package/public/r/comp-163.json +28 -0
  195. package/public/r/comp-164.json +24 -0
  196. package/public/r/comp-165.json +24 -0
  197. package/public/r/comp-166.json +25 -0
  198. package/public/r/comp-167.json +24 -0
  199. package/public/r/comp-168.json +24 -0
  200. package/public/r/comp-169.json +23 -0
  201. package/public/r/comp-17.json +25 -0
  202. package/public/r/comp-170.json +25 -0
  203. package/public/r/comp-171.json +28 -0
  204. package/public/r/comp-172.json +23 -0
  205. package/public/r/comp-173.json +23 -0
  206. package/public/r/comp-174.json +23 -0
  207. package/public/r/comp-175.json +23 -0
  208. package/public/r/comp-176.json +23 -0
  209. package/public/r/comp-177.json +23 -0
  210. package/public/r/comp-178.json +23 -0
  211. package/public/r/comp-179.json +24 -0
  212. package/public/r/comp-18.json +25 -0
  213. package/public/r/comp-180.json +23 -0
  214. package/public/r/comp-181.json +25 -0
  215. package/public/r/comp-182.json +24 -0
  216. package/public/r/comp-183.json +25 -0
  217. package/public/r/comp-184.json +25 -0
  218. package/public/r/comp-185.json +24 -0
  219. package/public/r/comp-186.json +25 -0
  220. package/public/r/comp-187.json +25 -0
  221. package/public/r/comp-188.json +25 -0
  222. package/public/r/comp-189.json +23 -0
  223. package/public/r/comp-19.json +23 -0
  224. package/public/r/comp-190.json +23 -0
  225. package/public/r/comp-191.json +24 -0
  226. package/public/r/comp-192.json +23 -0
  227. package/public/r/comp-193.json +23 -0
  228. package/public/r/comp-194.json +24 -0
  229. package/public/r/comp-195.json +23 -0
  230. package/public/r/comp-196.json +24 -0
  231. package/public/r/comp-197.json +23 -0
  232. package/public/r/comp-198.json +23 -0
  233. package/public/r/comp-199.json +23 -0
  234. package/public/r/comp-20.json +23 -0
  235. package/public/r/comp-200.json +25 -0
  236. package/public/r/comp-201.json +22 -0
  237. package/public/r/comp-202.json +22 -0
  238. package/public/r/comp-203.json +23 -0
  239. package/public/r/comp-204.json +23 -0
  240. package/public/r/comp-205.json +23 -0
  241. package/public/r/comp-206.json +24 -0
  242. package/public/r/comp-207.json +23 -0
  243. package/public/r/comp-208.json +23 -0
  244. package/public/r/comp-209.json +23 -0
  245. package/public/r/comp-21.json +23 -0
  246. package/public/r/comp-210.json +24 -0
  247. package/public/r/comp-211.json +24 -0
  248. package/public/r/comp-212.json +23 -0
  249. package/public/r/comp-213.json +23 -0
  250. package/public/r/comp-214.json +23 -0
  251. package/public/r/comp-215.json +24 -0
  252. package/public/r/comp-216.json +22 -0
  253. package/public/r/comp-217.json +22 -0
  254. package/public/r/comp-218.json +25 -0
  255. package/public/r/comp-219.json +23 -0
  256. package/public/r/comp-22.json +23 -0
  257. package/public/r/comp-220.json +24 -0
  258. package/public/r/comp-221.json +23 -0
  259. package/public/r/comp-222.json +26 -0
  260. package/public/r/comp-223.json +26 -0
  261. package/public/r/comp-224.json +23 -0
  262. package/public/r/comp-225.json +24 -0
  263. package/public/r/comp-226.json +26 -0
  264. package/public/r/comp-227.json +26 -0
  265. package/public/r/comp-228.json +26 -0
  266. package/public/r/comp-229.json +30 -0
  267. package/public/r/comp-23.json +24 -0
  268. package/public/r/comp-230.json +30 -0
  269. package/public/r/comp-231.json +32 -0
  270. package/public/r/comp-232.json +31 -0
  271. package/public/r/comp-233.json +30 -0
  272. package/public/r/comp-234.json +28 -0
  273. package/public/r/comp-235.json +28 -0
  274. package/public/r/comp-236.json +24 -0
  275. package/public/r/comp-237.json +26 -0
  276. package/public/r/comp-238.json +26 -0
  277. package/public/r/comp-239.json +26 -0
  278. package/public/r/comp-24.json +23 -0
  279. package/public/r/comp-240.json +23 -0
  280. package/public/r/comp-241.json +24 -0
  281. package/public/r/comp-242.json +23 -0
  282. package/public/r/comp-243.json +23 -0
  283. package/public/r/comp-244.json +23 -0
  284. package/public/r/comp-245.json +23 -0
  285. package/public/r/comp-246.json +23 -0
  286. package/public/r/comp-247.json +23 -0
  287. package/public/r/comp-248.json +23 -0
  288. package/public/r/comp-249.json +24 -0
  289. package/public/r/comp-25.json +24 -0
  290. package/public/r/comp-250.json +25 -0
  291. package/public/r/comp-251.json +25 -0
  292. package/public/r/comp-252.json +25 -0
  293. package/public/r/comp-253.json +25 -0
  294. package/public/r/comp-254.json +35 -0
  295. package/public/r/comp-255.json +30 -0
  296. package/public/r/comp-256.json +25 -0
  297. package/public/r/comp-257.json +25 -0
  298. package/public/r/comp-258.json +32 -0
  299. package/public/r/comp-259.json +26 -0
  300. package/public/r/comp-26.json +24 -0
  301. package/public/r/comp-260.json +26 -0
  302. package/public/r/comp-261.json +24 -0
  303. package/public/r/comp-262.json +31 -0
  304. package/public/r/comp-263.json +25 -0
  305. package/public/r/comp-264.json +33 -0
  306. package/public/r/comp-265.json +32 -0
  307. package/public/r/comp-266.json +24 -0
  308. package/public/r/comp-267.json +19 -0
  309. package/public/r/comp-268.json +19 -0
  310. package/public/r/comp-269.json +19 -0
  311. package/public/r/comp-27.json +24 -0
  312. package/public/r/comp-270.json +19 -0
  313. package/public/r/comp-271.json +19 -0
  314. package/public/r/comp-272.json +19 -0
  315. package/public/r/comp-273.json +19 -0
  316. package/public/r/comp-274.json +19 -0
  317. package/public/r/comp-275.json +19 -0
  318. package/public/r/comp-276.json +19 -0
  319. package/public/r/comp-277.json +21 -0
  320. package/public/r/comp-278.json +21 -0
  321. package/public/r/comp-279.json +23 -0
  322. package/public/r/comp-28.json +24 -0
  323. package/public/r/comp-280.json +23 -0
  324. package/public/r/comp-281.json +23 -0
  325. package/public/r/comp-282.json +23 -0
  326. package/public/r/comp-283.json +23 -0
  327. package/public/r/comp-284.json +23 -0
  328. package/public/r/comp-285.json +23 -0
  329. package/public/r/comp-286.json +23 -0
  330. package/public/r/comp-287.json +24 -0
  331. package/public/r/comp-288.json +23 -0
  332. package/public/r/comp-289.json +24 -0
  333. package/public/r/comp-29.json +24 -0
  334. package/public/r/comp-290.json +24 -0
  335. package/public/r/comp-291.json +24 -0
  336. package/public/r/comp-292.json +24 -0
  337. package/public/r/comp-293.json +26 -0
  338. package/public/r/comp-294.json +24 -0
  339. package/public/r/comp-295.json +23 -0
  340. package/public/r/comp-296.json +23 -0
  341. package/public/r/comp-297.json +30 -0
  342. package/public/r/comp-298.json +26 -0
  343. package/public/r/comp-299.json +28 -0
  344. package/public/r/comp-30.json +23 -0
  345. package/public/r/comp-300.json +29 -0
  346. package/public/r/comp-301.json +24 -0
  347. package/public/r/comp-302.json +18 -0
  348. package/public/r/comp-303.json +18 -0
  349. package/public/r/comp-304.json +18 -0
  350. package/public/r/comp-305.json +21 -0
  351. package/public/r/comp-306.json +21 -0
  352. package/public/r/comp-307.json +21 -0
  353. package/public/r/comp-308.json +21 -0
  354. package/public/r/comp-309.json +21 -0
  355. package/public/r/comp-31.json +21 -0
  356. package/public/r/comp-310.json +23 -0
  357. package/public/r/comp-311.json +20 -0
  358. package/public/r/comp-312.json +21 -0
  359. package/public/r/comp-313.json +26 -0
  360. package/public/r/comp-314.json +26 -0
  361. package/public/r/comp-315.json +24 -0
  362. package/public/r/comp-316.json +25 -0
  363. package/public/r/comp-317.json +24 -0
  364. package/public/r/comp-318.json +24 -0
  365. package/public/r/comp-319.json +24 -0
  366. package/public/r/comp-32.json +21 -0
  367. package/public/r/comp-320.json +27 -0
  368. package/public/r/comp-321.json +28 -0
  369. package/public/r/comp-322.json +27 -0
  370. package/public/r/comp-323.json +30 -0
  371. package/public/r/comp-324.json +28 -0
  372. package/public/r/comp-325.json +29 -0
  373. package/public/r/comp-326.json +30 -0
  374. package/public/r/comp-327.json +30 -0
  375. package/public/r/comp-328.json +37 -0
  376. package/public/r/comp-329.json +38 -0
  377. package/public/r/comp-33.json +18 -0
  378. package/public/r/comp-330.json +27 -0
  379. package/public/r/comp-331.json +42 -0
  380. package/public/r/comp-332.json +25 -0
  381. package/public/r/comp-333.json +29 -0
  382. package/public/r/comp-334.json +22 -0
  383. package/public/r/comp-335.json +22 -0
  384. package/public/r/comp-336.json +22 -0
  385. package/public/r/comp-337.json +22 -0
  386. package/public/r/comp-338.json +22 -0
  387. package/public/r/comp-339.json +22 -0
  388. package/public/r/comp-34.json +27 -0
  389. package/public/r/comp-340.json +22 -0
  390. package/public/r/comp-341.json +22 -0
  391. package/public/r/comp-342.json +22 -0
  392. package/public/r/comp-343.json +22 -0
  393. package/public/r/comp-344.json +22 -0
  394. package/public/r/comp-345.json +22 -0
  395. package/public/r/comp-346.json +22 -0
  396. package/public/r/comp-347.json +22 -0
  397. package/public/r/comp-348.json +22 -0
  398. package/public/r/comp-349.json +22 -0
  399. package/public/r/comp-35.json +27 -0
  400. package/public/r/comp-350.json +22 -0
  401. package/public/r/comp-351.json +22 -0
  402. package/public/r/comp-352.json +24 -0
  403. package/public/r/comp-353.json +24 -0
  404. package/public/r/comp-354.json +23 -0
  405. package/public/r/comp-355.json +23 -0
  406. package/public/r/comp-356.json +23 -0
  407. package/public/r/comp-357.json +23 -0
  408. package/public/r/comp-358.json +23 -0
  409. package/public/r/comp-359.json +24 -0
  410. package/public/r/comp-36.json +26 -0
  411. package/public/r/comp-360.json +25 -0
  412. package/public/r/comp-361.json +23 -0
  413. package/public/r/comp-362.json +24 -0
  414. package/public/r/comp-363.json +27 -0
  415. package/public/r/comp-364.json +26 -0
  416. package/public/r/comp-365.json +24 -0
  417. package/public/r/comp-366.json +23 -0
  418. package/public/r/comp-367.json +23 -0
  419. package/public/r/comp-368.json +23 -0
  420. package/public/r/comp-369.json +23 -0
  421. package/public/r/comp-37.json +23 -0
  422. package/public/r/comp-370.json +23 -0
  423. package/public/r/comp-371.json +24 -0
  424. package/public/r/comp-372.json +24 -0
  425. package/public/r/comp-373.json +25 -0
  426. package/public/r/comp-374.json +26 -0
  427. package/public/r/comp-375.json +25 -0
  428. package/public/r/comp-376.json +26 -0
  429. package/public/r/comp-377.json +27 -0
  430. package/public/r/comp-378.json +24 -0
  431. package/public/r/comp-379.json +24 -0
  432. package/public/r/comp-38.json +23 -0
  433. package/public/r/comp-380.json +24 -0
  434. package/public/r/comp-381.json +26 -0
  435. package/public/r/comp-382.json +25 -0
  436. package/public/r/comp-383.json +26 -0
  437. package/public/r/comp-384.json +24 -0
  438. package/public/r/comp-385.json +24 -0
  439. package/public/r/comp-386.json +24 -0
  440. package/public/r/comp-387.json +28 -0
  441. package/public/r/comp-388.json +26 -0
  442. package/public/r/comp-389.json +24 -0
  443. package/public/r/comp-39.json +23 -0
  444. package/public/r/comp-390.json +23 -0
  445. package/public/r/comp-391.json +23 -0
  446. package/public/r/comp-392.json +23 -0
  447. package/public/r/comp-393.json +23 -0
  448. package/public/r/comp-394.json +24 -0
  449. package/public/r/comp-395.json +24 -0
  450. package/public/r/comp-396.json +24 -0
  451. package/public/r/comp-397.json +25 -0
  452. package/public/r/comp-398.json +26 -0
  453. package/public/r/comp-399.json +26 -0
  454. package/public/r/comp-40.json +23 -0
  455. package/public/r/comp-400.json +20 -0
  456. package/public/r/comp-401.json +19 -0
  457. package/public/r/comp-402.json +19 -0
  458. package/public/r/comp-403.json +19 -0
  459. package/public/r/comp-404.json +19 -0
  460. package/public/r/comp-405.json +19 -0
  461. package/public/r/comp-406.json +19 -0
  462. package/public/r/comp-407.json +19 -0
  463. package/public/r/comp-408.json +19 -0
  464. package/public/r/comp-409.json +22 -0
  465. package/public/r/comp-41.json +29 -0
  466. package/public/r/comp-410.json +22 -0
  467. package/public/r/comp-411.json +22 -0
  468. package/public/r/comp-412.json +19 -0
  469. package/public/r/comp-413.json +22 -0
  470. package/public/r/comp-414.json +22 -0
  471. package/public/r/comp-415.json +22 -0
  472. package/public/r/comp-416.json +23 -0
  473. package/public/r/comp-417.json +22 -0
  474. package/public/r/comp-418.json +23 -0
  475. package/public/r/comp-419.json +23 -0
  476. package/public/r/comp-42.json +30 -0
  477. package/public/r/comp-420.json +23 -0
  478. package/public/r/comp-421.json +23 -0
  479. package/public/r/comp-422.json +23 -0
  480. package/public/r/comp-423.json +24 -0
  481. package/public/r/comp-424.json +22 -0
  482. package/public/r/comp-425.json +23 -0
  483. package/public/r/comp-426.json +23 -0
  484. package/public/r/comp-427.json +23 -0
  485. package/public/r/comp-428.json +23 -0
  486. package/public/r/comp-429.json +23 -0
  487. package/public/r/comp-43.json +32 -0
  488. package/public/r/comp-430.json +23 -0
  489. package/public/r/comp-431.json +23 -0
  490. package/public/r/comp-432.json +23 -0
  491. package/public/r/comp-433.json +24 -0
  492. package/public/r/comp-434.json +24 -0
  493. package/public/r/comp-435.json +24 -0
  494. package/public/r/comp-436.json +24 -0
  495. package/public/r/comp-437.json +25 -0
  496. package/public/r/comp-438.json +23 -0
  497. package/public/r/comp-439.json +24 -0
  498. package/public/r/comp-44.json +25 -0
  499. package/public/r/comp-440.json +25 -0
  500. package/public/r/comp-441.json +25 -0
  501. package/public/r/comp-442.json +24 -0
  502. package/public/r/comp-443.json +24 -0
  503. package/public/r/comp-444.json +24 -0
  504. package/public/r/comp-445.json +24 -0
  505. package/public/r/comp-446.json +24 -0
  506. package/public/r/comp-447.json +25 -0
  507. package/public/r/comp-448.json +23 -0
  508. package/public/r/comp-449.json +23 -0
  509. package/public/r/comp-45.json +25 -0
  510. package/public/r/comp-450.json +23 -0
  511. package/public/r/comp-451.json +23 -0
  512. package/public/r/comp-452.json +23 -0
  513. package/public/r/comp-453.json +25 -0
  514. package/public/r/comp-454.json +22 -0
  515. package/public/r/comp-455.json +22 -0
  516. package/public/r/comp-456.json +22 -0
  517. package/public/r/comp-457.json +21 -0
  518. package/public/r/comp-458.json +22 -0
  519. package/public/r/comp-459.json +26 -0
  520. package/public/r/comp-46.json +26 -0
  521. package/public/r/comp-460.json +26 -0
  522. package/public/r/comp-461.json +27 -0
  523. package/public/r/comp-462.json +27 -0
  524. package/public/r/comp-463.json +30 -0
  525. package/public/r/comp-464.json +29 -0
  526. package/public/r/comp-465.json +29 -0
  527. package/public/r/comp-466.json +21 -0
  528. package/public/r/comp-467.json +23 -0
  529. package/public/r/comp-468.json +21 -0
  530. package/public/r/comp-469.json +21 -0
  531. package/public/r/comp-47.json +32 -0
  532. package/public/r/comp-470.json +21 -0
  533. package/public/r/comp-471.json +21 -0
  534. package/public/r/comp-472.json +23 -0
  535. package/public/r/comp-473.json +24 -0
  536. package/public/r/comp-474.json +22 -0
  537. package/public/r/comp-475.json +22 -0
  538. package/public/r/comp-476.json +21 -0
  539. package/public/r/comp-477.json +31 -0
  540. package/public/r/comp-478.json +36 -0
  541. package/public/r/comp-479.json +28 -0
  542. package/public/r/comp-48.json +32 -0
  543. package/public/r/comp-480.json +30 -0
  544. package/public/r/comp-481.json +34 -0
  545. package/public/r/comp-482.json +33 -0
  546. package/public/r/comp-483.json +37 -0
  547. package/public/r/comp-484.json +41 -0
  548. package/public/r/comp-485.json +43 -0
  549. package/public/r/comp-486.json +23 -0
  550. package/public/r/comp-487.json +26 -0
  551. package/public/r/comp-488.json +27 -0
  552. package/public/r/comp-489.json +28 -0
  553. package/public/r/comp-49.json +32 -0
  554. package/public/r/comp-490.json +23 -0
  555. package/public/r/comp-491.json +24 -0
  556. package/public/r/comp-492.json +25 -0
  557. package/public/r/comp-493.json +23 -0
  558. package/public/r/comp-494.json +23 -0
  559. package/public/r/comp-495.json +24 -0
  560. package/public/r/comp-496.json +23 -0
  561. package/public/r/comp-497.json +24 -0
  562. package/public/r/comp-498.json +24 -0
  563. package/public/r/comp-499.json +24 -0
  564. package/public/r/comp-50.json +32 -0
  565. package/public/r/comp-500.json +25 -0
  566. package/public/r/comp-501.json +25 -0
  567. package/public/r/comp-502.json +26 -0
  568. package/public/r/comp-503.json +27 -0
  569. package/public/r/comp-504.json +28 -0
  570. package/public/r/comp-505.json +28 -0
  571. package/public/r/comp-506.json +26 -0
  572. package/public/r/comp-507.json +27 -0
  573. package/public/r/comp-508.json +25 -0
  574. package/public/r/comp-509.json +25 -0
  575. package/public/r/comp-51.json +23 -0
  576. package/public/r/comp-510.json +24 -0
  577. package/public/r/comp-511.json +27 -0
  578. package/public/r/comp-512.json +27 -0
  579. package/public/r/comp-513.json +21 -0
  580. package/public/r/comp-514.json +21 -0
  581. package/public/r/comp-515.json +21 -0
  582. package/public/r/comp-516.json +22 -0
  583. package/public/r/comp-517.json +22 -0
  584. package/public/r/comp-518.json +21 -0
  585. package/public/r/comp-519.json +21 -0
  586. package/public/r/comp-52.json +23 -0
  587. package/public/r/comp-520.json +22 -0
  588. package/public/r/comp-521.json +22 -0
  589. package/public/r/comp-522.json +21 -0
  590. package/public/r/comp-523.json +21 -0
  591. package/public/r/comp-524.json +21 -0
  592. package/public/r/comp-525.json +21 -0
  593. package/public/r/comp-526.json +22 -0
  594. package/public/r/comp-527.json +23 -0
  595. package/public/r/comp-528.json +22 -0
  596. package/public/r/comp-529.json +22 -0
  597. package/public/r/comp-53.json +24 -0
  598. package/public/r/comp-530.json +22 -0
  599. package/public/r/comp-531.json +22 -0
  600. package/public/r/comp-532.json +22 -0
  601. package/public/r/comp-533.json +22 -0
  602. package/public/r/comp-534.json +22 -0
  603. package/public/r/comp-535.json +22 -0
  604. package/public/r/comp-536.json +22 -0
  605. package/public/r/comp-537.json +22 -0
  606. package/public/r/comp-538.json +22 -0
  607. package/public/r/comp-539.json +22 -0
  608. package/public/r/comp-54.json +26 -0
  609. package/public/r/comp-540.json +21 -0
  610. package/public/r/comp-541.json +21 -0
  611. package/public/r/comp-542.json +124 -0
  612. package/public/r/comp-543.json +29 -0
  613. package/public/r/comp-544.json +26 -0
  614. package/public/r/comp-545.json +29 -0
  615. package/public/r/comp-546.json +29 -0
  616. package/public/r/comp-547.json +29 -0
  617. package/public/r/comp-548.json +29 -0
  618. package/public/r/comp-549.json +29 -0
  619. package/public/r/comp-55.json +27 -0
  620. package/public/r/comp-550.json +29 -0
  621. package/public/r/comp-551.json +30 -0
  622. package/public/r/comp-552.json +29 -0
  623. package/public/r/comp-553.json +29 -0
  624. package/public/r/comp-554.json +36 -0
  625. package/public/r/comp-555.json +23 -0
  626. package/public/r/comp-556.json +23 -0
  627. package/public/r/comp-557.json +23 -0
  628. package/public/r/comp-558.json +23 -0
  629. package/public/r/comp-559.json +23 -0
  630. package/public/r/comp-56.json +26 -0
  631. package/public/r/comp-560.json +23 -0
  632. package/public/r/comp-561.json +25 -0
  633. package/public/r/comp-562.json +23 -0
  634. package/public/r/comp-563.json +23 -0
  635. package/public/r/comp-564.json +24 -0
  636. package/public/r/comp-565.json +20 -0
  637. package/public/r/comp-566.json +20 -0
  638. package/public/r/comp-567.json +20 -0
  639. package/public/r/comp-568.json +20 -0
  640. package/public/r/comp-569.json +20 -0
  641. package/public/r/comp-57.json +26 -0
  642. package/public/r/comp-570.json +20 -0
  643. package/public/r/comp-571.json +23 -0
  644. package/public/r/comp-572.json +23 -0
  645. package/public/r/comp-573.json +20 -0
  646. package/public/r/comp-574.json +22 -0
  647. package/public/r/comp-575.json +20 -0
  648. package/public/r/comp-576.json +21 -0
  649. package/public/r/comp-577.json +23 -0
  650. package/public/r/comp-578.json +28 -0
  651. package/public/r/comp-579.json +28 -0
  652. package/public/r/comp-58.json +25 -0
  653. package/public/r/comp-580.json +29 -0
  654. package/public/r/comp-581.json +45 -0
  655. package/public/r/comp-582.json +42 -0
  656. package/public/r/comp-583.json +41 -0
  657. package/public/r/comp-584.json +41 -0
  658. package/public/r/comp-585.json +41 -0
  659. package/public/r/comp-586.json +34 -0
  660. package/public/r/comp-587.json +29 -0
  661. package/public/r/comp-588.json +33 -0
  662. package/public/r/comp-589.json +37 -0
  663. package/public/r/comp-59.json +22 -0
  664. package/public/r/comp-590.json +40 -0
  665. package/public/r/comp-591.json +29 -0
  666. package/public/r/comp-592.json +41 -0
  667. package/public/r/comp-593.json +36 -0
  668. package/public/r/comp-594.json +33 -0
  669. package/public/r/comp-595.json +22 -0
  670. package/public/r/comp-596.json +26 -0
  671. package/public/r/comp-597.json +22 -0
  672. package/public/r/comp-598.json +22 -0
  673. package/public/r/comp-599.json +22 -0
  674. package/public/r/comp-60.json +23 -0
  675. package/public/r/comp-600.json +17 -0
  676. package/public/r/comp-601.json +20 -0
  677. package/public/r/comp-602.json +17 -0
  678. package/public/r/comp-603.json +20 -0
  679. package/public/r/comp-604.json +17 -0
  680. package/public/r/comp-605.json +17 -0
  681. package/public/r/comp-606.json +20 -0
  682. package/public/r/comp-607.json +20 -0
  683. package/public/r/comp-608.json +20 -0
  684. package/public/r/comp-609.json +20 -0
  685. package/public/r/comp-61.json +23 -0
  686. package/public/r/comp-610.json +23 -0
  687. package/public/r/comp-611.json +23 -0
  688. package/public/r/comp-612.json +17 -0
  689. package/public/r/comp-613.json +20 -0
  690. package/public/r/comp-614.json +17 -0
  691. package/public/r/comp-615.json +26 -0
  692. package/public/r/comp-616.json +26 -0
  693. package/public/r/comp-617.json +26 -0
  694. package/public/r/comp-62.json +23 -0
  695. package/public/r/comp-63.json +22 -0
  696. package/public/r/comp-64.json +23 -0
  697. package/public/r/comp-65.json +22 -0
  698. package/public/r/comp-66.json +22 -0
  699. package/public/r/comp-67.json +23 -0
  700. package/public/r/comp-68.json +24 -0
  701. package/public/r/comp-69.json +24 -0
  702. package/public/r/comp-70.json +24 -0
  703. package/public/r/comp-71.json +22 -0
  704. package/public/r/comp-72.json +21 -0
  705. package/public/r/comp-73.json +18 -0
  706. package/public/r/comp-74.json +27 -0
  707. package/public/r/comp-75.json +22 -0
  708. package/public/r/comp-76.json +23 -0
  709. package/public/r/comp-77.json +22 -0
  710. package/public/r/comp-78.json +21 -0
  711. package/public/r/comp-79.json +22 -0
  712. package/public/r/comp-80.json +21 -0
  713. package/public/r/comp-81.json +21 -0
  714. package/public/r/comp-82.json +22 -0
  715. package/public/r/comp-83.json +21 -0
  716. package/public/r/comp-84.json +21 -0
  717. package/public/r/comp-85.json +22 -0
  718. package/public/r/comp-86.json +22 -0
  719. package/public/r/comp-87.json +21 -0
  720. package/public/r/comp-88.json +22 -0
  721. package/public/r/comp-89.json +21 -0
  722. package/public/r/comp-90.json +22 -0
  723. package/public/r/comp-91.json +22 -0
  724. package/public/r/comp-92.json +22 -0
  725. package/public/r/comp-93.json +22 -0
  726. package/public/r/comp-94.json +24 -0
  727. package/public/r/comp-95.json +26 -0
  728. package/public/r/comp-96.json +21 -0
  729. package/public/r/comp-97.json +21 -0
  730. package/public/r/comp-98.json +22 -0
  731. package/public/r/comp-99.json +23 -0
  732. package/public/r/comparison-event-handlers.json +1 -1
  733. package/public/r/comparison-hover.json +1 -1
  734. package/public/r/comparison.json +1 -1
  735. package/public/r/context-menu.json +16 -0
  736. package/public/r/contribution-graph-custom-footer.json +1 -1
  737. package/public/r/credit-card-apple.json +1 -1
  738. package/public/r/credit-card-back.json +1 -1
  739. package/public/r/credit-card.json +1 -1
  740. package/public/r/cropper.json +15 -0
  741. package/public/r/cursor-color.json +1 -1
  742. package/public/r/cursor-message.json +1 -1
  743. package/public/r/cursor-name-message.json +1 -1
  744. package/public/r/cursor-name.json +1 -1
  745. package/public/r/cursor.json +1 -1
  746. package/public/r/dashboard-1.json +263 -0
  747. package/public/r/dashboard-2.json +264 -0
  748. package/public/r/dashboard-3.json +225 -0
  749. package/public/r/datefield-rac.json +15 -0
  750. package/public/r/deck-controlled.json +1 -1
  751. package/public/r/deck-product-cards.json +1 -1
  752. package/public/r/deck.json +1 -1
  753. package/public/r/dialog-close-confirmation.json +1 -1
  754. package/public/r/dialog-demo.json +1 -1
  755. package/public/r/dialog-from-menu.json +1 -1
  756. package/public/r/dialog-nested.json +1 -1
  757. package/public/r/dialog-stack-controlled.json +1 -1
  758. package/public/r/dialog-stack-navigation.json +1 -1
  759. package/public/r/dialog-stack-six.json +1 -1
  760. package/public/r/dialog-stack.json +1 -1
  761. package/public/r/dropdown-menu.json +15 -0
  762. package/public/r/dropzone-custom-empty-state.json +1 -1
  763. package/public/r/dropzone-image-preview.json +1 -1
  764. package/public/r/editor.json +1 -1
  765. package/public/r/emails.json +243 -0
  766. package/public/r/empty-demo.json +1 -1
  767. package/public/r/example-app-calendar.json +323 -0
  768. package/public/r/field-2.json +19 -0
  769. package/public/r/field-autocomplete.json +1 -1
  770. package/public/r/field-checkbox-group.json +1 -1
  771. package/public/r/field-checkbox.json +1 -1
  772. package/public/r/field-combobox-multiple.json +1 -1
  773. package/public/r/field-combobox.json +1 -1
  774. package/public/r/field-complete-form.json +1 -1
  775. package/public/r/field-demo.json +1 -1
  776. package/public/r/field-disabled.json +1 -1
  777. package/public/r/field-error.json +1 -1
  778. package/public/r/field-number-field.json +1 -1
  779. package/public/r/field-radio.json +1 -1
  780. package/public/r/field-required.json +1 -1
  781. package/public/r/field-select.json +1 -1
  782. package/public/r/field-slider.json +1 -1
  783. package/public/r/field-switch.json +1 -1
  784. package/public/r/field-textarea.json +1 -1
  785. package/public/r/field-validity.json +1 -1
  786. package/public/r/fieldset-demo.json +1 -1
  787. package/public/r/files.json +195 -0
  788. package/public/r/form-demo.json +1 -1
  789. package/public/r/form-zod.json +1 -1
  790. package/public/r/form.json +1 -1
  791. package/public/r/frame-demo.json +1 -1
  792. package/public/r/gantt-lanes.json +1 -1
  793. package/public/r/gantt-no-sidebar.json +1 -1
  794. package/public/r/gantt-read-only.json +1 -1
  795. package/public/r/gantt.json +1 -1
  796. package/public/r/glimpse-custom.json +1 -1
  797. package/public/r/glimpse.json +1 -1
  798. package/public/r/gooey-toast-demo.json +1 -1
  799. package/public/r/gooey-toast-promise.json +1 -1
  800. package/public/r/gooey-toast-states.json +1 -1
  801. package/public/r/gooey-toast-with-button.json +1 -1
  802. package/public/r/group-demo.json +1 -1
  803. package/public/r/group-with-input.json +1 -1
  804. package/public/r/hero.json +1 -1
  805. package/public/r/hover-card.json +15 -0
  806. package/public/r/image-crop-circular.json +1 -1
  807. package/public/r/image-crop-custom.json +1 -1
  808. package/public/r/image-crop.json +1 -1
  809. package/public/r/image-zoom-background.json +1 -1
  810. package/public/r/image-zoom-margin.json +1 -1
  811. package/public/r/image-zoom.json +1 -1
  812. package/public/r/input-demo.json +1 -1
  813. package/public/r/input-disabled.json +1 -1
  814. package/public/r/input-file.json +1 -1
  815. package/public/r/input-group.json +20 -0
  816. package/public/r/input-lg.json +1 -1
  817. package/public/r/input-sm.json +1 -1
  818. package/public/r/input-with-button.json +1 -1
  819. package/public/r/input-with-label.json +1 -1
  820. package/public/r/kanban-simple.json +1 -1
  821. package/public/r/kanban.json +1 -1
  822. package/public/r/kbd.json +12 -0
  823. package/public/r/leads.json +271 -0
  824. package/public/r/list-simple.json +1 -1
  825. package/public/r/list.json +1 -1
  826. package/public/r/maps.json +188 -0
  827. package/public/r/menu-checkbox.json +1 -1
  828. package/public/r/menu-close-on-click.json +1 -1
  829. package/public/r/menu-demo.json +1 -1
  830. package/public/r/menu-group-labels.json +1 -1
  831. package/public/r/menu-hover.json +1 -1
  832. package/public/r/menu-link.json +1 -1
  833. package/public/r/menu-nested.json +1 -1
  834. package/public/r/menu-radio-group.json +1 -1
  835. package/public/r/meter-demo.json +1 -1
  836. package/public/r/meter-with-formatted-value.json +1 -1
  837. package/public/r/meter-with-range.json +1 -1
  838. package/public/r/mini-calendar-controlled.json +1 -1
  839. package/public/r/multiselect.json +15 -0
  840. package/public/r/navigation-menu.json +12 -0
  841. package/public/r/number-field-form.json +1 -1
  842. package/public/r/number-field-with-label.json +1 -1
  843. package/public/r/number-field-with-scrub.json +1 -1
  844. package/public/r/number-field-with-step.json +1 -1
  845. package/public/r/pill-avatar-group.json +1 -1
  846. package/public/r/pill-delta.json +1 -1
  847. package/public/r/pill-icon.json +1 -1
  848. package/public/r/pill-indicator.json +1 -1
  849. package/public/r/pill-status.json +1 -1
  850. package/public/r/pill.json +1 -1
  851. package/public/r/popover-demo.json +1 -1
  852. package/public/r/popover-with-close.json +1 -1
  853. package/public/r/preview-card-demo.json +1 -1
  854. package/public/r/pricing.json +1 -1
  855. package/public/r/progress-with-formatted-value.json +1 -1
  856. package/public/r/progress-with-label-value.json +1 -1
  857. package/public/r/radio-group-card.json +1 -1
  858. package/public/r/radio-group-demo.json +1 -1
  859. package/public/r/radio-group-disabled.json +1 -1
  860. package/public/r/radio-group-form.json +1 -1
  861. package/public/r/radio-group-with-description.json +1 -1
  862. package/public/r/reel-custom.json +1 -1
  863. package/public/r/reel-images.json +1 -1
  864. package/public/r/reel-minimal.json +1 -1
  865. package/public/r/reel.json +1 -1
  866. package/public/r/registry.json +18217 -0
  867. package/public/r/relative-time-controlled.json +1 -1
  868. package/public/r/relative-time-format-date.json +1 -1
  869. package/public/r/relative-time-format-time.json +1 -1
  870. package/public/r/relative-time.json +1 -1
  871. package/public/r/rentals.json +187 -0
  872. package/public/r/roadmap.json +1 -1
  873. package/public/r/sandbox-no-file-explorer.json +1 -1
  874. package/public/r/sandbox.json +1 -1
  875. package/public/r/scroll-area-both.json +1 -1
  876. package/public/r/scroll-area-demo.json +1 -1
  877. package/public/r/scroll-area-horizontal.json +1 -1
  878. package/public/r/select-demo.json +1 -1
  879. package/public/r/select-disabled.json +1 -1
  880. package/public/r/select-form.json +1 -1
  881. package/public/r/select-lg.json +1 -1
  882. package/public/r/select-native.json +12 -0
  883. package/public/r/select-sm.json +1 -1
  884. package/public/r/select-with-groups.json +1 -1
  885. package/public/r/select-without-alignment.json +1 -1
  886. package/public/r/select.json +1 -1
  887. package/public/r/separator-demo.json +1 -1
  888. package/public/r/sheet-demo.json +1 -1
  889. package/public/r/sheet-position.json +1 -1
  890. package/public/r/sidebar-nav.json +22 -0
  891. package/public/r/skeleton-demo.json +1 -1
  892. package/public/r/skeleton.json +1 -1
  893. package/public/r/slider-form.json +1 -1
  894. package/public/r/slider-with-label-value.json +1 -1
  895. package/public/r/sonner.json +16 -0
  896. package/public/r/stats-card.json +22 -0
  897. package/public/r/status-custom.json +1 -1
  898. package/public/r/stepper.json +15 -0
  899. package/public/r/stories-images.json +1 -1
  900. package/public/r/switch-card.json +1 -1
  901. package/public/r/switch-demo.json +1 -1
  902. package/public/r/switch-disabled.json +1 -1
  903. package/public/r/switch-form.json +1 -1
  904. package/public/r/switch-with-description.json +1 -1
  905. package/public/r/table-demo.json +1 -1
  906. package/public/r/table-framed.json +1 -1
  907. package/public/r/table-simple.json +1 -1
  908. package/public/r/table.json +1 -1
  909. package/public/r/tabs-demo.json +1 -1
  910. package/public/r/tabs-underline-vertical.json +1 -1
  911. package/public/r/tabs-underline.json +1 -1
  912. package/public/r/tabs-vertical.json +1 -1
  913. package/public/r/tags-create.json +1 -1
  914. package/public/r/tags-filter.json +1 -1
  915. package/public/r/tags.json +1 -1
  916. package/public/r/template-auth-eight.json +28 -0
  917. package/public/r/template-auth-five.json +28 -0
  918. package/public/r/template-auth-four.json +29 -0
  919. package/public/r/template-auth-nine.json +28 -0
  920. package/public/r/template-auth-one.json +49 -0
  921. package/public/r/template-auth-seven.json +29 -0
  922. package/public/r/template-auth-six.json +28 -0
  923. package/public/r/template-auth-three.json +49 -0
  924. package/public/r/template-auth-two.json +44 -0
  925. package/public/r/template-hero-eight.json +83 -0
  926. package/public/r/template-hero-eleven.json +29 -0
  927. package/public/r/template-hero-five.json +35 -0
  928. package/public/r/template-hero-four.json +35 -0
  929. package/public/r/template-hero-fourteen.json +101 -0
  930. package/public/r/template-hero-nine.json +74 -0
  931. package/public/r/template-hero-one.json +90 -0
  932. package/public/r/template-hero-seven.json +83 -0
  933. package/public/r/template-hero-six.json +83 -0
  934. package/public/r/template-hero-ten.json +77 -0
  935. package/public/r/template-hero-thirteen.json +101 -0
  936. package/public/r/template-hero-three.json +35 -0
  937. package/public/r/template-hero-twelve.json +30 -0
  938. package/public/r/template-hero-two.json +96 -0
  939. package/public/r/template-logo-cloud-five.json +68 -0
  940. package/public/r/template-logo-cloud-four.json +71 -0
  941. package/public/r/template-logo-cloud-one.json +89 -0
  942. package/public/r/template-logo-cloud-three.json +71 -0
  943. package/public/r/template-logo-cloud-two.json +74 -0
  944. package/public/r/textarea-demo.json +1 -1
  945. package/public/r/textarea-disabled.json +1 -1
  946. package/public/r/textarea-form.json +1 -1
  947. package/public/r/textarea-lg.json +1 -1
  948. package/public/r/textarea-sm.json +1 -1
  949. package/public/r/textarea-with-label.json +1 -1
  950. package/public/r/ticker-inline.json +1 -1
  951. package/public/r/timeline.json +15 -0
  952. package/public/r/toast-demo.json +1 -1
  953. package/public/r/toast-heights.json +1 -1
  954. package/public/r/toast-loading.json +1 -1
  955. package/public/r/toast-promise.json +1 -1
  956. package/public/r/toast-with-action.json +1 -1
  957. package/public/r/toast-with-status.json +1 -1
  958. package/public/r/toggle-demo.json +1 -1
  959. package/public/r/toggle-disabled.json +1 -1
  960. package/public/r/toggle-group-demo.json +1 -1
  961. package/public/r/toggle-group-disabled.json +1 -1
  962. package/public/r/toggle-group-lg.json +1 -1
  963. package/public/r/toggle-group-multiple.json +1 -1
  964. package/public/r/toggle-group-outline-with-separator.json +1 -1
  965. package/public/r/toggle-group-outline.json +1 -1
  966. package/public/r/toggle-group-sm.json +1 -1
  967. package/public/r/toggle-group-with-disabled-item.json +1 -1
  968. package/public/r/toggle-icon-group.json +1 -1
  969. package/public/r/toggle-lg.json +1 -1
  970. package/public/r/toggle-outline.json +1 -1
  971. package/public/r/toggle-sm.json +1 -1
  972. package/public/r/toggle-with-icon.json +1 -1
  973. package/public/r/toolbar-demo.json +1 -1
  974. package/public/r/tooltip-demo.json +1 -1
  975. package/public/r/tooltip-grouped.json +1 -1
  976. package/public/r/tree-controlled.json +1 -1
  977. package/public/r/tree-custom-icons.json +1 -1
  978. package/public/r/tree-no-lines.json +1 -1
  979. package/public/r/tree-simple.json +1 -1
  980. package/public/r/tree.json +1 -1
  981. package/public/r/typography.json +1 -1
  982. package/public/r/ui.json +5 -0
  983. package/public/r/use-character-limit.json +12 -0
  984. package/public/r/use-file-upload.json +12 -0
  985. package/public/r/use-pagination.json +12 -0
  986. package/public/r/use-slider-with-input.json +12 -0
  987. package/public/r/use-toast.json +12 -0
  988. package/public/r/utils.json +16 -0
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-553",
4
+ "type": "registry:component",
5
+ "registryDependencies": [
6
+ "https://loveui.dev/building-blocks/r/button.json"
7
+ ],
8
+ "files": [
9
+ {
10
+ "path": "registry/default/components/comp-553.tsx",
11
+ "content": "\"use client\"\n\nimport { useState } from \"react\"\nimport {\n AlertCircleIcon,\n FileArchiveIcon,\n FileIcon,\n FileSpreadsheetIcon,\n FileTextIcon,\n HeadphonesIcon,\n ImageIcon,\n Trash2Icon,\n UploadIcon,\n VideoIcon,\n XIcon,\n} from \"lucide-react\"\n\nimport {\n formatBytes,\n useFileUpload,\n type FileWithPreview,\n} from \"@/registry/building-blocks/default/hooks/use-file-upload\"\nimport { Button } from \"@/registry/building-blocks/default/ui/button\"\n\n// Create some dummy initial files\nconst initialFiles = [\n {\n name: \"intro.zip\",\n size: 252873,\n type: \"application/zip\",\n url: \"https://example.com/intro.zip\",\n id: \"intro.zip-1744638436563-8u5xuls\",\n },\n {\n name: \"image-01.jpg\",\n size: 1528737,\n type: \"image/jpeg\",\n url: \"https://picsum.photos/1000/800?grayscale&random=1\",\n id: \"image-01-123456789\",\n },\n {\n name: \"audio.mp3\",\n size: 1528737,\n type: \"audio/mpeg\",\n url: \"https://example.com/audio.mp3\",\n id: \"audio-123456789\",\n },\n]\n\nconst getFileIcon = (file: { file: File | { type: string; name: string } }) => {\n const fileType = file.file instanceof File ? file.file.type : file.file.type\n const fileName = file.file instanceof File ? file.file.name : file.file.name\n\n const iconMap = {\n pdf: {\n icon: FileTextIcon,\n conditions: (type: string, name: string) =>\n type.includes(\"pdf\") ||\n name.endsWith(\".pdf\") ||\n type.includes(\"word\") ||\n name.endsWith(\".doc\") ||\n name.endsWith(\".docx\"),\n },\n archive: {\n icon: FileArchiveIcon,\n conditions: (type: string, name: string) =>\n type.includes(\"zip\") ||\n type.includes(\"archive\") ||\n name.endsWith(\".zip\") ||\n name.endsWith(\".rar\"),\n },\n excel: {\n icon: FileSpreadsheetIcon,\n conditions: (type: string, name: string) =>\n type.includes(\"excel\") ||\n name.endsWith(\".xls\") ||\n name.endsWith(\".xlsx\"),\n },\n video: {\n icon: VideoIcon,\n conditions: (type: string) => type.includes(\"video/\"),\n },\n audio: {\n icon: HeadphonesIcon,\n conditions: (type: string) => type.includes(\"audio/\"),\n },\n image: {\n icon: ImageIcon,\n conditions: (type: string) => type.startsWith(\"image/\"),\n },\n }\n\n for (const { icon: Icon, conditions } of Object.values(iconMap)) {\n if (conditions(fileType, fileName)) {\n return <Icon className=\"size-5 opacity-60\" />\n }\n }\n\n return <FileIcon className=\"size-5 opacity-60\" />\n}\n\nconst getFilePreview = (file: {\n file: File | { type: string; name: string; url?: string }\n}) => {\n const fileType = file.file instanceof File ? file.file.type : file.file.type\n const fileName = file.file instanceof File ? file.file.name : file.file.name\n\n const renderImage = (src: string) => (\n <img\n src={src}\n alt={fileName}\n className=\"size-full rounded-t-[inherit] object-cover\"\n />\n )\n\n return (\n <div className=\"flex aspect-square items-center justify-center overflow-hidden rounded-t-[inherit] bg-accent\">\n {fileType.startsWith(\"image/\") ? (\n file.file instanceof File ? (\n (() => {\n const previewUrl = URL.createObjectURL(file.file)\n return renderImage(previewUrl)\n })()\n ) : file.file.url ? (\n renderImage(file.file.url)\n ) : (\n <ImageIcon className=\"size-5 opacity-60\" />\n )\n ) : (\n getFileIcon(file)\n )}\n </div>\n )\n}\n\n// Type for tracking upload progress\ntype UploadProgress = {\n fileId: string\n progress: number\n completed: boolean\n}\n\n// Function to simulate file upload with more realistic timing and progress\nconst simulateUpload = (\n totalBytes: number,\n onProgress: (progress: number) => void,\n onComplete: () => void\n) => {\n let timeoutId: NodeJS.Timeout\n let uploadedBytes = 0\n let lastProgressReport = 0\n\n const simulateChunk = () => {\n // Simulate variable network conditions with random chunk sizes\n const chunkSize = Math.floor(Math.random() * 300000) + 2000\n uploadedBytes = Math.min(totalBytes, uploadedBytes + chunkSize)\n\n // Calculate progress percentage (0-100)\n const progressPercent = Math.floor((uploadedBytes / totalBytes) * 100)\n\n // Only report progress if it's changed by at least 1%\n if (progressPercent > lastProgressReport) {\n lastProgressReport = progressPercent\n onProgress(progressPercent)\n }\n\n // Continue simulation if not complete\n if (uploadedBytes < totalBytes) {\n // Variable delay between 50ms and 500ms to simulate network fluctuations (reduced for faster uploads)\n const delay = Math.floor(Math.random() * 450) + 50\n\n // Occasionally add a longer pause to simulate network congestion (5% chance, shorter duration)\n const extraDelay = Math.random() < 0.05 ? 500 : 0\n\n timeoutId = setTimeout(simulateChunk, delay + extraDelay)\n } else {\n // Upload complete\n onComplete()\n }\n }\n\n // Start the simulation\n timeoutId = setTimeout(simulateChunk, 100)\n\n // Return a cleanup function to cancel the simulation\n return () => {\n if (timeoutId) {\n clearTimeout(timeoutId)\n }\n }\n}\n\nexport default function Component() {\n const maxSizeMB = 5\n const maxSize = maxSizeMB * 1024 * 1024 // 5MB default\n const maxFiles = 6\n\n // State to track upload progress for each file\n const [uploadProgress, setUploadProgress] = useState<UploadProgress[]>([])\n\n // Function to handle newly added files\n const handleFilesAdded = (addedFiles: FileWithPreview[]) => {\n // Initialize progress tracking for each new file\n const newProgressItems = addedFiles.map((file) => ({\n fileId: file.id,\n progress: 0,\n completed: false,\n }))\n\n // Add new progress items to state\n setUploadProgress((prev) => [...prev, ...newProgressItems])\n\n // Store cleanup functions\n const cleanupFunctions: Array<() => void> = []\n\n // Start simulated upload for each file\n addedFiles.forEach((file) => {\n const fileSize =\n file.file instanceof File ? file.file.size : file.file.size\n\n // Start the upload simulation and store the cleanup function\n const cleanup = simulateUpload(\n fileSize,\n // Progress callback\n (progress) => {\n setUploadProgress((prev) =>\n prev.map((item) =>\n item.fileId === file.id ? { ...item, progress } : item\n )\n )\n },\n // Complete callback\n () => {\n setUploadProgress((prev) =>\n prev.map((item) =>\n item.fileId === file.id ? { ...item, completed: true } : item\n )\n )\n }\n )\n\n cleanupFunctions.push(cleanup)\n })\n\n // Return a cleanup function that cancels all animations\n return () => {\n cleanupFunctions.forEach((cleanup) => cleanup())\n }\n }\n\n // Remove the progress tracking for the file\n const handleFileRemoved = (fileId: string) => {\n setUploadProgress((prev) => prev.filter((item) => item.fileId !== fileId))\n }\n\n const [\n { files, isDragging, errors },\n {\n handleDragEnter,\n handleDragLeave,\n handleDragOver,\n handleDrop,\n openFileDialog,\n removeFile,\n clearFiles,\n getInputProps,\n },\n ] = useFileUpload({\n multiple: true,\n maxFiles,\n maxSize,\n initialFiles,\n onFilesAdded: handleFilesAdded,\n })\n\n return (\n <div className=\"flex flex-col gap-2\">\n {/* Drop area */}\n <div\n onDragEnter={handleDragEnter}\n onDragLeave={handleDragLeave}\n onDragOver={handleDragOver}\n onDrop={handleDrop}\n data-dragging={isDragging || undefined}\n data-files={files.length > 0 || undefined}\n className=\"relative flex min-h-52 flex-col items-center overflow-hidden rounded-xl border border-dashed border-input p-4 transition-colors not-data-[files]:justify-center has-[input:focus]:border-ring has-[input:focus]:ring-[3px] has-[input:focus]:ring-ring/50 data-[dragging=true]:bg-accent/50\"\n >\n <input\n {...getInputProps()}\n className=\"sr-only\"\n aria-label=\"Upload image file\"\n />\n {files.length > 0 ? (\n <div className=\"flex w-full flex-col gap-3\">\n <div className=\"flex items-center justify-between gap-2\">\n <h3 className=\"truncate text-sm font-medium\">\n Files ({files.length})\n </h3>\n <div className=\"flex gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={openFileDialog}>\n <UploadIcon\n className=\"-ms-0.5 size-3.5 opacity-60\"\n aria-hidden=\"true\"\n />\n Add files\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n // Clear all progress tracking\n setUploadProgress([])\n clearFiles()\n }}\n >\n <Trash2Icon\n className=\"-ms-0.5 size-3.5 opacity-60\"\n aria-hidden=\"true\"\n />\n Remove all\n </Button>\n </div>\n </div>\n\n <div className=\"w-full space-y-2\">\n {files.map((file) => {\n // Find the upload progress for this file once to avoid repeated lookups\n const fileProgress = uploadProgress.find(\n (p) => p.fileId === file.id\n )\n const isUploading = fileProgress && !fileProgress.completed\n\n return (\n <div\n key={file.id}\n data-uploading={isUploading || undefined}\n className=\"flex flex-col gap-1 rounded-lg border bg-background p-2 pe-3 transition-opacity duration-300\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex items-center gap-3 overflow-hidden in-data-[uploading=true]:opacity-50\">\n <div className=\"flex aspect-square size-10 shrink-0 items-center justify-center rounded border\">\n {getFileIcon(file)}\n </div>\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <p className=\"truncate text-[13px] font-medium\">\n {file.file instanceof File\n ? file.file.name\n : file.file.name}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {formatBytes(\n file.file instanceof File\n ? file.file.size\n : file.file.size\n )}\n </p>\n </div>\n </div>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"-me-2 size-8 text-muted-foreground/80 hover:bg-transparent hover:text-foreground\"\n onClick={() => {\n handleFileRemoved(file.id)\n removeFile(file.id)\n }}\n aria-label=\"Remove file\"\n >\n <XIcon className=\"size-4\" aria-hidden=\"true\" />\n </Button>\n </div>\n\n {/* Upload progress bar */}\n {fileProgress &&\n (() => {\n const progress = fileProgress.progress || 0\n const completed = fileProgress.completed || false\n\n if (completed) return null\n\n return (\n <div className=\"mt-1 flex items-center gap-2\">\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-gray-100\">\n <div\n className=\"h-full bg-primary transition-all duration-300 ease-out\"\n style={{ width: `${progress}%` }}\n />\n </div>\n <span className=\"w-10 text-xs text-muted-foreground tabular-nums\">\n {progress}%\n </span>\n </div>\n )\n })()}\n </div>\n )\n })}\n </div>\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center px-4 py-3 text-center\">\n <div\n className=\"mb-2 flex size-11 shrink-0 items-center justify-center rounded-full border bg-background\"\n aria-hidden=\"true\"\n >\n <ImageIcon className=\"size-4 opacity-60\" />\n </div>\n <p className=\"mb-1.5 text-sm font-medium\">Drop your files here</p>\n <p className=\"text-xs text-muted-foreground\">\n Max {maxFiles} files ∙ Up to {maxSizeMB}MB\n </p>\n <Button variant=\"outline\" className=\"mt-4\" onClick={openFileDialog}>\n <UploadIcon className=\"-ms-1 opacity-60\" aria-hidden=\"true\" />\n Select images\n </Button>\n </div>\n )}\n </div>\n\n {errors.length > 0 && (\n <div\n className=\"flex items-center gap-1 text-xs text-destructive\"\n role=\"alert\"\n >\n <AlertCircleIcon className=\"size-3 shrink-0\" />\n <span>{errors[0]}</span>\n </div>\n )}\n\n <p\n aria-live=\"polite\"\n role=\"region\"\n className=\"mt-2 text-center text-xs text-muted-foreground\"\n >\n With simulated progress track ∙{\" \"}\n <a\n href=\"https://github.com/loveui/blob/main/apps/origin/docs/use-file-upload.md\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n >\n API\n </a>\n </p>\n </div>\n )\n}\n",
12
+ "type": "registry:component"
13
+ },
14
+ {
15
+ "path": "registry/default/hooks/use-file-upload.ts",
16
+ "content": "\"use client\"\n\nimport type React from \"react\"\nimport {\n useCallback,\n useRef,\n useState,\n type ChangeEvent,\n type DragEvent,\n type InputHTMLAttributes,\n} from \"react\"\n\nexport type FileMetadata = {\n name: string\n size: number\n type: string\n url: string\n id: string\n}\n\nexport type FileWithPreview = {\n file: File | FileMetadata\n id: string\n preview?: string\n}\n\nexport type FileUploadOptions = {\n maxFiles?: number // Only used when multiple is true, defaults to Infinity\n maxSize?: number // in bytes\n accept?: string\n multiple?: boolean // Defaults to false\n initialFiles?: FileMetadata[]\n onFilesChange?: (files: FileWithPreview[]) => void // Callback when files change\n onFilesAdded?: (addedFiles: FileWithPreview[]) => void // Callback when new files are added\n}\n\nexport type FileUploadState = {\n files: FileWithPreview[]\n isDragging: boolean\n errors: string[]\n}\n\nexport type FileUploadActions = {\n addFiles: (files: FileList | File[]) => void\n removeFile: (id: string) => void\n clearFiles: () => void\n clearErrors: () => void\n handleDragEnter: (e: DragEvent<HTMLElement>) => void\n handleDragLeave: (e: DragEvent<HTMLElement>) => void\n handleDragOver: (e: DragEvent<HTMLElement>) => void\n handleDrop: (e: DragEvent<HTMLElement>) => void\n handleFileChange: (e: ChangeEvent<HTMLInputElement>) => void\n openFileDialog: () => void\n getInputProps: (\n props?: InputHTMLAttributes<HTMLInputElement>\n ) => InputHTMLAttributes<HTMLInputElement> & {\n // Use `any` here to avoid cross-React ref type conflicts across packages\n ref: any\n }\n}\n\nexport const useFileUpload = (\n options: FileUploadOptions = {}\n): [FileUploadState, FileUploadActions] => {\n const {\n maxFiles = Infinity,\n maxSize = Infinity,\n accept = \"*\",\n multiple = false,\n initialFiles = [],\n onFilesChange,\n onFilesAdded,\n } = options\n\n const [state, setState] = useState<FileUploadState>({\n files: initialFiles.map((file) => ({\n file,\n id: file.id,\n preview: file.url,\n })),\n isDragging: false,\n errors: [],\n })\n\n const inputRef = useRef<HTMLInputElement>(null)\n\n const validateFile = useCallback(\n (file: File | FileMetadata): string | null => {\n if (file instanceof File) {\n if (file.size > maxSize) {\n return `File \"${file.name}\" exceeds the maximum size of ${formatBytes(maxSize)}.`\n }\n } else {\n if (file.size > maxSize) {\n return `File \"${file.name}\" exceeds the maximum size of ${formatBytes(maxSize)}.`\n }\n }\n\n if (accept !== \"*\") {\n const acceptedTypes = accept.split(\",\").map((type) => type.trim())\n const fileType = file instanceof File ? file.type || \"\" : file.type\n const fileExtension = `.${file instanceof File ? file.name.split(\".\").pop() : file.name.split(\".\").pop()}`\n\n const isAccepted = acceptedTypes.some((type) => {\n if (type.startsWith(\".\")) {\n return fileExtension.toLowerCase() === type.toLowerCase()\n }\n if (type.endsWith(\"/*\")) {\n const baseType = type.split(\"/\")[0]\n return fileType.startsWith(`${baseType}/`)\n }\n return fileType === type\n })\n\n if (!isAccepted) {\n return `File \"${file instanceof File ? file.name : file.name}\" is not an accepted file type.`\n }\n }\n\n return null\n },\n [accept, maxSize]\n )\n\n const createPreview = useCallback(\n (file: File | FileMetadata): string | undefined => {\n if (file instanceof File) {\n return URL.createObjectURL(file)\n }\n return file.url\n },\n []\n )\n\n const generateUniqueId = useCallback((file: File | FileMetadata): string => {\n if (file instanceof File) {\n return `${file.name}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`\n }\n return file.id\n }, [])\n\n const clearFiles = useCallback(() => {\n setState((prev) => {\n // Clean up object URLs\n prev.files.forEach((file) => {\n if (\n file.preview &&\n file.file instanceof File &&\n file.file.type.startsWith(\"image/\")\n ) {\n URL.revokeObjectURL(file.preview)\n }\n })\n\n if (inputRef.current) {\n inputRef.current.value = \"\"\n }\n\n const newState = {\n ...prev,\n files: [],\n errors: [],\n }\n\n onFilesChange?.(newState.files)\n return newState\n })\n }, [onFilesChange])\n\n const addFiles = useCallback(\n (newFiles: FileList | File[]) => {\n if (!newFiles || newFiles.length === 0) return\n\n const newFilesArray = Array.from(newFiles)\n const errors: string[] = []\n\n // Clear existing errors when new files are uploaded\n setState((prev) => ({ ...prev, errors: [] }))\n\n // In single file mode, clear existing files first\n if (!multiple) {\n clearFiles()\n }\n\n // Check if adding these files would exceed maxFiles (only in multiple mode)\n if (\n multiple &&\n maxFiles !== Infinity &&\n state.files.length + newFilesArray.length > maxFiles\n ) {\n errors.push(`You can only upload a maximum of ${maxFiles} files.`)\n setState((prev) => ({ ...prev, errors }))\n return\n }\n\n const validFiles: FileWithPreview[] = []\n\n newFilesArray.forEach((file) => {\n // Only check for duplicates if multiple files are allowed\n if (multiple) {\n const isDuplicate = state.files.some(\n (existingFile) =>\n existingFile.file.name === file.name &&\n existingFile.file.size === file.size\n )\n\n // Skip duplicate files silently\n if (isDuplicate) {\n return\n }\n }\n\n // Check file size\n if (file.size > maxSize) {\n errors.push(\n multiple\n ? `Some files exceed the maximum size of ${formatBytes(maxSize)}.`\n : `File exceeds the maximum size of ${formatBytes(maxSize)}.`\n )\n return\n }\n\n const error = validateFile(file)\n if (error) {\n errors.push(error)\n } else {\n validFiles.push({\n file,\n id: generateUniqueId(file),\n preview: createPreview(file),\n })\n }\n })\n\n // Only update state if we have valid files to add\n if (validFiles.length > 0) {\n // Call the onFilesAdded callback with the newly added valid files\n onFilesAdded?.(validFiles)\n\n setState((prev) => {\n const newFiles = !multiple\n ? validFiles\n : [...prev.files, ...validFiles]\n onFilesChange?.(newFiles)\n return {\n ...prev,\n files: newFiles,\n errors,\n }\n })\n } else if (errors.length > 0) {\n setState((prev) => ({\n ...prev,\n errors,\n }))\n }\n\n // Reset input value after handling files\n if (inputRef.current) {\n inputRef.current.value = \"\"\n }\n },\n [\n state.files,\n maxFiles,\n multiple,\n maxSize,\n validateFile,\n createPreview,\n generateUniqueId,\n clearFiles,\n onFilesChange,\n onFilesAdded,\n ]\n )\n\n const removeFile = useCallback(\n (id: string) => {\n setState((prev) => {\n const fileToRemove = prev.files.find((file) => file.id === id)\n if (\n fileToRemove &&\n fileToRemove.preview &&\n fileToRemove.file instanceof File &&\n fileToRemove.file.type.startsWith(\"image/\")\n ) {\n URL.revokeObjectURL(fileToRemove.preview)\n }\n\n const newFiles = prev.files.filter((file) => file.id !== id)\n onFilesChange?.(newFiles)\n\n return {\n ...prev,\n files: newFiles,\n errors: [],\n }\n })\n },\n [onFilesChange]\n )\n\n const clearErrors = useCallback(() => {\n setState((prev) => ({\n ...prev,\n errors: [],\n }))\n }, [])\n\n const handleDragEnter = useCallback((e: DragEvent<HTMLElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setState((prev) => ({ ...prev, isDragging: true }))\n }, [])\n\n const handleDragLeave = useCallback((e: DragEvent<HTMLElement>) => {\n e.preventDefault()\n e.stopPropagation()\n\n if (e.currentTarget.contains(e.relatedTarget as Node)) {\n return\n }\n\n setState((prev) => ({ ...prev, isDragging: false }))\n }, [])\n\n const handleDragOver = useCallback((e: DragEvent<HTMLElement>) => {\n e.preventDefault()\n e.stopPropagation()\n }, [])\n\n const handleDrop = useCallback(\n (e: DragEvent<HTMLElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setState((prev) => ({ ...prev, isDragging: false }))\n\n // Don't process files if the input is disabled\n if (inputRef.current?.disabled) {\n return\n }\n\n if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {\n // In single file mode, only use the first file\n if (!multiple) {\n const file = e.dataTransfer.files[0]\n addFiles([file])\n } else {\n addFiles(e.dataTransfer.files)\n }\n }\n },\n [addFiles, multiple]\n )\n\n const handleFileChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>) => {\n if (e.target.files && e.target.files.length > 0) {\n addFiles(e.target.files)\n }\n },\n [addFiles]\n )\n\n const openFileDialog = useCallback(() => {\n if (inputRef.current) {\n inputRef.current.click()\n }\n }, [])\n\n const getInputProps = useCallback(\n (props: InputHTMLAttributes<HTMLInputElement> = {}) => {\n return {\n ...props,\n type: \"file\" as const,\n onChange: handleFileChange,\n accept: props.accept || accept,\n multiple: props.multiple !== undefined ? props.multiple : multiple,\n // Cast to `any` to prevent mismatched React ref type errors across workspaces\n ref: inputRef as any,\n }\n },\n [accept, multiple, handleFileChange]\n )\n\n return [\n state,\n {\n addFiles,\n removeFile,\n clearFiles,\n clearErrors,\n handleDragEnter,\n handleDragLeave,\n handleDragOver,\n handleDrop,\n handleFileChange,\n openFileDialog,\n getInputProps,\n },\n ]\n}\n\n// Helper function to format bytes to human-readable format\nexport const formatBytes = (bytes: number, decimals = 2): string => {\n if (bytes === 0) return \"0 Bytes\"\n\n const k = 1024\n const dm = decimals < 0 ? 0 : decimals\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\", \"ZB\", \"YB\"]\n\n const i = Math.floor(Math.log(bytes) / Math.log(k))\n\n return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + sizes[i]\n}\n",
17
+ "type": "registry:hook"
18
+ }
19
+ ],
20
+ "meta": {
21
+ "tags": [
22
+ "upload",
23
+ "file",
24
+ "image",
25
+ "drag and drop"
26
+ ],
27
+ "colSpan": 2
28
+ }
29
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-554",
4
+ "type": "registry:component",
5
+ "registryDependencies": [
6
+ "https://loveui.dev/building-blocks/r/button.json",
7
+ "https://loveui.dev/building-blocks/r/cropper.json",
8
+ "https://loveui.dev/building-blocks/r/dialog.json",
9
+ "https://loveui.dev/building-blocks/r/slider.json"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "registry/default/components/comp-554.tsx",
14
+ "content": "\"use client\"\n\nimport { useCallback, useEffect, useRef, useState } from \"react\"\nimport {\n ArrowLeftIcon,\n CircleUserRoundIcon,\n XIcon,\n ZoomInIcon,\n ZoomOutIcon,\n} from \"lucide-react\"\n\nimport { useFileUpload } from \"@/registry/building-blocks/default/hooks/use-file-upload\"\nimport { Button } from \"@/registry/building-blocks/default/ui/button\"\nimport {\n Cropper,\n CropperCropArea,\n CropperDescription,\n CropperImage,\n} from \"@/registry/building-blocks/default/ui/cropper\"\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"@/registry/building-blocks/default/ui/dialog\"\nimport { Slider } from \"@/registry/building-blocks/default/ui/slider\"\n\n// Define type for pixel crop area\ntype Area = { x: number; y: number; width: number; height: number }\n\n// Helper function to create a cropped image blob\nconst createImage = (url: string): Promise<HTMLImageElement> =>\n new Promise((resolve, reject) => {\n const image = new Image()\n image.addEventListener(\"load\", () => resolve(image))\n image.addEventListener(\"error\", (error) => reject(error))\n image.setAttribute(\"crossOrigin\", \"anonymous\") // Needed for canvas Tainted check\n image.src = url\n })\n\nasync function getCroppedImg(\n imageSrc: string,\n pixelCrop: Area,\n outputWidth: number = pixelCrop.width, // Optional: specify output size\n outputHeight: number = pixelCrop.height\n): Promise<Blob | null> {\n try {\n const image = await createImage(imageSrc)\n const canvas = document.createElement(\"canvas\")\n const ctx = canvas.getContext(\"2d\")\n\n if (!ctx) {\n return null\n }\n\n // Set canvas size to desired output size\n canvas.width = outputWidth\n canvas.height = outputHeight\n\n // Draw the cropped image onto the canvas\n ctx.drawImage(\n image,\n pixelCrop.x,\n pixelCrop.y,\n pixelCrop.width,\n pixelCrop.height,\n 0,\n 0,\n outputWidth, // Draw onto the output size\n outputHeight\n )\n\n // Convert canvas to blob\n return new Promise((resolve) => {\n canvas.toBlob((blob) => {\n resolve(blob)\n }, \"image/jpeg\") // Specify format and quality if needed\n })\n } catch (error) {\n console.error(\"Error in getCroppedImg:\", error)\n return null\n }\n}\n\nexport default function Component() {\n const [\n { files, isDragging },\n {\n handleDragEnter,\n handleDragLeave,\n handleDragOver,\n handleDrop,\n openFileDialog,\n removeFile,\n getInputProps,\n },\n ] = useFileUpload({\n accept: \"image/*\",\n })\n\n const previewUrl = files[0]?.preview || null\n const fileId = files[0]?.id\n\n const [finalImageUrl, setFinalImageUrl] = useState<string | null>(null)\n const [isDialogOpen, setIsDialogOpen] = useState(false)\n\n // Ref to track the previous file ID to detect new uploads\n const previousFileIdRef = useRef<string | undefined | null>(null)\n\n // State to store the desired crop area in pixels\n const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null)\n\n // State for zoom level\n const [zoom, setZoom] = useState(1)\n\n // Callback for Cropper to provide crop data - Wrap with useCallback\n const handleCropChange = useCallback((pixels: Area | null) => {\n setCroppedAreaPixels(pixels)\n }, [])\n\n const handleApply = async () => {\n // Check if we have the necessary data\n if (!previewUrl || !fileId || !croppedAreaPixels) {\n console.error(\"Missing data for apply:\", {\n previewUrl,\n fileId,\n croppedAreaPixels,\n })\n // Remove file if apply is clicked without crop data?\n if (fileId) {\n removeFile(fileId)\n setCroppedAreaPixels(null)\n }\n return\n }\n\n try {\n // 1. Get the cropped image blob using the helper\n const croppedBlob = await getCroppedImg(previewUrl, croppedAreaPixels)\n\n if (!croppedBlob) {\n throw new Error(\"Failed to generate cropped image blob.\")\n }\n\n // 2. Create a NEW object URL from the cropped blob\n const newFinalUrl = URL.createObjectURL(croppedBlob)\n\n // 3. Revoke the OLD finalImageUrl if it exists\n if (finalImageUrl) {\n URL.revokeObjectURL(finalImageUrl)\n }\n\n // 4. Set the final avatar state to the NEW URL\n setFinalImageUrl(newFinalUrl)\n\n // 5. Close the dialog (don't remove the file yet)\n setIsDialogOpen(false)\n } catch (error) {\n console.error(\"Error during apply:\", error)\n // Close the dialog even if cropping fails\n setIsDialogOpen(false)\n }\n }\n\n const handleRemoveFinalImage = () => {\n if (finalImageUrl) {\n URL.revokeObjectURL(finalImageUrl)\n }\n setFinalImageUrl(null)\n }\n\n useEffect(() => {\n const currentFinalUrl = finalImageUrl\n // Cleanup function\n return () => {\n if (currentFinalUrl && currentFinalUrl.startsWith(\"blob:\")) {\n URL.revokeObjectURL(currentFinalUrl)\n }\n }\n }, [finalImageUrl])\n\n // Effect to open dialog when a *new* file is ready\n useEffect(() => {\n // Check if fileId exists and is different from the previous one\n if (fileId && fileId !== previousFileIdRef.current) {\n setIsDialogOpen(true) // Open dialog for the new file\n setCroppedAreaPixels(null) // Reset crop area for the new file\n setZoom(1) // Reset zoom for the new file\n }\n // Update the ref to the current fileId for the next render\n previousFileIdRef.current = fileId\n }, [fileId]) // Depend only on fileId\n\n return (\n <div className=\"flex flex-col items-center gap-2\">\n <div className=\"relative inline-flex\">\n {/* Drop area - uses finalImageUrl */}\n <button\n className=\"relative flex size-16 items-center justify-center overflow-hidden rounded-full border border-dashed border-input transition-colors outline-none hover:bg-accent/50 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-disabled:pointer-events-none has-disabled:opacity-50 has-[img]:border-none data-[dragging=true]:bg-accent/50\"\n onClick={openFileDialog}\n onDragEnter={handleDragEnter}\n onDragLeave={handleDragLeave}\n onDragOver={handleDragOver}\n onDrop={handleDrop}\n data-dragging={isDragging || undefined}\n aria-label={finalImageUrl ? \"Change image\" : \"Upload image\"}\n >\n {finalImageUrl ? (\n <img\n className=\"size-full object-cover\"\n src={finalImageUrl}\n alt=\"User avatar\"\n width={64}\n height={64}\n style={{ objectFit: \"cover\" }}\n />\n ) : (\n <div aria-hidden=\"true\">\n <CircleUserRoundIcon className=\"size-4 opacity-60\" />\n </div>\n )}\n </button>\n {/* Remove button - depends on finalImageUrl */}\n {finalImageUrl && (\n <Button\n onClick={handleRemoveFinalImage}\n size=\"icon\"\n className=\"absolute -top-1 -right-1 size-6 rounded-full border-2 border-background shadow-none focus-visible:border-background\"\n aria-label=\"Remove image\"\n >\n <XIcon className=\"size-3.5\" />\n </Button>\n )}\n <input\n {...getInputProps()}\n className=\"sr-only\"\n aria-label=\"Upload image file\"\n tabIndex={-1}\n />\n </div>\n\n {/* Cropper Dialog - Use isDialogOpen for open prop */}\n <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>\n <DialogContent className=\"gap-0 p-0 sm:max-w-140 *:[button]:hidden\">\n <DialogDescription className=\"sr-only\">\n Crop image dialog\n </DialogDescription>\n <DialogHeader className=\"contents space-y-0 text-left\">\n <DialogTitle className=\"flex items-center justify-between border-b p-4 text-base\">\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"-my-1 opacity-60\"\n onClick={() => setIsDialogOpen(false)}\n aria-label=\"Cancel\"\n >\n <ArrowLeftIcon aria-hidden=\"true\" />\n </Button>\n <span>Crop image</span>\n </div>\n <Button\n className=\"-my-1\"\n onClick={handleApply}\n disabled={!previewUrl}\n autoFocus\n >\n Apply\n </Button>\n </DialogTitle>\n </DialogHeader>\n {previewUrl && (\n <Cropper\n className=\"h-96 sm:h-120\"\n image={previewUrl}\n zoom={zoom}\n onCropChange={handleCropChange}\n onZoomChange={setZoom}\n >\n <CropperDescription />\n <CropperImage />\n <CropperCropArea />\n </Cropper>\n )}\n <DialogFooter className=\"border-t px-4 py-6\">\n <div className=\"mx-auto flex w-full max-w-80 items-center gap-4\">\n <ZoomOutIcon\n className=\"shrink-0 opacity-60\"\n size={16}\n aria-hidden=\"true\"\n />\n <Slider\n defaultValue={[1]}\n value={[zoom]}\n min={1}\n max={3}\n step={0.1}\n onValueChange={(value) => setZoom(value[0])}\n aria-label=\"Zoom slider\"\n />\n <ZoomInIcon\n className=\"shrink-0 opacity-60\"\n size={16}\n aria-hidden=\"true\"\n />\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n\n <p\n aria-live=\"polite\"\n role=\"region\"\n className=\"mt-2 text-xs text-muted-foreground\"\n >\n Avatar{\" \"}\n <a\n href=\"https://github.com/loveui/blob/main/apps/origin/docs/use-file-upload.md\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n >\n uploader\n </a>{\" \"}\n with{\" \"}\n <a\n href=\"https://github.com/origin-space/image-cropper\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n >\n cropper\n </a>\n </p>\n </div>\n )\n}\n",
15
+ "type": "registry:component"
16
+ },
17
+ {
18
+ "path": "registry/default/hooks/use-file-upload.ts",
19
+ "content": "\"use client\"\n\nimport type React from \"react\"\nimport {\n useCallback,\n useRef,\n useState,\n type ChangeEvent,\n type DragEvent,\n type InputHTMLAttributes,\n} from \"react\"\n\nexport type FileMetadata = {\n name: string\n size: number\n type: string\n url: string\n id: string\n}\n\nexport type FileWithPreview = {\n file: File | FileMetadata\n id: string\n preview?: string\n}\n\nexport type FileUploadOptions = {\n maxFiles?: number // Only used when multiple is true, defaults to Infinity\n maxSize?: number // in bytes\n accept?: string\n multiple?: boolean // Defaults to false\n initialFiles?: FileMetadata[]\n onFilesChange?: (files: FileWithPreview[]) => void // Callback when files change\n onFilesAdded?: (addedFiles: FileWithPreview[]) => void // Callback when new files are added\n}\n\nexport type FileUploadState = {\n files: FileWithPreview[]\n isDragging: boolean\n errors: string[]\n}\n\nexport type FileUploadActions = {\n addFiles: (files: FileList | File[]) => void\n removeFile: (id: string) => void\n clearFiles: () => void\n clearErrors: () => void\n handleDragEnter: (e: DragEvent<HTMLElement>) => void\n handleDragLeave: (e: DragEvent<HTMLElement>) => void\n handleDragOver: (e: DragEvent<HTMLElement>) => void\n handleDrop: (e: DragEvent<HTMLElement>) => void\n handleFileChange: (e: ChangeEvent<HTMLInputElement>) => void\n openFileDialog: () => void\n getInputProps: (\n props?: InputHTMLAttributes<HTMLInputElement>\n ) => InputHTMLAttributes<HTMLInputElement> & {\n // Use `any` here to avoid cross-React ref type conflicts across packages\n ref: any\n }\n}\n\nexport const useFileUpload = (\n options: FileUploadOptions = {}\n): [FileUploadState, FileUploadActions] => {\n const {\n maxFiles = Infinity,\n maxSize = Infinity,\n accept = \"*\",\n multiple = false,\n initialFiles = [],\n onFilesChange,\n onFilesAdded,\n } = options\n\n const [state, setState] = useState<FileUploadState>({\n files: initialFiles.map((file) => ({\n file,\n id: file.id,\n preview: file.url,\n })),\n isDragging: false,\n errors: [],\n })\n\n const inputRef = useRef<HTMLInputElement>(null)\n\n const validateFile = useCallback(\n (file: File | FileMetadata): string | null => {\n if (file instanceof File) {\n if (file.size > maxSize) {\n return `File \"${file.name}\" exceeds the maximum size of ${formatBytes(maxSize)}.`\n }\n } else {\n if (file.size > maxSize) {\n return `File \"${file.name}\" exceeds the maximum size of ${formatBytes(maxSize)}.`\n }\n }\n\n if (accept !== \"*\") {\n const acceptedTypes = accept.split(\",\").map((type) => type.trim())\n const fileType = file instanceof File ? file.type || \"\" : file.type\n const fileExtension = `.${file instanceof File ? file.name.split(\".\").pop() : file.name.split(\".\").pop()}`\n\n const isAccepted = acceptedTypes.some((type) => {\n if (type.startsWith(\".\")) {\n return fileExtension.toLowerCase() === type.toLowerCase()\n }\n if (type.endsWith(\"/*\")) {\n const baseType = type.split(\"/\")[0]\n return fileType.startsWith(`${baseType}/`)\n }\n return fileType === type\n })\n\n if (!isAccepted) {\n return `File \"${file instanceof File ? file.name : file.name}\" is not an accepted file type.`\n }\n }\n\n return null\n },\n [accept, maxSize]\n )\n\n const createPreview = useCallback(\n (file: File | FileMetadata): string | undefined => {\n if (file instanceof File) {\n return URL.createObjectURL(file)\n }\n return file.url\n },\n []\n )\n\n const generateUniqueId = useCallback((file: File | FileMetadata): string => {\n if (file instanceof File) {\n return `${file.name}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`\n }\n return file.id\n }, [])\n\n const clearFiles = useCallback(() => {\n setState((prev) => {\n // Clean up object URLs\n prev.files.forEach((file) => {\n if (\n file.preview &&\n file.file instanceof File &&\n file.file.type.startsWith(\"image/\")\n ) {\n URL.revokeObjectURL(file.preview)\n }\n })\n\n if (inputRef.current) {\n inputRef.current.value = \"\"\n }\n\n const newState = {\n ...prev,\n files: [],\n errors: [],\n }\n\n onFilesChange?.(newState.files)\n return newState\n })\n }, [onFilesChange])\n\n const addFiles = useCallback(\n (newFiles: FileList | File[]) => {\n if (!newFiles || newFiles.length === 0) return\n\n const newFilesArray = Array.from(newFiles)\n const errors: string[] = []\n\n // Clear existing errors when new files are uploaded\n setState((prev) => ({ ...prev, errors: [] }))\n\n // In single file mode, clear existing files first\n if (!multiple) {\n clearFiles()\n }\n\n // Check if adding these files would exceed maxFiles (only in multiple mode)\n if (\n multiple &&\n maxFiles !== Infinity &&\n state.files.length + newFilesArray.length > maxFiles\n ) {\n errors.push(`You can only upload a maximum of ${maxFiles} files.`)\n setState((prev) => ({ ...prev, errors }))\n return\n }\n\n const validFiles: FileWithPreview[] = []\n\n newFilesArray.forEach((file) => {\n // Only check for duplicates if multiple files are allowed\n if (multiple) {\n const isDuplicate = state.files.some(\n (existingFile) =>\n existingFile.file.name === file.name &&\n existingFile.file.size === file.size\n )\n\n // Skip duplicate files silently\n if (isDuplicate) {\n return\n }\n }\n\n // Check file size\n if (file.size > maxSize) {\n errors.push(\n multiple\n ? `Some files exceed the maximum size of ${formatBytes(maxSize)}.`\n : `File exceeds the maximum size of ${formatBytes(maxSize)}.`\n )\n return\n }\n\n const error = validateFile(file)\n if (error) {\n errors.push(error)\n } else {\n validFiles.push({\n file,\n id: generateUniqueId(file),\n preview: createPreview(file),\n })\n }\n })\n\n // Only update state if we have valid files to add\n if (validFiles.length > 0) {\n // Call the onFilesAdded callback with the newly added valid files\n onFilesAdded?.(validFiles)\n\n setState((prev) => {\n const newFiles = !multiple\n ? validFiles\n : [...prev.files, ...validFiles]\n onFilesChange?.(newFiles)\n return {\n ...prev,\n files: newFiles,\n errors,\n }\n })\n } else if (errors.length > 0) {\n setState((prev) => ({\n ...prev,\n errors,\n }))\n }\n\n // Reset input value after handling files\n if (inputRef.current) {\n inputRef.current.value = \"\"\n }\n },\n [\n state.files,\n maxFiles,\n multiple,\n maxSize,\n validateFile,\n createPreview,\n generateUniqueId,\n clearFiles,\n onFilesChange,\n onFilesAdded,\n ]\n )\n\n const removeFile = useCallback(\n (id: string) => {\n setState((prev) => {\n const fileToRemove = prev.files.find((file) => file.id === id)\n if (\n fileToRemove &&\n fileToRemove.preview &&\n fileToRemove.file instanceof File &&\n fileToRemove.file.type.startsWith(\"image/\")\n ) {\n URL.revokeObjectURL(fileToRemove.preview)\n }\n\n const newFiles = prev.files.filter((file) => file.id !== id)\n onFilesChange?.(newFiles)\n\n return {\n ...prev,\n files: newFiles,\n errors: [],\n }\n })\n },\n [onFilesChange]\n )\n\n const clearErrors = useCallback(() => {\n setState((prev) => ({\n ...prev,\n errors: [],\n }))\n }, [])\n\n const handleDragEnter = useCallback((e: DragEvent<HTMLElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setState((prev) => ({ ...prev, isDragging: true }))\n }, [])\n\n const handleDragLeave = useCallback((e: DragEvent<HTMLElement>) => {\n e.preventDefault()\n e.stopPropagation()\n\n if (e.currentTarget.contains(e.relatedTarget as Node)) {\n return\n }\n\n setState((prev) => ({ ...prev, isDragging: false }))\n }, [])\n\n const handleDragOver = useCallback((e: DragEvent<HTMLElement>) => {\n e.preventDefault()\n e.stopPropagation()\n }, [])\n\n const handleDrop = useCallback(\n (e: DragEvent<HTMLElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setState((prev) => ({ ...prev, isDragging: false }))\n\n // Don't process files if the input is disabled\n if (inputRef.current?.disabled) {\n return\n }\n\n if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {\n // In single file mode, only use the first file\n if (!multiple) {\n const file = e.dataTransfer.files[0]\n addFiles([file])\n } else {\n addFiles(e.dataTransfer.files)\n }\n }\n },\n [addFiles, multiple]\n )\n\n const handleFileChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>) => {\n if (e.target.files && e.target.files.length > 0) {\n addFiles(e.target.files)\n }\n },\n [addFiles]\n )\n\n const openFileDialog = useCallback(() => {\n if (inputRef.current) {\n inputRef.current.click()\n }\n }, [])\n\n const getInputProps = useCallback(\n (props: InputHTMLAttributes<HTMLInputElement> = {}) => {\n return {\n ...props,\n type: \"file\" as const,\n onChange: handleFileChange,\n accept: props.accept || accept,\n multiple: props.multiple !== undefined ? props.multiple : multiple,\n // Cast to `any` to prevent mismatched React ref type errors across workspaces\n ref: inputRef as any,\n }\n },\n [accept, multiple, handleFileChange]\n )\n\n return [\n state,\n {\n addFiles,\n removeFile,\n clearFiles,\n clearErrors,\n handleDragEnter,\n handleDragLeave,\n handleDragOver,\n handleDrop,\n handleFileChange,\n openFileDialog,\n getInputProps,\n },\n ]\n}\n\n// Helper function to format bytes to human-readable format\nexport const formatBytes = (bytes: number, decimals = 2): string => {\n if (bytes === 0) return \"0 Bytes\"\n\n const k = 1024\n const dm = decimals < 0 ? 0 : decimals\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\", \"ZB\", \"YB\"]\n\n const i = Math.floor(Math.log(bytes) / Math.log(k))\n\n return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + sizes[i]\n}\n",
20
+ "type": "registry:hook"
21
+ }
22
+ ],
23
+ "meta": {
24
+ "tags": [
25
+ "upload",
26
+ "file",
27
+ "image",
28
+ "drag and drop",
29
+ "crop",
30
+ "dialog",
31
+ "slider",
32
+ "zoom"
33
+ ],
34
+ "colSpan": 3
35
+ }
36
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-555",
4
+ "type": "registry:component",
5
+ "registryDependencies": [
6
+ "https://loveui.dev/building-blocks/r/cropper.json"
7
+ ],
8
+ "files": [
9
+ {
10
+ "path": "registry/default/components/comp-555.tsx",
11
+ "content": "import {\n Cropper,\n CropperCropArea,\n CropperDescription,\n CropperImage,\n} from \"@/registry/building-blocks/default/ui/cropper\"\n\nexport default function Component() {\n return (\n <div className=\"flex flex-col items-center gap-2\">\n <Cropper\n className=\"h-80\"\n image=\"https://raw.githubusercontent.com/origin-space/origin-images/refs/heads/main/cropper-01_bcxaic.jpg\"\n >\n <CropperDescription />\n <CropperImage />\n <CropperCropArea />\n </Cropper>\n\n <p\n aria-live=\"polite\"\n role=\"region\"\n className=\"mt-2 text-xs text-muted-foreground\"\n >\n Basic cropper ∙{\" \"}\n <a\n href=\"https://github.com/origin-space/image-cropper\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n >\n API\n </a>\n </p>\n </div>\n )\n}\n",
12
+ "type": "registry:component"
13
+ }
14
+ ],
15
+ "meta": {
16
+ "tags": [
17
+ "image",
18
+ "crop",
19
+ "zoom"
20
+ ],
21
+ "colSpan": 2
22
+ }
23
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-556",
4
+ "type": "registry:component",
5
+ "registryDependencies": [
6
+ "https://loveui.dev/building-blocks/r/cropper.json"
7
+ ],
8
+ "files": [
9
+ {
10
+ "path": "registry/default/components/comp-556.tsx",
11
+ "content": "import {\n Cropper,\n CropperCropArea,\n CropperDescription,\n CropperImage,\n} from \"@/registry/building-blocks/default/ui/cropper\"\n\nexport default function Component() {\n return (\n <div className=\"flex flex-col items-center gap-2\">\n <Cropper\n className=\"h-80\"\n image=\"https://raw.githubusercontent.com/origin-space/origin-images/refs/heads/main/cropper-02_a2xwtd.jpg\"\n aspectRatio={16 / 9}\n >\n <CropperDescription />\n <CropperImage />\n <CropperCropArea />\n </Cropper>\n\n <p\n aria-live=\"polite\"\n role=\"region\"\n className=\"mt-2 text-xs text-muted-foreground\"\n >\n Cropper with aspect ratio 16:9 ∙{\" \"}\n <a\n href=\"https://github.com/origin-space/image-cropper\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n >\n API\n </a>\n </p>\n </div>\n )\n}\n",
12
+ "type": "registry:component"
13
+ }
14
+ ],
15
+ "meta": {
16
+ "tags": [
17
+ "image",
18
+ "crop",
19
+ "zoom"
20
+ ],
21
+ "colSpan": 2
22
+ }
23
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-557",
4
+ "type": "registry:component",
5
+ "registryDependencies": [
6
+ "https://loveui.dev/building-blocks/r/cropper.json"
7
+ ],
8
+ "files": [
9
+ {
10
+ "path": "registry/default/components/comp-557.tsx",
11
+ "content": "import {\n Cropper,\n CropperCropArea,\n CropperDescription,\n CropperImage,\n} from \"@/registry/building-blocks/default/ui/cropper\"\n\nexport default function Component() {\n return (\n <div className=\"flex flex-col items-center gap-2\">\n <Cropper\n className=\"h-80\"\n image=\"https://raw.githubusercontent.com/origin-space/origin-images/refs/heads/main/cropper-03_uym8r0.jpg\"\n >\n <CropperDescription />\n <CropperImage />\n <CropperCropArea className=\"border-blue-500\" />\n </Cropper>\n\n <p\n aria-live=\"polite\"\n role=\"region\"\n className=\"mt-2 text-xs text-muted-foreground\"\n >\n Cropper with custom crop area color ∙{\" \"}\n <a\n href=\"https://github.com/origin-space/image-cropper\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n >\n API\n </a>\n </p>\n </div>\n )\n}\n",
12
+ "type": "registry:component"
13
+ }
14
+ ],
15
+ "meta": {
16
+ "tags": [
17
+ "image",
18
+ "crop",
19
+ "zoom"
20
+ ],
21
+ "colSpan": 2
22
+ }
23
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-558",
4
+ "type": "registry:component",
5
+ "registryDependencies": [
6
+ "https://loveui.dev/building-blocks/r/cropper.json"
7
+ ],
8
+ "files": [
9
+ {
10
+ "path": "registry/default/components/comp-558.tsx",
11
+ "content": "import {\n Cropper,\n CropperCropArea,\n CropperDescription,\n CropperImage,\n} from \"@/registry/building-blocks/default/ui/cropper\"\n\nexport default function Component() {\n return (\n <div className=\"flex flex-col items-center gap-2\">\n <Cropper\n className=\"h-80 bg-black\"\n image=\"https://raw.githubusercontent.com/origin-space/origin-images/refs/heads/main/cropper-04_jflxhw.jpg\"\n >\n <CropperDescription />\n <CropperImage />\n <CropperCropArea className=\"shadow-[0_0_0_9999px_rgba(255,255,255,0.5)]\" />\n </Cropper>\n\n <p\n aria-live=\"polite\"\n role=\"region\"\n className=\"mt-2 text-xs text-muted-foreground\"\n >\n Cropper with custom mask overlay ∙{\" \"}\n <a\n href=\"https://github.com/origin-space/image-cropper\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n >\n API\n </a>\n </p>\n </div>\n )\n}\n",
12
+ "type": "registry:component"
13
+ }
14
+ ],
15
+ "meta": {
16
+ "tags": [
17
+ "image",
18
+ "crop",
19
+ "zoom"
20
+ ],
21
+ "colSpan": 2
22
+ }
23
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-559",
4
+ "type": "registry:component",
5
+ "registryDependencies": [
6
+ "https://loveui.dev/building-blocks/r/cropper.json"
7
+ ],
8
+ "files": [
9
+ {
10
+ "path": "registry/default/components/comp-559.tsx",
11
+ "content": "import {\n Cropper,\n CropperCropArea,\n CropperDescription,\n CropperImage,\n} from \"@/registry/building-blocks/default/ui/cropper\"\n\nexport default function Component() {\n return (\n <div className=\"flex flex-col items-center gap-2\">\n <Cropper\n className=\"aspect-square size-80\"\n cropPadding={0}\n image=\"https://raw.githubusercontent.com/origin-space/origin-images/refs/heads/main/cropper-05_evczb3.jpg\"\n >\n <CropperDescription />\n <CropperImage />\n <CropperCropArea className=\"border-4 border-blue-500\" />\n </Cropper>\n\n <p\n aria-live=\"polite\"\n role=\"region\"\n className=\"mt-2 text-xs text-muted-foreground\"\n >\n Cropper with full size crop area ∙{\" \"}\n <a\n href=\"https://github.com/origin-space/image-cropper\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n >\n API\n </a>\n </p>\n </div>\n )\n}\n",
12
+ "type": "registry:component"
13
+ }
14
+ ],
15
+ "meta": {
16
+ "tags": [
17
+ "image",
18
+ "crop",
19
+ "zoom"
20
+ ],
21
+ "colSpan": 2
22
+ }
23
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-56",
4
+ "type": "registry:component",
5
+ "dependencies": [
6
+ "emblor"
7
+ ],
8
+ "registryDependencies": [
9
+ "https://loveui.dev/building-blocks/r/label.json"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "registry/default/components/comp-56.tsx",
14
+ "content": "\"use client\"\n\nimport { useId, useState } from \"react\"\nimport { Tag, TagInput } from \"emblor\"\n\nimport { Label } from \"@/registry/building-blocks/default/ui/label\"\n\nconst tags = [\n {\n id: \"1\",\n text: \"Sport\",\n },\n {\n id: \"2\",\n text: \"Coding\",\n },\n {\n id: \"3\",\n text: \"Travel\",\n },\n]\n\nexport default function Component() {\n const id = useId()\n const [exampleTags, setExampleTags] = useState<Tag[]>(tags)\n const [activeTagIndex, setActiveTagIndex] = useState<number | null>(null)\n\n return (\n <div className=\"*:not-first:mt-2\">\n <Label htmlFor={id}>Input with tags</Label>\n <TagInput\n id={id}\n tags={exampleTags}\n setTags={(newTags) => {\n setExampleTags(newTags)\n }}\n placeholder=\"Add a tag\"\n styleClasses={{\n tagList: {\n container: \"gap-1\",\n },\n input:\n \"rounded-md transition-[color,box-shadow] placeholder:text-muted-foreground/70 focus-visible:border-ring outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n tag: {\n body: \"relative h-7 bg-background border border-input hover:bg-background rounded-md font-medium text-xs ps-2 pe-7\",\n closeButton:\n \"absolute -inset-y-px -end-px p-0 rounded-s-none rounded-e-md flex size-7 transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] text-muted-foreground/80 hover:text-foreground\",\n },\n }}\n activeTagIndex={activeTagIndex}\n setActiveTagIndex={setActiveTagIndex}\n inlineTags={false}\n inputFieldPosition=\"top\"\n />\n <p\n className=\"mt-2 text-xs text-muted-foreground\"\n role=\"region\"\n aria-live=\"polite\"\n >\n Built with{\" \"}\n <a\n className=\"underline hover:text-foreground\"\n href=\"https://github.com/JaleelB/emblor\"\n target=\"_blank\"\n rel=\"noopener nofollow\"\n >\n emblor\n </a>\n </p>\n </div>\n )\n}\n",
15
+ "type": "registry:component"
16
+ }
17
+ ],
18
+ "meta": {
19
+ "tags": [
20
+ "input",
21
+ "label",
22
+ "tag",
23
+ "emblor"
24
+ ]
25
+ }
26
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-560",
4
+ "type": "registry:component",
5
+ "registryDependencies": [
6
+ "https://loveui.dev/building-blocks/r/cropper.json"
7
+ ],
8
+ "files": [
9
+ {
10
+ "path": "registry/default/components/comp-560.tsx",
11
+ "content": "import {\n Cropper,\n CropperCropArea,\n CropperDescription,\n CropperImage,\n} from \"@/registry/building-blocks/default/ui/cropper\"\n\nexport default function Component() {\n return (\n <div className=\"flex flex-col items-center gap-2\">\n <Cropper\n className=\"h-80\"\n image=\"https://raw.githubusercontent.com/origin-space/origin-images/refs/heads/main/cropper-06_dduwky.jpg\"\n >\n <CropperDescription />\n <CropperImage />\n <CropperCropArea className=\"rounded-full\" />\n </Cropper>\n\n <p\n aria-live=\"polite\"\n role=\"region\"\n className=\"mt-2 text-xs text-muted-foreground\"\n >\n Cropper with full-rounded mask ∙{\" \"}\n <a\n href=\"https://github.com/origin-space/image-cropper\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n >\n API\n </a>\n </p>\n </div>\n )\n}\n",
12
+ "type": "registry:component"
13
+ }
14
+ ],
15
+ "meta": {
16
+ "tags": [
17
+ "image",
18
+ "crop",
19
+ "zoom"
20
+ ],
21
+ "colSpan": 2
22
+ }
23
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-561",
4
+ "type": "registry:component",
5
+ "registryDependencies": [
6
+ "https://loveui.dev/building-blocks/r/cropper.json",
7
+ "https://loveui.dev/building-blocks/r/select.json"
8
+ ],
9
+ "files": [
10
+ {
11
+ "path": "registry/default/components/comp-561.tsx",
12
+ "content": "\"use client\"\n\nimport { useState } from \"react\"\n\nimport {\n Cropper,\n CropperCropArea,\n CropperDescription,\n CropperImage,\n} from \"@/registry/building-blocks/default/ui/cropper\"\nimport { Slider } from \"@/registry/building-blocks/default/ui/slider\"\n\nexport default function Component() {\n const [zoom, setZoom] = useState(1)\n\n return (\n <div className=\"flex flex-col items-center gap-2\">\n <div className=\"flex w-full flex-col gap-4\">\n <Cropper\n className=\"h-80\"\n image=\"https://raw.githubusercontent.com/origin-space/origin-images/refs/heads/main/cropper-07_scsejv.jpg\"\n zoom={zoom}\n onZoomChange={setZoom}\n >\n <CropperDescription />\n <CropperImage />\n <CropperCropArea />\n </Cropper>\n <div className=\"mx-auto flex w-full max-w-80 items-center gap-1\">\n <Slider\n defaultValue={[1]}\n value={[zoom]}\n min={1}\n max={3}\n step={0.1}\n onValueChange={(value) => setZoom(value[0])}\n aria-label=\"Zoom slider\"\n />\n <output className=\"block w-10 shrink-0 text-right text-sm font-medium tabular-nums\">\n {parseFloat(zoom.toFixed(1))}x\n </output>\n </div>\n </div>\n\n <p\n aria-live=\"polite\"\n role=\"region\"\n className=\"mt-2 text-xs text-muted-foreground\"\n >\n Cropper with zoom slider ∙{\" \"}\n <a\n href=\"https://github.com/origin-space/image-cropper\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n >\n API\n </a>\n </p>\n </div>\n )\n}\n",
13
+ "type": "registry:component"
14
+ }
15
+ ],
16
+ "meta": {
17
+ "tags": [
18
+ "image",
19
+ "crop",
20
+ "zoom",
21
+ "slider"
22
+ ],
23
+ "colSpan": 2
24
+ }
25
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-562",
4
+ "type": "registry:component",
5
+ "registryDependencies": [
6
+ "https://loveui.dev/building-blocks/r/cropper.json"
7
+ ],
8
+ "files": [
9
+ {
10
+ "path": "registry/default/components/comp-562.tsx",
11
+ "content": "import {\n Cropper,\n CropperCropArea,\n CropperDescription,\n CropperImage,\n} from \"@/registry/building-blocks/default/ui/cropper\"\n\nexport default function Component() {\n return (\n <div className=\"flex flex-col items-center gap-2\">\n <Cropper\n className=\"h-80\"\n minZoom={2}\n maxZoom={10}\n image=\"https://raw.githubusercontent.com/origin-space/origin-images/refs/heads/main/cropper-08_wneftq.jpg\"\n >\n <CropperDescription />\n <CropperImage />\n <CropperCropArea />\n </Cropper>\n\n <p\n aria-live=\"polite\"\n role=\"region\"\n className=\"mt-2 text-xs text-muted-foreground\"\n >\n Cropper with custom zoom limits ∙{\" \"}\n <a\n href=\"https://github.com/origin-space/image-cropper\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n >\n API\n </a>\n </p>\n </div>\n )\n}\n",
12
+ "type": "registry:component"
13
+ }
14
+ ],
15
+ "meta": {
16
+ "tags": [
17
+ "image",
18
+ "crop",
19
+ "zoom"
20
+ ],
21
+ "colSpan": 2
22
+ }
23
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-563",
4
+ "type": "registry:component",
5
+ "registryDependencies": [
6
+ "https://loveui.dev/building-blocks/r/cropper.json"
7
+ ],
8
+ "files": [
9
+ {
10
+ "path": "registry/default/components/comp-563.tsx",
11
+ "content": "\"use client\"\n\nimport React from \"react\"\n\nimport {\n Cropper,\n CropperCropArea,\n CropperDescription,\n CropperImage,\n} from \"@/registry/building-blocks/default/ui/cropper\"\n\ntype Area = { x: number; y: number; width: number; height: number }\n\nexport default function Component() {\n const [cropData, setCropData] = React.useState<Area | null>(null)\n\n return (\n <div className=\"flex flex-col items-center gap-2\">\n <div className=\"flex w-full flex-col gap-4\">\n <Cropper\n className=\"h-80\"\n cropPadding={20}\n image=\"https://raw.githubusercontent.com/origin-space/origin-images/refs/heads/main/cropper-09_qskkln.jpg\"\n onCropChange={setCropData}\n >\n <CropperDescription />\n <CropperImage />\n <CropperCropArea />\n </Cropper>\n\n {cropData && (\n <pre className=\"overflow-auto rounded-md border bg-muted px-4 py-3 font-mono text-xs text-foreground/80\">\n <code>{JSON.stringify(cropData, null, 2)}</code>\n </pre>\n )}\n </div>\n\n <p\n aria-live=\"polite\"\n role=\"region\"\n className=\"mt-2 text-xs text-muted-foreground\"\n >\n Cropper with crop data output ∙{\" \"}\n <a\n href=\"https://github.com/origin-space/image-cropper\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n API\n </a>\n </p>\n </div>\n )\n}\n",
12
+ "type": "registry:component"
13
+ }
14
+ ],
15
+ "meta": {
16
+ "tags": [
17
+ "image",
18
+ "crop",
19
+ "zoom"
20
+ ],
21
+ "colSpan": 2
22
+ }
23
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-564",
4
+ "type": "registry:component",
5
+ "registryDependencies": [
6
+ "https://loveui.dev/building-blocks/r/button.json",
7
+ "https://loveui.dev/building-blocks/r/cropper.json"
8
+ ],
9
+ "files": [
10
+ {
11
+ "path": "registry/default/components/comp-564.tsx",
12
+ "content": "\"use client\"\n\nimport { useCallback, useEffect, useState } from \"react\"\n\nimport { Button } from \"@/registry/building-blocks/default/ui/button\"\nimport {\n Cropper,\n CropperCropArea,\n CropperDescription,\n CropperImage,\n} from \"@/registry/building-blocks/default/ui/cropper\"\n\n// Define type for pixel crop area\ntype Area = { x: number; y: number; width: number; height: number }\n\n// --- Start: Copied Helper Functions ---\nconst createImage = (url: string): Promise<HTMLImageElement> =>\n new Promise((resolve, reject) => {\n const image = new Image()\n image.addEventListener(\"load\", () => resolve(image))\n image.addEventListener(\"error\", (error) => reject(error))\n image.setAttribute(\"crossOrigin\", \"anonymous\") // Needed for canvas Tainted check\n image.src = url\n })\n\nasync function getCroppedImg(\n imageSrc: string,\n pixelCrop: Area,\n outputWidth: number = pixelCrop.width, // Optional: specify output size\n outputHeight: number = pixelCrop.height\n): Promise<Blob | null> {\n try {\n const image = await createImage(imageSrc)\n const canvas = document.createElement(\"canvas\")\n const ctx = canvas.getContext(\"2d\")\n\n if (!ctx) {\n return null\n }\n\n // Set canvas size to desired output size\n canvas.width = outputWidth\n canvas.height = outputHeight\n\n // Draw the cropped image onto the canvas\n ctx.drawImage(\n image,\n pixelCrop.x,\n pixelCrop.y,\n pixelCrop.width,\n pixelCrop.height,\n 0,\n 0,\n outputWidth, // Draw onto the output size\n outputHeight\n )\n\n // Convert canvas to blob\n return new Promise((resolve) => {\n canvas.toBlob((blob) => {\n resolve(blob)\n }, \"image/jpeg\") // Specify format and quality if needed\n })\n } catch (error) {\n console.error(\"Error in getCroppedImg:\", error)\n return null\n }\n}\n// --- End: Copied Helper Functions ---\n\nconst ORIGINAL_IMAGE_URL =\n \"https://raw.githubusercontent.com/origin-space/origin-images/refs/heads/main/cropper-10_k24zxk.jpg\"\n\nexport default function Component() {\n const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null)\n const [croppedImageUrl, setCroppedImageUrl] = useState<string | null>(null)\n\n // Callback to update crop area state\n const handleCropChange = useCallback((pixels: Area | null) => {\n setCroppedAreaPixels(pixels)\n }, [])\n\n // Function to handle the crop button click\n const handleCrop = async () => {\n if (!croppedAreaPixels) {\n console.error(\"No crop area selected.\")\n return\n }\n\n try {\n const croppedBlob = await getCroppedImg(\n ORIGINAL_IMAGE_URL,\n croppedAreaPixels\n )\n if (!croppedBlob) {\n throw new Error(\"Failed to generate cropped image blob.\")\n }\n\n // Create a new object URL\n const newCroppedUrl = URL.createObjectURL(croppedBlob)\n\n // Revoke the old URL if it exists\n if (croppedImageUrl) {\n URL.revokeObjectURL(croppedImageUrl)\n }\n\n // Set the new URL\n setCroppedImageUrl(newCroppedUrl)\n } catch (error) {\n console.error(\"Error during cropping:\", error)\n // Optionally: Clear the old image URL on error\n if (croppedImageUrl) {\n URL.revokeObjectURL(croppedImageUrl)\n }\n setCroppedImageUrl(null)\n }\n }\n\n // Effect for cleaning up the object URL\n useEffect(() => {\n // This is the cleanup function that runs when the component unmounts\n // or when croppedImageUrl changes before the next effect runs.\n const currentUrl = croppedImageUrl\n return () => {\n if (currentUrl && currentUrl.startsWith(\"blob:\")) {\n URL.revokeObjectURL(currentUrl)\n console.log(\"Revoked URL:\", currentUrl) // Optional: for debugging\n }\n }\n }, [croppedImageUrl]) // Dependency array ensures cleanup runs when URL changes\n\n return (\n <div className=\"flex flex-col items-center gap-2\">\n <div className=\"flex w-full flex-col gap-4 md:flex-row\">\n <Cropper\n className=\"h-64 md:flex-1\"\n image={ORIGINAL_IMAGE_URL}\n onCropChange={handleCropChange}\n >\n <CropperDescription />\n <CropperImage />\n <CropperCropArea />\n </Cropper>\n <div className=\"flex w-26 flex-col gap-4\">\n <Button onClick={handleCrop} disabled={!croppedAreaPixels}>\n Crop preview\n </Button>\n {/* Display Area */}\n <div className=\"aspect-square w-full shrink-0 overflow-hidden rounded-lg border\">\n {croppedImageUrl ? (\n <img\n src={croppedImageUrl}\n alt=\"Cropped result\"\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <div className=\"flex h-full w-full items-center justify-center bg-muted p-2 text-center text-xs text-muted-foreground/80\">\n Image preview\n </div>\n )}\n </div>\n </div>\n </div>\n\n <p\n aria-live=\"polite\"\n role=\"region\"\n className=\"mt-2 text-xs text-muted-foreground\"\n >\n Cropper with image preview ∙{\" \"}\n <a\n href=\"https://github.com/origin-space/image-cropper\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n API\n </a>\n </p>\n </div>\n )\n}\n",
13
+ "type": "registry:component"
14
+ }
15
+ ],
16
+ "meta": {
17
+ "tags": [
18
+ "image",
19
+ "crop",
20
+ "zoom"
21
+ ],
22
+ "colSpan": 2
23
+ }
24
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-565",
4
+ "type": "registry:component",
5
+ "registryDependencies": [
6
+ "https://loveui.dev/building-blocks/r/tree.json"
7
+ ],
8
+ "files": [
9
+ {
10
+ "path": "registry/default/components/comp-565.tsx",
11
+ "content": "\"use client\"\n\nimport React from \"react\"\nimport { hotkeysCoreFeature, syncDataLoaderFeature } from \"@headless-tree/core\"\nimport { useTree } from \"@headless-tree/react\"\n\nimport { Tree, TreeItem, TreeItemLabel } from \"@/registry/building-blocks/default/ui/tree\"\n\ninterface Item {\n name: string\n children?: string[]\n}\n\nconst items: Record<string, Item> = {\n company: {\n name: \"Company\",\n children: [\"engineering\", \"marketing\", \"operations\"],\n },\n engineering: {\n name: \"Engineering\",\n children: [\"frontend\", \"backend\", \"platform-team\"],\n },\n frontend: { name: \"Frontend\", children: [\"design-system\", \"web-platform\"] },\n \"design-system\": {\n name: \"Design System\",\n children: [\"components\", \"tokens\", \"guidelines\"],\n },\n components: { name: \"Components\" },\n tokens: { name: \"Tokens\" },\n guidelines: { name: \"Guidelines\" },\n \"web-platform\": { name: \"Web Platform\" },\n backend: { name: \"Backend\", children: [\"apis\", \"infrastructure\"] },\n apis: { name: \"APIs\" },\n infrastructure: { name: \"Infrastructure\" },\n \"platform-team\": { name: \"Platform Team\" },\n marketing: { name: \"Marketing\", children: [\"content\", \"seo\"] },\n content: { name: \"Content\" },\n seo: { name: \"SEO\" },\n operations: { name: \"Operations\", children: [\"hr\", \"finance\"] },\n hr: { name: \"HR\" },\n finance: { name: \"Finance\" },\n}\n\nconst indent = 20\n\nexport default function Component() {\n const tree = useTree<Item>({\n initialState: {\n expandedItems: [\"engineering\", \"frontend\", \"design-system\"],\n },\n indent,\n rootItemId: \"company\",\n getItemName: (item) => item.getItemData().name,\n isItemFolder: (item) => (item.getItemData()?.children?.length ?? 0) > 0,\n dataLoader: {\n getItem: (itemId) => items[itemId],\n getChildren: (itemId) => items[itemId].children ?? [],\n },\n features: [syncDataLoaderFeature, hotkeysCoreFeature],\n })\n\n return (\n <div className=\"flex h-full flex-col gap-2 *:first:grow\">\n <Tree indent={indent} tree={tree}>\n {tree.getItems().map((item) => {\n return (\n <TreeItem key={item.getId()} item={item}>\n <TreeItemLabel />\n </TreeItem>\n )\n })}\n </Tree>\n\n <p\n aria-live=\"polite\"\n role=\"region\"\n className=\"mt-2 text-xs text-muted-foreground\"\n >\n Basic tree with no extra features ∙{\" \"}\n <a\n href=\"https://headless-tree.lukasbach.com\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n API\n </a>\n </p>\n </div>\n )\n}\n",
12
+ "type": "registry:component"
13
+ }
14
+ ],
15
+ "meta": {
16
+ "tags": [
17
+ "tree"
18
+ ]
19
+ }
20
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-566",
4
+ "type": "registry:component",
5
+ "registryDependencies": [
6
+ "https://loveui.dev/building-blocks/r/tree.json"
7
+ ],
8
+ "files": [
9
+ {
10
+ "path": "registry/default/components/comp-566.tsx",
11
+ "content": "\"use client\"\n\nimport React from \"react\"\nimport { hotkeysCoreFeature, syncDataLoaderFeature } from \"@headless-tree/core\"\nimport { useTree } from \"@headless-tree/react\"\n\nimport { Tree, TreeItem, TreeItemLabel } from \"@/registry/building-blocks/default/ui/tree\"\n\ninterface Item {\n name: string\n children?: string[]\n}\n\nconst items: Record<string, Item> = {\n company: {\n name: \"Company\",\n children: [\"engineering\", \"marketing\", \"operations\"],\n },\n engineering: {\n name: \"Engineering\",\n children: [\"frontend\", \"backend\", \"platform-team\"],\n },\n frontend: { name: \"Frontend\", children: [\"design-system\", \"web-platform\"] },\n \"design-system\": {\n name: \"Design System\",\n children: [\"components\", \"tokens\", \"guidelines\"],\n },\n components: { name: \"Components\" },\n tokens: { name: \"Tokens\" },\n guidelines: { name: \"Guidelines\" },\n \"web-platform\": { name: \"Web Platform\" },\n backend: { name: \"Backend\", children: [\"apis\", \"infrastructure\"] },\n apis: { name: \"APIs\" },\n infrastructure: { name: \"Infrastructure\" },\n \"platform-team\": { name: \"Platform Team\" },\n marketing: { name: \"Marketing\", children: [\"content\", \"seo\"] },\n content: { name: \"Content\" },\n seo: { name: \"SEO\" },\n operations: { name: \"Operations\", children: [\"hr\", \"finance\"] },\n hr: { name: \"HR\" },\n finance: { name: \"Finance\" },\n}\n\nconst indent = 20\n\nexport default function Component() {\n const tree = useTree<Item>({\n initialState: {\n expandedItems: [\"engineering\", \"frontend\", \"design-system\"],\n },\n indent,\n rootItemId: \"company\",\n getItemName: (item) => item.getItemData().name,\n isItemFolder: (item) => (item.getItemData()?.children?.length ?? 0) > 0,\n dataLoader: {\n getItem: (itemId) => items[itemId],\n getChildren: (itemId) => items[itemId].children ?? [],\n },\n features: [syncDataLoaderFeature, hotkeysCoreFeature],\n })\n\n return (\n <div className=\"flex h-full flex-col gap-2 *:first:grow\">\n <div>\n <Tree\n className=\"relative before:absolute before:inset-0 before:-ms-1 before:bg-[repeating-linear-gradient(to_right,transparent_0,transparent_calc(var(--tree-indent)-1px),var(--border)_calc(var(--tree-indent)-1px),var(--border)_calc(var(--tree-indent)))]\"\n indent={indent}\n tree={tree}\n >\n {tree.getItems().map((item) => {\n return (\n <TreeItem key={item.getId()} item={item}>\n <TreeItemLabel className=\"relative before:absolute before:inset-x-0 before:-inset-y-0.5 before:-z-10 before:bg-background\" />\n </TreeItem>\n )\n })}\n </Tree>\n </div>\n\n <p\n aria-live=\"polite\"\n role=\"region\"\n className=\"mt-2 text-xs text-muted-foreground\"\n >\n Basic tree with vertical lines ∙{\" \"}\n <a\n href=\"https://headless-tree.lukasbach.com\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n API\n </a>\n </p>\n </div>\n )\n}\n",
12
+ "type": "registry:component"
13
+ }
14
+ ],
15
+ "meta": {
16
+ "tags": [
17
+ "tree"
18
+ ]
19
+ }
20
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-567",
4
+ "type": "registry:component",
5
+ "registryDependencies": [
6
+ "https://loveui.dev/building-blocks/r/tree.json"
7
+ ],
8
+ "files": [
9
+ {
10
+ "path": "registry/default/components/comp-567.tsx",
11
+ "content": "\"use client\"\n\nimport React from \"react\"\nimport { hotkeysCoreFeature, syncDataLoaderFeature } from \"@headless-tree/core\"\nimport { useTree } from \"@headless-tree/react\"\nimport { FileIcon, FolderIcon, FolderOpenIcon } from \"lucide-react\"\n\nimport { Tree, TreeItem, TreeItemLabel } from \"@/registry/building-blocks/default/ui/tree\"\n\ninterface Item {\n name: string\n children?: string[]\n}\n\nconst items: Record<string, Item> = {\n company: {\n name: \"Company\",\n children: [\"engineering\", \"marketing\", \"operations\"],\n },\n engineering: {\n name: \"Engineering\",\n children: [\"frontend\", \"backend\", \"platform-team\"],\n },\n frontend: { name: \"Frontend\", children: [\"design-system\", \"web-platform\"] },\n \"design-system\": {\n name: \"Design System\",\n children: [\"components\", \"tokens\", \"guidelines\"],\n },\n components: { name: \"Components\" },\n tokens: { name: \"Tokens\" },\n guidelines: { name: \"Guidelines\" },\n \"web-platform\": { name: \"Web Platform\" },\n backend: { name: \"Backend\", children: [\"apis\", \"infrastructure\"] },\n apis: { name: \"APIs\" },\n infrastructure: { name: \"Infrastructure\" },\n \"platform-team\": { name: \"Platform Team\" },\n marketing: { name: \"Marketing\", children: [\"content\", \"seo\"] },\n content: { name: \"Content\" },\n seo: { name: \"SEO\" },\n operations: { name: \"Operations\", children: [\"hr\", \"finance\"] },\n hr: { name: \"HR\" },\n finance: { name: \"Finance\" },\n}\n\nconst indent = 20\n\nexport default function Component() {\n const tree = useTree<Item>({\n initialState: {\n expandedItems: [\"engineering\", \"frontend\", \"design-system\"],\n },\n indent,\n rootItemId: \"company\",\n getItemName: (item) => item.getItemData().name,\n isItemFolder: (item) => (item.getItemData()?.children?.length ?? 0) > 0,\n dataLoader: {\n getItem: (itemId) => items[itemId],\n getChildren: (itemId) => items[itemId].children ?? [],\n },\n features: [syncDataLoaderFeature, hotkeysCoreFeature],\n })\n\n return (\n <div className=\"flex h-full flex-col gap-2 *:first:grow\">\n <div>\n <Tree\n className=\"relative before:absolute before:inset-0 before:-ms-1 before:bg-[repeating-linear-gradient(to_right,transparent_0,transparent_calc(var(--tree-indent)-1px),var(--border)_calc(var(--tree-indent)-1px),var(--border)_calc(var(--tree-indent)))]\"\n indent={indent}\n tree={tree}\n >\n {tree.getItems().map((item) => {\n return (\n <TreeItem key={item.getId()} item={item}>\n <TreeItemLabel className=\"relative before:absolute before:inset-x-0 before:-inset-y-0.5 before:-z-10 before:bg-background\">\n <span className=\"flex items-center gap-2\">\n {item.isFolder() ? (\n item.isExpanded() ? (\n <FolderOpenIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n ) : (\n <FolderIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n )\n ) : (\n <FileIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n )}\n {item.getItemName()}\n </span>\n </TreeItemLabel>\n </TreeItem>\n )\n })}\n </Tree>\n </div>\n\n <p\n aria-live=\"polite\"\n role=\"region\"\n className=\"mt-2 text-xs text-muted-foreground\"\n >\n Basic tree with icons ∙{\" \"}\n <a\n href=\"https://headless-tree.lukasbach.com\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n API\n </a>\n </p>\n </div>\n )\n}\n",
12
+ "type": "registry:component"
13
+ }
14
+ ],
15
+ "meta": {
16
+ "tags": [
17
+ "tree"
18
+ ]
19
+ }
20
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "comp-568",
4
+ "type": "registry:component",
5
+ "registryDependencies": [
6
+ "https://loveui.dev/building-blocks/r/tree.json"
7
+ ],
8
+ "files": [
9
+ {
10
+ "path": "registry/default/components/comp-568.tsx",
11
+ "content": "\"use client\"\n\nimport React from \"react\"\nimport { hotkeysCoreFeature, syncDataLoaderFeature } from \"@headless-tree/core\"\nimport { useTree } from \"@headless-tree/react\"\nimport { FileIcon, FolderIcon, FolderOpenIcon } from \"lucide-react\"\n\nimport { Tree, TreeItem, TreeItemLabel } from \"@/registry/building-blocks/default/ui/tree\"\n\ninterface Item {\n name: string\n children?: string[]\n}\n\nconst items: Record<string, Item> = {\n company: {\n name: \"Company\",\n children: [\"engineering\", \"marketing\", \"operations\"],\n },\n engineering: {\n name: \"Engineering\",\n children: [\"frontend\", \"backend\", \"platform-team\"],\n },\n frontend: { name: \"Frontend\", children: [\"design-system\", \"web-platform\"] },\n \"design-system\": {\n name: \"Design System\",\n children: [\"components\", \"tokens\", \"guidelines\"],\n },\n components: { name: \"Components\" },\n tokens: { name: \"Tokens\" },\n guidelines: { name: \"Guidelines\" },\n \"web-platform\": { name: \"Web Platform\" },\n backend: { name: \"Backend\", children: [\"apis\", \"infrastructure\"] },\n apis: { name: \"APIs\" },\n infrastructure: { name: \"Infrastructure\" },\n \"platform-team\": { name: \"Platform Team\" },\n marketing: { name: \"Marketing\", children: [\"content\", \"seo\"] },\n content: { name: \"Content\" },\n seo: { name: \"SEO\" },\n operations: { name: \"Operations\", children: [\"hr\", \"finance\"] },\n hr: { name: \"HR\" },\n finance: { name: \"Finance\" },\n}\n\nconst indent = 20\n\nexport default function Component() {\n const tree = useTree<Item>({\n initialState: {\n expandedItems: [\"engineering\", \"frontend\", \"design-system\"],\n },\n indent,\n rootItemId: \"company\",\n getItemName: (item) => item.getItemData().name,\n isItemFolder: (item) => (item.getItemData()?.children?.length ?? 0) > 0,\n dataLoader: {\n getItem: (itemId) => items[itemId],\n getChildren: (itemId) => items[itemId].children ?? [],\n },\n features: [syncDataLoaderFeature, hotkeysCoreFeature],\n })\n\n return (\n <div className=\"flex h-full flex-col gap-2 *:first:grow\">\n <div>\n <Tree\n className=\"relative before:absolute before:inset-0 before:-ms-1 before:bg-[repeating-linear-gradient(to_right,transparent_0,transparent_calc(var(--tree-indent)-1px),var(--border)_calc(var(--tree-indent)-1px),var(--border)_calc(var(--tree-indent)))]\"\n indent={indent}\n tree={tree}\n >\n {tree.getItems().map((item) => {\n return (\n <TreeItem key={item.getId()} item={item}>\n <TreeItemLabel className=\"relative before:absolute before:inset-x-0 before:-inset-y-0.5 before:-z-10 before:bg-background\">\n <span className=\"-order-1 flex flex-1 items-center gap-2\">\n {item.isFolder() ? (\n item.isExpanded() ? (\n <FolderOpenIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n ) : (\n <FolderIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n )\n ) : (\n <FileIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n )}\n {item.getItemName()}\n </span>\n </TreeItemLabel>\n </TreeItem>\n )\n })}\n </Tree>\n </div>\n\n <p\n aria-live=\"polite\"\n role=\"region\"\n className=\"mt-2 text-xs text-muted-foreground\"\n >\n Basic tree with caret icon on the right ∙{\" \"}\n <a\n href=\"https://headless-tree.lukasbach.com\"\n className=\"underline hover:text-foreground\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n API\n </a>\n </p>\n </div>\n )\n}\n",
12
+ "type": "registry:component"
13
+ }
14
+ ],
15
+ "meta": {
16
+ "tags": [
17
+ "tree"
18
+ ]
19
+ }
20
+ }