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,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Item component - a flexible flex container for titles, descriptions, and actions
5
+ # Matches shadcn/ui Item component
6
+ #
7
+ # @example Basic item
8
+ # <%= render Shadcn::ItemComponent.new do |item| %>
9
+ # <% item.with_media(variant: :icon) do %>
10
+ # <svg>...</svg>
11
+ # <% end %>
12
+ # <% item.with_content do |content| %>
13
+ # <% content.with_title { "Item Title" } %>
14
+ # <% content.with_description { "Item description" } %>
15
+ # <% end %>
16
+ # <% item.with_actions do %>
17
+ # <%= render Shadcn::ButtonComponent.new(size: :sm) { "Action" } %>
18
+ # <% end %>
19
+ # <% end %>
20
+ #
21
+ # @example With variants
22
+ # <%= render Shadcn::ItemComponent.new(variant: :outline) do |item| %>
23
+ # ...
24
+ # <% end %>
25
+ #
26
+ class ItemComponent < BaseComponent
27
+ VARIANTS = {
28
+ default: "bg-transparent",
29
+ outline: "border border-border rounded-lg",
30
+ muted: "bg-muted/50 rounded-lg"
31
+ }.freeze
32
+
33
+ SIZES = {
34
+ default: "p-4 gap-4",
35
+ sm: "p-3 gap-3"
36
+ }.freeze
37
+
38
+ BASE_CLASSES = "flex items-start"
39
+
40
+ # Media slot for icons, images, or avatars
41
+ renders_one :media, lambda { |variant: :default, **options|
42
+ ItemMediaComponent.new(variant: variant, **options)
43
+ }
44
+
45
+ # Content slot for title and description
46
+ renders_one :content_slot, lambda { |**options|
47
+ ItemContentComponent.new(**options)
48
+ }
49
+
50
+ # Alias for more intuitive API
51
+ alias_method :with_content, :with_content_slot
52
+
53
+ # Actions slot for buttons
54
+ renders_one :actions, lambda { |**options|
55
+ ItemActionsComponent.new(**options)
56
+ }
57
+
58
+ # Header slot
59
+ renders_one :header, lambda { |**options|
60
+ ItemHeaderComponent.new(**options)
61
+ }
62
+
63
+ # Footer slot
64
+ renders_one :footer, lambda { |**options|
65
+ ItemFooterComponent.new(**options)
66
+ }
67
+
68
+ # @param variant [Symbol] :default, :outline, or :muted
69
+ # @param size [Symbol] :default or :sm
70
+ # @param tag [Symbol] HTML tag to use (:div, :li, :a, etc.)
71
+ # @param href [String] URL for link items (uses :a tag automatically)
72
+ def initialize(variant: :default, size: :default, tag: :div, href: nil, **options)
73
+ super(**options)
74
+ @variant = variant.to_sym
75
+ @size = size.to_sym
76
+ @tag = href ? :a : tag
77
+ @href = href
78
+ end
79
+
80
+ def call
81
+ content_tag(@tag, item_content, **item_attributes)
82
+ end
83
+
84
+ private
85
+
86
+ def item_content
87
+ safe_join([header, media, content_slot, content, actions, footer].compact)
88
+ end
89
+
90
+ def item_attributes
91
+ attrs = {
92
+ class: cn(BASE_CLASSES, VARIANTS[@variant], SIZES[@size], class_name)
93
+ }
94
+ attrs[:href] = @href if @href
95
+ attrs.merge(html_options).merge(build_data)
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Item Content component - wrapper for title and description
5
+ class ItemContentComponent < BaseComponent
6
+ BASE_CLASSES = "flex-1 min-w-0 space-y-1"
7
+
8
+ # Title slot
9
+ renders_one :title, lambda { |**options|
10
+ ItemTitleComponent.new(**options)
11
+ }
12
+
13
+ # Description slot
14
+ renders_one :description, lambda { |**options|
15
+ ItemDescriptionComponent.new(**options)
16
+ }
17
+
18
+ def call
19
+ content_tag(:div, class: merge_classes(BASE_CLASSES), **html_options.merge(build_data)) do
20
+ safe_join([title, description, content].compact)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Item Description component
5
+ class ItemDescriptionComponent < BaseComponent
6
+ BASE_CLASSES = "text-sm text-muted-foreground"
7
+
8
+ def call
9
+ content_tag(:p, content, class: merge_classes(BASE_CLASSES), **html_options.merge(build_data))
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Item Footer component - display footer content below main content
5
+ class ItemFooterComponent < BaseComponent
6
+ BASE_CLASSES = "mt-2 w-full"
7
+
8
+ def call
9
+ content_tag(:div, content, class: merge_classes(BASE_CLASSES), **html_options.merge(build_data))
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Item Group component - container for grouping related items
5
+ class ItemGroupComponent < BaseComponent
6
+ BASE_CLASSES = "flex flex-col"
7
+
8
+ # Items in the group
9
+ renders_many :items, lambda { |variant: :default, size: :default, **options|
10
+ ItemComponent.new(variant: variant, size: size, **options)
11
+ }
12
+
13
+ # Separators between items
14
+ renders_many :separators, lambda { |**options|
15
+ ItemSeparatorComponent.new(**options)
16
+ }
17
+
18
+ def call
19
+ content_tag(:div, class: merge_classes(BASE_CLASSES), **html_options.merge(build_data)) do
20
+ safe_join([items, separators, content].flatten.compact)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Item Header component - display header content above main content
5
+ class ItemHeaderComponent < BaseComponent
6
+ BASE_CLASSES = "mb-2"
7
+
8
+ def call
9
+ content_tag(:div, content, class: merge_classes(BASE_CLASSES), **html_options.merge(build_data))
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Item Media component - displays icons, images, or avatars
5
+ class ItemMediaComponent < BaseComponent
6
+ VARIANTS = {
7
+ default: "shrink-0",
8
+ icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted [&>svg]:size-5 [&>svg]:text-muted-foreground",
9
+ image: "shrink-0 overflow-hidden rounded-lg"
10
+ }.freeze
11
+
12
+ # @param variant [Symbol] :default, :icon, or :image
13
+ def initialize(variant: :default, **options)
14
+ super(**options)
15
+ @variant = variant.to_sym
16
+ end
17
+
18
+ def call
19
+ content_tag(:div, content, class: merge_classes(VARIANTS[@variant]), **html_options.merge(build_data))
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Item Separator component - visual divider between items
5
+ class ItemSeparatorComponent < BaseComponent
6
+ BASE_CLASSES = "shrink-0 bg-border h-[1px] w-full"
7
+
8
+ def call
9
+ content_tag(:div, "", class: merge_classes(BASE_CLASSES), role: "separator", **html_options.merge(build_data))
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Item Title component
5
+ class ItemTitleComponent < BaseComponent
6
+ BASE_CLASSES = "text-sm font-medium leading-none"
7
+
8
+ def call
9
+ content_tag(:div, content, class: merge_classes(BASE_CLASSES), **html_options.merge(build_data))
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Kbd component for displaying keyboard shortcuts
5
+ # Matches shadcn/ui Kbd component
6
+ #
7
+ # @example Basic keyboard shortcut
8
+ # <%= render Shadcn::KbdComponent.new { "⌘" } %>
9
+ #
10
+ # @example Multiple keys
11
+ # <%= render Shadcn::KbdComponent.new { "⌘K" } %>
12
+ #
13
+ # @example With key combination
14
+ # <span class="flex items-center gap-1">
15
+ # <%= render Shadcn::KbdComponent.new { "Ctrl" } %>
16
+ # <span>+</span>
17
+ # <%= render Shadcn::KbdComponent.new { "C" } %>
18
+ # </span>
19
+ #
20
+ class KbdComponent < BaseComponent
21
+ BASE_CLASSES = "pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100"
22
+
23
+ def call
24
+ tag.kbd(content, **kbd_attributes)
25
+ end
26
+
27
+ private
28
+
29
+ def kbd_attributes
30
+ attrs = { class: merge_classes(BASE_CLASSES) }
31
+ attrs.merge!(html_options)
32
+ attrs.merge!(build_data)
33
+ attrs.compact
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Label component for form fields
5
+ # Matches shadcn/ui Label component
6
+ #
7
+ # @example Basic label
8
+ # <%= render Shadcn::LabelComponent.new(for: "email") { "Email Address" } %>
9
+ #
10
+ # @example Required field label
11
+ # <%= render Shadcn::LabelComponent.new(for: "name", required: true) { "Name" } %>
12
+ #
13
+ # @example With custom styling
14
+ # <%= render Shadcn::LabelComponent.new(for: "bio", class_name: "text-lg") { "Biography" } %>
15
+ #
16
+ class LabelComponent < BaseComponent
17
+ BASE_CLASSES = "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
18
+
19
+ # @param for [String, nil] The ID of the input this label is for
20
+ # @param required [Boolean] Whether to show a required indicator
21
+ def initialize(for: nil, required: false, **options)
22
+ super(**options)
23
+ @for = binding.local_variable_get(:for)
24
+ @required = required
25
+ end
26
+
27
+ def call
28
+ label_text = content
29
+ label_text = safe_join([label_text, required_indicator]) if @required
30
+ tag.label(label_text, **label_attributes)
31
+ end
32
+
33
+ private
34
+
35
+ def required_indicator
36
+ content_tag(:span, " *", class: "text-destructive", "aria-hidden": "true")
37
+ end
38
+
39
+ def label_attributes
40
+ attrs = {
41
+ for: @for,
42
+ class: merge_classes(BASE_CLASSES)
43
+ }
44
+ attrs.merge!(html_options)
45
+ attrs.merge!(build_data)
46
+ attrs.compact
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Menubar Checkbox Item component
5
+ # A menu item that can be checked/unchecked
6
+ class MenubarCheckboxItemComponent < BaseComponent
7
+ BASE_CLASSES = "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
8
+
9
+ renders_one :shortcut, lambda { |**options|
10
+ MenubarShortcutComponent.new(**options)
11
+ }
12
+
13
+ # @param checked [Boolean] Whether item is checked
14
+ # @param disabled [Boolean] Whether item is disabled
15
+ def initialize(checked: false, disabled: false, **options, &block)
16
+ super(**options, &block)
17
+ @checked = checked
18
+ @disabled = disabled
19
+ end
20
+
21
+ def call
22
+ content_tag(:div, item_content, item_attributes)
23
+ end
24
+
25
+ private
26
+
27
+ def item_content
28
+ safe_join([
29
+ check_indicator,
30
+ content,
31
+ shortcut
32
+ ].compact)
33
+ end
34
+
35
+ def check_indicator
36
+ content_tag(:span, check_icon, class: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center")
37
+ end
38
+
39
+ def check_icon
40
+ return "" unless @checked
41
+
42
+ content_tag(:svg, check_svg_path, {
43
+ xmlns: "http://www.w3.org/2000/svg",
44
+ width: "16",
45
+ height: "16",
46
+ viewBox: "0 0 24 24",
47
+ fill: "none",
48
+ stroke: "currentColor",
49
+ "stroke-width": "2",
50
+ "stroke-linecap": "round",
51
+ "stroke-linejoin": "round",
52
+ class: "h-4 w-4"
53
+ })
54
+ end
55
+
56
+ def check_svg_path
57
+ content_tag(:polyline, "", points: "20 6 9 17 4 12")
58
+ end
59
+
60
+ def item_attributes
61
+ attrs = {
62
+ class: cn(BASE_CLASSES, class_name),
63
+ role: "menuitemcheckbox",
64
+ "aria-checked": @checked.to_s,
65
+ tabindex: @disabled ? nil : "-1",
66
+ "data-disabled": @disabled ? "" : nil,
67
+ "data-state": @checked ? "checked" : "unchecked",
68
+ "data-shadcn--menubar-target": "item",
69
+ "data-action": "click->shadcn--menubar#toggleCheckbox"
70
+ }
71
+ attrs.merge!(html_options)
72
+ attrs.merge!(build_data)
73
+ attrs.compact
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Menubar component
5
+ # Matches shadcn/ui Menubar component
6
+ # A visually persistent menu common in desktop applications
7
+ #
8
+ # @example Basic menubar
9
+ # <%= render Shadcn::MenubarComponent.new do |menubar| %>
10
+ # <% menubar.with_menu do |menu| %>
11
+ # <% menu.with_trigger { "File" } %>
12
+ # <% menu.with_content do |content| %>
13
+ # <% content.with_item(href: "#") { "New Tab" } %>
14
+ # <% content.with_item(href: "#") { "New Window" } %>
15
+ # <% content.with_separator %>
16
+ # <% content.with_item(href: "#") { "Exit" } %>
17
+ # <% end %>
18
+ # <% end %>
19
+ # <% menubar.with_menu do |menu| %>
20
+ # <% menu.with_trigger { "Edit" } %>
21
+ # <% menu.with_content do |content| %>
22
+ # <% content.with_item(href: "#") { "Undo" } %>
23
+ # <% content.with_item(href: "#") { "Redo" } %>
24
+ # <% end %>
25
+ # <% end %>
26
+ # <% end %>
27
+ #
28
+ class MenubarComponent < BaseComponent
29
+ BASE_CLASSES = "flex h-9 items-center space-x-1 rounded-md border bg-background p-1 shadow-sm"
30
+
31
+ renders_many :menus, lambda { |**options|
32
+ MenubarMenuComponent.new(**options)
33
+ }
34
+
35
+ def call
36
+ content_tag(:div, menubar_content, menubar_attributes)
37
+ end
38
+
39
+ private
40
+
41
+ def menubar_content
42
+ safe_join(menus)
43
+ end
44
+
45
+ def menubar_attributes
46
+ attrs = {
47
+ class: cn(BASE_CLASSES, class_name),
48
+ role: "menubar",
49
+ "data-controller": "shadcn--menubar"
50
+ }
51
+ attrs.merge!(html_options)
52
+ attrs.merge!(build_data)
53
+ attrs.compact
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Menubar Content component
5
+ # Container for menu items within a dropdown
6
+ class MenubarContentComponent < BaseComponent
7
+ BASE_CLASSES = "z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
8
+
9
+ renders_many :items, lambda { |**options, &block|
10
+ MenubarItemComponent.new(**options, &block)
11
+ }
12
+ renders_many :labels, lambda { |**options, &block|
13
+ MenubarLabelComponent.new(**options, &block)
14
+ }
15
+ renders_many :separators, lambda { |**options|
16
+ MenubarSeparatorComponent.new(**options)
17
+ }
18
+ renders_many :checkbox_items, lambda { |**options, &block|
19
+ MenubarCheckboxItemComponent.new(**options, &block)
20
+ }
21
+ renders_many :radio_groups, lambda { |**options, &block|
22
+ MenubarRadioGroupComponent.new(**options, &block)
23
+ }
24
+ renders_many :sub_menus, lambda { |**options, &block|
25
+ MenubarSubComponent.new(**options, &block)
26
+ }
27
+
28
+ # @param align [Symbol] Content alignment (:start, :center, :end)
29
+ # @param side_offset [Integer] Offset from trigger
30
+ def initialize(align: :start, side_offset: 4, **options)
31
+ super(**options)
32
+ @align = align
33
+ @side_offset = side_offset
34
+ end
35
+
36
+ def call
37
+ content_tag(:div, menu_content, content_attributes)
38
+ end
39
+
40
+ private
41
+
42
+ def menu_content
43
+ if items.any? || labels.any? || separators.any? || checkbox_items.any? || radio_groups.any? || sub_menus.any?
44
+ safe_join([labels, items, separators, checkbox_items, radio_groups, sub_menus, content].flatten.compact)
45
+ else
46
+ content
47
+ end
48
+ end
49
+
50
+ def content_attributes
51
+ attrs = {
52
+ class: cn(BASE_CLASSES, class_name),
53
+ role: "menu",
54
+ "aria-orientation": "vertical",
55
+ "data-state": "closed",
56
+ "data-shadcn--menubar-target": "content",
57
+ hidden: true
58
+ }
59
+ attrs.merge!(html_options)
60
+ attrs.merge!(build_data)
61
+ attrs.compact
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Menubar Item component
5
+ # Individual menu item within menubar content
6
+ class MenubarItemComponent < BaseComponent
7
+ BASE_CLASSES = "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
8
+
9
+ VARIANTS = {
10
+ default: "",
11
+ destructive: "text-destructive focus:bg-destructive focus:text-destructive-foreground"
12
+ }.freeze
13
+
14
+ renders_one :shortcut, lambda { |**options|
15
+ MenubarShortcutComponent.new(**options)
16
+ }
17
+
18
+ # @param href [String, nil] Link URL
19
+ # @param variant [Symbol] Item variant (:default, :destructive)
20
+ # @param disabled [Boolean] Whether item is disabled
21
+ # @param inset [Boolean] Whether to add left padding for icons
22
+ def initialize(href: nil, variant: :default, disabled: false, inset: false, **options, &block)
23
+ super(**options, &block)
24
+ @href = href
25
+ @variant = variant.to_sym
26
+ @disabled = disabled
27
+ @inset = inset
28
+ end
29
+
30
+ def call
31
+ tag_name = @href ? :a : :div
32
+ content_tag(tag_name, item_content, item_attributes)
33
+ end
34
+
35
+ private
36
+
37
+ def item_content
38
+ safe_join([content, shortcut].compact)
39
+ end
40
+
41
+ def item_classes
42
+ cn(
43
+ BASE_CLASSES,
44
+ VARIANTS[@variant],
45
+ @inset ? "pl-8" : "",
46
+ class_name
47
+ )
48
+ end
49
+
50
+ def item_attributes
51
+ attrs = {
52
+ class: item_classes,
53
+ role: "menuitem",
54
+ tabindex: @disabled ? nil : "-1",
55
+ href: @href,
56
+ "data-disabled": @disabled ? "" : nil,
57
+ "data-shadcn--menubar-target": "item",
58
+ "data-action": "click->shadcn--menubar#selectItem"
59
+ }
60
+ attrs.merge!(html_options)
61
+ attrs.merge!(build_data)
62
+ attrs.compact
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Menubar Label component
5
+ # Label for grouping menu items
6
+ class MenubarLabelComponent < BaseComponent
7
+ BASE_CLASSES = "px-2 py-1.5 text-sm font-semibold"
8
+
9
+ # @param inset [Boolean] Whether to add left padding
10
+ def initialize(inset: false, **options, &block)
11
+ super(**options, &block)
12
+ @inset = inset
13
+ end
14
+
15
+ def call
16
+ content_tag(:div, content, label_attributes)
17
+ end
18
+
19
+ private
20
+
21
+ def label_attributes
22
+ {
23
+ class: cn(BASE_CLASSES, @inset ? "pl-8" : "", class_name)
24
+ }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Menubar Menu component
5
+ # Individual menu section within a menubar
6
+ class MenubarMenuComponent < BaseComponent
7
+ renders_one :trigger, lambda { |**options|
8
+ MenubarTriggerComponent.new(**options)
9
+ }
10
+ renders_one :menu, lambda { |**options|
11
+ MenubarContentComponent.new(**options)
12
+ }
13
+
14
+ def call
15
+ content_tag(:div, menu_body, menu_attributes)
16
+ end
17
+
18
+ private
19
+
20
+ def menu_body
21
+ safe_join([trigger, menu].compact)
22
+ end
23
+
24
+ def menu_attributes
25
+ attrs = {
26
+ class: cn("relative", class_name),
27
+ "data-shadcn--menubar-target": "menu"
28
+ }
29
+ attrs.merge!(html_options)
30
+ attrs.merge!(build_data)
31
+ attrs.compact
32
+ end
33
+ end
34
+ end