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,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Toggle Group component for grouping related toggles
5
+ # Matches shadcn/ui ToggleGroup component
6
+ #
7
+ # @example Single selection
8
+ # <%= render Shadcn::ToggleGroupComponent.new(type: :single, name: "alignment") do |group| %>
9
+ # <% group.with_item(value: "left", aria_label: "Align left") { icon_left } %>
10
+ # <% group.with_item(value: "center", aria_label: "Align center") { icon_center } %>
11
+ # <% group.with_item(value: "right", aria_label: "Align right") { icon_right } %>
12
+ # <% end %>
13
+ #
14
+ # @example Multiple selection with outline variant
15
+ # <%= render Shadcn::ToggleGroupComponent.new(type: :multiple, variant: :outline) do |group| %>
16
+ # <% group.with_item(value: "bold") { "B" } %>
17
+ # <% group.with_item(value: "italic") { "I" } %>
18
+ # <% group.with_item(value: "underline") { "U" } %>
19
+ # <% end %>
20
+ #
21
+ class ToggleGroupComponent < BaseComponent
22
+ BASE_CLASSES = "inline-flex items-center justify-center gap-1 rounded-lg"
23
+
24
+ renders_many :items, ->(value:, pressed: false, disabled: false, aria_label: nil, **options, &block) do
25
+ ToggleGroupItemComponent.new(
26
+ value: value,
27
+ pressed: pressed,
28
+ disabled: disabled,
29
+ aria_label: aria_label,
30
+ variant: @variant,
31
+ size: @size,
32
+ group_type: @type,
33
+ **options,
34
+ &block
35
+ )
36
+ end
37
+
38
+ # @param type [Symbol] :single or :multiple selection mode
39
+ # @param variant [Symbol] :default or :outline
40
+ # @param size [Symbol] :sm, :default, or :lg
41
+ # @param value [String, Array] Currently selected value(s)
42
+ # @param name [String] Form field name
43
+ def initialize(
44
+ type: :single,
45
+ variant: :default,
46
+ size: :default,
47
+ value: nil,
48
+ name: nil,
49
+ **options
50
+ )
51
+ super(**options)
52
+ @type = type
53
+ @variant = variant
54
+ @size = size
55
+ @value = value
56
+ @name = name
57
+ end
58
+
59
+ def call
60
+ content_tag(:div, group_content, group_attributes)
61
+ end
62
+
63
+ private
64
+
65
+ def group_content
66
+ safe_join([
67
+ hidden_input,
68
+ items.map(&:to_s)
69
+ ].flatten.compact)
70
+ end
71
+
72
+ def hidden_input
73
+ return unless @name
74
+
75
+ tag(:input,
76
+ type: "hidden",
77
+ name: @name,
78
+ value: Array(@value).join(","),
79
+ "data-shadcn--toggle-group-target": "input"
80
+ )
81
+ end
82
+
83
+ def group_attributes
84
+ attrs = {
85
+ role: "group",
86
+ class: merge_classes(BASE_CLASSES),
87
+ "data-controller": "shadcn--toggle-group",
88
+ "data-shadcn--toggle-group-type-value": @type.to_s,
89
+ "data-shadcn--toggle-group-value-value": Array(@value).join(",")
90
+ }
91
+ attrs.merge!(html_options)
92
+ attrs.merge!(build_data)
93
+ attrs.compact
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Individual toggle item within a group
5
+ class ToggleGroupItemComponent < BaseComponent
6
+ VARIANTS = {
7
+ default: "bg-transparent",
8
+ outline: "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground"
9
+ }.freeze
10
+
11
+ SIZES = {
12
+ sm: "h-8 px-2 min-w-8",
13
+ default: "h-9 px-2.5 min-w-9",
14
+ lg: "h-10 px-3 min-w-10"
15
+ }.freeze
16
+
17
+ BASE_CLASSES = "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground"
18
+
19
+ def initialize(
20
+ value:,
21
+ pressed: false,
22
+ disabled: false,
23
+ aria_label: nil,
24
+ variant: :default,
25
+ size: :default,
26
+ group_type: :single,
27
+ **options,
28
+ &block
29
+ )
30
+ super(**options, &block)
31
+ @value = value
32
+ @pressed = pressed
33
+ @disabled = disabled
34
+ @aria_label = aria_label
35
+ @variant = variant
36
+ @size = size
37
+ @group_type = group_type
38
+ end
39
+
40
+ def call
41
+ content_tag(:button, content, button_attributes)
42
+ end
43
+
44
+ private
45
+
46
+ def button_attributes
47
+ attrs = {
48
+ type: "button",
49
+ class: cn(BASE_CLASSES, VARIANTS[@variant], SIZES[@size], class_name),
50
+ disabled: @disabled || nil,
51
+ "aria-pressed": @pressed.to_s,
52
+ "aria-label": @aria_label,
53
+ "data-state": @pressed ? "on" : "off",
54
+ "data-value": @value,
55
+ "data-shadcn--toggle-group-target": "item",
56
+ "data-action": "click->shadcn--toggle-group#toggle"
57
+ }
58
+ attrs.merge!(html_options)
59
+ attrs.compact
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Tooltip component for contextual information
5
+ # Matches shadcn/ui Tooltip component
6
+ # Uses Stimulus for interactivity
7
+ #
8
+ # @example Basic tooltip
9
+ # <%= render Shadcn::TooltipComponent.new(text: "Add to library") do %>
10
+ # <%= render Shadcn::ButtonComponent.new(variant: :outline, size: :icon) do %>
11
+ # +
12
+ # <% end %>
13
+ # <% end %>
14
+ #
15
+ # @example With custom positioning
16
+ # <%= render Shadcn::TooltipComponent.new(text: "Help", side: :right) do %>
17
+ # <span>Hover me</span>
18
+ # <% end %>
19
+ #
20
+ class TooltipComponent < BaseComponent
21
+ CONTENT_CLASSES = "z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
22
+
23
+ # @param text [String] Tooltip text content
24
+ # @param side [Symbol] Side to show tooltip (:top, :right, :bottom, :left)
25
+ # @param align [Symbol] Alignment (:start, :center, :end)
26
+ # @param delay_duration [Integer] Delay before showing (ms)
27
+ # @param skip_delay_duration [Integer] Delay when moving between triggers (ms)
28
+ def initialize(
29
+ text: nil,
30
+ side: :top,
31
+ align: :center,
32
+ delay_duration: 200,
33
+ skip_delay_duration: 300,
34
+ **options
35
+ )
36
+ @tooltip_content = text
37
+ @side = side
38
+ @align = align
39
+ @delay_duration = delay_duration
40
+ @skip_delay_duration = skip_delay_duration
41
+ super(**options)
42
+ end
43
+
44
+ def call
45
+ content_tag(:span, tooltip_structure, tooltip_attributes)
46
+ end
47
+
48
+ private
49
+
50
+ def tooltip_structure
51
+ safe_join([
52
+ trigger_wrapper,
53
+ tooltip_content_element
54
+ ])
55
+ end
56
+
57
+ def trigger_wrapper
58
+ content_tag(:span, content, {
59
+ "data-shadcn--tooltip-target": "trigger",
60
+ "data-action": "mouseenter->shadcn--tooltip#show mouseleave->shadcn--tooltip#hide focus->shadcn--tooltip#show blur->shadcn--tooltip#hide"
61
+ })
62
+ end
63
+
64
+ def tooltip_content_element
65
+ content_tag(:div, @tooltip_content, {
66
+ class: CONTENT_CLASSES,
67
+ role: "tooltip",
68
+ "data-shadcn--tooltip-target": "content",
69
+ "data-side": @side.to_s,
70
+ "data-state": "closed",
71
+ hidden: true
72
+ })
73
+ end
74
+
75
+ def tooltip_attributes
76
+ attrs = {
77
+ class: cn("relative inline-block", class_name),
78
+ "data-controller": "shadcn--tooltip",
79
+ "data-shadcn--tooltip-side-value": @side.to_s,
80
+ "data-shadcn--tooltip-align-value": @align.to_s,
81
+ "data-shadcn--tooltip-delay-value": @delay_duration,
82
+ "data-shadcn--tooltip-skip-delay-value": @skip_delay_duration
83
+ }
84
+ attrs.merge!(html_options)
85
+ attrs.merge!(build_data)
86
+ attrs.compact
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Typography component for consistent text styling
5
+ # Matches shadcn/ui Typography component
6
+ #
7
+ # @example Heading 1
8
+ # <%= render Shadcn::TypographyComponent.new(variant: :h1) { "Heading 1" } %>
9
+ #
10
+ # @example Heading 2
11
+ # <%= render Shadcn::TypographyComponent.new(variant: :h2) { "Heading 2" } %>
12
+ #
13
+ # @example Paragraph
14
+ # <%= render Shadcn::TypographyComponent.new(variant: :p) { "This is a paragraph of text." } %>
15
+ #
16
+ # @example Lead text
17
+ # <%= render Shadcn::TypographyComponent.new(variant: :lead) { "A modal dialog that interrupts the user." } %>
18
+ #
19
+ # @example Muted text
20
+ # <%= render Shadcn::TypographyComponent.new(variant: :muted) { "Enter your email address." } %>
21
+ #
22
+ # @example Blockquote
23
+ # <%= render Shadcn::TypographyComponent.new(variant: :blockquote) { "After all, everyone enjoys a good quote." } %>
24
+ #
25
+ # @example Inline code
26
+ # <%= render Shadcn::TypographyComponent.new(variant: :code) { "@radix-ui/react-alert-dialog" } %>
27
+ #
28
+ class TypographyComponent < BaseComponent
29
+ VARIANTS = {
30
+ h1: {
31
+ tag: :h1,
32
+ classes: "scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl"
33
+ },
34
+ h2: {
35
+ tag: :h2,
36
+ classes: "scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0"
37
+ },
38
+ h3: {
39
+ tag: :h3,
40
+ classes: "scroll-m-20 text-2xl font-semibold tracking-tight"
41
+ },
42
+ h4: {
43
+ tag: :h4,
44
+ classes: "scroll-m-20 text-xl font-semibold tracking-tight"
45
+ },
46
+ p: {
47
+ tag: :p,
48
+ classes: "leading-7 [&:not(:first-child)]:mt-6"
49
+ },
50
+ lead: {
51
+ tag: :p,
52
+ classes: "text-xl text-muted-foreground"
53
+ },
54
+ large: {
55
+ tag: :div,
56
+ classes: "text-lg font-semibold"
57
+ },
58
+ small: {
59
+ tag: :small,
60
+ classes: "text-sm font-medium leading-none"
61
+ },
62
+ muted: {
63
+ tag: :p,
64
+ classes: "text-sm text-muted-foreground"
65
+ },
66
+ blockquote: {
67
+ tag: :blockquote,
68
+ classes: "mt-6 border-l-2 pl-6 italic"
69
+ },
70
+ code: {
71
+ tag: :code,
72
+ classes: "relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold"
73
+ },
74
+ list: {
75
+ tag: :ul,
76
+ classes: "my-6 ml-6 list-disc [&>li]:mt-2"
77
+ }
78
+ }.freeze
79
+
80
+ # @param variant [Symbol] Typography variant (:h1, :h2, :h3, :h4, :p, :lead, :large, :small, :muted, :blockquote, :code, :list)
81
+ # @param tag [Symbol, nil] Override the default HTML tag
82
+ def initialize(variant: :p, tag: nil, **options)
83
+ super(**options)
84
+ @variant = variant.to_sym
85
+ @tag = tag
86
+ end
87
+
88
+ def call
89
+ content_tag(element_tag, content, **typography_attributes)
90
+ end
91
+
92
+ private
93
+
94
+ def typography_attributes
95
+ {
96
+ class: merge_classes(variant_classes)
97
+ }.merge(html_options).merge(build_data).compact
98
+ end
99
+
100
+ def variant_config
101
+ VARIANTS[@variant] || VARIANTS[:p]
102
+ end
103
+
104
+ def variant_classes
105
+ variant_config[:classes]
106
+ end
107
+
108
+ def element_tag
109
+ @tag || variant_config[:tag]
110
+ end
111
+ end
112
+ end
data/babel.config.cjs ADDED
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ presets: [
3
+ ['@babel/preset-env', { targets: { node: 'current' } }]
4
+ ]
5
+ }
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "shadcn/rails"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require "irb"
11
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,5 @@
1
+ # Importmap configuration for shadcn-rails
2
+ # This file is automatically included when using importmaps
3
+
4
+ pin "shadcn", to: "shadcn/index.js"
5
+ pin_all_from File.expand_path("../app/assets/javascripts/shadcn/controllers", __dir__), under: "shadcn/controllers"
data/fly.toml ADDED
@@ -0,0 +1,26 @@
1
+ # fly.toml - Fly.io configuration for shadcn-rails documentation site
2
+ # See https://fly.io/docs/reference/configuration/ for information about how to use this file.
3
+
4
+ app = 'shadcn-rails'
5
+ primary_region = 'iad'
6
+
7
+ [build]
8
+ dockerfile = 'test/dummy/Dockerfile'
9
+
10
+ [env]
11
+ RAILS_ENV = 'production'
12
+ RAILS_LOG_TO_STDOUT = 'true'
13
+ RAILS_SERVE_STATIC_FILES = 'true'
14
+
15
+ [http_service]
16
+ internal_port = 3000
17
+ force_https = true
18
+ auto_stop_machines = 'stop'
19
+ auto_start_machines = true
20
+ min_machines_running = 0
21
+ processes = ['app']
22
+
23
+ [[vm]]
24
+ memory = '512mb'
25
+ cpu_kind = 'shared'
26
+ cpus = 1
data/jest.config.js ADDED
@@ -0,0 +1,19 @@
1
+ /** @type {import('jest').Config} */
2
+ export default {
3
+ testEnvironment: 'jsdom',
4
+ testMatch: ['**/__tests__/**/*.test.js'],
5
+ moduleFileExtensions: ['js', 'json'],
6
+ transform: {
7
+ '^.+\\.js$': 'babel-jest'
8
+ },
9
+ transformIgnorePatterns: [
10
+ '/node_modules/(?!@hotwired/stimulus)'
11
+ ],
12
+ setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
13
+ collectCoverageFrom: [
14
+ 'app/assets/javascripts/shadcn/controllers/**/*.js',
15
+ '!**/node_modules/**'
16
+ ],
17
+ coverageDirectory: 'coverage',
18
+ verbose: true
19
+ }
data/jest.setup.js ADDED
@@ -0,0 +1,8 @@
1
+ import '@testing-library/jest-dom'
2
+
3
+ // Make jest globals available in ESM modules
4
+ import { jest } from '@jest/globals'
5
+ globalThis.jest = jest
6
+
7
+ // Mock scrollIntoView since JSDOM doesn't implement it
8
+ Element.prototype.scrollIntoView = jest.fn()
@@ -0,0 +1,188 @@
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 adding individual shadcn components
9
+ # Usage: rails generate shadcn:component button card dialog
10
+ class ComponentGenerator < Rails::Generators::Base
11
+ source_root File.expand_path("templates", __dir__)
12
+
13
+ argument :components, type: :array, default: [], banner: "component [component ...]"
14
+
15
+ class_option :all, type: :boolean, default: false,
16
+ desc: "Install all available components"
17
+ class_option :list, type: :boolean, default: false,
18
+ desc: "List all available components"
19
+
20
+ desc "Adds shadcn-rails components to your application"
21
+
22
+ # List of all available components
23
+ AVAILABLE_COMPONENTS = %w[
24
+ accordion
25
+ alert
26
+ avatar
27
+ badge
28
+ button
29
+ card
30
+ checkbox
31
+ collapsible
32
+ dialog
33
+ dropdown_menu
34
+ input
35
+ label
36
+ popover
37
+ progress
38
+ scroll_area
39
+ select
40
+ separator
41
+ sheet
42
+ skeleton
43
+ switch
44
+ table
45
+ tabs
46
+ textarea
47
+ toast
48
+ tooltip
49
+ ].freeze
50
+
51
+ def validate_components
52
+ if options[:list]
53
+ display_available_components
54
+ exit 0
55
+ end
56
+
57
+ if options[:all]
58
+ @components_to_install = AVAILABLE_COMPONENTS
59
+ else
60
+ validate_requested_components
61
+ @components_to_install = components
62
+ end
63
+ end
64
+
65
+ def display_installation_plan
66
+ say ""
67
+ say "Installing #{@components_to_install.length} component(s):", :green
68
+ @components_to_install.each { |c| say " - #{c}" }
69
+ say ""
70
+ end
71
+
72
+ def install_components
73
+ @components_to_install.each do |component|
74
+ install_component(component)
75
+ end
76
+ end
77
+
78
+ def display_usage_examples
79
+ say ""
80
+ say "=" * 60, :green
81
+ say " Components installed successfully!", :green
82
+ say "=" * 60, :green
83
+ say ""
84
+ say "Example usage:", :yellow
85
+
86
+ @components_to_install.first(3).each do |component|
87
+ display_component_example(component)
88
+ end
89
+
90
+ say ""
91
+ end
92
+
93
+ private
94
+
95
+ def display_available_components
96
+ say ""
97
+ say "Available shadcn-rails components:", :green
98
+ say ""
99
+ AVAILABLE_COMPONENTS.each { |c| say " - #{c}" }
100
+ say ""
101
+ say "Usage: rails generate shadcn:component button card dialog"
102
+ say " rails generate shadcn:component --all"
103
+ say ""
104
+ end
105
+
106
+ def validate_requested_components
107
+ if components.empty?
108
+ say "Error: Please specify at least one component or use --all", :red
109
+ say ""
110
+ display_available_components
111
+ exit 1
112
+ end
113
+
114
+ invalid = components - AVAILABLE_COMPONENTS
115
+ if invalid.any?
116
+ say "Error: Unknown component(s): #{invalid.join(', ')}", :red
117
+ say ""
118
+ display_available_components
119
+ exit 1
120
+ end
121
+ end
122
+
123
+ def install_component(name)
124
+ # Components are already available from the gem
125
+ # This generator just confirms installation and provides usage info
126
+ say " ✓ #{name.titleize}Component is available", :green
127
+ end
128
+
129
+ def display_component_example(component)
130
+ case component
131
+ when "button"
132
+ say <<~EXAMPLE
133
+
134
+ Button:
135
+ <%= render Shadcn::ButtonComponent.new(variant: :primary) do %>
136
+ Click me
137
+ <% end %>
138
+ EXAMPLE
139
+ when "card"
140
+ say <<~EXAMPLE
141
+
142
+ Card:
143
+ <%= render Shadcn::CardComponent.new do |card| %>
144
+ <% card.with_header do %>
145
+ <% card.with_title { "Card Title" } %>
146
+ <% end %>
147
+ <% card.with_content { "Card content" } %>
148
+ <% end %>
149
+ EXAMPLE
150
+ when "dialog"
151
+ say <<~EXAMPLE
152
+
153
+ Dialog:
154
+ <%= render Shadcn::DialogComponent.new do |dialog| %>
155
+ <% dialog.with_trigger do %>
156
+ <%= render Shadcn::ButtonComponent.new { "Open" } %>
157
+ <% end %>
158
+ <% dialog.with_content do |content| %>
159
+ <% content.with_title { "Dialog Title" } %>
160
+ Dialog content here
161
+ <% end %>
162
+ <% end %>
163
+ EXAMPLE
164
+ when "input"
165
+ say <<~EXAMPLE
166
+
167
+ Input:
168
+ <%= render Shadcn::LabelComponent.new(for: "email") { "Email" } %>
169
+ <%= render Shadcn::InputComponent.new(
170
+ type: "email",
171
+ id: "email",
172
+ name: "email",
173
+ placeholder: "Enter your email"
174
+ ) %>
175
+ EXAMPLE
176
+ else
177
+ say <<~EXAMPLE
178
+
179
+ #{component.titleize}:
180
+ <%= render Shadcn::#{component.camelize}Component.new do %>
181
+ Content here
182
+ <% end %>
183
+ EXAMPLE
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end