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,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::AlertTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_div_with_role_alert
10
+ render_inline(UI::Alert.new) { "Default alert" }
11
+ div = page.find("[role='alert']")
12
+ assert div.present?
13
+ end
14
+
15
+ def test_default_variant_classes
16
+ render_inline(UI::Alert.new) { "x" }
17
+ classes = page.find("[role='alert']")[:class].split
18
+ assert_includes classes, "bg-background"
19
+ assert_includes classes, "text-foreground"
20
+ assert_includes classes, "border-input"
21
+ end
22
+
23
+ def test_destructive_variant
24
+ render_inline(UI::Alert.new(variant: :destructive)) { "Error" }
25
+ classes = page.find("[role='alert']")[:class].split
26
+ assert_includes classes, "bg-destructive/10"
27
+ assert_includes classes, "text-destructive"
28
+ assert_includes classes, "border-destructive/30"
29
+ end
30
+
31
+ def test_warning_variant
32
+ render_inline(UI::Alert.new(variant: :warning)) { "Warn" }
33
+ classes = page.find("[role='alert']")[:class].split
34
+ assert_includes classes, "bg-amber-50"
35
+ assert_includes classes, "text-amber-800"
36
+ assert_includes classes, "border-amber-300"
37
+ end
38
+
39
+ def test_success_variant
40
+ render_inline(UI::Alert.new(variant: :success)) { "OK" }
41
+ classes = page.find("[role='alert']")[:class].split
42
+ assert_includes classes, "bg-emerald-50"
43
+ assert_includes classes, "text-emerald-800"
44
+ assert_includes classes, "border-emerald-300"
45
+ end
46
+
47
+ def test_info_variant
48
+ render_inline(UI::Alert.new(variant: :info)) { "Info" }
49
+ classes = page.find("[role='alert']")[:class].split
50
+ assert_includes classes, "bg-sky-50"
51
+ assert_includes classes, "text-sky-800"
52
+ assert_includes classes, "border-sky-300"
53
+ end
54
+
55
+ def test_default_variant_renders_default_icon_info
56
+ render_inline(UI::Alert.new) { "Default" }
57
+ icons = page.all("i", visible: :all)
58
+ assert_equal 1, icons.size
59
+ assert_equal "info", icons.first["data-lucide"]
60
+ end
61
+
62
+ def test_destructive_variant_renders_circle_alert_icon
63
+ render_inline(UI::Alert.new(variant: :destructive)) { "Error" }
64
+ icons = page.all("i", visible: :all)
65
+ assert_equal 1, icons.size
66
+ assert_equal "circle-alert", icons.first["data-lucide"]
67
+ end
68
+
69
+ def test_success_variant_renders_circle_check_icon
70
+ render_inline(UI::Alert.new(variant: :success)) { "Success" }
71
+ icons = page.all("i", visible: :all)
72
+ assert_equal 1, icons.size
73
+ assert_equal "circle-check", icons.first["data-lucide"]
74
+ end
75
+
76
+ def test_custom_icon_slot_overrides_default
77
+ render_inline(UI::Alert.new(variant: :default)) do |alert|
78
+ alert.with_icon(name: "badge-check")
79
+ "Custom icon"
80
+ end
81
+ icons = page.all("i", visible: :all)
82
+ assert_equal 1, icons.size
83
+ assert_equal "badge-check", icons.first["data-lucide"]
84
+ end
85
+
86
+ def test_title_slot_renders
87
+ render_inline(UI::Alert.new) do |alert|
88
+ alert.with_title { "Heads up" }
89
+ end
90
+ assert_selector "[role='alert'] h5", text: "Heads up"
91
+ end
92
+
93
+ def test_description_slot_renders
94
+ render_inline(UI::Alert.new) do |alert|
95
+ alert.with_description { "Description text" }
96
+ end
97
+ assert_selector "[role='alert'] div", text: "Description text"
98
+ end
99
+
100
+ def test_title_and_description_together
101
+ render_inline(UI::Alert.new) do |alert|
102
+ alert.with_title { "Title" }
103
+ alert.with_description { "Body" }
104
+ end
105
+ alert_el = page.find("[role='alert']")
106
+ assert_equal "TitleBody", alert_el.text.gsub(/\s+/, "")
107
+ end
108
+
109
+ def test_user_class_override_via_tailwind_merge
110
+ render_inline(UI::Alert.new(class: "bg-red-500")) { "Custom" }
111
+ classes = page.find("[role='alert']")[:class].split
112
+ refute_includes classes, "bg-background"
113
+ assert_includes classes, "bg-red-500"
114
+ end
115
+
116
+ def test_description_renders_html_content
117
+ render_inline(UI::Alert.new) do |alert|
118
+ alert.with_description { "Click <a href='/docs'>here</a> for details".html_safe }
119
+ end
120
+ assert_selector "[role='alert'] a[href='/docs']", text: "here"
121
+ end
122
+
123
+ def test_html_attrs_passthrough
124
+ render_inline(UI::Alert.new(id: "my-alert", data: { test_id: "alert-x" })) { "x" }
125
+ assert_selector "[role='alert']#my-alert[data-test-id='alert-x']"
126
+ end
127
+
128
+ def test_base_class_structure
129
+ render_inline(UI::Alert.new) { "x" }
130
+ classes = page.find("[role='alert']")[:class].split
131
+ assert_includes classes, "rounded-md"
132
+ assert_includes classes, "border"
133
+ assert_includes classes, "p-4"
134
+ # Layout switched from grid to flex (icon + content row).
135
+ assert_includes classes, "flex"
136
+ assert_includes classes, "items-start"
137
+ end
138
+
139
+ # Dismissible close button
140
+
141
+ def test_dismissible_false_does_not_render_close_button
142
+ render_inline(UI::Alert.new(dismissible: false)) { "Not dismissible" }
143
+ refute_selector "[role='alert'] button"
144
+ end
145
+
146
+ def test_dismissible_true_renders_close_button
147
+ render_inline(UI::Alert.new(dismissible: true)) { "Dismissible" }
148
+ assert_selector "[role='alert'] button[aria-label='Close alert']"
149
+ end
150
+
151
+ def test_close_button_renders_x_icon
152
+ render_inline(UI::Alert.new(dismissible: true)) { "Dismissible" }
153
+ icons = page.all("[role='alert'] button i")
154
+ assert_equal 1, icons.size
155
+ assert_equal "x", icons.first["data-lucide"]
156
+ end
157
+
158
+ def test_close_button_has_dismiss_action
159
+ render_inline(UI::Alert.new(dismissible: true)) { "Dismissible" }
160
+ button = page.find("[role='alert'] button")
161
+ assert_includes button["data-action"], "click->ui--alert#dismiss"
162
+ end
163
+
164
+ def test_close_button_is_positioned_at_top_right
165
+ render_inline(UI::Alert.new(dismissible: true)) { "Dismissible" }
166
+ button = page.find("[role='alert'] button")
167
+ classes = button[:class].split
168
+ assert_includes classes, "shrink-0"
169
+ assert_includes classes, "-mr-1"
170
+ assert_includes classes, "-mt-1"
171
+ end
172
+
173
+ def test_dismissible_alert_has_data_controller
174
+ render_inline(UI::Alert.new(dismissible: true)) { "Dismissible" }
175
+ alert = page.find("[role='alert']")
176
+ assert_equal "ui--alert", alert["data-controller"]
177
+ end
178
+
179
+ def test_non_dismissible_alert_has_no_data_controller
180
+ render_inline(UI::Alert.new(dismissible: false)) { "Not dismissible" }
181
+ alert = page.find("[role='alert']")
182
+ refute alert["data-controller"].present?
183
+ end
184
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::AvatarGroupTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_list_container
10
+ render_inline(UI::AvatarGroup.new) do |group|
11
+ group.with_avatar { "A" }
12
+ group.with_avatar { "B" }
13
+ end
14
+ assert_selector "div[role='list']"
15
+ end
16
+
17
+ def test_avatars_have_listitem_role
18
+ render_inline(UI::AvatarGroup.new) do |group|
19
+ group.with_avatar { "A" }
20
+ end
21
+ assert_selector "[data-slot='avatar'][role='listitem']"
22
+ end
23
+
24
+ def test_renders_all_avatars
25
+ render_inline(UI::AvatarGroup.new) do |group|
26
+ group.with_avatar { "A" }
27
+ group.with_avatar { "B" }
28
+ group.with_avatar { "C" }
29
+ end
30
+ avatars = page.all("[data-slot='avatar']")
31
+ assert_equal 3, avatars.size
32
+ end
33
+
34
+ def test_overflow_has_aria_label
35
+ render_inline(UI::AvatarGroup.new(max: 2)) do |group|
36
+ group.with_avatar { "A" }
37
+ group.with_avatar { "B" }
38
+ group.with_avatar { "C" }
39
+ group.with_avatar { "D" }
40
+ end
41
+ overflow = page.find("span[aria-label]")
42
+ assert_equal "2 more users", overflow["aria-label"]
43
+ end
44
+
45
+ def test_overflow_visible_count
46
+ render_inline(UI::AvatarGroup.new(max: 2)) do |group|
47
+ group.with_avatar { "A" }
48
+ group.with_avatar { "B" }
49
+ group.with_avatar { "C" }
50
+ group.with_avatar { "D" }
51
+ end
52
+ avatars = page.all("[data-slot='avatar']")
53
+ assert_equal 2, avatars.size
54
+ assert_selector "span", text: "+2"
55
+ end
56
+
57
+ def test_no_overflow_when_within_max
58
+ render_inline(UI::AvatarGroup.new(max: 5)) do |group|
59
+ group.with_avatar { "A" }
60
+ group.with_avatar { "B" }
61
+ end
62
+ refute_selector "span[aria-label]"
63
+ end
64
+
65
+ def test_user_class_merge
66
+ render_inline(UI::AvatarGroup.new(class: "my-group")) do |group|
67
+ group.with_avatar { "A" }
68
+ end
69
+ classes = page.find("div[role='list']")[:class].split
70
+ assert_includes classes, "my-group"
71
+ end
72
+
73
+ def test_html_attrs_passthrough
74
+ render_inline(UI::AvatarGroup.new(data: { test_id: "grp" })) do |group|
75
+ group.with_avatar { "A" }
76
+ end
77
+ assert_selector "div[data-test-id='grp']"
78
+ end
79
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::AvatarTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def avatar_el
10
+ page.find("[data-slot='avatar']")
11
+ end
12
+
13
+ def test_renders_span
14
+ render_inline(UI::Avatar.new) { "JD" }
15
+ assert avatar_el.present?
16
+ end
17
+
18
+ def test_default_shape_is_circle
19
+ render_inline(UI::Avatar.new) { "JD" }
20
+ classes = avatar_el[:class].split
21
+ assert_includes classes, "rounded-full"
22
+ end
23
+
24
+ def test_rounded_shape
25
+ render_inline(UI::Avatar.new(shape: :rounded)) { "PJ" }
26
+ classes = avatar_el[:class].split
27
+ assert_includes classes, "rounded-md"
28
+ end
29
+
30
+ def test_sm_size
31
+ render_inline(UI::Avatar.new(size: :sm)) { "JD" }
32
+ classes = avatar_el[:class].split
33
+ assert_includes classes, "h-6"
34
+ assert_includes classes, "w-6"
35
+ end
36
+
37
+ def test_default_size
38
+ render_inline(UI::Avatar.new) { "JD" }
39
+ classes = avatar_el[:class].split
40
+ assert_includes classes, "h-10"
41
+ assert_includes classes, "w-10"
42
+ end
43
+
44
+ def test_default_color
45
+ render_inline(UI::Avatar.new) { "JD" }
46
+ classes = avatar_el[:class].split
47
+ assert_includes classes, "bg-muted"
48
+ assert_includes classes, "text-foreground/70"
49
+ end
50
+
51
+ def test_primary_color
52
+ render_inline(UI::Avatar.new(color: :primary)) { "JD" }
53
+ classes = avatar_el[:class].split
54
+ assert_includes classes, "bg-primary"
55
+ assert_includes classes, "text-primary-foreground"
56
+ end
57
+
58
+ def test_destructive_color
59
+ render_inline(UI::Avatar.new(color: :destructive)) { "JD" }
60
+ classes = avatar_el[:class].split
61
+ assert_includes classes, "bg-destructive"
62
+ assert_includes classes, "text-white"
63
+ end
64
+
65
+ def test_amber_color
66
+ render_inline(UI::Avatar.new(color: :amber)) { "JD" }
67
+ classes = avatar_el[:class].split
68
+ assert_includes classes, "bg-amber-500"
69
+ assert_includes classes, "text-white"
70
+ end
71
+
72
+ def test_fallback_is_aria_hidden
73
+ render_inline(UI::Avatar.new) { "JD" }
74
+ fallback = avatar_el.find("span")
75
+ assert_equal "true", fallback["aria-hidden"]
76
+ end
77
+
78
+ def test_standalone_with_alt_gets_role_img
79
+ render_inline(UI::Avatar.new(alt: "John Doe")) { "JD" }
80
+ assert_equal "img", avatar_el[:role]
81
+ assert_equal "John Doe", avatar_el["aria-label"]
82
+ end
83
+
84
+ def test_standalone_without_alt_has_no_role
85
+ render_inline(UI::Avatar.new) { "JD" }
86
+ refute avatar_el[:role].present?
87
+ end
88
+
89
+ def test_image_slot
90
+ render_inline(UI::Avatar.new) do |avatar|
91
+ avatar.with_image(src: "https://example.com/avatar.jpg", alt: "Jane Doe")
92
+ end
93
+ assert_selector "[data-slot='avatar'] img[src='https://example.com/avatar.jpg'][alt='Jane Doe']"
94
+ end
95
+
96
+ def test_image_prevents_fallback
97
+ render_inline(UI::Avatar.new) do |avatar|
98
+ avatar.with_image(src: "https://example.com/avatar.jpg")
99
+ end
100
+ assert_selector "[data-slot='avatar'] img"
101
+ assert_equal 0, avatar_el.all("span").size
102
+ end
103
+
104
+ def test_fallback_slot_overrides_default
105
+ render_inline(UI::Avatar.new) do |avatar|
106
+ avatar.with_fallback { "FB" }
107
+ end
108
+ assert_selector "[data-slot='avatar'] > span", text: "FB"
109
+ end
110
+
111
+ def test_user_class_merge
112
+ render_inline(UI::Avatar.new(class: "border-2")) { "JD" }
113
+ classes = avatar_el[:class].split
114
+ assert_includes classes, "border-2"
115
+ end
116
+
117
+ def test_html_attrs_passthrough
118
+ render_inline(UI::Avatar.new(id: "my-avatar", data: { test_id: "av" })) { "JD" }
119
+ assert_selector "[data-slot='avatar']#my-avatar[data-test-id='av']"
120
+ end
121
+
122
+ # Fills the gap between sm (24px) and default (40px).
123
+ def test_size_md
124
+ render_inline(UI::Avatar.new(size: :md)) { "JD" }
125
+ classes = page.find("[data-slot='avatar']")[:class].split
126
+ assert_includes classes, "h-8"
127
+ assert_includes classes, "w-8"
128
+ end
129
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::BadgeTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_span_with_default_classes
10
+ render_inline(UI::Badge.new) { "New" }
11
+ span = page.find("span")
12
+ classes = span[:class].split
13
+ assert_includes classes, "bg-primary"
14
+ assert_includes classes, "rounded-full"
15
+ assert_equal "New", span.text.strip
16
+ end
17
+
18
+ def test_outline_variant_has_border_and_no_bg
19
+ render_inline(UI::Badge.new(variant: :outline)) { "x" }
20
+ classes = page.find("span")[:class].split
21
+ assert_includes classes, "border-input"
22
+ refute_includes classes, "bg-primary"
23
+ end
24
+
25
+ def test_secondary_variant
26
+ render_inline(UI::Badge.new(variant: :secondary)) { "Secondary" }
27
+ classes = page.find("span")[:class].split
28
+ assert_includes classes, "bg-secondary"
29
+ assert_includes classes, "text-secondary-foreground"
30
+ end
31
+
32
+ def test_destructive_variant
33
+ render_inline(UI::Badge.new(variant: :destructive)) { "Error" }
34
+ classes = page.find("span")[:class].split
35
+ assert_includes classes, "bg-destructive"
36
+ assert_includes classes, "text-white"
37
+ refute_includes classes, "bg-primary"
38
+ end
39
+
40
+ def test_success_variant
41
+ render_inline(UI::Badge.new(variant: :success)) { "OK" }
42
+ classes = page.find("span")[:class].split
43
+ assert_includes classes, "bg-emerald-500"
44
+ assert_includes classes, "text-white"
45
+ end
46
+
47
+ def test_warning_variant
48
+ render_inline(UI::Badge.new(variant: :warning)) { "Warn" }
49
+ classes = page.find("span")[:class].split
50
+ assert_includes classes, "bg-amber-500"
51
+ assert_includes classes, "text-white"
52
+ end
53
+
54
+ def test_size_sm
55
+ render_inline(UI::Badge.new(size: :sm)) { "Tiny" }
56
+ classes = page.find("span")[:class].split
57
+ assert_includes classes, "h-5"
58
+ assert_includes classes, "px-1.5"
59
+ assert_includes classes, "text-[10px]"
60
+ end
61
+
62
+ def test_size_lg
63
+ render_inline(UI::Badge.new(size: :lg)) { "Large" }
64
+ classes = page.find("span")[:class].split
65
+ assert_includes classes, "h-7"
66
+ assert_includes classes, "px-2.5"
67
+ assert_includes classes, "text-sm"
68
+ end
69
+
70
+ def test_leading_icon_slot
71
+ render_inline(UI::Badge.new) do |badge|
72
+ badge.with_leading_icon(name: "badge-check")
73
+ "Verified"
74
+ end
75
+ span = page.find("span")
76
+ icon = span.find("i", visible: :all)
77
+ assert_equal "badge-check", icon["data-lucide"]
78
+ assert_match(/<i[^>]*data-lucide="badge-check".*Verified/m, span.native.to_html)
79
+ end
80
+
81
+ def test_user_class_override_via_tailwind_merge
82
+ render_inline(UI::Badge.new(class: "bg-red-500")) { "Custom" }
83
+ classes = page.find("span")[:class].split
84
+ refute_includes classes, "bg-primary"
85
+ assert_includes classes, "bg-red-500"
86
+ end
87
+
88
+ def test_html_attrs_passthrough
89
+ render_inline(UI::Badge.new(id: "my-badge", data: { test_id: "badge-x" })) { "x" }
90
+ assert_selector "span#my-badge[data-test-id='badge-x']"
91
+ end
92
+
93
+ def test_default_size_classes
94
+ render_inline(UI::Badge.new) { "Default" }
95
+ classes = page.find("span")[:class].split
96
+ assert_includes classes, "h-6"
97
+ assert_includes classes, "px-2"
98
+ assert_includes classes, "text-xs"
99
+ end
100
+
101
+ def test_base_class_structure
102
+ render_inline(UI::Badge.new) { "Base" }
103
+ classes = page.find("span")[:class].split
104
+ assert_includes classes, "inline-flex"
105
+ assert_includes classes, "rounded-full"
106
+ assert_includes classes, "font-medium"
107
+ assert_includes classes, "whitespace-nowrap"
108
+ assert_includes classes, "border"
109
+ assert_includes classes, "border-transparent"
110
+ assert_includes classes, "transition-colors"
111
+ end
112
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::BannerTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def banner_el
10
+ page.find("[data-slot='banner']")
11
+ end
12
+
13
+ def test_renders_container
14
+ render_inline(UI::Banner.new) { "Message" }
15
+ assert banner_el.present?
16
+ end
17
+
18
+ def test_default_variant_is_introduction
19
+ render_inline(UI::Banner.new) { "x" }
20
+ classes = banner_el[:class].split
21
+ assert_includes classes, "bg-primary/10"
22
+ assert_includes classes, "border-primary/20"
23
+ end
24
+
25
+ def test_promotion_variant
26
+ render_inline(UI::Banner.new(variant: :promotion)) { "x" }
27
+ classes = banner_el[:class].split
28
+ assert_includes classes, "bg-accent"
29
+ assert_includes classes, "border-accent-foreground/20"
30
+ end
31
+
32
+ def test_full_width
33
+ render_inline(UI::Banner.new(full_width: true)) { "x" }
34
+ classes = banner_el[:class].split
35
+ assert_includes classes, "w-full"
36
+ end
37
+
38
+ def test_not_full_width_by_default
39
+ render_inline(UI::Banner.new) { "x" }
40
+ classes = banner_el[:class].split
41
+ refute_includes classes, "w-full"
42
+ end
43
+
44
+ def test_renders_without_body_text
45
+ render_inline(UI::Banner.new) do |banner|
46
+ banner.with_title { "Just a title" }
47
+ end
48
+ assert_selector "[data-slot='banner'] h4", text: "Just a title"
49
+ refute_selector "[data-slot='banner'] p"
50
+ end
51
+
52
+ def test_renders_title_slot
53
+ render_inline(UI::Banner.new) do |banner|
54
+ banner.with_title { "Try new feature" }
55
+ "Description here."
56
+ end
57
+ assert_selector "h4", text: "Try new feature"
58
+ assert_selector "[data-slot='banner'] p", text: /Description here/
59
+ end
60
+
61
+ def test_renders_action_slot
62
+ render_inline(UI::Banner.new) do |banner|
63
+ banner.with_action { "Get started" }
64
+ end
65
+ assert_selector "[data-slot='banner'] div", text: "Get started"
66
+ end
67
+
68
+ def test_renders_illustration_slot
69
+ render_inline(UI::Banner.new) do |banner|
70
+ banner.with_illustration do
71
+ "<img src='test.svg' alt='' />".html_safe
72
+ end
73
+ end
74
+ assert_selector "[data-slot='banner'] img[src='test.svg']"
75
+ end
76
+
77
+ def test_renders_leading_icon_slot
78
+ render_inline(UI::Banner.new) do |banner|
79
+ banner.with_leading_icon(name: "info")
80
+ end
81
+ assert_selector "[data-slot='banner'] i[data-lucide='info']"
82
+ end
83
+
84
+ def test_dismiss_button_uses_button_component
85
+ render_inline(UI::Banner.new) { "Message" }
86
+ assert_selector "button[aria-label='Dismiss']"
87
+ assert_selector "button[aria-label='Dismiss'] i[data-lucide='x']"
88
+ end
89
+
90
+ def test_dismiss_button_wired_to_controller
91
+ render_inline(UI::Banner.new) { "x" }
92
+ dismiss = page.find("button[aria-label='Dismiss']")
93
+ assert_includes dismiss["data-action"], "ui--banner#dismiss"
94
+ end
95
+
96
+ def test_custom_dismiss_icon
97
+ render_inline(UI::Banner.new(dismiss_icon: "check")) { "x" }
98
+ assert_selector "button[aria-label='Dismiss'] i[data-lucide='check']"
99
+ end
100
+
101
+ def test_dismissible_false_hides_close
102
+ render_inline(UI::Banner.new(dismissible: false)) { "x" }
103
+ refute_selector "button[aria-label='Dismiss']"
104
+ end
105
+
106
+ def test_user_class_merge
107
+ render_inline(UI::Banner.new(class: "my-banner")) { "x" }
108
+ classes = banner_el[:class].split
109
+ assert_includes classes, "my-banner"
110
+ end
111
+
112
+ def test_html_attrs_passthrough
113
+ render_inline(UI::Banner.new(id: "my-id", data: { test_id: "b" })) { "x" }
114
+ assert_selector "[data-slot='banner']#my-id[data-test-id='b']"
115
+ end
116
+ end