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,341 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Rails
5
+ class Configuration
6
+ # Style preset: currently only "default" is supported
7
+ attr_accessor :style
8
+
9
+ # Base color theme: neutral, stone, zinc, gray, slate
10
+ attr_accessor :base_color
11
+
12
+ # Whether to use CSS variables for theming
13
+ attr_accessor :css_variables
14
+
15
+ # Prefix for Tailwind classes (e.g., "tw-")
16
+ attr_accessor :tailwind_prefix
17
+
18
+ # Default radius for components
19
+ attr_accessor :radius
20
+
21
+ # Dark mode strategy: :class, :media, or :selector
22
+ attr_accessor :dark_mode
23
+
24
+ # Icon library to use: :lucide (default), :heroicons, etc.
25
+ attr_accessor :icon_library
26
+
27
+ # Component aliases (for overriding default component classes)
28
+ attr_reader :component_aliases
29
+
30
+ # Additional themes (name => css_variables hash)
31
+ attr_reader :themes
32
+
33
+ def initialize
34
+ @style = "default"
35
+ @base_color = "neutral"
36
+ @css_variables = true
37
+ @tailwind_prefix = ""
38
+ @radius = "0.5rem"
39
+ @dark_mode = :class
40
+ @icon_library = :lucide
41
+ @component_aliases = {}
42
+ @themes = {}
43
+ end
44
+
45
+ # Register a custom component alias
46
+ def alias_component(name, klass)
47
+ @component_aliases[name.to_sym] = klass
48
+ end
49
+
50
+ # Register a custom theme
51
+ def register_theme(name, variables)
52
+ @themes[name.to_sym] = variables
53
+ end
54
+
55
+ # Get all CSS variables for a theme
56
+ def css_variables_for_theme(theme = :default)
57
+ base_variables.merge(@themes[theme] || {})
58
+ end
59
+
60
+ # Default CSS variables matching shadcn/ui
61
+ def base_variables
62
+ THEME_VARIABLES[@base_color.to_sym] || THEME_VARIABLES[:neutral]
63
+ end
64
+
65
+ # Available base colors with their CSS variable values
66
+ THEME_VARIABLES = {
67
+ neutral: {
68
+ # Light mode
69
+ background: "0 0% 100%",
70
+ foreground: "0 0% 3.9%",
71
+ card: "0 0% 100%",
72
+ card_foreground: "0 0% 3.9%",
73
+ popover: "0 0% 100%",
74
+ popover_foreground: "0 0% 3.9%",
75
+ primary: "0 0% 9%",
76
+ primary_foreground: "0 0% 98%",
77
+ secondary: "0 0% 96.1%",
78
+ secondary_foreground: "0 0% 9%",
79
+ muted: "0 0% 96.1%",
80
+ muted_foreground: "0 0% 45.1%",
81
+ accent: "0 0% 96.1%",
82
+ accent_foreground: "0 0% 9%",
83
+ destructive: "0 84.2% 60.2%",
84
+ destructive_foreground: "0 0% 98%",
85
+ border: "0 0% 89.8%",
86
+ input: "0 0% 89.8%",
87
+ ring: "0 0% 3.9%",
88
+ radius: "0.5rem",
89
+ # Chart colors
90
+ chart_1: "12 76% 61%",
91
+ chart_2: "173 58% 39%",
92
+ chart_3: "197 37% 24%",
93
+ chart_4: "43 74% 66%",
94
+ chart_5: "27 87% 67%"
95
+ },
96
+ slate: {
97
+ background: "0 0% 100%",
98
+ foreground: "222.2 84% 4.9%",
99
+ card: "0 0% 100%",
100
+ card_foreground: "222.2 84% 4.9%",
101
+ popover: "0 0% 100%",
102
+ popover_foreground: "222.2 84% 4.9%",
103
+ primary: "222.2 47.4% 11.2%",
104
+ primary_foreground: "210 40% 98%",
105
+ secondary: "210 40% 96.1%",
106
+ secondary_foreground: "222.2 47.4% 11.2%",
107
+ muted: "210 40% 96.1%",
108
+ muted_foreground: "215.4 16.3% 46.9%",
109
+ accent: "210 40% 96.1%",
110
+ accent_foreground: "222.2 47.4% 11.2%",
111
+ destructive: "0 84.2% 60.2%",
112
+ destructive_foreground: "210 40% 98%",
113
+ border: "214.3 31.8% 91.4%",
114
+ input: "214.3 31.8% 91.4%",
115
+ ring: "222.2 84% 4.9%",
116
+ radius: "0.5rem",
117
+ chart_1: "12 76% 61%",
118
+ chart_2: "173 58% 39%",
119
+ chart_3: "197 37% 24%",
120
+ chart_4: "43 74% 66%",
121
+ chart_5: "27 87% 67%"
122
+ },
123
+ stone: {
124
+ background: "0 0% 100%",
125
+ foreground: "20 14.3% 4.1%",
126
+ card: "0 0% 100%",
127
+ card_foreground: "20 14.3% 4.1%",
128
+ popover: "0 0% 100%",
129
+ popover_foreground: "20 14.3% 4.1%",
130
+ primary: "24 9.8% 10%",
131
+ primary_foreground: "60 9.1% 97.8%",
132
+ secondary: "60 4.8% 95.9%",
133
+ secondary_foreground: "24 9.8% 10%",
134
+ muted: "60 4.8% 95.9%",
135
+ muted_foreground: "25 5.3% 44.7%",
136
+ accent: "60 4.8% 95.9%",
137
+ accent_foreground: "24 9.8% 10%",
138
+ destructive: "0 84.2% 60.2%",
139
+ destructive_foreground: "60 9.1% 97.8%",
140
+ border: "20 5.9% 90%",
141
+ input: "20 5.9% 90%",
142
+ ring: "20 14.3% 4.1%",
143
+ radius: "0.5rem",
144
+ chart_1: "12 76% 61%",
145
+ chart_2: "173 58% 39%",
146
+ chart_3: "197 37% 24%",
147
+ chart_4: "43 74% 66%",
148
+ chart_5: "27 87% 67%"
149
+ },
150
+ gray: {
151
+ background: "0 0% 100%",
152
+ foreground: "224 71.4% 4.1%",
153
+ card: "0 0% 100%",
154
+ card_foreground: "224 71.4% 4.1%",
155
+ popover: "0 0% 100%",
156
+ popover_foreground: "224 71.4% 4.1%",
157
+ primary: "220.9 39.3% 11%",
158
+ primary_foreground: "210 20% 98%",
159
+ secondary: "220 14.3% 95.9%",
160
+ secondary_foreground: "220.9 39.3% 11%",
161
+ muted: "220 14.3% 95.9%",
162
+ muted_foreground: "220 8.9% 46.1%",
163
+ accent: "220 14.3% 95.9%",
164
+ accent_foreground: "220.9 39.3% 11%",
165
+ destructive: "0 84.2% 60.2%",
166
+ destructive_foreground: "210 20% 98%",
167
+ border: "220 13% 91%",
168
+ input: "220 13% 91%",
169
+ ring: "224 71.4% 4.1%",
170
+ radius: "0.5rem",
171
+ chart_1: "12 76% 61%",
172
+ chart_2: "173 58% 39%",
173
+ chart_3: "197 37% 24%",
174
+ chart_4: "43 74% 66%",
175
+ chart_5: "27 87% 67%"
176
+ },
177
+ zinc: {
178
+ background: "0 0% 100%",
179
+ foreground: "240 10% 3.9%",
180
+ card: "0 0% 100%",
181
+ card_foreground: "240 10% 3.9%",
182
+ popover: "0 0% 100%",
183
+ popover_foreground: "240 10% 3.9%",
184
+ primary: "240 5.9% 10%",
185
+ primary_foreground: "0 0% 98%",
186
+ secondary: "240 4.8% 95.9%",
187
+ secondary_foreground: "240 5.9% 10%",
188
+ muted: "240 4.8% 95.9%",
189
+ muted_foreground: "240 3.8% 46.1%",
190
+ accent: "240 4.8% 95.9%",
191
+ accent_foreground: "240 5.9% 10%",
192
+ destructive: "0 84.2% 60.2%",
193
+ destructive_foreground: "0 0% 98%",
194
+ border: "240 5.9% 90%",
195
+ input: "240 5.9% 90%",
196
+ ring: "240 5.9% 10%",
197
+ radius: "0.5rem",
198
+ chart_1: "12 76% 61%",
199
+ chart_2: "173 58% 39%",
200
+ chart_3: "197 37% 24%",
201
+ chart_4: "43 74% 66%",
202
+ chart_5: "27 87% 67%"
203
+ }
204
+ }.freeze
205
+
206
+ # Dark mode variants for each theme
207
+ DARK_THEME_VARIABLES = {
208
+ neutral: {
209
+ background: "0 0% 3.9%",
210
+ foreground: "0 0% 98%",
211
+ card: "0 0% 3.9%",
212
+ card_foreground: "0 0% 98%",
213
+ popover: "0 0% 3.9%",
214
+ popover_foreground: "0 0% 98%",
215
+ primary: "0 0% 98%",
216
+ primary_foreground: "0 0% 9%",
217
+ secondary: "0 0% 14.9%",
218
+ secondary_foreground: "0 0% 98%",
219
+ muted: "0 0% 14.9%",
220
+ muted_foreground: "0 0% 63.9%",
221
+ accent: "0 0% 14.9%",
222
+ accent_foreground: "0 0% 98%",
223
+ destructive: "0 62.8% 30.6%",
224
+ destructive_foreground: "0 0% 98%",
225
+ border: "0 0% 14.9%",
226
+ input: "0 0% 14.9%",
227
+ ring: "0 0% 83.1%",
228
+ chart_1: "220 70% 50%",
229
+ chart_2: "160 60% 45%",
230
+ chart_3: "30 80% 55%",
231
+ chart_4: "280 65% 60%",
232
+ chart_5: "340 75% 55%"
233
+ },
234
+ slate: {
235
+ background: "222.2 84% 4.9%",
236
+ foreground: "210 40% 98%",
237
+ card: "222.2 84% 4.9%",
238
+ card_foreground: "210 40% 98%",
239
+ popover: "222.2 84% 4.9%",
240
+ popover_foreground: "210 40% 98%",
241
+ primary: "210 40% 98%",
242
+ primary_foreground: "222.2 47.4% 11.2%",
243
+ secondary: "217.2 32.6% 17.5%",
244
+ secondary_foreground: "210 40% 98%",
245
+ muted: "217.2 32.6% 17.5%",
246
+ muted_foreground: "215 20.2% 65.1%",
247
+ accent: "217.2 32.6% 17.5%",
248
+ accent_foreground: "210 40% 98%",
249
+ destructive: "0 62.8% 30.6%",
250
+ destructive_foreground: "210 40% 98%",
251
+ border: "217.2 32.6% 17.5%",
252
+ input: "217.2 32.6% 17.5%",
253
+ ring: "212.7 26.8% 83.9%",
254
+ chart_1: "220 70% 50%",
255
+ chart_2: "160 60% 45%",
256
+ chart_3: "30 80% 55%",
257
+ chart_4: "280 65% 60%",
258
+ chart_5: "340 75% 55%"
259
+ },
260
+ stone: {
261
+ background: "20 14.3% 4.1%",
262
+ foreground: "60 9.1% 97.8%",
263
+ card: "20 14.3% 4.1%",
264
+ card_foreground: "60 9.1% 97.8%",
265
+ popover: "20 14.3% 4.1%",
266
+ popover_foreground: "60 9.1% 97.8%",
267
+ primary: "60 9.1% 97.8%",
268
+ primary_foreground: "24 9.8% 10%",
269
+ secondary: "12 6.5% 15.1%",
270
+ secondary_foreground: "60 9.1% 97.8%",
271
+ muted: "12 6.5% 15.1%",
272
+ muted_foreground: "24 5.4% 63.9%",
273
+ accent: "12 6.5% 15.1%",
274
+ accent_foreground: "60 9.1% 97.8%",
275
+ destructive: "0 62.8% 30.6%",
276
+ destructive_foreground: "60 9.1% 97.8%",
277
+ border: "12 6.5% 15.1%",
278
+ input: "12 6.5% 15.1%",
279
+ ring: "24 5.7% 82.9%",
280
+ chart_1: "220 70% 50%",
281
+ chart_2: "160 60% 45%",
282
+ chart_3: "30 80% 55%",
283
+ chart_4: "280 65% 60%",
284
+ chart_5: "340 75% 55%"
285
+ },
286
+ gray: {
287
+ background: "224 71.4% 4.1%",
288
+ foreground: "210 20% 98%",
289
+ card: "224 71.4% 4.1%",
290
+ card_foreground: "210 20% 98%",
291
+ popover: "224 71.4% 4.1%",
292
+ popover_foreground: "210 20% 98%",
293
+ primary: "210 20% 98%",
294
+ primary_foreground: "220.9 39.3% 11%",
295
+ secondary: "215 27.9% 16.9%",
296
+ secondary_foreground: "210 20% 98%",
297
+ muted: "215 27.9% 16.9%",
298
+ muted_foreground: "217.9 10.6% 64.9%",
299
+ accent: "215 27.9% 16.9%",
300
+ accent_foreground: "210 20% 98%",
301
+ destructive: "0 62.8% 30.6%",
302
+ destructive_foreground: "210 20% 98%",
303
+ border: "215 27.9% 16.9%",
304
+ input: "215 27.9% 16.9%",
305
+ ring: "216 12.2% 83.9%",
306
+ chart_1: "220 70% 50%",
307
+ chart_2: "160 60% 45%",
308
+ chart_3: "30 80% 55%",
309
+ chart_4: "280 65% 60%",
310
+ chart_5: "340 75% 55%"
311
+ },
312
+ zinc: {
313
+ background: "240 10% 3.9%",
314
+ foreground: "0 0% 98%",
315
+ card: "240 10% 3.9%",
316
+ card_foreground: "0 0% 98%",
317
+ popover: "240 10% 3.9%",
318
+ popover_foreground: "0 0% 98%",
319
+ primary: "0 0% 98%",
320
+ primary_foreground: "240 5.9% 10%",
321
+ secondary: "240 3.7% 15.9%",
322
+ secondary_foreground: "0 0% 98%",
323
+ muted: "240 3.7% 15.9%",
324
+ muted_foreground: "240 5% 64.9%",
325
+ accent: "240 3.7% 15.9%",
326
+ accent_foreground: "0 0% 98%",
327
+ destructive: "0 62.8% 30.6%",
328
+ destructive_foreground: "0 0% 98%",
329
+ border: "240 3.7% 15.9%",
330
+ input: "240 3.7% 15.9%",
331
+ ring: "240 4.9% 83.9%",
332
+ chart_1: "220 70% 50%",
333
+ chart_2: "160 60% 45%",
334
+ chart_3: "30 80% 55%",
335
+ chart_4: "280 65% 60%",
336
+ chart_5: "340 75% 55%"
337
+ }
338
+ }.freeze
339
+ end
340
+ end
341
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ isolate_namespace Shadcn::Rails
7
+
8
+
9
+ # Configure autoloading for components
10
+ initializer "shadcn-rails.autoloading", before: :set_autoload_paths do |app|
11
+ components_path = root.join("app/components")
12
+ app.config.autoload_paths << components_path
13
+
14
+ # Enable reloading of engine components in development
15
+ if ::Rails.env.development?
16
+ app.reloaders << app.config.file_watcher.new([], { components_path.to_s => ["rb"] }) do
17
+ # Trigger reload when component files change
18
+ end
19
+ end
20
+ end
21
+
22
+ initializer "shadcn-rails.view_component" do
23
+ ActiveSupport.on_load(:view_component) do
24
+ # ViewComponent is loaded
25
+ end
26
+ end
27
+
28
+ initializer "shadcn-rails.helpers" do
29
+ ActiveSupport.on_load(:action_view) do
30
+ include Shadcn::Rails::Helpers::ClassNameHelper
31
+ include Shadcn::Rails::Helpers::ComponentHelper
32
+ include Shadcn::Rails::Helpers::PaginationHelper
33
+ end
34
+ end
35
+
36
+ initializer "shadcn-rails.assets" do |app|
37
+ if app.config.respond_to?(:assets) && app.config.assets.respond_to?(:paths)
38
+ app.config.assets.paths << root.join("app/assets/stylesheets")
39
+ app.config.assets.paths << root.join("app/assets/javascripts")
40
+ end
41
+ end
42
+
43
+ initializer "shadcn-rails.importmap", before: "importmap" do |app|
44
+ if defined?(Importmap)
45
+ app.config.importmap.paths << root.join("config/importmap.rb")
46
+ app.config.importmap.cache_sweepers << root.join("app/assets/javascripts")
47
+ end
48
+ end
49
+
50
+ # Configure view component preview paths after initialization
51
+ initializer "shadcn-rails.preview_paths", after: :load_config_initializers do |app|
52
+ if defined?(ViewComponent::Base) && app.config.respond_to?(:view_component)
53
+ app.config.view_component.preview_paths ||= []
54
+ app.config.view_component.preview_paths << root.join("test/components/previews")
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Rails
5
+ module Helpers
6
+ # Helper module providing the cn() function for merging Tailwind CSS classes
7
+ # This is the Ruby equivalent of shadcn/ui's cn() utility
8
+ module ClassNameHelper
9
+ extend ActiveSupport::Concern
10
+
11
+ # Merge CSS classes intelligently, handling Tailwind class conflicts
12
+ # Similar to clsx + tailwind-merge in JavaScript
13
+ #
14
+ # @param args [Array<String, Hash, Array, nil>] Classes to merge
15
+ # @return [String] Merged class string
16
+ #
17
+ # @example Basic usage
18
+ # cn("px-4 py-2", "bg-blue-500") # => "px-4 py-2 bg-blue-500"
19
+ #
20
+ # @example With conditionals
21
+ # cn("base-class", { "active" => is_active, "disabled" => is_disabled })
22
+ #
23
+ # @example Overriding classes
24
+ # cn("px-4", "px-8") # => "px-8" (later class wins for same property)
25
+ #
26
+ def cn(*args)
27
+ Shadcn::Rails::ClassMerger.merge(*args)
28
+ end
29
+
30
+ # Alias for cn() for those who prefer the full name
31
+ alias_method :class_names, :cn
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Rails
5
+ module Helpers
6
+ # Helper methods for rendering shadcn components
7
+ module ComponentHelper
8
+ extend ActiveSupport::Concern
9
+
10
+ # Render a shadcn component by name
11
+ # This allows for cleaner ERB syntax
12
+ #
13
+ # @param name [Symbol, String] Component name (e.g., :button, :card)
14
+ # @param args [Hash] Component arguments
15
+ # @param block [Proc] Optional block for component content
16
+ #
17
+ # @example
18
+ # <%= shadcn :button, variant: :primary do %>
19
+ # Click me
20
+ # <% end %>
21
+ #
22
+ def shadcn(name, **args, &block)
23
+ component_class = Shadcn::Rails.component_for(name)
24
+ render(component_class.new(**args), &block)
25
+ end
26
+
27
+ # Shorthand helpers for common components
28
+ # These provide a more Rails-like API
29
+
30
+ def shadcn_button(**args, &block)
31
+ render(Shadcn::ButtonComponent.new(**args), &block)
32
+ end
33
+
34
+ def shadcn_card(**args, &block)
35
+ render(Shadcn::CardComponent.new(**args), &block)
36
+ end
37
+
38
+ def shadcn_input(**args)
39
+ render(Shadcn::InputComponent.new(**args))
40
+ end
41
+
42
+ def shadcn_label(**args, &block)
43
+ render(Shadcn::LabelComponent.new(**args), &block)
44
+ end
45
+
46
+ def shadcn_badge(**args, &block)
47
+ render(Shadcn::BadgeComponent.new(**args), &block)
48
+ end
49
+
50
+ def shadcn_alert(**args, &block)
51
+ render(Shadcn::AlertComponent.new(**args), &block)
52
+ end
53
+
54
+ def shadcn_dialog(**args, &block)
55
+ render(Shadcn::DialogComponent.new(**args), &block)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Rails
5
+ module Helpers
6
+ # Helper methods for integrating with popular Rails pagination gems
7
+ # Supports: will_paginate, Kaminari, and Pagy
8
+ module PaginationHelper
9
+ extend ActiveSupport::Concern
10
+
11
+ # Render a shadcn pagination component from a paginated collection or Pagy object
12
+ #
13
+ # @param collection_or_pagy [Object] A paginated collection (Kaminari/will_paginate) or Pagy object
14
+ # @param pagy [Pagy, nil] Optional Pagy object (for explicit pagy usage)
15
+ # @param url_builder [Proc, nil] Lambda/Proc to generate page URLs, receives page number
16
+ # @param window [Integer] Number of pages to show around current page (default: 2)
17
+ # @param options [Hash] Additional options passed to PaginationComponent
18
+ #
19
+ # @example With Kaminari
20
+ # <%= shadcn_paginate @users %>
21
+ #
22
+ # @example With will_paginate
23
+ # <%= shadcn_paginate @posts %>
24
+ #
25
+ # @example With Pagy
26
+ # <%= shadcn_paginate @pagy %>
27
+ #
28
+ # @example With custom URL builder
29
+ # <%= shadcn_paginate @posts, url_builder: ->(page) { posts_path(page: page, sort: params[:sort]) } %>
30
+ #
31
+ def shadcn_paginate(collection_or_pagy, pagy: nil, url_builder: nil, window: 2, **options)
32
+ pagination_data = extract_pagination_data(collection_or_pagy, pagy: pagy)
33
+ return nil if pagination_data[:total_pages] <= 1
34
+
35
+ url_builder ||= default_url_builder
36
+ series = generate_page_series(
37
+ pagination_data[:current_page],
38
+ pagination_data[:total_pages],
39
+ window: window
40
+ )
41
+
42
+ render Shadcn::PaginationComponent.new(**options) do |pagination|
43
+ pagination.with_pagination_content do |content|
44
+ # Previous button
45
+ prev_href = pagination_data[:prev_page] ? url_builder.call(pagination_data[:prev_page]) : nil
46
+ content.with_previous(href: prev_href, disabled: pagination_data[:prev_page].nil?)
47
+
48
+ # Page items
49
+ series.each do |item|
50
+ case item
51
+ when :gap
52
+ content.with_ellipse
53
+ when Integer
54
+ content.with_item(href: url_builder.call(item)) { item.to_s }
55
+ when String # Current page (string format)
56
+ page_num = item.to_i
57
+ content.with_item(href: url_builder.call(page_num), active: true) { item }
58
+ end
59
+ end
60
+
61
+ # Next button
62
+ next_href = pagination_data[:next_page] ? url_builder.call(pagination_data[:next_page]) : nil
63
+ content.with_next_page(href: next_href, disabled: pagination_data[:next_page].nil?)
64
+ end
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ # Extract pagination data from various sources
71
+ # Returns a hash with :current_page, :total_pages, :prev_page, :next_page
72
+ def extract_pagination_data(collection_or_pagy, pagy: nil)
73
+ obj = pagy || collection_or_pagy
74
+
75
+ # Pagy detection (has .page and .pages methods, or is a Pagy instance)
76
+ if defined?(::Pagy) && obj.is_a?(::Pagy)
77
+ {
78
+ current_page: obj.page,
79
+ total_pages: obj.pages,
80
+ prev_page: obj.prev,
81
+ next_page: obj.next
82
+ }
83
+ # Duck typing for Pagy-like objects (has .page and .pages)
84
+ elsif obj.respond_to?(:page) && obj.respond_to?(:pages) && obj.respond_to?(:prev) && obj.respond_to?(:next)
85
+ {
86
+ current_page: obj.page,
87
+ total_pages: obj.pages,
88
+ prev_page: obj.prev,
89
+ next_page: obj.next
90
+ }
91
+ # Kaminari detection (has current_page and total_pages, with prev_page)
92
+ elsif obj.respond_to?(:current_page) && obj.respond_to?(:total_pages) && obj.respond_to?(:prev_page)
93
+ {
94
+ current_page: obj.current_page,
95
+ total_pages: obj.total_pages,
96
+ prev_page: obj.prev_page,
97
+ next_page: obj.next_page
98
+ }
99
+ # will_paginate detection (has current_page and total_pages, with previous_page)
100
+ elsif obj.respond_to?(:current_page) && obj.respond_to?(:total_pages) && obj.respond_to?(:previous_page)
101
+ {
102
+ current_page: obj.current_page,
103
+ total_pages: obj.total_pages,
104
+ prev_page: obj.previous_page,
105
+ next_page: obj.next_page
106
+ }
107
+ # Generic fallback for collections with current_page and total_pages
108
+ elsif obj.respond_to?(:current_page) && obj.respond_to?(:total_pages)
109
+ current = obj.current_page
110
+ total = obj.total_pages
111
+ {
112
+ current_page: current,
113
+ total_pages: total,
114
+ prev_page: current > 1 ? current - 1 : nil,
115
+ next_page: current < total ? current + 1 : nil
116
+ }
117
+ else
118
+ raise ArgumentError, "Expected a paginated collection (Kaminari/will_paginate) or Pagy object. " \
119
+ "Object must respond to current_page/total_pages or page/pages."
120
+ end
121
+ end
122
+
123
+ # Generate an array of page numbers with gaps
124
+ # Current page is returned as a String, others as Integers, gaps as :gap
125
+ #
126
+ # @example generate_page_series(5, 10, window: 2)
127
+ # => [1, :gap, 3, 4, "5", 6, 7, :gap, 10]
128
+ def generate_page_series(current_page, total_pages, window: 2)
129
+ return [(current_page.to_s)] if total_pages <= 1
130
+
131
+ series = []
132
+
133
+ # Always include first page
134
+ series << 1
135
+
136
+ # Calculate range around current page
137
+ range_start = [2, current_page - window].max
138
+ range_end = [total_pages - 1, current_page + window].min
139
+
140
+ # Add gap before range if needed
141
+ if range_start > 2
142
+ series << :gap
143
+ elsif range_start == 2
144
+ # No gap needed, just add page 2
145
+ end
146
+
147
+ # Add pages in range (including potential page 2 if range_start is 2)
148
+ (range_start..range_end).each do |page|
149
+ next if page == 1 || page == total_pages # Skip first and last (added separately)
150
+
151
+ if page == current_page
152
+ series << page.to_s
153
+ else
154
+ series << page
155
+ end
156
+ end
157
+
158
+ # Add gap after range if needed
159
+ if range_end < total_pages - 1
160
+ series << :gap
161
+ end
162
+
163
+ # Always include last page if more than 1 page
164
+ if total_pages > 1
165
+ if current_page == total_pages
166
+ series << total_pages.to_s
167
+ else
168
+ series << total_pages
169
+ end
170
+ end
171
+
172
+ # Handle current page being 1 or total_pages
173
+ if current_page == 1
174
+ series[0] = "1"
175
+ end
176
+
177
+ series
178
+ end
179
+
180
+ # Default URL builder using query params
181
+ def default_url_builder
182
+ ->(page) { "?page=#{page}" }
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Rails
5
+ VERSION = "0.1.0"
6
+ end
7
+ end