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,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::TabsTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def render_tabs(default_value: "a", variant: :line)
10
+ render_inline(UI::Tabs.new(default_value: default_value, variant: variant)) do |tabs|
11
+ tabs.with_list do |list|
12
+ list.with_trigger(value: "a") { "A" }
13
+ list.with_trigger(value: "b") { "B" }
14
+ end
15
+ tabs.with_panel(value: "a") { "panel-a" }
16
+ tabs.with_panel(value: "b") { "panel-b" }
17
+ end
18
+ end
19
+
20
+ def test_root_carries_controller
21
+ render_tabs
22
+ root = page.find("[data-controller~='ui--tabs']")
23
+ assert_equal "a", root["data-ui--tabs-active-value"]
24
+ end
25
+
26
+ def test_default_value_selects_active_tab
27
+ render_tabs(default_value: "b")
28
+ root = page.find("[data-controller~='ui--tabs']")
29
+ assert_equal "b", root["data-ui--tabs-active-value"]
30
+ end
31
+
32
+ def test_inactive_panel_is_hidden
33
+ render_tabs(default_value: "a")
34
+ assert_selector "[role='tabpanel'][data-value='b'][hidden]", visible: :all
35
+ assert_no_selector "[role='tabpanel'][data-value='a'][hidden]", visible: :all
36
+ end
37
+
38
+ def test_active_panel_not_hidden
39
+ render_tabs(default_value: "a")
40
+ panel = page.find("[role='tabpanel'][data-value='a']")
41
+ refute panel[:hidden].present?
42
+ end
43
+
44
+ def test_root_has_auto_generated_id
45
+ render_tabs
46
+ root = page.find("[data-controller~='ui--tabs']")
47
+ assert root[:id].present?
48
+ assert root[:id].start_with?("ui-tabs-")
49
+ end
50
+
51
+ def test_custom_id_preserved
52
+ render_inline(UI::Tabs.new(default_value: "a", id: "my-tabs")) do |tabs|
53
+ tabs.with_list do |list|
54
+ list.with_trigger(value: "a") { "A" }
55
+ end
56
+ tabs.with_panel(value: "a") { "panel-a" }
57
+ end
58
+ assert_selector "[data-controller~='ui--tabs']#my-tabs"
59
+ end
60
+
61
+ def test_user_class_merge
62
+ render_inline(UI::Tabs.new(default_value: "a", class: "my-custom-class")) do |tabs|
63
+ tabs.with_list do |list|
64
+ list.with_trigger(value: "a") { "A" }
65
+ end
66
+ tabs.with_panel(value: "a") { "panel-a" }
67
+ end
68
+ classes = page.find("[data-controller~='ui--tabs']")[:class].split
69
+ assert_includes classes, "my-custom-class"
70
+ assert_includes classes, "w-full"
71
+ end
72
+
73
+ def test_html_attrs_passthrough
74
+ render_inline(UI::Tabs.new(default_value: "a", data: { test_id: "tabs-x" })) do |tabs|
75
+ tabs.with_list do |list|
76
+ list.with_trigger(value: "a") { "A" }
77
+ end
78
+ tabs.with_panel(value: "a") { "panel-a" }
79
+ end
80
+ assert_selector "[data-controller~='ui--tabs'][data-test-id='tabs-x']"
81
+ end
82
+
83
+ def test_panels_rendered_at_root_level
84
+ render_tabs
85
+ panels = page.all("[role='tabpanel']", visible: :all)
86
+ assert_equal 2, panels.size
87
+ end
88
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::TextTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_paragraph_by_default
10
+ render_inline(UI::Text.new) { "Hello world" }
11
+ assert_selector "p", text: "Hello world"
12
+ end
13
+
14
+ def test_renders_as_span
15
+ render_inline(UI::Text.new(as: :span)) { "Inline text" }
16
+ assert_selector "span", text: "Inline text"
17
+ end
18
+
19
+ def test_renders_as_div
20
+ render_inline(UI::Text.new(as: :div)) { "Block text" }
21
+ assert_selector "div", text: "Block text"
22
+ end
23
+
24
+ def test_default_classes
25
+ render_inline(UI::Text.new) { "x" }
26
+ classes = page.find("p")[:class].split
27
+ assert_includes classes, "text-base"
28
+ assert_includes classes, "font-normal"
29
+ assert_includes classes, "text-foreground"
30
+ # Note: leading-normal is in base but tailwind_merge may remove it
31
+ end
32
+
33
+ def test_size_variants
34
+ render_inline(UI::Text.new(size: :sm)) { "Small" }
35
+ classes = page.find("p")[:class].split
36
+ assert_includes classes, "text-sm"
37
+ refute_includes classes, "text-base"
38
+ end
39
+
40
+ def test_weight_variants
41
+ render_inline(UI::Text.new(weight: :bold)) { "Bold" }
42
+ classes = page.find("p")[:class].split
43
+ assert_includes classes, "font-bold"
44
+ refute_includes classes, "font-normal"
45
+ end
46
+
47
+ def test_tone_variants
48
+ render_inline(UI::Text.new(tone: :muted)) { "Muted" }
49
+ classes = page.find("p")[:class].split
50
+ assert_includes classes, "text-muted-foreground"
51
+ refute_includes classes, "text-foreground"
52
+ end
53
+
54
+ def test_destructive_tone
55
+ render_inline(UI::Text.new(tone: :destructive)) { "Error" }
56
+ classes = page.find("p")[:class].split
57
+ assert_includes classes, "text-destructive"
58
+ end
59
+
60
+ def test_user_class_merge
61
+ render_inline(UI::Text.new(class: "text-center")) { "Centered" }
62
+ classes = page.find("p")[:class].split
63
+ assert_includes classes, "text-center"
64
+ end
65
+
66
+ def test_user_class_overrides_defaults
67
+ render_inline(UI::Text.new(class: "font-light")) { "Light" }
68
+ classes = page.find("p")[:class].split
69
+ refute_includes classes, "font-normal"
70
+ assert_includes classes, "font-light"
71
+ end
72
+
73
+ def test_html_attrs_passthrough
74
+ render_inline(UI::Text.new(id: "my-text", data: { test: "x" })) { "y" }
75
+ assert_selector "p#my-text[data-test='x']"
76
+ end
77
+
78
+ def test_large_heading_size
79
+ render_inline(UI::Text.new(size: :"4xl", weight: :bold)) { "Hero" }
80
+ classes = page.find("p")[:class].split
81
+ assert_includes classes, "text-4xl"
82
+ assert_includes classes, "font-bold"
83
+ end
84
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::ThemeToggleTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_wires_the_theme_controller
10
+ render_inline(UI::ThemeToggle.new)
11
+ assert_selector "[data-controller='ui--theme']"
12
+ end
13
+
14
+ def test_trigger_has_an_accessible_name
15
+ render_inline(UI::ThemeToggle.new)
16
+ assert_equal "Change theme", page.find("button[aria-haspopup]")[:"aria-label"]
17
+ end
18
+
19
+ def test_custom_label
20
+ render_inline(UI::ThemeToggle.new(label: "Appearance"))
21
+ assert_equal "Appearance", page.find("button[aria-haspopup]")[:"aria-label"]
22
+ end
23
+
24
+ def test_offers_all_three_preferences
25
+ render_inline(UI::ThemeToggle.new)
26
+ themes = page.all("[data-ui--theme-target='option']", visible: :all).map { |o| o[:"data-theme"] }
27
+ assert_equal %w[light dark system], themes
28
+ end
29
+
30
+ def test_options_wire_the_select_action
31
+ render_inline(UI::ThemeToggle.new)
32
+ page.all("[data-ui--theme-target='option']", visible: :all).each do |option|
33
+ assert_includes option[:"data-action"], "ui--theme#select"
34
+ end
35
+ end
36
+
37
+ # aria-checked is only valid on a radio/checkbox role, so the options must
38
+ # not fall back to UI::Menu::Item's default role="menuitem".
39
+ def test_options_use_the_radio_role_for_aria_checked
40
+ render_inline(UI::ThemeToggle.new)
41
+ options = page.all("[data-ui--theme-target='option']", visible: :all)
42
+ assert_equal 3, options.size
43
+ options.each do |option|
44
+ assert_equal "menuitemradio", option[:role]
45
+ assert_equal "false", option[:"aria-checked"]
46
+ end
47
+ end
48
+
49
+ # lucide swaps each <i data-lucide> for an <svg> on first render, so the
50
+ # element is gone afterwards and the icon cannot be changed by attribute.
51
+ # All three are rendered and shown by the preference on <html data-theme>:
52
+ # the sun for light, which is the default (and before any script runs).
53
+ def test_trigger_icon_follows_the_preference_with_light_by_default
54
+ render_inline(UI::ThemeToggle.new)
55
+ trigger = page.find("button[aria-haspopup]")
56
+ icon = ->(name) { trigger.find("i[data-lucide='#{name}']", visible: :all)[:class].split }
57
+ sun = icon.("sun")
58
+ assert_includes sun, "block"
59
+ assert_includes sun, "in-data-[theme=dark]:hidden"
60
+ assert_includes sun, "in-data-[theme=system]:hidden"
61
+ assert_includes icon.("moon"), "in-data-[theme=dark]:block"
62
+ assert_includes icon.("monitor"), "in-data-[theme=system]:block"
63
+ end
64
+
65
+ def test_html_attrs_passthrough
66
+ render_inline(UI::ThemeToggle.new(id: "theme", data: { test_id: "tt" }))
67
+ assert_selector "#theme[data-test-id='tt']"
68
+ end
69
+
70
+ # The checked option needs a persistent background, not just a focus ring —
71
+ # otherwise nothing shows which theme is active once focus moves.
72
+ def test_options_carry_the_checked_state_styling
73
+ render_inline(UI::ThemeToggle.new)
74
+ option = page.all("[data-ui--theme-target='option']", visible: :all).first
75
+ classes = option[:class].split
76
+ assert_includes classes, "aria-checked:bg-accent"
77
+ assert_includes classes, "aria-checked:font-medium"
78
+ end
79
+
80
+ def test_each_option_has_a_check_indicator_shown_only_when_checked
81
+ render_inline(UI::ThemeToggle.new)
82
+ page.all("[data-ui--theme-target='option']", visible: :all).each do |option|
83
+ check = option.find("i[data-lucide='check']", visible: :all)
84
+ assert_includes check[:class].split, "hidden"
85
+ assert_includes check[:class].split, "group-aria-checked:block"
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::Tooltip::ContentTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_content
10
+ result = render_inline(UI::Tooltip.new) do |tooltip|
11
+ tooltip.with_trigger { "Trigger" }
12
+ tooltip.with_content { "Tooltip content" }
13
+ end
14
+ # Tooltip content is hidden by default, check it's in the HTML
15
+ assert result.to_html.include?("Tooltip content")
16
+ end
17
+
18
+ def test_has_role_tooltip
19
+ render_inline(UI::Tooltip.new) do |tooltip|
20
+ tooltip.with_trigger { "Trigger" }
21
+ tooltip.with_content { "Content" }
22
+ end
23
+ assert_selector "[role='tooltip']", visible: false
24
+ end
25
+
26
+ def test_is_initially_hidden
27
+ render_inline(UI::Tooltip.new) do |tooltip|
28
+ tooltip.with_trigger { "Trigger" }
29
+ tooltip.with_content { "Content" }
30
+ end
31
+ content = page.find("[role='tooltip']", visible: false)
32
+ assert content[:hidden].present?
33
+ end
34
+
35
+ def test_has_data_target
36
+ render_inline(UI::Tooltip.new) do |tooltip|
37
+ tooltip.with_trigger { "Trigger" }
38
+ tooltip.with_content { "Content" }
39
+ end
40
+ content = page.find("[role='tooltip']", visible: false)
41
+ assert_equal "content", content[:"data-ui--tooltip-target"]
42
+ end
43
+
44
+ def test_default_side_top
45
+ render_inline(UI::Tooltip.new) do |tooltip|
46
+ tooltip.with_trigger { "Trigger" }
47
+ tooltip.with_content { "Content" }
48
+ end
49
+ content = page.find("[role='tooltip']", visible: false)
50
+ assert_equal "top", content[:"data-side"]
51
+ end
52
+
53
+ def test_custom_side
54
+ render_inline(UI::Tooltip.new) do |tooltip|
55
+ tooltip.with_trigger { "Trigger" }
56
+ tooltip.with_content(side: :right) { "Content" }
57
+ end
58
+ content = page.find("[role='tooltip']", visible: false)
59
+ assert_equal "right", content[:"data-side"]
60
+ end
61
+
62
+ def test_custom_align
63
+ render_inline(UI::Tooltip.new) do |tooltip|
64
+ tooltip.with_trigger { "Trigger" }
65
+ tooltip.with_content(align: :end) { "Content" }
66
+ end
67
+ content = page.find("[role='tooltip']", visible: false)
68
+ assert_equal "end", content[:"data-align"]
69
+ end
70
+
71
+ def test_has_animation_classes
72
+ render_inline(UI::Tooltip.new) do |tooltip|
73
+ tooltip.with_trigger { "Trigger" }
74
+ tooltip.with_content { "Content" }
75
+ end
76
+ content = page.find("[role='tooltip']", visible: false)
77
+ assert content[:class].include?("animate-in")
78
+ assert content[:class].include?("fade-in-0")
79
+ end
80
+
81
+ def test_custom_class_merge
82
+ render_inline(UI::Tooltip.new) do |tooltip|
83
+ tooltip.with_trigger { "Trigger" }
84
+ tooltip.with_content(class: "custom-content") { "Content" }
85
+ end
86
+ content = page.find("[role='tooltip']", visible: false)
87
+ assert_includes content[:class].split, "custom-content"
88
+ end
89
+
90
+ # Both tokens invert together, so the tooltip stays readable in either theme.
91
+ # A pinned near-white (text-slate-50) sat on a near-white bg-foreground in
92
+ # dark mode and was unreadable.
93
+ def test_uses_inverted_surface_tokens
94
+ render_inline(UI::Tooltip.new) do |tooltip|
95
+ tooltip.with_trigger { "Trigger" }
96
+ tooltip.with_content { "Content" }
97
+ end
98
+ content = page.find("[role='tooltip']", visible: false)
99
+ classes = content[:class].split
100
+ assert_includes classes, "bg-foreground"
101
+ assert_includes classes, "text-background"
102
+ refute_includes classes, "text-slate-50"
103
+ end
104
+
105
+ def test_does_not_clip_overflow
106
+ # Floating UI positions the arrow partially outside the content box
107
+ # via negative staticSide offset; overflow-hidden would clip it.
108
+ render_inline(UI::Tooltip.new) do |tooltip|
109
+ tooltip.with_trigger { "Trigger" }
110
+ tooltip.with_content { "Content" }
111
+ end
112
+ classes = page.find("[role='tooltip']", visible: false)[:class].split
113
+ refute_includes classes, "overflow-hidden"
114
+ end
115
+
116
+ def test_has_arrow_element
117
+ render_inline(UI::Tooltip.new) do |tooltip|
118
+ tooltip.with_trigger { "Trigger" }
119
+ tooltip.with_content { "Content" }
120
+ end
121
+ assert_selector "[data-ui--tooltip-target='arrow']", visible: false
122
+ end
123
+
124
+ def test_arrow_has_matching_background_and_rotation
125
+ render_inline(UI::Tooltip.new) do |tooltip|
126
+ tooltip.with_trigger { "Trigger" }
127
+ tooltip.with_content { "Content" }
128
+ end
129
+ arrow = page.find("[data-ui--tooltip-target='arrow']", visible: false)
130
+ classes = arrow[:class].split
131
+ assert_includes classes, "bg-foreground"
132
+ assert_includes classes, "rotate-45"
133
+ assert_includes classes, "absolute"
134
+ end
135
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::Tooltip::TriggerTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_content
10
+ render_inline(UI::Tooltip.new) do |tooltip|
11
+ tooltip.with_trigger { "Trigger text" }
12
+ tooltip.with_content { "Content" }
13
+ end
14
+ assert_text "Trigger text"
15
+ end
16
+
17
+ def test_has_aria_describedby
18
+ render_inline(UI::Tooltip.new(id: "test-id")) do |tooltip|
19
+ tooltip.with_trigger { "Trigger" }
20
+ tooltip.with_content { "Content" }
21
+ end
22
+ trigger = page.find("[data-ui--tooltip-target='trigger']")
23
+ assert_equal "ui-tooltip-content-test-id", trigger[:"aria-describedby"]
24
+ end
25
+
26
+ def test_has_data_target
27
+ render_inline(UI::Tooltip.new) do |tooltip|
28
+ tooltip.with_trigger { "Trigger" }
29
+ tooltip.with_content { "Content" }
30
+ end
31
+ assert_selector "[data-ui--tooltip-target='trigger']"
32
+ end
33
+
34
+ def test_has_interaction_actions
35
+ render_inline(UI::Tooltip.new) do |tooltip|
36
+ tooltip.with_trigger { "Trigger" }
37
+ tooltip.with_content { "Content" }
38
+ end
39
+ trigger = page.find("[data-ui--tooltip-target='trigger']")
40
+ assert trigger[:"data-action"].include?("mouseenter->ui--tooltip#show")
41
+ assert trigger[:"data-action"].include?("mouseleave->ui--tooltip#hide")
42
+ end
43
+
44
+ def test_custom_class_merge
45
+ render_inline(UI::Tooltip.new) do |tooltip|
46
+ tooltip.with_trigger(class: "custom-trigger") { "Trigger" }
47
+ tooltip.with_content { "Content" }
48
+ end
49
+ trigger = page.find("[data-ui--tooltip-target='trigger']")
50
+ assert_includes trigger[:class].split, "custom-trigger"
51
+ end
52
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::TooltipTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def render_tooltip
10
+ render_inline(UI::Tooltip.new) do |tooltip|
11
+ tooltip.with_trigger { "Hover me" }
12
+ tooltip.with_content { "Tooltip text" }
13
+ end
14
+ end
15
+
16
+ def test_root_renders_trigger_and_content
17
+ result = render_tooltip
18
+ assert_text "Hover me"
19
+ # Tooltip content is hidden by default, check it's in the HTML
20
+ assert result.to_html.include?("Tooltip text")
21
+ end
22
+
23
+ def test_trigger_has_aria_describedby
24
+ render_tooltip
25
+ trigger = page.find("[data-ui--tooltip-target='trigger']")
26
+ assert trigger[:"aria-describedby"].present?
27
+ assert trigger[:"aria-describedby"].start_with?("ui-tooltip-content-")
28
+ end
29
+
30
+ def test_trigger_has_interaction_actions
31
+ render_tooltip
32
+ trigger = page.find("[data-ui--tooltip-target='trigger']")
33
+ assert trigger[:"data-action"].include?("mouseenter->ui--tooltip#show")
34
+ assert trigger[:"data-action"].include?("mouseleave->ui--tooltip#hide")
35
+ assert trigger[:"data-action"].include?("focus->ui--tooltip#show")
36
+ assert trigger[:"data-action"].include?("blur->ui--tooltip#hide")
37
+ end
38
+
39
+ def test_content_has_role_tooltip
40
+ render_tooltip
41
+ assert_selector "[role='tooltip']", visible: false
42
+ end
43
+
44
+ def test_content_is_initially_hidden
45
+ render_tooltip
46
+ content = page.find("[role='tooltip']", visible: false)
47
+ assert content[:hidden].present?
48
+ end
49
+
50
+ def test_content_has_data_targets
51
+ render_tooltip
52
+ content = page.find("[role='tooltip']", visible: false)
53
+ assert_equal "content", content[:"data-ui--tooltip-target"]
54
+ end
55
+
56
+ def test_default_side_is_top
57
+ render_tooltip
58
+ content = page.find("[role='tooltip']", visible: false)
59
+ assert_equal "top", content[:"data-side"]
60
+ end
61
+
62
+ def test_custom_side
63
+ render_inline(UI::Tooltip.new) do |tooltip|
64
+ tooltip.with_trigger { "Hover me" }
65
+ tooltip.with_content(side: :bottom) { "Tooltip text" }
66
+ end
67
+ content = page.find("[role='tooltip']", visible: false)
68
+ assert_equal "bottom", content[:"data-side"]
69
+ end
70
+
71
+ def test_custom_id_preserved
72
+ render_inline(UI::Tooltip.new(id: "my-tooltip")) do |tooltip|
73
+ tooltip.with_trigger { "Hover me" }
74
+ tooltip.with_content { "Tooltip text" }
75
+ end
76
+ assert_selector "#my-tooltip"
77
+ end
78
+
79
+ def test_user_class_merge
80
+ render_inline(UI::Tooltip.new(class: "my-custom-class")) do |tooltip|
81
+ tooltip.with_trigger { "Hover me" }
82
+ tooltip.with_content { "Tooltip text" }
83
+ end
84
+ root = page.find("[data-controller='ui--tooltip']")
85
+ classes = root[:class].split
86
+ assert_includes classes, "my-custom-class"
87
+ assert_includes classes, "inline-block"
88
+ end
89
+
90
+ def test_html_attrs_passthrough
91
+ render_inline(UI::Tooltip.new(data: { test_id: "tooltip-x" })) do |tooltip|
92
+ tooltip.with_trigger { "Hover me" }
93
+ tooltip.with_content { "Tooltip text" }
94
+ end
95
+ assert_selector "[data-controller='ui--tooltip'][data-test-id='tooltip-x']"
96
+ end
97
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::Typography::CodeTest < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_code_tag
10
+ render_inline(UI::Typography::Code.new) { "npm install" }
11
+ assert_selector "code", text: "npm install"
12
+ end
13
+
14
+ def test_has_mono_font_and_background
15
+ render_inline(UI::Typography::Code.new) { "x" }
16
+ classes = page.find("code")[:class].split
17
+ assert_includes classes, "font-mono"
18
+ assert_includes classes, "bg-muted"
19
+ assert_includes classes, "rounded"
20
+ end
21
+
22
+ def test_size_variants
23
+ render_inline(UI::Typography::Code.new(size: :sm)) { "small" }
24
+ classes = page.find("code")[:class].split
25
+ assert_includes classes, "text-xs"
26
+ end
27
+
28
+ def test_user_class_merge
29
+ render_inline(UI::Typography::Code.new(class: "text-destructive")) { "x" }
30
+ classes = page.find("code")[:class].split
31
+ assert_includes classes, "text-destructive"
32
+ end
33
+ end
34
+
35
+ class UI::Typography::BlockquoteTest < ViewComponent::TestCase
36
+ def setup_fixtures(*); end
37
+ def teardown_fixtures(*); end
38
+
39
+ def test_renders_blockquote_tag
40
+ render_inline(UI::Typography::Blockquote.new) { "A famous quote" }
41
+ assert_selector "blockquote", text: "A famous quote"
42
+ end
43
+
44
+ def test_has_left_border_and_italic
45
+ render_inline(UI::Typography::Blockquote.new) { "x" }
46
+ classes = page.find("blockquote")[:class].split
47
+ assert_includes classes, "border-l-2"
48
+ assert_includes classes, "italic"
49
+ assert_includes classes, "pl-6"
50
+ end
51
+
52
+ def test_user_class_merge
53
+ render_inline(UI::Typography::Blockquote.new(class: "mt-8")) { "x" }
54
+ classes = page.find("blockquote")[:class].split
55
+ assert_includes classes, "mt-8"
56
+ end
57
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UI::Typography::H1Test < ViewComponent::TestCase
6
+ def setup_fixtures(*); end
7
+ def teardown_fixtures(*); end
8
+
9
+ def test_renders_h1_tag
10
+ render_inline(UI::Typography::H1.new) { "Heading 1" }
11
+ assert_selector "h1", text: "Heading 1"
12
+ end
13
+
14
+ def test_has_correct_classes
15
+ render_inline(UI::Typography::H1.new) { "x" }
16
+ classes = page.find("h1")[:class].split
17
+ assert_includes classes, "text-4xl"
18
+ assert_includes classes, "font-bold"
19
+ end
20
+ end
21
+
22
+ class UI::Typography::H2Test < ViewComponent::TestCase
23
+ def setup_fixtures(*); end
24
+ def teardown_fixtures(*); end
25
+
26
+ def test_renders_h2_tag
27
+ render_inline(UI::Typography::H2.new) { "Heading 2" }
28
+ assert_selector "h2", text: "Heading 2"
29
+ end
30
+
31
+ def test_has_correct_classes
32
+ render_inline(UI::Typography::H2.new) { "x" }
33
+ classes = page.find("h2")[:class].split
34
+ assert_includes classes, "text-3xl"
35
+ assert_includes classes, "font-semibold"
36
+ end
37
+ end
38
+
39
+ class UI::Typography::H3Test < ViewComponent::TestCase
40
+ def setup_fixtures(*); end
41
+ def teardown_fixtures(*); end
42
+
43
+ def test_renders_h3_tag
44
+ render_inline(UI::Typography::H3.new) { "Heading 3" }
45
+ assert_selector "h3", text: "Heading 3"
46
+ end
47
+
48
+ def test_has_correct_classes
49
+ render_inline(UI::Typography::H3.new) { "x" }
50
+ classes = page.find("h3")[:class].split
51
+ assert_includes classes, "text-2xl"
52
+ assert_includes classes, "font-semibold"
53
+ end
54
+ end
55
+
56
+ class UI::Typography::H4Test < ViewComponent::TestCase
57
+ def setup_fixtures(*); end
58
+ def teardown_fixtures(*); end
59
+
60
+ def test_renders_h4_tag
61
+ render_inline(UI::Typography::H4.new) { "Heading 4" }
62
+ assert_selector "h4", text: "Heading 4"
63
+ end
64
+
65
+ def test_has_correct_classes
66
+ render_inline(UI::Typography::H4.new) { "x" }
67
+ classes = page.find("h4")[:class].split
68
+ assert_includes classes, "text-xl"
69
+ assert_includes classes, "font-semibold"
70
+ end
71
+ end