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,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::Dialog::TriggerTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def render_dialog(trigger_args = {}, &block)
10
+ render_inline(UI::Dialog.new(id: "profile-dialog")) do |dlg|
11
+ dlg.with_trigger(**trigger_args) { "Open" }
12
+ dlg.with_content { |c| c.with_title("Hi"); block&.call(c) }
13
+ end
14
+ end
15
+
16
+ def test_default_renders_ui_button
17
+ render_dialog
18
+ btn = page.find("[data-ui--dialog-target='trigger']")
19
+ assert_equal "button", btn.tag_name
20
+ assert_equal "button", btn[:type]
21
+ assert_equal "Open", btn.text.strip
22
+ classes = btn[:class].split
23
+ assert_includes classes, "bg-primary"
24
+ assert_includes classes, "h-9"
25
+ end
26
+
27
+ def test_variant_option_passed_to_button
28
+ render_dialog(variant: :outline)
29
+ btn = page.find("[data-ui--dialog-target='trigger']")
30
+ assert_includes btn[:class], "border-input"
31
+ end
32
+
33
+ def test_size_option_passed_to_button
34
+ render_dialog(size: :sm)
35
+ btn = page.find("[data-ui--dialog-target='trigger']")
36
+ assert_includes btn[:class].split, "h-8"
37
+ end
38
+
39
+ def test_as_renders_alternate_tag
40
+ render_dialog(as: :a, href: "#open")
41
+ el = page.find("[data-ui--dialog-target='trigger']")
42
+ assert_equal "a", el.tag_name
43
+ assert_equal "#open", el[:href]
44
+ end
45
+
46
+ def test_data_action_wires_open
47
+ render_dialog
48
+ action = page.find("[data-ui--dialog-target='trigger']")["data-action"]
49
+ assert_match(/click->ui--dialog#open/, action)
50
+ end
51
+
52
+ def test_aria_attributes_link_to_dialog
53
+ render_dialog
54
+ btn = page.find("[data-ui--dialog-target='trigger']")
55
+ dialog = page.find("dialog", visible: :all)
56
+ assert_equal "dialog", btn["aria-haspopup"]
57
+ assert_equal "false", btn["aria-expanded"]
58
+ assert_equal dialog[:id], btn["aria-controls"]
59
+ assert_equal "profile-dialog", dialog[:id]
60
+ end
61
+
62
+ def test_user_class_merge
63
+ render_dialog(class: "px-8")
64
+ classes = page.find("[data-ui--dialog-target='trigger']")[:class].split
65
+ assert_includes classes, "px-8"
66
+ end
67
+
68
+ def test_caller_data_action_is_preserved_alongside_open_action
69
+ render_dialog(data: { action: "mouseenter->something#hover" })
70
+ action = page.find("[data-ui--dialog-target='trigger']")["data-action"]
71
+ assert_match(/mouseenter->something#hover/, action)
72
+ assert_match(/click->ui--dialog#open/, action)
73
+ end
74
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::DialogTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def render_dialog(**opts)
10
+ render_inline(UI::Dialog.new(**opts)) do |dialog|
11
+ dialog.with_trigger { "Open dialog" }
12
+ dialog.with_content do |content|
13
+ content.with_title { "Edit profile" }
14
+ content.with_description { "Make changes to your profile here." }
15
+ "Dialog body"
16
+ end
17
+ end
18
+ end
19
+
20
+ def test_root_carries_controller
21
+ render_dialog
22
+ assert_selector "[data-controller~='ui--dialog']"
23
+ end
24
+
25
+ def test_dialog_element_has_generated_id
26
+ render_dialog
27
+ dialog = page.find("dialog", visible: :all)
28
+ assert dialog[:id].present?
29
+ assert dialog[:id].start_with?("ui-dialog-")
30
+ end
31
+
32
+ def test_trigger_and_content_render
33
+ result = render_dialog
34
+ assert_text "Open dialog"
35
+ assert result.to_html.include?("Dialog body")
36
+ end
37
+
38
+ def test_custom_id_lands_on_dialog_element
39
+ render_dialog(id: "profile-dialog")
40
+ assert_selector "dialog#profile-dialog", visible: :all
41
+ end
42
+
43
+ def test_user_class_merge
44
+ render_dialog(class: "my-dialog-root")
45
+ classes = page.find("[data-controller~='ui--dialog']")[:class].split
46
+ assert_includes classes, "my-dialog-root"
47
+ assert_includes classes, "contents"
48
+ end
49
+
50
+ def test_html_attrs_passthrough
51
+ render_dialog(data: { test_id: "dialog-x" })
52
+ assert_selector "[data-controller~='ui--dialog'][data-test-id='dialog-x']"
53
+ end
54
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::DividerTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_horizontal_hr_by_default
10
+ render_inline(UI::Divider.new)
11
+ hr = page.find("hr")
12
+ classes = hr[:class].split
13
+ assert_includes classes, "border-t"
14
+ assert_includes classes, "w-full"
15
+ end
16
+
17
+ def test_renders_vertical_orientation
18
+ render_inline(UI::Divider.new(orientation: :vertical))
19
+ hr = page.find("hr")
20
+ classes = hr[:class].split
21
+ assert_includes classes, "border-l"
22
+ assert_includes classes, "self-stretch"
23
+ assert_includes classes, "h-full"
24
+ end
25
+
26
+ def test_with_label_renders_wrapper_div
27
+ render_inline(UI::Divider.new(label: "OR"))
28
+ assert_selector "div.flex"
29
+ assert_selector "span", text: "OR"
30
+ end
31
+
32
+ def test_label_has_muted_foreground_color
33
+ render_inline(UI::Divider.new(label: "Section"))
34
+ span = page.find("span")
35
+ classes = span[:class].split
36
+ assert_includes classes, "text-muted-foreground"
37
+ assert_includes classes, "text-xs"
38
+ end
39
+
40
+ def test_user_class_merge
41
+ render_inline(UI::Divider.new(class: "my-4"))
42
+ hr = page.find("hr")
43
+ classes = hr[:class].split
44
+ assert_includes classes, "my-4"
45
+ end
46
+
47
+ def test_html_attrs_passthrough
48
+ render_inline(UI::Divider.new(data: { test_id: "divider-x" }))
49
+ assert_selector "hr[data-test-id='divider-x']"
50
+ end
51
+
52
+ def test_label_wrapper_includes_user_class
53
+ render_inline(UI::Divider.new(label: "OR", class: "gap-4"))
54
+ div = page.find("div")
55
+ classes = div[:class].split
56
+ assert_includes classes, "gap-4"
57
+ assert_includes classes, "flex"
58
+ end
59
+
60
+ def test_labelled_rules_share_the_row_instead_of_each_taking_it_all
61
+ render_inline(UI::Divider.new(label: "OR"))
62
+ rules = page.all("hr")
63
+ assert_equal 2, rules.size
64
+ rules.each do |rule|
65
+ assert_includes rule[:class].split, "flex-1"
66
+ refute_includes rule[:class].split, "w-full"
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::IconTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_i_tag_with_lucide_data_attribute
10
+ render_inline UI::Icon.new(name: "menu")
11
+ icon = page.find("i", visible: :all)
12
+ assert_equal "menu", icon["data-lucide"]
13
+ end
14
+
15
+ def test_default_size_classes
16
+ render_inline UI::Icon.new(name: "menu")
17
+ icon = page.find("i", visible: :all)
18
+ classes = icon[:class].split
19
+ assert_includes classes, "w-5"
20
+ assert_includes classes, "h-5"
21
+ end
22
+
23
+ def test_size_xs
24
+ render_inline UI::Icon.new(name: "menu", size: :xs)
25
+ icon = page.find("i", visible: :all)
26
+ classes = icon[:class].split
27
+ assert_includes classes, "w-3"
28
+ assert_includes classes, "h-3"
29
+ end
30
+
31
+ def test_size_sm
32
+ render_inline UI::Icon.new(name: "menu", size: :sm)
33
+ classes = page.find("i", visible: :all)[:class].split
34
+ assert_includes classes, "w-4"
35
+ assert_includes classes, "h-4"
36
+ end
37
+
38
+ def test_size_lg
39
+ render_inline UI::Icon.new(name: "menu", size: :lg)
40
+ classes = page.find("i", visible: :all)[:class].split
41
+ assert_includes classes, "w-6"
42
+ assert_includes classes, "h-6"
43
+ end
44
+
45
+ def test_size_xl
46
+ render_inline UI::Icon.new(name: "menu", size: :xl)
47
+ classes = page.find("i", visible: :all)[:class].split
48
+ assert_includes classes, "w-8"
49
+ assert_includes classes, "h-8"
50
+ end
51
+
52
+ def test_user_class_merges
53
+ render_inline UI::Icon.new(name: "menu", class: "text-red-500")
54
+ classes = page.find("i", visible: :all)[:class].split
55
+ assert_includes classes, "text-red-500"
56
+ assert_includes classes, "w-5"
57
+ end
58
+
59
+ def test_renders_nothing_when_name_blank
60
+ render_inline UI::Icon.new(name: "")
61
+ assert_no_selector "i", visible: :all
62
+ end
63
+
64
+ def test_renders_nothing_when_name_nil
65
+ render_inline UI::Icon.new(name: nil)
66
+ assert_no_selector "i", visible: :all
67
+ end
68
+
69
+ def test_html_attrs_pass_through
70
+ render_inline UI::Icon.new(name: "menu", id: "header-menu", aria: { label: "Open menu" })
71
+ icon = page.find("i", visible: :all)
72
+ assert_equal "header-menu", icon[:id]
73
+ assert_equal "Open menu", icon["aria-label"]
74
+ end
75
+
76
+ def test_data_attributes_keep_the_icon_name
77
+ render_inline(UI::Icon.new(name: "copy", data: { clipboard_target: "idle" }))
78
+ icon = page.find("i", visible: :all)
79
+ assert_equal "copy", icon["data-lucide"]
80
+ assert_equal "idle", icon["data-clipboard-target"]
81
+ end
82
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::ImageTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_img_with_asset_path
10
+ render_inline(UI::Image.new(src: "brand-logo.svg"))
11
+ assert_selector "img"
12
+ assert_match %r{/(assets/)?brand-logo}, page.find("img")[:src]
13
+ end
14
+
15
+ def test_default_rounded_and_base_classes
16
+ render_inline(UI::Image.new(src: "brand-logo.svg"))
17
+ classes = page.find("img")[:class].split
18
+ assert_includes classes, "rounded-sm"
19
+ assert_includes classes, "block"
20
+ assert_includes classes, "max-w-full"
21
+ end
22
+
23
+ def test_rounded_variant
24
+ render_inline(UI::Image.new(src: "brand-logo.svg", rounded: :lg))
25
+ classes = page.find("img")[:class].split
26
+ assert_includes classes, "rounded-lg"
27
+ refute_includes classes, "rounded-sm"
28
+ end
29
+
30
+ # Velvet sized the wrapper div instead of the image; these must land on <img>.
31
+ def test_width_and_height_apply_to_the_image
32
+ render_inline(UI::Image.new(src: "brand-logo.svg", width: "14px", height: "20px"))
33
+ style = page.find("img")[:style]
34
+ assert_includes style, "width: 14px"
35
+ assert_includes style, "height: 20px"
36
+ end
37
+
38
+ def test_numeric_dimensions_get_px_units
39
+ render_inline(UI::Image.new(src: "brand-logo.svg", width: 42))
40
+ assert_includes page.find("img")[:style], "width: 42px"
41
+ end
42
+
43
+ def test_omits_style_when_no_dimensions_given
44
+ render_inline(UI::Image.new(src: "brand-logo.svg"))
45
+ assert_nil page.find("img")[:style].presence
46
+ end
47
+
48
+ def test_alt_defaults_to_humanized_filename
49
+ render_inline(UI::Image.new(src: "brand-logo.png"))
50
+ assert_equal "Brand logo", page.find("img")[:alt]
51
+ end
52
+
53
+ def test_explicit_alt_wins
54
+ render_inline(UI::Image.new(src: "brand-logo.png", alt: "Brand logo"))
55
+ assert_equal "Brand logo", page.find("img")[:alt]
56
+ end
57
+
58
+ def test_lazy_loading_by_default
59
+ render_inline(UI::Image.new(src: "brand-logo.svg"))
60
+ assert_equal "lazy", page.find("img")[:loading]
61
+ end
62
+
63
+ def test_user_class_override_via_tailwind_merge
64
+ render_inline(UI::Image.new(src: "brand-logo.svg", rounded: :lg, class: "rounded-none"))
65
+ classes = page.find("img")[:class].split
66
+ assert_includes classes, "rounded-none"
67
+ refute_includes classes, "rounded-lg"
68
+ end
69
+
70
+ def test_html_attrs_passthrough
71
+ render_inline(UI::Image.new(src: "brand-logo.svg", id: "logo", data: { test_id: "img" }))
72
+ assert_selector "img#logo[data-test-id='img']"
73
+ end
74
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::InputGroupTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ # ── Wrapper ──────────────────────────────────────────────────────────────────
10
+
11
+ def test_renders_wrapper_div_with_group_role
12
+ render_inline(UI::InputGroup.new) do |g|
13
+ g.with_field(placeholder: "Search")
14
+ end
15
+ assert_selector "div[role='group'][data-slot='input-group']"
16
+ end
17
+
18
+ def test_wrapper_has_border_class
19
+ render_inline(UI::InputGroup.new) do |g|
20
+ g.with_field(placeholder: "x")
21
+ end
22
+ wrapper = page.find("div[data-slot='input-group']")
23
+ assert_includes wrapper[:class], "border-input"
24
+ end
25
+
26
+ # ── Field slot ───────────────────────────────────────────────────────────────
27
+
28
+ def test_field_renders_input_inside_wrapper
29
+ render_inline(UI::InputGroup.new) do |g|
30
+ g.with_field(placeholder: "Email")
31
+ end
32
+ assert_selector "div[data-slot='input-group'] input[placeholder='Email']"
33
+ end
34
+
35
+ def test_field_input_has_no_border
36
+ render_inline(UI::InputGroup.new) do |g|
37
+ g.with_field(placeholder: "x")
38
+ end
39
+ inp = page.find("input")
40
+ classes = inp[:class].split
41
+ assert_includes classes, "border-0!"
42
+ end
43
+
44
+ def test_field_input_has_transparent_background
45
+ render_inline(UI::InputGroup.new) do |g|
46
+ g.with_field(placeholder: "x")
47
+ end
48
+ inp = page.find("input")
49
+ assert_includes inp[:class], "bg-transparent"
50
+ end
51
+
52
+ def test_field_input_has_data_slot
53
+ render_inline(UI::InputGroup.new) do |g|
54
+ g.with_field(placeholder: "x")
55
+ end
56
+ assert_selector "input[data-slot='input-group-control']"
57
+ end
58
+
59
+ def test_field_forwards_type
60
+ render_inline(UI::InputGroup.new) do |g|
61
+ g.with_field(type: :email, placeholder: "Email")
62
+ end
63
+ assert_selector "input[type='email']"
64
+ end
65
+
66
+ def test_field_error_sets_aria_invalid
67
+ render_inline(UI::InputGroup.new) do |g|
68
+ g.with_field(error: true, placeholder: "x")
69
+ end
70
+ assert_selector "input[aria-invalid='true']"
71
+ end
72
+
73
+ def test_field_error_adds_destructive_class_to_wrapper
74
+ render_inline(UI::InputGroup.new) do |g|
75
+ g.with_field(error: true, placeholder: "x")
76
+ end
77
+ wrapper = page.find("div[data-slot='input-group']")
78
+ assert_includes wrapper[:class], "border-destructive"
79
+ end
80
+
81
+ def test_field_disabled_sets_disabled_on_input
82
+ render_inline(UI::InputGroup.new) do |g|
83
+ g.with_field(disabled: true, placeholder: "x")
84
+ end
85
+ assert_selector "input[disabled]"
86
+ end
87
+
88
+ # ── Addon slot ───────────────────────────────────────────────────────────────
89
+
90
+ def test_addon_renders_with_data_slot
91
+ render_inline(UI::InputGroup.new) do |g|
92
+ g.with_addon(align: "inline-start") { "@" }
93
+ g.with_field(placeholder: "x")
94
+ end
95
+ assert_selector "[data-slot='input-group-addon']"
96
+ end
97
+
98
+ def test_addon_inline_start_has_order_first
99
+ render_inline(UI::InputGroup.new) do |g|
100
+ g.with_addon(align: "inline-start") { "pre" }
101
+ g.with_field(placeholder: "x")
102
+ end
103
+ addon = page.find("[data-slot='input-group-addon']")
104
+ assert_includes addon[:class], "order-first"
105
+ end
106
+
107
+ def test_addon_inline_end_has_order_last
108
+ render_inline(UI::InputGroup.new) do |g|
109
+ g.with_field(placeholder: "x")
110
+ g.with_addon(align: "inline-end") { "post" }
111
+ end
112
+ addon = page.find("[data-slot='input-group-addon']")
113
+ assert_includes addon[:class], "order-last"
114
+ end
115
+
116
+ def test_addon_renders_icon_sub_slot
117
+ render_inline(UI::InputGroup.new) do |g|
118
+ g.with_addon(align: "inline-start") do |addon|
119
+ addon.with_icon(name: "mail")
120
+ end
121
+ g.with_field(placeholder: "Email")
122
+ end
123
+ assert_selector "[data-slot='input-group-addon'] i[data-lucide='mail']"
124
+ end
125
+
126
+ def test_multiple_addons
127
+ render_inline(UI::InputGroup.new) do |g|
128
+ g.with_addon(align: "inline-start") { "$" }
129
+ g.with_addon(align: "inline-end") { ".00" }
130
+ g.with_field(placeholder: "Amount")
131
+ end
132
+ assert_equal 2, page.all("[data-slot='input-group-addon']").size
133
+ end
134
+
135
+ # ── html_attrs passthrough ───────────────────────────────────────────────────
136
+
137
+ def test_wrapper_html_attrs_pass_through
138
+ render_inline(UI::InputGroup.new(id: "my-group")) do |g|
139
+ g.with_field(placeholder: "x")
140
+ end
141
+ assert_selector "div#my-group[data-slot='input-group']"
142
+ end
143
+ end