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,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarMenuSubButton component - button within submenu item
5
+ class SidebarMenuSubButtonComponent < BaseComponent
6
+ SIZES = {
7
+ sm: "text-xs",
8
+ md: "text-sm"
9
+ }.freeze
10
+
11
+ BASE_CLASSES = "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground"
12
+ ACTIVE_CLASSES = "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground"
13
+
14
+ def initialize(size: :md, is_active: false, href: nil, **options)
15
+ super(**options)
16
+ @size = size.to_sym
17
+ @is_active = is_active
18
+ @href = href
19
+ end
20
+
21
+ def call
22
+ if @href
23
+ content_tag(:a, content, button_attributes.merge(href: @href))
24
+ else
25
+ content_tag(:button, content, button_attributes.merge(type: "button"))
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def button_attributes
32
+ attrs = {
33
+ class: cn(BASE_CLASSES, ACTIVE_CLASSES, SIZES[@size], class_name),
34
+ "data-sidebar": "menu-sub-button",
35
+ "data-size": @size,
36
+ "data-active": @is_active
37
+ }
38
+ attrs.merge!(html_options)
39
+ attrs.merge!(build_data)
40
+ attrs.compact
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarMenuSub component - submenu container
5
+ class SidebarMenuSubComponent < BaseComponent
6
+ BASE_CLASSES = "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5"
7
+ COLLAPSED_CLASSES = "group-data-[collapsible=icon]:hidden"
8
+
9
+ renders_many :items, lambda { |**options|
10
+ SidebarMenuSubItemComponent.new(**options)
11
+ }
12
+
13
+ def call
14
+ content_tag(:ul, sub_content, sub_attributes)
15
+ end
16
+
17
+ private
18
+
19
+ def sub_content
20
+ items.any? ? safe_join(items) : content
21
+ end
22
+
23
+ def sub_attributes
24
+ attrs = {
25
+ class: cn(BASE_CLASSES, COLLAPSED_CLASSES, class_name),
26
+ "data-sidebar": "menu-sub"
27
+ }
28
+ attrs.merge!(html_options)
29
+ attrs.merge!(build_data)
30
+ attrs.compact
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarMenuSubItem component - submenu item
5
+ class SidebarMenuSubItemComponent < BaseComponent
6
+ renders_one :button, lambda { |**options|
7
+ SidebarMenuSubButtonComponent.new(**options)
8
+ }
9
+
10
+ def call
11
+ content_tag(:li, item_content, item_attributes)
12
+ end
13
+
14
+ private
15
+
16
+ def item_content
17
+ button || content
18
+ end
19
+
20
+ def item_attributes
21
+ attrs = {
22
+ "data-sidebar": "menu-sub-item"
23
+ }
24
+ attrs[:class] = class_name if class_name.present?
25
+ attrs.merge!(html_options)
26
+ attrs.merge!(build_data)
27
+ attrs.compact
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarProvider component - wrapper that provides sidebar context and state
5
+ class SidebarProviderComponent < BaseComponent
6
+ BASE_CLASSES = "group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar"
7
+
8
+ renders_one :sidebar, lambda { |**options|
9
+ SidebarComponent.new(**options)
10
+ }
11
+ renders_one :inset, lambda { |**options|
12
+ SidebarInsetComponent.new(**options)
13
+ }
14
+
15
+ def initialize(default_open: true, open: nil, keyboard_shortcut: "b", **options)
16
+ super(**options)
17
+ @default_open = default_open
18
+ @open = open
19
+ @keyboard_shortcut = keyboard_shortcut
20
+ end
21
+
22
+ def call
23
+ content_tag(:div, provider_content, provider_attributes)
24
+ end
25
+
26
+ private
27
+
28
+ def provider_content
29
+ safe_join([sidebar, inset, content].compact)
30
+ end
31
+
32
+ def provider_attributes
33
+ attrs = {
34
+ class: cn(BASE_CLASSES, class_name),
35
+ style: sidebar_style,
36
+ "data-controller": "shadcn--sidebar",
37
+ "data-shadcn--sidebar-open-value": initial_open_state,
38
+ "data-shadcn--sidebar-keyboard-shortcut-value": @keyboard_shortcut
39
+ }
40
+ attrs.merge!(html_options)
41
+ attrs.merge!(build_data)
42
+ attrs.compact
43
+ end
44
+
45
+ def sidebar_style
46
+ [
47
+ "--sidebar-width: 16rem",
48
+ "--sidebar-width-icon: 3rem"
49
+ ].join("; ")
50
+ end
51
+
52
+ def initial_open_state
53
+ # Use explicit open prop if provided, otherwise use default_open
54
+ @open.nil? ? @default_open : @open
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarRail component - thin interactive rail for expanding collapsed sidebar
5
+ class SidebarRailComponent < BaseComponent
6
+ BASE_CLASSES = "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex"
7
+ HOVER_EXPAND_CLASSES = "[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize [[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar [[data-side=left][data-collapsible=offcanvas]_&]:-right-2 [[data-side=right][data-collapsible=offcanvas]_&]:-left-2"
8
+
9
+ def call
10
+ content_tag(:button, nil, rail_attributes)
11
+ end
12
+
13
+ private
14
+
15
+ def rail_attributes
16
+ attrs = {
17
+ type: "button",
18
+ class: cn(BASE_CLASSES, HOVER_EXPAND_CLASSES, class_name),
19
+ tabindex: "-1",
20
+ "aria-label": "Toggle Sidebar",
21
+ title: "Toggle Sidebar",
22
+ "data-sidebar": "rail",
23
+ "data-action": "click->shadcn--sidebar#toggle"
24
+ }
25
+ attrs.merge!(html_options)
26
+ attrs.merge!(build_data)
27
+ attrs.compact
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarSeparator component - visual separator for sidebar
5
+ class SidebarSeparatorComponent < BaseComponent
6
+ BASE_CLASSES = "mx-2 w-auto bg-sidebar-border"
7
+
8
+ def call
9
+ content_tag(:hr, nil, separator_attributes)
10
+ end
11
+
12
+ private
13
+
14
+ def separator_attributes
15
+ attrs = {
16
+ class: cn(BASE_CLASSES, class_name),
17
+ "data-sidebar": "separator"
18
+ }
19
+ attrs.merge!(html_options)
20
+ attrs.merge!(build_data)
21
+ attrs.compact
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarTrigger component - button to toggle sidebar open/closed
5
+ class SidebarTriggerComponent < BaseComponent
6
+ BASE_CLASSES = "h-7 w-7"
7
+
8
+ def call
9
+ render ButtonComponent.new(
10
+ variant: :ghost,
11
+ size: :icon,
12
+ class_name: cn(BASE_CLASSES, class_name),
13
+ data: { sidebar: "trigger", action: "click->shadcn--sidebar#toggle" },
14
+ **html_options
15
+ ) do
16
+ trigger_content
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def trigger_content
23
+ if content?
24
+ content
25
+ else
26
+ default_icon
27
+ end
28
+ end
29
+
30
+ def default_icon
31
+ # PanelLeft icon from Lucide
32
+ content_tag(:svg, nil,
33
+ xmlns: "http://www.w3.org/2000/svg",
34
+ width: "24",
35
+ height: "24",
36
+ viewBox: "0 0 24 24",
37
+ fill: "none",
38
+ stroke: "currentColor",
39
+ "stroke-width": "2",
40
+ "stroke-linecap": "round",
41
+ "stroke-linejoin": "round",
42
+ class: "size-4"
43
+ ) do
44
+ safe_join([
45
+ content_tag(:rect, nil, width: "18", height: "18", x: "3", y: "3", rx: "2"),
46
+ content_tag(:path, nil, d: "M9 3v18")
47
+ ])
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Skeleton component for loading placeholders
5
+ # Matches shadcn/ui Skeleton component
6
+ #
7
+ # @example Basic skeleton
8
+ # <%= render Shadcn::SkeletonComponent.new(class_name: "h-4 w-[250px]") %>
9
+ #
10
+ # @example Circle skeleton (for avatars)
11
+ # <%= render Shadcn::SkeletonComponent.new(class_name: "h-12 w-12 rounded-full") %>
12
+ #
13
+ # @example Card skeleton
14
+ # <div class="flex flex-col space-y-3">
15
+ # <%= render Shadcn::SkeletonComponent.new(class_name: "h-[125px] w-[250px] rounded-xl") %>
16
+ # <div class="space-y-2">
17
+ # <%= render Shadcn::SkeletonComponent.new(class_name: "h-4 w-[250px]") %>
18
+ # <%= render Shadcn::SkeletonComponent.new(class_name: "h-4 w-[200px]") %>
19
+ # </div>
20
+ # </div>
21
+ #
22
+ class SkeletonComponent < BaseComponent
23
+ BASE_CLASSES = "animate-pulse rounded-md bg-primary/10"
24
+
25
+ def call
26
+ content_tag(:div, content, class: merge_classes(BASE_CLASSES), **html_options.merge(build_data))
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Slider component for selecting values within a range
5
+ # Uses a native range input with custom styling for reliability
6
+ # Matches shadcn/ui Slider component appearance
7
+ #
8
+ # @example Basic usage
9
+ # <%= render Shadcn::SliderComponent.new(name: "volume", value: 50, max: 100) %>
10
+ #
11
+ # @example With step
12
+ # <%= render Shadcn::SliderComponent.new(name: "rating", value: 3, min: 1, max: 5, step: 1) %>
13
+ #
14
+ class SliderComponent < BaseComponent
15
+ BASE_CLASSES = "shadcn-slider w-full h-1.5 rounded-full appearance-none cursor-pointer bg-primary/20 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
16
+
17
+ # @param name [String] Input name attribute
18
+ # @param value [Numeric] Current value
19
+ # @param min [Numeric] Minimum value
20
+ # @param max [Numeric] Maximum value
21
+ # @param step [Numeric] Step increment
22
+ # @param disabled [Boolean] Whether slider is disabled
23
+ def initialize(
24
+ name: nil,
25
+ value: 0,
26
+ min: 0,
27
+ max: 100,
28
+ step: 1,
29
+ disabled: false,
30
+ **options
31
+ )
32
+ super(**options)
33
+ @name = name
34
+ @value = value.to_f
35
+ @min = min.to_f
36
+ @max = max.to_f
37
+ @step = step.to_f
38
+ @disabled = disabled
39
+ end
40
+
41
+ def call
42
+ tag(:input, input_attributes)
43
+ end
44
+
45
+ private
46
+
47
+ def input_attributes
48
+ attrs = {
49
+ type: "range",
50
+ name: @name,
51
+ value: @value,
52
+ min: @min,
53
+ max: @max,
54
+ step: @step,
55
+ disabled: @disabled ? true : nil,
56
+ class: merge_classes(BASE_CLASSES),
57
+ style: slider_style,
58
+ "data-controller": "shadcn--slider",
59
+ "data-action": "input->shadcn--slider#updateStyle"
60
+ }
61
+ attrs.merge!(html_options)
62
+ attrs.merge!(build_data)
63
+ attrs.compact
64
+ end
65
+
66
+ def slider_style
67
+ # CSS custom property for the fill percentage
68
+ "--slider-fill: #{percentage}%"
69
+ end
70
+
71
+ def percentage
72
+ return 0 if @max == @min
73
+ ((@value - @min) / (@max - @min) * 100).round(2)
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Spinner component for loading states
5
+ # Matches shadcn/ui Spinner component
6
+ #
7
+ # @example Basic spinner
8
+ # <%= render Shadcn::SpinnerComponent.new %>
9
+ #
10
+ # @example With size
11
+ # <%= render Shadcn::SpinnerComponent.new(size: :sm) %>
12
+ # <%= render Shadcn::SpinnerComponent.new(size: :lg) %>
13
+ #
14
+ # @example Custom color
15
+ # <%= render Shadcn::SpinnerComponent.new(class_name: "text-primary") %>
16
+ #
17
+ class SpinnerComponent < BaseComponent
18
+ SIZES = {
19
+ sm: "h-4 w-4",
20
+ default: "h-6 w-6",
21
+ lg: "h-8 w-8",
22
+ xl: "h-12 w-12"
23
+ }.freeze
24
+
25
+ BASE_CLASSES = "animate-spin text-muted-foreground"
26
+
27
+ # @param size [Symbol] Spinner size (:sm, :default, :lg, :xl)
28
+ def initialize(size: :default, **options)
29
+ super(**options)
30
+ @size = size.to_sym
31
+ end
32
+
33
+ def call
34
+ tag.svg(**spinner_attributes) do
35
+ safe_join([
36
+ tag.circle(
37
+ class: "opacity-25",
38
+ cx: "12",
39
+ cy: "12",
40
+ r: "10",
41
+ stroke: "currentColor",
42
+ "stroke-width": "4",
43
+ fill: "none"
44
+ ),
45
+ tag.path(
46
+ class: "opacity-75",
47
+ fill: "currentColor",
48
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
49
+ )
50
+ ])
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def spinner_attributes
57
+ {
58
+ class: cn(BASE_CLASSES, SIZES[@size], class_name),
59
+ xmlns: "http://www.w3.org/2000/svg",
60
+ fill: "none",
61
+ viewBox: "0 0 24 24",
62
+ role: "status",
63
+ "aria-label": "Loading"
64
+ }.merge(html_options).merge(build_data).compact
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Switch component for toggle inputs
5
+ # Uses a custom button element with hidden input for form submission
6
+ # Requires Stimulus controller for interactivity
7
+ #
8
+ # @example Basic switch
9
+ # <%= render Shadcn::SwitchComponent.new(name: "notifications") %>
10
+ #
11
+ # @example With integrated label
12
+ # <%= render Shadcn::SwitchComponent.new(name: "dark_mode") { "Enable dark mode" } %>
13
+ #
14
+ # @example Checked by default
15
+ # <%= render Shadcn::SwitchComponent.new(name: "active", checked: true) %>
16
+ #
17
+ class SwitchComponent < BaseComponent
18
+ BASE_CLASSES = [
19
+ "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full",
20
+ "border-2 border-transparent shadow-sm transition-colors",
21
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
22
+ "disabled:cursor-not-allowed disabled:opacity-50",
23
+ "data-[state=checked]:bg-primary data-[state=unchecked]:bg-input"
24
+ ].join(" ")
25
+
26
+ THUMB_CLASSES = [
27
+ "pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0",
28
+ "transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
29
+ ].join(" ")
30
+
31
+ # @param name [String, nil] Input name attribute
32
+ # @param id [String, nil] Input id attribute
33
+ # @param value [String] Value when checked
34
+ # @param checked [Boolean] Whether switch is on
35
+ # @param disabled [Boolean] Whether switch is disabled
36
+ # @param required [Boolean] Whether switch is required
37
+ def initialize(
38
+ name: nil,
39
+ id: nil,
40
+ value: "1",
41
+ checked: false,
42
+ disabled: false,
43
+ required: false,
44
+ **options
45
+ )
46
+ super(**options)
47
+ @name = name
48
+ @id = id || (name ? "switch-#{name}" : nil)
49
+ @value = value
50
+ @checked = checked
51
+ @disabled = disabled
52
+ @required = required
53
+ end
54
+
55
+ def call
56
+ if content.present?
57
+ # Render with integrated label
58
+ content_tag(:label, class: "flex items-center gap-3 cursor-pointer") do
59
+ safe_join([
60
+ switch_wrapper,
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
+ switch_wrapper
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def switch_wrapper
72
+ content_tag(:span, wrapper_attributes) do
73
+ safe_join([
74
+ hidden_unchecked_input,
75
+ hidden_input,
76
+ switch_button
77
+ ].compact)
78
+ end
79
+ end
80
+
81
+ def wrapper_attributes
82
+ {
83
+ class: "inline-flex items-center",
84
+ "data-controller": "shadcn--switch",
85
+ "data-shadcn--switch-checked-value": @checked.to_s
86
+ }
87
+ end
88
+
89
+ def hidden_unchecked_input
90
+ # Rails convention: hidden input with "0" for unchecked state
91
+ return unless @name
92
+
93
+ tag(:input,
94
+ type: "hidden",
95
+ name: @name,
96
+ value: "0",
97
+ autocomplete: "off"
98
+ )
99
+ end
100
+
101
+ def hidden_input
102
+ return unless @name
103
+
104
+ tag(:input,
105
+ type: "checkbox",
106
+ name: @name,
107
+ id: @id,
108
+ value: @value,
109
+ checked: @checked || nil,
110
+ disabled: @disabled || nil,
111
+ required: @required || nil,
112
+ class: "sr-only",
113
+ "data-shadcn--switch-target": "input",
114
+ tabindex: "-1"
115
+ )
116
+ end
117
+
118
+ def switch_button
119
+ content_tag(:button, switch_thumb, switch_button_attributes)
120
+ end
121
+
122
+ def switch_thumb
123
+ content_tag(:span, "", class: THUMB_CLASSES, "data-state": state, "data-shadcn--switch-target": "thumb")
124
+ end
125
+
126
+ def state
127
+ @checked ? "checked" : "unchecked"
128
+ end
129
+
130
+ def switch_button_attributes
131
+ attrs = {
132
+ type: "button",
133
+ role: "switch",
134
+ class: cn(BASE_CLASSES, class_name),
135
+ disabled: @disabled || nil,
136
+ "aria-checked": @checked.to_s,
137
+ "aria-required": @required ? "true" : nil,
138
+ "data-state": state,
139
+ "data-shadcn--switch-target": "button",
140
+ "data-action": "click->shadcn--switch#toggle keydown->shadcn--switch#handleKeydown",
141
+ tabindex: "0"
142
+ }
143
+ attrs.merge!(html_options.except(:class))
144
+ attrs.compact
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Table Body component
5
+ class TableBodyComponent < BaseComponent
6
+ BASE_CLASSES = "[&_tr:last-child]:border-0"
7
+
8
+ renders_many :rows, lambda { |**options, &block|
9
+ TableRowComponent.new(**options, &block)
10
+ }
11
+
12
+ def call
13
+ content_tag(:tbody, safe_join([rows, content].compact.flatten), class: merge_classes(BASE_CLASSES))
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Table Caption component
5
+ class TableCaptionComponent < BaseComponent
6
+ BASE_CLASSES = "mt-4 text-sm text-muted-foreground"
7
+
8
+ def call
9
+ content_tag(:caption, content, class: merge_classes(BASE_CLASSES), **html_options)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Table Cell component
5
+ class TableCellComponent < BaseComponent
6
+ BASE_CLASSES = "p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]"
7
+
8
+ def call
9
+ content_tag(:td, content, class: merge_classes(BASE_CLASSES), **html_options)
10
+ end
11
+ end
12
+ end