shadcn-rails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +40 -0
  3. data/CHANGELOG.md +54 -0
  4. data/CLAUDE.md +463 -0
  5. data/PROGRESS.md +485 -0
  6. data/README.md +1483 -0
  7. data/Rakefile +29 -0
  8. data/__tests__/controllers/__snapshots__/calendar_controller.test.js.snap +13 -0
  9. data/__tests__/controllers/__snapshots__/popover_controller.test.js.snap +46 -0
  10. data/__tests__/controllers/__snapshots__/sheet_controller.test.js.snap +111 -0
  11. data/__tests__/controllers/__snapshots__/tabs_controller.test.js.snap +27 -0
  12. data/__tests__/controllers/accordion_controller.test.js +904 -0
  13. data/__tests__/controllers/calendar_controller.test.js +1370 -0
  14. data/__tests__/controllers/carousel_controller.test.js +912 -0
  15. data/__tests__/controllers/checkbox_controller.test.js +454 -0
  16. data/__tests__/controllers/collapsible_controller.test.js +407 -0
  17. data/__tests__/controllers/combobox_controller.test.js +966 -0
  18. data/__tests__/controllers/context_menu_controller.test.js +627 -0
  19. data/__tests__/controllers/date_picker_controller.test.js +636 -0
  20. data/__tests__/controllers/dialog_controller.test.js +878 -0
  21. data/__tests__/controllers/drawer_controller.test.js +995 -0
  22. data/__tests__/controllers/menubar_controller.test.js +736 -0
  23. data/__tests__/controllers/navigation_menu_controller.test.js +598 -0
  24. data/__tests__/controllers/popover_controller.test.js +1007 -0
  25. data/__tests__/controllers/radio_group_controller.test.js +640 -0
  26. data/__tests__/controllers/resizable_controller.test.js +680 -0
  27. data/__tests__/controllers/select_controller.test.js +674 -0
  28. data/__tests__/controllers/sheet_controller.test.js +986 -0
  29. data/__tests__/controllers/slider_controller.test.js +1036 -0
  30. data/__tests__/controllers/switch_controller.test.js +424 -0
  31. data/__tests__/controllers/tabs_controller.test.js +907 -0
  32. data/__tests__/controllers/toggle_group_controller.test.js +839 -0
  33. data/__tests__/controllers/tooltip_controller.test.js +808 -0
  34. data/__tests__/helpers/stimulus-test-helper.js +203 -0
  35. data/app/assets/config/manifest.js +1 -0
  36. data/app/assets/javascripts/shadcn/controllers/accordion_controller.d.ts +53 -0
  37. data/app/assets/javascripts/shadcn/controllers/accordion_controller.js +140 -0
  38. data/app/assets/javascripts/shadcn/controllers/avatar_controller.d.ts +22 -0
  39. data/app/assets/javascripts/shadcn/controllers/avatar_controller.js +26 -0
  40. data/app/assets/javascripts/shadcn/controllers/calendar_controller.js +592 -0
  41. data/app/assets/javascripts/shadcn/controllers/carousel_controller.js +263 -0
  42. data/app/assets/javascripts/shadcn/controllers/checkbox_controller.d.ts +31 -0
  43. data/app/assets/javascripts/shadcn/controllers/checkbox_controller.js +48 -0
  44. data/app/assets/javascripts/shadcn/controllers/collapsible_controller.d.ts +43 -0
  45. data/app/assets/javascripts/shadcn/controllers/collapsible_controller.js +73 -0
  46. data/app/assets/javascripts/shadcn/controllers/combobox_controller.js +234 -0
  47. data/app/assets/javascripts/shadcn/controllers/command_controller.js +141 -0
  48. data/app/assets/javascripts/shadcn/controllers/command_dialog_controller.js +162 -0
  49. data/app/assets/javascripts/shadcn/controllers/context_menu_controller.js +202 -0
  50. data/app/assets/javascripts/shadcn/controllers/date_picker_controller.js +282 -0
  51. data/app/assets/javascripts/shadcn/controllers/dialog_controller.d.ts +67 -0
  52. data/app/assets/javascripts/shadcn/controllers/dialog_controller.js +187 -0
  53. data/app/assets/javascripts/shadcn/controllers/drawer_controller.d.ts +58 -0
  54. data/app/assets/javascripts/shadcn/controllers/drawer_controller.js +112 -0
  55. data/app/assets/javascripts/shadcn/controllers/dropdown_controller.d.ts +83 -0
  56. data/app/assets/javascripts/shadcn/controllers/dropdown_controller.js +225 -0
  57. data/app/assets/javascripts/shadcn/controllers/hover_card_controller.d.ts +59 -0
  58. data/app/assets/javascripts/shadcn/controllers/hover_card_controller.js +143 -0
  59. data/app/assets/javascripts/shadcn/controllers/input_otp_controller.d.ts +44 -0
  60. data/app/assets/javascripts/shadcn/controllers/input_otp_controller.js +206 -0
  61. data/app/assets/javascripts/shadcn/controllers/menubar_controller.js +323 -0
  62. data/app/assets/javascripts/shadcn/controllers/navigation_menu_controller.js +251 -0
  63. data/app/assets/javascripts/shadcn/controllers/popover_controller.d.ts +56 -0
  64. data/app/assets/javascripts/shadcn/controllers/popover_controller.js +141 -0
  65. data/app/assets/javascripts/shadcn/controllers/radio_group_controller.d.ts +47 -0
  66. data/app/assets/javascripts/shadcn/controllers/radio_group_controller.js +108 -0
  67. data/app/assets/javascripts/shadcn/controllers/resizable_controller.js +272 -0
  68. data/app/assets/javascripts/shadcn/controllers/scroll_area_controller.d.ts +44 -0
  69. data/app/assets/javascripts/shadcn/controllers/scroll_area_controller.js +74 -0
  70. data/app/assets/javascripts/shadcn/controllers/select_controller.d.ts +84 -0
  71. data/app/assets/javascripts/shadcn/controllers/select_controller.js +222 -0
  72. data/app/assets/javascripts/shadcn/controllers/sheet_controller.d.ts +60 -0
  73. data/app/assets/javascripts/shadcn/controllers/sheet_controller.js +151 -0
  74. data/app/assets/javascripts/shadcn/controllers/sidebar_controller.js +148 -0
  75. data/app/assets/javascripts/shadcn/controllers/slider_controller.d.ts +102 -0
  76. data/app/assets/javascripts/shadcn/controllers/slider_controller.js +364 -0
  77. data/app/assets/javascripts/shadcn/controllers/switch_controller.d.ts +46 -0
  78. data/app/assets/javascripts/shadcn/controllers/switch_controller.js +78 -0
  79. data/app/assets/javascripts/shadcn/controllers/tabs_controller.d.ts +51 -0
  80. data/app/assets/javascripts/shadcn/controllers/tabs_controller.js +126 -0
  81. data/app/assets/javascripts/shadcn/controllers/toast_controller.d.ts +37 -0
  82. data/app/assets/javascripts/shadcn/controllers/toast_controller.js +58 -0
  83. data/app/assets/javascripts/shadcn/controllers/toggle_controller.d.ts +27 -0
  84. data/app/assets/javascripts/shadcn/controllers/toggle_controller.js +42 -0
  85. data/app/assets/javascripts/shadcn/controllers/toggle_group_controller.d.ts +44 -0
  86. data/app/assets/javascripts/shadcn/controllers/toggle_group_controller.js +68 -0
  87. data/app/assets/javascripts/shadcn/controllers/tooltip_controller.d.ts +56 -0
  88. data/app/assets/javascripts/shadcn/controllers/tooltip_controller.js +117 -0
  89. data/app/assets/javascripts/shadcn/index.d.ts +74 -0
  90. data/app/assets/javascripts/shadcn/index.js +133 -0
  91. data/app/assets/stylesheets/.keep +0 -0
  92. data/app/assets/stylesheets/shadcn/base.css +445 -0
  93. data/app/assets/stylesheets/shadcn/components.css +513 -0
  94. data/app/assets/stylesheets/shadcn/index.css +18 -0
  95. data/app/assets/stylesheets/shadcn/themes/gray.css +68 -0
  96. data/app/assets/stylesheets/shadcn/themes/slate.css +68 -0
  97. data/app/assets/stylesheets/shadcn/themes/stone.css +68 -0
  98. data/app/assets/stylesheets/shadcn/themes/zinc.css +68 -0
  99. data/app/components/shadcn/accordion_component.rb +63 -0
  100. data/app/components/shadcn/accordion_content_component.rb +29 -0
  101. data/app/components/shadcn/accordion_item_component.rb +40 -0
  102. data/app/components/shadcn/accordion_trigger_component.rb +49 -0
  103. data/app/components/shadcn/alert_component.rb +75 -0
  104. data/app/components/shadcn/alert_description_component.rb +12 -0
  105. data/app/components/shadcn/alert_dialog_action_component.rb +24 -0
  106. data/app/components/shadcn/alert_dialog_cancel_component.rb +24 -0
  107. data/app/components/shadcn/alert_dialog_component.rb +71 -0
  108. data/app/components/shadcn/alert_dialog_content_component.rb +57 -0
  109. data/app/components/shadcn/alert_dialog_description_component.rb +12 -0
  110. data/app/components/shadcn/alert_dialog_footer_component.rb +19 -0
  111. data/app/components/shadcn/alert_dialog_header_component.rb +19 -0
  112. data/app/components/shadcn/alert_dialog_title_component.rb +12 -0
  113. data/app/components/shadcn/alert_title_component.rb +12 -0
  114. data/app/components/shadcn/aspect_ratio_component.rb +49 -0
  115. data/app/components/shadcn/avatar_component.rb +107 -0
  116. data/app/components/shadcn/avatar_fallback_component.rb +17 -0
  117. data/app/components/shadcn/badge_component.rb +49 -0
  118. data/app/components/shadcn/base_component.rb +100 -0
  119. data/app/components/shadcn/breadcrumb_component.rb +70 -0
  120. data/app/components/shadcn/breadcrumb_item_component.rb +50 -0
  121. data/app/components/shadcn/button_component.rb +141 -0
  122. data/app/components/shadcn/button_group_component.rb +69 -0
  123. data/app/components/shadcn/calendar_component.rb +337 -0
  124. data/app/components/shadcn/card_action_component.rb +10 -0
  125. data/app/components/shadcn/card_component.rb +63 -0
  126. data/app/components/shadcn/card_content_component.rb +19 -0
  127. data/app/components/shadcn/card_description_component.rb +12 -0
  128. data/app/components/shadcn/card_footer_component.rb +12 -0
  129. data/app/components/shadcn/card_header_component.rb +24 -0
  130. data/app/components/shadcn/card_title_component.rb +18 -0
  131. data/app/components/shadcn/carousel_component.rb +275 -0
  132. data/app/components/shadcn/checkbox_component.rb +103 -0
  133. data/app/components/shadcn/collapsible_component.rb +66 -0
  134. data/app/components/shadcn/collapsible_content_component.rb +28 -0
  135. data/app/components/shadcn/combobox_component.rb +322 -0
  136. data/app/components/shadcn/command_component.rb +52 -0
  137. data/app/components/shadcn/command_dialog_component.rb +76 -0
  138. data/app/components/shadcn/command_empty_component.rb +12 -0
  139. data/app/components/shadcn/command_group_component.rb +34 -0
  140. data/app/components/shadcn/command_input_component.rb +59 -0
  141. data/app/components/shadcn/command_item_component.rb +48 -0
  142. data/app/components/shadcn/command_list_component.rb +38 -0
  143. data/app/components/shadcn/command_separator_component.rb +12 -0
  144. data/app/components/shadcn/command_shortcut_component.rb +12 -0
  145. data/app/components/shadcn/context_menu_component.rb +64 -0
  146. data/app/components/shadcn/context_menu_content_component.rb +44 -0
  147. data/app/components/shadcn/context_menu_item_component.rb +63 -0
  148. data/app/components/shadcn/context_menu_label_component.rb +18 -0
  149. data/app/components/shadcn/context_menu_separator_component.rb +12 -0
  150. data/app/components/shadcn/context_menu_shortcut_component.rb +12 -0
  151. data/app/components/shadcn/date_picker_component.rb +368 -0
  152. data/app/components/shadcn/dialog_component.rb +77 -0
  153. data/app/components/shadcn/dialog_content_component.rb +91 -0
  154. data/app/components/shadcn/dialog_description_component.rb +12 -0
  155. data/app/components/shadcn/dialog_footer_component.rb +12 -0
  156. data/app/components/shadcn/dialog_header_component.rb +19 -0
  157. data/app/components/shadcn/dialog_title_component.rb +12 -0
  158. data/app/components/shadcn/drawer_component.rb +72 -0
  159. data/app/components/shadcn/drawer_content_component.rb +76 -0
  160. data/app/components/shadcn/drawer_description_component.rb +12 -0
  161. data/app/components/shadcn/drawer_footer_component.rb +12 -0
  162. data/app/components/shadcn/drawer_header_component.rb +19 -0
  163. data/app/components/shadcn/drawer_title_component.rb +12 -0
  164. data/app/components/shadcn/dropdown_menu_component.rb +75 -0
  165. data/app/components/shadcn/dropdown_menu_content_component.rb +49 -0
  166. data/app/components/shadcn/dropdown_menu_group_component.rb +10 -0
  167. data/app/components/shadcn/dropdown_menu_item_component.rb +63 -0
  168. data/app/components/shadcn/dropdown_menu_label_component.rb +18 -0
  169. data/app/components/shadcn/dropdown_menu_separator_component.rb +12 -0
  170. data/app/components/shadcn/dropdown_menu_shortcut_component.rb +12 -0
  171. data/app/components/shadcn/empty_component.rb +48 -0
  172. data/app/components/shadcn/empty_content_component.rb +12 -0
  173. data/app/components/shadcn/empty_description_component.rb +12 -0
  174. data/app/components/shadcn/empty_header_component.rb +29 -0
  175. data/app/components/shadcn/empty_media_component.rb +21 -0
  176. data/app/components/shadcn/empty_title_component.rb +12 -0
  177. data/app/components/shadcn/field_component.rb +113 -0
  178. data/app/components/shadcn/hover_card_component.rb +64 -0
  179. data/app/components/shadcn/hover_card_content_component.rb +36 -0
  180. data/app/components/shadcn/input_component.rb +108 -0
  181. data/app/components/shadcn/input_group_component.rb +70 -0
  182. data/app/components/shadcn/input_otp_component.rb +183 -0
  183. data/app/components/shadcn/item_actions_component.rb +12 -0
  184. data/app/components/shadcn/item_component.rb +98 -0
  185. data/app/components/shadcn/item_content_component.rb +24 -0
  186. data/app/components/shadcn/item_description_component.rb +12 -0
  187. data/app/components/shadcn/item_footer_component.rb +12 -0
  188. data/app/components/shadcn/item_group_component.rb +24 -0
  189. data/app/components/shadcn/item_header_component.rb +12 -0
  190. data/app/components/shadcn/item_media_component.rb +22 -0
  191. data/app/components/shadcn/item_separator_component.rb +12 -0
  192. data/app/components/shadcn/item_title_component.rb +12 -0
  193. data/app/components/shadcn/kbd_component.rb +36 -0
  194. data/app/components/shadcn/label_component.rb +49 -0
  195. data/app/components/shadcn/menubar_checkbox_item_component.rb +76 -0
  196. data/app/components/shadcn/menubar_component.rb +56 -0
  197. data/app/components/shadcn/menubar_content_component.rb +64 -0
  198. data/app/components/shadcn/menubar_item_component.rb +65 -0
  199. data/app/components/shadcn/menubar_label_component.rb +27 -0
  200. data/app/components/shadcn/menubar_menu_component.rb +34 -0
  201. data/app/components/shadcn/menubar_radio_group_component.rb +42 -0
  202. data/app/components/shadcn/menubar_radio_item_component.rb +76 -0
  203. data/app/components/shadcn/menubar_separator_component.rb +22 -0
  204. data/app/components/shadcn/menubar_shortcut_component.rb +21 -0
  205. data/app/components/shadcn/menubar_sub_component.rb +38 -0
  206. data/app/components/shadcn/menubar_sub_content_component.rb +45 -0
  207. data/app/components/shadcn/menubar_sub_trigger_component.rb +59 -0
  208. data/app/components/shadcn/menubar_trigger_component.rb +31 -0
  209. data/app/components/shadcn/native_select_component.rb +150 -0
  210. data/app/components/shadcn/navigation_menu_component.rb +76 -0
  211. data/app/components/shadcn/navigation_menu_content_component.rb +30 -0
  212. data/app/components/shadcn/navigation_menu_item_component.rb +39 -0
  213. data/app/components/shadcn/navigation_menu_link_component.rb +38 -0
  214. data/app/components/shadcn/navigation_menu_list_component.rb +29 -0
  215. data/app/components/shadcn/navigation_menu_trigger_component.rb +59 -0
  216. data/app/components/shadcn/pagination_component.rb +195 -0
  217. data/app/components/shadcn/pagination_content_component.rb +47 -0
  218. data/app/components/shadcn/pagination_ellipsis_component.rb +30 -0
  219. data/app/components/shadcn/pagination_item_component.rb +53 -0
  220. data/app/components/shadcn/pagination_next_component.rb +48 -0
  221. data/app/components/shadcn/pagination_previous_component.rb +48 -0
  222. data/app/components/shadcn/popover_component.rb +76 -0
  223. data/app/components/shadcn/popover_content_component.rb +25 -0
  224. data/app/components/shadcn/progress_component.rb +77 -0
  225. data/app/components/shadcn/radio_group_component.rb +129 -0
  226. data/app/components/shadcn/radio_group_item_component.rb +109 -0
  227. data/app/components/shadcn/resizable_handle_component.rb +98 -0
  228. data/app/components/shadcn/resizable_panel_component.rb +56 -0
  229. data/app/components/shadcn/resizable_panel_group_component.rb +94 -0
  230. data/app/components/shadcn/scroll_area_component.rb +110 -0
  231. data/app/components/shadcn/select_component.rb +151 -0
  232. data/app/components/shadcn/select_group_component.rb +32 -0
  233. data/app/components/shadcn/select_item_component.rb +59 -0
  234. data/app/components/shadcn/select_separator_component.rb +12 -0
  235. data/app/components/shadcn/separator_component.rb +54 -0
  236. data/app/components/shadcn/sheet_component.rb +82 -0
  237. data/app/components/shadcn/sheet_content_component.rb +95 -0
  238. data/app/components/shadcn/sheet_description_component.rb +12 -0
  239. data/app/components/shadcn/sheet_footer_component.rb +12 -0
  240. data/app/components/shadcn/sheet_header_component.rb +19 -0
  241. data/app/components/shadcn/sheet_title_component.rb +12 -0
  242. data/app/components/shadcn/sidebar_component.rb +180 -0
  243. data/app/components/shadcn/sidebar_content_component.rb +32 -0
  244. data/app/components/shadcn/sidebar_footer_component.rb +24 -0
  245. data/app/components/shadcn/sidebar_group_action_component.rb +26 -0
  246. data/app/components/shadcn/sidebar_group_component.rb +38 -0
  247. data/app/components/shadcn/sidebar_group_content_component.rb +32 -0
  248. data/app/components/shadcn/sidebar_group_label_component.rb +25 -0
  249. data/app/components/shadcn/sidebar_header_component.rb +24 -0
  250. data/app/components/shadcn/sidebar_inset_component.rb +25 -0
  251. data/app/components/shadcn/sidebar_menu_action_component.rb +37 -0
  252. data/app/components/shadcn/sidebar_menu_badge_component.rb +25 -0
  253. data/app/components/shadcn/sidebar_menu_button_component.rb +52 -0
  254. data/app/components/shadcn/sidebar_menu_component.rb +32 -0
  255. data/app/components/shadcn/sidebar_menu_item_component.rb +41 -0
  256. data/app/components/shadcn/sidebar_menu_skeleton_component.rb +46 -0
  257. data/app/components/shadcn/sidebar_menu_sub_button_component.rb +43 -0
  258. data/app/components/shadcn/sidebar_menu_sub_component.rb +33 -0
  259. data/app/components/shadcn/sidebar_menu_sub_item_component.rb +30 -0
  260. data/app/components/shadcn/sidebar_provider_component.rb +57 -0
  261. data/app/components/shadcn/sidebar_rail_component.rb +30 -0
  262. data/app/components/shadcn/sidebar_separator_component.rb +24 -0
  263. data/app/components/shadcn/sidebar_trigger_component.rb +51 -0
  264. data/app/components/shadcn/skeleton_component.rb +29 -0
  265. data/app/components/shadcn/slider_component.rb +76 -0
  266. data/app/components/shadcn/spinner_component.rb +67 -0
  267. data/app/components/shadcn/switch_component.rb +147 -0
  268. data/app/components/shadcn/table_body_component.rb +16 -0
  269. data/app/components/shadcn/table_caption_component.rb +12 -0
  270. data/app/components/shadcn/table_cell_component.rb +12 -0
  271. data/app/components/shadcn/table_component.rb +57 -0
  272. data/app/components/shadcn/table_footer_component.rb +16 -0
  273. data/app/components/shadcn/table_head_component.rb +12 -0
  274. data/app/components/shadcn/table_header_component.rb +16 -0
  275. data/app/components/shadcn/table_row_component.rb +40 -0
  276. data/app/components/shadcn/tabs_component.rb +78 -0
  277. data/app/components/shadcn/tabs_content_component.rb +32 -0
  278. data/app/components/shadcn/tabs_list_component.rb +30 -0
  279. data/app/components/shadcn/tabs_trigger_component.rb +37 -0
  280. data/app/components/shadcn/textarea_component.rb +84 -0
  281. data/app/components/shadcn/toast_action_component.rb +18 -0
  282. data/app/components/shadcn/toast_component.rb +114 -0
  283. data/app/components/shadcn/toast_description_component.rb +12 -0
  284. data/app/components/shadcn/toast_title_component.rb +12 -0
  285. data/app/components/shadcn/toast_viewport_component.rb +12 -0
  286. data/app/components/shadcn/toggle_component.rb +77 -0
  287. data/app/components/shadcn/toggle_group_component.rb +96 -0
  288. data/app/components/shadcn/toggle_group_item_component.rb +62 -0
  289. data/app/components/shadcn/tooltip_component.rb +89 -0
  290. data/app/components/shadcn/typography_component.rb +112 -0
  291. data/babel.config.cjs +5 -0
  292. data/bin/console +11 -0
  293. data/bin/setup +8 -0
  294. data/config/importmap.rb +5 -0
  295. data/fly.toml +26 -0
  296. data/jest.config.js +19 -0
  297. data/jest.setup.js +8 -0
  298. data/lib/generators/shadcn/component/component_generator.rb +188 -0
  299. data/lib/generators/shadcn/install/install_generator.rb +140 -0
  300. data/lib/generators/shadcn/install/templates/initializer.rb.tt +35 -0
  301. data/lib/generators/shadcn/install/templates/shadcn.yml.tt +35 -0
  302. data/lib/generators/shadcn/theme/theme_generator.rb +128 -0
  303. data/lib/shadcn/rails/class_merger.rb +228 -0
  304. data/lib/shadcn/rails/configuration.rb +341 -0
  305. data/lib/shadcn/rails/engine.rb +59 -0
  306. data/lib/shadcn/rails/helpers/class_name_helper.rb +35 -0
  307. data/lib/shadcn/rails/helpers/component_helper.rb +60 -0
  308. data/lib/shadcn/rails/helpers/pagination_helper.rb +187 -0
  309. data/lib/shadcn/rails/version.rb +7 -0
  310. data/lib/shadcn/rails.rb +179 -0
  311. data/package-lock.json +7415 -0
  312. data/package.json +68 -0
  313. data/rollup.config.js +29 -0
  314. data/sig/shadcn/rails.rbs +6 -0
  315. metadata +526 -0
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/base"
5
+
6
+ module Shadcn
7
+ module Generators
8
+ # Generator for installing shadcn-rails in a Rails application
9
+ # Usage: rails generate shadcn:install
10
+ class InstallGenerator < Rails::Generators::Base
11
+ source_root File.expand_path("templates", __dir__)
12
+
13
+ class_option :theme, type: :string, default: "neutral",
14
+ desc: "Base color theme (neutral, slate, stone, zinc, gray)"
15
+ class_option :css_variables, type: :boolean, default: true,
16
+ desc: "Use CSS variables for theming"
17
+ class_option :dark_mode, type: :string, default: "class",
18
+ desc: "Dark mode strategy (class, media, selector)"
19
+ class_option :skip_tailwind, type: :boolean, default: false,
20
+ desc: "Skip Tailwind CSS configuration"
21
+
22
+ desc "Installs shadcn-rails and configures your application"
23
+
24
+ def create_initializer
25
+ template "initializer.rb.tt", "config/initializers/shadcn.rb"
26
+ end
27
+
28
+ def create_config_file
29
+ template "shadcn.yml.tt", "config/shadcn.yml"
30
+ end
31
+
32
+ def add_stylesheet
33
+ return if options[:skip_tailwind]
34
+
35
+ if File.exist?("app/assets/stylesheets/application.tailwind.css")
36
+ inject_into_file "app/assets/stylesheets/application.tailwind.css", before: "@tailwind base;" do
37
+ "/* shadcn-rails styles */\n@import \"shadcn/base\";\n@import \"shadcn/components\";\n\n"
38
+ end
39
+ elsif File.exist?("app/assets/stylesheets/application.css")
40
+ append_to_file "app/assets/stylesheets/application.css" do
41
+ "\n/*\n *= require shadcn/base\n *= require shadcn/components\n */\n"
42
+ end
43
+ else
44
+ say "Could not find application stylesheet. Please manually import shadcn/base.css and shadcn/components.css", :yellow
45
+ end
46
+ end
47
+
48
+ def configure_tailwind
49
+ return if options[:skip_tailwind]
50
+ return unless File.exist?("tailwind.config.js")
51
+
52
+ inject_into_file "tailwind.config.js", after: "content: [" do
53
+ "\n './app/components/**/*.{rb,html,erb}',\n './app/views/**/*.{html,erb}',"
54
+ end
55
+
56
+ say "Updated tailwind.config.js to include component paths", :green
57
+ end
58
+
59
+ def setup_stimulus_controllers
60
+ if importmap?
61
+ setup_importmap
62
+ elsif using_esbuild? || using_webpack?
63
+ setup_bundler
64
+ else
65
+ say "Could not detect JavaScript bundler. Please manually configure Stimulus controllers.", :yellow
66
+ end
67
+ end
68
+
69
+ def display_post_install_message
70
+ say ""
71
+ say "=" * 60, :green
72
+ say " shadcn-rails has been installed!", :green
73
+ say "=" * 60, :green
74
+ say ""
75
+ say "Next steps:", :yellow
76
+ say " 1. Ensure Tailwind CSS is configured with the shadcn theme"
77
+ say " 2. Import the Stimulus controllers in your application"
78
+ say " 3. Start using components in your views:"
79
+ say ""
80
+ say " <%= render Shadcn::ButtonComponent.new(variant: :primary) do %>"
81
+ say " Click me"
82
+ say " <% end %>"
83
+ say ""
84
+ say "For more information, visit: https://github.com/iheanyi/shadcn-rails"
85
+ say ""
86
+ end
87
+
88
+ private
89
+
90
+ def importmap?
91
+ File.exist?("config/importmap.rb")
92
+ end
93
+
94
+ def using_esbuild?
95
+ File.exist?("esbuild.config.mjs") ||
96
+ (File.exist?("package.json") && File.read("package.json").include?("esbuild"))
97
+ end
98
+
99
+ def using_webpack?
100
+ File.exist?("webpack.config.js") ||
101
+ (File.exist?("package.json") && File.read("package.json").include?("webpack"))
102
+ end
103
+
104
+ def setup_importmap
105
+ append_to_file "config/importmap.rb" do
106
+ <<~RUBY
107
+
108
+ # shadcn-rails Stimulus controllers
109
+ pin "shadcn", to: "shadcn/index.js"
110
+ RUBY
111
+ end
112
+
113
+ # Update application.js to register controllers
114
+ if File.exist?("app/javascript/controllers/application.js")
115
+ append_to_file "app/javascript/controllers/application.js" do
116
+ <<~JS
117
+
118
+ // Import and register shadcn-rails controllers
119
+ import { registerShadcnControllers } from "shadcn"
120
+ registerShadcnControllers(application)
121
+ JS
122
+ end
123
+ end
124
+
125
+ say "Configured importmap for shadcn-rails", :green
126
+ end
127
+
128
+ def setup_bundler
129
+ # Add to package.json dependencies
130
+ if File.exist?("package.json")
131
+ say "Please add the following to your JavaScript entry point:", :yellow
132
+ say ""
133
+ say " import { registerShadcnControllers } from 'shadcn-rails'"
134
+ say " registerShadcnControllers(application)"
135
+ say ""
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # shadcn-rails configuration
4
+ Shadcn::Rails.configure do |config|
5
+ # Style preset (currently only "default" is supported)
6
+ config.style = "default"
7
+
8
+ # Base color theme: neutral, stone, zinc, gray, slate
9
+ config.base_color = "<%= options[:theme] %>"
10
+
11
+ # Whether to use CSS variables for theming
12
+ config.css_variables = <%= options[:css_variables] %>
13
+
14
+ # Prefix for Tailwind classes (e.g., "tw-")
15
+ # config.tailwind_prefix = ""
16
+
17
+ # Default border radius for components
18
+ config.radius = "0.5rem"
19
+
20
+ # Dark mode strategy: :class, :media, or :selector
21
+ config.dark_mode = :<%= options[:dark_mode] %>
22
+
23
+ # Icon library to use: :lucide (default)
24
+ # config.icon_library = :lucide
25
+
26
+ # Register custom component aliases
27
+ # config.alias_component :my_button, MyCustomButtonComponent
28
+
29
+ # Register custom themes
30
+ # config.register_theme :corporate, {
31
+ # primary: "210 100% 50%",
32
+ # primary_foreground: "0 0% 100%",
33
+ # ...
34
+ # }
35
+ end
@@ -0,0 +1,35 @@
1
+ # shadcn-rails configuration file
2
+ # This file mirrors the components.json structure from shadcn/ui
3
+
4
+ # Style preset (currently only "default" is supported)
5
+ style: "default"
6
+
7
+ # Tailwind CSS configuration
8
+ tailwind:
9
+ # Path to your CSS file (for Tailwind CSS v4, leave config blank)
10
+ css: "app/assets/stylesheets/application.tailwind.css"
11
+
12
+ # Base color theme: neutral, stone, zinc, gray, slate
13
+ base_color: "<%= options[:theme] %>"
14
+
15
+ # Use CSS variables for theming
16
+ css_variables: <%= options[:css_variables] %>
17
+
18
+ # Prefix for Tailwind utility classes (e.g., "tw-")
19
+ prefix: ""
20
+
21
+ # Dark mode configuration
22
+ dark_mode: "<%= options[:dark_mode] %>"
23
+
24
+ # Component aliases for path mapping
25
+ aliases:
26
+ components: "app/components"
27
+ ui: "app/components/shadcn"
28
+ lib: "lib"
29
+ hooks: "app/javascript/hooks"
30
+
31
+ # Custom themes (optional)
32
+ # themes:
33
+ # corporate:
34
+ # primary: "210 100% 50%"
35
+ # primary_foreground: "0 0% 100%"
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/base"
5
+
6
+ module Shadcn
7
+ module Generators
8
+ # Generator for switching or customizing themes
9
+ # Usage: rails generate shadcn:theme slate
10
+ class ThemeGenerator < Rails::Generators::Base
11
+ source_root File.expand_path("templates", __dir__)
12
+
13
+ argument :theme_name, type: :string, default: "neutral",
14
+ banner: "theme_name"
15
+
16
+ class_option :list, type: :boolean, default: false,
17
+ desc: "List all available themes"
18
+
19
+ desc "Changes the shadcn-rails theme or creates a custom theme"
20
+
21
+ AVAILABLE_THEMES = %w[neutral slate stone zinc gray].freeze
22
+
23
+ def validate_theme
24
+ if options[:list]
25
+ display_available_themes
26
+ exit 0
27
+ end
28
+
29
+ unless AVAILABLE_THEMES.include?(theme_name)
30
+ say "Error: Unknown theme '#{theme_name}'", :red
31
+ display_available_themes
32
+ exit 1
33
+ end
34
+ end
35
+
36
+ def update_initializer
37
+ initializer_path = "config/initializers/shadcn.rb"
38
+
39
+ if File.exist?(initializer_path)
40
+ gsub_file initializer_path,
41
+ /config\.base_color = ["']?\w+["']?/,
42
+ "config.base_color = \"#{theme_name}\""
43
+ say "Updated initializer to use '#{theme_name}' theme", :green
44
+ else
45
+ say "Initializer not found. Run 'rails generate shadcn:install' first.", :yellow
46
+ end
47
+ end
48
+
49
+ def update_config_file
50
+ config_path = "config/shadcn.yml"
51
+
52
+ if File.exist?(config_path)
53
+ gsub_file config_path,
54
+ /base_color: \w+/,
55
+ "base_color: #{theme_name}"
56
+ say "Updated shadcn.yml to use '#{theme_name}' theme", :green
57
+ end
58
+ end
59
+
60
+ def update_stylesheet
61
+ # Check for theme import in stylesheet
62
+ stylesheet_path = "app/assets/stylesheets/application.tailwind.css"
63
+
64
+ if File.exist?(stylesheet_path)
65
+ content = File.read(stylesheet_path)
66
+
67
+ # Remove existing theme import
68
+ AVAILABLE_THEMES.each do |theme|
69
+ next if theme == "neutral" # neutral uses base.css
70
+ gsub_file stylesheet_path, /@import ["']shadcn\/themes\/#{theme}["'];\n?/, ""
71
+ end
72
+
73
+ # Add new theme import if not neutral
74
+ unless theme_name == "neutral"
75
+ inject_into_file stylesheet_path, after: '@import "shadcn/base";' do
76
+ "\n@import \"shadcn/themes/#{theme_name}\";"
77
+ end
78
+ end
79
+
80
+ say "Updated stylesheet with '#{theme_name}' theme", :green
81
+ end
82
+ end
83
+
84
+ def display_completion_message
85
+ say ""
86
+ say "Theme changed to '#{theme_name}'!", :green
87
+ say ""
88
+ say "The following colors are now active:", :yellow
89
+ display_theme_preview(theme_name)
90
+ say ""
91
+ end
92
+
93
+ private
94
+
95
+ def display_available_themes
96
+ say ""
97
+ say "Available themes:", :green
98
+ say ""
99
+ AVAILABLE_THEMES.each do |theme|
100
+ say " - #{theme}"
101
+ end
102
+ say ""
103
+ say "Usage: rails generate shadcn:theme slate"
104
+ say ""
105
+ end
106
+
107
+ def display_theme_preview(theme)
108
+ case theme
109
+ when "neutral"
110
+ say " Primary: Pure black/white"
111
+ say " Style: Clean, minimal"
112
+ when "slate"
113
+ say " Primary: Cool blue-gray"
114
+ say " Style: Professional, corporate"
115
+ when "stone"
116
+ say " Primary: Warm gray-brown"
117
+ say " Style: Earthy, natural"
118
+ when "zinc"
119
+ say " Primary: Cool gray"
120
+ say " Style: Modern, sleek"
121
+ when "gray"
122
+ say " Primary: True gray"
123
+ say " Style: Neutral, balanced"
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Rails
5
+ # Utility class for merging CSS classes intelligently
6
+ # Handles Tailwind CSS class conflicts similar to tailwind-merge
7
+ class ClassMerger
8
+ # Class groups that conflict with each other
9
+ # Later classes in the same group override earlier ones
10
+ CLASS_GROUPS = {
11
+ # Display
12
+ display: %w[block inline-block inline flex inline-flex grid inline-grid hidden],
13
+
14
+ # Position
15
+ position: %w[static fixed absolute relative sticky],
16
+
17
+ # Overflow
18
+ overflow: %w[overflow-auto overflow-hidden overflow-visible overflow-scroll],
19
+ overflow_x: %w[overflow-x-auto overflow-x-hidden overflow-x-visible overflow-x-scroll],
20
+ overflow_y: %w[overflow-y-auto overflow-y-hidden overflow-y-visible overflow-y-scroll],
21
+
22
+ # Flex direction
23
+ flex_direction: %w[flex-row flex-row-reverse flex-col flex-col-reverse],
24
+
25
+ # Flex wrap
26
+ flex_wrap: %w[flex-wrap flex-wrap-reverse flex-nowrap],
27
+
28
+ # Justify content
29
+ justify: %w[justify-start justify-end justify-center justify-between justify-around justify-evenly],
30
+
31
+ # Align items
32
+ items: %w[items-start items-end items-center items-baseline items-stretch],
33
+
34
+ # Align content
35
+ content: %w[content-start content-end content-center content-between content-around content-evenly],
36
+
37
+ # Align self
38
+ self: %w[self-auto self-start self-end self-center self-stretch],
39
+
40
+ # Font size
41
+ font_size: %w[text-xs text-sm text-base text-lg text-xl text-2xl text-3xl text-4xl text-5xl text-6xl text-7xl text-8xl text-9xl],
42
+
43
+ # Font weight
44
+ font_weight: %w[font-thin font-extralight font-light font-normal font-medium font-semibold font-bold font-extrabold font-black],
45
+
46
+ # Text align
47
+ text_align: %w[text-left text-center text-right text-justify],
48
+
49
+ # Whitespace
50
+ whitespace: %w[whitespace-normal whitespace-nowrap whitespace-pre whitespace-pre-line whitespace-pre-wrap],
51
+
52
+ # Text decoration
53
+ text_decoration: %w[underline overline line-through no-underline],
54
+
55
+ # Border style
56
+ border_style: %w[border-solid border-dashed border-dotted border-double border-none],
57
+
58
+ # Cursor
59
+ cursor: %w[cursor-auto cursor-default cursor-pointer cursor-wait cursor-text cursor-move cursor-not-allowed]
60
+ }.freeze
61
+
62
+ # Regex patterns for conflicting classes
63
+ CONFLICT_PATTERNS = {
64
+ # Spacing (margin, padding)
65
+ /^m-/ => :margin,
66
+ /^mx-/ => :margin_x,
67
+ /^my-/ => :margin_y,
68
+ /^mt-/ => :margin_top,
69
+ /^mr-/ => :margin_right,
70
+ /^mb-/ => :margin_bottom,
71
+ /^ml-/ => :margin_left,
72
+ /^p-/ => :padding,
73
+ /^px-/ => :padding_x,
74
+ /^py-/ => :padding_y,
75
+ /^pt-/ => :padding_top,
76
+ /^pr-/ => :padding_right,
77
+ /^pb-/ => :padding_bottom,
78
+ /^pl-/ => :padding_left,
79
+
80
+ # Sizing
81
+ /^w-/ => :width,
82
+ /^min-w-/ => :min_width,
83
+ /^max-w-/ => :max_width,
84
+ /^h-/ => :height,
85
+ /^min-h-/ => :min_height,
86
+ /^max-h-/ => :max_height,
87
+ /^size-/ => :size,
88
+
89
+ # Colors
90
+ /^bg-/ => :background,
91
+ /^text-(?!left|center|right|justify|xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)/ => :text_color,
92
+ /^border-(?!solid|dashed|dotted|double|none|[0-9])/ => :border_color,
93
+ /^ring-(?!offset)/ => :ring_color,
94
+ /^ring-offset-(?!\d)/ => :ring_offset_color,
95
+
96
+ # Border radius
97
+ /^rounded(?:-|$)/ => :border_radius,
98
+ /^rounded-t(?:-|$)/ => :border_radius_top,
99
+ /^rounded-r(?:-|$)/ => :border_radius_right,
100
+ /^rounded-b(?:-|$)/ => :border_radius_bottom,
101
+ /^rounded-l(?:-|$)/ => :border_radius_left,
102
+ /^rounded-tl(?:-|$)/ => :border_radius_top_left,
103
+ /^rounded-tr(?:-|$)/ => :border_radius_top_right,
104
+ /^rounded-bl(?:-|$)/ => :border_radius_bottom_left,
105
+ /^rounded-br(?:-|$)/ => :border_radius_bottom_right,
106
+
107
+ # Border width
108
+ /^border(?:-[0-9]|$)/ => :border_width,
109
+ /^border-t(?:-[0-9]|$)/ => :border_width_top,
110
+ /^border-r(?:-[0-9]|$)/ => :border_width_right,
111
+ /^border-b(?:-[0-9]|$)/ => :border_width_bottom,
112
+ /^border-l(?:-[0-9]|$)/ => :border_width_left,
113
+
114
+ # Opacity
115
+ /^opacity-/ => :opacity,
116
+
117
+ # Z-index
118
+ /^z-/ => :z_index,
119
+
120
+ # Gap
121
+ /^gap-/ => :gap,
122
+ /^gap-x-/ => :gap_x,
123
+ /^gap-y-/ => :gap_y,
124
+
125
+ # Grid
126
+ /^grid-cols-/ => :grid_cols,
127
+ /^grid-rows-/ => :grid_rows,
128
+ /^col-span-/ => :col_span,
129
+ /^row-span-/ => :row_span,
130
+
131
+ # Shadow
132
+ /^shadow(?:-|$)/ => :shadow,
133
+
134
+ # Transition
135
+ /^transition(?:-|$)/ => :transition,
136
+ /^duration-/ => :duration,
137
+ /^ease-/ => :ease,
138
+
139
+ # Transform
140
+ /^scale-/ => :scale,
141
+ /^rotate-/ => :rotate,
142
+ /^translate-x-/ => :translate_x,
143
+ /^translate-y-/ => :translate_y
144
+ }.freeze
145
+
146
+ class << self
147
+ # Merge CSS classes, handling conflicts
148
+ # @param args [Array] Classes to merge (strings, hashes, arrays, or nil)
149
+ # @return [String] Merged class string
150
+ def merge(*args)
151
+ classes = flatten_classes(args)
152
+ dedupe_classes(classes).join(" ")
153
+ end
154
+
155
+ private
156
+
157
+ # Flatten nested arrays and handle conditional hashes
158
+ def flatten_classes(args)
159
+ args.flat_map do |arg|
160
+ case arg
161
+ when nil, false
162
+ []
163
+ when String
164
+ arg.split
165
+ when Array
166
+ flatten_classes(arg)
167
+ when Hash
168
+ arg.filter_map { |klass, condition| klass.to_s if condition }
169
+ else
170
+ arg.to_s.split
171
+ end
172
+ end
173
+ end
174
+
175
+ # Remove duplicate/conflicting classes, keeping last occurrence
176
+ def dedupe_classes(classes)
177
+ class_groups = {}
178
+ result = []
179
+
180
+ classes.each do |klass|
181
+ # Strip responsive/state prefixes for conflict detection
182
+ base_class = strip_prefixes(klass)
183
+ group = find_conflict_group(base_class)
184
+
185
+ if group
186
+ # Get the prefix (like "sm:", "hover:", etc.)
187
+ prefix = klass.sub(base_class, "")
188
+
189
+ # Create a unique key combining prefix and group
190
+ key = "#{prefix}:#{group}"
191
+
192
+ # If we've seen this group before, remove the old class
193
+ if class_groups[key]
194
+ result.delete(class_groups[key])
195
+ end
196
+
197
+ class_groups[key] = klass
198
+ end
199
+
200
+ result << klass
201
+ end
202
+
203
+ result.uniq
204
+ end
205
+
206
+ # Strip responsive and state prefixes
207
+ def strip_prefixes(klass)
208
+ klass.gsub(/^(?:sm:|md:|lg:|xl:|2xl:|hover:|focus:|active:|disabled:|dark:|group-hover:|peer-\w+:)+/, "")
209
+ end
210
+
211
+ # Find the conflict group for a class
212
+ def find_conflict_group(klass)
213
+ # Check static groups first
214
+ CLASS_GROUPS.each do |group, classes|
215
+ return group if classes.include?(klass)
216
+ end
217
+
218
+ # Check pattern-based groups
219
+ CONFLICT_PATTERNS.each do |pattern, group|
220
+ return group if klass.match?(pattern)
221
+ end
222
+
223
+ nil
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end