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,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Select component for dropdown selection
5
+ # Matches shadcn/ui Select component
6
+ # Uses Stimulus for interactivity
7
+ #
8
+ # @example Basic select
9
+ # <%= render Shadcn::SelectComponent.new(name: "fruit", placeholder: "Select a fruit") do |select| %>
10
+ # <% select.with_item(value: "apple") { "Apple" } %>
11
+ # <% select.with_item(value: "banana") { "Banana" } %>
12
+ # <% select.with_item(value: "orange") { "Orange" } %>
13
+ # <% end %>
14
+ #
15
+ # @example With groups
16
+ # <%= render Shadcn::SelectComponent.new(name: "timezone") do |select| %>
17
+ # <% select.with_group(label: "North America") do |group| %>
18
+ # <% group.with_item(value: "est") { "Eastern" } %>
19
+ # <% group.with_item(value: "pst") { "Pacific" } %>
20
+ # <% end %>
21
+ # <% end %>
22
+ #
23
+ class SelectComponent < BaseComponent
24
+ TRIGGER_CLASSES = "flex h-10 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1"
25
+ CONTENT_CLASSES = "absolute left-0 top-full z-50 mt-1 max-h-96 min-w-[var(--radix-select-trigger-width)] w-max overflow-hidden rounded-md border bg-popover 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"
26
+ VIEWPORT_CLASSES = "p-1"
27
+
28
+ renders_many :items, lambda { |value:, **options, &block|
29
+ SelectItemComponent.new(value: value, **options, &block)
30
+ }
31
+ renders_many :groups, lambda { |label: nil, **options, &block|
32
+ SelectGroupComponent.new(label: label, **options, &block)
33
+ }
34
+
35
+ # @param name [String, nil] Form field name
36
+ # @param id [String, nil] Element ID
37
+ # @param value [String, nil] Currently selected value
38
+ # @param placeholder [String] Placeholder text
39
+ # @param disabled [Boolean] Whether select is disabled
40
+ # @param required [Boolean] Whether select is required
41
+ def initialize(
42
+ name: nil,
43
+ id: nil,
44
+ value: nil,
45
+ placeholder: "Select...",
46
+ disabled: false,
47
+ required: false,
48
+ **options
49
+ )
50
+ super(**options)
51
+ @name = name
52
+ @id = id
53
+ @value = value
54
+ @placeholder = placeholder
55
+ @disabled = disabled
56
+ @required = required
57
+ end
58
+
59
+ def call
60
+ content_tag(:div, select_structure, select_attributes)
61
+ end
62
+
63
+ private
64
+
65
+ def select_structure
66
+ safe_join([
67
+ hidden_input,
68
+ trigger,
69
+ content_wrapper
70
+ ])
71
+ end
72
+
73
+ def hidden_input
74
+ tag(:input,
75
+ type: "hidden",
76
+ name: @name,
77
+ id: @id,
78
+ value: @value,
79
+ required: @required || nil,
80
+ "data-shadcn--select-target": "input"
81
+ )
82
+ end
83
+
84
+ def trigger
85
+ content_tag(:button, trigger_content, trigger_attributes)
86
+ end
87
+
88
+ def trigger_content
89
+ safe_join([
90
+ content_tag(:span, @value.presence || @placeholder, "data-shadcn--select-target": "display"),
91
+ chevron_icon
92
+ ])
93
+ end
94
+
95
+ def chevron_icon
96
+ content_tag(:svg,
97
+ content_tag(:path, nil, d: "m6 9 6 6 6-6", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round"),
98
+ xmlns: "http://www.w3.org/2000/svg",
99
+ width: "16",
100
+ height: "16",
101
+ viewBox: "0 0 24 24",
102
+ fill: "none",
103
+ class: "h-4 w-4 opacity-50"
104
+ )
105
+ end
106
+
107
+ def trigger_attributes
108
+ {
109
+ type: "button",
110
+ class: cn(TRIGGER_CLASSES, class_name),
111
+ role: "combobox",
112
+ disabled: @disabled || nil,
113
+ "aria-expanded": "false",
114
+ "aria-haspopup": "listbox",
115
+ "data-shadcn--select-target": "trigger",
116
+ "data-action": "click->shadcn--select#toggle keydown->shadcn--select#handleKeydown",
117
+ "data-placeholder": @placeholder
118
+ }
119
+ end
120
+
121
+ def content_wrapper
122
+ content_tag(:div, viewport, {
123
+ class: CONTENT_CLASSES,
124
+ role: "listbox",
125
+ "data-shadcn--select-target": "content",
126
+ "data-state": "closed",
127
+ hidden: true
128
+ })
129
+ end
130
+
131
+ def viewport
132
+ content_tag(:div, items_content, class: VIEWPORT_CLASSES)
133
+ end
134
+
135
+ def items_content
136
+ safe_join([items, groups, content].compact.flatten)
137
+ end
138
+
139
+ def select_attributes
140
+ attrs = {
141
+ class: cn("relative inline-block", class_name),
142
+ "data-controller": "shadcn--select",
143
+ "data-shadcn--select-value-value": @value,
144
+ "data-action": "keydown.escape->shadcn--select#close"
145
+ }
146
+ attrs.merge!(html_options.except(:class))
147
+ attrs.merge!(build_data)
148
+ attrs.compact
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Select Group component
5
+ class SelectGroupComponent < BaseComponent
6
+ LABEL_CLASSES = "px-2 py-1.5 text-sm font-semibold"
7
+
8
+ renders_many :items, lambda { |value:, **options, &block|
9
+ SelectItemComponent.new(value: value, **options, &block)
10
+ }
11
+
12
+ # @param label [String, nil] Group label
13
+ def initialize(label: nil, **options, &block)
14
+ super(**options, &block)
15
+ @label = label
16
+ end
17
+
18
+ def call
19
+ content_tag(:div, group_content, role: "group")
20
+ end
21
+
22
+ private
23
+
24
+ def group_content
25
+ safe_join([
26
+ (@label ? content_tag(:div, @label, class: LABEL_CLASSES) : nil),
27
+ items,
28
+ content
29
+ ].compact.flatten)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Select Item component
5
+ class SelectItemComponent < BaseComponent
6
+ BASE_CLASSES = "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
7
+
8
+ # @param value [String] The value for this option
9
+ # @param disabled [Boolean] Whether this option is disabled
10
+ def initialize(value:, disabled: false, **options, &block)
11
+ super(**options, &block)
12
+ @value = value
13
+ @disabled = disabled
14
+ end
15
+
16
+ def call
17
+ content_tag(:div, item_content, item_attributes)
18
+ end
19
+
20
+ private
21
+
22
+ def item_content
23
+ safe_join([
24
+ check_indicator,
25
+ content_tag(:span, content)
26
+ ])
27
+ end
28
+
29
+ def check_indicator
30
+ content_tag(:span, check_icon, class: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center")
31
+ end
32
+
33
+ def check_icon
34
+ content_tag(:svg,
35
+ content_tag(:path, nil, d: "M20 6 9 17l-5-5", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round"),
36
+ xmlns: "http://www.w3.org/2000/svg",
37
+ width: "12",
38
+ height: "12",
39
+ viewBox: "0 0 24 24",
40
+ fill: "none",
41
+ class: "h-4 w-4 opacity-0",
42
+ "data-shadcn--select-target": "checkIcon"
43
+ )
44
+ end
45
+
46
+ def item_attributes
47
+ {
48
+ class: merge_classes(BASE_CLASSES),
49
+ role: "option",
50
+ "aria-selected": "false",
51
+ "data-shadcn--select-target": "item",
52
+ "data-value": @value,
53
+ "data-disabled": @disabled ? "" : nil,
54
+ "data-action": "click->shadcn--select#select",
55
+ tabindex: @disabled ? nil : "-1"
56
+ }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Select Separator component
5
+ class SelectSeparatorComponent < BaseComponent
6
+ BASE_CLASSES = "-mx-1 my-1 h-px bg-muted"
7
+
8
+ def call
9
+ content_tag(:div, "", class: merge_classes(BASE_CLASSES), role: "separator")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Separator component for visually dividing content
5
+ # Matches shadcn/ui Separator component
6
+ #
7
+ # @example Horizontal separator
8
+ # <%= render Shadcn::SeparatorComponent.new %>
9
+ #
10
+ # @example Vertical separator
11
+ # <%= render Shadcn::SeparatorComponent.new(orientation: :vertical) %>
12
+ #
13
+ # @example Decorative separator (no semantic meaning)
14
+ # <%= render Shadcn::SeparatorComponent.new(decorative: true) %>
15
+ #
16
+ class SeparatorComponent < BaseComponent
17
+ ORIENTATIONS = {
18
+ horizontal: "h-[1px] w-full",
19
+ vertical: "h-full w-[1px]"
20
+ }.freeze
21
+
22
+ BASE_CLASSES = "shrink-0 bg-border"
23
+
24
+ # @param orientation [Symbol] Separator orientation (:horizontal, :vertical)
25
+ # @param decorative [Boolean] Whether the separator is purely decorative
26
+ def initialize(orientation: :horizontal, decorative: false, **options)
27
+ super(**options)
28
+ @orientation = orientation.to_sym
29
+ @decorative = decorative
30
+ end
31
+
32
+ def call
33
+ tag.div(**separator_attributes)
34
+ end
35
+
36
+ private
37
+
38
+ def separator_classes
39
+ cn(BASE_CLASSES, ORIENTATIONS[@orientation], class_name)
40
+ end
41
+
42
+ def separator_attributes
43
+ attrs = {
44
+ class: separator_classes,
45
+ role: @decorative ? "none" : "separator",
46
+ "aria-orientation": @decorative ? nil : @orientation.to_s,
47
+ "data-orientation": @orientation.to_s
48
+ }
49
+ attrs.merge!(html_options)
50
+ attrs.merge!(build_data)
51
+ attrs.compact
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Sheet component for slide-out panels
5
+ # Matches shadcn/ui Sheet component
6
+ # Uses Stimulus for interactivity
7
+ #
8
+ # @example Basic sheet
9
+ # <%= render Shadcn::SheetComponent.new(side: :right) do |sheet| %>
10
+ # <% sheet.with_trigger do %>
11
+ # <%= render Shadcn::ButtonComponent.new(variant: :outline) { "Open Sheet" } %>
12
+ # <% end %>
13
+ # <% sheet.with_content do |content| %>
14
+ # <% content.with_header do %>
15
+ # <% content.with_title { "Edit Profile" } %>
16
+ # <% content.with_description { "Make changes to your profile." } %>
17
+ # <% end %>
18
+ # <div class="py-4">
19
+ # Sheet body content
20
+ # </div>
21
+ # <% content.with_footer do %>
22
+ # <%= render Shadcn::ButtonComponent.new { "Save changes" } %>
23
+ # <% end %>
24
+ # <% end %>
25
+ # <% end %>
26
+ #
27
+ class SheetComponent < BaseComponent
28
+ SIDES = {
29
+ top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
30
+ bottom: "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
31
+ left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
32
+ right: "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm"
33
+ }.freeze
34
+
35
+ renders_one :trigger
36
+ renders_one :body, lambda { |**options|
37
+ SheetContentComponent.new(side: @side, **options)
38
+ }
39
+
40
+ # @param side [Symbol] Side to show sheet (:top, :right, :bottom, :left)
41
+ # @param open [Boolean] Whether sheet starts open
42
+ def initialize(side: :right, open: false, **options)
43
+ super(**options)
44
+ @side = side.to_sym
45
+ @open = open
46
+ end
47
+
48
+ def call
49
+ content_tag(:div, sheet_structure, sheet_attributes)
50
+ end
51
+
52
+ private
53
+
54
+ def sheet_structure
55
+ safe_join([
56
+ trigger_wrapper,
57
+ body
58
+ ].compact)
59
+ end
60
+
61
+ def trigger_wrapper
62
+ return unless trigger
63
+
64
+ content_tag(:div, trigger, {
65
+ "data-shadcn--sheet-target": "trigger",
66
+ "data-action": "click->shadcn--sheet#open"
67
+ })
68
+ end
69
+
70
+ def sheet_attributes
71
+ attrs = {
72
+ class: class_name,
73
+ "data-controller": "shadcn--sheet",
74
+ "data-shadcn--sheet-open-value": @open.to_s,
75
+ "data-shadcn--sheet-side-value": @side.to_s
76
+ }
77
+ attrs.merge!(html_options)
78
+ attrs.merge!(build_data)
79
+ attrs.compact
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Sheet Content component
5
+ class SheetContentComponent < BaseComponent
6
+ OVERLAY_CLASSES = "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
7
+ BASE_CONTENT_CLASSES = "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out"
8
+ CLOSE_CLASSES = "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary"
9
+
10
+ renders_one :header, lambda { |**options|
11
+ SheetHeaderComponent.new(**options)
12
+ }
13
+ renders_one :title, lambda { |**options|
14
+ SheetTitleComponent.new(**options)
15
+ }
16
+ renders_one :description, lambda { |**options|
17
+ SheetDescriptionComponent.new(**options)
18
+ }
19
+ renders_one :footer, lambda { |**options|
20
+ SheetFooterComponent.new(**options)
21
+ }
22
+
23
+ # @param side [Symbol] Side the sheet appears from
24
+ def initialize(side: :right, **options)
25
+ super(**options)
26
+ @side = side.to_sym
27
+ end
28
+
29
+ def call
30
+ content_tag(:template, content_wrapper, { "data-shadcn--sheet-target": "template" })
31
+ end
32
+
33
+ private
34
+
35
+ def content_wrapper
36
+ safe_join([overlay, sheet_panel])
37
+ end
38
+
39
+ def overlay
40
+ content_tag(:div, "", {
41
+ class: OVERLAY_CLASSES,
42
+ "data-shadcn--sheet-target": "overlay",
43
+ "data-action": "click->shadcn--sheet#close",
44
+ "data-state": "closed"
45
+ })
46
+ end
47
+
48
+ def sheet_panel
49
+ content_tag(:div, panel_content, panel_attributes)
50
+ end
51
+
52
+ def panel_content
53
+ safe_join([
54
+ header,
55
+ title,
56
+ description,
57
+ content,
58
+ footer,
59
+ close_button
60
+ ].compact)
61
+ end
62
+
63
+ def panel_attributes
64
+ {
65
+ class: cn(BASE_CONTENT_CLASSES, SheetComponent::SIDES[@side], class_name),
66
+ role: "dialog",
67
+ "aria-modal": "true",
68
+ "data-shadcn--sheet-target": "content",
69
+ "data-state": "closed",
70
+ tabindex: "-1"
71
+ }
72
+ end
73
+
74
+ def close_button
75
+ content_tag(:button, close_icon, {
76
+ type: "button",
77
+ class: CLOSE_CLASSES,
78
+ "data-action": "click->shadcn--sheet#close",
79
+ "aria-label": "Close"
80
+ })
81
+ end
82
+
83
+ def close_icon
84
+ content_tag(:svg,
85
+ content_tag(:path, nil, d: "M18 6 6 18M6 6l12 12", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round"),
86
+ xmlns: "http://www.w3.org/2000/svg",
87
+ width: "16",
88
+ height: "16",
89
+ viewBox: "0 0 24 24",
90
+ fill: "none",
91
+ class: "h-4 w-4"
92
+ )
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Sheet Description component
5
+ class SheetDescriptionComponent < BaseComponent
6
+ BASE_CLASSES = "text-sm text-muted-foreground"
7
+
8
+ def call
9
+ content_tag(:p, content, class: merge_classes(BASE_CLASSES))
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Sheet Footer component
5
+ class SheetFooterComponent < BaseComponent
6
+ BASE_CLASSES = "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2"
7
+
8
+ def call
9
+ content_tag(:div, content, class: merge_classes(BASE_CLASSES))
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Sheet Header component
5
+ class SheetHeaderComponent < BaseComponent
6
+ BASE_CLASSES = "flex flex-col space-y-2 text-center sm:text-left"
7
+
8
+ renders_one :title, lambda { |**options|
9
+ SheetTitleComponent.new(**options)
10
+ }
11
+ renders_one :description, lambda { |**options|
12
+ SheetDescriptionComponent.new(**options)
13
+ }
14
+
15
+ def call
16
+ content_tag(:div, safe_join([title, description, content].compact), class: merge_classes(BASE_CLASSES))
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Sheet Title component
5
+ class SheetTitleComponent < BaseComponent
6
+ BASE_CLASSES = "text-lg font-semibold text-foreground"
7
+
8
+ def call
9
+ content_tag(:h2, content, class: merge_classes(BASE_CLASSES))
10
+ end
11
+ end
12
+ end