shadcn_view_component 0.3.3

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 (507) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +255 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +604 -0
  5. data/app/assets/stylesheets/shadcn-themes.css +1075 -0
  6. data/app/assets/stylesheets/shadcn.css +853 -0
  7. data/app/components/shadcn/accordion/component.rb +44 -0
  8. data/app/components/shadcn/accordion/content/component.rb +44 -0
  9. data/app/components/shadcn/accordion/item/component.rb +41 -0
  10. data/app/components/shadcn/accordion/preview.rb +16 -0
  11. data/app/components/shadcn/accordion/previews/default.html.erb +10 -0
  12. data/app/components/shadcn/accordion/previews/multiple.html.erb +13 -0
  13. data/app/components/shadcn/accordion/trigger/component.rb +52 -0
  14. data/app/components/shadcn/alert/component.rb +52 -0
  15. data/app/components/shadcn/alert/preview.rb +11 -0
  16. data/app/components/shadcn/alert/previews/default.html.erb +8 -0
  17. data/app/components/shadcn/alert.rb +17 -0
  18. data/app/components/shadcn/alert_dialog/action/component.rb +35 -0
  19. data/app/components/shadcn/alert_dialog/cancel/component.rb +16 -0
  20. data/app/components/shadcn/alert_dialog/component.rb +20 -0
  21. data/app/components/shadcn/alert_dialog/content/component.rb +55 -0
  22. data/app/components/shadcn/alert_dialog/header/component.rb +33 -0
  23. data/app/components/shadcn/alert_dialog/preview.rb +16 -0
  24. data/app/components/shadcn/alert_dialog/previews/default.html.erb +13 -0
  25. data/app/components/shadcn/alert_dialog/previews/small_with_media.html.erb +38 -0
  26. data/app/components/shadcn/alert_dialog.rb +32 -0
  27. data/app/components/shadcn/application_view_component.rb +225 -0
  28. data/app/components/shadcn/application_view_component_preview.rb +8 -0
  29. data/app/components/shadcn/aspect_ratio/component.rb +34 -0
  30. data/app/components/shadcn/aspect_ratio/preview.rb +16 -0
  31. data/app/components/shadcn/aspect_ratio/previews/default.html.erb +5 -0
  32. data/app/components/shadcn/aspect_ratio/previews/ratios.html.erb +12 -0
  33. data/app/components/shadcn/attachment/action/component.rb +19 -0
  34. data/app/components/shadcn/attachment/component.rb +76 -0
  35. data/app/components/shadcn/attachment/media/component.rb +56 -0
  36. data/app/components/shadcn/attachment/preview.rb +11 -0
  37. data/app/components/shadcn/attachment/previews/default.html.erb +80 -0
  38. data/app/components/shadcn/attachment/trigger/component.rb +31 -0
  39. data/app/components/shadcn/attachment.rb +42 -0
  40. data/app/components/shadcn/avatar/component.rb +43 -0
  41. data/app/components/shadcn/avatar/fallback/component.rb +23 -0
  42. data/app/components/shadcn/avatar/image/component.rb +20 -0
  43. data/app/components/shadcn/avatar/preview.rb +11 -0
  44. data/app/components/shadcn/avatar/previews/default.html.erb +13 -0
  45. data/app/components/shadcn/avatar.rb +32 -0
  46. data/app/components/shadcn/badge/component.rb +57 -0
  47. data/app/components/shadcn/badge/preview.rb +11 -0
  48. data/app/components/shadcn/badge/previews/default.html.erb +5 -0
  49. data/app/components/shadcn/breadcrumb/component.rb +15 -0
  50. data/app/components/shadcn/breadcrumb/ellipsis/component.rb +28 -0
  51. data/app/components/shadcn/breadcrumb/page/component.rb +25 -0
  52. data/app/components/shadcn/breadcrumb/preview.rb +16 -0
  53. data/app/components/shadcn/breadcrumb/previews/custom_separator.html.erb +17 -0
  54. data/app/components/shadcn/breadcrumb/previews/default.html.erb +15 -0
  55. data/app/components/shadcn/breadcrumb/separator/component.rb +31 -0
  56. data/app/components/shadcn/breadcrumb.rb +18 -0
  57. data/app/components/shadcn/bubble/component.rb +90 -0
  58. data/app/components/shadcn/bubble/preview.rb +16 -0
  59. data/app/components/shadcn/bubble/previews/alignment.html.erb +33 -0
  60. data/app/components/shadcn/bubble/previews/default.html.erb +22 -0
  61. data/app/components/shadcn/bubble/reactions/component.rb +54 -0
  62. data/app/components/shadcn/bubble.rb +24 -0
  63. data/app/components/shadcn/button/component.rb +85 -0
  64. data/app/components/shadcn/button/preview.rb +31 -0
  65. data/app/components/shadcn/button/previews/default.html.erb +8 -0
  66. data/app/components/shadcn/button/previews/sizes.html.erb +5 -0
  67. data/app/components/shadcn/button/previews/variants.html.erb +5 -0
  68. data/app/components/shadcn/button_group/component.rb +53 -0
  69. data/app/components/shadcn/button_group/preview.rb +15 -0
  70. data/app/components/shadcn/button_group/previews/default.html.erb +18 -0
  71. data/app/components/shadcn/button_group/previews/vertical.html.erb +5 -0
  72. data/app/components/shadcn/button_group/separator/component.rb +30 -0
  73. data/app/components/shadcn/button_group/text/component.rb +20 -0
  74. data/app/components/shadcn/calendar/component.rb +558 -0
  75. data/app/components/shadcn/calendar/month.rb +74 -0
  76. data/app/components/shadcn/calendar/preview.rb +64 -0
  77. data/app/components/shadcn/calendar/previews/booked.html.erb +18 -0
  78. data/app/components/shadcn/calendar/previews/custom_cell_size.html.erb +11 -0
  79. data/app/components/shadcn/calendar/previews/date_and_time.html.erb +31 -0
  80. data/app/components/shadcn/calendar/previews/default.html.erb +8 -0
  81. data/app/components/shadcn/calendar/previews/dropdown_caption.html.erb +11 -0
  82. data/app/components/shadcn/calendar/previews/in_a_form.html.erb +8 -0
  83. data/app/components/shadcn/calendar/previews/presets.html.erb +20 -0
  84. data/app/components/shadcn/calendar/previews/range.html.erb +10 -0
  85. data/app/components/shadcn/calendar/previews/week_numbers.html.erb +8 -0
  86. data/app/components/shadcn/card/component.rb +24 -0
  87. data/app/components/shadcn/card/header/component.rb +26 -0
  88. data/app/components/shadcn/card/preview.rb +16 -0
  89. data/app/components/shadcn/card/previews/default.html.erb +18 -0
  90. data/app/components/shadcn/card/previews/image.html.erb +37 -0
  91. data/app/components/shadcn/card.rb +21 -0
  92. data/app/components/shadcn/carousel/component.rb +61 -0
  93. data/app/components/shadcn/carousel/content/component.rb +86 -0
  94. data/app/components/shadcn/carousel/item/component.rb +42 -0
  95. data/app/components/shadcn/carousel/next/component.rb +59 -0
  96. data/app/components/shadcn/carousel/preview.rb +22 -0
  97. data/app/components/shadcn/carousel/previews/default.html.erb +22 -0
  98. data/app/components/shadcn/carousel/previews/sizes.html.erb +42 -0
  99. data/app/components/shadcn/carousel/previews/vertical.html.erb +27 -0
  100. data/app/components/shadcn/carousel/previous/component.rb +59 -0
  101. data/app/components/shadcn/chart/area/component.rb +43 -0
  102. data/app/components/shadcn/chart/bar/component.rb +75 -0
  103. data/app/components/shadcn/chart/cartesian/component.rb +152 -0
  104. data/app/components/shadcn/chart/component.rb +124 -0
  105. data/app/components/shadcn/chart/focusable.rb +38 -0
  106. data/app/components/shadcn/chart/legend/component.rb +55 -0
  107. data/app/components/shadcn/chart/line/component.rb +50 -0
  108. data/app/components/shadcn/chart/pie/component.rb +169 -0
  109. data/app/components/shadcn/chart/plot.rb +147 -0
  110. data/app/components/shadcn/chart/preview.rb +45 -0
  111. data/app/components/shadcn/chart/previews/area.html.erb +31 -0
  112. data/app/components/shadcn/chart/previews/bar.html.erb +32 -0
  113. data/app/components/shadcn/chart/previews/bar_stacked.html.erb +30 -0
  114. data/app/components/shadcn/chart/previews/default.html.erb +30 -0
  115. data/app/components/shadcn/chart/previews/donut.html.erb +16 -0
  116. data/app/components/shadcn/chart/previews/line.html.erb +30 -0
  117. data/app/components/shadcn/chart/previews/percentages.html.erb +17 -0
  118. data/app/components/shadcn/chart/table/component.rb +75 -0
  119. data/app/components/shadcn/chart/tooltip/component.rb +111 -0
  120. data/app/components/shadcn/checkbox/component.rb +109 -0
  121. data/app/components/shadcn/checkbox/preview.rb +16 -0
  122. data/app/components/shadcn/checkbox/previews/default.html.erb +14 -0
  123. data/app/components/shadcn/checkbox/previews/states.html.erb +23 -0
  124. data/app/components/shadcn/collapsible/component.rb +35 -0
  125. data/app/components/shadcn/collapsible/content/component.rb +20 -0
  126. data/app/components/shadcn/collapsible/preview.rb +11 -0
  127. data/app/components/shadcn/collapsible/previews/default.html.erb +9 -0
  128. data/app/components/shadcn/collapsible/trigger/component.rb +23 -0
  129. data/app/components/shadcn/combobox/chip/component.rb +60 -0
  130. data/app/components/shadcn/combobox/chip_input/component.rb +43 -0
  131. data/app/components/shadcn/combobox/chips/component.rb +54 -0
  132. data/app/components/shadcn/combobox/clear/component.rb +32 -0
  133. data/app/components/shadcn/combobox/component.rb +113 -0
  134. data/app/components/shadcn/combobox/content/component.rb +59 -0
  135. data/app/components/shadcn/combobox/empty/component.rb +23 -0
  136. data/app/components/shadcn/combobox/input/component.rb +85 -0
  137. data/app/components/shadcn/combobox/item/component.rb +69 -0
  138. data/app/components/shadcn/combobox/list/component.rb +27 -0
  139. data/app/components/shadcn/combobox/preview.rb +31 -0
  140. data/app/components/shadcn/combobox/previews/default.html.erb +20 -0
  141. data/app/components/shadcn/combobox/previews/grouped.html.erb +25 -0
  142. data/app/components/shadcn/combobox/previews/multiple.html.erb +28 -0
  143. data/app/components/shadcn/combobox/previews/with_clear.html.erb +16 -0
  144. data/app/components/shadcn/combobox/trigger/component.rb +54 -0
  145. data/app/components/shadcn/combobox.rb +16 -0
  146. data/app/components/shadcn/command/component.rb +67 -0
  147. data/app/components/shadcn/command/dialog/component.rb +72 -0
  148. data/app/components/shadcn/command/empty/component.rb +29 -0
  149. data/app/components/shadcn/command/group/component.rb +63 -0
  150. data/app/components/shadcn/command/input/component.rb +66 -0
  151. data/app/components/shadcn/command/item/component.rb +62 -0
  152. data/app/components/shadcn/command/list/component.rb +36 -0
  153. data/app/components/shadcn/command/preview.rb +26 -0
  154. data/app/components/shadcn/command/previews/default.html.erb +41 -0
  155. data/app/components/shadcn/command/previews/dialog.html.erb +33 -0
  156. data/app/components/shadcn/command/previews/ranking.html.erb +16 -0
  157. data/app/components/shadcn/command/separator/component.rb +33 -0
  158. data/app/components/shadcn/command.rb +14 -0
  159. data/app/components/shadcn/concerns/modal_root.rb +43 -0
  160. data/app/components/shadcn/concerns/submits_value.rb +81 -0
  161. data/app/components/shadcn/context_menu/component.rb +45 -0
  162. data/app/components/shadcn/context_menu/content/component.rb +40 -0
  163. data/app/components/shadcn/context_menu/preview.rb +16 -0
  164. data/app/components/shadcn/context_menu/previews/default.html.erb +34 -0
  165. data/app/components/shadcn/context_menu/previews/items.html.erb +35 -0
  166. data/app/components/shadcn/context_menu/sub/component.rb +47 -0
  167. data/app/components/shadcn/context_menu/sub_content/component.rb +31 -0
  168. data/app/components/shadcn/context_menu/trigger/component.rb +50 -0
  169. data/app/components/shadcn/context_menu.rb +43 -0
  170. data/app/components/shadcn/dialog/close/component.rb +20 -0
  171. data/app/components/shadcn/dialog/component.rb +20 -0
  172. data/app/components/shadcn/dialog/content/component.rb +78 -0
  173. data/app/components/shadcn/dialog/header/component.rb +23 -0
  174. data/app/components/shadcn/dialog/overlay/component.rb +28 -0
  175. data/app/components/shadcn/dialog/preview.rb +28 -0
  176. data/app/components/shadcn/dialog/previews/default.html.erb +17 -0
  177. data/app/components/shadcn/dialog/previews/inside_stacking_context.html.erb +25 -0
  178. data/app/components/shadcn/dialog/previews/no_close_button.html.erb +15 -0
  179. data/app/components/shadcn/dialog/previews/scrollable_content.html.erb +29 -0
  180. data/app/components/shadcn/dialog/trigger/component.rb +24 -0
  181. data/app/components/shadcn/dialog.rb +17 -0
  182. data/app/components/shadcn/drawer/component.rb +33 -0
  183. data/app/components/shadcn/drawer/content/component.rb +95 -0
  184. data/app/components/shadcn/drawer/header/component.rb +34 -0
  185. data/app/components/shadcn/drawer/preview.rb +29 -0
  186. data/app/components/shadcn/drawer/previews/default.html.erb +21 -0
  187. data/app/components/shadcn/drawer/previews/scrollable.html.erb +20 -0
  188. data/app/components/shadcn/drawer/previews/scrollable_side.html.erb +21 -0
  189. data/app/components/shadcn/drawer/previews/sides.html.erb +17 -0
  190. data/app/components/shadcn/drawer.rb +27 -0
  191. data/app/components/shadcn/dropdown_menu/checkbox_item/component.rb +61 -0
  192. data/app/components/shadcn/dropdown_menu/component.rb +48 -0
  193. data/app/components/shadcn/dropdown_menu/content/component.rb +49 -0
  194. data/app/components/shadcn/dropdown_menu/group/component.rb +16 -0
  195. data/app/components/shadcn/dropdown_menu/item/component.rb +50 -0
  196. data/app/components/shadcn/dropdown_menu/preview.rb +16 -0
  197. data/app/components/shadcn/dropdown_menu/previews/checkboxes_and_radio.html.erb +34 -0
  198. data/app/components/shadcn/dropdown_menu/previews/default.html.erb +23 -0
  199. data/app/components/shadcn/dropdown_menu/radio_group/component.rb +16 -0
  200. data/app/components/shadcn/dropdown_menu/radio_item/component.rb +63 -0
  201. data/app/components/shadcn/dropdown_menu/sub/component.rb +44 -0
  202. data/app/components/shadcn/dropdown_menu/sub_content/component.rb +45 -0
  203. data/app/components/shadcn/dropdown_menu/sub_trigger/component.rb +58 -0
  204. data/app/components/shadcn/dropdown_menu/trigger/component.rb +25 -0
  205. data/app/components/shadcn/dropdown_menu.rb +18 -0
  206. data/app/components/shadcn/empty/component.rb +17 -0
  207. data/app/components/shadcn/empty/media/component.rb +48 -0
  208. data/app/components/shadcn/empty/preview.rb +16 -0
  209. data/app/components/shadcn/empty/previews/default.html.erb +15 -0
  210. data/app/components/shadcn/empty/previews/media.html.erb +32 -0
  211. data/app/components/shadcn/empty.rb +26 -0
  212. data/app/components/shadcn/field/component.rb +61 -0
  213. data/app/components/shadcn/field/error/component.rb +49 -0
  214. data/app/components/shadcn/field/legend/component.rb +30 -0
  215. data/app/components/shadcn/field/preview.rb +17 -0
  216. data/app/components/shadcn/field/previews/default.html.erb +29 -0
  217. data/app/components/shadcn/field/previews/form_builder.html.erb +38 -0
  218. data/app/components/shadcn/field/separator/component.rb +35 -0
  219. data/app/components/shadcn/field.rb +44 -0
  220. data/app/components/shadcn/hover_card/component.rb +51 -0
  221. data/app/components/shadcn/hover_card/content/component.rb +45 -0
  222. data/app/components/shadcn/hover_card/preview.rb +16 -0
  223. data/app/components/shadcn/hover_card/previews/default.html.erb +36 -0
  224. data/app/components/shadcn/hover_card/previews/sides.html.erb +15 -0
  225. data/app/components/shadcn/hover_card/trigger/component.rb +33 -0
  226. data/app/components/shadcn/icon/component.rb +76 -0
  227. data/app/components/shadcn/icon.rb +43 -0
  228. data/app/components/shadcn/input/component.rb +26 -0
  229. data/app/components/shadcn/input/preview.rb +16 -0
  230. data/app/components/shadcn/input/previews/default.html.erb +5 -0
  231. data/app/components/shadcn/input/previews/states.html.erb +20 -0
  232. data/app/components/shadcn/input_group/addon/component.rb +65 -0
  233. data/app/components/shadcn/input_group/button/component.rb +40 -0
  234. data/app/components/shadcn/input_group/component.rb +43 -0
  235. data/app/components/shadcn/input_group/input/component.rb +25 -0
  236. data/app/components/shadcn/input_group/preview.rb +17 -0
  237. data/app/components/shadcn/input_group/previews/addons.html.erb +53 -0
  238. data/app/components/shadcn/input_group/previews/default.html.erb +25 -0
  239. data/app/components/shadcn/input_group/text/component.rb +20 -0
  240. data/app/components/shadcn/input_group/textarea/component.rb +21 -0
  241. data/app/components/shadcn/input_otp/component.rb +95 -0
  242. data/app/components/shadcn/input_otp/preview.rb +17 -0
  243. data/app/components/shadcn/input_otp/previews/default.html.erb +19 -0
  244. data/app/components/shadcn/input_otp/previews/variations.html.erb +33 -0
  245. data/app/components/shadcn/input_otp/separator/component.rb +21 -0
  246. data/app/components/shadcn/input_otp/slot/component.rb +60 -0
  247. data/app/components/shadcn/input_otp.rb +11 -0
  248. data/app/components/shadcn/item/component.rb +56 -0
  249. data/app/components/shadcn/item/group/component.rb +53 -0
  250. data/app/components/shadcn/item/media/component.rb +53 -0
  251. data/app/components/shadcn/item/preview.rb +20 -0
  252. data/app/components/shadcn/item/previews/default.html.erb +32 -0
  253. data/app/components/shadcn/item/previews/link.html.erb +39 -0
  254. data/app/components/shadcn/item/previews/outline.html.erb +8 -0
  255. data/app/components/shadcn/item/separator/component.rb +24 -0
  256. data/app/components/shadcn/item.rb +34 -0
  257. data/app/components/shadcn/kbd/component.rb +23 -0
  258. data/app/components/shadcn/kbd/group/component.rb +17 -0
  259. data/app/components/shadcn/kbd/preview.rb +11 -0
  260. data/app/components/shadcn/kbd/previews/default.html.erb +4 -0
  261. data/app/components/shadcn/label/component.rb +20 -0
  262. data/app/components/shadcn/label/preview.rb +11 -0
  263. data/app/components/shadcn/label/previews/default.html.erb +4 -0
  264. data/app/components/shadcn/marker/component.rb +46 -0
  265. data/app/components/shadcn/marker/icon/component.rb +23 -0
  266. data/app/components/shadcn/marker/preview.rb +17 -0
  267. data/app/components/shadcn/marker/previews/default.html.erb +18 -0
  268. data/app/components/shadcn/marker/previews/status.html.erb +32 -0
  269. data/app/components/shadcn/marker.rb +17 -0
  270. data/app/components/shadcn/menubar/component.rb +36 -0
  271. data/app/components/shadcn/menubar/content/component.rb +49 -0
  272. data/app/components/shadcn/menubar/menu/component.rb +39 -0
  273. data/app/components/shadcn/menubar/preview.rb +16 -0
  274. data/app/components/shadcn/menubar/previews/default.html.erb +52 -0
  275. data/app/components/shadcn/menubar/previews/items.html.erb +38 -0
  276. data/app/components/shadcn/menubar/sub/component.rb +43 -0
  277. data/app/components/shadcn/menubar/sub_content/component.rb +31 -0
  278. data/app/components/shadcn/menubar/sub_trigger/component.rb +31 -0
  279. data/app/components/shadcn/menubar/trigger/component.rb +41 -0
  280. data/app/components/shadcn/menubar.rb +47 -0
  281. data/app/components/shadcn/message/component.rb +33 -0
  282. data/app/components/shadcn/message/preview.rb +16 -0
  283. data/app/components/shadcn/message/previews/actions.html.erb +51 -0
  284. data/app/components/shadcn/message/previews/default.html.erb +35 -0
  285. data/app/components/shadcn/message.rb +35 -0
  286. data/app/components/shadcn/message_scroller/button/component.rb +78 -0
  287. data/app/components/shadcn/message_scroller/component.rb +59 -0
  288. data/app/components/shadcn/message_scroller/content/component.rb +50 -0
  289. data/app/components/shadcn/message_scroller/item/component.rb +45 -0
  290. data/app/components/shadcn/message_scroller/preview.rb +11 -0
  291. data/app/components/shadcn/message_scroller/previews/default.html.erb +72 -0
  292. data/app/components/shadcn/message_scroller/viewport/component.rb +41 -0
  293. data/app/components/shadcn/mode_switcher/component.rb +67 -0
  294. data/app/components/shadcn/mode_switcher/preview.rb +11 -0
  295. data/app/components/shadcn/mode_switcher/previews/default.html.erb +5 -0
  296. data/app/components/shadcn/mode_toggle/component.rb +75 -0
  297. data/app/components/shadcn/mode_toggle/preview.rb +11 -0
  298. data/app/components/shadcn/mode_toggle/previews/default.html.erb +20 -0
  299. data/app/components/shadcn/native_select/component.rb +59 -0
  300. data/app/components/shadcn/native_select/preview.rb +16 -0
  301. data/app/components/shadcn/native_select/previews/default.html.erb +9 -0
  302. data/app/components/shadcn/native_select/previews/groups.html.erb +26 -0
  303. data/app/components/shadcn/native_select.rb +16 -0
  304. data/app/components/shadcn/navigation_menu/component.rb +51 -0
  305. data/app/components/shadcn/navigation_menu/content/component.rb +58 -0
  306. data/app/components/shadcn/navigation_menu/preview.rb +11 -0
  307. data/app/components/shadcn/navigation_menu/previews/default.html.erb +49 -0
  308. data/app/components/shadcn/navigation_menu/trigger/component.rb +59 -0
  309. data/app/components/shadcn/navigation_menu.rb +28 -0
  310. data/app/components/shadcn/pagination/component.rb +19 -0
  311. data/app/components/shadcn/pagination/ellipsis/component.rb +28 -0
  312. data/app/components/shadcn/pagination/link/component.rb +37 -0
  313. data/app/components/shadcn/pagination/next/component.rb +31 -0
  314. data/app/components/shadcn/pagination/preview.rb +16 -0
  315. data/app/components/shadcn/pagination/previews/default.html.erb +18 -0
  316. data/app/components/shadcn/pagination/previews/simple.html.erb +39 -0
  317. data/app/components/shadcn/pagination/previous/component.rb +31 -0
  318. data/app/components/shadcn/pagination.rb +14 -0
  319. data/app/components/shadcn/parts.rb +38 -0
  320. data/app/components/shadcn/popover/anchor/component.rb +17 -0
  321. data/app/components/shadcn/popover/component.rb +42 -0
  322. data/app/components/shadcn/popover/content/component.rb +40 -0
  323. data/app/components/shadcn/popover/preview.rb +28 -0
  324. data/app/components/shadcn/popover/previews/align.html.erb +13 -0
  325. data/app/components/shadcn/popover/previews/default.html.erb +9 -0
  326. data/app/components/shadcn/popover/previews/inside_containing_block.html.erb +41 -0
  327. data/app/components/shadcn/popover/previews/inside_stacking_context.html.erb +30 -0
  328. data/app/components/shadcn/popover/trigger/component.rb +24 -0
  329. data/app/components/shadcn/popover.rb +16 -0
  330. data/app/components/shadcn/progress/component.rb +62 -0
  331. data/app/components/shadcn/progress/preview.rb +11 -0
  332. data/app/components/shadcn/progress/previews/default.html.erb +5 -0
  333. data/app/components/shadcn/radio_group/component.rb +51 -0
  334. data/app/components/shadcn/radio_group/item/component.rb +74 -0
  335. data/app/components/shadcn/radio_group/preview.rb +16 -0
  336. data/app/components/shadcn/radio_group/previews/default.html.erb +15 -0
  337. data/app/components/shadcn/radio_group/previews/states.html.erb +41 -0
  338. data/app/components/shadcn/resizable/component.rb +59 -0
  339. data/app/components/shadcn/resizable/handle/component.rb +70 -0
  340. data/app/components/shadcn/resizable/panel/component.rb +53 -0
  341. data/app/components/shadcn/resizable/preview.rb +23 -0
  342. data/app/components/shadcn/resizable/previews/default.html.erb +25 -0
  343. data/app/components/shadcn/resizable/previews/vertical.html.erb +19 -0
  344. data/app/components/shadcn/resizable/previews/with_handle.html.erb +17 -0
  345. data/app/components/shadcn/scroll_area/component.rb +84 -0
  346. data/app/components/shadcn/scroll_area/preview.rb +11 -0
  347. data/app/components/shadcn/scroll_area/previews/default.html.erb +36 -0
  348. data/app/components/shadcn/scroll_area/scrollbar/component.rb +72 -0
  349. data/app/components/shadcn/scroll_area/viewport/component.rb +70 -0
  350. data/app/components/shadcn/scroll_area.rb +14 -0
  351. data/app/components/shadcn/select/component.rb +67 -0
  352. data/app/components/shadcn/select/content/component.rb +157 -0
  353. data/app/components/shadcn/select/empty/component.rb +38 -0
  354. data/app/components/shadcn/select/group/component.rb +16 -0
  355. data/app/components/shadcn/select/item/component.rb +71 -0
  356. data/app/components/shadcn/select/list/component.rb +41 -0
  357. data/app/components/shadcn/select/preview.rb +24 -0
  358. data/app/components/shadcn/select/previews/default.html.erb +17 -0
  359. data/app/components/shadcn/select/previews/scrollable.html.erb +19 -0
  360. data/app/components/shadcn/select/previews/searchable.html.erb +18 -0
  361. data/app/components/shadcn/select/previews/states.html.erb +25 -0
  362. data/app/components/shadcn/select/search/component.rb +54 -0
  363. data/app/components/shadcn/select/trigger/component.rb +78 -0
  364. data/app/components/shadcn/select/value/component.rb +28 -0
  365. data/app/components/shadcn/select.rb +15 -0
  366. data/app/components/shadcn/separator/component.rb +41 -0
  367. data/app/components/shadcn/separator/preview.rb +11 -0
  368. data/app/components/shadcn/separator/previews/default.html.erb +9 -0
  369. data/app/components/shadcn/sheet/component.rb +16 -0
  370. data/app/components/shadcn/sheet/content/component.rb +98 -0
  371. data/app/components/shadcn/sheet/header/component.rb +23 -0
  372. data/app/components/shadcn/sheet/preview.rb +16 -0
  373. data/app/components/shadcn/sheet/previews/default.html.erb +16 -0
  374. data/app/components/shadcn/sheet/previews/no_close_button.html.erb +14 -0
  375. data/app/components/shadcn/sheet.rb +22 -0
  376. data/app/components/shadcn/sidebar/component.rb +232 -0
  377. data/app/components/shadcn/sidebar/input/component.rb +25 -0
  378. data/app/components/shadcn/sidebar/menu_action/component.rb +58 -0
  379. data/app/components/shadcn/sidebar/menu_button/component.rb +100 -0
  380. data/app/components/shadcn/sidebar/menu_skeleton/component.rb +51 -0
  381. data/app/components/shadcn/sidebar/menu_sub_button/component.rb +60 -0
  382. data/app/components/shadcn/sidebar/preview.rb +22 -0
  383. data/app/components/shadcn/sidebar/previews/default.html.erb +189 -0
  384. data/app/components/shadcn/sidebar/previews/floating.html.erb +39 -0
  385. data/app/components/shadcn/sidebar/previews/inset.html.erb +48 -0
  386. data/app/components/shadcn/sidebar/provider/component.rb +53 -0
  387. data/app/components/shadcn/sidebar/rail/component.rb +43 -0
  388. data/app/components/shadcn/sidebar/separator/component.rb +23 -0
  389. data/app/components/shadcn/sidebar/trigger/component.rb +39 -0
  390. data/app/components/shadcn/sidebar.rb +98 -0
  391. data/app/components/shadcn/skeleton/component.rb +14 -0
  392. data/app/components/shadcn/skeleton/preview.rb +11 -0
  393. data/app/components/shadcn/skeleton/previews/default.html.erb +7 -0
  394. data/app/components/shadcn/slider/component.rb +145 -0
  395. data/app/components/shadcn/slider/preview.rb +16 -0
  396. data/app/components/shadcn/slider/previews/default.html.erb +17 -0
  397. data/app/components/shadcn/slider/previews/vertical.html.erb +7 -0
  398. data/app/components/shadcn/slider/range/component.rb +51 -0
  399. data/app/components/shadcn/slider/thumb/component.rb +58 -0
  400. data/app/components/shadcn/slider/track/component.rb +32 -0
  401. data/app/components/shadcn/spinner/component.rb +21 -0
  402. data/app/components/shadcn/spinner/preview.rb +11 -0
  403. data/app/components/shadcn/spinner/previews/default.html.erb +7 -0
  404. data/app/components/shadcn/switch/component.rb +104 -0
  405. data/app/components/shadcn/switch/preview.rb +11 -0
  406. data/app/components/shadcn/switch/previews/default.html.erb +24 -0
  407. data/app/components/shadcn/table/component.rb +26 -0
  408. data/app/components/shadcn/table/preview.rb +16 -0
  409. data/app/components/shadcn/table/previews/default.html.erb +19 -0
  410. data/app/components/shadcn/table/previews/footer.html.erb +25 -0
  411. data/app/components/shadcn/table.rb +33 -0
  412. data/app/components/shadcn/tabs/component.rb +40 -0
  413. data/app/components/shadcn/tabs/content/component.rb +34 -0
  414. data/app/components/shadcn/tabs/list/component.rb +51 -0
  415. data/app/components/shadcn/tabs/preview.rb +16 -0
  416. data/app/components/shadcn/tabs/previews/default.html.erb +21 -0
  417. data/app/components/shadcn/tabs/previews/line.html.erb +23 -0
  418. data/app/components/shadcn/tabs/trigger/component.rb +66 -0
  419. data/app/components/shadcn/textarea/component.rb +24 -0
  420. data/app/components/shadcn/textarea/preview.rb +16 -0
  421. data/app/components/shadcn/textarea/previews/default.html.erb +3 -0
  422. data/app/components/shadcn/textarea/previews/states.html.erb +18 -0
  423. data/app/components/shadcn/theme_selector/component.rb +88 -0
  424. data/app/components/shadcn/theme_selector/preview.rb +17 -0
  425. data/app/components/shadcn/theme_selector/previews/all_themes.html.erb +20 -0
  426. data/app/components/shadcn/theme_selector/previews/default.html.erb +18 -0
  427. data/app/components/shadcn/toaster/component.rb +139 -0
  428. data/app/components/shadcn/toaster/preview.rb +16 -0
  429. data/app/components/shadcn/toaster/previews/default.html.erb +19 -0
  430. data/app/components/shadcn/toaster/previews/from_the_server.html.erb +15 -0
  431. data/app/components/shadcn/toaster/toast/component.rb +134 -0
  432. data/app/components/shadcn/toggle/component.rb +81 -0
  433. data/app/components/shadcn/toggle/preview.rb +11 -0
  434. data/app/components/shadcn/toggle/previews/default.html.erb +6 -0
  435. data/app/components/shadcn/toggle_group/component.rb +62 -0
  436. data/app/components/shadcn/toggle_group/item/component.rb +59 -0
  437. data/app/components/shadcn/toggle_group/preview.rb +16 -0
  438. data/app/components/shadcn/toggle_group/previews/default.html.erb +12 -0
  439. data/app/components/shadcn/toggle_group/previews/sizes.html.erb +37 -0
  440. data/app/components/shadcn/tooltip/component.rb +38 -0
  441. data/app/components/shadcn/tooltip/content/component.rb +62 -0
  442. data/app/components/shadcn/tooltip/preview.rb +21 -0
  443. data/app/components/shadcn/tooltip/previews/default.html.erb +4 -0
  444. data/app/components/shadcn/tooltip/previews/sides.html.erb +19 -0
  445. data/app/components/shadcn/tooltip/previews/with_keyboard_shortcut.html.erb +11 -0
  446. data/app/components/shadcn/tooltip/provider/component.rb +28 -0
  447. data/app/components/shadcn/tooltip/trigger/component.rb +25 -0
  448. data/app/helpers/shadcn_view_component/form_helper.rb +25 -0
  449. data/app/helpers/shadcn_view_component/theme_helper.rb +61 -0
  450. data/app/javascript/shadcn/animation.js +180 -0
  451. data/app/javascript/shadcn/command_score.js +110 -0
  452. data/app/javascript/shadcn/controllers/accordion_controller.js +127 -0
  453. data/app/javascript/shadcn/controllers/avatar_controller.js +32 -0
  454. data/app/javascript/shadcn/controllers/calendar_controller.js +633 -0
  455. data/app/javascript/shadcn/controllers/carousel_controller.js +139 -0
  456. data/app/javascript/shadcn/controllers/chart_controller.js +110 -0
  457. data/app/javascript/shadcn/controllers/checkbox_controller.js +61 -0
  458. data/app/javascript/shadcn/controllers/collapsible_controller.js +55 -0
  459. data/app/javascript/shadcn/controllers/combobox_controller.js +360 -0
  460. data/app/javascript/shadcn/controllers/command_controller.js +199 -0
  461. data/app/javascript/shadcn/controllers/dialog_controller.js +154 -0
  462. data/app/javascript/shadcn/controllers/drawer_controller.js +265 -0
  463. data/app/javascript/shadcn/controllers/dropdown_menu_controller.js +259 -0
  464. data/app/javascript/shadcn/controllers/hover_card_controller.js +85 -0
  465. data/app/javascript/shadcn/controllers/input_group_controller.js +17 -0
  466. data/app/javascript/shadcn/controllers/input_otp_controller.js +80 -0
  467. data/app/javascript/shadcn/controllers/menubar_controller.js +151 -0
  468. data/app/javascript/shadcn/controllers/message_scroller_controller.js +456 -0
  469. data/app/javascript/shadcn/controllers/navigation_menu_controller.js +211 -0
  470. data/app/javascript/shadcn/controllers/popover_controller.js +66 -0
  471. data/app/javascript/shadcn/controllers/radio_group_controller.js +69 -0
  472. data/app/javascript/shadcn/controllers/resizable_controller.js +167 -0
  473. data/app/javascript/shadcn/controllers/scroll_area_controller.js +253 -0
  474. data/app/javascript/shadcn/controllers/select_controller.js +320 -0
  475. data/app/javascript/shadcn/controllers/sidebar_controller.js +273 -0
  476. data/app/javascript/shadcn/controllers/slider_controller.js +230 -0
  477. data/app/javascript/shadcn/controllers/switch_controller.js +39 -0
  478. data/app/javascript/shadcn/controllers/tabs_controller.js +81 -0
  479. data/app/javascript/shadcn/controllers/theme_controller.js +67 -0
  480. data/app/javascript/shadcn/controllers/toaster_controller.js +281 -0
  481. data/app/javascript/shadcn/controllers/toggle_controller.js +24 -0
  482. data/app/javascript/shadcn/controllers/toggle_group_controller.js +73 -0
  483. data/app/javascript/shadcn/controllers/tooltip_controller.js +58 -0
  484. data/app/javascript/shadcn/direction.js +41 -0
  485. data/app/javascript/shadcn/dismiss.js +73 -0
  486. data/app/javascript/shadcn/floating.js +207 -0
  487. data/app/javascript/shadcn/focus.js +86 -0
  488. data/app/javascript/shadcn/id.js +14 -0
  489. data/app/javascript/shadcn/index.js +110 -0
  490. data/app/javascript/shadcn/popper.js +262 -0
  491. data/app/javascript/shadcn/scroll_geometry.js +267 -0
  492. data/app/javascript/shadcn/theme.js +161 -0
  493. data/app/javascript/shadcn/top_layer.js +59 -0
  494. data/app/javascript/shadcn/typeahead.js +99 -0
  495. data/config/importmap.rb +6 -0
  496. data/config/locales/en.yml +62 -0
  497. data/lib/generators/shadcn_view_component/install/install_generator.rb +149 -0
  498. data/lib/shadcn_view_component/engine.rb +73 -0
  499. data/lib/shadcn_view_component/form_builder.rb +264 -0
  500. data/lib/shadcn_view_component/icon_registry.rb +66 -0
  501. data/lib/shadcn_view_component/icons.rb +32 -0
  502. data/lib/shadcn_view_component/themes.rb +1065 -0
  503. data/lib/shadcn_view_component/version.rb +3 -0
  504. data/lib/shadcn_view_component.rb +82 -0
  505. data/lib/tasks/icons.rake +53 -0
  506. data/lib/tasks/themes.rake +171 -0
  507. metadata +608 -0
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Calendar
5
+ class Preview < ApplicationViewComponentPreview
6
+ # Upstream's own first example, minus the border it wraps it in: a single
7
+ # month with today selected.
8
+ def default
9
+ render_with_template
10
+ end
11
+
12
+ # Upstream's "Month and Year Selector".
13
+ def dropdown_caption
14
+ render_with_template
15
+ end
16
+
17
+ # Upstream's "Range Calendar".
18
+ def range
19
+ render_with_template
20
+ end
21
+
22
+ # Upstream's "Presets".
23
+ def presets
24
+ render_with_template
25
+ end
26
+
27
+ # Upstream's "Date and Time Picker".
28
+ def date_and_time
29
+ render_with_template
30
+ end
31
+
32
+ # Upstream's "Booked dates".
33
+ def booked
34
+ render_with_template
35
+ end
36
+
37
+ # Upstream's "Custom Cell Size".
38
+ def custom_cell_size
39
+ render_with_template
40
+ end
41
+
42
+ # Upstream's "Week Numbers".
43
+ def week_numbers
44
+ render_with_template
45
+ end
46
+
47
+ # The hidden inputs a Rails form posts.
48
+ def in_a_form
49
+ render_with_template
50
+ end
51
+
52
+ # The month is an argument here so a month can be asked for by URL. That
53
+ # is what `spec/system/calendar_spec.rb` compares the browser's own render
54
+ # against: the grid exists twice — in `Calendar::Month` and in the
55
+ # controller — and the only thing that keeps the two honest is asking the
56
+ # server what it would have drawn.
57
+ #
58
+ # @param month text
59
+ def month(month: "2026-09-01")
60
+ render_component(month: Date.parse(month), class: "rounded-md border shadow-sm")
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,18 @@
1
+ <%# Upstream's "Booked dates": the taken days are refused and struck through.
2
+ `disabled:` takes anything answering `===` — a Date, a Range, or a list of
3
+ them — so the booking rule is written where the app already keeps it.
4
+
5
+ The line through them is the caller's, not the component's: upstream reaches
6
+ for `modifiers` and `modifiersClassNames`, which this port does not have, and
7
+ a class on the root reaches the same cells. %>
8
+ <%
9
+ booked = [ Date.new(2026, 8, 4), Date.new(2026, 8, 5),
10
+ Date.new(2026, 8, 11)..Date.new(2026, 8, 13),
11
+ Date.new(2026, 8, 20), Date.new(2026, 8, 26)..Date.new(2026, 8, 28) ]
12
+ %>
13
+ <%= render(Shadcn::Calendar::Component.new(
14
+ month: Date.new(2026, 8, 1),
15
+ selected: Date.new(2026, 8, 17),
16
+ disabled: booked,
17
+ class: "rounded-md border shadow-sm [&_td[data-disabled]>button]:line-through"
18
+ )) %>
@@ -0,0 +1,11 @@
1
+ <%# Upstream's "Custom Cell Size": the whole calendar is sized from one custom
2
+ property, so a caller changes `--cell-size` and everything follows. %>
3
+ <%= render(Shadcn::Calendar::Component.new(
4
+ mode: :range,
5
+ month: Date.new(2026, 8, 1),
6
+ selected: Date.new(2026, 8, 9)..Date.new(2026, 8, 19),
7
+ caption_layout: :dropdown,
8
+ start_month: Date.new(2024, 1, 1),
9
+ end_month: Date.new(2030, 12, 31),
10
+ class: "rounded-md border shadow-sm [--cell-size:--spacing(12)]"
11
+ )) %>
@@ -0,0 +1,31 @@
1
+ <%# Upstream's "Date and Time Picker": a date, then the two ends of the day it
2
+ covers. The times are real `<input type="time">` fields, so the browser's own
3
+ picker and validation come with them — the same call the gem makes for
4
+ `native_select`.
5
+
6
+ `step` is in *seconds*, so 900 is a quarter of an hour. Measured in Chrome:
7
+ the field's arrows then move 09:00 → 09:15 → 09:30, and a typed 09:07 is
8
+ refused at submit with the browser's own message. What it does not do is
9
+ turn the field into a list — the segments still take any digits, and the
10
+ complaint comes afterwards. Where only a quarter-hour will do, the control
11
+ is a `<select>` rather than this one. %>
12
+ <div class="w-fit rounded-md border shadow-sm">
13
+ <%= render(Shadcn::Calendar::Component.new(
14
+ month: Date.new(2026, 8, 1),
15
+ selected: Date.new(2026, 8, 12)
16
+ )) %>
17
+ <div class="flex gap-3 border-t p-3">
18
+ <%= render(Shadcn::Field::Component.new(class: "flex-1")) do |field| %>
19
+ <% field.with_label(for: "calendar-start-time") { "Start Time" } %>
20
+ <% field.with_field_content do %>
21
+ <%= render(Shadcn::Input::Component.new(id: "calendar-start-time", type: "time", value: "09:00", step: 900)) %>
22
+ <% end %>
23
+ <% end %>
24
+ <%= render(Shadcn::Field::Component.new(class: "flex-1")) do |field| %>
25
+ <% field.with_label(for: "calendar-end-time") { "End Time" } %>
26
+ <% field.with_field_content do %>
27
+ <%= render(Shadcn::Input::Component.new(id: "calendar-end-time", type: "time", value: "17:00", step: 900)) %>
28
+ <% end %>
29
+ <% end %>
30
+ </div>
31
+ </div>
@@ -0,0 +1,8 @@
1
+ <%# Upstream's "Calendar" example. The month is fixed rather than `Date.current`
2
+ so the snapshot does not change every day — the same reason the component
3
+ takes `month:` at all. %>
4
+ <%= render(Shadcn::Calendar::Component.new(
5
+ month: Date.new(2026, 8, 1),
6
+ selected: Date.new(2026, 8, 12),
7
+ class: "rounded-md border shadow-sm"
8
+ )) %>
@@ -0,0 +1,11 @@
1
+ <%# Upstream's "Month and Year Selector": the caption becomes two native
2
+ `<select>`s laid invisible over the label, so the control is the real one and
3
+ what you read is the span underneath it. %>
4
+ <%= render(Shadcn::Calendar::Component.new(
5
+ month: Date.new(2026, 8, 1),
6
+ selected: Date.new(2026, 8, 12),
7
+ caption_layout: :dropdown,
8
+ start_month: Date.new(2020, 1, 1),
9
+ end_month: Date.new(2030, 12, 31),
10
+ class: "rounded-md border shadow-sm"
11
+ )) %>
@@ -0,0 +1,8 @@
1
+ <%# The half a Rails form sees: the component renders the hidden inputs and the
2
+ controller rewrites them, so `params[:booking][:on]` is a date the model can
3
+ take. With a model, `f.shadcn_calendar :on` does all of this. %>
4
+ <%= render(Shadcn::Calendar::Component.new(
5
+ month: Date.new(2026, 8, 1),
6
+ name: "booking[on]",
7
+ class: "rounded-md border shadow-sm"
8
+ )) %>
@@ -0,0 +1,20 @@
1
+ <%# Upstream's "Presets": five shortcuts beside the month. Upstream's own
2
+ example holds them in React state; here the offset in days rides on the
3
+ button and the controller does the arithmetic, so a host writes no
4
+ JavaScript. The buttons are block content, which lands inside the root —
5
+ which is what lets their `data-action` reach the controller. %>
6
+ <%= render(Shadcn::Calendar::Component.new(
7
+ month: Date.current,
8
+ class: "rounded-md border shadow-sm"
9
+ )) do %>
10
+ <div class="flex flex-col gap-2 border-t pt-3">
11
+ <% { "Today" => 0, "Tomorrow" => 1, "In 3 days" => 3, "In a week" => 7, "In 2 weeks" => 14 }.each do |label, offset| %>
12
+ <%= render(Shadcn::Button::Component.new(
13
+ variant: :outline,
14
+ size: :sm,
15
+ "data-action": "click->shadcn--calendar#preset",
16
+ "data-shadcn--calendar-offset-param": offset
17
+ )) { label } %>
18
+ <% end %>
19
+ </div>
20
+ <% end %>
@@ -0,0 +1,10 @@
1
+ <%# Upstream's "Range Calendar": two months side by side and one range across
2
+ both. The two ends are two dates, so in a form they are two attributes —
3
+ `f.shadcn_calendar :starts_on, mode: :range, to: :ends_on`. %>
4
+ <%= render(Shadcn::Calendar::Component.new(
5
+ mode: :range,
6
+ month: Date.new(2026, 1, 1),
7
+ selected: Date.new(2026, 1, 12)..Date.new(2026, 2, 10),
8
+ number_of_months: 2,
9
+ class: "rounded-md border shadow-sm"
10
+ )) %>
@@ -0,0 +1,8 @@
1
+ <%# Upstream's "Week Numbers". The number is ISO-8601 — `Date#cweek` — where
2
+ upstream's follows the locale, which is written up in features/calendar.md. %>
3
+ <%= render(Shadcn::Calendar::Component.new(
4
+ month: Date.new(2026, 1, 1),
5
+ selected: Date.new(2026, 1, 15),
6
+ show_week_number: true,
7
+ class: "rounded-md border shadow-sm"
8
+ )) %>
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Card
5
+ # Port of registry/new-york-v4/ui/card.tsx
6
+ class Component < ApplicationViewComponent
7
+ renders_one :header, "Shadcn::Card::Header::Component"
8
+ renders_one :card_content, "Shadcn::Card::Content::Component"
9
+ renders_one :footer, "Shadcn::Card::Footer::Component"
10
+
11
+ slot_name :card
12
+
13
+ style do
14
+ base {
15
+ "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm"
16
+ }
17
+ end
18
+
19
+ def call
20
+ render_element(body: safe_join([ header, card_content, footer, content ].compact))
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Card
5
+ module Header
6
+ class Component < ApplicationViewComponent
7
+ renders_one :title, "Shadcn::Card::Title::Component"
8
+ renders_one :description, "Shadcn::Card::Description::Component"
9
+ renders_one :action, "Shadcn::Card::Action::Component"
10
+
11
+ slot_name :"card-header"
12
+
13
+ style do
14
+ base {
15
+ "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start " \
16
+ "gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6"
17
+ }
18
+ end
19
+
20
+ def call
21
+ render_element(body: safe_join([ title, description, action, content ].compact))
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Card
5
+ class Preview < ApplicationViewComponentPreview
6
+ def default
7
+ render_with_template
8
+ end
9
+
10
+ # Upstream's "Image".
11
+ def image
12
+ render_with_template
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ <%= render(Shadcn::Card::Component.new(class: "w-full max-w-sm")) do |card| %>
2
+ <% card.with_header do |h| %>
3
+ <% h.with_title { "Login to your account" } %>
4
+ <% h.with_description { "Enter your email below to login." } %>
5
+ <% h.with_action do %>
6
+ <%= render(Shadcn::Button::Component.new(variant: :link, size: :sm)) { "Sign Up" } %>
7
+ <% end %>
8
+ <% end %>
9
+ <% card.with_card_content do %>
10
+ <div class="flex flex-col gap-4">
11
+ <%= render(Shadcn::Label::Component.new(for: "email")) { "Email" } %>
12
+ <%= render(Shadcn::Input::Component.new(id: "email", type: "email", placeholder: "m@example.com")) %>
13
+ </div>
14
+ <% end %>
15
+ <% card.with_footer(class: "flex-col gap-2") do %>
16
+ <%= render(Shadcn::Button::Component.new(class: "w-full")) { "Login" } %>
17
+ <% end %>
18
+ <% end %>
@@ -0,0 +1,37 @@
1
+ <%# Upstream's "Image": the picture is the card's first child, full width, and
2
+ the card's top padding comes off so it sits flush against the edge.
3
+
4
+ Everything is rendered in the *block*, none of it through the `header` and
5
+ `footer` slots. `Card#call` emits its slots and then the block, so an image
6
+ written first but passed as block content lands last — under the footer,
7
+ which is what the first version of this preview did. Upstream has no such
8
+ reordering: its Card renders `{children}` and the source order is the DOM
9
+ order. The select and dropdown previews render their items in the block for
10
+ the same reason.
11
+
12
+ Two more things are the caller's here and the component's there. The Card in
13
+ the registry this gem ports is `flex flex-col gap-6 rounded-xl border
14
+ bg-card py-6 …` and nothing else — no `has-[>img:first-child]:pt-0`, no
15
+ `*:[img:first-child]:rounded-t-xl` — so `pt-0` and `overflow-hidden` are
16
+ written out. Measured on the docs page on 2026-08-12, where the Card also
17
+ carries `--card-spacing`, `ring-1 ring-foreground/10` and a `size`, none of
18
+ which is in `vendor/shadcn/ui/card.tsx`: that is the other registry, and it
19
+ is why this is close rather than identical.
20
+
21
+ The footer's top padding is not written here: `card-footer` carries
22
+ `[.border-t]:pt-6`, so giving it a border is what asks for it.
23
+
24
+ A block of colour rather than a photograph: the gallery ships no binary
25
+ assets, and `aspect-video w-full` is the part of the shape that matters. %>
26
+ <%= render(Shadcn::Card::Component.new(class: "w-full max-w-sm overflow-hidden pt-0")) do %>
27
+ <div class="flex aspect-video w-full items-center justify-center bg-muted text-sm text-foreground/70">
28
+ Where the picture goes
29
+ </div>
30
+ <%= render(Shadcn::Card::Header::Component.new) do |h| %>
31
+ <% h.with_title { "Lakeside cabin" } %>
32
+ <% h.with_description { "Two bedrooms, one jetty." } %>
33
+ <% end %>
34
+ <%= render(Shadcn::Card::Footer::Component.new(class: "border-t bg-muted/50")) do %>
35
+ <%= render(Shadcn::Button::Component.new(class: "w-full")) { "Book" } %>
36
+ <% end %>
37
+ <% end %>
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # The parts of the Card family that are just an element with a
5
+ # `data-slot` and a fixed set of classes. Anything with variants, slots or
6
+ # markup of its own keeps its own `component.rb`.
7
+ module Card
8
+ extend Parts
9
+
10
+ part :action, slot: "card-action",
11
+ classes: "col-start-2 row-span-2 row-start-1 self-start justify-self-end"
12
+
13
+ part :content, slot: "card-content", classes: "px-6"
14
+
15
+ part :description, slot: "card-description", classes: "text-sm text-muted-foreground"
16
+
17
+ part :footer, slot: "card-footer", classes: "flex items-center px-6 [.border-t]:pt-6"
18
+
19
+ part :title, slot: "card-title", classes: "leading-none font-semibold"
20
+ end
21
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Carousel
5
+ # Port of registry/new-york-v4/ui/carousel.tsx
6
+ #
7
+ # Upstream builds this on **embla-carousel**, and this port does not. That
8
+ # is a measurement rather than a preference: of embla's 3,170 lines,
9
+ # `carousel.tsx` reaches for six things — `scrollPrev`, `scrollNext`,
10
+ # `canScrollPrev`, `canScrollNext` and the `select`/`reInit` events — and
11
+ # everything else about the component is flex markup and two buttons.
12
+ #
13
+ # The six are what a scroll container already answers. The viewport is a
14
+ # scroller with snap points, so the browser does the dragging, the momentum
15
+ # and the snapping, and this controller reads `scrollLeft` for the two
16
+ # `canScroll` questions and writes it for the two `scroll` ones. It is the
17
+ # same trade `scroll-area` made — the layout is CSS, and the controller
18
+ # computes a number per axis.
19
+ #
20
+ # What is *not* reproduced is in features/carousel.md: `opts` (embla's
21
+ # `loop`, `align`, `slidesToScroll`), the `api` object, its event stream,
22
+ # and plugins. None has markup, so `parity_spec` cannot see their absence;
23
+ # the feature doc is where that is written down.
24
+ class Component < ApplicationViewComponent
25
+ renders_one :carousel_content, "Shadcn::Carousel::Content::Component"
26
+ renders_one :previous, "Shadcn::Carousel::Previous::Component"
27
+ renders_one :next_control, "Shadcn::Carousel::Next::Component"
28
+
29
+ slot_name :carousel
30
+
31
+ style do
32
+ base { "relative" }
33
+ end
34
+
35
+ attr_reader :orientation
36
+
37
+ def initialize(orientation: :horizontal, **attributes)
38
+ @orientation = orientation&.to_sym == :vertical ? :vertical : :horizontal
39
+ super(**attributes)
40
+ end
41
+
42
+ def element_attributes(**defaults)
43
+ super(**{
44
+ role: "region",
45
+ "aria-roledescription" => "carousel",
46
+ "data-orientation" => orientation,
47
+ "data-controller" => "shadcn--carousel",
48
+ "data-shadcn--carousel-orientation-value" => orientation,
49
+ # Capture, as upstream does (`onKeyDownCapture`, carousel.tsx:119): a
50
+ # slide can hold controls of its own, and one that stops a keydown
51
+ # would otherwise take the carousel's arrows with it.
52
+ "data-action" => "keydown->shadcn--carousel#keydown:capture"
53
+ }.merge(defaults))
54
+ end
55
+
56
+ def call
57
+ render_element(body: safe_join([ carousel_content, previous, next_control, content ].compact))
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Carousel
5
+ module Content
6
+ # CarouselContent — the viewport, with the flex track inside it.
7
+ #
8
+ # The viewport keeps upstream's `overflow-hidden` and nothing else, which
9
+ # matters: `overflow: hidden` still makes an element a scroll container,
10
+ # so `scrollLeft` works on it even though a person cannot drag it. What
11
+ # turns dragging back on is a rule in `shadcn.css` keyed on this
12
+ # `data-slot`, beside the one that hides the scroll area's scrollbars —
13
+ # the same shape of answer, in the same place.
14
+ class Component < ApplicationViewComponent
15
+ renders_many :items, "Shadcn::Carousel::Item::Component"
16
+
17
+ slot_name :"carousel-content"
18
+
19
+ style do
20
+ base { "overflow-hidden" }
21
+ end
22
+
23
+ attr_reader :orientation
24
+
25
+ def initialize(orientation: :horizontal, **attributes)
26
+ @orientation = orientation&.to_sym == :vertical ? :vertical : :horizontal
27
+ # Held for the track and kept off the viewport, which is upstream's
28
+ # arrangement: `{...props}` and the className land on the inner div.
29
+ @track = attributes
30
+ super()
31
+ end
32
+
33
+ # The caller's attributes went to the track above, so the viewport is
34
+ # given only its own.
35
+ def element_attributes(**defaults)
36
+ super(**{
37
+ # Upstream sets no tabindex, and upstream's viewport is not a
38
+ # scroller: embla translates a track inside a hidden overflow, so
39
+ # there is no region for a keyboard to be shut out of. Ours is a
40
+ # scroller, which is the whole mechanism, and axe fails a scrollable
41
+ # region with no keyboard access — the same divergence, for the same
42
+ # reason, as `scroll-area-viewport`.
43
+ #
44
+ # It earns its place beyond the audit: Tab reaches the slides and
45
+ # the arrows move them, where before the only way through was the
46
+ # two buttons.
47
+ tabindex: "0",
48
+ role: "group",
49
+ "aria-roledescription" => "slides",
50
+ "data-orientation" => orientation,
51
+ "data-shadcn--carousel-target" => "viewport"
52
+ }.merge(defaults))
53
+ end
54
+
55
+ # Upstream gives the *track* the caller's className and leaves the
56
+ # viewport with `overflow-hidden` and nothing else (carousel.tsx:138-152).
57
+ # This port had it the other way round, so a preview asking for a
58
+ # smaller gutter with `-ml-2` put it on the viewport, where it does
59
+ # nothing, while the track kept `-ml-4` and the items took `pl-2`. A
60
+ # gutter that disagrees with its own padding is a slide whose content
61
+ # starts outside the window, which is a card with no left border.
62
+ def call
63
+ render_element(body: tag.div(safe_join([ items, content ].flatten.compact), **track_attributes))
64
+ end
65
+
66
+ private
67
+
68
+ def track_attributes
69
+ extra = @track.except(:class, "class")
70
+
71
+ # Merged rather than concatenated: a caller asking for a smaller gutter
72
+ # passes `-ml-2`, and `flex -ml-4 -ml-2` leaves both in the attribute
73
+ # for the cascade to arbitrate. `cn` is what resolves that everywhere
74
+ # else in this port.
75
+ extra.merge(class: ShadcnViewComponent.cn(track_classes, @track[:class] || @track["class"]))
76
+ end
77
+
78
+ def track_classes
79
+ horizontal? ? "flex -ml-4" : "flex -mt-4 flex-col"
80
+ end
81
+
82
+ def horizontal? = orientation == :horizontal
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Carousel
5
+ module Item
6
+ # CarouselItem — one slide, full width until the caller says otherwise
7
+ # with a `basis-*` of its own, which is upstream's "Sizes" example.
8
+ class Component < ApplicationViewComponent
9
+ slot_name :"carousel-item"
10
+
11
+ style do
12
+ base { "min-w-0 shrink-0 grow-0 basis-full" }
13
+
14
+ variants {
15
+ orientation {
16
+ horizontal { "pl-4" }
17
+ vertical { "pt-4" }
18
+ }
19
+ }
20
+
21
+ defaults { { orientation: :horizontal } }
22
+ end
23
+
24
+ attr_reader :orientation
25
+
26
+ def initialize(orientation: :horizontal, **attributes)
27
+ @orientation = orientation&.to_sym == :vertical ? :vertical : :horizontal
28
+ super(**attributes)
29
+ end
30
+
31
+ def style_variants = { orientation: }
32
+
33
+ def element_attributes(**defaults)
34
+ super(**{
35
+ role: "group",
36
+ "aria-roledescription" => "slide"
37
+ }.merge(defaults))
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Carousel
5
+ module Next
6
+ # CarouselNext — the button's own variants, as `pagination-link` takes
7
+ # them: upstream renders a `<Button variant="outline" size="icon">` and
8
+ # adds its placement on top, so the classes come from
9
+ # `Button.variant_classes` rather than from a style block of this
10
+ # component's own. Redefining `style` here would *replace* the button's
11
+ # base rather than add to it, which is a round control with no round.
12
+ class Component < ApplicationViewComponent
13
+ default_tag :button
14
+ slot_name :"carousel-next"
15
+
16
+ PLACEMENT = {
17
+ horizontal: "top-1/2 -right-12 -translate-y-1/2",
18
+ vertical: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90"
19
+ }.freeze
20
+
21
+ attr_reader :orientation, :variant, :size
22
+
23
+ def initialize(orientation: :horizontal, variant: :outline, size: :icon, **attributes)
24
+ @orientation = orientation&.to_sym == :vertical ? :vertical : :horizontal
25
+ @variant = variant&.to_sym || :outline
26
+ @size = size&.to_sym || :icon
27
+ super(**attributes)
28
+ end
29
+
30
+ # Disabled until the controller says otherwise: at rest a carousel is at
31
+ # one end, and which end has room is a question about a viewport the
32
+ # server has never measured.
33
+ def element_attributes(**defaults)
34
+ super(**{
35
+ type: "button",
36
+ disabled: true,
37
+ "data-shadcn--carousel-target" => "next",
38
+ "data-action" => "click->shadcn--carousel#next"
39
+ }.merge(defaults))
40
+ end
41
+
42
+ def css_classes(extra = nil)
43
+ Button::Component.variant_classes(
44
+ variant:,
45
+ size:,
46
+ class: [ "absolute size-8 rounded-full", PLACEMENT.fetch(orientation), extra ].compact.join(" ")
47
+ )
48
+ end
49
+
50
+ def call
51
+ render_element(body: safe_join([
52
+ render(Icon::Component.new("arrow-right")),
53
+ tag.span(shadcn_t("carousel.next"), class: "sr-only")
54
+ ]))
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Carousel
5
+ class Preview < ApplicationViewComponentPreview
6
+ def default
7
+ render_with_template
8
+ end
9
+
10
+ # Upstream's "Sizes" and "Spacing": both are the caller's classes on the
11
+ # item, which is why neither is an argument.
12
+ def sizes
13
+ render_with_template
14
+ end
15
+
16
+ # Upstream's "Orientation".
17
+ def vertical
18
+ render_with_template
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ <%# Upstream's own example: five slides, each the width of the viewport, with
2
+ the two controls sitting outside the box — `-left-12` and `-right-12`, which
3
+ is why the page needs room around it. %>
4
+ <div class="px-14">
5
+ <%= render(Shadcn::Carousel::Component.new(class: "w-full max-w-xs")) do |c| %>
6
+ <% c.with_carousel_content do |content| %>
7
+ <% 5.times do |i| %>
8
+ <% content.with_item do %>
9
+ <div class="p-1">
10
+ <%= render(Shadcn::Card::Component.new) do |card| %>
11
+ <% card.with_card_content(class: "flex aspect-square items-center justify-center p-6") do %>
12
+ <span class="text-4xl font-semibold"><%= i + 1 %></span>
13
+ <% end %>
14
+ <% end %>
15
+ </div>
16
+ <% end %>
17
+ <% end %>
18
+ <% end %>
19
+ <% c.with_previous %>
20
+ <% c.with_next_control %>
21
+ <% end %>
22
+ </div>