velvetkit 0.1.0.beta

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 (481) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +16 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +197 -0
  5. data/lib/generators/velvetkit/add/add_generator.rb +111 -0
  6. data/lib/generators/velvetkit/ai/ai_generator.rb +78 -0
  7. data/lib/generators/velvetkit/helpers.rb +64 -0
  8. data/lib/generators/velvetkit/install/install_generator.rb +108 -0
  9. data/lib/velvetkit/namespace.rb +57 -0
  10. data/lib/velvetkit/reference.rb +108 -0
  11. data/lib/velvetkit/registry.rb +162 -0
  12. data/lib/velvetkit/version.rb +5 -0
  13. data/lib/velvetkit.rb +28 -0
  14. data/registry/app/assets/tailwind/velvetkit.css +342 -0
  15. data/registry/app/components/ui/accordion/content.html.erb +5 -0
  16. data/registry/app/components/ui/accordion/content.rb +34 -0
  17. data/registry/app/components/ui/accordion/item.html.erb +1 -0
  18. data/registry/app/components/ui/accordion/item.rb +40 -0
  19. data/registry/app/components/ui/accordion/trigger.html.erb +1 -0
  20. data/registry/app/components/ui/accordion/trigger.rb +37 -0
  21. data/registry/app/components/ui/accordion.html.erb +7 -0
  22. data/registry/app/components/ui/accordion.rb +34 -0
  23. data/registry/app/components/ui/alert/description.html.erb +1 -0
  24. data/registry/app/components/ui/alert/description.rb +12 -0
  25. data/registry/app/components/ui/alert/title.html.erb +1 -0
  26. data/registry/app/components/ui/alert/title.rb +12 -0
  27. data/registry/app/components/ui/alert.html.erb +16 -0
  28. data/registry/app/components/ui/alert.rb +55 -0
  29. data/registry/app/components/ui/avatar/fallback.html.erb +1 -0
  30. data/registry/app/components/ui/avatar/fallback.rb +12 -0
  31. data/registry/app/components/ui/avatar/image.rb +20 -0
  32. data/registry/app/components/ui/avatar.html.erb +1 -0
  33. data/registry/app/components/ui/avatar.rb +56 -0
  34. data/registry/app/components/ui/avatar_group.html.erb +1 -0
  35. data/registry/app/components/ui/avatar_group.rb +35 -0
  36. data/registry/app/components/ui/badge.html.erb +1 -0
  37. data/registry/app/components/ui/badge.rb +38 -0
  38. data/registry/app/components/ui/banner.html.erb +10 -0
  39. data/registry/app/components/ui/banner.rb +47 -0
  40. data/registry/app/components/ui/base.rb +53 -0
  41. data/registry/app/components/ui/breadcrumb/auto_ellipsis.html.erb +23 -0
  42. data/registry/app/components/ui/breadcrumb/auto_ellipsis.rb +12 -0
  43. data/registry/app/components/ui/breadcrumb/ellipsis.html.erb +18 -0
  44. data/registry/app/components/ui/breadcrumb/ellipsis.rb +8 -0
  45. data/registry/app/components/ui/breadcrumb/item.html.erb +1 -0
  46. data/registry/app/components/ui/breadcrumb/item.rb +12 -0
  47. data/registry/app/components/ui/breadcrumb/item_slot.html.erb +10 -0
  48. data/registry/app/components/ui/breadcrumb/item_slot.rb +47 -0
  49. data/registry/app/components/ui/breadcrumb/list.html.erb +1 -0
  50. data/registry/app/components/ui/breadcrumb/list.rb +12 -0
  51. data/registry/app/components/ui/breadcrumb/separator.html.erb +1 -0
  52. data/registry/app/components/ui/breadcrumb/separator.rb +12 -0
  53. data/registry/app/components/ui/breadcrumb.html.erb +30 -0
  54. data/registry/app/components/ui/breadcrumb.rb +83 -0
  55. data/registry/app/components/ui/button.html.erb +1 -0
  56. data/registry/app/components/ui/button.rb +81 -0
  57. data/registry/app/components/ui/button_to.html.erb +1 -0
  58. data/registry/app/components/ui/button_to.rb +33 -0
  59. data/registry/app/components/ui/card/content.html.erb +1 -0
  60. data/registry/app/components/ui/card/content.rb +12 -0
  61. data/registry/app/components/ui/card/description.html.erb +1 -0
  62. data/registry/app/components/ui/card/description.rb +12 -0
  63. data/registry/app/components/ui/card/footer.html.erb +1 -0
  64. data/registry/app/components/ui/card/footer.rb +12 -0
  65. data/registry/app/components/ui/card/header.html.erb +1 -0
  66. data/registry/app/components/ui/card/header.rb +15 -0
  67. data/registry/app/components/ui/card/title.html.erb +1 -0
  68. data/registry/app/components/ui/card/title.rb +12 -0
  69. data/registry/app/components/ui/card.html.erb +1 -0
  70. data/registry/app/components/ui/card.rb +18 -0
  71. data/registry/app/components/ui/checkbox.html.erb +18 -0
  72. data/registry/app/components/ui/checkbox.rb +133 -0
  73. data/registry/app/components/ui/data_grid.html.erb +2 -0
  74. data/registry/app/components/ui/data_grid.rb +84 -0
  75. data/registry/app/components/ui/date_picker.html.erb +70 -0
  76. data/registry/app/components/ui/date_picker.rb +111 -0
  77. data/registry/app/components/ui/dialog/close.html.erb +7 -0
  78. data/registry/app/components/ui/dialog/close.rb +53 -0
  79. data/registry/app/components/ui/dialog/content.html.erb +26 -0
  80. data/registry/app/components/ui/dialog/content.rb +102 -0
  81. data/registry/app/components/ui/dialog/description.html.erb +1 -0
  82. data/registry/app/components/ui/dialog/description.rb +16 -0
  83. data/registry/app/components/ui/dialog/footer.html.erb +1 -0
  84. data/registry/app/components/ui/dialog/footer.rb +14 -0
  85. data/registry/app/components/ui/dialog/header.html.erb +1 -0
  86. data/registry/app/components/ui/dialog/header.rb +16 -0
  87. data/registry/app/components/ui/dialog/title.html.erb +1 -0
  88. data/registry/app/components/ui/dialog/title.rb +16 -0
  89. data/registry/app/components/ui/dialog/trigger.html.erb +5 -0
  90. data/registry/app/components/ui/dialog/trigger.rb +75 -0
  91. data/registry/app/components/ui/dialog.html.erb +3 -0
  92. data/registry/app/components/ui/dialog.rb +41 -0
  93. data/registry/app/components/ui/divider.html.erb +9 -0
  94. data/registry/app/components/ui/divider.rb +41 -0
  95. data/registry/app/components/ui/icon.html.erb +1 -0
  96. data/registry/app/components/ui/icon.rb +41 -0
  97. data/registry/app/components/ui/image.html.erb +1 -0
  98. data/registry/app/components/ui/image.rb +74 -0
  99. data/registry/app/components/ui/input.html.erb +9 -0
  100. data/registry/app/components/ui/input.rb +127 -0
  101. data/registry/app/components/ui/input_group/addon.html.erb +6 -0
  102. data/registry/app/components/ui/input_group/addon.rb +51 -0
  103. data/registry/app/components/ui/input_group/field.rb +26 -0
  104. data/registry/app/components/ui/input_group.html.erb +5 -0
  105. data/registry/app/components/ui/input_group.rb +55 -0
  106. data/registry/app/components/ui/label.html.erb +2 -0
  107. data/registry/app/components/ui/label.rb +48 -0
  108. data/registry/app/components/ui/link.html.erb +1 -0
  109. data/registry/app/components/ui/link.rb +64 -0
  110. data/registry/app/components/ui/menu/content.html.erb +5 -0
  111. data/registry/app/components/ui/menu/content.rb +106 -0
  112. data/registry/app/components/ui/menu/group.html.erb +1 -0
  113. data/registry/app/components/ui/menu/group.rb +19 -0
  114. data/registry/app/components/ui/menu/item.html.erb +1 -0
  115. data/registry/app/components/ui/menu/item.rb +68 -0
  116. data/registry/app/components/ui/menu/label.html.erb +1 -0
  117. data/registry/app/components/ui/menu/label.rb +33 -0
  118. data/registry/app/components/ui/menu/separator.html.erb +1 -0
  119. data/registry/app/components/ui/menu/separator.rb +20 -0
  120. data/registry/app/components/ui/menu/trigger.html.erb +1 -0
  121. data/registry/app/components/ui/menu/trigger.rb +29 -0
  122. data/registry/app/components/ui/menu.html.erb +3 -0
  123. data/registry/app/components/ui/menu.rb +23 -0
  124. data/registry/app/components/ui/native_select.html.erb +16 -0
  125. data/registry/app/components/ui/native_select.rb +109 -0
  126. data/registry/app/components/ui/pagination.html.erb +34 -0
  127. data/registry/app/components/ui/pagination.rb +62 -0
  128. data/registry/app/components/ui/password_input.html.erb +20 -0
  129. data/registry/app/components/ui/password_input.rb +94 -0
  130. data/registry/app/components/ui/popover/content.html.erb +12 -0
  131. data/registry/app/components/ui/popover/content.rb +83 -0
  132. data/registry/app/components/ui/popover/trigger.html.erb +1 -0
  133. data/registry/app/components/ui/popover/trigger.rb +29 -0
  134. data/registry/app/components/ui/popover.html.erb +3 -0
  135. data/registry/app/components/ui/popover.rb +24 -0
  136. data/registry/app/components/ui/radio_group/item.html.erb +18 -0
  137. data/registry/app/components/ui/radio_group/item.rb +97 -0
  138. data/registry/app/components/ui/radio_group.html.erb +1 -0
  139. data/registry/app/components/ui/radio_group.rb +35 -0
  140. data/registry/app/components/ui/select.html.erb +60 -0
  141. data/registry/app/components/ui/select.rb +181 -0
  142. data/registry/app/components/ui/sheet/close.rb +4 -0
  143. data/registry/app/components/ui/sheet/content.html.erb +24 -0
  144. data/registry/app/components/ui/sheet/content.rb +97 -0
  145. data/registry/app/components/ui/sheet/trigger.rb +4 -0
  146. data/registry/app/components/ui/sheet.html.erb +3 -0
  147. data/registry/app/components/ui/sheet.rb +37 -0
  148. data/registry/app/components/ui/sidebar/content.html.erb +1 -0
  149. data/registry/app/components/ui/sidebar/content.rb +18 -0
  150. data/registry/app/components/ui/sidebar/footer.html.erb +3 -0
  151. data/registry/app/components/ui/sidebar/footer.rb +16 -0
  152. data/registry/app/components/ui/sidebar/group.html.erb +1 -0
  153. data/registry/app/components/ui/sidebar/group.rb +19 -0
  154. data/registry/app/components/ui/sidebar/group_label.html.erb +1 -0
  155. data/registry/app/components/ui/sidebar/group_label.rb +18 -0
  156. data/registry/app/components/ui/sidebar/header.html.erb +1 -0
  157. data/registry/app/components/ui/sidebar/header.rb +16 -0
  158. data/registry/app/components/ui/sidebar/inset.html.erb +1 -0
  159. data/registry/app/components/ui/sidebar/inset.rb +17 -0
  160. data/registry/app/components/ui/sidebar/menu.html.erb +1 -0
  161. data/registry/app/components/ui/sidebar/menu.rb +18 -0
  162. data/registry/app/components/ui/sidebar/menu_button.html.erb +1 -0
  163. data/registry/app/components/ui/sidebar/menu_button.rb +30 -0
  164. data/registry/app/components/ui/sidebar/menu_item.html.erb +1 -0
  165. data/registry/app/components/ui/sidebar/menu_item.rb +23 -0
  166. data/registry/app/components/ui/sidebar/trigger.html.erb +1 -0
  167. data/registry/app/components/ui/sidebar/trigger.rb +24 -0
  168. data/registry/app/components/ui/sidebar/wrapper.html.erb +1 -0
  169. data/registry/app/components/ui/sidebar/wrapper.rb +40 -0
  170. data/registry/app/components/ui/sidebar.html.erb +11 -0
  171. data/registry/app/components/ui/sidebar.rb +29 -0
  172. data/registry/app/components/ui/spinner.html.erb +1 -0
  173. data/registry/app/components/ui/spinner.rb +20 -0
  174. data/registry/app/components/ui/switch.html.erb +12 -0
  175. data/registry/app/components/ui/switch.rb +109 -0
  176. data/registry/app/components/ui/table/cell.html.erb +1 -0
  177. data/registry/app/components/ui/table/cell.rb +16 -0
  178. data/registry/app/components/ui/table/column.html.erb +1 -0
  179. data/registry/app/components/ui/table/column.rb +18 -0
  180. data/registry/app/components/ui/table/head.html.erb +1 -0
  181. data/registry/app/components/ui/table/head.rb +13 -0
  182. data/registry/app/components/ui/table/row.html.erb +1 -0
  183. data/registry/app/components/ui/table/row.rb +26 -0
  184. data/registry/app/components/ui/table.html.erb +4 -0
  185. data/registry/app/components/ui/table.rb +45 -0
  186. data/registry/app/components/ui/tabs/content.html.erb +1 -0
  187. data/registry/app/components/ui/tabs/content.rb +43 -0
  188. data/registry/app/components/ui/tabs/list.html.erb +1 -0
  189. data/registry/app/components/ui/tabs/list.rb +28 -0
  190. data/registry/app/components/ui/tabs/trigger.html.erb +1 -0
  191. data/registry/app/components/ui/tabs/trigger.rb +91 -0
  192. data/registry/app/components/ui/tabs.html.erb +3 -0
  193. data/registry/app/components/ui/tabs.rb +27 -0
  194. data/registry/app/components/ui/text.html.erb +1 -0
  195. data/registry/app/components/ui/text.rb +48 -0
  196. data/registry/app/components/ui/theme_toggle.html.erb +9 -0
  197. data/registry/app/components/ui/theme_toggle.rb +63 -0
  198. data/registry/app/components/ui/tooltip/content.html.erb +1 -0
  199. data/registry/app/components/ui/tooltip/content.rb +65 -0
  200. data/registry/app/components/ui/tooltip/trigger.html.erb +1 -0
  201. data/registry/app/components/ui/tooltip/trigger.rb +33 -0
  202. data/registry/app/components/ui/tooltip.html.erb +3 -0
  203. data/registry/app/components/ui/tooltip.rb +24 -0
  204. data/registry/app/components/ui/typography/blockquote.html.erb +1 -0
  205. data/registry/app/components/ui/typography/blockquote.rb +15 -0
  206. data/registry/app/components/ui/typography/code.html.erb +1 -0
  207. data/registry/app/components/ui/typography/code.rb +25 -0
  208. data/registry/app/components/ui/typography/h1.rb +7 -0
  209. data/registry/app/components/ui/typography/h2.rb +7 -0
  210. data/registry/app/components/ui/typography/h3.rb +7 -0
  211. data/registry/app/components/ui/typography/h4.rb +7 -0
  212. data/registry/app/components/ui/typography/large.rb +7 -0
  213. data/registry/app/components/ui/typography/lead.rb +7 -0
  214. data/registry/app/components/ui/typography/muted.rb +7 -0
  215. data/registry/app/components/ui/typography/small.rb +7 -0
  216. data/registry/app/javascript/ag_grid.ts +26 -0
  217. data/registry/app/javascript/cally.ts +8 -0
  218. data/registry/app/javascript/controllers/ui/_internal/use_dismiss.ts +88 -0
  219. data/registry/app/javascript/controllers/ui/_internal/use_floating.ts +112 -0
  220. data/registry/app/javascript/controllers/ui/accordion_controller.ts +100 -0
  221. data/registry/app/javascript/controllers/ui/alert_controller.ts +7 -0
  222. data/registry/app/javascript/controllers/ui/banner_controller.ts +7 -0
  223. data/registry/app/javascript/controllers/ui/data_grid_controller.ts +198 -0
  224. data/registry/app/javascript/controllers/ui/date_picker_controller.ts +141 -0
  225. data/registry/app/javascript/controllers/ui/dialog_controller.ts +70 -0
  226. data/registry/app/javascript/controllers/ui/menu_controller.ts +201 -0
  227. data/registry/app/javascript/controllers/ui/password_visibility_controller.ts +43 -0
  228. data/registry/app/javascript/controllers/ui/popover_controller.ts +108 -0
  229. data/registry/app/javascript/controllers/ui/select_controller.ts +274 -0
  230. data/registry/app/javascript/controllers/ui/sidebar_controller.ts +64 -0
  231. data/registry/app/javascript/controllers/ui/tabs_controller.ts +74 -0
  232. data/registry/app/javascript/controllers/ui/theme_controller.ts +101 -0
  233. data/registry/app/javascript/controllers/ui/tooltip_controller.ts +102 -0
  234. data/registry/app/javascript/velvetkit_icons.ts +58 -0
  235. data/registry/app/views/layouts/_theme_script.html.erb +19 -0
  236. data/registry/components.yml +102 -0
  237. data/registry/config/locales/velvetkit.en.yml +56 -0
  238. data/registry/docs/agents.md +62 -0
  239. data/registry/test/components/previews/ui/accordion_preview/faq_style.html.erb +20 -0
  240. data/registry/test/components/previews/ui/accordion_preview/multiple.html.erb +27 -0
  241. data/registry/test/components/previews/ui/accordion_preview/single.html.erb +22 -0
  242. data/registry/test/components/previews/ui/accordion_preview.rb +31 -0
  243. data/registry/test/components/previews/ui/alert_preview/title_only.html.erb +12 -0
  244. data/registry/test/components/previews/ui/alert_preview/variants.html.erb +22 -0
  245. data/registry/test/components/previews/ui/alert_preview/with_icon.html.erb +12 -0
  246. data/registry/test/components/previews/ui/alert_preview/with_link.html.erb +18 -0
  247. data/registry/test/components/previews/ui/alert_preview.rb +34 -0
  248. data/registry/test/components/previews/ui/avatar_group_preview/default.html.erb +11 -0
  249. data/registry/test/components/previews/ui/avatar_group_preview/overflow.html.erb +23 -0
  250. data/registry/test/components/previews/ui/avatar_group_preview/small.html.erb +12 -0
  251. data/registry/test/components/previews/ui/avatar_group_preview.rb +23 -0
  252. data/registry/test/components/previews/ui/avatar_preview/colors.html.erb +8 -0
  253. data/registry/test/components/previews/ui/avatar_preview/shapes.html.erb +10 -0
  254. data/registry/test/components/previews/ui/avatar_preview/sizes.html.erb +8 -0
  255. data/registry/test/components/previews/ui/avatar_preview/with_image.html.erb +10 -0
  256. data/registry/test/components/previews/ui/avatar_preview.rb +24 -0
  257. data/registry/test/components/previews/ui/badge_preview/sizes.html.erb +5 -0
  258. data/registry/test/components/previews/ui/badge_preview/variants.html.erb +8 -0
  259. data/registry/test/components/previews/ui/badge_preview/with_icon.html.erb +14 -0
  260. data/registry/test/components/previews/ui/badge_preview.rb +20 -0
  261. data/registry/test/components/previews/ui/banner_preview/introduction.html.erb +9 -0
  262. data/registry/test/components/previews/ui/banner_preview/promotion.html.erb +9 -0
  263. data/registry/test/components/previews/ui/banner_preview/with_illustration.html.erb +12 -0
  264. data/registry/test/components/previews/ui/banner_preview.rb +25 -0
  265. data/registry/test/components/previews/ui/breadcrumb_preview/collapsed.html.erb +50 -0
  266. data/registry/test/components/previews/ui/breadcrumb_preview/default.html.erb +8 -0
  267. data/registry/test/components/previews/ui/breadcrumb_preview/long_path.html.erb +8 -0
  268. data/registry/test/components/previews/ui/breadcrumb_preview/medium.html.erb +7 -0
  269. data/registry/test/components/previews/ui/breadcrumb_preview/with_avatar.html.erb +8 -0
  270. data/registry/test/components/previews/ui/breadcrumb_preview.rb +66 -0
  271. data/registry/test/components/previews/ui/button_preview/disabled.html.erb +39 -0
  272. data/registry/test/components/previews/ui/button_preview/icon_only.html.erb +17 -0
  273. data/registry/test/components/previews/ui/button_preview/loading.html.erb +12 -0
  274. data/registry/test/components/previews/ui/button_preview/sizes.html.erb +6 -0
  275. data/registry/test/components/previews/ui/button_preview/variants.html.erb +8 -0
  276. data/registry/test/components/previews/ui/button_preview/with_both_icons.html.erb +12 -0
  277. data/registry/test/components/previews/ui/button_preview/with_leading_icon.html.erb +22 -0
  278. data/registry/test/components/previews/ui/button_preview/with_trailing_icon.html.erb +14 -0
  279. data/registry/test/components/previews/ui/button_preview.rb +65 -0
  280. data/registry/test/components/previews/ui/button_to_preview/states.html.erb +8 -0
  281. data/registry/test/components/previews/ui/button_to_preview/variants.html.erb +5 -0
  282. data/registry/test/components/previews/ui/button_to_preview/with_icon.html.erb +11 -0
  283. data/registry/test/components/previews/ui/button_to_preview.rb +21 -0
  284. data/registry/test/components/previews/ui/card_preview/complete.html.erb +31 -0
  285. data/registry/test/components/previews/ui/card_preview/default.html.erb +7 -0
  286. data/registry/test/components/previews/ui/card_preview/settings_form.html.erb +25 -0
  287. data/registry/test/components/previews/ui/card_preview/with_footer.html.erb +13 -0
  288. data/registry/test/components/previews/ui/card_preview/with_header.html.erb +11 -0
  289. data/registry/test/components/previews/ui/card_preview.rb +44 -0
  290. data/registry/test/components/previews/ui/checkbox_preview/in_form.html.erb +9 -0
  291. data/registry/test/components/previews/ui/checkbox_preview/states.html.erb +22 -0
  292. data/registry/test/components/previews/ui/checkbox_preview/with_label.html.erb +5 -0
  293. data/registry/test/components/previews/ui/checkbox_preview.rb +27 -0
  294. data/registry/test/components/previews/ui/data_grid_preview.rb +18 -0
  295. data/registry/test/components/previews/ui/date_picker_preview/default.html.erb +3 -0
  296. data/registry/test/components/previews/ui/date_picker_preview/states.html.erb +7 -0
  297. data/registry/test/components/previews/ui/date_picker_preview.rb +13 -0
  298. data/registry/test/components/previews/ui/dialog_preview/confirmation.html.erb +11 -0
  299. data/registry/test/components/previews/ui/dialog_preview/custom_trigger.html.erb +16 -0
  300. data/registry/test/components/previews/ui/dialog_preview/default.html.erb +15 -0
  301. data/registry/test/components/previews/ui/dialog_preview/long_content.html.erb +16 -0
  302. data/registry/test/components/previews/ui/dialog_preview/playground.html.erb +12 -0
  303. data/registry/test/components/previews/ui/dialog_preview/scrollable_body.html.erb +29 -0
  304. data/registry/test/components/previews/ui/dialog_preview/sizes.html.erb +46 -0
  305. data/registry/test/components/previews/ui/dialog_preview/without_close_button.html.erb +11 -0
  306. data/registry/test/components/previews/ui/dialog_preview/without_title.html.erb +13 -0
  307. data/registry/test/components/previews/ui/dialog_preview.rb +38 -0
  308. data/registry/test/components/previews/ui/divider_preview/horizontal.html.erb +5 -0
  309. data/registry/test/components/previews/ui/divider_preview/vertical.html.erb +9 -0
  310. data/registry/test/components/previews/ui/divider_preview/with_label.html.erb +5 -0
  311. data/registry/test/components/previews/ui/divider_preview.rb +19 -0
  312. data/registry/test/components/previews/ui/icon_preview/common.html.erb +8 -0
  313. data/registry/test/components/previews/ui/icon_preview/sizes.html.erb +7 -0
  314. data/registry/test/components/previews/ui/icon_preview.rb +20 -0
  315. data/registry/test/components/previews/ui/image_preview/radii.html.erb +8 -0
  316. data/registry/test/components/previews/ui/image_preview/sizing.html.erb +9 -0
  317. data/registry/test/components/previews/ui/image_preview.rb +16 -0
  318. data/registry/test/components/previews/ui/input_group_preview/addons.html.erb +29 -0
  319. data/registry/test/components/previews/ui/input_group_preview/playground.html.erb +12 -0
  320. data/registry/test/components/previews/ui/input_group_preview.rb +17 -0
  321. data/registry/test/components/previews/ui/input_preview/sizes.html.erb +5 -0
  322. data/registry/test/components/previews/ui/input_preview/states.html.erb +7 -0
  323. data/registry/test/components/previews/ui/input_preview/with_labels.html.erb +10 -0
  324. data/registry/test/components/previews/ui/input_preview.rb +33 -0
  325. data/registry/test/components/previews/ui/label_preview/sizes.html.erb +6 -0
  326. data/registry/test/components/previews/ui/label_preview/states.html.erb +6 -0
  327. data/registry/test/components/previews/ui/label_preview/weights.html.erb +5 -0
  328. data/registry/test/components/previews/ui/label_preview.rb +22 -0
  329. data/registry/test/components/previews/ui/link_preview/sizes.html.erb +5 -0
  330. data/registry/test/components/previews/ui/link_preview/variants.html.erb +8 -0
  331. data/registry/test/components/previews/ui/link_preview/with_icons.html.erb +11 -0
  332. data/registry/test/components/previews/ui/link_preview.rb +20 -0
  333. data/registry/test/components/previews/ui/menu_preview/positions.html.erb +122 -0
  334. data/registry/test/components/previews/ui/menu_preview/variants.html.erb +218 -0
  335. data/registry/test/components/previews/ui/menu_preview.rb +26 -0
  336. data/registry/test/components/previews/ui/native_select_preview/sizes.html.erb +6 -0
  337. data/registry/test/components/previews/ui/native_select_preview/states.html.erb +6 -0
  338. data/registry/test/components/previews/ui/native_select_preview.rb +23 -0
  339. data/registry/test/components/previews/ui/pagination_preview/states.html.erb +15 -0
  340. data/registry/test/components/previews/ui/pagination_preview.rb +15 -0
  341. data/registry/test/components/previews/ui/password_input_preview/states.html.erb +19 -0
  342. data/registry/test/components/previews/ui/password_input_preview.rb +21 -0
  343. data/registry/test/components/previews/ui/popover_preview/default.html.erb +7 -0
  344. data/registry/test/components/previews/ui/popover_preview/positions.html.erb +30 -0
  345. data/registry/test/components/previews/ui/popover_preview/with_close_button.html.erb +8 -0
  346. data/registry/test/components/previews/ui/popover_preview/with_rich_content.html.erb +17 -0
  347. data/registry/test/components/previews/ui/popover_preview/without_title.html.erb +9 -0
  348. data/registry/test/components/previews/ui/popover_preview.rb +32 -0
  349. data/registry/test/components/previews/ui/radio_group_preview/default.html.erb +10 -0
  350. data/registry/test/components/previews/ui/radio_group_preview/disabled.html.erb +17 -0
  351. data/registry/test/components/previews/ui/radio_group_preview/with_required.html.erb +10 -0
  352. data/registry/test/components/previews/ui/radio_group_preview.rb +25 -0
  353. data/registry/test/components/previews/ui/select_preview/default.html.erb +4 -0
  354. data/registry/test/components/previews/ui/select_preview/long_list.html.erb +5 -0
  355. data/registry/test/components/previews/ui/select_preview/multiple.html.erb +6 -0
  356. data/registry/test/components/previews/ui/select_preview/sizes.html.erb +5 -0
  357. data/registry/test/components/previews/ui/select_preview/states.html.erb +6 -0
  358. data/registry/test/components/previews/ui/select_preview.rb +45 -0
  359. data/registry/test/components/previews/ui/sheet_preview/bottom.html.erb +12 -0
  360. data/registry/test/components/previews/ui/sheet_preview/left.html.erb +10 -0
  361. data/registry/test/components/previews/ui/sheet_preview/playground.html.erb +15 -0
  362. data/registry/test/components/previews/ui/sheet_preview/right.html.erb +11 -0
  363. data/registry/test/components/previews/ui/sheet_preview/top.html.erb +8 -0
  364. data/registry/test/components/previews/ui/sheet_preview.rb +22 -0
  365. data/registry/test/components/previews/ui/sidebar_preview/_brand.html.erb +14 -0
  366. data/registry/test/components/previews/ui/sidebar_preview/_shell.html.erb +60 -0
  367. data/registry/test/components/previews/ui/sidebar_preview/collapsible.html.erb +55 -0
  368. data/registry/test/components/previews/ui/sidebar_preview/default.html.erb +17 -0
  369. data/registry/test/components/previews/ui/sidebar_preview/with_dropdown_header.html.erb +33 -0
  370. data/registry/test/components/previews/ui/sidebar_preview/with_groups.html.erb +22 -0
  371. data/registry/test/components/previews/ui/sidebar_preview.rb +15 -0
  372. data/registry/test/components/previews/ui/spinner_preview/in_buttons.html.erb +7 -0
  373. data/registry/test/components/previews/ui/spinner_preview/sizes.html.erb +7 -0
  374. data/registry/test/components/previews/ui/spinner_preview.rb +19 -0
  375. data/registry/test/components/previews/ui/switch_preview/default.html.erb +4 -0
  376. data/registry/test/components/previews/ui/switch_preview/sizes.html.erb +5 -0
  377. data/registry/test/components/previews/ui/switch_preview/states.html.erb +7 -0
  378. data/registry/test/components/previews/ui/switch_preview.rb +26 -0
  379. data/registry/test/components/previews/ui/table_preview/customers.html.erb +15 -0
  380. data/registry/test/components/previews/ui/table_preview/empty.html.erb +8 -0
  381. data/registry/test/components/previews/ui/table_preview/with_caption.html.erb +14 -0
  382. data/registry/test/components/previews/ui/table_preview.rb +12 -0
  383. data/registry/test/components/previews/ui/tabs_preview/default.html.erb +39 -0
  384. data/registry/test/components/previews/ui/tabs_preview/pill_variant.html.erb +18 -0
  385. data/registry/test/components/previews/ui/tabs_preview/settings_form.html.erb +42 -0
  386. data/registry/test/components/previews/ui/tabs_preview/with_icons.html.erb +34 -0
  387. data/registry/test/components/previews/ui/tabs_preview.rb +33 -0
  388. data/registry/test/components/previews/ui/text_preview/sizes.html.erb +10 -0
  389. data/registry/test/components/previews/ui/text_preview/tones.html.erb +5 -0
  390. data/registry/test/components/previews/ui/text_preview/weights.html.erb +6 -0
  391. data/registry/test/components/previews/ui/text_preview.rb +26 -0
  392. data/registry/test/components/previews/ui/theme_toggle_preview/in_a_header.html.erb +14 -0
  393. data/registry/test/components/previews/ui/theme_toggle_preview/palette.html.erb +47 -0
  394. data/registry/test/components/previews/ui/theme_toggle_preview.rb +15 -0
  395. data/registry/test/components/previews/ui/tooltip_preview/default.html.erb +8 -0
  396. data/registry/test/components/previews/ui/tooltip_preview/positions.html.erb +34 -0
  397. data/registry/test/components/previews/ui/tooltip_preview/with_button.html.erb +8 -0
  398. data/registry/test/components/previews/ui/tooltip_preview/with_icon.html.erb +10 -0
  399. data/registry/test/components/previews/ui/tooltip_preview.rb +29 -0
  400. data/registry/test/components/previews/ui/typography_preview/article.html.erb +11 -0
  401. data/registry/test/components/previews/ui/typography_preview/code_and_quotes.html.erb +6 -0
  402. data/registry/test/components/previews/ui/typography_preview/headings.html.erb +6 -0
  403. data/registry/test/components/previews/ui/typography_preview/text_styles.html.erb +7 -0
  404. data/registry/test/components/previews/ui/typography_preview.rb +23 -0
  405. data/registry/test/components/ui/accordion/content_test.rb +64 -0
  406. data/registry/test/components/ui/accordion/item_test.rb +61 -0
  407. data/registry/test/components/ui/accordion/trigger_test.rb +80 -0
  408. data/registry/test/components/ui/accordion_test.rb +96 -0
  409. data/registry/test/components/ui/alert/description_test.rb +37 -0
  410. data/registry/test/components/ui/alert/title_test.rb +38 -0
  411. data/registry/test/components/ui/alert_test.rb +184 -0
  412. data/registry/test/components/ui/avatar_group_test.rb +79 -0
  413. data/registry/test/components/ui/avatar_test.rb +129 -0
  414. data/registry/test/components/ui/badge_test.rb +112 -0
  415. data/registry/test/components/ui/banner_test.rb +116 -0
  416. data/registry/test/components/ui/base_test.rb +98 -0
  417. data/registry/test/components/ui/breadcrumb/ellipsis_test.rb +51 -0
  418. data/registry/test/components/ui/breadcrumb/item_test.rb +39 -0
  419. data/registry/test/components/ui/breadcrumb/list_test.rb +34 -0
  420. data/registry/test/components/ui/breadcrumb/separator_test.rb +36 -0
  421. data/registry/test/components/ui/breadcrumb_test.rb +276 -0
  422. data/registry/test/components/ui/button_test.rb +276 -0
  423. data/registry/test/components/ui/button_to_test.rb +74 -0
  424. data/registry/test/components/ui/card/content_test.rb +41 -0
  425. data/registry/test/components/ui/card/description_test.rb +32 -0
  426. data/registry/test/components/ui/card/footer_test.rb +34 -0
  427. data/registry/test/components/ui/card/header_test.rb +57 -0
  428. data/registry/test/components/ui/card/title_test.rb +34 -0
  429. data/registry/test/components/ui/card_test.rb +67 -0
  430. data/registry/test/components/ui/checkbox_test.rb +236 -0
  431. data/registry/test/components/ui/data_grid_test.rb +88 -0
  432. data/registry/test/components/ui/date_picker_test.rb +168 -0
  433. data/registry/test/components/ui/dialog/content_test.rb +156 -0
  434. data/registry/test/components/ui/dialog/trigger_test.rb +74 -0
  435. data/registry/test/components/ui/dialog_test.rb +54 -0
  436. data/registry/test/components/ui/divider_test.rb +69 -0
  437. data/registry/test/components/ui/icon_test.rb +82 -0
  438. data/registry/test/components/ui/image_test.rb +74 -0
  439. data/registry/test/components/ui/input_group_test.rb +143 -0
  440. data/registry/test/components/ui/input_test.rb +251 -0
  441. data/registry/test/components/ui/label_test.rb +96 -0
  442. data/registry/test/components/ui/link_test.rb +95 -0
  443. data/registry/test/components/ui/menu/content_test.rb +233 -0
  444. data/registry/test/components/ui/menu/group_test.rb +24 -0
  445. data/registry/test/components/ui/menu/item_test.rb +76 -0
  446. data/registry/test/components/ui/menu/label_test.rb +43 -0
  447. data/registry/test/components/ui/menu/separator_test.rb +32 -0
  448. data/registry/test/components/ui/menu/trigger_test.rb +109 -0
  449. data/registry/test/components/ui/menu_test.rb +152 -0
  450. data/registry/test/components/ui/native_select_test.rb +118 -0
  451. data/registry/test/components/ui/pagination_test.rb +92 -0
  452. data/registry/test/components/ui/password_input_test.rb +98 -0
  453. data/registry/test/components/ui/popover/content_test.rb +226 -0
  454. data/registry/test/components/ui/popover/trigger_test.rb +83 -0
  455. data/registry/test/components/ui/popover_test.rb +101 -0
  456. data/registry/test/components/ui/radio_group/item_test.rb +14 -0
  457. data/registry/test/components/ui/radio_group_test.rb +139 -0
  458. data/registry/test/components/ui/select_test.rb +181 -0
  459. data/registry/test/components/ui/sheet/content_test.rb +135 -0
  460. data/registry/test/components/ui/sheet/trigger_test.rb +23 -0
  461. data/registry/test/components/ui/sheet_test.rb +43 -0
  462. data/registry/test/components/ui/sidebar/menu_button_test.rb +43 -0
  463. data/registry/test/components/ui/sidebar/trigger_test.rb +17 -0
  464. data/registry/test/components/ui/sidebar/wrapper_test.rb +15 -0
  465. data/registry/test/components/ui/sidebar_test.rb +43 -0
  466. data/registry/test/components/ui/spinner_test.rb +63 -0
  467. data/registry/test/components/ui/switch_test.rb +206 -0
  468. data/registry/test/components/ui/table_test.rb +106 -0
  469. data/registry/test/components/ui/tabs/content_test.rb +93 -0
  470. data/registry/test/components/ui/tabs/list_test.rb +65 -0
  471. data/registry/test/components/ui/tabs/trigger_test.rb +159 -0
  472. data/registry/test/components/ui/tabs_test.rb +88 -0
  473. data/registry/test/components/ui/text_test.rb +84 -0
  474. data/registry/test/components/ui/theme_toggle_test.rb +88 -0
  475. data/registry/test/components/ui/tooltip/content_test.rb +135 -0
  476. data/registry/test/components/ui/tooltip/trigger_test.rb +52 -0
  477. data/registry/test/components/ui/tooltip_test.rb +97 -0
  478. data/registry/test/components/ui/typography/code_blockquote_test.rb +57 -0
  479. data/registry/test/components/ui/typography/heading_test.rb +71 -0
  480. data/registry/test/components/ui/typography/text_variants_test.rb +71 -0
  481. metadata +540 -0
