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,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Sidebar component for application layouts
5
+ # Matches shadcn/ui Sidebar component
6
+ # A composable, themeable sidebar with collapsible state
7
+ #
8
+ # @example Basic sidebar
9
+ # <%= render Shadcn::SidebarComponent.new do |sidebar| %>
10
+ # <% sidebar.with_header do %>
11
+ # <div class="flex items-center gap-2 px-4">
12
+ # <span class="font-semibold">App Name</span>
13
+ # </div>
14
+ # <% end %>
15
+ # <% sidebar.with_sidebar_content do |sc| %>
16
+ # <% sc.with_group do |group| %>
17
+ # <% group.with_label { "Platform" } %>
18
+ # <% group.with_group_content do |gc| %>
19
+ # <% gc.with_menu do |menu| %>
20
+ # <% menu.with_item do |item| %>
21
+ # <% item.with_button(href: "/dashboard") do %>
22
+ # Dashboard
23
+ # <% end %>
24
+ # <% end %>
25
+ # <% end %>
26
+ # <% end %>
27
+ # <% end %>
28
+ # <% end %>
29
+ # <% sidebar.with_footer do %>
30
+ # User info here
31
+ # <% end %>
32
+ # <% end %>
33
+ #
34
+ class SidebarComponent < BaseComponent
35
+ SIDEBAR_WIDTH = "16rem"
36
+ SIDEBAR_WIDTH_MOBILE = "18rem"
37
+ SIDEBAR_WIDTH_ICON = "3rem"
38
+
39
+ SIDES = {
40
+ left: "left",
41
+ right: "right"
42
+ }.freeze
43
+
44
+ VARIANTS = {
45
+ sidebar: "sidebar",
46
+ floating: "floating",
47
+ inset: "inset"
48
+ }.freeze
49
+
50
+ COLLAPSIBLE_MODES = {
51
+ offcanvas: "offcanvas",
52
+ icon: "icon",
53
+ none: "none"
54
+ }.freeze
55
+
56
+ # Slots
57
+ renders_one :header, lambda { |**options|
58
+ SidebarHeaderComponent.new(**options)
59
+ }
60
+ renders_one :sidebar_content, lambda { |**options|
61
+ SidebarContentComponent.new(**options)
62
+ }
63
+ renders_one :footer, lambda { |**options|
64
+ SidebarFooterComponent.new(**options)
65
+ }
66
+ renders_one :rail, lambda { |**options|
67
+ SidebarRailComponent.new(**options)
68
+ }
69
+
70
+ # @param side [Symbol] Side to show sidebar (:left, :right)
71
+ # @param variant [Symbol] Visual style (:sidebar, :floating, :inset)
72
+ # @param collapsible [Symbol] Collapse mode (:offcanvas, :icon, :none)
73
+ # @param default_open [Boolean] Whether sidebar starts open
74
+ def initialize(
75
+ side: :left,
76
+ variant: :sidebar,
77
+ collapsible: :offcanvas,
78
+ default_open: true,
79
+ **options
80
+ )
81
+ super(**options)
82
+ @side = side.to_sym
83
+ @variant = variant.to_sym
84
+ @collapsible = collapsible.to_sym
85
+ @default_open = default_open
86
+ end
87
+
88
+ def call
89
+ content_tag(:aside, sidebar_content, sidebar_attributes)
90
+ end
91
+
92
+ private
93
+
94
+ def sidebar_content
95
+ safe_join([
96
+ sidebar_inner
97
+ ].compact)
98
+ end
99
+
100
+ def sidebar_inner
101
+ content_tag(:div, inner_content, inner_attributes)
102
+ end
103
+
104
+ def inner_content
105
+ safe_join([header, sidebar_content, footer, rail].compact)
106
+ end
107
+
108
+ def inner_attributes
109
+ {
110
+ class: cn(
111
+ "flex h-full w-full flex-col bg-sidebar",
112
+ "group-data-[collapsible=icon]:overflow-hidden"
113
+ ),
114
+ "data-shadcn--sidebar-target": "inner"
115
+ }
116
+ end
117
+
118
+ def sidebar_attributes
119
+ attrs = {
120
+ class: cn(sidebar_classes, class_name),
121
+ "data-controller": "shadcn--sidebar",
122
+ "data-side": @side.to_s,
123
+ "data-variant": @variant.to_s,
124
+ "data-collapsible": @collapsible.to_s,
125
+ "data-state": @default_open ? "expanded" : "collapsed",
126
+ "data-shadcn--sidebar-open-value": @default_open.to_s,
127
+ "data-shadcn--sidebar-side-value": @side.to_s,
128
+ "data-shadcn--sidebar-variant-value": @variant.to_s,
129
+ "data-shadcn--sidebar-collapsible-value": @collapsible.to_s,
130
+ "data-action": "keydown.ctrl+b@window->shadcn--sidebar#toggle keydown.meta+b@window->shadcn--sidebar#toggle"
131
+ }
132
+ attrs.merge!(html_options)
133
+ attrs.merge!(build_data)
134
+ attrs.compact
135
+ end
136
+
137
+ def sidebar_classes
138
+ cn(
139
+ # Base styles
140
+ "group peer hidden md:block text-sidebar-foreground",
141
+ # Fixed positioning for sidebar variant
142
+ @variant == :sidebar && "fixed inset-y-0 z-10 h-svh",
143
+ # Side-specific positioning
144
+ @side == :left && "left-0",
145
+ @side == :right && "right-0",
146
+ # Width handling
147
+ "w-[--sidebar-width]",
148
+ # Transition
149
+ "transition-[left,right,width] duration-200 ease-linear",
150
+ # Collapsed state styles
151
+ "group-data-[collapsible=offcanvas]:w-0",
152
+ "group-data-[collapsible=icon]:w-[--sidebar-width-icon]",
153
+ # Variant-specific styles
154
+ variant_classes,
155
+ # Side-specific collapsed offset
156
+ collapsed_offset_classes
157
+ )
158
+ end
159
+
160
+ def variant_classes
161
+ case @variant
162
+ when :floating
163
+ "p-2"
164
+ when :inset
165
+ "border-r bg-transparent"
166
+ else
167
+ "border-r"
168
+ end
169
+ end
170
+
171
+ def collapsed_offset_classes
172
+ case @side
173
+ when :left
174
+ "group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
175
+ when :right
176
+ "group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]"
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarContent component - scrollable content area for sidebar
5
+ class SidebarContentComponent < BaseComponent
6
+ BASE_CLASSES = "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden"
7
+
8
+ renders_many :groups, lambda { |**options|
9
+ SidebarGroupComponent.new(**options)
10
+ }
11
+
12
+ def call
13
+ content_tag(:div, content_structure, content_attributes)
14
+ end
15
+
16
+ private
17
+
18
+ def content_structure
19
+ groups.any? ? safe_join(groups) : content
20
+ end
21
+
22
+ def content_attributes
23
+ attrs = {
24
+ class: cn(BASE_CLASSES, class_name),
25
+ "data-sidebar": "content"
26
+ }
27
+ attrs.merge!(html_options)
28
+ attrs.merge!(build_data)
29
+ attrs.compact
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarFooter component - sticky footer section for sidebar
5
+ class SidebarFooterComponent < BaseComponent
6
+ BASE_CLASSES = "flex flex-col gap-2 p-2"
7
+
8
+ def call
9
+ content_tag(:div, content, footer_attributes)
10
+ end
11
+
12
+ private
13
+
14
+ def footer_attributes
15
+ attrs = {
16
+ class: cn(BASE_CLASSES, class_name),
17
+ "data-sidebar": "footer"
18
+ }
19
+ attrs.merge!(html_options)
20
+ attrs.merge!(build_data)
21
+ attrs.compact
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarGroupAction component - action button for a sidebar group
5
+ class SidebarGroupActionComponent < BaseComponent
6
+ BASE_CLASSES = "absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0"
7
+ COLLAPSED_CLASSES = "group-data-[collapsible=icon]:hidden"
8
+
9
+ def call
10
+ content_tag(:button, content, action_attributes)
11
+ end
12
+
13
+ private
14
+
15
+ def action_attributes
16
+ attrs = {
17
+ type: "button",
18
+ class: cn(BASE_CLASSES, COLLAPSED_CLASSES, class_name),
19
+ "data-sidebar": "group-action"
20
+ }
21
+ attrs.merge!(html_options)
22
+ attrs.merge!(build_data)
23
+ attrs.compact
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarGroup component - groups related sidebar items
5
+ class SidebarGroupComponent < BaseComponent
6
+ BASE_CLASSES = "relative flex w-full min-w-0 flex-col p-2"
7
+
8
+ renders_one :label, lambda { |**options|
9
+ SidebarGroupLabelComponent.new(**options)
10
+ }
11
+ renders_one :action, lambda { |**options|
12
+ SidebarGroupActionComponent.new(**options)
13
+ }
14
+ renders_one :group_content, lambda { |**options|
15
+ SidebarGroupContentComponent.new(**options)
16
+ }
17
+
18
+ def call
19
+ content_tag(:div, group_content_structure, group_attributes)
20
+ end
21
+
22
+ private
23
+
24
+ def group_content_structure
25
+ safe_join([label, action, group_content, content].compact)
26
+ end
27
+
28
+ def group_attributes
29
+ attrs = {
30
+ class: cn(BASE_CLASSES, class_name),
31
+ "data-sidebar": "group"
32
+ }
33
+ attrs.merge!(html_options)
34
+ attrs.merge!(build_data)
35
+ attrs.compact
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarGroupContent component - content container for a sidebar group
5
+ class SidebarGroupContentComponent < BaseComponent
6
+ BASE_CLASSES = "w-full text-sm"
7
+
8
+ renders_many :menus, lambda { |**options|
9
+ SidebarMenuComponent.new(**options)
10
+ }
11
+
12
+ def call
13
+ content_tag(:div, content_structure, content_attributes)
14
+ end
15
+
16
+ private
17
+
18
+ def content_structure
19
+ menus.any? ? safe_join(menus) : content
20
+ end
21
+
22
+ def content_attributes
23
+ attrs = {
24
+ class: cn(BASE_CLASSES, class_name),
25
+ "data-sidebar": "group-content"
26
+ }
27
+ attrs.merge!(html_options)
28
+ attrs.merge!(build_data)
29
+ attrs.compact
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarGroupLabel component - label for a sidebar group
5
+ class SidebarGroupLabelComponent < BaseComponent
6
+ BASE_CLASSES = "duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0"
7
+ COLLAPSED_CLASSES = "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0"
8
+
9
+ def call
10
+ content_tag(:div, content, label_attributes)
11
+ end
12
+
13
+ private
14
+
15
+ def label_attributes
16
+ attrs = {
17
+ class: cn(BASE_CLASSES, COLLAPSED_CLASSES, class_name),
18
+ "data-sidebar": "group-label"
19
+ }
20
+ attrs.merge!(html_options)
21
+ attrs.merge!(build_data)
22
+ attrs.compact
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarHeader component - sticky header section for sidebar
5
+ class SidebarHeaderComponent < BaseComponent
6
+ BASE_CLASSES = "flex flex-col gap-2 p-2"
7
+
8
+ def call
9
+ content_tag(:div, content, header_attributes)
10
+ end
11
+
12
+ private
13
+
14
+ def header_attributes
15
+ attrs = {
16
+ class: cn(BASE_CLASSES, class_name),
17
+ "data-sidebar": "header"
18
+ }
19
+ attrs.merge!(html_options)
20
+ attrs.merge!(build_data)
21
+ attrs.compact
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarInset component - main content area next to sidebar
5
+ class SidebarInsetComponent < BaseComponent
6
+ BASE_CLASSES = "relative flex min-h-svh flex-1 flex-col bg-background"
7
+ PEER_CLASSES = "peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow"
8
+
9
+ def call
10
+ content_tag(:main, content, inset_attributes)
11
+ end
12
+
13
+ private
14
+
15
+ def inset_attributes
16
+ attrs = {
17
+ class: cn(BASE_CLASSES, PEER_CLASSES, class_name),
18
+ "data-sidebar": "inset"
19
+ }
20
+ attrs.merge!(html_options)
21
+ attrs.merge!(build_data)
22
+ attrs.compact
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarMenuAction component - action button within menu item
5
+ class SidebarMenuActionComponent < BaseComponent
6
+ BASE_CLASSES = "absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0"
7
+ COLLAPSED_CLASSES = "group-data-[collapsible=icon]:hidden"
8
+ SHOW_ON_HOVER_CLASSES = "after:absolute after:-inset-2 after:md:hidden group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0"
9
+
10
+ def initialize(show_on_hover: false, **options)
11
+ super(**options)
12
+ @show_on_hover = show_on_hover
13
+ end
14
+
15
+ def call
16
+ content_tag(:button, content, action_attributes)
17
+ end
18
+
19
+ private
20
+
21
+ def action_attributes
22
+ attrs = {
23
+ type: "button",
24
+ class: cn(
25
+ BASE_CLASSES,
26
+ COLLAPSED_CLASSES,
27
+ @show_on_hover ? SHOW_ON_HOVER_CLASSES : nil,
28
+ class_name
29
+ ),
30
+ "data-sidebar": "menu-action"
31
+ }
32
+ attrs.merge!(html_options)
33
+ attrs.merge!(build_data)
34
+ attrs.compact
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarMenuBadge component - badge within menu item
5
+ class SidebarMenuBadgeComponent < BaseComponent
6
+ BASE_CLASSES = "absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none"
7
+ COLLAPSED_CLASSES = "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden"
8
+
9
+ def call
10
+ content_tag(:span, content, badge_attributes)
11
+ end
12
+
13
+ private
14
+
15
+ def badge_attributes
16
+ attrs = {
17
+ class: cn(BASE_CLASSES, COLLAPSED_CLASSES, class_name),
18
+ "data-sidebar": "menu-badge"
19
+ }
20
+ attrs.merge!(html_options)
21
+ attrs.merge!(build_data)
22
+ attrs.compact
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarMenuButton component - button/link within menu item
5
+ class SidebarMenuButtonComponent < BaseComponent
6
+ VARIANTS = {
7
+ default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
8
+ outline: "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]"
9
+ }.freeze
10
+
11
+ SIZES = {
12
+ default: "h-8 text-sm",
13
+ sm: "h-7 text-xs",
14
+ lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0"
15
+ }.freeze
16
+
17
+ BASE_CLASSES = "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left outline-none ring-sidebar-ring transition-[width,height,padding] 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 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0"
18
+
19
+ def initialize(variant: :default, size: :default, is_active: false, as_child: false, tooltip: nil, href: nil, **options)
20
+ super(**options)
21
+ @variant = variant.to_sym
22
+ @size = size.to_sym
23
+ @is_active = is_active
24
+ @as_child = as_child
25
+ @tooltip = tooltip
26
+ @href = href
27
+ end
28
+
29
+ def call
30
+ if @href
31
+ content_tag(:a, content, button_attributes.merge(href: @href))
32
+ else
33
+ content_tag(:button, content, button_attributes.merge(type: "button"))
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def button_attributes
40
+ attrs = {
41
+ class: cn(BASE_CLASSES, VARIANTS[@variant], SIZES[@size], class_name),
42
+ "data-sidebar": "menu-button",
43
+ "data-size": @size,
44
+ "data-active": @is_active
45
+ }
46
+ attrs[:"data-tooltip"] = @tooltip if @tooltip
47
+ attrs.merge!(html_options)
48
+ attrs.merge!(build_data)
49
+ attrs.compact
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarMenu component - menu container for sidebar items
5
+ class SidebarMenuComponent < BaseComponent
6
+ BASE_CLASSES = "flex w-full min-w-0 flex-col gap-1"
7
+
8
+ renders_many :items, lambda { |**options|
9
+ SidebarMenuItemComponent.new(**options)
10
+ }
11
+
12
+ def call
13
+ content_tag(:ul, menu_content, menu_attributes)
14
+ end
15
+
16
+ private
17
+
18
+ def menu_content
19
+ items.any? ? safe_join(items) : content
20
+ end
21
+
22
+ def menu_attributes
23
+ attrs = {
24
+ class: cn(BASE_CLASSES, class_name),
25
+ "data-sidebar": "menu"
26
+ }
27
+ attrs.merge!(html_options)
28
+ attrs.merge!(build_data)
29
+ attrs.compact
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarMenuItem component - individual menu item
5
+ class SidebarMenuItemComponent < BaseComponent
6
+ BASE_CLASSES = "group/menu-item relative"
7
+
8
+ renders_one :button, lambda { |**options|
9
+ SidebarMenuButtonComponent.new(**options)
10
+ }
11
+ renders_one :action, lambda { |**options|
12
+ SidebarMenuActionComponent.new(**options)
13
+ }
14
+ renders_one :badge, lambda { |**options|
15
+ SidebarMenuBadgeComponent.new(**options)
16
+ }
17
+ renders_one :sub, lambda { |**options|
18
+ SidebarMenuSubComponent.new(**options)
19
+ }
20
+
21
+ def call
22
+ content_tag(:li, item_content, item_attributes)
23
+ end
24
+
25
+ private
26
+
27
+ def item_content
28
+ safe_join([button, action, badge, sub, content].compact)
29
+ end
30
+
31
+ def item_attributes
32
+ attrs = {
33
+ class: cn(BASE_CLASSES, class_name),
34
+ "data-sidebar": "menu-item"
35
+ }
36
+ attrs.merge!(html_options)
37
+ attrs.merge!(build_data)
38
+ attrs.compact
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # SidebarMenuSkeleton component - loading skeleton for menu item
5
+ class SidebarMenuSkeletonComponent < BaseComponent
6
+ BASE_CLASSES = "rounded-md h-8 flex gap-2 px-2 items-center"
7
+
8
+ def initialize(show_icon: false, **options)
9
+ super(**options)
10
+ @show_icon = show_icon
11
+ end
12
+
13
+ def call
14
+ content_tag(:div, skeleton_content, skeleton_attributes)
15
+ end
16
+
17
+ private
18
+
19
+ def skeleton_content
20
+ safe_join([
21
+ @show_icon ? icon_skeleton : nil,
22
+ text_skeleton
23
+ ].compact)
24
+ end
25
+
26
+ def icon_skeleton
27
+ content_tag(:span, "", class: "size-4 rounded-md bg-sidebar-accent animate-pulse")
28
+ end
29
+
30
+ def text_skeleton
31
+ # Random width between 60-80%
32
+ width = rand(60..80)
33
+ content_tag(:span, "", class: "h-4 flex-1 bg-sidebar-accent animate-pulse rounded-md", style: "max-width: #{width}%")
34
+ end
35
+
36
+ def skeleton_attributes
37
+ attrs = {
38
+ class: cn(BASE_CLASSES, class_name),
39
+ "data-sidebar": "menu-skeleton"
40
+ }
41
+ attrs.merge!(html_options)
42
+ attrs.merge!(build_data)
43
+ attrs.compact
44
+ end
45
+ end
46
+ end