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,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Base class for every ported shadcn/ui component.
5
+ #
6
+ # It exists to make the React → ViewComponent mapping mechanical:
7
+ #
8
+ # React Ruby
9
+ # ------------------------------------ ---------------------------------------
10
+ # cva(base, {variants, defaultVariants}) style { base {} variants {} defaults {} }
11
+ # cn(...) TailwindMerge, wired as the style postprocessor
12
+ # data-slot="card-header" slot_name :"card-header"
13
+ # <div {...props} /> **attributes, splatted through Rails' tag builder
14
+ # asChild as: :a / as: :span
15
+ #
16
+ # A component that is a single element carrying `data-slot` + classes needs no
17
+ # template at all: the inherited `#call` renders it. Components whose markup is
18
+ # richer (an icon, a nested wrapper, an indicator) get a sidecar
19
+ # `component.html.erb`, which overrides `#call`.
20
+ class ApplicationViewComponent < ViewComponent::Base
21
+ include ViewComponentContrib::StyleVariants
22
+
23
+ # `cn()`: concatenate, then let tailwind-merge drop conflicting utilities so
24
+ # that classes passed in by the caller win — exactly like shadcn's helper.
25
+ style_config.postprocess_with { |classes| ShadcnViewComponent.cn(classes) }
26
+
27
+ # Elements that must not be given a block/closing tag.
28
+ VOID_TAGS = %i[area base br col embed hr img input link meta source track wbr].freeze
29
+
30
+ class << self
31
+ # The element rendered by default, i.e. the tag in the TSX source.
32
+ def default_tag(value = nil)
33
+ return @default_tag = value.to_sym if value
34
+
35
+ @default_tag ||= superclass.respond_to?(:default_tag) ? superclass.default_tag : :div
36
+ end
37
+
38
+ # The `data-slot` value shadcn stamps on this part. Inherited, so that
39
+ # specialisations such as PaginationPrevious keep PaginationLink's slot.
40
+ def slot_name(value = nil)
41
+ return @slot_name = value.to_s if value
42
+ return @slot_name if defined?(@slot_name)
43
+
44
+ @slot_name = superclass.respond_to?(:slot_name) ? superclass.slot_name : nil
45
+ end
46
+ end
47
+
48
+ attr_reader :attributes
49
+
50
+ # @param as [Symbol, String, nil] render a different element (shadcn's `asChild`)
51
+ # @param attributes [Hash] anything else is forwarded to the element, like `{...props}`
52
+ def initialize(as: nil, **attributes)
53
+ @as = as
54
+ @attributes = attributes
55
+ end
56
+
57
+ def call
58
+ render_element(body: content)
59
+ end
60
+
61
+ def tag_name
62
+ (@as || self.class.default_tag).to_sym
63
+ end
64
+
65
+ # Renders the component's root element. `defaults` are the attributes the
66
+ # component itself contributes; see `#element_attributes` for how they rank
67
+ # against the caller's.
68
+ #
69
+ # The body comes either from a block (templates: `<%= render_element do %>`)
70
+ # or from `body:` — `#call` passes ViewComponent's already-captured `content`
71
+ # that way, because re-capturing it here would swallow it.
72
+ def render_element(body: nil, **defaults, &block)
73
+ attrs = element_attributes(**defaults)
74
+ return tag.public_send(tag_name, **attrs) if VOID_TAGS.include?(tag_name)
75
+
76
+ content_tag(tag_name, block ? capture(&block) : body, attrs)
77
+ end
78
+
79
+ # The full attribute hash for the root element.
80
+ #
81
+ # Precedence, lowest to highest: the `data-slot` marker, then the component's
82
+ # own `defaults` (what subclasses pass up through `super`), then whatever the
83
+ # caller gave us. The caller winning is the point — it is what `{...props}`
84
+ # does in every shadcn component, where the spread comes last.
85
+ def element_attributes(**defaults)
86
+ base = { "data-slot" => self.class.slot_name }.compact
87
+ merged = merge_attributes(base, defaults, attributes)
88
+ merged["class"] = css_classes(merged.delete("class"))
89
+ merged.compact
90
+ end
91
+
92
+ # Compiles the cva-equivalent style set. Caller classes go last so
93
+ # tailwind-merge resolves conflicts in their favour. Parts that carry no
94
+ # classes of their own still have to pass the caller's through.
95
+ #
96
+ # Without caller classes the result depends only on the component class and
97
+ # its variants, and the style blocks are static once loaded — so it is
98
+ # computed once. That is not just a saving on the merge: it keeps the
99
+ # library's own class strings out of tailwind_merge's LRU, which is a fixed
100
+ # size shared with everything the caller varies.
101
+ def css_classes(extra = nil)
102
+ return compile_classes(extra) if extra.present?
103
+
104
+ ShadcnViewComponent.class_cache.fetch_or_store([ self.class.name, style_variants ]) do
105
+ compile_classes(nil)
106
+ end
107
+ end
108
+
109
+ # Overridden by components that expose variants (button, badge, alert, …).
110
+ def style_variants
111
+ {}
112
+ end
113
+
114
+ # The user-visible strings the components render, looked up under
115
+ # `shadcn_view_component.*` and falling back to the bundled English — so the
116
+ # gem works untranslated and a host app can override any key.
117
+ #
118
+ # The fallback is the `locale: :en` default below, and it has to be there.
119
+ # This method used to be a bare `I18n.t` while the comment above it already
120
+ # claimed the English was a default; it was not, and the gem only looked
121
+ # untranslatable-proof because every app it had been tried in ran in
122
+ # English. A host running `it` got `I18n::MissingTranslationData` out of a
123
+ # searchable select — and with `config.i18n.raise_on_missing_translations`,
124
+ # which the Rails generators turn on in development and test, a raised page
125
+ # rather than a fallback string.
126
+ #
127
+ # A lambda, so the second lookup only happens when the first misses.
128
+ def shadcn_t(key, **interpolations)
129
+ full_key = "shadcn_view_component.#{key}"
130
+
131
+ I18n.t(full_key, **interpolations,
132
+ default: ->(*) { I18n.t(full_key, locale: :en, **interpolations) })
133
+ end
134
+
135
+ # Combines inline styles without dropping whatever the caller passed.
136
+ def merged_style(*declarations)
137
+ [ attributes[:style], *declarations ].compact.reject(&:empty?).join(";").presence
138
+ end
139
+
140
+ # Radix's context-only roots (Dialog, Popover, Select, …) render no element
141
+ # at all. We need somewhere to hang the Stimulus controller, so those roots
142
+ # emit a wrapper that is invisible to layout.
143
+ CONTENTS_STYLE = "display:contents"
144
+
145
+ private
146
+
147
+ def compile_classes(extra)
148
+ compiled = style(**style_variants, class: extra)
149
+ (compiled || ShadcnViewComponent.cn(extra)).presence
150
+ end
151
+
152
+ # Shallow merge, with three exceptions that are combined rather than
153
+ # replaced: `data:`/`aria:` hashes, `class`, and `data-action` — Stimulus
154
+ # reads a space-separated list, so an action passed in by the caller adds to
155
+ # the component's own instead of silently replacing it.
156
+ #
157
+ # Keys are normalised to strings so `class:` and `"class"` cannot end up as
158
+ # two separate attributes.
159
+ def merge_attributes(*hashes)
160
+ hashes.compact.map { |hash| normalize_attributes(hash) }.reduce({}) do |acc, hash|
161
+ hash.each do |name, value|
162
+ case name
163
+ when "data", "aria"
164
+ acc[name] = (acc[name] || {}).merge(value || {})
165
+ when "class"
166
+ acc["class"] = [ acc["class"], value ].compact
167
+ when "data-action"
168
+ append_action(acc, value)
169
+ when "data-controller"
170
+ append_controller(acc, value)
171
+ else
172
+ acc[name] = value
173
+ end
174
+ end
175
+ acc
176
+ end
177
+ end
178
+
179
+ # Stringifies the keys and lifts `data: { action: … }` — the idiomatic Rails
180
+ # spelling — out to a top-level `data-action`, so both spellings reach the
181
+ # concatenation rule above. Without this the two forms land in different
182
+ # branches and the element ends up with the attribute twice, which is invalid
183
+ # HTML: the browser keeps the first and the other action never fires.
184
+ def normalize_attributes(hash)
185
+ hash.each_with_object({}) do |(key, value), normalized|
186
+ name = key.to_s
187
+
188
+ if name == "data" && value.is_a?(Hash)
189
+ data = value.transform_keys(&:to_s)
190
+ action = data.delete("action")
191
+ controller = data.delete("controller")
192
+
193
+ append_action(normalized, action) if action
194
+ append_controller(normalized, controller) if controller
195
+ normalized["data"] = data unless data.empty?
196
+ elsif name == "data-action"
197
+ append_action(normalized, value)
198
+ elsif name == "data-controller"
199
+ append_controller(normalized, value)
200
+ else
201
+ normalized[name] = value
202
+ end
203
+ end
204
+ end
205
+
206
+ def append_action(hash, value)
207
+ hash["data-action"] = [ hash["data-action"], value ].compact.join(" ")
208
+ end
209
+
210
+ # The same treatment `data-action` gets, and for the same reason: Stimulus
211
+ # reads a space-separated list, so a controller a caller attaches has to add
212
+ # to the component's own rather than replace it.
213
+ #
214
+ # This was missed when `data-action` was fixed, and the failure is quieter.
215
+ # A host writing `data: { controller: "my-thing" }` got two `data-controller`
216
+ # attributes — invalid HTML, and the browser keeps the first, which is the
217
+ # component's. So `my-thing` never connected, with nothing logged and the
218
+ # component still working perfectly. Found in a host app wiring a dependent
219
+ # select: choosing a client was supposed to reload its locations, and simply
220
+ # stopped doing so.
221
+ def append_controller(hash, value)
222
+ hash["data-controller"] = [ hash["data-controller"], value ].compact.join(" ")
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Base class for the sidecar `preview.rb` files rendered by Lookbook.
5
+ class ApplicationViewComponentPreview < ViewComponentContrib::Preview::Base
6
+ self.abstract_class = true
7
+ end
8
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module AspectRatio
5
+ # Port of registry/new-york-v4/ui/aspect-ratio.tsx
6
+ #
7
+ # Radix wraps the content in a relatively positioned box whose bottom
8
+ # padding encodes the ratio, then absolutely positions the slot inside it.
9
+ class Component < ApplicationViewComponent
10
+ slot_name :"aspect-ratio"
11
+
12
+ attr_reader :ratio
13
+
14
+ def initialize(ratio: 1, **attributes)
15
+ @ratio = ratio
16
+ super(**attributes)
17
+ end
18
+
19
+ def call
20
+ wrapper_style = "position:relative;width:100%;padding-bottom:#{100.0 / ratio}%"
21
+
22
+ content_tag(:div, style: wrapper_style) do
23
+ render_element(body: content, style: inner_style)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def inner_style
30
+ [ attributes[:style], "position:absolute;top:0;right:0;bottom:0;left:0" ].compact.join(";")
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module AspectRatio
5
+ class Preview < ApplicationViewComponentPreview
6
+ def default
7
+ render_with_template
8
+ end
9
+
10
+ # Upstream's "Square" and "Portrait", beside the default's 16/9.
11
+ def ratios
12
+ render_with_template
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ <div class="w-[420px]">
2
+ <%= render(Shadcn::AspectRatio::Component.new(ratio: 16.0 / 9, class: "rounded-lg bg-muted")) do %>
3
+ <div class="flex size-full items-center justify-center text-sm text-foreground/70">16 / 9</div>
4
+ <% end %>
5
+ </div>
@@ -0,0 +1,12 @@
1
+ <%# Upstream shows "Square" and "Portrait" as separate examples; here they sit
2
+ beside the 16/9 of the default so the argument's effect is the difference
3
+ between them rather than something to remember between pages. %>
4
+ <div class="flex items-start gap-4">
5
+ <% [[1, "1 / 1"], [3.0 / 4, "3 / 4"], [16.0 / 9, "16 / 9"]].each do |ratio, label| %>
6
+ <div class="w-[180px]">
7
+ <%= render(Shadcn::AspectRatio::Component.new(ratio:, class: "rounded-lg bg-muted")) do %>
8
+ <div class="flex size-full items-center justify-center text-sm text-foreground/70"><%= label %></div>
9
+ <% end %>
10
+ </div>
11
+ <% end %>
12
+ </div>
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Attachment
5
+ module Action
6
+ # AttachmentAction — a Button with two different defaults, which is all
7
+ # upstream changes about it (attachment.tsx:144-159). Subclassed rather
8
+ # than declared with `part … from:`, because that restamps the slot and
9
+ # leaves the arguments alone, and these are arguments.
10
+ class Component < Button::Component
11
+ slot_name :"attachment-action"
12
+
13
+ def initialize(variant: :ghost, size: :"icon-xs", **attributes)
14
+ super
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Attachment
5
+ # Port of registry/new-york-v4/ui/attachment.tsx
6
+ #
7
+ # `state:` is not a cva variant upstream and is not one here: it is written
8
+ # as `data-state` and read by selectors on this element and on its children
9
+ # — `data-[state=idle]:border-dashed` here, and
10
+ # `group-data-[state=error]/attachment:…` on the media and the description
11
+ # (attachment.tsx:9 and :49). Only `size` and `orientation` choose classes.
12
+ class Component < ApplicationViewComponent
13
+ slot_name :attachment
14
+
15
+ style do
16
+ base {
17
+ "group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap " \
18
+ "rounded-xl border bg-card text-card-foreground transition-colors " \
19
+ "focus-within:ring-1 focus-within:ring-ring/50 has-[>a,>button]:hover:bg-muted/50 " \
20
+ "data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed"
21
+ }
22
+
23
+ variants {
24
+ size {
25
+ default {
26
+ "gap-2 text-sm has-data-[slot=attachment-content]:px-2.5 " \
27
+ "has-data-[slot=attachment-content]:py-2 " \
28
+ "has-data-[slot=attachment-media]:p-2"
29
+ }
30
+ sm {
31
+ "gap-2.5 text-xs has-data-[slot=attachment-content]:px-2 " \
32
+ "has-data-[slot=attachment-content]:py-1.5 " \
33
+ "has-data-[slot=attachment-media]:p-1.5"
34
+ }
35
+ xs {
36
+ "gap-1.5 rounded-lg text-xs has-data-[slot=attachment-content]:px-1.5 " \
37
+ "has-data-[slot=attachment-content]:py-1 " \
38
+ "has-data-[slot=attachment-media]:p-1"
39
+ }
40
+ }
41
+
42
+ orientation {
43
+ horizontal { "min-w-40 items-center" }
44
+ vertical { "w-24 flex-col has-data-[slot=attachment-content]:w-30" }
45
+ }
46
+ }
47
+
48
+ # Upstream's cva declares no `defaultVariants` — the defaults live on the
49
+ # function's own parameters instead (attachment.tsx:28-30). Same values,
50
+ # and here there is only one place to put them.
51
+ defaults { { size: :default, orientation: :horizontal } }
52
+ end
53
+
54
+ attr_reader :state, :size, :orientation
55
+
56
+ def initialize(state: :done, size: :default, orientation: :horizontal, **attributes)
57
+ @state = state&.to_sym || :done
58
+ @size = size&.to_sym || :default
59
+ @orientation = orientation&.to_sym || :horizontal
60
+ super(**attributes)
61
+ end
62
+
63
+ def style_variants
64
+ { size:, orientation: }
65
+ end
66
+
67
+ def element_attributes(**defaults)
68
+ super(**{
69
+ "data-state" => state,
70
+ "data-size" => size,
71
+ "data-orientation" => orientation
72
+ }.merge(defaults))
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Attachment
5
+ module Media
6
+ # AttachmentMedia — the square thumbnail: an icon, or the file itself.
7
+ class Component < ApplicationViewComponent
8
+ slot_name :"attachment-media"
9
+
10
+ style do
11
+ base {
12
+ "relative flex aspect-square w-10 shrink-0 items-center justify-center " \
13
+ "overflow-hidden rounded-lg bg-muted text-foreground " \
14
+ "group-data-[orientation=vertical]/attachment:w-full " \
15
+ "group-data-[size=sm]/attachment:w-8 group-data-[size=xs]/attachment:w-7 " \
16
+ "group-data-[size=xs]/attachment:rounded-md " \
17
+ "group-data-[state=error]/attachment:bg-destructive/10 " \
18
+ "group-data-[state=error]/attachment:text-destructive " \
19
+ "group-data-[orientation=vertical]/attachment:*:data-[slot=spinner]:size-6! " \
20
+ "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 " \
21
+ "group-data-[orientation=vertical]/attachment:[&_svg:not([class*='size-'])]:size-6 " \
22
+ "group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5"
23
+ }
24
+
25
+ variants {
26
+ variant {
27
+ icon { "" }
28
+ image {
29
+ "opacity-60 group-data-[state=done]/attachment:opacity-100 " \
30
+ "group-data-[state=idle]/attachment:opacity-100 " \
31
+ "*:[img]:aspect-square *:[img]:w-full *:[img]:object-cover"
32
+ }
33
+ }
34
+ }
35
+
36
+ defaults { { variant: :icon } }
37
+ end
38
+
39
+ attr_reader :variant
40
+
41
+ def initialize(variant: :icon, **attributes)
42
+ @variant = variant&.to_sym || :icon
43
+ super(**attributes)
44
+ end
45
+
46
+ def style_variants
47
+ { variant: }
48
+ end
49
+
50
+ def element_attributes(**defaults)
51
+ super(**{ "data-variant" => variant }.merge(defaults))
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Attachment
5
+ class Preview < ApplicationViewComponentPreview
6
+ def default
7
+ render_with_template
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,80 @@
1
+ <%# Read from upstream's own demo: an attachment is a media square beside a
2
+ content column of title and description, and the two orientations rearrange
3
+ exactly that.
4
+
5
+ `state:` is the interesting argument. It is not a variant — it picks no
6
+ classes — it is written as `data-state` and read by selectors on this
7
+ element and on its children, which is how `error` reaches the description's
8
+ colour and how `uploading` and `processing` reach the title's `shimmer`.
9
+ That last one is shadcn's own CSS rather than Tailwind's; see the section at
10
+ the end of `shadcn.css`.
11
+
12
+ The media squares carry a file extension rather than a glyph. `Icon`'s
13
+ registry holds the eleven icons the *components* need and none of them is a
14
+ document, and the alternative — inlining lucide paths for a demo — is what
15
+ the sidebar preview does only because that demo is a copy of upstream's.
16
+ Recalling an SVG path from memory is the one thing not to do here. %>
17
+ <div class="flex w-[420px] flex-col gap-6">
18
+ <%= render(Shadcn::Attachment::Component.new) do %>
19
+ <%= render(Shadcn::Attachment::Media::Component.new) do %>
20
+ <span class="text-[10px] font-medium">PDF</span>
21
+ <% end %>
22
+ <%= render(Shadcn::Attachment::Content::Component.new) do %>
23
+ <%= render(Shadcn::Attachment::Title::Component.new) { "quarterly-report.pdf" } %>
24
+ <%= render(Shadcn::Attachment::Description::Component.new) { "PDF · 2.4 MB" } %>
25
+ <% end %>
26
+ <%= render(Shadcn::Attachment::Actions::Component.new) do %>
27
+ <%= render(Shadcn::Attachment::Action::Component.new("aria-label": "Remove")) do %>
28
+ <%= render(Shadcn::Icon::Component.new("x")) %>
29
+ <% end %>
30
+ <% end %>
31
+ <% end %>
32
+
33
+ <%# `uploading` and `error` are the two states that change what you see. %>
34
+ <%= render(Shadcn::Attachment::Component.new(state: :uploading, size: :sm)) do %>
35
+ <%= render(Shadcn::Attachment::Media::Component.new) do %>
36
+ <span class="text-[10px] font-medium">TXT</span>
37
+ <% end %>
38
+ <%= render(Shadcn::Attachment::Content::Component.new) do %>
39
+ <%= render(Shadcn::Attachment::Title::Component.new) { "notes.txt" } %>
40
+ <%= render(Shadcn::Attachment::Description::Component.new) { "Uploading…" } %>
41
+ <% end %>
42
+ <% end %>
43
+
44
+ <%= render(Shadcn::Attachment::Component.new(state: :error, size: :sm)) do %>
45
+ <%= render(Shadcn::Attachment::Media::Component.new) do %>
46
+ <span class="text-[10px] font-medium">ZIP</span>
47
+ <% end %>
48
+ <%= render(Shadcn::Attachment::Content::Component.new) do %>
49
+ <%= render(Shadcn::Attachment::Title::Component.new) { "archive.zip" } %>
50
+ <%= render(Shadcn::Attachment::Description::Component.new) { "Upload failed" } %>
51
+ <% end %>
52
+ <% end %>
53
+
54
+ <%# The vertical orientation is what the group scrolls, and the trigger is
55
+ what makes a whole card clickable — stretched over it at `z-10`, under the
56
+ actions' `z-20` so a close button still wins.
57
+
58
+ Six of them, so the row genuinely overflows and `scroll-fade-x` has
59
+ something to fade. That utility is driven by a scroll timeline, and it is
60
+ the one thing on this page a screenshot taken through the suite gets
61
+ wrong: the harness injects `animation-duration: 0s !important`, which
62
+ lands both 1ms interpolations on their end value and paints a fade on the
63
+ leading edge at rest. Measured with the duration handed back —
64
+ `--scroll-fade-s` is 0 at `scrollLeft: 0` and only the trailing edge
65
+ fades, which is correct. %>
66
+ <%= render(Shadcn::Attachment::Group::Component.new) do %>
67
+ <% [ "workspace.png", "desk-reference.jpg", "office-reference.jpg",
68
+ "whiteboard.png", "sketch-01.jpg", "sketch-02.jpg" ].each do |name| %>
69
+ <%= render(Shadcn::Attachment::Component.new(orientation: :vertical)) do %>
70
+ <%= render(Shadcn::Attachment::Media::Component.new(variant: :image)) do %>
71
+ <span class="text-[10px] font-medium">IMG</span>
72
+ <% end %>
73
+ <%= render(Shadcn::Attachment::Content::Component.new) do %>
74
+ <%= render(Shadcn::Attachment::Title::Component.new) { name } %>
75
+ <% end %>
76
+ <%= render(Shadcn::Attachment::Trigger::Component.new("aria-label": "Open #{name}")) %>
77
+ <% end %>
78
+ <% end %>
79
+ <% end %>
80
+ </div>
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Attachment
5
+ module Trigger
6
+ # AttachmentTrigger — a transparent button stretched over the whole
7
+ # attachment, so the card is clickable without nesting a button inside the
8
+ # actions that already sit on top of it (`attachment-actions` carries
9
+ # `z-20` against this one's `z-10`).
10
+ #
11
+ # Its own file for the `type`: upstream sets `type="button"` only when it
12
+ # is really a `<button>`, and leaves it off when `asChild` makes it
13
+ # something else (attachment.tsx:174). `as:` is this gem's `asChild`, so
14
+ # the same condition is `tag_name == :button`.
15
+ class Component < ApplicationViewComponent
16
+ default_tag :button
17
+ slot_name :"attachment-trigger"
18
+
19
+ style do
20
+ base { "absolute inset-0 z-10 outline-none" }
21
+ end
22
+
23
+ def element_attributes(**defaults)
24
+ return super(**defaults) if tag_name.to_sym != :button
25
+
26
+ super(**{ type: "button" }.merge(defaults))
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # The parts of the Attachment 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
+ #
8
+ # `attachment-group` and `attachment-title` reach for three utilities that are
9
+ # shadcn's own CSS rather than Tailwind's — `scroll-fade-x`, `scrollbar-none`
10
+ # and `shimmer`. They are defined in `shadcn.css`; see the comment there for
11
+ # where they were read from and what could not be checked.
12
+ module Attachment
13
+ extend Parts
14
+
15
+ part :group, slot: "attachment-group",
16
+ classes: "flex min-w-0 scroll-fade-x snap-x snap-mandatory scroll-px-1 " \
17
+ "scrollbar-none gap-3 overflow-x-auto overscroll-x-contain py-1 " \
18
+ "*:data-[slot=attachment]:flex-none " \
19
+ "*:data-[slot=attachment]:snap-start"
20
+
21
+ part :content, slot: "attachment-content",
22
+ classes: "max-w-full min-w-0 flex-1 leading-tight " \
23
+ "group-data-[orientation=vertical]/attachment:px-1"
24
+
25
+ part :title, slot: "attachment-title", tag: :span,
26
+ classes: "block max-w-full min-w-0 truncate font-medium " \
27
+ "group-data-[state=processing]/attachment:shimmer " \
28
+ "group-data-[state=uploading]/attachment:shimmer"
29
+
30
+ part :description, slot: "attachment-description", tag: :span,
31
+ classes: "mt-0.5 block min-w-0 truncate text-xs text-muted-foreground " \
32
+ "group-data-[state=error]/attachment:text-destructive/80 " \
33
+ "max-w-full"
34
+
35
+ part :actions, slot: "attachment-actions",
36
+ classes: "relative z-20 flex shrink-0 items-center " \
37
+ "group-data-[orientation=vertical]/attachment:absolute " \
38
+ "group-data-[orientation=vertical]/attachment:top-3 " \
39
+ "group-data-[orientation=vertical]/attachment:right-3 " \
40
+ "group-data-[orientation=vertical]/attachment:gap-1"
41
+ end
42
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Avatar
5
+ # Port of registry/new-york-v4/ui/avatar.tsx
6
+ #
7
+ # Radix swaps the image for the fallback based on the image's loading state;
8
+ # the `shadcn--avatar` Stimulus controller does the same here.
9
+ class Component < ApplicationViewComponent
10
+ renders_one :image, "Shadcn::Avatar::Image::Component"
11
+ renders_one :fallback, "Shadcn::Avatar::Fallback::Component"
12
+ renders_one :badge, "Shadcn::Avatar::Badge::Component"
13
+
14
+ default_tag :span
15
+ slot_name :avatar
16
+
17
+ style do
18
+ base {
19
+ "group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full " \
20
+ "select-none data-[size=lg]:size-10 data-[size=sm]:size-6"
21
+ }
22
+ end
23
+
24
+ attr_reader :size
25
+
26
+ def initialize(size: :default, **attributes)
27
+ @size = size&.to_sym || :default
28
+ super(**attributes)
29
+ end
30
+
31
+ def element_attributes(**defaults)
32
+ super(**{
33
+ "data-size" => size,
34
+ "data-controller" => "shadcn--avatar"
35
+ }.merge(defaults))
36
+ end
37
+
38
+ def call
39
+ render_element(body: safe_join([ image, fallback, badge, content ].compact))
40
+ end
41
+ end
42
+ end
43
+ end