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,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Container for navigation menu items
5
+ class NavigationMenuListComponent < BaseComponent
6
+ BASE_CLASSES = "group flex flex-1 list-none items-center justify-center space-x-1"
7
+
8
+ renders_many :items, lambda { |**options|
9
+ NavigationMenuItemComponent.new(**options)
10
+ }
11
+
12
+ def call
13
+ content_tag(:ul, list_content, list_attributes)
14
+ end
15
+
16
+ private
17
+
18
+ def list_content
19
+ safe_join(items)
20
+ end
21
+
22
+ def list_attributes
23
+ {
24
+ class: cn(BASE_CLASSES, class_name),
25
+ "data-shadcn--navigation-menu-target": "list"
26
+ }.merge(html_options).compact
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Trigger button that opens navigation menu content
5
+ class NavigationMenuTriggerComponent < BaseComponent
6
+ TRIGGER_CLASSES = [
7
+ "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2",
8
+ "text-sm font-medium transition-colors",
9
+ "hover:bg-accent hover:text-accent-foreground",
10
+ "focus:bg-accent focus:text-accent-foreground focus:outline-none",
11
+ "disabled:pointer-events-none disabled:opacity-50",
12
+ "data-[state=open]:bg-accent/50"
13
+ ].join(" ").freeze
14
+
15
+ def call
16
+ content_tag(:button, trigger_content, trigger_attributes)
17
+ end
18
+
19
+ private
20
+
21
+ def trigger_content
22
+ safe_join([
23
+ content,
24
+ chevron_icon
25
+ ])
26
+ end
27
+
28
+ def chevron_icon
29
+ content_tag(:svg, chevron_path,
30
+ xmlns: "http://www.w3.org/2000/svg",
31
+ width: "24",
32
+ height: "24",
33
+ viewBox: "0 0 24 24",
34
+ fill: "none",
35
+ stroke: "currentColor",
36
+ "stroke-width": "2",
37
+ "stroke-linecap": "round",
38
+ "stroke-linejoin": "round",
39
+ class: "relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180",
40
+ "aria-hidden": "true"
41
+ )
42
+ end
43
+
44
+ def chevron_path
45
+ content_tag(:path, nil, d: "m6 9 6 6 6-6")
46
+ end
47
+
48
+ def trigger_attributes
49
+ {
50
+ class: cn(TRIGGER_CLASSES, class_name),
51
+ type: "button",
52
+ "data-shadcn--navigation-menu-target": "trigger",
53
+ "data-action": "click->shadcn--navigation-menu#toggle mouseenter->shadcn--navigation-menu#hoverOpen",
54
+ "aria-expanded": "false",
55
+ "data-state": "closed"
56
+ }.merge(html_options).compact
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Pagination component for navigating paged content
5
+ # Matches shadcn/ui Pagination component
6
+ #
7
+ # Supports three usage patterns:
8
+ #
9
+ # 1. Slot-based API (full control):
10
+ # <%= render Shadcn::PaginationComponent.new do |pagination| %>
11
+ # <% pagination.with_pagination_content do |content| %>
12
+ # <% content.with_previous(href: "/page/1") %>
13
+ # <% content.with_item(href: "/page/1") { "1" } %>
14
+ # <% content.with_item(href: "/page/2", active: true) { "2" } %>
15
+ # <% content.with_item(href: "/page/3") { "3" } %>
16
+ # <% content.with_ellipse %>
17
+ # <% content.with_next_page(href: "/page/3") %>
18
+ # <% end %>
19
+ # <% end %>
20
+ #
21
+ # 2. Collection-based API (Kaminari/will_paginate):
22
+ # <%= render Shadcn::PaginationComponent.new(collection: @posts) %>
23
+ #
24
+ # 3. Pagy-based API:
25
+ # <%= render Shadcn::PaginationComponent.new(pagy: @pagy) %>
26
+ #
27
+ class PaginationComponent < BaseComponent
28
+ BASE_CLASSES = "mx-auto flex w-full justify-center"
29
+
30
+ renders_one :pagination_content, lambda { |**options|
31
+ PaginationContentComponent.new(**options)
32
+ }
33
+
34
+ # @param collection [Object, nil] Kaminari or will_paginate collection
35
+ # @param pagy [Object, nil] Pagy object
36
+ # @param url_builder [Proc, nil] Lambda to generate page URLs, receives page number
37
+ # @param window [Integer] Number of pages to show around current page
38
+ def initialize(collection: nil, pagy: nil, url_builder: nil, window: 2, **options)
39
+ super(**options)
40
+ @collection = collection
41
+ @pagy = pagy
42
+ @url_builder = url_builder || ->(page) { "?page=#{page}" }
43
+ @window = window
44
+ end
45
+
46
+ def call
47
+ if auto_generate?
48
+ return "" if total_pages <= 1
49
+
50
+ content_tag(:nav, auto_generated_content, pagination_attributes)
51
+ else
52
+ content_tag(:nav, build_pagination_content, pagination_attributes)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def auto_generate?
59
+ @collection.present? || @pagy.present?
60
+ end
61
+
62
+ def auto_generated_content
63
+ pagination_data = extract_pagination_data
64
+ series = generate_page_series(pagination_data[:current_page], pagination_data[:total_pages])
65
+
66
+ # Build the items for the content component
67
+ items_html = []
68
+
69
+ # Previous button
70
+ prev_href = pagination_data[:prev_page] ? @url_builder.call(pagination_data[:prev_page]) : nil
71
+ items_html << PaginationPreviousComponent.new(href: prev_href, disabled: pagination_data[:prev_page].nil?).render_in(view_context)
72
+
73
+ # Page items
74
+ series.each do |item|
75
+ case item
76
+ when :gap
77
+ items_html << PaginationEllipsisComponent.new.render_in(view_context)
78
+ when Integer
79
+ items_html << PaginationItemComponent.new(href: @url_builder.call(item)).render_in(view_context) { item.to_s }
80
+ when String # Current page (string format)
81
+ page_num = item.to_i
82
+ items_html << PaginationItemComponent.new(href: @url_builder.call(page_num), active: true).render_in(view_context) { item }
83
+ end
84
+ end
85
+
86
+ # Next button
87
+ next_href = pagination_data[:next_page] ? @url_builder.call(pagination_data[:next_page]) : nil
88
+ items_html << PaginationNextComponent.new(href: next_href, disabled: pagination_data[:next_page].nil?).render_in(view_context)
89
+
90
+ content_tag(:ul, safe_join(items_html), class: "flex flex-row items-center gap-1")
91
+ end
92
+
93
+ def extract_pagination_data
94
+ obj = @pagy || @collection
95
+
96
+ # Pagy detection
97
+ if defined?(::Pagy) && obj.is_a?(::Pagy)
98
+ {
99
+ current_page: obj.page,
100
+ total_pages: obj.pages,
101
+ prev_page: obj.prev,
102
+ next_page: obj.next
103
+ }
104
+ # Duck typing for Pagy-like objects
105
+ elsif obj.respond_to?(:page) && obj.respond_to?(:pages) && obj.respond_to?(:prev) && obj.respond_to?(:next)
106
+ {
107
+ current_page: obj.page,
108
+ total_pages: obj.pages,
109
+ prev_page: obj.prev,
110
+ next_page: obj.next
111
+ }
112
+ # Kaminari detection
113
+ elsif obj.respond_to?(:current_page) && obj.respond_to?(:total_pages) && obj.respond_to?(:prev_page)
114
+ {
115
+ current_page: obj.current_page,
116
+ total_pages: obj.total_pages,
117
+ prev_page: obj.prev_page,
118
+ next_page: obj.next_page
119
+ }
120
+ # will_paginate detection
121
+ elsif obj.respond_to?(:current_page) && obj.respond_to?(:total_pages) && obj.respond_to?(:previous_page)
122
+ {
123
+ current_page: obj.current_page,
124
+ total_pages: obj.total_pages,
125
+ prev_page: obj.previous_page,
126
+ next_page: obj.next_page
127
+ }
128
+ # Generic fallback
129
+ elsif obj.respond_to?(:current_page) && obj.respond_to?(:total_pages)
130
+ current = obj.current_page
131
+ total = obj.total_pages
132
+ {
133
+ current_page: current,
134
+ total_pages: total,
135
+ prev_page: current > 1 ? current - 1 : nil,
136
+ next_page: current < total ? current + 1 : nil
137
+ }
138
+ else
139
+ raise ArgumentError, "Expected a paginated collection (Kaminari/will_paginate) or Pagy object"
140
+ end
141
+ end
142
+
143
+ def total_pages
144
+ extract_pagination_data[:total_pages]
145
+ end
146
+
147
+ def generate_page_series(current_page, total_pages)
148
+ return [current_page.to_s] if total_pages <= 1
149
+
150
+ series = []
151
+
152
+ # Always include first page
153
+ series << (current_page == 1 ? "1" : 1)
154
+
155
+ # Calculate range around current page
156
+ range_start = [2, current_page - @window].max
157
+ range_end = [total_pages - 1, current_page + @window].min
158
+
159
+ # Add gap before range if needed
160
+ series << :gap if range_start > 2
161
+
162
+ # Add pages in range
163
+ (range_start..range_end).each do |page|
164
+ next if page == 1 || page == total_pages
165
+
166
+ series << (page == current_page ? page.to_s : page)
167
+ end
168
+
169
+ # Add gap after range if needed
170
+ series << :gap if range_end < total_pages - 1
171
+
172
+ # Always include last page if more than 1 page
173
+ if total_pages > 1
174
+ series << (current_page == total_pages ? total_pages.to_s : total_pages)
175
+ end
176
+
177
+ series
178
+ end
179
+
180
+ def build_pagination_content
181
+ pagination_content || ""
182
+ end
183
+
184
+ def pagination_attributes
185
+ attrs = {
186
+ role: "navigation",
187
+ "aria-label": "pagination",
188
+ class: merge_classes(BASE_CLASSES)
189
+ }
190
+ attrs.merge!(html_options)
191
+ attrs.merge!(build_data)
192
+ attrs.compact
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Pagination Content component - container for items
5
+ # Uses a single polymorphic slot to maintain ordering between items and ellipses
6
+ class PaginationContentComponent < BaseComponent
7
+ BASE_CLASSES = "flex flex-row items-center gap-1"
8
+
9
+ renders_one :previous, lambda { |href: nil, disabled: false, **options|
10
+ PaginationPreviousComponent.new(href: href, disabled: disabled, **options)
11
+ }
12
+
13
+ renders_one :next_page, lambda { |href: nil, disabled: false, **options|
14
+ PaginationNextComponent.new(href: href, disabled: disabled, **options)
15
+ }
16
+
17
+ # Single slot for all page elements (items and ellipses) to maintain order
18
+ renders_many :elements, types: {
19
+ item: {
20
+ renders: lambda { |href: nil, active: false, disabled: false, **options|
21
+ PaginationItemComponent.new(href: href, active: active, disabled: disabled, **options)
22
+ },
23
+ as: :item
24
+ },
25
+ ellipse: {
26
+ renders: lambda { |**options|
27
+ PaginationEllipsisComponent.new(**options)
28
+ },
29
+ as: :ellipse
30
+ }
31
+ }
32
+
33
+ def call
34
+ content_tag(:ul, list_content, class: merge_classes(BASE_CLASSES))
35
+ end
36
+
37
+ private
38
+
39
+ def list_content
40
+ safe_join([
41
+ previous,
42
+ elements,
43
+ next_page
44
+ ].flatten.compact)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Pagination Ellipsis component
5
+ class PaginationEllipsisComponent < BaseComponent
6
+ BASE_CLASSES = "flex h-9 w-9 items-center justify-center"
7
+
8
+ def call
9
+ content_tag(:li) do
10
+ content_tag(:span, ellipsis_content, class: merge_classes(BASE_CLASSES), "aria-hidden": "true")
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def ellipsis_content
17
+ content_tag(:svg,
18
+ content_tag(:circle, nil, cx: "12", cy: "12", r: "1") +
19
+ content_tag(:circle, nil, cx: "19", cy: "12", r: "1") +
20
+ content_tag(:circle, nil, cx: "5", cy: "12", r: "1"),
21
+ xmlns: "http://www.w3.org/2000/svg",
22
+ width: "16",
23
+ height: "16",
24
+ viewBox: "0 0 24 24",
25
+ fill: "currentColor",
26
+ class: "h-4 w-4"
27
+ ) + content_tag(:span, "More pages", class: "sr-only")
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Pagination Item component - wrapper for links
5
+ class PaginationItemComponent < BaseComponent
6
+ LINK_CLASSES = "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-9 w-9"
7
+ ACTIVE_CLASSES = "border border-input bg-background shadow-sm"
8
+
9
+ def initialize(href: nil, active: false, disabled: false, **options)
10
+ super(**options)
11
+ @href = href
12
+ @active = active
13
+ @disabled = disabled
14
+ end
15
+
16
+ def call
17
+ content_tag(:li) do
18
+ link_element
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def link_element
25
+ classes = cn(LINK_CLASSES, @active ? ACTIVE_CLASSES : "", class_name)
26
+
27
+ if @href
28
+ content_tag(:a, content, link_attributes(classes))
29
+ else
30
+ content_tag(:span, content, span_attributes(classes))
31
+ end
32
+ end
33
+
34
+ def link_attributes(classes)
35
+ attrs = {
36
+ href: @href,
37
+ class: classes,
38
+ "aria-current": @active ? "page" : nil
39
+ }
40
+ attrs.merge!(html_options)
41
+ attrs.compact
42
+ end
43
+
44
+ def span_attributes(classes)
45
+ attrs = {
46
+ class: classes,
47
+ "aria-current": @active ? "page" : nil
48
+ }
49
+ attrs.merge!(html_options)
50
+ attrs.compact
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Pagination Next button
5
+ class PaginationNextComponent < BaseComponent
6
+ BASE_CLASSES = "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2 gap-1 pr-2.5"
7
+
8
+ def initialize(href: nil, disabled: false, **options)
9
+ super(**options)
10
+ @href = href
11
+ @disabled = disabled
12
+ end
13
+
14
+ def call
15
+ content_tag(:li) do
16
+ link_content
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def link_content
23
+ inner = safe_join(["Next", chevron_right])
24
+
25
+ if @href && !@disabled
26
+ content_tag(:a, inner, href: @href, class: merge_classes(BASE_CLASSES), "aria-label": "Go to next page")
27
+ else
28
+ content_tag(:span, inner, class: cn(merge_classes(BASE_CLASSES), "pointer-events-none opacity-50"), "aria-disabled": "true")
29
+ end
30
+ end
31
+
32
+ def chevron_right
33
+ content_tag(:svg,
34
+ content_tag(:path, nil, d: "m9 18 6-6-6-6"),
35
+ xmlns: "http://www.w3.org/2000/svg",
36
+ width: "16",
37
+ height: "16",
38
+ viewBox: "0 0 24 24",
39
+ fill: "none",
40
+ stroke: "currentColor",
41
+ "stroke-width": "2",
42
+ "stroke-linecap": "round",
43
+ "stroke-linejoin": "round",
44
+ class: "h-4 w-4"
45
+ )
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Pagination Previous button
5
+ class PaginationPreviousComponent < BaseComponent
6
+ BASE_CLASSES = "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2 gap-1 pl-2.5"
7
+
8
+ def initialize(href: nil, disabled: false, **options)
9
+ super(**options)
10
+ @href = href
11
+ @disabled = disabled
12
+ end
13
+
14
+ def call
15
+ content_tag(:li) do
16
+ link_content
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def link_content
23
+ inner = safe_join([chevron_left, "Previous"])
24
+
25
+ if @href && !@disabled
26
+ content_tag(:a, inner, href: @href, class: merge_classes(BASE_CLASSES), "aria-label": "Go to previous page")
27
+ else
28
+ content_tag(:span, inner, class: cn(merge_classes(BASE_CLASSES), "pointer-events-none opacity-50"), "aria-disabled": "true")
29
+ end
30
+ end
31
+
32
+ def chevron_left
33
+ content_tag(:svg,
34
+ content_tag(:path, nil, d: "m15 18-6-6 6-6"),
35
+ xmlns: "http://www.w3.org/2000/svg",
36
+ width: "16",
37
+ height: "16",
38
+ viewBox: "0 0 24 24",
39
+ fill: "none",
40
+ stroke: "currentColor",
41
+ "stroke-width": "2",
42
+ "stroke-linecap": "round",
43
+ "stroke-linejoin": "round",
44
+ class: "h-4 w-4"
45
+ )
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Popover component for rich content in an overlay
5
+ # Matches shadcn/ui Popover component
6
+ # Uses Stimulus for interactivity
7
+ #
8
+ # @example Basic popover
9
+ # <%= render Shadcn::PopoverComponent.new do |popover| %>
10
+ # <% popover.with_trigger do %>
11
+ # <%= render Shadcn::ButtonComponent.new(variant: :outline) { "Open popover" } %>
12
+ # <% end %>
13
+ # <% popover.with_content do %>
14
+ # <div class="grid gap-4">
15
+ # <h4 class="font-medium leading-none">Dimensions</h4>
16
+ # <p class="text-sm text-muted-foreground">Set the dimensions for the layer.</p>
17
+ # </div>
18
+ # <% end %>
19
+ # <% end %>
20
+ #
21
+ class PopoverComponent < BaseComponent
22
+ renders_one :trigger
23
+ renders_one :body, lambda { |**options, &block|
24
+ PopoverContentComponent.new(**options, &block)
25
+ }
26
+
27
+ # @param open [Boolean] Whether popover starts open
28
+ # @param side [Symbol] Side to show content (:top, :right, :bottom, :left)
29
+ # @param align [Symbol] Alignment (:start, :center, :end)
30
+ # @param modal [Boolean] Whether to trap focus
31
+ def initialize(open: false, side: :bottom, align: :center, modal: false, **options)
32
+ super(**options)
33
+ @open = open
34
+ @side = side
35
+ @align = align
36
+ @modal = modal
37
+ end
38
+
39
+ def call
40
+ content_tag(:div, popover_structure, popover_attributes)
41
+ end
42
+
43
+ private
44
+
45
+ def popover_structure
46
+ safe_join([
47
+ trigger_wrapper,
48
+ body
49
+ ].compact)
50
+ end
51
+
52
+ def trigger_wrapper
53
+ return unless trigger
54
+
55
+ content_tag(:div, trigger, {
56
+ "data-shadcn--popover-target": "trigger",
57
+ "data-action": "click->shadcn--popover#toggle"
58
+ })
59
+ end
60
+
61
+ def popover_attributes
62
+ attrs = {
63
+ class: cn("relative inline-block", class_name),
64
+ "data-controller": "shadcn--popover",
65
+ "data-shadcn--popover-open-value": @open.to_s,
66
+ "data-shadcn--popover-side-value": @side.to_s,
67
+ "data-shadcn--popover-align-value": @align.to_s,
68
+ "data-shadcn--popover-modal-value": @modal.to_s,
69
+ "data-action": "keydown.escape->shadcn--popover#close"
70
+ }
71
+ attrs.merge!(html_options)
72
+ attrs.merge!(build_data)
73
+ attrs.compact
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Popover Content component
5
+ class PopoverContentComponent < BaseComponent
6
+ BASE_CLASSES = "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
7
+
8
+ def call
9
+ content_tag(:div, content, content_attributes)
10
+ end
11
+
12
+ private
13
+
14
+ def content_attributes
15
+ {
16
+ class: merge_classes(BASE_CLASSES),
17
+ "data-shadcn--popover-target": "content",
18
+ "data-state": "closed",
19
+ "data-side": "bottom",
20
+ tabindex: "-1",
21
+ hidden: true
22
+ }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Progress component for showing completion status
5
+ # Matches shadcn/ui Progress component
6
+ #
7
+ # @example Basic progress
8
+ # <%= render Shadcn::ProgressComponent.new(value: 60) %>
9
+ #
10
+ # @example With custom max
11
+ # <%= render Shadcn::ProgressComponent.new(value: 30, max: 50) %>
12
+ #
13
+ # @example Indeterminate (loading)
14
+ # <%= render Shadcn::ProgressComponent.new(indeterminate: true) %>
15
+ #
16
+ class ProgressComponent < BaseComponent
17
+ BASE_CLASSES = "relative h-2 w-full overflow-hidden rounded-full bg-primary/20"
18
+ INDICATOR_CLASSES = "h-full w-full flex-1 bg-primary transition-all"
19
+
20
+ # @param value [Integer, Float, nil] Current progress value
21
+ # @param max [Integer, Float] Maximum progress value
22
+ # @param indeterminate [Boolean] Whether to show indeterminate state
23
+ def initialize(value: nil, max: 100, indeterminate: false, **options)
24
+ super(**options)
25
+ @value = value
26
+ @max = max
27
+ @indeterminate = indeterminate
28
+ end
29
+
30
+ def call
31
+ content_tag(:div, progress_indicator, progress_attributes)
32
+ end
33
+
34
+ private
35
+
36
+ def progress_indicator
37
+ content_tag(:div, "", indicator_attributes)
38
+ end
39
+
40
+ def progress_percentage
41
+ return 0 if @value.nil? || @max.zero?
42
+ [(@value.to_f / @max.to_f * 100).round, 100].min
43
+ end
44
+
45
+ def indicator_style
46
+ if @indeterminate
47
+ nil # Animation handled by CSS
48
+ else
49
+ "transform: translateX(-#{100 - progress_percentage}%)"
50
+ end
51
+ end
52
+
53
+ def progress_attributes
54
+ attrs = {
55
+ class: merge_classes(BASE_CLASSES),
56
+ role: "progressbar",
57
+ "aria-valuemin": 0,
58
+ "aria-valuemax": @max,
59
+ "aria-valuenow": @indeterminate ? nil : @value,
60
+ "data-state": @indeterminate ? "indeterminate" : "determinate",
61
+ "data-value": @value,
62
+ "data-max": @max
63
+ }
64
+ attrs.merge!(html_options)
65
+ attrs.merge!(build_data)
66
+ attrs.compact
67
+ end
68
+
69
+ def indicator_attributes
70
+ {
71
+ class: cn(INDICATOR_CLASSES, @indeterminate ? "animate-progress-indeterminate" : ""),
72
+ style: indicator_style,
73
+ "data-state": @indeterminate ? "indeterminate" : "determinate"
74
+ }
75
+ end
76
+ end
77
+ end