poetry-ui 0.0.2

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 (442) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +6 -0
  3. data/CHANGELOG.md +3 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +87 -0
  6. data/THIRD_PARTY_NOTICES.md +201 -0
  7. data/app/components/poetry/ui/accordion/component.html.erb +3 -0
  8. data/app/components/poetry/ui/accordion/component.rb +197 -0
  9. data/app/components/poetry/ui/accordion/preview.rb +39 -0
  10. data/app/components/poetry/ui/accordion/style.rb +37 -0
  11. data/app/components/poetry/ui/alert/component.html.erb +6 -0
  12. data/app/components/poetry/ui/alert/component.rb +61 -0
  13. data/app/components/poetry/ui/alert/preview.rb +35 -0
  14. data/app/components/poetry/ui/alert/style.rb +24 -0
  15. data/app/components/poetry/ui/alert_dialog/component.html.erb +17 -0
  16. data/app/components/poetry/ui/alert_dialog/component.rb +208 -0
  17. data/app/components/poetry/ui/alert_dialog/preview.rb +48 -0
  18. data/app/components/poetry/ui/alert_dialog/style.rb +46 -0
  19. data/app/components/poetry/ui/aspect_ratio/component.rb +63 -0
  20. data/app/components/poetry/ui/aspect_ratio/preview.rb +29 -0
  21. data/app/components/poetry/ui/aspect_ratio/style.rb +13 -0
  22. data/app/components/poetry/ui/attachment/component.html.erb +13 -0
  23. data/app/components/poetry/ui/attachment/component.rb +139 -0
  24. data/app/components/poetry/ui/attachment/preview.rb +83 -0
  25. data/app/components/poetry/ui/attachment/style.rb +59 -0
  26. data/app/components/poetry/ui/autocomplete/component.html.erb +16 -0
  27. data/app/components/poetry/ui/autocomplete/component.rb +193 -0
  28. data/app/components/poetry/ui/autocomplete/preview.rb +39 -0
  29. data/app/components/poetry/ui/autocomplete/style.rb +18 -0
  30. data/app/components/poetry/ui/avatar/component.rb +103 -0
  31. data/app/components/poetry/ui/avatar/group_preview.html.erb +11 -0
  32. data/app/components/poetry/ui/avatar/preview.rb +50 -0
  33. data/app/components/poetry/ui/avatar/style.rb +34 -0
  34. data/app/components/poetry/ui/badge/component.rb +73 -0
  35. data/app/components/poetry/ui/badge/preview.rb +54 -0
  36. data/app/components/poetry/ui/badge/style.rb +35 -0
  37. data/app/components/poetry/ui/breadcrumb/component.html.erb +32 -0
  38. data/app/components/poetry/ui/breadcrumb/component.rb +102 -0
  39. data/app/components/poetry/ui/breadcrumb/dropdown_crumb_preview.html.erb +21 -0
  40. data/app/components/poetry/ui/breadcrumb/preview.rb +45 -0
  41. data/app/components/poetry/ui/breadcrumb/style.rb +25 -0
  42. data/app/components/poetry/ui/bubble/component.html.erb +4 -0
  43. data/app/components/poetry/ui/bubble/component.rb +106 -0
  44. data/app/components/poetry/ui/bubble/preview.rb +62 -0
  45. data/app/components/poetry/ui/bubble/style.rb +47 -0
  46. data/app/components/poetry/ui/button/component.html.erb +14 -0
  47. data/app/components/poetry/ui/button/component.rb +168 -0
  48. data/app/components/poetry/ui/button/preview.rb +94 -0
  49. data/app/components/poetry/ui/button/style.rb +41 -0
  50. data/app/components/poetry/ui/button_group/component.rb +70 -0
  51. data/app/components/poetry/ui/button_group/popup_triggers_preview.html.erb +21 -0
  52. data/app/components/poetry/ui/button_group/preview.rb +46 -0
  53. data/app/components/poetry/ui/button_group/style.rb +27 -0
  54. data/app/components/poetry/ui/button_group/text_prefix_preview.html.erb +7 -0
  55. data/app/components/poetry/ui/calendar/component.html.erb +72 -0
  56. data/app/components/poetry/ui/calendar/component.rb +366 -0
  57. data/app/components/poetry/ui/calendar/preview.rb +55 -0
  58. data/app/components/poetry/ui/calendar/style.rb +55 -0
  59. data/app/components/poetry/ui/card/component.html.erb +11 -0
  60. data/app/components/poetry/ui/card/component.rb +90 -0
  61. data/app/components/poetry/ui/card/preview.rb +29 -0
  62. data/app/components/poetry/ui/card/style.rb +23 -0
  63. data/app/components/poetry/ui/carousel/component.html.erb +17 -0
  64. data/app/components/poetry/ui/carousel/component.rb +179 -0
  65. data/app/components/poetry/ui/carousel/preview.rb +67 -0
  66. data/app/components/poetry/ui/carousel/style.rb +39 -0
  67. data/app/components/poetry/ui/checkbox/component.html.erb +22 -0
  68. data/app/components/poetry/ui/checkbox/component.rb +179 -0
  69. data/app/components/poetry/ui/checkbox/preview.rb +68 -0
  70. data/app/components/poetry/ui/checkbox/style.rb +28 -0
  71. data/app/components/poetry/ui/clipboard_text/component.html.erb +15 -0
  72. data/app/components/poetry/ui/clipboard_text/component.rb +154 -0
  73. data/app/components/poetry/ui/clipboard_text/preview.rb +24 -0
  74. data/app/components/poetry/ui/clipboard_text/style.rb +23 -0
  75. data/app/components/poetry/ui/code_block/component.html.erb +13 -0
  76. data/app/components/poetry/ui/code_block/component.rb +141 -0
  77. data/app/components/poetry/ui/code_block/preview.rb +40 -0
  78. data/app/components/poetry/ui/code_block/style.rb +38 -0
  79. data/app/components/poetry/ui/collapsible/component.html.erb +4 -0
  80. data/app/components/poetry/ui/collapsible/component.rb +134 -0
  81. data/app/components/poetry/ui/collapsible/preview.rb +24 -0
  82. data/app/components/poetry/ui/combobox/component.html.erb +49 -0
  83. data/app/components/poetry/ui/combobox/component.rb +1086 -0
  84. data/app/components/poetry/ui/combobox/preview.rb +164 -0
  85. data/app/components/poetry/ui/combobox/style.rb +148 -0
  86. data/app/components/poetry/ui/command/component.html.erb +20 -0
  87. data/app/components/poetry/ui/command/component.rb +526 -0
  88. data/app/components/poetry/ui/command/dialog_component.html.erb +14 -0
  89. data/app/components/poetry/ui/command/dialog_component.rb +206 -0
  90. data/app/components/poetry/ui/command/dialog_preview.rb +39 -0
  91. data/app/components/poetry/ui/command/preview.rb +121 -0
  92. data/app/components/poetry/ui/command/style.rb +78 -0
  93. data/app/components/poetry/ui/composable_trigger.rb +63 -0
  94. data/app/components/poetry/ui/context_menu/component.html.erb +6 -0
  95. data/app/components/poetry/ui/context_menu/component.rb +358 -0
  96. data/app/components/poetry/ui/context_menu/preview.rb +111 -0
  97. data/app/components/poetry/ui/context_menu/style.rb +59 -0
  98. data/app/components/poetry/ui/data_table/component.html.erb +78 -0
  99. data/app/components/poetry/ui/data_table/component.rb +277 -0
  100. data/app/components/poetry/ui/data_table/preview.rb +68 -0
  101. data/app/components/poetry/ui/data_table/state.rb +115 -0
  102. data/app/components/poetry/ui/data_table/style.rb +42 -0
  103. data/app/components/poetry/ui/date_field/component.html.erb +7 -0
  104. data/app/components/poetry/ui/date_field/component.rb +209 -0
  105. data/app/components/poetry/ui/date_field/preview.rb +42 -0
  106. data/app/components/poetry/ui/date_field/style.rb +35 -0
  107. data/app/components/poetry/ui/date_picker/component.html.erb +23 -0
  108. data/app/components/poetry/ui/date_picker/component.rb +242 -0
  109. data/app/components/poetry/ui/date_picker/preview.rb +31 -0
  110. data/app/components/poetry/ui/date_picker/style.rb +25 -0
  111. data/app/components/poetry/ui/deferred/component.html.erb +16 -0
  112. data/app/components/poetry/ui/deferred/component.rb +90 -0
  113. data/app/components/poetry/ui/deferred/preview.rb +24 -0
  114. data/app/components/poetry/ui/dialog/component.html.erb +21 -0
  115. data/app/components/poetry/ui/dialog/component.rb +194 -0
  116. data/app/components/poetry/ui/dialog/preview.rb +33 -0
  117. data/app/components/poetry/ui/dialog/style.rb +30 -0
  118. data/app/components/poetry/ui/drawer/component.html.erb +16 -0
  119. data/app/components/poetry/ui/drawer/component.rb +207 -0
  120. data/app/components/poetry/ui/drawer/preview.rb +69 -0
  121. data/app/components/poetry/ui/drawer/style.rb +105 -0
  122. data/app/components/poetry/ui/dropdown_menu/component.html.erb +6 -0
  123. data/app/components/poetry/ui/dropdown_menu/component.rb +350 -0
  124. data/app/components/poetry/ui/dropdown_menu/preview.rb +101 -0
  125. data/app/components/poetry/ui/dropdown_menu/style.rb +60 -0
  126. data/app/components/poetry/ui/empty/component.html.erb +10 -0
  127. data/app/components/poetry/ui/empty/component.rb +79 -0
  128. data/app/components/poetry/ui/empty/preview.rb +27 -0
  129. data/app/components/poetry/ui/empty/style.rb +27 -0
  130. data/app/components/poetry/ui/family_identity.rb +52 -0
  131. data/app/components/poetry/ui/field/component.html.erb +19 -0
  132. data/app/components/poetry/ui/field/component.rb +183 -0
  133. data/app/components/poetry/ui/field/preview.html.erb +30 -0
  134. data/app/components/poetry/ui/field/preview.rb +72 -0
  135. data/app/components/poetry/ui/field/style.rb +68 -0
  136. data/app/components/poetry/ui/field_group/component.rb +61 -0
  137. data/app/components/poetry/ui/field_group/preview.html.erb +30 -0
  138. data/app/components/poetry/ui/field_group/preview.rb +20 -0
  139. data/app/components/poetry/ui/field_group/style.rb +25 -0
  140. data/app/components/poetry/ui/field_separator/component.rb +70 -0
  141. data/app/components/poetry/ui/field_separator/preview.rb +18 -0
  142. data/app/components/poetry/ui/field_separator/style.rb +20 -0
  143. data/app/components/poetry/ui/fieldset/component.rb +90 -0
  144. data/app/components/poetry/ui/fieldset/preview.html.erb +22 -0
  145. data/app/components/poetry/ui/fieldset/preview.rb +25 -0
  146. data/app/components/poetry/ui/fieldset/style.rb +18 -0
  147. data/app/components/poetry/ui/file_input/component.html.erb +14 -0
  148. data/app/components/poetry/ui/file_input/component.rb +188 -0
  149. data/app/components/poetry/ui/file_input/preview.rb +30 -0
  150. data/app/components/poetry/ui/file_input/style.rb +35 -0
  151. data/app/components/poetry/ui/hover_card/component.html.erb +6 -0
  152. data/app/components/poetry/ui/hover_card/component.rb +201 -0
  153. data/app/components/poetry/ui/hover_card/preview.rb +60 -0
  154. data/app/components/poetry/ui/hover_card/style.rb +17 -0
  155. data/app/components/poetry/ui/icon/component.rb +127 -0
  156. data/app/components/poetry/ui/icon/preview.rb +21 -0
  157. data/app/components/poetry/ui/input/component.rb +80 -0
  158. data/app/components/poetry/ui/input/preview.rb +35 -0
  159. data/app/components/poetry/ui/input/style.rb +18 -0
  160. data/app/components/poetry/ui/input_group/button_preview.html.erb +12 -0
  161. data/app/components/poetry/ui/input_group/component.rb +69 -0
  162. data/app/components/poetry/ui/input_group/preview.rb +23 -0
  163. data/app/components/poetry/ui/input_group/search_preview.html.erb +13 -0
  164. data/app/components/poetry/ui/input_group/style.rb +40 -0
  165. data/app/components/poetry/ui/input_group/textarea_preview.html.erb +10 -0
  166. data/app/components/poetry/ui/input_group_field.rb +61 -0
  167. data/app/components/poetry/ui/input_otp/component.html.erb +25 -0
  168. data/app/components/poetry/ui/input_otp/component.rb +250 -0
  169. data/app/components/poetry/ui/input_otp/preview.rb +91 -0
  170. data/app/components/poetry/ui/input_otp/style.rb +36 -0
  171. data/app/components/poetry/ui/item/component.html.erb +13 -0
  172. data/app/components/poetry/ui/item/component.rb +99 -0
  173. data/app/components/poetry/ui/item/group_preview.html.erb +15 -0
  174. data/app/components/poetry/ui/item/preview.rb +52 -0
  175. data/app/components/poetry/ui/item/style.rb +45 -0
  176. data/app/components/poetry/ui/kbd/component.rb +47 -0
  177. data/app/components/poetry/ui/kbd/group_preview.html.erb +4 -0
  178. data/app/components/poetry/ui/kbd/preview.rb +23 -0
  179. data/app/components/poetry/ui/kbd/style.rb +13 -0
  180. data/app/components/poetry/ui/label/component.rb +46 -0
  181. data/app/components/poetry/ui/label/preview.html.erb +5 -0
  182. data/app/components/poetry/ui/label/preview.rb +16 -0
  183. data/app/components/poetry/ui/label/style.rb +13 -0
  184. data/app/components/poetry/ui/link/component.rb +62 -0
  185. data/app/components/poetry/ui/link/preview.rb +31 -0
  186. data/app/components/poetry/ui/link/style.rb +20 -0
  187. data/app/components/poetry/ui/marker/component.html.erb +4 -0
  188. data/app/components/poetry/ui/marker/component.rb +81 -0
  189. data/app/components/poetry/ui/marker/preview.rb +43 -0
  190. data/app/components/poetry/ui/marker/style.rb +24 -0
  191. data/app/components/poetry/ui/menubar/component.html.erb +3 -0
  192. data/app/components/poetry/ui/menubar/component.rb +493 -0
  193. data/app/components/poetry/ui/menubar/preview.rb +164 -0
  194. data/app/components/poetry/ui/menubar/style.rb +71 -0
  195. data/app/components/poetry/ui/menus.rb +386 -0
  196. data/app/components/poetry/ui/message/component.html.erb +8 -0
  197. data/app/components/poetry/ui/message/component.rb +64 -0
  198. data/app/components/poetry/ui/message/preview.rb +33 -0
  199. data/app/components/poetry/ui/message/style.rb +29 -0
  200. data/app/components/poetry/ui/message_scroller/component.html.erb +15 -0
  201. data/app/components/poetry/ui/message_scroller/component.rb +163 -0
  202. data/app/components/poetry/ui/message_scroller/preview.rb +33 -0
  203. data/app/components/poetry/ui/message_scroller/style.rb +39 -0
  204. data/app/components/poetry/ui/metadata_list/component.rb +88 -0
  205. data/app/components/poetry/ui/metadata_list/preview.rb +55 -0
  206. data/app/components/poetry/ui/metadata_list/style.rb +35 -0
  207. data/app/components/poetry/ui/meter/component.rb +117 -0
  208. data/app/components/poetry/ui/meter/preview.rb +28 -0
  209. data/app/components/poetry/ui/meter/style.rb +16 -0
  210. data/app/components/poetry/ui/native_select/component.rb +124 -0
  211. data/app/components/poetry/ui/native_select/labelled_preview.html.erb +7 -0
  212. data/app/components/poetry/ui/native_select/preview.rb +20 -0
  213. data/app/components/poetry/ui/native_select/style.rb +22 -0
  214. data/app/components/poetry/ui/navigation_menu/bar_preview.html.erb +14 -0
  215. data/app/components/poetry/ui/navigation_menu/component.html.erb +25 -0
  216. data/app/components/poetry/ui/navigation_menu/component.rb +254 -0
  217. data/app/components/poetry/ui/navigation_menu/preview.rb +20 -0
  218. data/app/components/poetry/ui/navigation_menu/style.rb +73 -0
  219. data/app/components/poetry/ui/navigation_menu/viewport_preview.html.erb +23 -0
  220. data/app/components/poetry/ui/number_field/component.html.erb +19 -0
  221. data/app/components/poetry/ui/number_field/component.rb +263 -0
  222. data/app/components/poetry/ui/number_field/preview.rb +66 -0
  223. data/app/components/poetry/ui/number_field/style.rb +14 -0
  224. data/app/components/poetry/ui/pagination/component.html.erb +40 -0
  225. data/app/components/poetry/ui/pagination/component.rb +182 -0
  226. data/app/components/poetry/ui/pagination/preview.rb +47 -0
  227. data/app/components/poetry/ui/pagination/style.rb +27 -0
  228. data/app/components/poetry/ui/popover/component.html.erb +17 -0
  229. data/app/components/poetry/ui/popover/component.rb +203 -0
  230. data/app/components/poetry/ui/popover/preview.html.erb +29 -0
  231. data/app/components/poetry/ui/popover/preview.rb +66 -0
  232. data/app/components/poetry/ui/popover/style.rb +26 -0
  233. data/app/components/poetry/ui/popper_consumer.rb +69 -0
  234. data/app/components/poetry/ui/progress/component.rb +95 -0
  235. data/app/components/poetry/ui/progress/preview.rb +22 -0
  236. data/app/components/poetry/ui/progress/style.rb +19 -0
  237. data/app/components/poetry/ui/questionnaire/component.html.erb +80 -0
  238. data/app/components/poetry/ui/questionnaire/component.rb +396 -0
  239. data/app/components/poetry/ui/questionnaire/preview.rb +99 -0
  240. data/app/components/poetry/ui/questionnaire/style.rb +53 -0
  241. data/app/components/poetry/ui/radio_group/component.html.erb +4 -0
  242. data/app/components/poetry/ui/radio_group/component.rb +323 -0
  243. data/app/components/poetry/ui/radio_group/preview.rb +109 -0
  244. data/app/components/poetry/ui/radio_group/style.rb +53 -0
  245. data/app/components/poetry/ui/resizable/component.html.erb +10 -0
  246. data/app/components/poetry/ui/resizable/component.rb +178 -0
  247. data/app/components/poetry/ui/resizable/preview.rb +42 -0
  248. data/app/components/poetry/ui/resizable/style.rb +30 -0
  249. data/app/components/poetry/ui/scroll_area/component.rb +76 -0
  250. data/app/components/poetry/ui/scroll_area/preview.rb +35 -0
  251. data/app/components/poetry/ui/scroll_area/style.rb +19 -0
  252. data/app/components/poetry/ui/search_field/component.html.erb +15 -0
  253. data/app/components/poetry/ui/search_field/component.rb +137 -0
  254. data/app/components/poetry/ui/search_field/preview.rb +23 -0
  255. data/app/components/poetry/ui/search_field/style.rb +18 -0
  256. data/app/components/poetry/ui/select/component.html.erb +15 -0
  257. data/app/components/poetry/ui/select/component.rb +639 -0
  258. data/app/components/poetry/ui/select/preview.rb +113 -0
  259. data/app/components/poetry/ui/select/style.rb +58 -0
  260. data/app/components/poetry/ui/sensitive_input/component.html.erb +29 -0
  261. data/app/components/poetry/ui/sensitive_input/component.rb +274 -0
  262. data/app/components/poetry/ui/sensitive_input/preview.rb +40 -0
  263. data/app/components/poetry/ui/sensitive_input/style.rb +49 -0
  264. data/app/components/poetry/ui/separator/component.rb +57 -0
  265. data/app/components/poetry/ui/separator/preview.rb +19 -0
  266. data/app/components/poetry/ui/separator/style.rb +15 -0
  267. data/app/components/poetry/ui/sheet/component.html.erb +21 -0
  268. data/app/components/poetry/ui/sheet/component.rb +97 -0
  269. data/app/components/poetry/ui/sheet/preview.rb +57 -0
  270. data/app/components/poetry/ui/sheet/style.rb +47 -0
  271. data/app/components/poetry/ui/sidebar/component.html.erb +20 -0
  272. data/app/components/poetry/ui/sidebar/component.rb +249 -0
  273. data/app/components/poetry/ui/sidebar/preview.rb +21 -0
  274. data/app/components/poetry/ui/sidebar/shell_preview.html.erb +47 -0
  275. data/app/components/poetry/ui/sidebar/style.rb +139 -0
  276. data/app/components/poetry/ui/skeleton/component.rb +39 -0
  277. data/app/components/poetry/ui/skeleton/preview.rb +22 -0
  278. data/app/components/poetry/ui/skeleton/style.rb +13 -0
  279. data/app/components/poetry/ui/slider/component.html.erb +13 -0
  280. data/app/components/poetry/ui/slider/component.rb +360 -0
  281. data/app/components/poetry/ui/slider/preview.rb +73 -0
  282. data/app/components/poetry/ui/slider/style.rb +55 -0
  283. data/app/components/poetry/ui/spinner/component.rb +58 -0
  284. data/app/components/poetry/ui/spinner/preview.rb +22 -0
  285. data/app/components/poetry/ui/spinner/style.rb +12 -0
  286. data/app/components/poetry/ui/stat/component.html.erb +13 -0
  287. data/app/components/poetry/ui/stat/component.rb +102 -0
  288. data/app/components/poetry/ui/stat/preview.rb +47 -0
  289. data/app/components/poetry/ui/stat/style.rb +29 -0
  290. data/app/components/poetry/ui/switch/component.html.erb +15 -0
  291. data/app/components/poetry/ui/switch/component.rb +136 -0
  292. data/app/components/poetry/ui/switch/preview.rb +66 -0
  293. data/app/components/poetry/ui/switch/style.rb +22 -0
  294. data/app/components/poetry/ui/table/component.html.erb +3 -0
  295. data/app/components/poetry/ui/table/component.rb +93 -0
  296. data/app/components/poetry/ui/table/preview.rb +96 -0
  297. data/app/components/poetry/ui/table/style.rb +26 -0
  298. data/app/components/poetry/ui/tabs/component.html.erb +10 -0
  299. data/app/components/poetry/ui/tabs/component.rb +253 -0
  300. data/app/components/poetry/ui/tabs/preview.rb +43 -0
  301. data/app/components/poetry/ui/tabs/style.rb +49 -0
  302. data/app/components/poetry/ui/tag_group/component.html.erb +8 -0
  303. data/app/components/poetry/ui/tag_group/component.rb +203 -0
  304. data/app/components/poetry/ui/tag_group/preview.rb +43 -0
  305. data/app/components/poetry/ui/tag_group/style.rb +29 -0
  306. data/app/components/poetry/ui/textarea/component.rb +64 -0
  307. data/app/components/poetry/ui/textarea/preview.rb +75 -0
  308. data/app/components/poetry/ui/textarea/style.rb +18 -0
  309. data/app/components/poetry/ui/time_field/component.html.erb +6 -0
  310. data/app/components/poetry/ui/time_field/component.rb +85 -0
  311. data/app/components/poetry/ui/time_field/preview.rb +40 -0
  312. data/app/components/poetry/ui/time_field/style.rb +30 -0
  313. data/app/components/poetry/ui/timeline/component.rb +134 -0
  314. data/app/components/poetry/ui/timeline/preview.rb +61 -0
  315. data/app/components/poetry/ui/timeline/style.rb +54 -0
  316. data/app/components/poetry/ui/toast/component.html.erb +24 -0
  317. data/app/components/poetry/ui/toast/component.rb +178 -0
  318. data/app/components/poetry/ui/toast/preview.rb +69 -0
  319. data/app/components/poetry/ui/toast/style.rb +33 -0
  320. data/app/components/poetry/ui/toast_trigger/addressed_preview.html.erb +12 -0
  321. data/app/components/poetry/ui/toast_trigger/component.html.erb +3 -0
  322. data/app/components/poetry/ui/toast_trigger/component.rb +82 -0
  323. data/app/components/poetry/ui/toast_trigger/default_preview.html.erb +11 -0
  324. data/app/components/poetry/ui/toast_trigger/preview.rb +19 -0
  325. data/app/components/poetry/ui/toaster/component.html.erb +1 -0
  326. data/app/components/poetry/ui/toaster/component.rb +99 -0
  327. data/app/components/poetry/ui/toaster/positions_preview.html.erb +24 -0
  328. data/app/components/poetry/ui/toaster/preview.rb +46 -0
  329. data/app/components/poetry/ui/toaster/stamp_preview.html.erb +15 -0
  330. data/app/components/poetry/ui/toaster/style.rb +26 -0
  331. data/app/components/poetry/ui/toggle/component.rb +120 -0
  332. data/app/components/poetry/ui/toggle/preview.rb +79 -0
  333. data/app/components/poetry/ui/toggle/style.rb +33 -0
  334. data/app/components/poetry/ui/toggle_group/component.html.erb +4 -0
  335. data/app/components/poetry/ui/toggle_group/component.rb +252 -0
  336. data/app/components/poetry/ui/toggle_group/preview.rb +143 -0
  337. data/app/components/poetry/ui/toggle_group/style.rb +33 -0
  338. data/app/components/poetry/ui/toolbar/component.rb +125 -0
  339. data/app/components/poetry/ui/toolbar/preview.rb +41 -0
  340. data/app/components/poetry/ui/toolbar/style.rb +20 -0
  341. data/app/components/poetry/ui/tooltip/component.html.erb +20 -0
  342. data/app/components/poetry/ui/tooltip/component.rb +185 -0
  343. data/app/components/poetry/ui/tooltip/preview.rb +74 -0
  344. data/app/components/poetry/ui/tooltip/previews/provider_row.html.erb +25 -0
  345. data/app/components/poetry/ui/tooltip/style.rb +24 -0
  346. data/app/components/poetry/ui/tree/component.html.erb +32 -0
  347. data/app/components/poetry/ui/tree/component.rb +233 -0
  348. data/app/components/poetry/ui/tree/preview.rb +55 -0
  349. data/app/components/poetry/ui/tree/style.rb +29 -0
  350. data/app/components/poetry/ui/typeset/component.rb +73 -0
  351. data/app/components/poetry/ui/typeset/preview.rb +45 -0
  352. data/app/components/poetry/ui/typeset/style.rb +15 -0
  353. data/app/controllers/poetry/ui/llms_controller.rb +28 -0
  354. data/app/helpers/poetry/ui/components_helper.rb +1794 -0
  355. data/app/helpers/poetry/ui/form_builder/type_inference.rb +117 -0
  356. data/app/helpers/poetry/ui/form_builder.rb +916 -0
  357. data/app/helpers/poetry/ui/optimistic_form_builder.rb +55 -0
  358. data/app/helpers/poetry/ui/toast_stream_actions.rb +27 -0
  359. data/config/component_descriptions.yml +96 -0
  360. data/config/component_registry.yml +12257 -0
  361. data/config/locales/en.yml +93 -0
  362. data/config/routes.rb +8 -0
  363. data/config/template_classes.txt +113 -0
  364. data/lib/generators/poetry/add/add_generator.rb +218 -0
  365. data/lib/generators/poetry/agent_rules/agent_rules_generator.rb +91 -0
  366. data/lib/generators/poetry/agents/agents_generator.rb +24 -0
  367. data/lib/generators/poetry/agents_section.rb +148 -0
  368. data/lib/generators/poetry/block/block_generator.rb +67 -0
  369. data/lib/generators/poetry/block/templates/action_bar.html.erb +45 -0
  370. data/lib/generators/poetry/block/templates/app_shell.html.erb +139 -0
  371. data/lib/generators/poetry/block/templates/data_index.html.erb +85 -0
  372. data/lib/generators/poetry/block/templates/destructive_panel.html.erb +26 -0
  373. data/lib/generators/poetry/block/templates/page_header.html.erb +25 -0
  374. data/lib/generators/poetry/block/templates/section_card.html.erb +42 -0
  375. data/lib/generators/poetry/block/templates/stepper.html.erb +79 -0
  376. data/lib/generators/poetry/block/templates/top_nav.html.erb +44 -0
  377. data/lib/generators/poetry/diff/diff_generator.rb +135 -0
  378. data/lib/generators/poetry/editor/editor_generator.rb +344 -0
  379. data/lib/generators/poetry/editor/templates/poetry-check-hook +81 -0
  380. data/lib/generators/poetry/install/install_generator.rb +364 -0
  381. data/lib/generators/poetry/pagination/pagination_generator.rb +76 -0
  382. data/lib/generators/poetry/pagination/templates/kaminari_paginator.html.erb +29 -0
  383. data/lib/generators/poetry/pagination/templates/poetry_link_renderer.rb +34 -0
  384. data/lib/generators/poetry/pagination/templates/poetry_pagy_helper.rb +28 -0
  385. data/lib/generators/poetry/recipes/agent_embed/app/javascript/controllers/agent_demo_controller.js +131 -0
  386. data/lib/generators/poetry/recipes/screen_data_index/app/controllers/orders_controller.rb +15 -0
  387. data/lib/generators/poetry/recipes/screen_data_index/app/views/orders/index.html.erb +7 -0
  388. data/lib/generators/poetry/recipes/screen_data_index/test/system/orders_index_test.rb +16 -0
  389. data/lib/generators/poetry/recipes/screen_settings/app/controllers/settings_controller.rb +14 -0
  390. data/lib/generators/poetry/recipes/screen_settings/app/views/settings/show.html.erb +9 -0
  391. data/lib/generators/poetry/recipes/screen_settings/test/system/settings_show_test.rb +16 -0
  392. data/lib/generators/poetry/scaffold_templates/scaffold_templates_generator.rb +60 -0
  393. data/lib/generators/poetry/scaffold_templates/templates/_form.html.erb.tt +77 -0
  394. data/lib/generators/poetry/scaffold_templates/templates/controller.rb.tt +78 -0
  395. data/lib/generators/poetry/scaffold_templates/templates/edit.html.erb.tt +14 -0
  396. data/lib/generators/poetry/scaffold_templates/templates/index.html.erb.tt +24 -0
  397. data/lib/generators/poetry/scaffold_templates/templates/new.html.erb.tt +11 -0
  398. data/lib/generators/poetry/scaffold_templates/templates/partial.html.erb.tt +13 -0
  399. data/lib/generators/poetry/scaffold_templates/templates/show.html.erb.tt +18 -0
  400. data/lib/generators/poetry/skill/skill_generator.rb +34 -0
  401. data/lib/generators/poetry/skill/templates/poetry-component/SKILL.md +58 -0
  402. data/lib/generators/poetry/skill/templates/poetry-component/references/anatomy.md +152 -0
  403. data/lib/generators/poetry/skill/templates/poetry-component/references/checklist.md +67 -0
  404. data/lib/generators/poetry/skill/templates/poetry-component/references/documentation.md +137 -0
  405. data/lib/generators/poetry/skill/templates/poetry-design/SKILL.md +56 -0
  406. data/lib/generators/poetry/skill/templates/poetry-design/references/audit.md +123 -0
  407. data/lib/generators/poetry/skill/templates/poetry-design/references/compose.md +73 -0
  408. data/lib/generators/poetry/skill/templates/poetry-design/references/figma.md +68 -0
  409. data/lib/generators/poetry/skill/templates/poetry-design/references/paper.md +56 -0
  410. data/lib/generators/poetry/skill/templates/poetry-design/references/study.md +47 -0
  411. data/lib/generators/poetry/skill/templates/poetry-design/references/theme.md +70 -0
  412. data/lib/generators/poetry/skills_section.rb +58 -0
  413. data/lib/poetry/ui/chat.rb +283 -0
  414. data/lib/poetry/ui/code_block_highlighter.rb +60 -0
  415. data/lib/poetry/ui/engine.rb +55 -0
  416. data/lib/poetry/ui/recipes.rb +157 -0
  417. data/lib/poetry/ui/testing/combobox.rb +167 -0
  418. data/lib/poetry/ui/testing/dialog.rb +73 -0
  419. data/lib/poetry/ui/testing/menu.rb +127 -0
  420. data/lib/poetry/ui/testing/registration.rb +58 -0
  421. data/lib/poetry/ui/testing/select.rb +147 -0
  422. data/lib/poetry/ui/testing/tester.rb +130 -0
  423. data/lib/poetry/ui/testing.rb +130 -0
  424. data/lib/poetry/ui/themes.rb +109 -0
  425. data/lib/poetry/ui/version.rb +8 -0
  426. data/lib/poetry/ui/webmcp.rb +91 -0
  427. data/lib/poetry/ui.rb +285 -0
  428. data/lib/poetry-ui.rb +5 -0
  429. data/lib/tasks/poetry/check.rake +73 -0
  430. data/lib/tasks/poetry/design.rake +293 -0
  431. data/lib/tasks/poetry/verify.rake +36 -0
  432. data/themes/default.css +1793 -0
  433. data/themes/luma.css +1764 -0
  434. data/themes/lyra.css +1768 -0
  435. data/themes/maia.css +1768 -0
  436. data/themes/mira.css +1760 -0
  437. data/themes/nova.css +1755 -0
  438. data/themes/rhea.css +1771 -0
  439. data/themes/sera.css +1757 -0
  440. data/themes/vega.css +1775 -0
  441. data/typeset/typeset.css +499 -0
  442. metadata +502 -0
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # The recipes channel: multi-file payloads served
6
+ # through the registry beside components and blocks. Every definition's
7
+ # files are LAZY callables over gem-shipped sources - the same files
8
+ # the generators install - so the projection cannot drift from the
9
+ # generator path. Names share the flat kebab namespace (RegistryIndex
10
+ # collision-checks recipes against components and blocks at first
11
+ # touch).
12
+ module Recipes
13
+ # Gem-relative path of the scaffold override template set.
14
+ SCAFFOLD_TEMPLATES = "lib/generators/poetry/scaffold_templates/templates"
15
+ # Gem-relative path holding the screen-slice and embed sources.
16
+ SCREEN_SOURCES = "lib/generators/poetry/recipes"
17
+
18
+ class << self
19
+ # Every recipe definition, in registry order: the agent embed, the
20
+ # three skill bundles, the scaffold-template set, and the screen
21
+ # slices.
22
+ #
23
+ # @return [Array<Hash>] name/title/description + lazy "files"
24
+ def definitions
25
+ [agent_embed, skill_poetry, skill_poetry_design, skill_poetry_component, scaffold_templates,
26
+ screen(
27
+ "screen-data-index", title: "Data index screen",
28
+ description: "The data-index block as a working feature slice: OrdersController, " \
29
+ "its index view rendering the block, and a system test. Add " \
30
+ "`resources :orders, only: :index` to routes.",
31
+ dependencies: ["data-index"]
32
+ ),
33
+ screen(
34
+ "screen-settings", title: "Settings screen",
35
+ description: "A settings page composed from the page-header, section-card, and " \
36
+ "destructive-panel blocks: SettingsController, the composed view, " \
37
+ "and a system test. Add `resource :settings, only: :show` to routes.",
38
+ dependencies: %w[page-header section-card destructive-panel]
39
+ )]
40
+ end
41
+
42
+ private
43
+
44
+ def skill_poetry
45
+ {
46
+ "name" => "skill-poetry",
47
+ "title" => "poetry skill bundle",
48
+ "description" => "The component-usage Claude Code skill (SKILL.md + per-family " \
49
+ "references), generated from the live registry - the same files " \
50
+ "`bin/rails g poetry:skill` installs.",
51
+ "files" => lambda {
52
+ Poetry::Ui.runtime_skill_files.map do |rel, content|
53
+ { "path" => rel, "target" => ".claude/skills/poetry/#{rel}", "content" => content }
54
+ end
55
+ }
56
+ }
57
+ end
58
+
59
+ def skill_poetry_design
60
+ {
61
+ "name" => "skill-poetry-design",
62
+ "title" => "poetry-design skill bundle",
63
+ "description" => "The design-taste Claude Code skill (theme / compose / audit / study " \
64
+ "references) - the same files `bin/rails g poetry:skill` installs.",
65
+ "files" => lambda {
66
+ require "generators/poetry/skills_section"
67
+ files = Object.new.extend(Poetry::Generators::SkillsSection).design_skill_files
68
+ files.map do |rel, content|
69
+ { "path" => rel, "target" => ".claude/skills/poetry-design/#{rel}", "content" => content }
70
+ end
71
+ }
72
+ }
73
+ end
74
+
75
+ def skill_poetry_component
76
+ {
77
+ "name" => "skill-poetry-component",
78
+ "title" => "poetry-component skill bundle",
79
+ "description" => "The component-authoring Claude Code skill (anatomy / documentation / " \
80
+ "audit references) - the same files `bin/rails g poetry:skill` installs.",
81
+ "files" => lambda {
82
+ require "generators/poetry/skills_section"
83
+ files = Object.new.extend(Poetry::Generators::SkillsSection).component_skill_files
84
+ files.map do |rel, content|
85
+ { "path" => rel, "target" => ".claude/skills/poetry-component/#{rel}", "content" => content }
86
+ end
87
+ }
88
+ }
89
+ end
90
+
91
+ # The scaffold override set: static .tt files whose targets mirror
92
+ # `poetry:scaffold_templates` exactly - Rails does the
93
+ # parameterization later, so the payload needs none.
94
+ def scaffold_templates
95
+ {
96
+ "name" => "scaffold-templates",
97
+ "title" => "poetry scaffold templates",
98
+ "description" => "The lib/templates override set that makes the STANDARD `rails g " \
99
+ "scaffold` emit poetry-composed views and a matching controller - " \
100
+ "the same files `bin/rails g poetry:scaffold_templates` copies.",
101
+ "files" => lambda {
102
+ base = Poetry::Ui.root.join(SCAFFOLD_TEMPLATES)
103
+ base.glob("*.tt").sort.map do |file|
104
+ name = file.basename.to_s
105
+ target = if name == "controller.rb.tt"
106
+ "lib/templates/rails/scaffold_controller/controller.rb.tt"
107
+ else
108
+ "lib/templates/erb/scaffold/#{name}"
109
+ end
110
+ { "path" => name, "target" => target, "content" => file.read }
111
+ end
112
+ }
113
+ }
114
+ end
115
+
116
+ # The in-page GUI agent loader: the Turbo-hardened page-agent
117
+ # embed as a copy-in Stimulus controller - CDN-pinned script,
118
+ # opt-in, BYO key, and the poetry operator ground rules embedded
119
+ # as fallback instructions.
120
+ def agent_embed
121
+ {
122
+ "name" => "agent-embed",
123
+ "title" => "In-page agent embed",
124
+ "description" => "An opt-in page-agent loader for a poetry app as a Stimulus " \
125
+ "controller: pinned CDN script (?autoInit=false), session-only " \
126
+ "BYO key, Turbo-hardened (remounts on turbo:load, idle-only " \
127
+ "rebuild, module-init catch-up), poetry operator instructions " \
128
+ "built in. Wire a form with targets model/baseUrl/apiKey/status.",
129
+ "files" => lambda {
130
+ base = Poetry::Ui.root.join(SCREEN_SOURCES, "agent_embed")
131
+ base.glob("**/*").select(&:file?).sort.map do |file|
132
+ rel = file.relative_path_from(base).to_s
133
+ { "path" => rel, "target" => rel, "content" => file.read }
134
+ end
135
+ }
136
+ }
137
+ end
138
+
139
+ def screen(name, title:, description:, dependencies:)
140
+ {
141
+ "name" => name,
142
+ "title" => title,
143
+ "description" => description,
144
+ "registry_dependencies" => dependencies,
145
+ "files" => lambda {
146
+ base = Poetry::Ui.root.join(SCREEN_SOURCES, name.tr("-", "_"))
147
+ base.glob("**/*").select(&:file?).sort.map do |file|
148
+ rel = file.relative_path_from(base).to_s
149
+ { "path" => rel, "target" => rel, "content" => file.read }
150
+ end
151
+ }
152
+ }
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Testing
6
+ # The Combobox interaction contract, both modes: open, FILTER by
7
+ # typing into the command input, commit an option; single-select
8
+ # closes on commit and the native select serializes, multiple keeps
9
+ # the popover open, grows chips, and serializes name[] through a
10
+ # native select-multiple.
11
+ #
12
+ # CONTENT RESOLVES THROUGH THE ID PAIR, document-wide: portal-on-open
13
+ # moves the open popup to body (docs/portal-on-open.md), so root
14
+ # scoping stops holding for the content and its items. The trigger
15
+ # (single) or the chips inline input (multiple) carries aria-controls
16
+ # naming the listbox; the content wrapper is its ancestor -
17
+ # id-anchored selectors keep Capybara's waiting semantics.
18
+ #
19
+ # @example Filtering, then committing
20
+ # combo = poetry_combobox("#assignee")
21
+ # combo.filter("Ada").select_option("Ada Lovelace")
22
+ # assert_equal "ada", combo.value
23
+ class Combobox < Tester
24
+ # Whether the popup is currently open.
25
+ #
26
+ # @return [Boolean]
27
+ def open?(wait: 0)
28
+ session.has_selector?("##{content_id}[data-open]", visible: :all, wait: wait)
29
+ rescue Capybara::ElementNotFound
30
+ false
31
+ end
32
+
33
+ # Opens the popup (no-op when already open). via: :keyboard focuses
34
+ # the trigger and presses ArrowDown; :mouse presses it.
35
+ #
36
+ # @return [Combobox] self
37
+ def open(via: :mouse)
38
+ return self if open?
39
+
40
+ case via
41
+ when :keyboard
42
+ focus(trigger)
43
+ keys(:down)
44
+ else
45
+ press(trigger)
46
+ end
47
+
48
+ session.assert_selector("##{content_id}[data-slot='combobox-content'][data-open][data-side]")
49
+ self
50
+ end
51
+
52
+ # Escape-closes the open popup and waits for the closed state.
53
+ #
54
+ # @return [Combobox] self
55
+ def close
56
+ keys(:escape) if open?
57
+ session.assert_selector("##{content_id}[data-closed]", visible: :all)
58
+ self
59
+ end
60
+
61
+ # Type into the filter input (opens first when closed). Single mode
62
+ # keeps the input inside the (portaled) popup; multiple keeps it
63
+ # inline in the chips field at home.
64
+ #
65
+ # @return [Combobox] self
66
+ def filter(query)
67
+ open
68
+ filter_input.set(query)
69
+ self
70
+ end
71
+
72
+ # Commit by exact visible text; single-select waits for the close,
73
+ # multiple leaves the popover up (assert on values/chips instead).
74
+ #
75
+ # @return [Combobox] self
76
+ def select_option(text, via: :mouse)
77
+ self.open(via: via)
78
+
79
+ if via == :keyboard
80
+ walk_highlight_to(text)
81
+ keys(:enter)
82
+ else
83
+ press(option(text))
84
+ end
85
+
86
+ self
87
+ end
88
+
89
+ # Single: the committed value. Multiple: the committed value list.
90
+ #
91
+ # @return [String, Array<String>]
92
+ def value
93
+ native = hidden_part("combobox-native")
94
+ native.multiple? ? native.value : native.value.to_s
95
+ end
96
+
97
+ # The visible chip texts (multiple mode).
98
+ #
99
+ # @return [Array<String>]
100
+ def chips
101
+ parts("combobox-chip").map(&:text)
102
+ end
103
+
104
+ # Remove a chip by its accessible text (multiple mode).
105
+ #
106
+ # @return [Combobox] self
107
+ def remove_chip(text)
108
+ press(root.find("[data-slot='combobox-chip']", text: text).find("button"))
109
+ self
110
+ end
111
+
112
+ private
113
+
114
+ # Bounded highlight walk: at most one pass over the items - a
115
+ # missing/mistyped label raises instead of arrowing forever.
116
+ def walk_highlight_to(text)
117
+ items = content.all("[data-slot='combobox-item']", visible: :all)
118
+
119
+ (items.size + 1).times do
120
+ return if highlighted_text == text
121
+
122
+ keys(:down)
123
+ end
124
+
125
+ raise Capybara::ElementNotFound,
126
+ "no option #{text.inspect} reached by ArrowDown - options: #{items.map(&:text).inspect}"
127
+ end
128
+
129
+ def trigger
130
+ part("combobox-trigger")
131
+ end
132
+
133
+ # The aria-controls anchor stays HOME in both modes: the trigger
134
+ # button (single) or the chips frame's inline input (multiple).
135
+ def list_id
136
+ @list_id ||= root.first(
137
+ "[data-slot='combobox-trigger'], [data-slot='combobox-chips'] [data-slot='combobox-chip-input']",
138
+ minimum: 1, visible: :all
139
+ )["aria-controls"]
140
+ end
141
+
142
+ def content_id
143
+ @content_id ||= session.find("##{list_id}", visible: :all)
144
+ .ancestor("[data-slot='combobox-content']", visible: :all)[:id]
145
+ end
146
+
147
+ def content
148
+ session.find("##{content_id}", visible: :all)
149
+ end
150
+
151
+ def filter_input
152
+ content.first("[data-slot='combobox-input']", minimum: 0, visible: :all) || part("combobox-input")
153
+ end
154
+
155
+ def option(text)
156
+ content.find("[data-slot='combobox-item']", text: text, exact_text: true)
157
+ end
158
+
159
+ def highlighted_text
160
+ content.find("[data-slot='combobox-item'][data-highlighted]", wait: 1).text
161
+ rescue Capybara::ElementNotFound
162
+ nil
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Testing
6
+ # The Dialog interaction contract: the trigger opens, focus is
7
+ # trapped inside, Escape (or the close affordance) dismisses, and
8
+ # focus RETURNS to the trigger. The root you hand this tester is the
9
+ # element carrying data-component=dialog (trigger + content).
10
+ #
11
+ # @example Opening, asserting, dismissing
12
+ # dialog = poetry_dialog("[data-component='dialog']")
13
+ # dialog.open(via: :keyboard)
14
+ # assert_equal "Are you sure?", dialog.title
15
+ # dialog.close
16
+ class Dialog < Tester
17
+ # Whether the dialog is currently open.
18
+ #
19
+ # @return [Boolean]
20
+ def open?(wait: 0)
21
+ part?("dialog-content", wait: wait) &&
22
+ !hidden_part("dialog-content")["data-open"].nil?
23
+ rescue Capybara::ElementNotFound
24
+ false
25
+ end
26
+
27
+ # Opens the dialog (no-op when already open). via: :keyboard
28
+ # focuses the trigger and presses Enter; :mouse presses it.
29
+ #
30
+ # @return [Dialog] self
31
+ def open(via: :mouse)
32
+ return self if open?
33
+
34
+ case via
35
+ when :keyboard
36
+ focus(trigger)
37
+ keys(:enter)
38
+ else
39
+ press(trigger)
40
+ end
41
+
42
+ root.assert_selector("[data-slot='dialog-content'][data-open]")
43
+ self
44
+ end
45
+
46
+ # Escape-closes the open dialog and waits for the closed state.
47
+ #
48
+ # @return [Dialog] self
49
+ def close
50
+ keys(:escape) if open?
51
+ root.assert_selector("[data-slot='dialog-content'][data-closed]", visible: :all)
52
+ self
53
+ end
54
+
55
+ # The dialog's accessible title text.
56
+ #
57
+ # @return [String]
58
+ def title
59
+ part("dialog-title").text
60
+ end
61
+
62
+ private
63
+
64
+ # The trigger has no slot of its own - it is the consumer's Button
65
+ # wired to the dialog controller's open action (the contract).
66
+ def trigger
67
+ root.find("[data-action*='#{Poetry::Ui::Dialog::Component.stimulus_action(:open)}']",
68
+ match: :first)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Testing
6
+ # The DropdownMenu interaction contract: trigger toggles, open lands
7
+ # focus in the menu, arrows move data-highlighted, Enter activates
8
+ # the highlighted item, Escape closes with focus returned to the
9
+ # trigger.
10
+ #
11
+ # CONTENT RESOLVES THROUGH THE ID PAIR, document-wide: portal-on-open
12
+ # moves the open menu to body (docs/portal-on-open.md), so root
13
+ # scoping stops holding for the content and its items - the trigger's
14
+ # aria-controls id is the production controllers' own resolution
15
+ # rule, and id-anchored selectors keep Capybara's waiting semantics.
16
+ #
17
+ # @example Activating a menu item
18
+ # menu = poetry_dropdown_menu("#row-actions")
19
+ # menu.choose("Archive", via: :keyboard)
20
+ class Menu < Tester
21
+ # Whether the menu is currently open.
22
+ #
23
+ # @return [Boolean]
24
+ def open?(wait: 0)
25
+ session.has_selector?("##{content_id}[data-open]", visible: :all, wait: wait)
26
+ rescue Capybara::ElementNotFound
27
+ false
28
+ end
29
+
30
+ # Opens the menu (no-op when already open). via: :keyboard focuses
31
+ # the trigger and presses ArrowDown; :mouse presses it.
32
+ #
33
+ # @return [Menu] self
34
+ def open(via: :mouse)
35
+ return self if open?
36
+
37
+ case via
38
+ when :keyboard
39
+ focus(trigger)
40
+ keys(:down)
41
+ else
42
+ press(trigger)
43
+ end
44
+
45
+ session.assert_selector("##{content_id}[data-slot='dropdown-menu-content'][data-open][data-side]")
46
+ self
47
+ end
48
+
49
+ # Escape-closes the open menu and waits for the closed state.
50
+ #
51
+ # @return [Menu] self
52
+ def close
53
+ keys(:escape) if open?
54
+ session.assert_selector("##{content_id}[data-closed]", visible: :all)
55
+ self
56
+ end
57
+
58
+ # Opens first when closed; activates by exact visible text.
59
+ #
60
+ # @return [Menu] self
61
+ def choose(text, via: :mouse)
62
+ self.open(via: via)
63
+
64
+ if via == :keyboard
65
+ walk_highlight_to(text)
66
+ keys(:enter)
67
+ else
68
+ press(item(text))
69
+ end
70
+
71
+ self
72
+ end
73
+
74
+ # The visible item texts (opens the menu first when closed).
75
+ #
76
+ # @return [Array<String>]
77
+ def items
78
+ open unless open?
79
+ content.all("[data-slot='dropdown-menu-item']").map(&:text)
80
+ end
81
+
82
+ private
83
+
84
+ # Bounded highlight walk: at most one pass over the items - a
85
+ # missing/mistyped label raises instead of arrowing forever.
86
+ def walk_highlight_to(text)
87
+ list = content.all("[data-slot='dropdown-menu-item']")
88
+
89
+ (list.size + 1).times do
90
+ return if highlighted_text == text
91
+
92
+ keys(:down)
93
+ end
94
+
95
+ raise Capybara::ElementNotFound,
96
+ "no option #{text.inspect} reached by ArrowDown - " \
97
+ "options: #{list.map(&:text).inspect}"
98
+ end
99
+
100
+ def trigger
101
+ part("dropdown-menu-trigger")
102
+ end
103
+
104
+ # The trigger's aria-controls names the menu wherever it sits.
105
+ def content_id
106
+ @content_id ||= trigger["aria-controls"]
107
+ end
108
+
109
+ def content
110
+ session.find("##{content_id}", visible: :all)
111
+ end
112
+
113
+ # Substring match, not exact: menu items legitimately carry more
114
+ # than their label (shortcut glyphs, badges).
115
+ def item(text)
116
+ content.find("[data-slot='dropdown-menu-item']", text: text, match: :first)
117
+ end
118
+
119
+ def highlighted_text
120
+ content.find("[data-slot='dropdown-menu-item'][data-highlighted]", wait: 1).text
121
+ rescue Capybara::ElementNotFound
122
+ nil
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Testing
6
+ # Raised by assert_poetry_controllers_registered outside Minitest.
7
+ class RegistrationError < StandardError; end
8
+
9
+ # The page-wide registration guard behind
10
+ # {Testing#poetry_unregistered_controllers}: every `data-controller`
11
+ # identifier with poetry's prefix must be registered on the host's
12
+ # Stimulus application, or the element is silently inert - Stimulus
13
+ # never errors on an unknown identifier, and one failed import in
14
+ # the controllers graph takes every poetry controller down with it.
15
+ #
16
+ # @api private
17
+ module Registration
18
+ PREFIX = "poetry--"
19
+
20
+ # The JS run in the page: `application` is an expression naming
21
+ # the Stimulus application (window.Stimulus in Rails' default
22
+ # controllers/application.js).
23
+ #
24
+ # @return [String]
25
+ def self.script(application)
26
+ <<~JS
27
+ (() => {
28
+ const application = #{application};
29
+ if (!application) return { error: "no Stimulus application at #{application} - the controllers module never ran (one failed import kills the whole graph)" };
30
+ const registry = application.router && application.router.modulesByIdentifier;
31
+ const registered = registry ? Array.from(registry.keys()) : (application.controllers || []).map((c) => c.identifier);
32
+ const known = new Set(registered);
33
+ const missing = new Set();
34
+ for (const element of document.querySelectorAll("[data-controller]")) {
35
+ for (const identifier of element.getAttribute("data-controller").split(/\\s+/)) {
36
+ if (identifier.startsWith(#{PREFIX.inspect}) && !known.has(identifier)) missing.add(identifier);
37
+ }
38
+ }
39
+ return { missing: Array.from(missing).sort(), registered: registered.filter((id) => id.startsWith(#{PREFIX.inspect})).length };
40
+ })()
41
+ JS
42
+ end
43
+
44
+ # The failure text: the identifiers, and the two causes worth
45
+ # checking first.
46
+ #
47
+ # @return [String]
48
+ def self.message(result)
49
+ return result["error"] if result["error"]
50
+
51
+ "#{result["missing"].size} poetry controller(s) on the page are not registered on the Stimulus " \
52
+ "application: #{result["missing"].join(", ")} (#{result["registered"]} poetry controllers are). " \
53
+ "Check the importmap pins and that every poetry gem's register call runs in controllers/index.js."
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end