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,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Menubar
5
+ module SubTrigger
6
+ # Its own file for two differences from the dropdown's, both one token
7
+ # wide and both upstream's: `outline-none` where the dropdown writes
8
+ # `outline-hidden`, and a chevron sized `h-4 w-4` where the dropdown
9
+ # writes `size-4` (menubar.tsx:238). The same size, spelled differently —
10
+ # and `parity_spec` compares tokens, so the spelling is the port.
11
+ class Component < Shadcn::DropdownMenu::SubTrigger::Component
12
+ slot_name :"menubar-sub-trigger"
13
+
14
+ style do
15
+ base {
16
+ "flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm " \
17
+ "outline-none select-none focus:bg-accent focus:text-accent-foreground " \
18
+ "data-[inset]:pl-8 data-[state=open]:bg-accent " \
19
+ "data-[state=open]:text-accent-foreground"
20
+ }
21
+ end
22
+
23
+ private
24
+
25
+ def chevron
26
+ render(Icon::Component.new("chevron-right", class: "ml-auto h-4 w-4"))
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Menubar
5
+ module Trigger
6
+ # One name on the bar.
7
+ class Component < ApplicationViewComponent
8
+ default_tag :button
9
+ slot_name :"menubar-trigger"
10
+
11
+ style do
12
+ base {
13
+ "flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden " \
14
+ "select-none focus:bg-accent focus:text-accent-foreground " \
15
+ "data-[state=open]:bg-accent data-[state=open]:text-accent-foreground"
16
+ }
17
+ end
18
+
19
+ def element_attributes(**defaults)
20
+ super(**{
21
+ type: "button",
22
+ role: "menuitem",
23
+ "aria-haspopup" => "menu",
24
+ "aria-expanded" => "false",
25
+ "data-state" => "closed",
26
+ "data-shadcn--dropdown-menu-target" => "trigger",
27
+ "data-shadcn--menubar-target" => "trigger",
28
+ # Two controllers listening to one element: the menu's, which opens
29
+ # and closes it, and the bar's, which decides what the arrows do and
30
+ # what a hover means while another menu is already open.
31
+ "data-action" => "click->shadcn--dropdown-menu#toggle " \
32
+ "keydown->shadcn--dropdown-menu#triggerKeydown " \
33
+ "keydown->shadcn--menubar#keydown " \
34
+ "pointerenter->shadcn--menubar#pointerEnter " \
35
+ "focus->shadcn--menubar#focused"
36
+ }.merge(defaults))
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # The Menubar family — sixteen slots, and the third built on the dropdown's.
5
+ #
6
+ # Measured against `dropdown-menu.tsx`: eight carry byte-identical class
7
+ # strings once the prefix is normalised, and those are declared with `from:`.
8
+ # The rest differ for real and are written out: the items round to `rounded-xs`
9
+ # where the dropdown rounds to `rounded-sm`, the sub-trigger takes
10
+ # `outline-none` rather than `outline-hidden`, and anything reading a
11
+ # `--radix-*` property reads `--radix-menubar-*`.
12
+ module Menubar
13
+ extend Parts
14
+
15
+ part :label, slot: "menubar-label", from: DropdownMenu::Label::Component
16
+
17
+ part :separator, slot: "menubar-separator", from: DropdownMenu::Separator::Component
18
+
19
+ part :shortcut, slot: "menubar-shortcut", from: DropdownMenu::Shortcut::Component
20
+
21
+ part :group, slot: "menubar-group", from: DropdownMenu::Group::Component
22
+
23
+ part :radio_group, slot: "menubar-radio-group", from: DropdownMenu::RadioGroup::Component
24
+
25
+ part :item, slot: "menubar-item", from: DropdownMenu::Item::Component
26
+
27
+ # `rounded-xs`, where the dropdown's are `rounded-sm`. One token, and the
28
+ # only reason these are not `from:` like the rest.
29
+ part :checkbox_item, slot: "menubar-checkbox-item",
30
+ from: DropdownMenu::CheckboxItem::Component,
31
+ classes: "relative flex cursor-default items-center gap-2 rounded-xs " \
32
+ "py-1.5 pr-2 pl-8 text-sm outline-hidden select-none " \
33
+ "focus:bg-accent focus:text-accent-foreground " \
34
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50 " \
35
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 " \
36
+ "[&_svg:not([class*='size-'])]:size-4"
37
+
38
+ part :radio_item, slot: "menubar-radio-item",
39
+ from: DropdownMenu::RadioItem::Component,
40
+ classes: "relative flex cursor-default items-center gap-2 rounded-xs " \
41
+ "py-1.5 pr-2 pl-8 text-sm outline-hidden select-none " \
42
+ "focus:bg-accent focus:text-accent-foreground " \
43
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50 " \
44
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 " \
45
+ "[&_svg:not([class*='size-'])]:size-4"
46
+ end
47
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Message
5
+ # Port of registry/new-york-v4/ui/message.tsx
6
+ #
7
+ # `align:` is not a cva variant upstream — the classes are one string and the
8
+ # side is chosen by `data-[align=end]:flex-row-reverse` reading the attribute
9
+ # this writes (message.tsx:23-26). Its own file all the same, because a
10
+ # computed attribute is exactly what `part` does not do.
11
+ class Component < ApplicationViewComponent
12
+ slot_name :message
13
+
14
+ style do
15
+ base {
16
+ "group/message relative flex w-full min-w-0 gap-2 text-sm " \
17
+ "data-[align=end]:flex-row-reverse"
18
+ }
19
+ end
20
+
21
+ attr_reader :align
22
+
23
+ def initialize(align: :start, **attributes)
24
+ @align = align&.to_sym || :start
25
+ super(**attributes)
26
+ end
27
+
28
+ def element_attributes(**defaults)
29
+ super(**{ "data-align" => align }.merge(defaults))
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Message
5
+ class Preview < ApplicationViewComponentPreview
6
+ def default
7
+ render_with_template
8
+ end
9
+
10
+ # Upstream's "Actions" and "Attachment".
11
+ def actions
12
+ render_with_template
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,51 @@
1
+ <%# Upstream's "Actions" and "Attachment".
2
+
3
+ Both live in the content column rather than beside the bubble, which is
4
+ what makes them line up with it whichever way the message is aligned: the
5
+ footer holds the row of buttons, and an attachment sits above the bubble as
6
+ another block in the same column.
7
+
8
+ The buttons are `icon-xs` ghosts and each has a name of its own — an
9
+ icon-only control that says nothing is a control a screen reader cannot
10
+ offer, which is the thing upstream's own accessibility note on this page is
11
+ about. %>
12
+ <div class="flex w-[420px] flex-col gap-6">
13
+ <%= render(Shadcn::Message::Group::Component.new) do %>
14
+ <%= render(Shadcn::Message::Component.new(align: :end)) do %>
15
+ <%= render(Shadcn::Message::Content::Component.new) do %>
16
+ <%= render(Shadcn::Attachment::Component.new(size: :sm)) do %>
17
+ <%= render(Shadcn::Attachment::Media::Component.new) { "PDF" } %>
18
+ <%= render(Shadcn::Attachment::Content::Component.new) do %>
19
+ <%= render(Shadcn::Attachment::Title::Component.new) { "contract.pdf" } %>
20
+ <%= render(Shadcn::Attachment::Description::Component.new) { "184 KB" } %>
21
+ <% end %>
22
+ <% end %>
23
+ <%= render(Shadcn::Bubble::Component.new(align: :end)) do %>
24
+ <%= render(Shadcn::Bubble::Content::Component.new) { "Signed and sent back." } %>
25
+ <% end %>
26
+ <% end %>
27
+ <% end %>
28
+
29
+ <%= render(Shadcn::Message::Component.new) do %>
30
+ <%= render(Shadcn::Message::Avatar::Component.new) do %>
31
+ <%= render(Shadcn::Avatar::Component.new(class: "size-8")) do |avatar| %>
32
+ <% avatar.with_fallback { "SC" } %>
33
+ <% end %>
34
+ <% end %>
35
+ <%= render(Shadcn::Message::Content::Component.new) do %>
36
+ <%= render(Shadcn::Bubble::Component.new(variant: :muted)) do %>
37
+ <%= render(Shadcn::Bubble::Content::Component.new) { "Got it — filing it now." } %>
38
+ <% end %>
39
+ <%= render(Shadcn::Message::Footer::Component.new) do %>
40
+ <div class="flex items-center gap-1">
41
+ <% [[ "check", "Copy" ], [ "circle", "Retry" ], [ "x", "Dismiss" ]].each do |icon, label| %>
42
+ <%= render(Shadcn::Button::Component.new(variant: :ghost, size: :"icon-xs", "aria-label": label)) do %>
43
+ <%= render(Shadcn::Icon::Component.new(icon)) %>
44
+ <% end %>
45
+ <% end %>
46
+ </div>
47
+ <% end %>
48
+ <% end %>
49
+ <% end %>
50
+ <% end %>
51
+ </div>
@@ -0,0 +1,35 @@
1
+ <%# The composition upstream's own demo renders, read from it rather than
2
+ inferred from the TSX: a group of messages, each an avatar beside a
3
+ content column, and the *bubble* nested inside that column. The message
4
+ lays out the row; the bubble is what carries the surface.
5
+
6
+ `align: :end` is what turns a message around — `data-[align=end]` reverses
7
+ the row and pushes the bubble and the footer to the other side. %>
8
+ <div class="flex w-[420px] flex-col gap-6">
9
+ <%= render(Shadcn::Message::Group::Component.new) do %>
10
+ <%= render(Shadcn::Message::Component.new) do %>
11
+ <%= render(Shadcn::Message::Avatar::Component.new) do %>
12
+ <%= render(Shadcn::Avatar::Component.new(class: "size-8")) do |avatar| %>
13
+ <% avatar.with_fallback { "SC" } %>
14
+ <% end %>
15
+ <% end %>
16
+ <%= render(Shadcn::Message::Content::Component.new) do %>
17
+ <%= render(Shadcn::Bubble::Component.new(variant: :muted)) do %>
18
+ <%= render(Shadcn::Bubble::Content::Component.new) do %>
19
+ I checked the registry and the component is published.
20
+ <% end %>
21
+ <% end %>
22
+ <% end %>
23
+ <% end %>
24
+
25
+ <%= render(Shadcn::Message::Component.new(align: :end)) do %>
26
+ <%= render(Shadcn::Message::Content::Component.new) do %>
27
+ <%= render(Shadcn::Message::Header::Component.new) { "You" } %>
28
+ <%= render(Shadcn::Bubble::Component.new(align: :end)) do %>
29
+ <%= render(Shadcn::Bubble::Content::Component.new) { "Thanks — I will pull it in." } %>
30
+ <% end %>
31
+ <%= render(Shadcn::Message::Footer::Component.new) { "9:41 AM" } %>
32
+ <% end %>
33
+ <% end %>
34
+ <% end %>
35
+ </div>
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # The parts of the Message 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
+ # Five of the six are exactly that: only `Message` itself computes an
9
+ # attribute, from `align:`.
10
+ module Message
11
+ extend Parts
12
+
13
+ part :group, slot: "message-group", classes: "flex min-w-0 flex-col gap-2"
14
+
15
+ part :avatar, slot: "message-avatar",
16
+ classes: "flex w-fit min-w-8 shrink-0 items-center justify-center self-end " \
17
+ "overflow-hidden rounded-full bg-muted " \
18
+ "group-has-data-[slot=message-footer]/message:-translate-y-8"
19
+
20
+ part :content, slot: "message-content",
21
+ classes: "flex w-full min-w-0 flex-col gap-2.5 wrap-break-word " \
22
+ "group-data-[align=end]/message:*:data-slot:self-end"
23
+
24
+ part :header, slot: "message-header",
25
+ classes: "flex max-w-full min-w-0 items-center px-3 text-xs font-medium " \
26
+ "text-muted-foreground " \
27
+ "group-has-data-[variant=ghost]/message:px-0"
28
+
29
+ part :footer, slot: "message-footer",
30
+ classes: "flex max-w-full min-w-0 items-center px-3 text-xs font-medium " \
31
+ "text-muted-foreground " \
32
+ "group-has-data-[variant=ghost]/message:px-0 " \
33
+ "group-data-[align=end]/message:justify-end"
34
+ end
35
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module MessageScroller
5
+ module Button
6
+ # The floating "jump to the end" control, and its start-side twin.
7
+ #
8
+ # A Button with different defaults — `secondary`, `icon-sm` — plus the
9
+ # positioning and the transition, so it subclasses rather than being
10
+ # declared with `part … from:`, which restamps the slot and leaves the
11
+ # arguments alone.
12
+ #
13
+ # `data-active` is the controller's to write and decides everything
14
+ # visible: the button is not hidden when there is nowhere to scroll, it is
15
+ # scaled, faded and pushed off its edge, with a slower easing on the way
16
+ # out than on the way in. Rendered `false` so it starts out of sight
17
+ # without a frame of flicker before the controller connects.
18
+ class Component < Shadcn::Button::Component
19
+ slot_name :"message-scroller-button"
20
+
21
+ style do
22
+ base {
23
+ "absolute inset-s-1/2 -translate-x-1/2 border-border bg-background " \
24
+ "text-foreground transition-[translate,scale,opacity] duration-200 " \
25
+ "hover:bg-muted hover:text-foreground " \
26
+ "data-[active=false]:pointer-events-none data-[active=false]:scale-95 " \
27
+ "data-[active=false]:opacity-0 data-[active=false]:duration-400 " \
28
+ "data-[active=false]:ease-[cubic-bezier(0.7,0,0.84,0)] " \
29
+ "data-[active=true]:translate-y-0 data-[active=true]:scale-100 " \
30
+ "data-[active=true]:opacity-100 " \
31
+ "data-[active=true]:ease-[cubic-bezier(0.23,1,0.32,1)] " \
32
+ "data-[direction=end]:bottom-4 " \
33
+ "data-[direction=end]:data-[active=false]:translate-y-full " \
34
+ "data-[direction=start]:top-4 " \
35
+ "data-[direction=start]:data-[active=false]:-translate-y-full " \
36
+ "rtl:translate-x-1/2 data-[direction=start]:[&_svg]:rotate-180"
37
+ }
38
+ end
39
+
40
+ attr_reader :direction
41
+
42
+ def initialize(direction: :end, variant: :secondary, size: :"icon-sm", **attributes)
43
+ @direction = direction&.to_sym || :end
44
+ super(variant:, size:, **attributes)
45
+ end
46
+
47
+ def element_attributes(**defaults)
48
+ super(**{
49
+ "data-direction" => direction,
50
+ "data-active" => "false",
51
+ # Out of the tab order while there is nowhere to scroll, as the
52
+ # primitive does (components.tsx:400). It is invisible and
53
+ # `pointer-events-none` in that state, so leaving it tabbable would
54
+ # give a keyboard a stop that a pointer does not have.
55
+ tabindex: -1,
56
+ "data-shadcn--message-scroller-target" => "button",
57
+ "data-action" => "shadcn--message-scroller#jump"
58
+ }.merge(defaults))
59
+ end
60
+
61
+ # The chevron and its name. Upstream renders one arrow and rotates it for
62
+ # the start direction (message-scroller.tsx:108-115), which is why there
63
+ # is one icon here and a `rotate-180` in the classes above.
64
+ def call
65
+ return render_element(body: content) if content.present?
66
+
67
+ render_element(
68
+ body: safe_join([
69
+ render(Shadcn::Icon::Component.new("chevron-down")),
70
+ tag.span(shadcn_t("message_scroller.#{direction == :end ? 'scroll_to_end' : 'scroll_to_start'}"),
71
+ class: "sr-only")
72
+ ])
73
+ )
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module MessageScroller
5
+ # Port of registry/new-york-v4/ui/message-scroller.tsx, which is a thin
6
+ # wrapper over `@shadcn/react/message-scroller` — vendored at
7
+ # `vendor/shadcn-react/` so the Stimulus port has a source to answer to.
8
+ #
9
+ # Upstream's `MessageScrollerProvider` renders no DOM at all: it is pure
10
+ # context holding the five options below (components.tsx:89-107). There is
11
+ # nothing to reproduce, so the options are Stimulus values and the
12
+ # controller lives on this element — the same answer `useSidebar` got.
13
+ class Component < ApplicationViewComponent
14
+ slot_name :"message-scroller"
15
+
16
+ style do
17
+ base {
18
+ "group/message-scroller relative flex size-full min-h-0 flex-col overflow-hidden"
19
+ }
20
+ end
21
+
22
+ attr_reader :auto_scroll, :default_scroll_position, :scroll_edge_threshold,
23
+ :scroll_margin, :scroll_previous_item_peek
24
+
25
+ # Defaults are upstream's own (components.tsx:89-96). `scrollEdgeThreshold`,
26
+ # `scrollMargin` and `scrollPreviousItemPeek` are undefined there and fall
27
+ # back inside the primitive; passing nil here leaves the value off the
28
+ # element so the controller's own default applies, rather than baking a
29
+ # number this file cannot check.
30
+ def initialize(auto_scroll: false, default_scroll_position: :end,
31
+ scroll_edge_threshold: nil, scroll_margin: nil,
32
+ scroll_previous_item_peek: nil, **attributes)
33
+ @auto_scroll = auto_scroll
34
+ @default_scroll_position = default_scroll_position
35
+ @scroll_edge_threshold = scroll_edge_threshold
36
+ @scroll_margin = scroll_margin
37
+ @scroll_previous_item_peek = scroll_previous_item_peek
38
+ super(**attributes)
39
+ end
40
+
41
+ def element_attributes(**defaults)
42
+ super(**values.merge(defaults))
43
+ end
44
+
45
+ private
46
+
47
+ def values
48
+ {
49
+ "data-controller" => "shadcn--message-scroller",
50
+ "data-shadcn--message-scroller-auto-scroll-value" => auto_scroll,
51
+ "data-shadcn--message-scroller-default-scroll-position-value" => default_scroll_position,
52
+ "data-shadcn--message-scroller-scroll-edge-threshold-value" => scroll_edge_threshold,
53
+ "data-shadcn--message-scroller-scroll-margin-value" => scroll_margin,
54
+ "data-shadcn--message-scroller-scroll-previous-item-peek-value" => scroll_previous_item_peek
55
+ }.compact
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module MessageScroller
5
+ module Content
6
+ # The column of messages, and the tail spacer under it.
7
+ #
8
+ # The spacer is not decoration and not a slot: it is a hidden sibling of
9
+ # the rows whose height the controller sets, so the last message can be
10
+ # scrolled to the *top* of the viewport rather than stopping at the bottom
11
+ # (components.tsx:301-307). It is also the reason `scroll_geometry.js`
12
+ # measures the rows instead of reading `scrollHeight`, which the spacer
13
+ # inflates.
14
+ #
15
+ # `role="log"` with `aria-relevant="additions"` is upstream's default and
16
+ # is what makes an arriving message announce itself. Both are defaults, so
17
+ # a caller who knows better can say so.
18
+ class Component < ApplicationViewComponent
19
+ slot_name :"message-scroller-content"
20
+
21
+ style do
22
+ base { "flex h-max min-h-full flex-col gap-8" }
23
+ end
24
+
25
+ def element_attributes(**defaults)
26
+ super(**{
27
+ role: "log",
28
+ "aria-relevant" => "additions",
29
+ "data-shadcn--message-scroller-target" => "content"
30
+ }.merge(defaults))
31
+ end
32
+
33
+ def call
34
+ render_element(body: safe_join([ content, spacer ]))
35
+ end
36
+
37
+ private
38
+
39
+ def spacer
40
+ tag.div(
41
+ "aria-hidden": true,
42
+ "data-message-scroller-spacer": "",
43
+ "data-shadcn--message-scroller-target": "spacer",
44
+ hidden: true
45
+ )
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module MessageScroller
5
+ module Item
6
+ # One row. Its two attributes are the markup's half of the contract with
7
+ # `scroll_geometry.js`, which reads `dataset.messageId` and
8
+ # `dataset.scrollAnchor` — so they are rendered whether or not a controller
9
+ # is present.
10
+ #
11
+ # `data-scroll-anchor` is written as the string `"false"` rather than
12
+ # omitted, exactly as upstream does (components.tsx:340). Nothing here
13
+ # depends on the difference, but a host's CSS might.
14
+ #
15
+ # `content-visibility: auto` lets the browser skip laying out rows that are
16
+ # off screen, and `contain-intrinsic-size` is the guess it uses for their
17
+ # height until it does. They arrive as arbitrary properties because
18
+ # Tailwind has no utility for either.
19
+ class Component < ApplicationViewComponent
20
+ slot_name :"message-scroller-item"
21
+
22
+ style do
23
+ base {
24
+ "min-w-0 shrink-0 [contain-intrinsic-size:auto_10rem] [content-visibility:auto]"
25
+ }
26
+ end
27
+
28
+ attr_reader :message_id, :scroll_anchor
29
+
30
+ def initialize(message_id: nil, scroll_anchor: false, **attributes)
31
+ @message_id = message_id
32
+ @scroll_anchor = scroll_anchor
33
+ super(**attributes)
34
+ end
35
+
36
+ def element_attributes(**defaults)
37
+ super(**{
38
+ "data-message-id" => message_id,
39
+ "data-scroll-anchor" => scroll_anchor.to_s
40
+ }.compact.merge(defaults))
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module MessageScroller
5
+ class Preview < ApplicationViewComponentPreview
6
+ def default
7
+ render_with_template
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,72 @@
1
+ <%# The markup only. The Stimulus controller does not exist yet, so nothing
2
+ here follows the live end, sizes the tail spacer or lights the buttons —
3
+ both stay at `data-active="false"`, which is how they render.
4
+
5
+ That is deliberate rather than unfinished: the rows carry `data-message-id`
6
+ and `data-scroll-anchor`, which is the markup's half of the contract
7
+ `shadcn/scroll_geometry.js` already reads, and building the controller
8
+ against real markup beats inventing target names for it. See
9
+ `.claude/docs/features/message-scroller.md`.
10
+
11
+ The height is the preview's, not the component's: the scroller is
12
+ `size-full`, so it takes whatever it is put in — and with no height at all
13
+ there is nothing to scroll and nothing to see. %>
14
+ <%# `bg-card`, and that is not decoration. The buttons are `bg-background` with
15
+ no border width — upstream's own string (message-scroller.tsx:102), and the
16
+ `new-york-v4` Button's `secondary` variant adds no border either — so on a
17
+ surface that is *also* `--background` they have no visible edge at all. They
18
+ are meant to float over a panel, which is what a chat log sits in.
19
+
20
+ Worth knowing before comparing this to ui.shadcn.com: that page now serves
21
+ the Base UI registry, whose Button is `rounded-2xl border`. Measured on the
22
+ live demo. Its buttons look different because it is a different Button, not
23
+ because this port dropped something. %>
24
+ <div class="h-[420px] w-[420px] rounded-xl border bg-card">
25
+ <%= render(Shadcn::MessageScroller::Component.new(auto_scroll: true)) do %>
26
+ <%= render(Shadcn::MessageScroller::Viewport::Component.new) do %>
27
+ <%= render(Shadcn::MessageScroller::Content::Component.new(class: "p-4")) do %>
28
+ <%# Enough of them to overflow the frame. A scroller whose content
29
+ fits has nothing to follow and nothing to publish: `scrollHeight`
30
+ equals `clientHeight`, `data-scrollable` never appears, and every
31
+ assertion about it passes for the wrong reason. %>
32
+ <% [
33
+ [ "Is the registry entry published?", :start ],
34
+ [ "It is — the component and the example both went out this morning.", :end ],
35
+ [ "Good. What changed in the last release?", :start ],
36
+ [ "The scroller keeps the viewport steady when older messages load above it.", :end ],
37
+ [ "That is the part everyone gets wrong.", :start ],
38
+ [ "It is also the part with the most tests upstream.", :end ],
39
+ [ "Does it still follow the end while a reply streams in?", :start ],
40
+ [ "It does, and it stops the moment you scroll up to read.", :end ],
41
+ [ "What brings it back?", :start ],
42
+ [ "The button at the bottom, or reaching the end yourself.", :end ],
43
+ [ "And the tail spacer?", :start ],
44
+ [ "It is what lets the last message reach the top instead of the bottom.", :end ]
45
+ ].each_with_index do |(text, align), index| %>
46
+ <% id = "m#{index + 1}" %>
47
+ <%# No row here is anchored, deliberately. `scroll_anchor: true` marks
48
+ a turn the scroller takes to the *top* of the viewport, and it
49
+ acts on any anchor it has not handled yet — so a server-rendered
50
+ one is jumped to on load and then holds following off, which is
51
+ correct and makes a fixture that says nothing about anything else.
52
+ The behaviour is covered where it actually happens, on a turn that
53
+ arrives: see `spec/system/message_scroller_spec.rb`. %>
54
+ <%= render(Shadcn::MessageScroller::Item::Component.new(message_id: id)) do %>
55
+ <%= render(Shadcn::Message::Component.new(align:)) do %>
56
+ <%= render(Shadcn::Message::Content::Component.new) do %>
57
+ <%= render(Shadcn::Bubble::Component.new(
58
+ variant: align == :end ? :default : :muted, align:
59
+ )) do %>
60
+ <%= render(Shadcn::Bubble::Content::Component.new) { text } %>
61
+ <% end %>
62
+ <% end %>
63
+ <% end %>
64
+ <% end %>
65
+ <% end %>
66
+ <% end %>
67
+ <% end %>
68
+
69
+ <%= render(Shadcn::MessageScroller::Button::Component.new(direction: :start)) %>
70
+ <%= render(Shadcn::MessageScroller::Button::Component.new) %>
71
+ <% end %>
72
+ </div>
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module MessageScroller
5
+ module Viewport
6
+ # The scroll container. Three of its classes are shadcn's own CSS rather
7
+ # than Tailwind's — `scroll-fade-b`, `scrollbar-thin`,
8
+ # `scrollbar-gutter-stable` — reproduced at the end of `shadcn.css`;
9
+ # without them these would be inert and `parity_spec`, which compares class
10
+ # text and not generated CSS, would not notice.
11
+ #
12
+ # `data-autoscrolling:scrollbar-none` is the one variant here that reads an
13
+ # attribute the controller writes: the scrollbar is hidden while a
14
+ # programmatic scroll is in flight, so a smooth jump does not flicker one
15
+ # in and out.
16
+ class Component < ApplicationViewComponent
17
+ slot_name :"message-scroller-viewport"
18
+
19
+ style do
20
+ base {
21
+ "size-full min-h-0 min-w-0 scroll-fade-b scrollbar-thin " \
22
+ "scrollbar-gutter-stable overflow-y-auto overscroll-contain " \
23
+ "contain-content data-autoscrolling:scrollbar-none"
24
+ }
25
+ end
26
+
27
+ # `tabindex: 0` is the primitive's (components.tsx:209) and is not
28
+ # decoration: a scrollable region with no focusable content inside it
29
+ # cannot be scrolled from a keyboard at all unless it is focusable
30
+ # itself. axe fails the page without it, which is how the omission was
31
+ # found.
32
+ def element_attributes(**defaults)
33
+ super(**{
34
+ tabindex: 0,
35
+ "data-shadcn--message-scroller-target" => "viewport"
36
+ }.merge(defaults))
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end