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,275 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Carousel component for sliding content
5
+ # Matches shadcn/ui Carousel component
6
+ # Uses Stimulus for interactivity with swipe and keyboard navigation
7
+ #
8
+ # @example Basic carousel
9
+ # <%= render Shadcn::CarouselComponent.new do |carousel| %>
10
+ # <% carousel.with_slides do |slides| %>
11
+ # <% 5.times do |i| %>
12
+ # <% slides.with_item do %>
13
+ # <div class="p-6 text-center">Slide <%= i + 1 %></div>
14
+ # <% end %>
15
+ # <% end %>
16
+ # <% end %>
17
+ # <% carousel.with_previous { "Previous" } %>
18
+ # <% carousel.with_next { "Next" } %>
19
+ # <% end %>
20
+ #
21
+ # @example Vertical carousel
22
+ # <%= render Shadcn::CarouselComponent.new(orientation: :vertical) do |carousel| %>
23
+ # <% carousel.with_slides do |slides| %>
24
+ # <% 5.times do |i| %>
25
+ # <% slides.with_item do %>
26
+ # <div class="p-6 text-center">Slide <%= i + 1 %></div>
27
+ # <% end %>
28
+ # <% end %>
29
+ # <% end %>
30
+ # <% end %>
31
+ #
32
+ class CarouselComponent < BaseComponent
33
+ renders_one :slides, lambda { |**options|
34
+ CarouselContentComponent.new(orientation: @orientation, **options)
35
+ }
36
+ renders_one :previous, lambda { |**options, &block|
37
+ CarouselPreviousComponent.new(orientation: @orientation, **options, &block)
38
+ }
39
+ renders_one :next, lambda { |**options, &block|
40
+ CarouselNextComponent.new(orientation: @orientation, **options, &block)
41
+ }
42
+
43
+ ORIENTATIONS = {
44
+ horizontal: "horizontal",
45
+ vertical: "vertical"
46
+ }.freeze
47
+
48
+ # @param orientation [Symbol] Direction of carousel (:horizontal, :vertical)
49
+ # @param loop [Boolean] Whether to loop infinitely
50
+ # @param autoplay [Boolean] Whether to auto-advance slides
51
+ # @param autoplay_interval [Integer] Milliseconds between auto-advance (default: 4000)
52
+ # @param align [Symbol] Item alignment (:start, :center, :end)
53
+ def initialize(orientation: :horizontal, loop: false, autoplay: false, autoplay_interval: 4000, align: :start, **options)
54
+ super(**options)
55
+ @orientation = orientation.to_sym
56
+ @loop = loop
57
+ @autoplay = autoplay
58
+ @autoplay_interval = autoplay_interval
59
+ @align = align.to_sym
60
+ end
61
+
62
+ def call
63
+ content_tag(:div, carousel_content, carousel_attributes)
64
+ end
65
+
66
+ private
67
+
68
+ def carousel_content
69
+ safe_join([slides, previous, self.next].compact)
70
+ end
71
+
72
+ def carousel_attributes
73
+ attrs = {
74
+ class: cn(
75
+ "relative",
76
+ class_name
77
+ ),
78
+ role: "region",
79
+ "aria-roledescription": "carousel",
80
+ "data-controller": "shadcn--carousel",
81
+ "data-shadcn--carousel-orientation-value": @orientation.to_s,
82
+ "data-shadcn--carousel-loop-value": @loop.to_s,
83
+ "data-shadcn--carousel-autoplay-value": @autoplay.to_s,
84
+ "data-shadcn--carousel-autoplay-interval-value": @autoplay_interval.to_s,
85
+ "data-shadcn--carousel-align-value": @align.to_s
86
+ }
87
+ attrs.merge!(html_options)
88
+ attrs.merge!(build_data)
89
+ attrs.compact
90
+ end
91
+ end
92
+
93
+ # Container for carousel items
94
+ class CarouselContentComponent < BaseComponent
95
+ renders_many :items, lambda { |**options, &block|
96
+ CarouselItemComponent.new(orientation: @orientation, **options, &block)
97
+ }
98
+
99
+ def initialize(orientation: :horizontal, **options)
100
+ super(**options)
101
+ @orientation = orientation.to_sym
102
+ end
103
+
104
+ def call
105
+ content_tag(:div, wrapper_content, wrapper_attributes)
106
+ end
107
+
108
+ private
109
+
110
+ def wrapper_content
111
+ content_tag(:div, items_content, content_attributes)
112
+ end
113
+
114
+ def items_content
115
+ safe_join([items, content].compact.flatten)
116
+ end
117
+
118
+ def wrapper_attributes
119
+ {
120
+ class: "overflow-hidden",
121
+ "data-shadcn--carousel-target": "viewport"
122
+ }
123
+ end
124
+
125
+ def content_attributes
126
+ {
127
+ class: cn(
128
+ "flex",
129
+ @orientation == :horizontal ? "-ml-4" : "-mt-4 flex-col",
130
+ class_name
131
+ ),
132
+ "data-shadcn--carousel-target": "content"
133
+ }
134
+ end
135
+ end
136
+
137
+ # Individual carousel item
138
+ class CarouselItemComponent < BaseComponent
139
+ def initialize(orientation: :horizontal, basis: nil, **options)
140
+ super(**options)
141
+ @orientation = orientation.to_sym
142
+ @basis = basis
143
+ end
144
+
145
+ def call
146
+ content_tag(:div, content, item_attributes)
147
+ end
148
+
149
+ private
150
+
151
+ def item_attributes
152
+ {
153
+ role: "group",
154
+ "aria-roledescription": "slide",
155
+ class: cn(
156
+ "min-w-0 shrink-0 grow-0",
157
+ @orientation == :horizontal ? "pl-4" : "pt-4",
158
+ @basis || "basis-full",
159
+ class_name
160
+ ),
161
+ "data-shadcn--carousel-target": "item"
162
+ }
163
+ end
164
+ end
165
+
166
+ # Previous button component
167
+ class CarouselPreviousComponent < BaseComponent
168
+ def initialize(orientation: :horizontal, **options)
169
+ super(**options)
170
+ @orientation = orientation.to_sym
171
+ end
172
+
173
+ def call
174
+ content_tag(:button, button_content, button_attributes)
175
+ end
176
+
177
+ private
178
+
179
+ def button_content
180
+ if content.present?
181
+ content
182
+ else
183
+ default_icon
184
+ end
185
+ end
186
+
187
+ def default_icon
188
+ # Left arrow for horizontal, up arrow for vertical
189
+ if @orientation == :horizontal
190
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><path d="m12 19-7-7 7-7"/><path d="M19 12H5"/></svg>'.html_safe
191
+ else
192
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><path d="m18 15-6-6-6 6"/></svg>'.html_safe
193
+ end
194
+ end
195
+
196
+ def button_attributes
197
+ position_classes = if @orientation == :horizontal
198
+ "-left-12 top-1/2 -translate-y-1/2"
199
+ else
200
+ "-top-12 left-1/2 -translate-x-1/2 rotate-90"
201
+ end
202
+
203
+ {
204
+ type: "button",
205
+ class: cn(
206
+ "absolute h-8 w-8 rounded-full",
207
+ "inline-flex items-center justify-center whitespace-nowrap text-sm font-medium",
208
+ "ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
209
+ "disabled:pointer-events-none disabled:opacity-50",
210
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
211
+ position_classes,
212
+ class_name
213
+ ),
214
+ "data-shadcn--carousel-target": "prevButton",
215
+ "data-action": "click->shadcn--carousel#previous",
216
+ "aria-label": "Previous slide"
217
+ }
218
+ end
219
+ end
220
+
221
+ # Next button component
222
+ class CarouselNextComponent < BaseComponent
223
+ def initialize(orientation: :horizontal, **options)
224
+ super(**options)
225
+ @orientation = orientation.to_sym
226
+ end
227
+
228
+ def call
229
+ content_tag(:button, button_content, button_attributes)
230
+ end
231
+
232
+ private
233
+
234
+ def button_content
235
+ if content.present?
236
+ content
237
+ else
238
+ default_icon
239
+ end
240
+ end
241
+
242
+ def default_icon
243
+ # Right arrow for horizontal, down arrow for vertical
244
+ if @orientation == :horizontal
245
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>'.html_safe
246
+ else
247
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><path d="m6 9 6 6 6-6"/></svg>'.html_safe
248
+ end
249
+ end
250
+
251
+ def button_attributes
252
+ position_classes = if @orientation == :horizontal
253
+ "-right-12 top-1/2 -translate-y-1/2"
254
+ else
255
+ "-bottom-12 left-1/2 -translate-x-1/2 rotate-90"
256
+ end
257
+
258
+ {
259
+ type: "button",
260
+ class: cn(
261
+ "absolute h-8 w-8 rounded-full",
262
+ "inline-flex items-center justify-center whitespace-nowrap text-sm font-medium",
263
+ "ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
264
+ "disabled:pointer-events-none disabled:opacity-50",
265
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
266
+ position_classes,
267
+ class_name
268
+ ),
269
+ "data-shadcn--carousel-target": "nextButton",
270
+ "data-action": "click->shadcn--carousel#next",
271
+ "aria-label": "Next slide"
272
+ }
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Checkbox component using native <input type="checkbox">
5
+ # Styled with CSS to match shadcn/ui design
6
+ # Works without JavaScript
7
+ #
8
+ # @example Basic checkbox
9
+ # <%= render Shadcn::CheckboxComponent.new(name: "terms", id: "terms") %>
10
+ # <%= render Shadcn::LabelComponent.new(for: "terms") { "Accept terms" } %>
11
+ #
12
+ # @example Checked by default
13
+ # <%= render Shadcn::CheckboxComponent.new(name: "subscribe", checked: true) %>
14
+ #
15
+ # @example Disabled
16
+ # <%= render Shadcn::CheckboxComponent.new(name: "locked", disabled: true) %>
17
+ #
18
+ # @example With integrated label
19
+ # <%= render Shadcn::CheckboxComponent.new(name: "terms", id: "terms") { "Accept terms and conditions" } %>
20
+ #
21
+ class CheckboxComponent < BaseComponent
22
+ BASE_CLASSES = [
23
+ "shadcn-checkbox",
24
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary",
25
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1",
26
+ "disabled:cursor-not-allowed disabled:opacity-50",
27
+ "cursor-pointer"
28
+ ].join(" ")
29
+
30
+ # @param name [String, nil] Input name attribute
31
+ # @param id [String, nil] Input id attribute
32
+ # @param value [String] Value when checked
33
+ # @param checked [Boolean] Whether checkbox is checked
34
+ # @param disabled [Boolean] Whether checkbox is disabled
35
+ # @param required [Boolean] Whether checkbox is required
36
+ def initialize(
37
+ name: nil,
38
+ id: nil,
39
+ value: "1",
40
+ checked: false,
41
+ disabled: false,
42
+ required: false,
43
+ **options
44
+ )
45
+ super(**options)
46
+ @name = name
47
+ @id = id || (name ? "checkbox-#{name}" : nil)
48
+ @value = value
49
+ @checked = checked
50
+ @disabled = disabled
51
+ @required = required
52
+ end
53
+
54
+ def call
55
+ if content.present?
56
+ # Render with integrated label
57
+ content_tag(:label, class: "flex items-center space-x-2 cursor-pointer") do
58
+ safe_join([
59
+ hidden_input,
60
+ checkbox_input,
61
+ content_tag(:span, content, class: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70")
62
+ ])
63
+ end
64
+ else
65
+ # Render just the checkbox (for use with external labels)
66
+ safe_join([hidden_input, checkbox_input].compact)
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def hidden_input
73
+ # Include hidden input with "0" value for unchecked state (Rails convention)
74
+ return unless @name
75
+
76
+ tag(:input,
77
+ type: "hidden",
78
+ name: @name,
79
+ value: "0",
80
+ autocomplete: "off"
81
+ )
82
+ end
83
+
84
+ def checkbox_input
85
+ tag(:input, input_attributes)
86
+ end
87
+
88
+ def input_attributes
89
+ attrs = {
90
+ type: "checkbox",
91
+ name: @name,
92
+ id: @id,
93
+ value: @value,
94
+ class: cn(BASE_CLASSES, class_name),
95
+ disabled: @disabled || nil,
96
+ checked: @checked || nil,
97
+ required: @required || nil
98
+ }
99
+ attrs.merge!(html_options.except(:class))
100
+ attrs.compact
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Collapsible component for expandable content
5
+ # Matches shadcn/ui Collapsible component
6
+ # Uses Stimulus for interactivity
7
+ #
8
+ # @example Basic collapsible
9
+ # <%= render Shadcn::CollapsibleComponent.new do |collapsible| %>
10
+ # <% collapsible.with_trigger do %>
11
+ # <%= render Shadcn::ButtonComponent.new(variant: :ghost, size: :sm) do %>
12
+ # Toggle
13
+ # <% end %>
14
+ # <% end %>
15
+ # <% collapsible.with_content do %>
16
+ # Hidden content here
17
+ # <% end %>
18
+ # <% end %>
19
+ #
20
+ class CollapsibleComponent < BaseComponent
21
+ renders_one :trigger
22
+ renders_one :body, lambda { |**options, &block|
23
+ CollapsibleContentComponent.new(**options, &block)
24
+ }
25
+
26
+ # @param open [Boolean] Whether collapsible starts open
27
+ # @param disabled [Boolean] Whether collapsible is disabled
28
+ def initialize(open: false, disabled: false, **options)
29
+ super(**options)
30
+ @open = open
31
+ @disabled = disabled
32
+ end
33
+
34
+ def call
35
+ content_tag(:div, collapsible_content, collapsible_attributes)
36
+ end
37
+
38
+ private
39
+
40
+ def collapsible_content
41
+ safe_join([trigger_wrapper, body].compact)
42
+ end
43
+
44
+ def trigger_wrapper
45
+ return unless trigger
46
+
47
+ content_tag(:div, trigger, {
48
+ "data-shadcn--collapsible-target": "trigger",
49
+ "data-action": "click->shadcn--collapsible#toggle"
50
+ })
51
+ end
52
+
53
+ def collapsible_attributes
54
+ attrs = {
55
+ class: class_name,
56
+ "data-controller": "shadcn--collapsible",
57
+ "data-shadcn--collapsible-open-value": @open.to_s,
58
+ "data-shadcn--collapsible-disabled-value": @disabled.to_s,
59
+ "data-state": @open ? "open" : "closed"
60
+ }
61
+ attrs.merge!(html_options)
62
+ attrs.merge!(build_data)
63
+ attrs.compact
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Collapsible Content component
5
+ class CollapsibleContentComponent < BaseComponent
6
+ BASE_CLASSES = "overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down"
7
+
8
+ def initialize(open: false, **options, &block)
9
+ super(**options, &block)
10
+ @open = open
11
+ end
12
+
13
+ def call
14
+ content_tag(:div, content, content_attributes)
15
+ end
16
+
17
+ private
18
+
19
+ def content_attributes
20
+ {
21
+ class: merge_classes(BASE_CLASSES),
22
+ "data-shadcn--collapsible-target": "content",
23
+ "data-state": @open ? "open" : "closed",
24
+ hidden: !@open
25
+ }
26
+ end
27
+ end
28
+ end