@@ -0,0 +1,276 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::ButtonTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_default_variant_and_size
10
+ render_inline(UI::Button.new) { "Save" }
11
+ btn = page.find("button")
12
+ assert_equal "button", btn[:type]
13
+ assert_includes btn[:class], "bg-primary"
14
+ assert_includes btn[:class], "text-primary-foreground"
15
+ assert_includes btn[:class], "h-9"
16
+ assert_equal "Save", btn.text.strip
17
+ end
18
+
19
+ def test_renders_destructive_variant
20
+ render_inline(UI::Button.new(variant: :destructive)) { "Delete" }
21
+ btn = page.find("button")
22
+ classes = btn[:class].split
23
+ assert_includes classes, "bg-destructive"
24
+ refute_includes classes, "bg-primary"
25
+ # destructive variant overrides the default focus ring color
26
+ assert_includes classes, "focus:ring-destructive"
27
+ refute_includes classes, "focus:ring-primary"
28
+ end
29
+
30
+ def test_renders_outline_variant
31
+ render_inline(UI::Button.new(variant: :outline)) { "Cancel" }
32
+ btn = page.find("button")
33
+ assert_includes btn[:class], "border-input"
34
+ assert_includes btn[:class], "bg-background"
35
+ end
36
+
37
+ def test_renders_secondary_variant
38
+ render_inline(UI::Button.new(variant: :secondary)) { "Secondary" }
39
+ assert_selector "button.bg-secondary", text: "Secondary"
40
+ end
41
+
42
+ def test_renders_ghost_variant
43
+ render_inline(UI::Button.new(variant: :ghost)) { "Ghost" }
44
+ btn = page.find("button")
45
+ assert_includes btn[:class], "hover:bg-accent"
46
+ refute_includes btn[:class], "bg-primary"
47
+ end
48
+
49
+ def test_renders_link_variant
50
+ render_inline(UI::Button.new(variant: :link)) { "Link" }
51
+ btn = page.find("button")
52
+ assert_includes btn[:class], "text-primary"
53
+ assert_includes btn[:class], "underline-offset-4"
54
+ end
55
+
56
+ def test_size_sm
57
+ render_inline(UI::Button.new(size: :sm)) { "Small" }
58
+ assert_selector "button.h-8", text: "Small"
59
+ end
60
+
61
+ def test_size_lg
62
+ render_inline(UI::Button.new(size: :lg)) { "Large" }
63
+ assert_selector "button.h-10", text: "Large"
64
+ end
65
+
66
+ def test_size_icon
67
+ render_inline(UI::Button.new(size: :icon)) { "" }
68
+ btn = page.find("button")
69
+ assert_includes btn[:class], "h-9"
70
+ assert_includes btn[:class], "w-9"
71
+ end
72
+
73
+ def test_size_icon_sm
74
+ render_inline(UI::Button.new(size: :icon_sm)) { "" }
75
+ btn = page.find("button")
76
+ classes = btn[:class].split
77
+ assert_includes classes, "h-7"
78
+ assert_includes classes, "w-7"
79
+ end
80
+
81
+ def test_full_width_adds_w_full
82
+ render_inline(UI::Button.new(full_width: true)) { "Wide" }
83
+ assert_selector "button.w-full"
84
+ end
85
+
86
+ def test_loading_state_marks_cursor
87
+ render_inline(UI::Button.new(loading: true)) { "Loading" }
88
+ btn = page.find("button")
89
+ assert_includes btn[:class].split, "disabled:cursor-not-allowed"
90
+ end
91
+
92
+ def test_loading_disables_the_button
93
+ render_inline(UI::Button.new(loading: true)) { "Loading" }
94
+ btn = page.find("button[disabled]")
95
+ assert btn.disabled?, "button should be disabled while loading"
96
+ end
97
+
98
+ def test_disabled_param_disables_the_button
99
+ render_inline(UI::Button.new(disabled: true)) { "Off" }
100
+ btn = page.find("button[disabled]")
101
+ assert btn.disabled?, "button should be disabled when disabled: true"
102
+ end
103
+
104
+ def test_disabled_emits_disabled_grayscale_classes
105
+ render_inline(UI::Button.new(disabled: true)) { "Off" }
106
+ classes = page.find("button[disabled]")[:class].split
107
+ refute_includes classes, "disabled:opacity-50"
108
+ assert_includes classes, "disabled:bg-muted!"
109
+ assert_includes classes, "disabled:text-muted-foreground!"
110
+ assert_includes classes, "disabled:pointer-events-none"
111
+ assert_includes classes, "disabled:cursor-not-allowed"
112
+ end
113
+
114
+ def test_disabled_outline_variant_uses_muted_border
115
+ render_inline(UI::Button.new(variant: :outline, disabled: true)) { "Off" }
116
+ classes = page.find("button[disabled]")[:class].split
117
+ assert_includes classes, "disabled:bg-muted!"
118
+ assert_includes classes, "disabled:text-muted-foreground!"
119
+ assert_includes classes, "disabled:border-input!"
120
+ end
121
+
122
+ def test_disabled_ghost_variant_keeps_transparent_bg
123
+ render_inline(UI::Button.new(variant: :ghost, disabled: true)) { "Off" }
124
+ classes = page.find("button[disabled]")[:class].split
125
+ assert_includes classes, "disabled:bg-transparent!"
126
+ assert_includes classes, "disabled:text-muted-foreground!"
127
+ end
128
+
129
+ def test_disabled_link_variant_drops_underline
130
+ render_inline(UI::Button.new(variant: :link, disabled: true)) { "Off" }
131
+ classes = page.find("button[disabled]")[:class].split
132
+ assert_includes classes, "disabled:text-muted-foreground!"
133
+ assert_includes classes, "disabled:no-underline!"
134
+ end
135
+
136
+ def test_default_button_is_not_disabled
137
+ render_inline(UI::Button.new) { "Active" }
138
+ btn = page.find("button")
139
+ refute btn.disabled?, "button should not be disabled by default"
140
+ end
141
+
142
+ def test_disabled_and_loading_both_disable
143
+ render_inline(UI::Button.new(disabled: true, loading: true)) { "Both" }
144
+ btn = page.find("button[disabled]")
145
+ assert btn.disabled?
146
+ end
147
+
148
+ def test_type_attribute_can_be_overridden
149
+ render_inline(UI::Button.new(type: :submit)) { "Submit" }
150
+ assert_selector "button[type='submit']", text: "Submit"
151
+ end
152
+
153
+ def test_user_class_overrides_variant_default
154
+ render_inline(UI::Button.new(class: "bg-red-500")) { "Custom" }
155
+ btn = page.find("button")
156
+ classes = btn[:class].split
157
+ refute_includes classes, "bg-primary"
158
+ assert_includes classes, "bg-red-500"
159
+ assert_includes classes, "text-primary-foreground"
160
+ end
161
+
162
+ def test_arbitrary_html_attributes_pass_through
163
+ render_inline(UI::Button.new(data: { test_id: "save-btn" }, id: "primary-cta")) { "Save" }
164
+ assert_selector "button#primary-cta[data-test-id='save-btn']", text: "Save"
165
+ end
166
+
167
+ def test_as_child_param_accepted_without_error
168
+ assert_nothing_raised do
169
+ render_inline(UI::Button.new(as_child: true)) { "Future" }
170
+ end
171
+ assert_selector "button", text: "Future"
172
+ end
173
+
174
+ # ── Icon slots ──────────────────────────────────────────────────────────────
175
+
176
+ def test_leading_icon_renders_before_content
177
+ render_inline(UI::Button.new) do |btn|
178
+ btn.with_leading_icon(name: "arrow-right")
179
+ "Continue"
180
+ end
181
+ btn = page.find("button")
182
+ icons = btn.all("i", visible: :all)
183
+ assert_equal 1, icons.size
184
+ assert_equal "arrow-right", icons.first["data-lucide"]
185
+ # icon precedes text in DOM order
186
+ assert_match(/<i[^>]*data-lucide="arrow-right".*Continue/m, btn.native.to_html)
187
+ end
188
+
189
+ def test_trailing_icon_renders_after_content
190
+ render_inline(UI::Button.new) do |btn|
191
+ btn.with_trailing_icon(name: "arrow-right")
192
+ "Next"
193
+ end
194
+ btn = page.find("button")
195
+ assert_match(/Next.*<i[^>]*data-lucide="arrow-right"/m, btn.native.to_html)
196
+ end
197
+
198
+ def test_both_leading_and_trailing_icons
199
+ render_inline(UI::Button.new) do |btn|
200
+ btn.with_leading_icon(name: "lock")
201
+ btn.with_trailing_icon(name: "arrow-right")
202
+ "Sign in"
203
+ end
204
+ btn = page.find("button")
205
+ icons = btn.all("i", visible: :all).map { |i| i["data-lucide"] }
206
+ assert_equal [ "lock", "arrow-right" ], icons
207
+ end
208
+
209
+ def test_icon_only_button_renders_icon_without_text_slot
210
+ render_inline(UI::Button.new(size: :icon)) do |btn|
211
+ btn.with_leading_icon(name: "menu")
212
+ end
213
+ btn = page.find("button")
214
+ classes = btn[:class].split
215
+ assert_includes classes, "h-9"
216
+ assert_includes classes, "w-9"
217
+ assert_equal "menu", btn.find("i", visible: :all)["data-lucide"]
218
+ end
219
+
220
+ def test_loading_replaces_leading_icon_with_animated_loader
221
+ render_inline(UI::Button.new(loading: true)) do |btn|
222
+ btn.with_leading_icon(name: "lock")
223
+ "Saving"
224
+ end
225
+ btn = page.find("button")
226
+ icons = btn.all("i", visible: :all)
227
+ assert_equal 1, icons.size
228
+ loader = icons.first
229
+ assert_equal "loader", loader["data-lucide"]
230
+ assert_includes loader[:class].split, "animate-spin"
231
+ end
232
+
233
+ def test_loading_hides_trailing_icon
234
+ render_inline(UI::Button.new(loading: true)) do |btn|
235
+ btn.with_trailing_icon(name: "arrow-right")
236
+ "Saving"
237
+ end
238
+ btn = page.find("button")
239
+ # only the loader is rendered; trailing icon dropped
240
+ icons = btn.all("i", visible: :all).map { |i| i["data-lucide"] }
241
+ assert_equal [ "loader" ], icons
242
+ end
243
+
244
+ def test_icon_slot_auto_sizes_to_button_size_sm
245
+ render_inline(UI::Button.new(size: :sm)) do |btn|
246
+ btn.with_leading_icon(name: "lock")
247
+ "Auth"
248
+ end
249
+ icon = page.find("button i", visible: :all)
250
+ classes = icon[:class].split
251
+ assert_includes classes, "w-3"
252
+ assert_includes classes, "h-3"
253
+ end
254
+
255
+ def test_icon_slot_auto_sizes_to_button_size_lg
256
+ render_inline(UI::Button.new(size: :lg)) do |btn|
257
+ btn.with_leading_icon(name: "lock")
258
+ "Auth"
259
+ end
260
+ icon = page.find("button i", visible: :all)
261
+ classes = icon[:class].split
262
+ assert_includes classes, "w-5"
263
+ assert_includes classes, "h-5"
264
+ end
265
+
266
+ def test_icon_slot_explicit_size_overrides_auto
267
+ render_inline(UI::Button.new(size: :sm)) do |btn|
268
+ btn.with_leading_icon(name: "lock", size: :xl)
269
+ "Big"
270
+ end
271
+ icon = page.find("button i", visible: :all)
272
+ classes = icon[:class].split
273
+ assert_includes classes, "w-8"
274
+ assert_includes classes, "h-8"
275
+ end
276
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::ButtonToTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_a_post_form_with_a_submit_button
10
+ render_inline(UI::ButtonTo.new("/session")) { "Continue" }
11
+ assert_selector "form[method='post'][action='/session']"
12
+ assert_selector "button[type='submit']", text: "Continue"
13
+ end
14
+
15
+ def test_inherits_button_variant_classes
16
+ render_inline(UI::ButtonTo.new("/session", variant: :outline)) { "Continue" }
17
+ classes = page.find("button")[:class].split
18
+ assert_includes classes, "border"
19
+ assert_includes classes, "border-input"
20
+ end
21
+
22
+ def test_full_width
23
+ render_inline(UI::ButtonTo.new("/session", full_width: true)) { "Continue" }
24
+ assert_includes page.find("button")[:class].split, "w-full"
25
+ end
26
+
27
+ def test_form_class_lands_on_the_form_not_the_button
28
+ render_inline(UI::ButtonTo.new("/session", form_class: "w-full")) { "Continue" }
29
+ assert_includes page.find("form")[:class].split, "w-full"
30
+ refute_includes page.find("button")[:class].split, "form-class"
31
+ end
32
+
33
+ # Velvet::ButtonTo inherited these slots but its template never rendered them.
34
+ def test_renders_leading_icon_slot
35
+ render_inline(UI::ButtonTo.new("/session")) do |button|
36
+ button.with_leading_icon(name: "mail")
37
+ "Continue"
38
+ end
39
+ assert_selector "button i[data-lucide='mail']"
40
+ end
41
+
42
+ def test_renders_trailing_icon_slot
43
+ render_inline(UI::ButtonTo.new("/session")) do |button|
44
+ button.with_trailing_icon(name: "arrow-right")
45
+ "Continue"
46
+ end
47
+ assert_selector "button i[data-lucide='arrow-right']"
48
+ end
49
+
50
+ def test_loading_state_shows_spinner_and_disables
51
+ render_inline(UI::ButtonTo.new("/session", loading: true)) { "Continue" }
52
+ button = page.find("button")
53
+ assert_equal "true", button[:"aria-busy"]
54
+ assert button[:disabled].present?
55
+ assert_selector "button .sr-only", text: "Loading, please wait"
56
+ end
57
+
58
+ def test_disabled
59
+ render_inline(UI::ButtonTo.new("/session", disabled: true)) { "Continue" }
60
+ assert page.find("button")[:disabled].present?
61
+ end
62
+
63
+ def test_user_class_override_via_tailwind_merge
64
+ render_inline(UI::ButtonTo.new("/session", class: "bg-red-500")) { "Continue" }
65
+ classes = page.find("button")[:class].split
66
+ assert_includes classes, "bg-red-500"
67
+ refute_includes classes, "bg-primary"
68
+ end
69
+
70
+ def test_html_attrs_passthrough
71
+ render_inline(UI::ButtonTo.new("/session", data: { turbo: false })) { "Continue" }
72
+ assert_selector "button[data-turbo='false']"
73
+ end
74
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::Card::ContentTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_div
10
+ render_inline(UI::Card::Content.new) { "Content" }
11
+ assert_selector "div", text: "Content"
12
+ end
13
+
14
+ def test_default_classes
15
+ render_inline(UI::Card::Content.new) { "x" }
16
+ classes = page.find("div")[:class].split
17
+ assert_includes classes, "p-6"
18
+ assert_includes classes, "pt-0"
19
+ end
20
+
21
+ def test_user_class_override
22
+ render_inline(UI::Card::Content.new(class: "p-4")) { "x" }
23
+ classes = page.find("div")[:class].split
24
+ refute_includes classes, "p-6"
25
+ assert_includes classes, "p-4"
26
+ end
27
+
28
+ def test_html_attrs_passthrough
29
+ render_inline(UI::Card::Content.new(id: "my-content", data: { test_id: "c" })) { "x" }
30
+ assert_selector "div#my-content[data-test-id='c']"
31
+ end
32
+
33
+ # Content sits flush under a header, but needs its own top padding when it
34
+ # is the first thing in the card.
35
+ def test_content_first_in_a_card_gets_top_padding
36
+ render_inline(UI::Card::Content.new) { "Body" }
37
+ classes = page.find("div")[:class].split
38
+ assert_includes classes, "pt-0"
39
+ assert_includes classes, "first:pt-6"
40
+ end
41
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::Card::DescriptionTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_p
10
+ render_inline(UI::Card::Description.new) { "Description" }
11
+ assert_selector "p", text: "Description"
12
+ end
13
+
14
+ def test_default_classes
15
+ render_inline(UI::Card::Description.new) { "x" }
16
+ classes = page.find("p")[:class].split
17
+ assert_includes classes, "text-sm"
18
+ assert_includes classes, "text-muted-foreground"
19
+ end
20
+
21
+ def test_user_class_override
22
+ render_inline(UI::Card::Description.new(class: "text-xs")) { "x" }
23
+ classes = page.find("p")[:class].split
24
+ refute_includes classes, "text-sm"
25
+ assert_includes classes, "text-xs"
26
+ end
27
+
28
+ def test_html_attrs_passthrough
29
+ render_inline(UI::Card::Description.new(id: "my-desc", data: { test_id: "d" })) { "x" }
30
+ assert_selector "p#my-desc[data-test-id='d']"
31
+ end
32
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::Card::FooterTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_div
10
+ render_inline(UI::Card::Footer.new) { "Footer" }
11
+ assert_selector "div", text: "Footer"
12
+ end
13
+
14
+ def test_default_classes
15
+ render_inline(UI::Card::Footer.new) { "x" }
16
+ classes = page.find("div")[:class].split
17
+ assert_includes classes, "flex"
18
+ assert_includes classes, "items-center"
19
+ assert_includes classes, "p-6"
20
+ assert_includes classes, "pt-0"
21
+ end
22
+
23
+ def test_user_class_override
24
+ render_inline(UI::Card::Footer.new(class: "p-4")) { "x" }
25
+ classes = page.find("div")[:class].split
26
+ refute_includes classes, "p-6"
27
+ assert_includes classes, "p-4"
28
+ end
29
+
30
+ def test_html_attrs_passthrough
31
+ render_inline(UI::Card::Footer.new(id: "my-footer", data: { test_id: "f" })) { "x" }
32
+ assert_selector "div#my-footer[data-test-id='f']"
33
+ end
34
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::Card::HeaderTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_div
10
+ render_inline(UI::Card::Header.new) { "Header" }
11
+ assert_selector "div", text: "Header"
12
+ end
13
+
14
+ def test_default_classes
15
+ render_inline(UI::Card::Header.new) { "x" }
16
+ classes = page.find("div")[:class].split
17
+ assert_includes classes, "flex"
18
+ assert_includes classes, "flex-col"
19
+ assert_includes classes, "gap-1.5"
20
+ assert_includes classes, "p-6"
21
+ end
22
+
23
+ def test_renders_with_title_slot
24
+ render_inline(UI::Card::Header.new) do |header|
25
+ header.with_title { "Title" }
26
+ end
27
+ assert_selector "h3", text: "Title"
28
+ end
29
+
30
+ def test_renders_with_description_slot
31
+ render_inline(UI::Card::Header.new) do |header|
32
+ header.with_description { "Description" }
33
+ end
34
+ assert_selector "p", text: "Description"
35
+ end
36
+
37
+ def test_renders_title_and_description_together
38
+ render_inline(UI::Card::Header.new) do |header|
39
+ header.with_title { "Title" }
40
+ header.with_description { "Description" }
41
+ end
42
+ header_el = page.find("div")
43
+ assert_equal "TitleDescription", header_el.text.gsub(/\s+/, "")
44
+ end
45
+
46
+ def test_user_class_override
47
+ render_inline(UI::Card::Header.new(class: "p-4")) { "x" }
48
+ classes = page.find("div")[:class].split
49
+ refute_includes classes, "p-6"
50
+ assert_includes classes, "p-4"
51
+ end
52
+
53
+ def test_html_attrs_passthrough
54
+ render_inline(UI::Card::Header.new(id: "my-header", data: { test_id: "hdr" })) { "x" }
55
+ assert_selector "div#my-header[data-test-id='hdr']"
56
+ end
57
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::Card::TitleTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_h3
10
+ render_inline(UI::Card::Title.new) { "Title" }
11
+ assert_selector "h3", text: "Title"
12
+ end
13
+
14
+ def test_default_classes
15
+ render_inline(UI::Card::Title.new) { "x" }
16
+ classes = page.find("h3")[:class].split
17
+ assert_includes classes, "text-lg"
18
+ assert_includes classes, "font-semibold"
19
+ assert_includes classes, "leading-none"
20
+ assert_includes classes, "tracking-tight"
21
+ end
22
+
23
+ def test_user_class_override
24
+ render_inline(UI::Card::Title.new(class: "text-xl")) { "x" }
25
+ classes = page.find("h3")[:class].split
26
+ refute_includes classes, "text-lg"
27
+ assert_includes classes, "text-xl"
28
+ end
29
+
30
+ def test_html_attrs_passthrough
31
+ render_inline(UI::Card::Title.new(id: "my-title", data: { test_id: "t" })) { "x" }
32
+ assert_selector "h3#my-title[data-test-id='t']"
33
+ end
34
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::CardTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_default_card
10
+ render_inline(UI::Card.new) { "Card content" }
11
+ assert_selector "div", text: "Card content"
12
+ end
13
+
14
+ def test_root_classes
15
+ render_inline(UI::Card.new) { "x" }
16
+ classes = page.find("div")[:class].split
17
+ assert_includes classes, "rounded-xl"
18
+ assert_includes classes, "border"
19
+ assert_includes classes, "bg-card"
20
+ assert_includes classes, "text-card-foreground"
21
+ assert_includes classes, "shadow"
22
+ end
23
+
24
+ def test_renders_with_header_slot
25
+ render_inline(UI::Card.new) do |card|
26
+ card.with_header { "Header" }
27
+ "Body"
28
+ end
29
+ assert_selector "div div", text: "Header"
30
+ end
31
+
32
+ def test_renders_with_content_slot
33
+ render_inline(UI::Card.new) do |card|
34
+ card.with_content { "Content" }
35
+ end
36
+ assert_selector "div div", text: "Content"
37
+ end
38
+
39
+ def test_renders_with_footer_slot
40
+ render_inline(UI::Card.new) do |card|
41
+ card.with_footer { "Footer" }
42
+ end
43
+ assert_selector "div div", text: "Footer"
44
+ end
45
+
46
+ def test_renders_all_slots_together
47
+ render_inline(UI::Card.new(id: "all-slots")) do |card|
48
+ card.with_header { "Header" }
49
+ card.with_content { "Content" }
50
+ card.with_footer { "Footer" }
51
+ end
52
+ card_el = page.find("div#all-slots")
53
+ assert_equal "HeaderContentFooter", card_el.text.gsub(/\s+/, "")
54
+ end
55
+
56
+ def test_user_class_override_via_tailwind_merge
57
+ render_inline(UI::Card.new(class: "bg-red-500")) { "x" }
58
+ classes = page.find("div")[:class].split
59
+ refute_includes classes, "bg-card"
60
+ assert_includes classes, "bg-red-500"
61
+ end
62
+
63
+ def test_html_attrs_passthrough
64
+ render_inline(UI::Card.new(id: "my-card", data: { test_id: "card-x" })) { "x" }
65
+ assert_selector "div#my-card[data-test-id='card-x']"
66
+ end
67
+ end